diff --git a/lib/client/plex_client.dart b/lib/client/plex_client.dart index 45552c39..bafa1672 100644 --- a/lib/client/plex_client.dart +++ b/lib/client/plex_client.dart @@ -1102,92 +1102,6 @@ class PlexClient { } } - /// Find adjacent episode in a given direction - /// - /// [direction]: +1 for next episode, -1 for previous episode - /// - /// Handles navigation within current season and across seasons automatically. - Future findAdjacentEpisode( - PlexMetadata currentEpisode, - int direction, - ) async { - if (currentEpisode.type.toLowerCase() != 'episode') { - return null; - } - - final parentKey = currentEpisode.parentRatingKey; - final grandparentKey = currentEpisode.grandparentRatingKey; - - if (parentKey == null || grandparentKey == null) { - return null; - } - - // Extract serverId/serverName from currentEpisode to propagate - final serverId = currentEpisode.serverId; - final serverName = currentEpisode.serverName; - - try { - // Get all episodes in the current season - final episodes = await getChildren(parentKey); - - // Find the current episode index - final currentIndex = episodes.indexWhere( - (e) => e.ratingKey == currentEpisode.ratingKey, - ); - - if (currentIndex == -1) return null; - - final targetIndex = currentIndex + direction; - - // Check if target episode is within current season - if (targetIndex >= 0 && targetIndex < episodes.length) { - return episodes[targetIndex].copyWith( - serverId: serverId, - serverName: serverName, - ); - } - - // Need to move to adjacent season - final isAtBoundary = direction > 0 - ? currentIndex == episodes.length - 1 - : currentIndex == 0; - - if (isAtBoundary) { - // Get all seasons - final seasons = await getChildren(grandparentKey); - final currentSeasonIndex = seasons.indexWhere( - (s) => s.ratingKey == parentKey, - ); - - if (currentSeasonIndex == -1) return null; - - final targetSeasonIndex = currentSeasonIndex + direction; - - // Check if target season exists - if (targetSeasonIndex >= 0 && targetSeasonIndex < seasons.length) { - final targetSeason = seasons[targetSeasonIndex]; - final targetSeasonEpisodes = await getChildren( - targetSeason.ratingKey, - ); - - if (targetSeasonEpisodes.isNotEmpty) { - // Return first episode for next season, last for previous - return (direction > 0 - ? targetSeasonEpisodes.first - : targetSeasonEpisodes.last).copyWith( - serverId: serverId, - serverName: serverName, - ); - } - } - } - } catch (e) { - // Silently handle errors - } - - return null; - } - /// Get library hubs (recommendations for a specific library section) /// Returns a list of recommendation hubs like "Trending Movies", "Top in Genre", etc. Future> getLibraryHubs( @@ -1736,6 +1650,49 @@ class PlexClient { } } + /// Create a play queue for a TV show (all episodes) + /// + /// This is a convenience method that creates a play queue from a show's URI. + /// Perfect for sequential or shuffle playback of an entire series. + /// + /// Parameters: + /// - [showRatingKey]: The rating key of the show + /// - [shuffle]: Whether to shuffle the episodes (0 = off, 1 = on) + /// - [startingEpisodeKey]: Optional rating key of episode to start from + /// + /// Returns a PlayQueueResponse with all episodes from the show + Future createShowPlayQueue({ + required String showRatingKey, + int shuffle = 0, + String? startingEpisodeKey, + }) async { + try { + // Get machine identifier for building the URI + final machineId = + config.machineIdentifier ?? await getMachineIdentifier(); + if (machineId == null) { + throw Exception('Could not get server machine identifier'); + } + + // Build the URI for the show's episodes + final uri = + 'server://$machineId/com.plexapp.plugins.library/library/metadata/$showRatingKey/children'; + + // Create the play queue with optional starting episode + return await createPlayQueue( + uri: uri, + type: 'video', + shuffle: shuffle, + key: startingEpisodeKey != null + ? '/library/metadata/$startingEpisodeKey' + : null, + ); + } catch (e) { + appLogger.e('Failed to create show play queue', error: e); + return null; + } + } + /// Extract both Metadata and Directory entries from response /// Folders can come back as either type List _extractMetadataAndDirectories(Response response) { diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 46f40a82..53d0fb03 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -25,22 +25,32 @@ const AppLocale _baseLocale = AppLocale.en; /// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum /// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check enum AppLocale with BaseAppLocale { - en(languageCode: 'en', build: Translations.build), - de(languageCode: 'de', build: _StringsDe.build), - it(languageCode: 'it', build: _StringsIt.build), - nl(languageCode: 'nl', build: _StringsNl.build), - sv(languageCode: 'sv', build: _StringsSv.build), - zh(languageCode: 'zh', build: _StringsZh.build); + en(languageCode: 'en', build: Translations.build), + de(languageCode: 'de', build: _StringsDe.build), + it(languageCode: 'it', build: _StringsIt.build), + nl(languageCode: 'nl', build: _StringsNl.build), + sv(languageCode: 'sv', build: _StringsSv.build), + zh(languageCode: 'zh', build: _StringsZh.build); - const AppLocale({required this.languageCode, this.scriptCode, this.countryCode, required this.build}); // ignore: unused_element + const AppLocale({ + required this.languageCode, + this.scriptCode, + this.countryCode, + required this.build, + }); // ignore: unused_element - @override final String languageCode; - @override final String? scriptCode; - @override final String? countryCode; - @override final TranslationBuilder build; + @override + final String languageCode; + @override + final String? scriptCode; + @override + final String? countryCode; + @override + final TranslationBuilder build; - /// Gets current instance managed by [LocaleSettings]. - Translations get translations => LocaleSettings.instance.translationMap[this]!; + /// Gets current instance managed by [LocaleSettings]. + Translations get translations => + LocaleSettings.instance.translationMap[this]!; } /// Method A: Simple @@ -69,10 +79,14 @@ Translations get t => LocaleSettings.instance.currentTranslations; /// final t = Translations.of(context); // Get t variable. /// String a = t.someKey.anotherKey; // Use t variable. /// String b = t['someKey.anotherKey']; // Only for edge cases! -class TranslationProvider extends BaseTranslationProvider { - TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance); +class TranslationProvider + extends BaseTranslationProvider { + TranslationProvider({required super.child}) + : super(settings: LocaleSettings.instance); - static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(context); + static InheritedLocaleData of( + BuildContext context, + ) => InheritedLocaleData.of(context); } /// Method B shorthand via [BuildContext] extension method. @@ -81,7500 +95,13522 @@ class TranslationProvider extends BaseTranslationProvider TranslationProvider.of(this).translations; + Translations get t => TranslationProvider.of(this).translations; } /// Manages all translation instances and the current locale -class LocaleSettings extends BaseFlutterLocaleSettings { - LocaleSettings._() : super(utils: AppLocaleUtils.instance); +class LocaleSettings + extends BaseFlutterLocaleSettings { + LocaleSettings._() : super(utils: AppLocaleUtils.instance); - static final instance = LocaleSettings._(); + static final instance = LocaleSettings._(); - // static aliases (checkout base methods for documentation) - static AppLocale get currentLocale => instance.currentLocale; - static Stream getLocaleStream() => instance.getLocaleStream(); - static AppLocale setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale); - static AppLocale setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale); - static AppLocale useDeviceLocale() => instance.useDeviceLocale(); - @Deprecated('Use [AppLocaleUtils.supportedLocales]') static List get supportedLocales => instance.supportedLocales; - @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') static List get supportedLocalesRaw => instance.supportedLocalesRaw; - static void setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver( - language: language, - locale: locale, - cardinalResolver: cardinalResolver, - ordinalResolver: ordinalResolver, - ); + // static aliases (checkout base methods for documentation) + static AppLocale get currentLocale => instance.currentLocale; + static Stream getLocaleStream() => instance.getLocaleStream(); + static AppLocale setLocale( + AppLocale locale, { + bool? listenToDeviceLocale = false, + }) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale setLocaleRaw( + String rawLocale, { + bool? listenToDeviceLocale = false, + }) => instance.setLocaleRaw( + rawLocale, + listenToDeviceLocale: listenToDeviceLocale, + ); + static AppLocale useDeviceLocale() => instance.useDeviceLocale(); + @Deprecated('Use [AppLocaleUtils.supportedLocales]') + static List get supportedLocales => instance.supportedLocales; + @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') + static List get supportedLocalesRaw => instance.supportedLocalesRaw; + static void setPluralResolver({ + String? language, + AppLocale? locale, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) => instance.setPluralResolver( + language: language, + locale: locale, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); } /// Provides utility functions without any side effects. class AppLocaleUtils extends BaseAppLocaleUtils { - AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values); + AppLocaleUtils._() + : super(baseLocale: _baseLocale, locales: AppLocale.values); - static final instance = AppLocaleUtils._(); + static final instance = AppLocaleUtils._(); - // static aliases (checkout base methods for documentation) - static AppLocale parse(String rawLocale) => instance.parse(rawLocale); - static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode); - static AppLocale findDeviceLocale() => instance.findDeviceLocale(); - static List get supportedLocales => instance.supportedLocales; - static List get supportedLocalesRaw => instance.supportedLocalesRaw; + // static aliases (checkout base methods for documentation) + static AppLocale parse(String rawLocale) => instance.parse(rawLocale); + static AppLocale parseLocaleParts({ + required String languageCode, + String? scriptCode, + String? countryCode, + }) => instance.parseLocaleParts( + languageCode: languageCode, + scriptCode: scriptCode, + countryCode: countryCode, + ); + static AppLocale findDeviceLocale() => instance.findDeviceLocale(); + static List get supportedLocales => instance.supportedLocales; + static List get supportedLocalesRaw => instance.supportedLocalesRaw; } // translations // Path: class Translations implements BaseTranslations { - /// Returns the current translations of the given [context]. - /// - /// Usage: - /// final t = Translations.of(context); - static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations; + /// Returns the current translations of the given [context]. + /// + /// Usage: + /// final t = Translations.of(context); + static Translations of(BuildContext context) => + InheritedLocaleData.of(context).translations; - /// You can call this constructor and build your own translation instance of this locale. - /// Constructing via the enum [AppLocale.build] is preferred. - Translations.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) - : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), - $meta = TranslationMetadata( - locale: AppLocale.en, - overrides: overrides ?? {}, - cardinalResolver: cardinalResolver, - ordinalResolver: ordinalResolver, - ) { - $meta.setFlatMapFunction(_flatMapFunction); - } + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + Translations.build({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) : assert( + overrides == null, + 'Set "translation_overrides: true" in order to enable this feature.', + ), + $meta = TranslationMetadata( + locale: AppLocale.en, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } - /// Metadata for the translations of . - @override final TranslationMetadata $meta; + /// Metadata for the translations of . + @override + final TranslationMetadata $meta; - /// Access flat map - dynamic operator[](String key) => $meta.getTranslation(key); + /// Access flat map + dynamic operator [](String key) => $meta.getTranslation(key); - late final Translations _root = this; // ignore: unused_field + late final Translations _root = this; // ignore: unused_field - // Translations - late final _StringsAppEn app = _StringsAppEn._(_root); - late final _StringsAuthEn auth = _StringsAuthEn._(_root); - late final _StringsCommonEn common = _StringsCommonEn._(_root); - late final _StringsScreensEn screens = _StringsScreensEn._(_root); - late final _StringsUpdateEn update = _StringsUpdateEn._(_root); - late final _StringsSettingsEn settings = _StringsSettingsEn._(_root); - late final _StringsSearchEn search = _StringsSearchEn._(_root); - late final _StringsHotkeysEn hotkeys = _StringsHotkeysEn._(_root); - late final _StringsPinEntryEn pinEntry = _StringsPinEntryEn._(_root); - late final _StringsFileInfoEn fileInfo = _StringsFileInfoEn._(_root); - late final _StringsMediaMenuEn mediaMenu = _StringsMediaMenuEn._(_root); - late final _StringsAccessibilityEn accessibility = _StringsAccessibilityEn._(_root); - late final _StringsTooltipsEn tooltips = _StringsTooltipsEn._(_root); - late final _StringsVideoControlsEn videoControls = _StringsVideoControlsEn._(_root); - late final _StringsUserStatusEn userStatus = _StringsUserStatusEn._(_root); - late final _StringsMessagesEn messages = _StringsMessagesEn._(_root); - late final _StringsProfileEn profile = _StringsProfileEn._(_root); - late final _StringsSubtitlingStylingEn subtitlingStyling = _StringsSubtitlingStylingEn._(_root); - late final _StringsDialogEn dialog = _StringsDialogEn._(_root); - late final _StringsDiscoverEn discover = _StringsDiscoverEn._(_root); - late final _StringsErrorsEn errors = _StringsErrorsEn._(_root); - late final _StringsLibrariesEn libraries = _StringsLibrariesEn._(_root); - late final _StringsAboutEn about = _StringsAboutEn._(_root); - late final _StringsServerSelectionEn serverSelection = _StringsServerSelectionEn._(_root); - late final _StringsHubDetailEn hubDetail = _StringsHubDetailEn._(_root); - late final _StringsLogsEn logs = _StringsLogsEn._(_root); - late final _StringsLicensesEn licenses = _StringsLicensesEn._(_root); - late final _StringsNavigationEn navigation = _StringsNavigationEn._(_root); - late final _StringsCollectionsEn collections = _StringsCollectionsEn._(_root); - late final _StringsPlaylistsEn playlists = _StringsPlaylistsEn._(_root); + // Translations + late final _StringsAppEn app = _StringsAppEn._(_root); + late final _StringsAuthEn auth = _StringsAuthEn._(_root); + late final _StringsCommonEn common = _StringsCommonEn._(_root); + late final _StringsScreensEn screens = _StringsScreensEn._(_root); + late final _StringsUpdateEn update = _StringsUpdateEn._(_root); + late final _StringsSettingsEn settings = _StringsSettingsEn._(_root); + late final _StringsSearchEn search = _StringsSearchEn._(_root); + late final _StringsHotkeysEn hotkeys = _StringsHotkeysEn._(_root); + late final _StringsPinEntryEn pinEntry = _StringsPinEntryEn._(_root); + late final _StringsFileInfoEn fileInfo = _StringsFileInfoEn._(_root); + late final _StringsMediaMenuEn mediaMenu = _StringsMediaMenuEn._(_root); + late final _StringsAccessibilityEn accessibility = _StringsAccessibilityEn._( + _root, + ); + late final _StringsTooltipsEn tooltips = _StringsTooltipsEn._(_root); + late final _StringsVideoControlsEn videoControls = _StringsVideoControlsEn._( + _root, + ); + late final _StringsUserStatusEn userStatus = _StringsUserStatusEn._(_root); + late final _StringsMessagesEn messages = _StringsMessagesEn._(_root); + late final _StringsProfileEn profile = _StringsProfileEn._(_root); + late final _StringsSubtitlingStylingEn subtitlingStyling = + _StringsSubtitlingStylingEn._(_root); + late final _StringsDialogEn dialog = _StringsDialogEn._(_root); + late final _StringsDiscoverEn discover = _StringsDiscoverEn._(_root); + late final _StringsErrorsEn errors = _StringsErrorsEn._(_root); + late final _StringsLibrariesEn libraries = _StringsLibrariesEn._(_root); + late final _StringsAboutEn about = _StringsAboutEn._(_root); + late final _StringsServerSelectionEn serverSelection = + _StringsServerSelectionEn._(_root); + late final _StringsHubDetailEn hubDetail = _StringsHubDetailEn._(_root); + late final _StringsLogsEn logs = _StringsLogsEn._(_root); + late final _StringsLicensesEn licenses = _StringsLicensesEn._(_root); + late final _StringsNavigationEn navigation = _StringsNavigationEn._(_root); + late final _StringsCollectionsEn collections = _StringsCollectionsEn._(_root); + late final _StringsPlaylistsEn playlists = _StringsPlaylistsEn._(_root); } // Path: app class _StringsAppEn { - _StringsAppEn._(this._root); + _StringsAppEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Plezy'; - String get loading => 'Loading...'; + // Translations + String get title => 'Plezy'; + String get loading => 'Loading...'; } // Path: auth class _StringsAuthEn { - _StringsAuthEn._(this._root); + _StringsAuthEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get signInWithPlex => 'Sign in with Plex'; - String get showQRCode => 'Show QR Code'; - String get cancel => 'Cancel'; - String get authenticate => 'Authenticate'; - String get retry => 'Retry'; - String get debugEnterToken => 'Debug: Enter Plex Token'; - String get plexTokenLabel => 'Plex Auth Token'; - String get plexTokenHint => 'Enter your Plex.tv token'; - String get authenticationTimeout => 'Authentication timed out. Please try again.'; - String get scanQRCodeInstruction => 'Scan this QR code with a device logged into Plex to authenticate.'; - String get waitingForAuth => 'Waiting for authentication...\nPlease complete sign-in in your browser.'; + // Translations + String get signInWithPlex => 'Sign in with Plex'; + String get showQRCode => 'Show QR Code'; + String get cancel => 'Cancel'; + String get authenticate => 'Authenticate'; + String get retry => 'Retry'; + String get debugEnterToken => 'Debug: Enter Plex Token'; + String get plexTokenLabel => 'Plex Auth Token'; + String get plexTokenHint => 'Enter your Plex.tv token'; + String get authenticationTimeout => + 'Authentication timed out. Please try again.'; + String get scanQRCodeInstruction => + 'Scan this QR code with a device logged into Plex to authenticate.'; + String get waitingForAuth => + 'Waiting for authentication...\nPlease complete sign-in in your browser.'; } // Path: common class _StringsCommonEn { - _StringsCommonEn._(this._root); + _StringsCommonEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get cancel => 'Cancel'; - String get save => 'Save'; - String get close => 'Close'; - String get clear => 'Clear'; - String get reset => 'Reset'; - String get later => 'Later'; - String get submit => 'Submit'; - String get confirm => 'Confirm'; - String get retry => 'Retry'; - String get playNow => 'Play Now'; - String get logout => 'Logout'; - String get online => 'Online'; - String get offline => 'Offline'; - String get owned => 'Owned'; - String get shared => 'Shared'; - String get current => 'CURRENT'; - String get unknown => 'Unknown'; - String get refresh => 'Refresh'; - String get yes => 'Yes'; - String get no => 'No'; - String get server => 'Server'; - String get delete => 'Delete'; - String get shuffle => 'Shuffle'; - String get addTo => 'Add to...'; + // Translations + String get cancel => 'Cancel'; + String get save => 'Save'; + String get close => 'Close'; + String get clear => 'Clear'; + String get reset => 'Reset'; + String get later => 'Later'; + String get submit => 'Submit'; + String get confirm => 'Confirm'; + String get retry => 'Retry'; + String get playNow => 'Play Now'; + String get logout => 'Logout'; + String get online => 'Online'; + String get offline => 'Offline'; + String get owned => 'Owned'; + String get shared => 'Shared'; + String get current => 'CURRENT'; + String get unknown => 'Unknown'; + String get refresh => 'Refresh'; + String get yes => 'Yes'; + String get no => 'No'; + String get server => 'Server'; + String get delete => 'Delete'; + String get shuffle => 'Shuffle'; + String get addTo => 'Add to...'; } // Path: screens class _StringsScreensEn { - _StringsScreensEn._(this._root); + _StringsScreensEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get licenses => 'Licenses'; - String get switchProfile => 'Switch Profile'; - String get subtitleStyling => 'Subtitle Styling'; - String get search => 'Search'; - String get logs => 'Logs'; + // Translations + String get licenses => 'Licenses'; + String get switchProfile => 'Switch Profile'; + String get subtitleStyling => 'Subtitle Styling'; + String get search => 'Search'; + String get logs => 'Logs'; } // Path: update class _StringsUpdateEn { - _StringsUpdateEn._(this._root); + _StringsUpdateEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get available => 'Update Available'; - String versionAvailable({required Object version}) => 'Version ${version} is available'; - String currentVersion({required Object version}) => 'Current: ${version}'; - String get skipVersion => 'Skip This Version'; - String get viewRelease => 'View Release'; - String get latestVersion => 'You are on the latest version'; - String get checkFailed => 'Failed to check for updates'; + // Translations + String get available => 'Update Available'; + String versionAvailable({required Object version}) => + 'Version ${version} is available'; + String currentVersion({required Object version}) => 'Current: ${version}'; + String get skipVersion => 'Skip This Version'; + String get viewRelease => 'View Release'; + String get latestVersion => 'You are on the latest version'; + String get checkFailed => 'Failed to check for updates'; } // Path: settings class _StringsSettingsEn { - _StringsSettingsEn._(this._root); + _StringsSettingsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Settings'; - String get language => 'Language'; - String get theme => 'Theme'; - String get appearance => 'Appearance'; - String get videoPlayback => 'Video Playback'; - String get shufflePlay => 'Shuffle Play'; - String get advanced => 'Advanced'; - String get useSeasonPostersDescription => 'Show season poster instead of series poster for episodes'; - String get showHeroSectionDescription => 'Display featured content carousel on home screen'; - String get secondsLabel => 'Seconds'; - String get minutesLabel => 'Minutes'; - String get secondsShort => 's'; - String get minutesShort => 'm'; - String durationHint({required Object min, required Object max}) => 'Enter duration (${min}-${max})'; - String get systemTheme => 'System'; - String get systemThemeDescription => 'Follow system settings'; - String get lightTheme => 'Light'; - String get darkTheme => 'Dark'; - String get libraryDensity => 'Library Density'; - String get compact => 'Compact'; - String get compactDescription => 'Smaller cards, more items visible'; - String get normal => 'Normal'; - String get normalDescription => 'Default size'; - String get comfortable => 'Comfortable'; - String get comfortableDescription => 'Larger cards, fewer items visible'; - String get viewMode => 'View Mode'; - String get gridView => 'Grid'; - String get gridViewDescription => 'Display items in a grid layout'; - String get listView => 'List'; - String get listViewDescription => 'Display items in a list layout'; - String get useSeasonPosters => 'Use Season Posters'; - String get showHeroSection => 'Show Hero Section'; - String get hardwareDecoding => 'Hardware Decoding'; - String get hardwareDecodingDescription => 'Use hardware acceleration when available'; - String get bufferSize => 'Buffer Size'; - String bufferSizeMB({required Object size}) => '${size}MB'; - String get subtitleStyling => 'Subtitle Styling'; - String get subtitleStylingDescription => 'Customize subtitle appearance'; - String get smallSkipDuration => 'Small Skip Duration'; - String get largeSkipDuration => 'Large Skip Duration'; - String secondsUnit({required Object seconds}) => '${seconds} seconds'; - String get defaultSleepTimer => 'Default Sleep Timer'; - String minutesUnit({required Object minutes}) => '${minutes} minutes'; - String get rememberTrackSelections => 'Remember track selections per show/movie'; - String get rememberTrackSelectionsDescription => 'Automatically save audio and subtitle language preferences when you change tracks during playback'; - String get unwatchedOnly => 'Unwatched Only'; - String get unwatchedOnlyDescription => 'Only include unwatched episodes in shuffle queue'; - String get shuffleOrderNavigation => 'Shuffle Order Navigation'; - String get shuffleOrderNavigationDescription => 'Next/previous buttons follow shuffled order'; - String get loopShuffleQueue => 'Loop Shuffle Queue'; - String get loopShuffleQueueDescription => 'Restart queue when reaching the end'; - String get videoPlayerControls => 'Video Player Controls'; - String get keyboardShortcuts => 'Keyboard Shortcuts'; - String get keyboardShortcutsDescription => 'Customize keyboard shortcuts'; - String get debugLogging => 'Debug Logging'; - String get debugLoggingDescription => 'Enable detailed logging for troubleshooting'; - String get viewLogs => 'View Logs'; - String get viewLogsDescription => 'View application logs'; - String get clearCache => 'Clear Cache'; - String get clearCacheDescription => 'This will clear all cached images and data. The app may take longer to load content after clearing the cache.'; - String get clearCacheSuccess => 'Cache cleared successfully'; - String get resetSettings => 'Reset Settings'; - String get resetSettingsDescription => 'This will reset all settings to their default values. This action cannot be undone.'; - String get resetSettingsSuccess => 'Settings reset successfully'; - String get shortcutsReset => 'Shortcuts reset to defaults'; - String get about => 'About'; - String get aboutDescription => 'App information and licenses'; - String get updates => 'Updates'; - String get updateAvailable => 'Update Available'; - String get checkForUpdates => 'Check for Updates'; - String get validationErrorEnterNumber => 'Please enter a valid number'; - String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Duration must be between ${min} and ${max} ${unit}'; - String shortcutAlreadyAssigned({required Object action}) => 'Shortcut already assigned to ${action}'; - String shortcutUpdated({required Object action}) => 'Shortcut updated for ${action}'; + // Translations + String get title => 'Settings'; + String get language => 'Language'; + String get theme => 'Theme'; + String get appearance => 'Appearance'; + String get videoPlayback => 'Video Playback'; + String get shufflePlay => 'Shuffle Play'; + String get advanced => 'Advanced'; + String get useSeasonPostersDescription => + 'Show season poster instead of series poster for episodes'; + String get showHeroSectionDescription => + 'Display featured content carousel on home screen'; + String get secondsLabel => 'Seconds'; + String get minutesLabel => 'Minutes'; + String get secondsShort => 's'; + String get minutesShort => 'm'; + String durationHint({required Object min, required Object max}) => + 'Enter duration (${min}-${max})'; + String get systemTheme => 'System'; + String get systemThemeDescription => 'Follow system settings'; + String get lightTheme => 'Light'; + String get darkTheme => 'Dark'; + String get libraryDensity => 'Library Density'; + String get compact => 'Compact'; + String get compactDescription => 'Smaller cards, more items visible'; + String get normal => 'Normal'; + String get normalDescription => 'Default size'; + String get comfortable => 'Comfortable'; + String get comfortableDescription => 'Larger cards, fewer items visible'; + String get viewMode => 'View Mode'; + String get gridView => 'Grid'; + String get gridViewDescription => 'Display items in a grid layout'; + String get listView => 'List'; + String get listViewDescription => 'Display items in a list layout'; + String get useSeasonPosters => 'Use Season Posters'; + String get showHeroSection => 'Show Hero Section'; + String get hardwareDecoding => 'Hardware Decoding'; + String get hardwareDecodingDescription => + 'Use hardware acceleration when available'; + String get bufferSize => 'Buffer Size'; + String bufferSizeMB({required Object size}) => '${size}MB'; + String get subtitleStyling => 'Subtitle Styling'; + String get subtitleStylingDescription => 'Customize subtitle appearance'; + String get smallSkipDuration => 'Small Skip Duration'; + String get largeSkipDuration => 'Large Skip Duration'; + String secondsUnit({required Object seconds}) => '${seconds} seconds'; + String get defaultSleepTimer => 'Default Sleep Timer'; + String minutesUnit({required Object minutes}) => '${minutes} minutes'; + String get rememberTrackSelections => + 'Remember track selections per show/movie'; + String get rememberTrackSelectionsDescription => + 'Automatically save audio and subtitle language preferences when you change tracks during playback'; + String get unwatchedOnly => 'Unwatched Only'; + String get unwatchedOnlyDescription => + 'Only include unwatched episodes in shuffle queue'; + String get shuffleOrderNavigation => 'Shuffle Order Navigation'; + String get shuffleOrderNavigationDescription => + 'Next/previous buttons follow shuffled order'; + String get loopShuffleQueue => 'Loop Shuffle Queue'; + String get loopShuffleQueueDescription => + 'Restart queue when reaching the end'; + String get videoPlayerControls => 'Video Player Controls'; + String get keyboardShortcuts => 'Keyboard Shortcuts'; + String get keyboardShortcutsDescription => 'Customize keyboard shortcuts'; + String get debugLogging => 'Debug Logging'; + String get debugLoggingDescription => + 'Enable detailed logging for troubleshooting'; + String get viewLogs => 'View Logs'; + String get viewLogsDescription => 'View application logs'; + String get clearCache => 'Clear Cache'; + String get clearCacheDescription => + 'This will clear all cached images and data. The app may take longer to load content after clearing the cache.'; + String get clearCacheSuccess => 'Cache cleared successfully'; + String get resetSettings => 'Reset Settings'; + String get resetSettingsDescription => + 'This will reset all settings to their default values. This action cannot be undone.'; + String get resetSettingsSuccess => 'Settings reset successfully'; + String get shortcutsReset => 'Shortcuts reset to defaults'; + String get about => 'About'; + String get aboutDescription => 'App information and licenses'; + String get updates => 'Updates'; + String get updateAvailable => 'Update Available'; + String get checkForUpdates => 'Check for Updates'; + String get validationErrorEnterNumber => 'Please enter a valid number'; + String validationErrorDuration({ + required Object min, + required Object max, + required Object unit, + }) => 'Duration must be between ${min} and ${max} ${unit}'; + String shortcutAlreadyAssigned({required Object action}) => + 'Shortcut already assigned to ${action}'; + String shortcutUpdated({required Object action}) => + 'Shortcut updated for ${action}'; } // Path: search class _StringsSearchEn { - _StringsSearchEn._(this._root); + _StringsSearchEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get hint => 'Search movies, shows, music...'; - String get tryDifferentTerm => 'Try a different search term'; - String get searchYourMedia => 'Search your media'; - String get enterTitleActorOrKeyword => 'Enter a title, actor, or keyword'; + // Translations + String get hint => 'Search movies, shows, music...'; + String get tryDifferentTerm => 'Try a different search term'; + String get searchYourMedia => 'Search your media'; + String get enterTitleActorOrKeyword => 'Enter a title, actor, or keyword'; } // Path: hotkeys class _StringsHotkeysEn { - _StringsHotkeysEn._(this._root); + _StringsHotkeysEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String setShortcutFor({required Object actionName}) => 'Set Shortcut for ${actionName}'; - String get clearShortcut => 'Clear shortcut'; + // Translations + String setShortcutFor({required Object actionName}) => + 'Set Shortcut for ${actionName}'; + String get clearShortcut => 'Clear shortcut'; } // Path: pinEntry class _StringsPinEntryEn { - _StringsPinEntryEn._(this._root); + _StringsPinEntryEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get enterPin => 'Enter PIN'; - String get showPin => 'Show PIN'; - String get hidePin => 'Hide PIN'; + // Translations + String get enterPin => 'Enter PIN'; + String get showPin => 'Show PIN'; + String get hidePin => 'Hide PIN'; } // Path: fileInfo class _StringsFileInfoEn { - _StringsFileInfoEn._(this._root); + _StringsFileInfoEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'File Info'; - String get video => 'Video'; - String get audio => 'Audio'; - String get file => 'File'; - String get advanced => 'Advanced'; - String get codec => 'Codec'; - String get resolution => 'Resolution'; - String get bitrate => 'Bitrate'; - String get frameRate => 'Frame Rate'; - String get aspectRatio => 'Aspect Ratio'; - String get profile => 'Profile'; - String get bitDepth => 'Bit Depth'; - String get colorSpace => 'Color Space'; - String get colorRange => 'Color Range'; - String get colorPrimaries => 'Color Primaries'; - String get chromaSubsampling => 'Chroma Subsampling'; - String get channels => 'Channels'; - String get path => 'Path'; - String get size => 'Size'; - String get container => 'Container'; - String get duration => 'Duration'; - String get optimizedForStreaming => 'Optimized for Streaming'; - String get has64bitOffsets => '64-bit Offsets'; + // Translations + String get title => 'File Info'; + String get video => 'Video'; + String get audio => 'Audio'; + String get file => 'File'; + String get advanced => 'Advanced'; + String get codec => 'Codec'; + String get resolution => 'Resolution'; + String get bitrate => 'Bitrate'; + String get frameRate => 'Frame Rate'; + String get aspectRatio => 'Aspect Ratio'; + String get profile => 'Profile'; + String get bitDepth => 'Bit Depth'; + String get colorSpace => 'Color Space'; + String get colorRange => 'Color Range'; + String get colorPrimaries => 'Color Primaries'; + String get chromaSubsampling => 'Chroma Subsampling'; + String get channels => 'Channels'; + String get path => 'Path'; + String get size => 'Size'; + String get container => 'Container'; + String get duration => 'Duration'; + String get optimizedForStreaming => 'Optimized for Streaming'; + String get has64bitOffsets => '64-bit Offsets'; } // Path: mediaMenu class _StringsMediaMenuEn { - _StringsMediaMenuEn._(this._root); + _StringsMediaMenuEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get markAsWatched => 'Mark as Watched'; - String get markAsUnwatched => 'Mark as Unwatched'; - String get removeFromContinueWatching => 'Remove from Continue Watching'; - String get goToSeries => 'Go to series'; - String get goToSeason => 'Go to season'; - String get shufflePlay => 'Shuffle Play'; - String get fileInfo => 'File Info'; + // Translations + String get markAsWatched => 'Mark as Watched'; + String get markAsUnwatched => 'Mark as Unwatched'; + String get removeFromContinueWatching => 'Remove from Continue Watching'; + String get goToSeries => 'Go to series'; + String get goToSeason => 'Go to season'; + String get shufflePlay => 'Shuffle Play'; + String get fileInfo => 'File Info'; } // Path: accessibility class _StringsAccessibilityEn { - _StringsAccessibilityEn._(this._root); + _StringsAccessibilityEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String mediaCardMovie({required Object title}) => '${title}, movie'; - String mediaCardShow({required Object title}) => '${title}, TV show'; - String mediaCardEpisode({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - String mediaCardSeason({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - String get mediaCardWatched => 'watched'; - String mediaCardPartiallyWatched({required Object percent}) => '${percent} percent watched'; - String get mediaCardUnwatched => 'unwatched'; - String get tapToPlay => 'Tap to play'; + // Translations + String mediaCardMovie({required Object title}) => '${title}, movie'; + String mediaCardShow({required Object title}) => '${title}, TV show'; + String mediaCardEpisode({ + required Object title, + required Object episodeInfo, + }) => '${title}, ${episodeInfo}'; + String mediaCardSeason({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + String get mediaCardWatched => 'watched'; + String mediaCardPartiallyWatched({required Object percent}) => + '${percent} percent watched'; + String get mediaCardUnwatched => 'unwatched'; + String get tapToPlay => 'Tap to play'; } // Path: tooltips class _StringsTooltipsEn { - _StringsTooltipsEn._(this._root); + _StringsTooltipsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get shufflePlay => 'Shuffle play'; - String get markAsWatched => 'Mark as watched'; - String get markAsUnwatched => 'Mark as unwatched'; + // Translations + String get shufflePlay => 'Shuffle play'; + String get markAsWatched => 'Mark as watched'; + String get markAsUnwatched => 'Mark as unwatched'; } // Path: videoControls class _StringsVideoControlsEn { - _StringsVideoControlsEn._(this._root); + _StringsVideoControlsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get audioLabel => 'Audio'; - String get subtitlesLabel => 'Subtitles'; - String get resetToZero => 'Reset to 0ms'; - String addTime({required Object amount, required Object unit}) => '+${amount}${unit}'; - String minusTime({required Object amount, required Object unit}) => '-${amount}${unit}'; - String playsLater({required Object label}) => '${label} plays later'; - String playsEarlier({required Object label}) => '${label} plays earlier'; - String get noOffset => 'No offset'; - String get letterbox => 'Letterbox'; - String get fillScreen => 'Fill screen'; - String get stretch => 'Stretch'; - String get lockRotation => 'Lock rotation'; - String get unlockRotation => 'Unlock rotation'; - String get sleepTimer => 'Sleep Timer'; - String get timerActive => 'Timer Active'; - String playbackWillPauseIn({required Object duration}) => 'Playback will pause in ${duration}'; - String get sleepTimerCompleted => 'Sleep timer completed - playback paused'; - String get playButton => 'Play'; - String get pauseButton => 'Pause'; - String seekBackwardButton({required Object seconds}) => 'Seek backward ${seconds} seconds'; - String seekForwardButton({required Object seconds}) => 'Seek forward ${seconds} seconds'; - String get previousButton => 'Previous episode'; - String get nextButton => 'Next episode'; - String get previousChapterButton => 'Previous chapter'; - String get nextChapterButton => 'Next chapter'; - String get volumeButton => 'Volume'; - String get muteButton => 'Mute'; - String get unmuteButton => 'Unmute'; - String get settingsButton => 'Video settings'; - String get audioTrackButton => 'Audio tracks'; - String get subtitlesButton => 'Subtitles'; - String get chaptersButton => 'Chapters'; - String get versionsButton => 'Video versions'; - String get aspectRatioButton => 'Aspect ratio'; - String get fullscreenButton => 'Enter fullscreen'; - String get exitFullscreenButton => 'Exit fullscreen'; - String get rotationLockButton => 'Rotation lock'; - String get timelineSlider => 'Video timeline'; - String get volumeSlider => 'Volume level'; - String get backButton => 'Back'; + // Translations + String get audioLabel => 'Audio'; + String get subtitlesLabel => 'Subtitles'; + String get resetToZero => 'Reset to 0ms'; + String addTime({required Object amount, required Object unit}) => + '+${amount}${unit}'; + String minusTime({required Object amount, required Object unit}) => + '-${amount}${unit}'; + String playsLater({required Object label}) => '${label} plays later'; + String playsEarlier({required Object label}) => '${label} plays earlier'; + String get noOffset => 'No offset'; + String get letterbox => 'Letterbox'; + String get fillScreen => 'Fill screen'; + String get stretch => 'Stretch'; + String get lockRotation => 'Lock rotation'; + String get unlockRotation => 'Unlock rotation'; + String get sleepTimer => 'Sleep Timer'; + String get timerActive => 'Timer Active'; + String playbackWillPauseIn({required Object duration}) => + 'Playback will pause in ${duration}'; + String get sleepTimerCompleted => 'Sleep timer completed - playback paused'; + String get playButton => 'Play'; + String get pauseButton => 'Pause'; + String seekBackwardButton({required Object seconds}) => + 'Seek backward ${seconds} seconds'; + String seekForwardButton({required Object seconds}) => + 'Seek forward ${seconds} seconds'; + String get previousButton => 'Previous episode'; + String get nextButton => 'Next episode'; + String get previousChapterButton => 'Previous chapter'; + String get nextChapterButton => 'Next chapter'; + String get volumeButton => 'Volume'; + String get muteButton => 'Mute'; + String get unmuteButton => 'Unmute'; + String get settingsButton => 'Video settings'; + String get audioTrackButton => 'Audio tracks'; + String get subtitlesButton => 'Subtitles'; + String get chaptersButton => 'Chapters'; + String get versionsButton => 'Video versions'; + String get aspectRatioButton => 'Aspect ratio'; + String get fullscreenButton => 'Enter fullscreen'; + String get exitFullscreenButton => 'Exit fullscreen'; + String get rotationLockButton => 'Rotation lock'; + String get timelineSlider => 'Video timeline'; + String get volumeSlider => 'Volume level'; + String get backButton => 'Back'; } // Path: userStatus class _StringsUserStatusEn { - _StringsUserStatusEn._(this._root); + _StringsUserStatusEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get admin => 'Admin'; - String get restricted => 'Restricted'; - String get protected => 'Protected'; - String get current => 'CURRENT'; + // Translations + String get admin => 'Admin'; + String get restricted => 'Restricted'; + String get protected => 'Protected'; + String get current => 'CURRENT'; } // Path: messages class _StringsMessagesEn { - _StringsMessagesEn._(this._root); + _StringsMessagesEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get markedAsWatched => 'Marked as watched'; - String get markedAsUnwatched => 'Marked as unwatched'; - String get removedFromContinueWatching => 'Removed from Continue Watching'; - String errorLoading({required Object error}) => 'Error: ${error}'; - String get fileInfoNotAvailable => 'File information not available'; - String errorLoadingFileInfo({required Object error}) => 'Error loading file info: ${error}'; - String get errorLoadingSeries => 'Error loading series'; - String get errorLoadingSeason => 'Error loading season'; - String get musicNotSupported => 'Music playback is not yet supported'; - String get logsCleared => 'Logs cleared'; - String get logsCopied => 'Logs copied to clipboard'; - String get noLogsAvailable => 'No logs available'; - String libraryScanning({required Object title}) => 'Scanning "${title}"...'; - String libraryScanStarted({required Object title}) => 'Library scan started for "${title}"'; - String libraryScanFailed({required Object error}) => 'Failed to scan library: ${error}'; - String metadataRefreshing({required Object title}) => 'Refreshing metadata for "${title}"...'; - String metadataRefreshStarted({required Object title}) => 'Metadata refresh started for "${title}"'; - String metadataRefreshFailed({required Object error}) => 'Failed to refresh metadata: ${error}'; - String get noPlexToken => 'No Plex token found. Please login again.'; - String get logoutConfirm => 'Are you sure you want to logout?'; - String get noSeasonsFound => 'No seasons found'; - String get noEpisodesFound => 'No episodes found in first season'; - String get noEpisodesFoundGeneral => 'No episodes found'; - String get noResultsFound => 'No results found'; - String sleepTimerSet({required Object label}) => 'Sleep timer set for ${label}'; - String failedToSwitchProfile({required Object displayName}) => 'Failed to switch to ${displayName}'; - String get noItemsAvailable => 'No items available'; - String get failedToCreatePlayQueue => 'Failed to create play queue'; - String get failedToCreatePlayQueueNoItems => 'Failed to create play queue - no items'; - String failedPlayback({required Object action, required Object error}) => 'Failed to ${action}: ${error}'; + // Translations + String get markedAsWatched => 'Marked as watched'; + String get markedAsUnwatched => 'Marked as unwatched'; + String get removedFromContinueWatching => 'Removed from Continue Watching'; + String errorLoading({required Object error}) => 'Error: ${error}'; + String get fileInfoNotAvailable => 'File information not available'; + String errorLoadingFileInfo({required Object error}) => + 'Error loading file info: ${error}'; + String get errorLoadingSeries => 'Error loading series'; + String get errorLoadingSeason => 'Error loading season'; + String get musicNotSupported => 'Music playback is not yet supported'; + String get logsCleared => 'Logs cleared'; + String get logsCopied => 'Logs copied to clipboard'; + String get noLogsAvailable => 'No logs available'; + String libraryScanning({required Object title}) => 'Scanning "${title}"...'; + String libraryScanStarted({required Object title}) => + 'Library scan started for "${title}"'; + String libraryScanFailed({required Object error}) => + 'Failed to scan library: ${error}'; + String metadataRefreshing({required Object title}) => + 'Refreshing metadata for "${title}"...'; + String metadataRefreshStarted({required Object title}) => + 'Metadata refresh started for "${title}"'; + String metadataRefreshFailed({required Object error}) => + 'Failed to refresh metadata: ${error}'; + String get noPlexToken => 'No Plex token found. Please login again.'; + String get logoutConfirm => 'Are you sure you want to logout?'; + String get noSeasonsFound => 'No seasons found'; + String get noEpisodesFound => 'No episodes found in first season'; + String get noEpisodesFoundGeneral => 'No episodes found'; + String get noResultsFound => 'No results found'; + String sleepTimerSet({required Object label}) => + 'Sleep timer set for ${label}'; + String failedToSwitchProfile({required Object displayName}) => + 'Failed to switch to ${displayName}'; + String get noItemsAvailable => 'No items available'; + String get failedToCreatePlayQueue => 'Failed to create play queue'; + String get failedToCreatePlayQueueNoItems => + 'Failed to create play queue - no items'; + String failedPlayback({required Object action, required Object error}) => + 'Failed to ${action}: ${error}'; } // Path: profile class _StringsProfileEn { - _StringsProfileEn._(this._root); + _StringsProfileEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get noUsersAvailable => 'No users available'; + // Translations + String get noUsersAvailable => 'No users available'; } // Path: subtitlingStyling class _StringsSubtitlingStylingEn { - _StringsSubtitlingStylingEn._(this._root); + _StringsSubtitlingStylingEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get stylingOptions => 'Styling Options'; - String get fontSize => 'Font Size'; - String get textColor => 'Text Color'; - String get borderSize => 'Border Size'; - String get borderColor => 'Border Color'; - String get backgroundOpacity => 'Background Opacity'; - String get backgroundColor => 'Background Color'; + // Translations + String get stylingOptions => 'Styling Options'; + String get fontSize => 'Font Size'; + String get textColor => 'Text Color'; + String get borderSize => 'Border Size'; + String get borderColor => 'Border Color'; + String get backgroundOpacity => 'Background Opacity'; + String get backgroundColor => 'Background Color'; } // Path: dialog class _StringsDialogEn { - _StringsDialogEn._(this._root); + _StringsDialogEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get confirmAction => 'Confirm Action'; - String get areYouSure => 'Are you sure you want to perform this action?'; - String get cancel => 'Cancel'; - String get playNow => 'Play Now'; + // Translations + String get confirmAction => 'Confirm Action'; + String get areYouSure => 'Are you sure you want to perform this action?'; + String get cancel => 'Cancel'; + String get playNow => 'Play Now'; } // Path: discover class _StringsDiscoverEn { - _StringsDiscoverEn._(this._root); + _StringsDiscoverEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Discover'; - String get switchProfile => 'Switch Profile'; - String get logout => 'Logout'; - String get noContentAvailable => 'No content available'; - String get addMediaToLibraries => 'Add some media to your libraries'; - String get continueWatching => 'Continue Watching'; - String get recentlyAdded => 'Recently Added'; - String get play => 'Play'; - String get resume => 'Resume'; - String playEpisode({required Object season, required Object episode}) => 'Play S${season}, E${episode}'; - String resumeEpisode({required Object season, required Object episode}) => 'Resume S${season}, E${episode}'; - String get pause => 'Pause'; - String get overview => 'Overview'; - String get cast => 'Cast'; - String get seasons => 'Seasons'; - String get studio => 'Studio'; - String get rating => 'Rating'; - String get watched => 'Watched'; - String episodeCount({required Object count}) => '${count} episodes'; - String watchedProgress({required Object watched, required Object total}) => '${watched}/${total} watched'; - String get movie => 'Movie'; - String get tvShow => 'TV Show'; - String minutesLeft({required Object minutes}) => '${minutes} min left'; + // Translations + String get title => 'Discover'; + String get switchProfile => 'Switch Profile'; + String get logout => 'Logout'; + String get noContentAvailable => 'No content available'; + String get addMediaToLibraries => 'Add some media to your libraries'; + String get continueWatching => 'Continue Watching'; + String get recentlyAdded => 'Recently Added'; + String get play => 'Play'; + String get resume => 'Resume'; + String playEpisode({required Object season, required Object episode}) => + 'Play S${season}, E${episode}'; + String resumeEpisode({required Object season, required Object episode}) => + 'Resume S${season}, E${episode}'; + String get pause => 'Pause'; + String get overview => 'Overview'; + String get cast => 'Cast'; + String get seasons => 'Seasons'; + String get studio => 'Studio'; + String get rating => 'Rating'; + String get watched => 'Watched'; + String episodeCount({required Object count}) => '${count} episodes'; + String watchedProgress({required Object watched, required Object total}) => + '${watched}/${total} watched'; + String get movie => 'Movie'; + String get tvShow => 'TV Show'; + String minutesLeft({required Object minutes}) => '${minutes} min left'; } // Path: errors class _StringsErrorsEn { - _StringsErrorsEn._(this._root); + _StringsErrorsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String searchFailed({required Object error}) => 'Search failed: ${error}'; - String connectionTimeout({required Object context}) => 'Connection timeout while loading ${context}'; - String get connectionFailed => 'Unable to connect to Plex server'; - String failedToLoad({required Object context, required Object error}) => 'Failed to load ${context}: ${error}'; - String get noClientAvailable => 'No client available'; - String authenticationFailed({required Object error}) => 'Authentication failed: ${error}'; - String get couldNotLaunchUrl => 'Could not launch auth URL'; - String get pleaseEnterToken => 'Please enter a token'; - String get invalidToken => 'Invalid token'; - String failedToVerifyToken({required Object error}) => 'Failed to verify token: ${error}'; - String failedToSwitchProfile({required Object displayName}) => 'Failed to switch to ${displayName}'; - String get connectionFailedGeneric => 'Connection failed'; + // Translations + String searchFailed({required Object error}) => 'Search failed: ${error}'; + String connectionTimeout({required Object context}) => + 'Connection timeout while loading ${context}'; + String get connectionFailed => 'Unable to connect to Plex server'; + String failedToLoad({required Object context, required Object error}) => + 'Failed to load ${context}: ${error}'; + String get noClientAvailable => 'No client available'; + String authenticationFailed({required Object error}) => + 'Authentication failed: ${error}'; + String get couldNotLaunchUrl => 'Could not launch auth URL'; + String get pleaseEnterToken => 'Please enter a token'; + String get invalidToken => 'Invalid token'; + String failedToVerifyToken({required Object error}) => + 'Failed to verify token: ${error}'; + String failedToSwitchProfile({required Object displayName}) => + 'Failed to switch to ${displayName}'; + String get connectionFailedGeneric => 'Connection failed'; } // Path: libraries class _StringsLibrariesEn { - _StringsLibrariesEn._(this._root); + _StringsLibrariesEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Libraries'; - String get scanLibraryFiles => 'Scan Library Files'; - String get scanLibrary => 'Scan Library'; - String get analyze => 'Analyze'; - String get analyzeLibrary => 'Analyze Library'; - String get refreshMetadata => 'Refresh Metadata'; - String get emptyTrash => 'Empty Trash'; - String emptyingTrash({required Object title}) => 'Emptying trash for "${title}"...'; - String trashEmptied({required Object title}) => 'Trash emptied for "${title}"'; - String failedToEmptyTrash({required Object error}) => 'Failed to empty trash: ${error}'; - String analyzing({required Object title}) => 'Analyzing "${title}"...'; - String analysisStarted({required Object title}) => 'Analysis started for "${title}"'; - String failedToAnalyze({required Object error}) => 'Failed to analyze library: ${error}'; - String get noLibrariesFound => 'No libraries found'; - String get thisLibraryIsEmpty => 'This library is empty'; - String get all => 'All'; - String get clearAll => 'Clear All'; - String scanLibraryConfirm({required Object title}) => 'Are you sure you want to scan "${title}"?'; - String analyzeLibraryConfirm({required Object title}) => 'Are you sure you want to analyze "${title}"?'; - String refreshMetadataConfirm({required Object title}) => 'Are you sure you want to refresh metadata for "${title}"?'; - String emptyTrashConfirm({required Object title}) => 'Are you sure you want to empty trash for "${title}"?'; - String get manageLibraries => 'Manage Libraries'; - String get sort => 'Sort'; - String get sortBy => 'Sort By'; - String get filters => 'Filters'; - String loadingLibraryWithCount({required Object count}) => 'Loading library... (${count} items loaded)'; - String get confirmActionMessage => 'Are you sure you want to perform this action?'; - String get showLibrary => 'Show library'; - String get hideLibrary => 'Hide library'; - String get libraryOptions => 'Library options'; - String get content => 'library content'; - String get selectLibrary => 'Select library'; - String filtersWithCount({required Object count}) => 'Filters (${count})'; - String get noRecommendations => 'No recommendations available'; - String get noCollections => 'No collections in this library'; - String get noFoldersFound => 'No folders found'; - String get folders => 'folders'; - late final _StringsLibrariesTabsEn tabs = _StringsLibrariesTabsEn._(_root); - late final _StringsLibrariesGroupingsEn groupings = _StringsLibrariesGroupingsEn._(_root); + // Translations + String get title => 'Libraries'; + String get scanLibraryFiles => 'Scan Library Files'; + String get scanLibrary => 'Scan Library'; + String get analyze => 'Analyze'; + String get analyzeLibrary => 'Analyze Library'; + String get refreshMetadata => 'Refresh Metadata'; + String get emptyTrash => 'Empty Trash'; + String emptyingTrash({required Object title}) => + 'Emptying trash for "${title}"...'; + String trashEmptied({required Object title}) => + 'Trash emptied for "${title}"'; + String failedToEmptyTrash({required Object error}) => + 'Failed to empty trash: ${error}'; + String analyzing({required Object title}) => 'Analyzing "${title}"...'; + String analysisStarted({required Object title}) => + 'Analysis started for "${title}"'; + String failedToAnalyze({required Object error}) => + 'Failed to analyze library: ${error}'; + String get noLibrariesFound => 'No libraries found'; + String get thisLibraryIsEmpty => 'This library is empty'; + String get all => 'All'; + String get clearAll => 'Clear All'; + String scanLibraryConfirm({required Object title}) => + 'Are you sure you want to scan "${title}"?'; + String analyzeLibraryConfirm({required Object title}) => + 'Are you sure you want to analyze "${title}"?'; + String refreshMetadataConfirm({required Object title}) => + 'Are you sure you want to refresh metadata for "${title}"?'; + String emptyTrashConfirm({required Object title}) => + 'Are you sure you want to empty trash for "${title}"?'; + String get manageLibraries => 'Manage Libraries'; + String get sort => 'Sort'; + String get sortBy => 'Sort By'; + String get filters => 'Filters'; + String loadingLibraryWithCount({required Object count}) => + 'Loading library... (${count} items loaded)'; + String get confirmActionMessage => + 'Are you sure you want to perform this action?'; + String get showLibrary => 'Show library'; + String get hideLibrary => 'Hide library'; + String get libraryOptions => 'Library options'; + String get content => 'library content'; + String get selectLibrary => 'Select library'; + String filtersWithCount({required Object count}) => 'Filters (${count})'; + String get noRecommendations => 'No recommendations available'; + String get noCollections => 'No collections in this library'; + String get noFoldersFound => 'No folders found'; + String get folders => 'folders'; + late final _StringsLibrariesTabsEn tabs = _StringsLibrariesTabsEn._(_root); + late final _StringsLibrariesGroupingsEn groupings = + _StringsLibrariesGroupingsEn._(_root); } // Path: about class _StringsAboutEn { - _StringsAboutEn._(this._root); + _StringsAboutEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'About'; - String get openSourceLicenses => 'Open Source Licenses'; - String versionLabel({required Object version}) => 'Version ${version}'; - String get appDescription => 'A beautiful Plex client for Flutter'; - String get viewLicensesDescription => 'View licenses of third-party libraries'; + // Translations + String get title => 'About'; + String get openSourceLicenses => 'Open Source Licenses'; + String versionLabel({required Object version}) => 'Version ${version}'; + String get appDescription => 'A beautiful Plex client for Flutter'; + String get viewLicensesDescription => + 'View licenses of third-party libraries'; } // Path: serverSelection class _StringsServerSelectionEn { - _StringsServerSelectionEn._(this._root); + _StringsServerSelectionEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get connectingToServer => 'Connecting to server...'; - String get connectingToAllServers => 'Connecting to all servers...'; - String get connectToAllServers => 'Connect to All Servers'; - String get allServerConnectionsFailed => 'Failed to connect to any servers. Please check your network and try again.'; - String foundServers({required Object count}) => 'Found ${count} server(s)'; - String get orSelectIndividually => 'Or select a server individually:'; - String get serverDebugCopied => 'Server debug data copied to clipboard'; - String get copyDebugData => 'Copy Debug Data'; - String get noServersFound => 'No servers found'; - String malformedServerData({required Object count}) => 'Found ${count} server(s) with malformed data. No valid servers available.'; - String get incompleteServerInfo => 'Some servers have incomplete information and were skipped. Please check your Plex.tv account.'; - String get incompleteConnectionInfo => 'Server connection information is incomplete. Please try again.'; - String malformedServerInfo({required Object message}) => 'Server information is malformed: ${message}'; - String get networkConnectionFailed => 'Network connection failed. Please check your internet connection and try again.'; - String get authenticationFailed => 'Authentication failed. Please sign in again.'; - String get plexServiceUnavailable => 'Plex service unavailable. Please try again later.'; - String failedToLoadServers({required Object error}) => 'Failed to load servers: ${error}'; + // Translations + String get connectingToServer => 'Connecting to server...'; + String get connectingToAllServers => 'Connecting to all servers...'; + String get connectToAllServers => 'Connect to All Servers'; + String get allServerConnectionsFailed => + 'Failed to connect to any servers. Please check your network and try again.'; + String foundServers({required Object count}) => 'Found ${count} server(s)'; + String get orSelectIndividually => 'Or select a server individually:'; + String get serverDebugCopied => 'Server debug data copied to clipboard'; + String get copyDebugData => 'Copy Debug Data'; + String get noServersFound => 'No servers found'; + String malformedServerData({required Object count}) => + 'Found ${count} server(s) with malformed data. No valid servers available.'; + String get incompleteServerInfo => + 'Some servers have incomplete information and were skipped. Please check your Plex.tv account.'; + String get incompleteConnectionInfo => + 'Server connection information is incomplete. Please try again.'; + String malformedServerInfo({required Object message}) => + 'Server information is malformed: ${message}'; + String get networkConnectionFailed => + 'Network connection failed. Please check your internet connection and try again.'; + String get authenticationFailed => + 'Authentication failed. Please sign in again.'; + String get plexServiceUnavailable => + 'Plex service unavailable. Please try again later.'; + String failedToLoadServers({required Object error}) => + 'Failed to load servers: ${error}'; } // Path: hubDetail class _StringsHubDetailEn { - _StringsHubDetailEn._(this._root); + _StringsHubDetailEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Title'; - String get releaseYear => 'Release Year'; - String get dateAdded => 'Date Added'; - String get rating => 'Rating'; - String get noItemsFound => 'No items found'; + // Translations + String get title => 'Title'; + String get releaseYear => 'Release Year'; + String get dateAdded => 'Date Added'; + String get rating => 'Rating'; + String get noItemsFound => 'No items found'; } // Path: logs class _StringsLogsEn { - _StringsLogsEn._(this._root); + _StringsLogsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Logs'; - String get clearLogs => 'Clear Logs'; - String get copyLogs => 'Copy Logs'; - String get exportLogs => 'Export Logs'; - String get noLogsToShow => 'No logs to show'; - String get error => 'Error:'; - String get stackTrace => 'Stack Trace:'; + // Translations + String get title => 'Logs'; + String get clearLogs => 'Clear Logs'; + String get copyLogs => 'Copy Logs'; + String get exportLogs => 'Export Logs'; + String get noLogsToShow => 'No logs to show'; + String get error => 'Error:'; + String get stackTrace => 'Stack Trace:'; } // Path: licenses class _StringsLicensesEn { - _StringsLicensesEn._(this._root); + _StringsLicensesEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get relatedPackages => 'Related Packages'; - String get license => 'License'; - String licenseNumber({required Object number}) => 'License ${number}'; - String licensesCount({required Object count}) => '${count} licenses'; + // Translations + String get relatedPackages => 'Related Packages'; + String get license => 'License'; + String licenseNumber({required Object number}) => 'License ${number}'; + String licensesCount({required Object count}) => '${count} licenses'; } // Path: navigation class _StringsNavigationEn { - _StringsNavigationEn._(this._root); + _StringsNavigationEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get home => 'Home'; - String get search => 'Search'; - String get libraries => 'Libraries'; - String get settings => 'Settings'; + // Translations + String get home => 'Home'; + String get search => 'Search'; + String get libraries => 'Libraries'; + String get settings => 'Settings'; } // Path: collections class _StringsCollectionsEn { - _StringsCollectionsEn._(this._root); + _StringsCollectionsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Collections'; - String get collection => 'Collection'; - String get empty => 'Collection is empty'; - String get noItems => 'No items in this collection'; - String get unknownLibrarySection => 'Cannot delete: Unknown library section'; - String get deleteCollection => 'Delete Collection'; - String deleteConfirm({required Object title}) => 'Are you sure you want to delete "${title}"? This action cannot be undone.'; - String get deleted => 'Collection deleted'; - String get deleteFailed => 'Failed to delete collection'; - String deleteFailedWithError({required Object error}) => 'Failed to delete collection: ${error}'; - String failedToLoadItems({required Object error}) => 'Failed to load collection items: ${error}'; - String get addTo => 'Add to collection'; - String get selectCollection => 'Select Collection'; - String get createNewCollection => 'Create New Collection'; - String get collectionName => 'Collection Name'; - String get enterCollectionName => 'Enter collection name'; - String get addedToCollection => 'Added to collection'; - String get errorAddingToCollection => 'Failed to add to collection'; - String get created => 'Collection created'; - String get removeFromCollection => 'Remove from collection'; - String removeFromCollectionConfirm({required Object title}) => 'Remove "${title}" from this collection?'; - String get removedFromCollection => 'Removed from collection'; - String get removeFromCollectionFailed => 'Failed to remove from collection'; - String removeFromCollectionError({required Object error}) => 'Error removing from collection: ${error}'; + // Translations + String get title => 'Collections'; + String get collection => 'Collection'; + String get empty => 'Collection is empty'; + String get noItems => 'No items in this collection'; + String get unknownLibrarySection => 'Cannot delete: Unknown library section'; + String get deleteCollection => 'Delete Collection'; + String deleteConfirm({required Object title}) => + 'Are you sure you want to delete "${title}"? This action cannot be undone.'; + String get deleted => 'Collection deleted'; + String get deleteFailed => 'Failed to delete collection'; + String deleteFailedWithError({required Object error}) => + 'Failed to delete collection: ${error}'; + String failedToLoadItems({required Object error}) => + 'Failed to load collection items: ${error}'; + String get addTo => 'Add to collection'; + String get selectCollection => 'Select Collection'; + String get createNewCollection => 'Create New Collection'; + String get collectionName => 'Collection Name'; + String get enterCollectionName => 'Enter collection name'; + String get addedToCollection => 'Added to collection'; + String get errorAddingToCollection => 'Failed to add to collection'; + String get created => 'Collection created'; + String get removeFromCollection => 'Remove from collection'; + String removeFromCollectionConfirm({required Object title}) => + 'Remove "${title}" from this collection?'; + String get removedFromCollection => 'Removed from collection'; + String get removeFromCollectionFailed => 'Failed to remove from collection'; + String removeFromCollectionError({required Object error}) => + 'Error removing from collection: ${error}'; } // Path: playlists class _StringsPlaylistsEn { - _StringsPlaylistsEn._(this._root); + _StringsPlaylistsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get title => 'Playlists'; - String get playlist => 'Playlist'; - String get noPlaylists => 'No playlists found'; - String get create => 'Create Playlist'; - String get newPlaylist => 'New Playlist'; - String get playlistName => 'Playlist Name'; - String get enterPlaylistName => 'Enter playlist name'; - String get edit => 'Edit Playlist'; - String get delete => 'Delete Playlist'; - String get addTo => 'Add to Playlist'; - String get addItems => 'Add Items'; - String get removeItem => 'Remove from Playlist'; - String get clearPlaylist => 'Clear Playlist'; - String get playAll => 'Play All'; - String get shuffle => 'Shuffle'; - String get smartPlaylist => 'Smart Playlist'; - String get regularPlaylist => 'Regular Playlist'; - String itemCount({required Object count}) => '${count} items'; - String get oneItem => '1 item'; - String get emptyPlaylist => 'This playlist is empty'; - String get deleteConfirm => 'Delete Playlist?'; - String deleteMessage({required Object name}) => 'Are you sure you want to delete "${name}"?'; - String get created => 'Playlist created'; - String get updated => 'Playlist updated'; - String get deleted => 'Playlist deleted'; - String get itemAdded => 'Added to playlist'; - String get itemRemoved => 'Removed from playlist'; - String get selectPlaylist => 'Select Playlist'; - String get createNewPlaylist => 'Create New Playlist'; - String get errorCreating => 'Failed to create playlist'; - String get errorDeleting => 'Failed to delete playlist'; - String get errorLoading => 'Failed to load playlists'; - String get errorAdding => 'Failed to add to playlist'; - String get errorReordering => 'Failed to reorder playlist item'; - String get errorRemoving => 'Failed to remove from playlist'; + // Translations + String get title => 'Playlists'; + String get playlist => 'Playlist'; + String get noPlaylists => 'No playlists found'; + String get create => 'Create Playlist'; + String get newPlaylist => 'New Playlist'; + String get playlistName => 'Playlist Name'; + String get enterPlaylistName => 'Enter playlist name'; + String get edit => 'Edit Playlist'; + String get delete => 'Delete Playlist'; + String get addTo => 'Add to Playlist'; + String get addItems => 'Add Items'; + String get removeItem => 'Remove from Playlist'; + String get clearPlaylist => 'Clear Playlist'; + String get playAll => 'Play All'; + String get shuffle => 'Shuffle'; + String get smartPlaylist => 'Smart Playlist'; + String get regularPlaylist => 'Regular Playlist'; + String itemCount({required Object count}) => '${count} items'; + String get oneItem => '1 item'; + String get emptyPlaylist => 'This playlist is empty'; + String get deleteConfirm => 'Delete Playlist?'; + String deleteMessage({required Object name}) => + 'Are you sure you want to delete "${name}"?'; + String get created => 'Playlist created'; + String get updated => 'Playlist updated'; + String get deleted => 'Playlist deleted'; + String get itemAdded => 'Added to playlist'; + String get itemRemoved => 'Removed from playlist'; + String get selectPlaylist => 'Select Playlist'; + String get createNewPlaylist => 'Create New Playlist'; + String get errorCreating => 'Failed to create playlist'; + String get errorDeleting => 'Failed to delete playlist'; + String get errorLoading => 'Failed to load playlists'; + String get errorAdding => 'Failed to add to playlist'; + String get errorReordering => 'Failed to reorder playlist item'; + String get errorRemoving => 'Failed to remove from playlist'; } // Path: libraries.tabs class _StringsLibrariesTabsEn { - _StringsLibrariesTabsEn._(this._root); + _StringsLibrariesTabsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get recommended => 'Recommended'; - String get browse => 'Browse'; - String get collections => 'Collections'; - String get playlists => 'Playlists'; + // Translations + String get recommended => 'Recommended'; + String get browse => 'Browse'; + String get collections => 'Collections'; + String get playlists => 'Playlists'; } // Path: libraries.groupings class _StringsLibrariesGroupingsEn { - _StringsLibrariesGroupingsEn._(this._root); + _StringsLibrariesGroupingsEn._(this._root); - final Translations _root; // ignore: unused_field + final Translations _root; // ignore: unused_field - // Translations - String get all => 'All'; - String get movies => 'Movies'; - String get shows => 'TV Shows'; - String get seasons => 'Seasons'; - String get episodes => 'Episodes'; - String get folders => 'Folders'; + // Translations + String get all => 'All'; + String get movies => 'Movies'; + String get shows => 'TV Shows'; + String get seasons => 'Seasons'; + String get episodes => 'Episodes'; + String get folders => 'Folders'; } // Path: class _StringsDe implements Translations { - /// You can call this constructor and build your own translation instance of this locale. - /// Constructing via the enum [AppLocale.build] is preferred. - _StringsDe.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) - : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), - $meta = TranslationMetadata( - locale: AppLocale.de, - overrides: overrides ?? {}, - cardinalResolver: cardinalResolver, - ordinalResolver: ordinalResolver, - ) { - $meta.setFlatMapFunction(_flatMapFunction); - } + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsDe.build({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) : assert( + overrides == null, + 'Set "translation_overrides: true" in order to enable this feature.', + ), + $meta = TranslationMetadata( + locale: AppLocale.de, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } - /// Metadata for the translations of . - @override final TranslationMetadata $meta; + /// Metadata for the translations of . + @override + final TranslationMetadata $meta; - /// Access flat map - @override dynamic operator[](String key) => $meta.getTranslation(key); + /// Access flat map + @override + dynamic operator [](String key) => $meta.getTranslation(key); - @override late final _StringsDe _root = this; // ignore: unused_field + @override + late final _StringsDe _root = this; // ignore: unused_field - // Translations - @override late final _StringsAppDe app = _StringsAppDe._(_root); - @override late final _StringsAuthDe auth = _StringsAuthDe._(_root); - @override late final _StringsCommonDe common = _StringsCommonDe._(_root); - @override late final _StringsScreensDe screens = _StringsScreensDe._(_root); - @override late final _StringsUpdateDe update = _StringsUpdateDe._(_root); - @override late final _StringsSettingsDe settings = _StringsSettingsDe._(_root); - @override late final _StringsSearchDe search = _StringsSearchDe._(_root); - @override late final _StringsHotkeysDe hotkeys = _StringsHotkeysDe._(_root); - @override late final _StringsPinEntryDe pinEntry = _StringsPinEntryDe._(_root); - @override late final _StringsFileInfoDe fileInfo = _StringsFileInfoDe._(_root); - @override late final _StringsMediaMenuDe mediaMenu = _StringsMediaMenuDe._(_root); - @override late final _StringsAccessibilityDe accessibility = _StringsAccessibilityDe._(_root); - @override late final _StringsTooltipsDe tooltips = _StringsTooltipsDe._(_root); - @override late final _StringsVideoControlsDe videoControls = _StringsVideoControlsDe._(_root); - @override late final _StringsUserStatusDe userStatus = _StringsUserStatusDe._(_root); - @override late final _StringsMessagesDe messages = _StringsMessagesDe._(_root); - @override late final _StringsProfileDe profile = _StringsProfileDe._(_root); - @override late final _StringsSubtitlingStylingDe subtitlingStyling = _StringsSubtitlingStylingDe._(_root); - @override late final _StringsDialogDe dialog = _StringsDialogDe._(_root); - @override late final _StringsDiscoverDe discover = _StringsDiscoverDe._(_root); - @override late final _StringsErrorsDe errors = _StringsErrorsDe._(_root); - @override late final _StringsLibrariesDe libraries = _StringsLibrariesDe._(_root); - @override late final _StringsAboutDe about = _StringsAboutDe._(_root); - @override late final _StringsServerSelectionDe serverSelection = _StringsServerSelectionDe._(_root); - @override late final _StringsHubDetailDe hubDetail = _StringsHubDetailDe._(_root); - @override late final _StringsLogsDe logs = _StringsLogsDe._(_root); - @override late final _StringsLicensesDe licenses = _StringsLicensesDe._(_root); - @override late final _StringsNavigationDe navigation = _StringsNavigationDe._(_root); - @override late final _StringsPlaylistsDe playlists = _StringsPlaylistsDe._(_root); - @override late final _StringsCollectionsDe collections = _StringsCollectionsDe._(_root); + // Translations + @override + late final _StringsAppDe app = _StringsAppDe._(_root); + @override + late final _StringsAuthDe auth = _StringsAuthDe._(_root); + @override + late final _StringsCommonDe common = _StringsCommonDe._(_root); + @override + late final _StringsScreensDe screens = _StringsScreensDe._(_root); + @override + late final _StringsUpdateDe update = _StringsUpdateDe._(_root); + @override + late final _StringsSettingsDe settings = _StringsSettingsDe._(_root); + @override + late final _StringsSearchDe search = _StringsSearchDe._(_root); + @override + late final _StringsHotkeysDe hotkeys = _StringsHotkeysDe._(_root); + @override + late final _StringsPinEntryDe pinEntry = _StringsPinEntryDe._(_root); + @override + late final _StringsFileInfoDe fileInfo = _StringsFileInfoDe._(_root); + @override + late final _StringsMediaMenuDe mediaMenu = _StringsMediaMenuDe._(_root); + @override + late final _StringsAccessibilityDe accessibility = _StringsAccessibilityDe._( + _root, + ); + @override + late final _StringsTooltipsDe tooltips = _StringsTooltipsDe._(_root); + @override + late final _StringsVideoControlsDe videoControls = _StringsVideoControlsDe._( + _root, + ); + @override + late final _StringsUserStatusDe userStatus = _StringsUserStatusDe._(_root); + @override + late final _StringsMessagesDe messages = _StringsMessagesDe._(_root); + @override + late final _StringsProfileDe profile = _StringsProfileDe._(_root); + @override + late final _StringsSubtitlingStylingDe subtitlingStyling = + _StringsSubtitlingStylingDe._(_root); + @override + late final _StringsDialogDe dialog = _StringsDialogDe._(_root); + @override + late final _StringsDiscoverDe discover = _StringsDiscoverDe._(_root); + @override + late final _StringsErrorsDe errors = _StringsErrorsDe._(_root); + @override + late final _StringsLibrariesDe libraries = _StringsLibrariesDe._(_root); + @override + late final _StringsAboutDe about = _StringsAboutDe._(_root); + @override + late final _StringsServerSelectionDe serverSelection = + _StringsServerSelectionDe._(_root); + @override + late final _StringsHubDetailDe hubDetail = _StringsHubDetailDe._(_root); + @override + late final _StringsLogsDe logs = _StringsLogsDe._(_root); + @override + late final _StringsLicensesDe licenses = _StringsLicensesDe._(_root); + @override + late final _StringsNavigationDe navigation = _StringsNavigationDe._(_root); + @override + late final _StringsPlaylistsDe playlists = _StringsPlaylistsDe._(_root); + @override + late final _StringsCollectionsDe collections = _StringsCollectionsDe._(_root); } // Path: app class _StringsAppDe implements _StringsAppEn { - _StringsAppDe._(this._root); + _StringsAppDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Plezy'; - @override String get loading => 'Lädt...'; + // Translations + @override + String get title => 'Plezy'; + @override + String get loading => 'Lädt...'; } // Path: auth class _StringsAuthDe implements _StringsAuthEn { - _StringsAuthDe._(this._root); + _StringsAuthDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get signInWithPlex => 'Mit Plex anmelden'; - @override String get showQRCode => 'QR-Code anzeigen'; - @override String get cancel => 'Abbrechen'; - @override String get authenticate => 'Authentifizieren'; - @override String get retry => 'Erneut versuchen'; - @override String get debugEnterToken => 'Debug: Plex-Token eingeben'; - @override String get plexTokenLabel => 'Plex-Auth-Token'; - @override String get plexTokenHint => 'Plex.tv-Token eingeben'; - @override String get authenticationTimeout => 'Authentifizierung abgelaufen. Bitte erneut versuchen.'; - @override String get scanQRCodeInstruction => 'Diesen QR-Code mit einem bei Plex angemeldeten Gerät scannen, um zu authentifizieren.'; - @override String get waitingForAuth => 'Warte auf Authentifizierung...\nBitte Anmeldung im Browser abschließen.'; + // Translations + @override + String get signInWithPlex => 'Mit Plex anmelden'; + @override + String get showQRCode => 'QR-Code anzeigen'; + @override + String get cancel => 'Abbrechen'; + @override + String get authenticate => 'Authentifizieren'; + @override + String get retry => 'Erneut versuchen'; + @override + String get debugEnterToken => 'Debug: Plex-Token eingeben'; + @override + String get plexTokenLabel => 'Plex-Auth-Token'; + @override + String get plexTokenHint => 'Plex.tv-Token eingeben'; + @override + String get authenticationTimeout => + 'Authentifizierung abgelaufen. Bitte erneut versuchen.'; + @override + String get scanQRCodeInstruction => + 'Diesen QR-Code mit einem bei Plex angemeldeten Gerät scannen, um zu authentifizieren.'; + @override + String get waitingForAuth => + 'Warte auf Authentifizierung...\nBitte Anmeldung im Browser abschließen.'; } // Path: common class _StringsCommonDe implements _StringsCommonEn { - _StringsCommonDe._(this._root); + _StringsCommonDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get cancel => 'Abbrechen'; - @override String get save => 'Speichern'; - @override String get close => 'Schließen'; - @override String get clear => 'Leeren'; - @override String get reset => 'Zurücksetzen'; - @override String get later => 'Später'; - @override String get submit => 'Senden'; - @override String get confirm => 'Bestätigen'; - @override String get retry => 'Erneut versuchen'; - @override String get playNow => 'Jetzt abspielen'; - @override String get logout => 'Abmelden'; - @override String get online => 'Online'; - @override String get offline => 'Offline'; - @override String get owned => 'Besitzer'; - @override String get shared => 'Geteilt'; - @override String get current => 'AKTUELL'; - @override String get unknown => 'Unbekannt'; - @override String get refresh => 'Aktualisieren'; - @override String get yes => 'Ja'; - @override String get no => 'Nein'; - @override String get server => 'Server'; - @override String get delete => 'Löschen'; - @override String get shuffle => 'Zufall'; - @override String get addTo => 'Hinzufügen zu...'; + // Translations + @override + String get cancel => 'Abbrechen'; + @override + String get save => 'Speichern'; + @override + String get close => 'Schließen'; + @override + String get clear => 'Leeren'; + @override + String get reset => 'Zurücksetzen'; + @override + String get later => 'Später'; + @override + String get submit => 'Senden'; + @override + String get confirm => 'Bestätigen'; + @override + String get retry => 'Erneut versuchen'; + @override + String get playNow => 'Jetzt abspielen'; + @override + String get logout => 'Abmelden'; + @override + String get online => 'Online'; + @override + String get offline => 'Offline'; + @override + String get owned => 'Besitzer'; + @override + String get shared => 'Geteilt'; + @override + String get current => 'AKTUELL'; + @override + String get unknown => 'Unbekannt'; + @override + String get refresh => 'Aktualisieren'; + @override + String get yes => 'Ja'; + @override + String get no => 'Nein'; + @override + String get server => 'Server'; + @override + String get delete => 'Löschen'; + @override + String get shuffle => 'Zufall'; + @override + String get addTo => 'Hinzufügen zu...'; } // Path: screens class _StringsScreensDe implements _StringsScreensEn { - _StringsScreensDe._(this._root); + _StringsScreensDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get licenses => 'Lizenzen'; - @override String get selectServer => 'Server auswählen'; - @override String get switchProfile => 'Profil wechseln'; - @override String get subtitleStyling => 'Untertitel-Stil'; - @override String get search => 'Suche'; - @override String get logs => 'Protokolle'; + // Translations + @override + String get licenses => 'Lizenzen'; + @override + String get selectServer => 'Server auswählen'; + @override + String get switchProfile => 'Profil wechseln'; + @override + String get subtitleStyling => 'Untertitel-Stil'; + @override + String get search => 'Suche'; + @override + String get logs => 'Protokolle'; } // Path: update class _StringsUpdateDe implements _StringsUpdateEn { - _StringsUpdateDe._(this._root); + _StringsUpdateDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get available => 'Update verfügbar'; - @override String versionAvailable({required Object version}) => 'Version ${version} ist verfügbar'; - @override String currentVersion({required Object version}) => 'Aktuell: ${version}'; - @override String get skipVersion => 'Diese Version überspringen'; - @override String get viewRelease => 'Release anzeigen'; - @override String get latestVersion => 'Aktuellste Version installiert'; - @override String get checkFailed => 'Fehler bei der Updateprüfung'; + // Translations + @override + String get available => 'Update verfügbar'; + @override + String versionAvailable({required Object version}) => + 'Version ${version} ist verfügbar'; + @override + String currentVersion({required Object version}) => 'Aktuell: ${version}'; + @override + String get skipVersion => 'Diese Version überspringen'; + @override + String get viewRelease => 'Release anzeigen'; + @override + String get latestVersion => 'Aktuellste Version installiert'; + @override + String get checkFailed => 'Fehler bei der Updateprüfung'; } // Path: settings class _StringsSettingsDe implements _StringsSettingsEn { - _StringsSettingsDe._(this._root); + _StringsSettingsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Einstellungen'; - @override String get language => 'Sprache'; - @override String get theme => 'Design'; - @override String get appearance => 'Darstellung'; - @override String get videoPlayback => 'Videowiedergabe'; - @override String get shufflePlay => 'Zufallswiedergabe'; - @override String get advanced => 'Erweitert'; - @override String get useSeasonPostersDescription => 'Staffelposter statt Serienposter für Episoden anzeigen'; - @override String get showHeroSectionDescription => 'Bereich mit empfohlenen Inhalten auf der Startseite anzeigen'; - @override String get secondsLabel => 'Sekunden'; - @override String get minutesLabel => 'Minuten'; - @override String get secondsShort => 's'; - @override String get minutesShort => 'm'; - @override String durationHint({required Object min, required Object max}) => 'Dauer eingeben (${min}-${max})'; - @override String get systemTheme => 'System'; - @override String get systemThemeDescription => 'Systemeinstellungen folgen'; - @override String get lightTheme => 'Hell'; - @override String get darkTheme => 'Dunkel'; - @override String get libraryDensity => 'Mediathekdichte'; - @override String get compact => 'Kompakt'; - @override String get compactDescription => 'Kleinere Karten, mehr Elemente sichtbar'; - @override String get normal => 'Normal'; - @override String get normalDescription => 'Standardgröße'; - @override String get comfortable => 'Großzügig'; - @override String get comfortableDescription => 'Größere Karten, weniger Elemente sichtbar'; - @override String get viewMode => 'Ansichtsmodus'; - @override String get gridView => 'Raster'; - @override String get gridViewDescription => 'Elemente im Raster anzeigen'; - @override String get listView => 'Liste'; - @override String get listViewDescription => 'Elemente in Listenansicht anzeigen'; - @override String get useSeasonPosters => 'Staffelposter verwenden'; - @override String get showHeroSection => 'Hero-Bereich anzeigen'; - @override String get hardwareDecoding => 'Hardware-Decodierung'; - @override String get hardwareDecodingDescription => 'Hardwarebeschleunigung verwenden, sofern verfügbar'; - @override String get bufferSize => 'Puffergröße'; - @override String bufferSizeMB({required Object size}) => '${size}MB'; - @override String get subtitleStyling => 'Untertitel-Stil'; - @override String get subtitleStylingDescription => 'Aussehen von Untertiteln anpassen'; - @override String get smallSkipDuration => 'Kleine Sprungdauer'; - @override String get largeSkipDuration => 'Große Sprungdauer'; - @override String secondsUnit({required Object seconds}) => '${seconds} Sekunden'; - @override String get defaultSleepTimer => 'Standard-Sleep-Timer'; - @override String minutesUnit({required Object minutes}) => '${minutes} Minuten'; - @override String get rememberTrackSelections => 'Spurauswahl pro Serie/Film merken'; - @override String get rememberTrackSelectionsDescription => 'Audio- und Untertitelsprache automatisch speichern, wenn während der Wiedergabe geändert'; - @override String get unwatchedOnly => 'Nur ungesehene'; - @override String get unwatchedOnlyDescription => 'Nur ungesehene Episoden in die Shuffle-Warteschlange aufnehmen'; - @override String get shuffleOrderNavigation => 'Navigation der Shuffle-Reihenfolge'; - @override String get shuffleOrderNavigationDescription => 'Weiter/Zurück folgt der zufälligen Reihenfolge'; - @override String get loopShuffleQueue => 'Shuffle-Warteschlange wiederholen'; - @override String get loopShuffleQueueDescription => 'Warteschlange neu starten, wenn das Ende erreicht ist'; - @override String get videoPlayerControls => 'Videoplayer-Steuerung'; - @override String get keyboardShortcuts => 'Tastenkürzel'; - @override String get keyboardShortcutsDescription => 'Tastenkürzel anpassen'; - @override String get debugLogging => 'Debug-Protokollierung'; - @override String get debugLoggingDescription => 'Detaillierte Protokolle zur Fehleranalyse aktivieren'; - @override String get viewLogs => 'Protokolle anzeigen'; - @override String get viewLogsDescription => 'App-Protokolle anzeigen'; - @override String get clearCache => 'Cache löschen'; - @override String get clearCacheDescription => 'Löscht alle zwischengespeicherten Bilder und Daten. Die App kann danach langsamer laden.'; - @override String get clearCacheSuccess => 'Cache erfolgreich gelöscht'; - @override String get resetSettings => 'Einstellungen zurücksetzen'; - @override String get resetSettingsDescription => 'Alle Einstellungen auf Standard zurücksetzen. Dies kann nicht rückgängig gemacht werden.'; - @override String get resetSettingsSuccess => 'Einstellungen erfolgreich zurückgesetzt'; - @override String get shortcutsReset => 'Tastenkürzel auf Standard zurückgesetzt'; - @override String get about => 'Über'; - @override String get aboutDescription => 'App-Informationen und Lizenzen'; - @override String get updates => 'Updates'; - @override String get updateAvailable => 'Update verfügbar'; - @override String get checkForUpdates => 'Nach Updates suchen'; - @override String get validationErrorEnterNumber => 'Bitte eine gültige Zahl eingeben'; - @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Dauer muss zwischen ${min} und ${max} ${unit} liegen'; - @override String shortcutAlreadyAssigned({required Object action}) => 'Tastenkürzel bereits zugewiesen an ${action}'; - @override String shortcutUpdated({required Object action}) => 'Tastenkürzel aktualisiert für ${action}'; + // Translations + @override + String get title => 'Einstellungen'; + @override + String get language => 'Sprache'; + @override + String get theme => 'Design'; + @override + String get appearance => 'Darstellung'; + @override + String get videoPlayback => 'Videowiedergabe'; + @override + String get shufflePlay => 'Zufallswiedergabe'; + @override + String get advanced => 'Erweitert'; + @override + String get useSeasonPostersDescription => + 'Staffelposter statt Serienposter für Episoden anzeigen'; + @override + String get showHeroSectionDescription => + 'Bereich mit empfohlenen Inhalten auf der Startseite anzeigen'; + @override + String get secondsLabel => 'Sekunden'; + @override + String get minutesLabel => 'Minuten'; + @override + String get secondsShort => 's'; + @override + String get minutesShort => 'm'; + @override + String durationHint({required Object min, required Object max}) => + 'Dauer eingeben (${min}-${max})'; + @override + String get systemTheme => 'System'; + @override + String get systemThemeDescription => 'Systemeinstellungen folgen'; + @override + String get lightTheme => 'Hell'; + @override + String get darkTheme => 'Dunkel'; + @override + String get libraryDensity => 'Mediathekdichte'; + @override + String get compact => 'Kompakt'; + @override + String get compactDescription => 'Kleinere Karten, mehr Elemente sichtbar'; + @override + String get normal => 'Normal'; + @override + String get normalDescription => 'Standardgröße'; + @override + String get comfortable => 'Großzügig'; + @override + String get comfortableDescription => + 'Größere Karten, weniger Elemente sichtbar'; + @override + String get viewMode => 'Ansichtsmodus'; + @override + String get gridView => 'Raster'; + @override + String get gridViewDescription => 'Elemente im Raster anzeigen'; + @override + String get listView => 'Liste'; + @override + String get listViewDescription => 'Elemente in Listenansicht anzeigen'; + @override + String get useSeasonPosters => 'Staffelposter verwenden'; + @override + String get showHeroSection => 'Hero-Bereich anzeigen'; + @override + String get hardwareDecoding => 'Hardware-Decodierung'; + @override + String get hardwareDecodingDescription => + 'Hardwarebeschleunigung verwenden, sofern verfügbar'; + @override + String get bufferSize => 'Puffergröße'; + @override + String bufferSizeMB({required Object size}) => '${size}MB'; + @override + String get subtitleStyling => 'Untertitel-Stil'; + @override + String get subtitleStylingDescription => 'Aussehen von Untertiteln anpassen'; + @override + String get smallSkipDuration => 'Kleine Sprungdauer'; + @override + String get largeSkipDuration => 'Große Sprungdauer'; + @override + String secondsUnit({required Object seconds}) => '${seconds} Sekunden'; + @override + String get defaultSleepTimer => 'Standard-Sleep-Timer'; + @override + String minutesUnit({required Object minutes}) => '${minutes} Minuten'; + @override + String get rememberTrackSelections => 'Spurauswahl pro Serie/Film merken'; + @override + String get rememberTrackSelectionsDescription => + 'Audio- und Untertitelsprache automatisch speichern, wenn während der Wiedergabe geändert'; + @override + String get unwatchedOnly => 'Nur ungesehene'; + @override + String get unwatchedOnlyDescription => + 'Nur ungesehene Episoden in die Shuffle-Warteschlange aufnehmen'; + @override + String get shuffleOrderNavigation => 'Navigation der Shuffle-Reihenfolge'; + @override + String get shuffleOrderNavigationDescription => + 'Weiter/Zurück folgt der zufälligen Reihenfolge'; + @override + String get loopShuffleQueue => 'Shuffle-Warteschlange wiederholen'; + @override + String get loopShuffleQueueDescription => + 'Warteschlange neu starten, wenn das Ende erreicht ist'; + @override + String get videoPlayerControls => 'Videoplayer-Steuerung'; + @override + String get keyboardShortcuts => 'Tastenkürzel'; + @override + String get keyboardShortcutsDescription => 'Tastenkürzel anpassen'; + @override + String get debugLogging => 'Debug-Protokollierung'; + @override + String get debugLoggingDescription => + 'Detaillierte Protokolle zur Fehleranalyse aktivieren'; + @override + String get viewLogs => 'Protokolle anzeigen'; + @override + String get viewLogsDescription => 'App-Protokolle anzeigen'; + @override + String get clearCache => 'Cache löschen'; + @override + String get clearCacheDescription => + 'Löscht alle zwischengespeicherten Bilder und Daten. Die App kann danach langsamer laden.'; + @override + String get clearCacheSuccess => 'Cache erfolgreich gelöscht'; + @override + String get resetSettings => 'Einstellungen zurücksetzen'; + @override + String get resetSettingsDescription => + 'Alle Einstellungen auf Standard zurücksetzen. Dies kann nicht rückgängig gemacht werden.'; + @override + String get resetSettingsSuccess => 'Einstellungen erfolgreich zurückgesetzt'; + @override + String get shortcutsReset => 'Tastenkürzel auf Standard zurückgesetzt'; + @override + String get about => 'Über'; + @override + String get aboutDescription => 'App-Informationen und Lizenzen'; + @override + String get updates => 'Updates'; + @override + String get updateAvailable => 'Update verfügbar'; + @override + String get checkForUpdates => 'Nach Updates suchen'; + @override + String get validationErrorEnterNumber => 'Bitte eine gültige Zahl eingeben'; + @override + String validationErrorDuration({ + required Object min, + required Object max, + required Object unit, + }) => 'Dauer muss zwischen ${min} und ${max} ${unit} liegen'; + @override + String shortcutAlreadyAssigned({required Object action}) => + 'Tastenkürzel bereits zugewiesen an ${action}'; + @override + String shortcutUpdated({required Object action}) => + 'Tastenkürzel aktualisiert für ${action}'; } // Path: search class _StringsSearchDe implements _StringsSearchEn { - _StringsSearchDe._(this._root); + _StringsSearchDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get hint => 'Filme, Serien, Musik suchen...'; - @override String get tryDifferentTerm => 'Anderen Suchbegriff versuchen'; - @override String get searchYourMedia => 'In den eigenen Medien suchen'; - @override String get enterTitleActorOrKeyword => 'Titel, Schauspieler oder Stichwort eingeben'; + // Translations + @override + String get hint => 'Filme, Serien, Musik suchen...'; + @override + String get tryDifferentTerm => 'Anderen Suchbegriff versuchen'; + @override + String get searchYourMedia => 'In den eigenen Medien suchen'; + @override + String get enterTitleActorOrKeyword => + 'Titel, Schauspieler oder Stichwort eingeben'; } // Path: hotkeys class _StringsHotkeysDe implements _StringsHotkeysEn { - _StringsHotkeysDe._(this._root); + _StringsHotkeysDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String setShortcutFor({required Object actionName}) => 'Tastenkürzel festlegen für ${actionName}'; - @override String get clearShortcut => 'Kürzel löschen'; + // Translations + @override + String setShortcutFor({required Object actionName}) => + 'Tastenkürzel festlegen für ${actionName}'; + @override + String get clearShortcut => 'Kürzel löschen'; } // Path: pinEntry class _StringsPinEntryDe implements _StringsPinEntryEn { - _StringsPinEntryDe._(this._root); + _StringsPinEntryDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get enterPin => 'PIN eingeben'; - @override String get showPin => 'PIN anzeigen'; - @override String get hidePin => 'PIN verbergen'; + // Translations + @override + String get enterPin => 'PIN eingeben'; + @override + String get showPin => 'PIN anzeigen'; + @override + String get hidePin => 'PIN verbergen'; } // Path: fileInfo class _StringsFileInfoDe implements _StringsFileInfoEn { - _StringsFileInfoDe._(this._root); + _StringsFileInfoDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Dateiinfo'; - @override String get video => 'Video'; - @override String get audio => 'Audio'; - @override String get file => 'Datei'; - @override String get advanced => 'Erweitert'; - @override String get codec => 'Codec'; - @override String get resolution => 'Auflösung'; - @override String get bitrate => 'Bitrate'; - @override String get frameRate => 'Bildrate'; - @override String get aspectRatio => 'Seitenverhältnis'; - @override String get profile => 'Profil'; - @override String get bitDepth => 'Farbtiefe'; - @override String get colorSpace => 'Farbraum'; - @override String get colorRange => 'Farbbereich'; - @override String get colorPrimaries => 'Primärfarben'; - @override String get chromaSubsampling => 'Chroma-Subsampling'; - @override String get channels => 'Kanäle'; - @override String get path => 'Pfad'; - @override String get size => 'Größe'; - @override String get container => 'Container'; - @override String get duration => 'Dauer'; - @override String get optimizedForStreaming => 'Für Streaming optimiert'; - @override String get has64bitOffsets => '64-Bit-Offsets'; + // Translations + @override + String get title => 'Dateiinfo'; + @override + String get video => 'Video'; + @override + String get audio => 'Audio'; + @override + String get file => 'Datei'; + @override + String get advanced => 'Erweitert'; + @override + String get codec => 'Codec'; + @override + String get resolution => 'Auflösung'; + @override + String get bitrate => 'Bitrate'; + @override + String get frameRate => 'Bildrate'; + @override + String get aspectRatio => 'Seitenverhältnis'; + @override + String get profile => 'Profil'; + @override + String get bitDepth => 'Farbtiefe'; + @override + String get colorSpace => 'Farbraum'; + @override + String get colorRange => 'Farbbereich'; + @override + String get colorPrimaries => 'Primärfarben'; + @override + String get chromaSubsampling => 'Chroma-Subsampling'; + @override + String get channels => 'Kanäle'; + @override + String get path => 'Pfad'; + @override + String get size => 'Größe'; + @override + String get container => 'Container'; + @override + String get duration => 'Dauer'; + @override + String get optimizedForStreaming => 'Für Streaming optimiert'; + @override + String get has64bitOffsets => '64-Bit-Offsets'; } // Path: mediaMenu class _StringsMediaMenuDe implements _StringsMediaMenuEn { - _StringsMediaMenuDe._(this._root); + _StringsMediaMenuDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get markAsWatched => 'Als gesehen markieren'; - @override String get markAsUnwatched => 'Als ungesehen markieren'; - @override String get removeFromContinueWatching => 'Aus ‚Weiterschauen‘ entfernen'; - @override String get goToSeries => 'Zur Serie'; - @override String get goToSeason => 'Zur Staffel'; - @override String get shufflePlay => 'Zufallswiedergabe'; - @override String get fileInfo => 'Dateiinfo'; + // Translations + @override + String get markAsWatched => 'Als gesehen markieren'; + @override + String get markAsUnwatched => 'Als ungesehen markieren'; + @override + String get removeFromContinueWatching => 'Aus ‚Weiterschauen‘ entfernen'; + @override + String get goToSeries => 'Zur Serie'; + @override + String get goToSeason => 'Zur Staffel'; + @override + String get shufflePlay => 'Zufallswiedergabe'; + @override + String get fileInfo => 'Dateiinfo'; } // Path: accessibility class _StringsAccessibilityDe implements _StringsAccessibilityEn { - _StringsAccessibilityDe._(this._root); + _StringsAccessibilityDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String mediaCardMovie({required Object title}) => '${title}, Film'; - @override String mediaCardShow({required Object title}) => '${title}, Serie'; - @override String mediaCardEpisode({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - @override String mediaCardSeason({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - @override String get mediaCardWatched => 'angesehen'; - @override String mediaCardPartiallyWatched({required Object percent}) => '${percent} Prozent angesehen'; - @override String get mediaCardUnwatched => 'ungeschaut'; - @override String get tapToPlay => 'Zum Abspielen tippen'; + // Translations + @override + String mediaCardMovie({required Object title}) => '${title}, Film'; + @override + String mediaCardShow({required Object title}) => '${title}, Serie'; + @override + String mediaCardEpisode({ + required Object title, + required Object episodeInfo, + }) => '${title}, ${episodeInfo}'; + @override + String mediaCardSeason({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + @override + String get mediaCardWatched => 'angesehen'; + @override + String mediaCardPartiallyWatched({required Object percent}) => + '${percent} Prozent angesehen'; + @override + String get mediaCardUnwatched => 'ungeschaut'; + @override + String get tapToPlay => 'Zum Abspielen tippen'; } // Path: tooltips class _StringsTooltipsDe implements _StringsTooltipsEn { - _StringsTooltipsDe._(this._root); + _StringsTooltipsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get shufflePlay => 'Zufallswiedergabe'; - @override String get markAsWatched => 'Als gesehen markieren'; - @override String get markAsUnwatched => 'Als ungesehen markieren'; + // Translations + @override + String get shufflePlay => 'Zufallswiedergabe'; + @override + String get markAsWatched => 'Als gesehen markieren'; + @override + String get markAsUnwatched => 'Als ungesehen markieren'; } // Path: videoControls class _StringsVideoControlsDe implements _StringsVideoControlsEn { - _StringsVideoControlsDe._(this._root); + _StringsVideoControlsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get audioLabel => 'Audio'; - @override String get subtitlesLabel => 'Untertitel'; - @override String get resetToZero => 'Auf 0 ms zurücksetzen'; - @override String addTime({required Object amount, required Object unit}) => '+${amount}${unit}'; - @override String minusTime({required Object amount, required Object unit}) => '-${amount}${unit}'; - @override String playsLater({required Object label}) => '${label} spielt später'; - @override String playsEarlier({required Object label}) => '${label} spielt früher'; - @override String get noOffset => 'Kein Offset'; - @override String get letterbox => 'Letterbox'; - @override String get fillScreen => 'Bild füllen'; - @override String get stretch => 'Strecken'; - @override String get lockRotation => 'Rotation sperren'; - @override String get unlockRotation => 'Rotation entsperren'; - @override String get sleepTimer => 'Schlaftimer'; - @override String get timerActive => 'Schlaftimer aktiv'; - @override String playbackWillPauseIn({required Object duration}) => 'Wiedergabe wird in ${duration} pausiert'; - @override String get sleepTimerCompleted => 'Schlaftimer abgelaufen – Wiedergabe pausiert'; - @override String get playButton => 'Wiedergeben'; - @override String get pauseButton => 'Pause'; - @override String seekBackwardButton({required Object seconds}) => '${seconds} Sekunden zurück'; - @override String seekForwardButton({required Object seconds}) => '${seconds} Sekunden vor'; - @override String get previousButton => 'Vorherige Episode'; - @override String get nextButton => 'Nächste Episode'; - @override String get previousChapterButton => 'Vorheriges Kapitel'; - @override String get nextChapterButton => 'Nächstes Kapitel'; - @override String get volumeButton => 'Lautstärke'; - @override String get muteButton => 'Stumm schalten'; - @override String get unmuteButton => 'Stummschaltung aufheben'; - @override String get settingsButton => 'Videoeinstellungen'; - @override String get audioTrackButton => 'Tonspuren'; - @override String get subtitlesButton => 'Untertitel'; - @override String get chaptersButton => 'Kapitel'; - @override String get versionsButton => 'Videoversionen'; - @override String get aspectRatioButton => 'Seitenverhältnis'; - @override String get fullscreenButton => 'Vollbild aktivieren'; - @override String get exitFullscreenButton => 'Vollbild verlassen'; - @override String get rotationLockButton => 'Dreh­sperre'; - @override String get timelineSlider => 'Video-Zeitleiste'; - @override String get volumeSlider => 'Lautstärkepegel'; - @override String get backButton => 'Zurück'; + // Translations + @override + String get audioLabel => 'Audio'; + @override + String get subtitlesLabel => 'Untertitel'; + @override + String get resetToZero => 'Auf 0 ms zurücksetzen'; + @override + String addTime({required Object amount, required Object unit}) => + '+${amount}${unit}'; + @override + String minusTime({required Object amount, required Object unit}) => + '-${amount}${unit}'; + @override + String playsLater({required Object label}) => '${label} spielt später'; + @override + String playsEarlier({required Object label}) => '${label} spielt früher'; + @override + String get noOffset => 'Kein Offset'; + @override + String get letterbox => 'Letterbox'; + @override + String get fillScreen => 'Bild füllen'; + @override + String get stretch => 'Strecken'; + @override + String get lockRotation => 'Rotation sperren'; + @override + String get unlockRotation => 'Rotation entsperren'; + @override + String get sleepTimer => 'Schlaftimer'; + @override + String get timerActive => 'Schlaftimer aktiv'; + @override + String playbackWillPauseIn({required Object duration}) => + 'Wiedergabe wird in ${duration} pausiert'; + @override + String get sleepTimerCompleted => + 'Schlaftimer abgelaufen – Wiedergabe pausiert'; + @override + String get playButton => 'Wiedergeben'; + @override + String get pauseButton => 'Pause'; + @override + String seekBackwardButton({required Object seconds}) => + '${seconds} Sekunden zurück'; + @override + String seekForwardButton({required Object seconds}) => + '${seconds} Sekunden vor'; + @override + String get previousButton => 'Vorherige Episode'; + @override + String get nextButton => 'Nächste Episode'; + @override + String get previousChapterButton => 'Vorheriges Kapitel'; + @override + String get nextChapterButton => 'Nächstes Kapitel'; + @override + String get volumeButton => 'Lautstärke'; + @override + String get muteButton => 'Stumm schalten'; + @override + String get unmuteButton => 'Stummschaltung aufheben'; + @override + String get settingsButton => 'Videoeinstellungen'; + @override + String get audioTrackButton => 'Tonspuren'; + @override + String get subtitlesButton => 'Untertitel'; + @override + String get chaptersButton => 'Kapitel'; + @override + String get versionsButton => 'Videoversionen'; + @override + String get aspectRatioButton => 'Seitenverhältnis'; + @override + String get fullscreenButton => 'Vollbild aktivieren'; + @override + String get exitFullscreenButton => 'Vollbild verlassen'; + @override + String get rotationLockButton => 'Dreh­sperre'; + @override + String get timelineSlider => 'Video-Zeitleiste'; + @override + String get volumeSlider => 'Lautstärkepegel'; + @override + String get backButton => 'Zurück'; } // Path: userStatus class _StringsUserStatusDe implements _StringsUserStatusEn { - _StringsUserStatusDe._(this._root); + _StringsUserStatusDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get admin => 'Eigentümer'; - @override String get restricted => 'Eingeschränkt'; - @override String get protected => 'Geschützt'; - @override String get current => 'AKTUELL'; + // Translations + @override + String get admin => 'Eigentümer'; + @override + String get restricted => 'Eingeschränkt'; + @override + String get protected => 'Geschützt'; + @override + String get current => 'AKTUELL'; } // Path: messages class _StringsMessagesDe implements _StringsMessagesEn { - _StringsMessagesDe._(this._root); + _StringsMessagesDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get markedAsWatched => 'Als gesehen markiert'; - @override String get markedAsUnwatched => 'Als ungesehen markiert'; - @override String get removedFromContinueWatching => 'Aus ‚Weiterschauen‘ entfernt'; - @override String errorLoading({required Object error}) => 'Fehler: ${error}'; - @override String get fileInfoNotAvailable => 'Dateiinfo nicht verfügbar'; - @override String errorLoadingFileInfo({required Object error}) => 'Fehler beim Laden der Dateiinfo: ${error}'; - @override String get errorLoadingSeries => 'Fehler beim Laden der Serie'; - @override String get errorLoadingSeason => 'Fehler beim Laden der Staffel'; - @override String get musicNotSupported => 'Musikwiedergabe wird noch nicht unterstützt'; - @override String get logsCleared => 'Protokolle gelöscht'; - @override String get logsCopied => 'Protokolle in Zwischenablage kopiert'; - @override String get noLogsAvailable => 'Keine Protokolle verfügbar'; - @override String libraryScanning({required Object title}) => 'Scanne „${title}“...'; - @override String libraryScanStarted({required Object title}) => 'Mediathekscan gestartet für „${title}“'; - @override String libraryScanFailed({required Object error}) => 'Fehler beim Scannen der Mediathek: ${error}'; - @override String metadataRefreshing({required Object title}) => 'Metadaten werden aktualisiert für „${title}“...'; - @override String metadataRefreshStarted({required Object title}) => 'Metadaten-Aktualisierung gestartet für „${title}“'; - @override String metadataRefreshFailed({required Object error}) => 'Metadaten konnten nicht aktualisiert werden: ${error}'; - @override String get noPlexToken => 'Kein Plex-Token gefunden. Bitte erneut anmelden.'; - @override String get logoutConfirm => 'Abmeldung wirklich durchführen?'; - @override String get noSeasonsFound => 'Keine Staffeln gefunden'; - @override String get noEpisodesFound => 'Keine Episoden in der ersten Staffel gefunden'; - @override String get noEpisodesFoundGeneral => 'Keine Episoden gefunden'; - @override String get noResultsFound => 'Keine Ergebnisse gefunden'; - @override String sleepTimerSet({required Object label}) => 'Sleep-Timer gesetzt auf ${label}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Profilwechsel zu ${displayName} fehlgeschlagen'; - @override String get noItemsAvailable => 'Keine Elemente verfügbar'; - @override String get failedToCreatePlayQueue => 'Wiedergabewarteschlange konnte nicht erstellt werden'; - @override String get failedToCreatePlayQueueNoItems => 'Wiedergabewarteschlange konnte nicht erstellt werden – keine Elemente'; - @override String failedPlayback({required Object action, required Object error}) => 'Wiedergabe für ${action} fehlgeschlagen: ${error}'; + // Translations + @override + String get markedAsWatched => 'Als gesehen markiert'; + @override + String get markedAsUnwatched => 'Als ungesehen markiert'; + @override + String get removedFromContinueWatching => 'Aus ‚Weiterschauen‘ entfernt'; + @override + String errorLoading({required Object error}) => 'Fehler: ${error}'; + @override + String get fileInfoNotAvailable => 'Dateiinfo nicht verfügbar'; + @override + String errorLoadingFileInfo({required Object error}) => + 'Fehler beim Laden der Dateiinfo: ${error}'; + @override + String get errorLoadingSeries => 'Fehler beim Laden der Serie'; + @override + String get errorLoadingSeason => 'Fehler beim Laden der Staffel'; + @override + String get musicNotSupported => 'Musikwiedergabe wird noch nicht unterstützt'; + @override + String get logsCleared => 'Protokolle gelöscht'; + @override + String get logsCopied => 'Protokolle in Zwischenablage kopiert'; + @override + String get noLogsAvailable => 'Keine Protokolle verfügbar'; + @override + String libraryScanning({required Object title}) => 'Scanne „${title}“...'; + @override + String libraryScanStarted({required Object title}) => + 'Mediathekscan gestartet für „${title}“'; + @override + String libraryScanFailed({required Object error}) => + 'Fehler beim Scannen der Mediathek: ${error}'; + @override + String metadataRefreshing({required Object title}) => + 'Metadaten werden aktualisiert für „${title}“...'; + @override + String metadataRefreshStarted({required Object title}) => + 'Metadaten-Aktualisierung gestartet für „${title}“'; + @override + String metadataRefreshFailed({required Object error}) => + 'Metadaten konnten nicht aktualisiert werden: ${error}'; + @override + String get noPlexToken => 'Kein Plex-Token gefunden. Bitte erneut anmelden.'; + @override + String get logoutConfirm => 'Abmeldung wirklich durchführen?'; + @override + String get noSeasonsFound => 'Keine Staffeln gefunden'; + @override + String get noEpisodesFound => 'Keine Episoden in der ersten Staffel gefunden'; + @override + String get noEpisodesFoundGeneral => 'Keine Episoden gefunden'; + @override + String get noResultsFound => 'Keine Ergebnisse gefunden'; + @override + String sleepTimerSet({required Object label}) => + 'Sleep-Timer gesetzt auf ${label}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Profilwechsel zu ${displayName} fehlgeschlagen'; + @override + String get noItemsAvailable => 'Keine Elemente verfügbar'; + @override + String get failedToCreatePlayQueue => + 'Wiedergabewarteschlange konnte nicht erstellt werden'; + @override + String get failedToCreatePlayQueueNoItems => + 'Wiedergabewarteschlange konnte nicht erstellt werden – keine Elemente'; + @override + String failedPlayback({required Object action, required Object error}) => + 'Wiedergabe für ${action} fehlgeschlagen: ${error}'; } // Path: profile class _StringsProfileDe implements _StringsProfileEn { - _StringsProfileDe._(this._root); + _StringsProfileDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get noUsersAvailable => 'Keine Benutzer verfügbar'; + // Translations + @override + String get noUsersAvailable => 'Keine Benutzer verfügbar'; } // Path: subtitlingStyling class _StringsSubtitlingStylingDe implements _StringsSubtitlingStylingEn { - _StringsSubtitlingStylingDe._(this._root); + _StringsSubtitlingStylingDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get stylingOptions => 'Stiloptionen'; - @override String get fontSize => 'Schriftgröße'; - @override String get textColor => 'Textfarbe'; - @override String get borderSize => 'Rahmengröße'; - @override String get borderColor => 'Rahmenfarbe'; - @override String get backgroundOpacity => 'Hintergrunddeckkraft'; - @override String get backgroundColor => 'Hintergrundfarbe'; + // Translations + @override + String get stylingOptions => 'Stiloptionen'; + @override + String get fontSize => 'Schriftgröße'; + @override + String get textColor => 'Textfarbe'; + @override + String get borderSize => 'Rahmengröße'; + @override + String get borderColor => 'Rahmenfarbe'; + @override + String get backgroundOpacity => 'Hintergrunddeckkraft'; + @override + String get backgroundColor => 'Hintergrundfarbe'; } // Path: dialog class _StringsDialogDe implements _StringsDialogEn { - _StringsDialogDe._(this._root); + _StringsDialogDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get confirmAction => 'Aktion bestätigen'; - @override String get areYouSure => 'Aktion wirklich ausführen?'; - @override String get cancel => 'Abbrechen'; - @override String get playNow => 'Jetzt abspielen'; + // Translations + @override + String get confirmAction => 'Aktion bestätigen'; + @override + String get areYouSure => 'Aktion wirklich ausführen?'; + @override + String get cancel => 'Abbrechen'; + @override + String get playNow => 'Jetzt abspielen'; } // Path: discover class _StringsDiscoverDe implements _StringsDiscoverEn { - _StringsDiscoverDe._(this._root); + _StringsDiscoverDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Entdecken'; - @override String get switchProfile => 'Profil wechseln'; - @override String get switchServer => 'Server wechseln'; - @override String get logout => 'Abmelden'; - @override String get noContentAvailable => 'Kein Inhalt verfügbar'; - @override String get addMediaToLibraries => 'Medien zur Mediathek hinzufügen'; - @override String get continueWatching => 'Weiterschauen'; - @override String get recentlyAdded => 'Kürzlich hinzugefügt'; - @override String get play => 'Abspielen'; - @override String get resume => 'Fortsetzen'; - @override String playEpisode({required Object season, required Object episode}) => 'S${season}, E${episode} - Abspielen'; - @override String resumeEpisode({required Object season, required Object episode}) => 'S${season}, E${episode} - Fortsetzen'; - @override String get pause => 'Pause'; - @override String get overview => 'Übersicht'; - @override String get cast => 'Besetzung'; - @override String get seasons => 'Staffeln'; - @override String get studio => 'Studio'; - @override String get rating => 'Altersfreigabe'; - @override String get watched => 'Gesehen'; - @override String episodeCount({required Object count}) => '${count} Episoden'; - @override String watchedProgress({required Object watched, required Object total}) => '${watched} von ${total} gesehen'; - @override String get movie => 'Film'; - @override String get tvShow => 'Serie'; - @override String minutesLeft({required Object minutes}) => '${minutes} Min übrig'; + // Translations + @override + String get title => 'Entdecken'; + @override + String get switchProfile => 'Profil wechseln'; + @override + String get switchServer => 'Server wechseln'; + @override + String get logout => 'Abmelden'; + @override + String get noContentAvailable => 'Kein Inhalt verfügbar'; + @override + String get addMediaToLibraries => 'Medien zur Mediathek hinzufügen'; + @override + String get continueWatching => 'Weiterschauen'; + @override + String get recentlyAdded => 'Kürzlich hinzugefügt'; + @override + String get play => 'Abspielen'; + @override + String get resume => 'Fortsetzen'; + @override + String playEpisode({required Object season, required Object episode}) => + 'S${season}, E${episode} - Abspielen'; + @override + String resumeEpisode({required Object season, required Object episode}) => + 'S${season}, E${episode} - Fortsetzen'; + @override + String get pause => 'Pause'; + @override + String get overview => 'Übersicht'; + @override + String get cast => 'Besetzung'; + @override + String get seasons => 'Staffeln'; + @override + String get studio => 'Studio'; + @override + String get rating => 'Altersfreigabe'; + @override + String get watched => 'Gesehen'; + @override + String episodeCount({required Object count}) => '${count} Episoden'; + @override + String watchedProgress({required Object watched, required Object total}) => + '${watched} von ${total} gesehen'; + @override + String get movie => 'Film'; + @override + String get tvShow => 'Serie'; + @override + String minutesLeft({required Object minutes}) => '${minutes} Min übrig'; } // Path: errors class _StringsErrorsDe implements _StringsErrorsEn { - _StringsErrorsDe._(this._root); + _StringsErrorsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String searchFailed({required Object error}) => 'Suche fehlgeschlagen: ${error}'; - @override String connectionTimeout({required Object context}) => 'Zeitüberschreitung beim Laden von ${context}'; - @override String get connectionFailed => 'Verbindung zum Plex-Server fehlgeschlagen'; - @override String failedToLoad({required Object context, required Object error}) => 'Fehler beim Laden von ${context}: ${error}'; - @override String get noClientAvailable => 'Kein Client verfügbar'; - @override String authenticationFailed({required Object error}) => 'Authentifizierung fehlgeschlagen: ${error}'; - @override String get couldNotLaunchUrl => 'Auth-URL konnte nicht geöffnet werden'; - @override String get pleaseEnterToken => 'Bitte Token eingeben'; - @override String get invalidToken => 'Ungültiges Token'; - @override String failedToVerifyToken({required Object error}) => 'Token-Verifizierung fehlgeschlagen: ${error}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Profilwechsel zu ${displayName} fehlgeschlagen'; - @override String get connectionFailedGeneric => 'Verbindung fehlgeschlagen'; + // Translations + @override + String searchFailed({required Object error}) => + 'Suche fehlgeschlagen: ${error}'; + @override + String connectionTimeout({required Object context}) => + 'Zeitüberschreitung beim Laden von ${context}'; + @override + String get connectionFailed => 'Verbindung zum Plex-Server fehlgeschlagen'; + @override + String failedToLoad({required Object context, required Object error}) => + 'Fehler beim Laden von ${context}: ${error}'; + @override + String get noClientAvailable => 'Kein Client verfügbar'; + @override + String authenticationFailed({required Object error}) => + 'Authentifizierung fehlgeschlagen: ${error}'; + @override + String get couldNotLaunchUrl => 'Auth-URL konnte nicht geöffnet werden'; + @override + String get pleaseEnterToken => 'Bitte Token eingeben'; + @override + String get invalidToken => 'Ungültiges Token'; + @override + String failedToVerifyToken({required Object error}) => + 'Token-Verifizierung fehlgeschlagen: ${error}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Profilwechsel zu ${displayName} fehlgeschlagen'; + @override + String get connectionFailedGeneric => 'Verbindung fehlgeschlagen'; } // Path: libraries class _StringsLibrariesDe implements _StringsLibrariesEn { - _StringsLibrariesDe._(this._root); + _StringsLibrariesDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Mediatheken'; - @override String get scanLibraryFiles => 'Mediatheksdateien scannen'; - @override String get scanLibrary => 'Mediathek scannen'; - @override String get analyze => 'Analysieren'; - @override String get analyzeLibrary => 'Mediathek analysieren'; - @override String get refreshMetadata => 'Metadaten aktualisieren'; - @override String get emptyTrash => 'Papierkorb leeren'; - @override String emptyingTrash({required Object title}) => 'Papierkorb für „${title}“ wird geleert...'; - @override String trashEmptied({required Object title}) => 'Papierkorb für „${title}“ geleert'; - @override String failedToEmptyTrash({required Object error}) => 'Papierkorb konnte nicht geleert werden: ${error}'; - @override String analyzing({required Object title}) => 'Analysiere „${title}“...'; - @override String analysisStarted({required Object title}) => 'Analyse gestartet für „${title}“'; - @override String failedToAnalyze({required Object error}) => 'Analyse der Mediathek fehlgeschlagen: ${error}'; - @override String get noLibrariesFound => 'Keine Mediatheken gefunden'; - @override String get thisLibraryIsEmpty => 'Diese Mediathek ist leer'; - @override String get all => 'Alle'; - @override String get clearAll => 'Alle löschen'; - @override String scanLibraryConfirm({required Object title}) => '„${title}“ wirklich scannen?'; - @override String analyzeLibraryConfirm({required Object title}) => '„${title}“ wirklich analysieren?'; - @override String refreshMetadataConfirm({required Object title}) => 'Metadaten für „${title}“ wirklich aktualisieren?'; - @override String emptyTrashConfirm({required Object title}) => 'Papierkorb für „${title}“ wirklich leeren?'; - @override String get manageLibraries => 'Mediatheken verwalten'; - @override String get sort => 'Sortieren'; - @override String get sortBy => 'Sortieren nach'; - @override String get filters => 'Filter'; - @override String loadingLibraryWithCount({required Object count}) => 'Lade Mediathek... (${count} Elemente geladen)'; - @override String get confirmActionMessage => 'Aktion wirklich durchführen?'; - @override String get showLibrary => 'Mediathek anzeigen'; - @override String get hideLibrary => 'Mediathek ausblenden'; - @override String get libraryOptions => 'Mediatheksoptionen'; - @override String get content => 'Bibliotheksinhalt'; - @override String get selectLibrary => 'Bibliothek auswählen'; - @override String filtersWithCount({required Object count}) => 'Filter (${count})'; - @override String get noRecommendations => 'Keine Empfehlungen verfügbar'; - @override String get noCollections => 'Keine Sammlungen in dieser Mediathek'; - @override String get noFoldersFound => 'Keine Ordner gefunden'; - @override String get folders => 'Ordner'; - @override late final _StringsLibrariesTabsDe tabs = _StringsLibrariesTabsDe._(_root); - @override late final _StringsLibrariesGroupingsDe groupings = _StringsLibrariesGroupingsDe._(_root); + // Translations + @override + String get title => 'Mediatheken'; + @override + String get scanLibraryFiles => 'Mediatheksdateien scannen'; + @override + String get scanLibrary => 'Mediathek scannen'; + @override + String get analyze => 'Analysieren'; + @override + String get analyzeLibrary => 'Mediathek analysieren'; + @override + String get refreshMetadata => 'Metadaten aktualisieren'; + @override + String get emptyTrash => 'Papierkorb leeren'; + @override + String emptyingTrash({required Object title}) => + 'Papierkorb für „${title}“ wird geleert...'; + @override + String trashEmptied({required Object title}) => + 'Papierkorb für „${title}“ geleert'; + @override + String failedToEmptyTrash({required Object error}) => + 'Papierkorb konnte nicht geleert werden: ${error}'; + @override + String analyzing({required Object title}) => 'Analysiere „${title}“...'; + @override + String analysisStarted({required Object title}) => + 'Analyse gestartet für „${title}“'; + @override + String failedToAnalyze({required Object error}) => + 'Analyse der Mediathek fehlgeschlagen: ${error}'; + @override + String get noLibrariesFound => 'Keine Mediatheken gefunden'; + @override + String get thisLibraryIsEmpty => 'Diese Mediathek ist leer'; + @override + String get all => 'Alle'; + @override + String get clearAll => 'Alle löschen'; + @override + String scanLibraryConfirm({required Object title}) => + '„${title}“ wirklich scannen?'; + @override + String analyzeLibraryConfirm({required Object title}) => + '„${title}“ wirklich analysieren?'; + @override + String refreshMetadataConfirm({required Object title}) => + 'Metadaten für „${title}“ wirklich aktualisieren?'; + @override + String emptyTrashConfirm({required Object title}) => + 'Papierkorb für „${title}“ wirklich leeren?'; + @override + String get manageLibraries => 'Mediatheken verwalten'; + @override + String get sort => 'Sortieren'; + @override + String get sortBy => 'Sortieren nach'; + @override + String get filters => 'Filter'; + @override + String loadingLibraryWithCount({required Object count}) => + 'Lade Mediathek... (${count} Elemente geladen)'; + @override + String get confirmActionMessage => 'Aktion wirklich durchführen?'; + @override + String get showLibrary => 'Mediathek anzeigen'; + @override + String get hideLibrary => 'Mediathek ausblenden'; + @override + String get libraryOptions => 'Mediatheksoptionen'; + @override + String get content => 'Bibliotheksinhalt'; + @override + String get selectLibrary => 'Bibliothek auswählen'; + @override + String filtersWithCount({required Object count}) => 'Filter (${count})'; + @override + String get noRecommendations => 'Keine Empfehlungen verfügbar'; + @override + String get noCollections => 'Keine Sammlungen in dieser Mediathek'; + @override + String get noFoldersFound => 'Keine Ordner gefunden'; + @override + String get folders => 'Ordner'; + @override + late final _StringsLibrariesTabsDe tabs = _StringsLibrariesTabsDe._(_root); + @override + late final _StringsLibrariesGroupingsDe groupings = + _StringsLibrariesGroupingsDe._(_root); } // Path: about class _StringsAboutDe implements _StringsAboutEn { - _StringsAboutDe._(this._root); + _StringsAboutDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Über'; - @override String get openSourceLicenses => 'Open-Source-Lizenzen'; - @override String versionLabel({required Object version}) => 'Version ${version}'; - @override String get appDescription => 'Ein schöner Plex-Client für Flutter'; - @override String get viewLicensesDescription => 'Lizenzen von Drittanbieter-Bibliotheken anzeigen'; + // Translations + @override + String get title => 'Über'; + @override + String get openSourceLicenses => 'Open-Source-Lizenzen'; + @override + String versionLabel({required Object version}) => 'Version ${version}'; + @override + String get appDescription => 'Ein schöner Plex-Client für Flutter'; + @override + String get viewLicensesDescription => + 'Lizenzen von Drittanbieter-Bibliotheken anzeigen'; } // Path: serverSelection class _StringsServerSelectionDe implements _StringsServerSelectionEn { - _StringsServerSelectionDe._(this._root); + _StringsServerSelectionDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get connectingToServer => 'Verbinde mit Server...'; - @override String get connectingToAllServers => 'Verbinde mit allen Servern...'; - @override String get connectToAllServers => 'Mit allen Servern verbinden'; - @override String get allServerConnectionsFailed => 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.'; - @override String foundServers({required Object count}) => '${count} Server gefunden'; - @override String get orSelectIndividually => 'Oder einzelnen Server auswählen:'; - @override String get serverDebugCopied => 'Server-Debugdaten kopiert'; - @override String get copyDebugData => 'Debugdaten kopieren'; - @override String get noServersFound => 'Keine Server gefunden'; - @override String malformedServerData({required Object count}) => '${count} Server mit fehlerhaften Daten gefunden. Keine gültigen Server verfügbar.'; - @override String get incompleteServerInfo => 'Einige Serverinformationen sind unvollständig und wurden übersprungen. Plex.tv-Konto prüfen.'; - @override String get incompleteConnectionInfo => 'Serververbindungsinformationen unvollständig. Bitte erneut versuchen.'; - @override String malformedServerInfo({required Object message}) => 'Serverinformationen fehlerhaft: ${message}'; - @override String get networkConnectionFailed => 'Netzwerkverbindung fehlgeschlagen. Internetverbindung prüfen und erneut versuchen.'; - @override String get authenticationFailed => 'Authentifizierung fehlgeschlagen. Bitte erneut anmelden.'; - @override String get plexServiceUnavailable => 'Plex-Dienst nicht verfügbar. Bitte später erneut versuchen.'; - @override String failedToLoadServers({required Object error}) => 'Server konnten nicht geladen werden: ${error}'; + // Translations + @override + String get connectingToServer => 'Verbinde mit Server...'; + @override + String get connectingToAllServers => 'Verbinde mit allen Servern...'; + @override + String get connectToAllServers => 'Mit allen Servern verbinden'; + @override + String get allServerConnectionsFailed => + 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.'; + @override + String foundServers({required Object count}) => '${count} Server gefunden'; + @override + String get orSelectIndividually => 'Oder einzelnen Server auswählen:'; + @override + String get serverDebugCopied => 'Server-Debugdaten kopiert'; + @override + String get copyDebugData => 'Debugdaten kopieren'; + @override + String get noServersFound => 'Keine Server gefunden'; + @override + String malformedServerData({required Object count}) => + '${count} Server mit fehlerhaften Daten gefunden. Keine gültigen Server verfügbar.'; + @override + String get incompleteServerInfo => + 'Einige Serverinformationen sind unvollständig und wurden übersprungen. Plex.tv-Konto prüfen.'; + @override + String get incompleteConnectionInfo => + 'Serververbindungsinformationen unvollständig. Bitte erneut versuchen.'; + @override + String malformedServerInfo({required Object message}) => + 'Serverinformationen fehlerhaft: ${message}'; + @override + String get networkConnectionFailed => + 'Netzwerkverbindung fehlgeschlagen. Internetverbindung prüfen und erneut versuchen.'; + @override + String get authenticationFailed => + 'Authentifizierung fehlgeschlagen. Bitte erneut anmelden.'; + @override + String get plexServiceUnavailable => + 'Plex-Dienst nicht verfügbar. Bitte später erneut versuchen.'; + @override + String failedToLoadServers({required Object error}) => + 'Server konnten nicht geladen werden: ${error}'; } // Path: hubDetail class _StringsHubDetailDe implements _StringsHubDetailEn { - _StringsHubDetailDe._(this._root); + _StringsHubDetailDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Titel'; - @override String get releaseYear => 'Erscheinungsjahr'; - @override String get dateAdded => 'Hinzugefügt am'; - @override String get rating => 'Bewertung'; - @override String get noItemsFound => 'Keine Elemente gefunden'; + // Translations + @override + String get title => 'Titel'; + @override + String get releaseYear => 'Erscheinungsjahr'; + @override + String get dateAdded => 'Hinzugefügt am'; + @override + String get rating => 'Bewertung'; + @override + String get noItemsFound => 'Keine Elemente gefunden'; } // Path: logs class _StringsLogsDe implements _StringsLogsEn { - _StringsLogsDe._(this._root); + _StringsLogsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Protokolle'; - @override String get clearLogs => 'Protokolle löschen'; - @override String get copyLogs => 'Protokolle kopieren'; - @override String get exportLogs => 'Protokolle exportieren'; - @override String get noLogsToShow => 'Keine Protokolle zum Anzeigen'; - @override String get error => 'Fehler:'; - @override String get stackTrace => 'Stacktrace:'; + // Translations + @override + String get title => 'Protokolle'; + @override + String get clearLogs => 'Protokolle löschen'; + @override + String get copyLogs => 'Protokolle kopieren'; + @override + String get exportLogs => 'Protokolle exportieren'; + @override + String get noLogsToShow => 'Keine Protokolle zum Anzeigen'; + @override + String get error => 'Fehler:'; + @override + String get stackTrace => 'Stacktrace:'; } // Path: licenses class _StringsLicensesDe implements _StringsLicensesEn { - _StringsLicensesDe._(this._root); + _StringsLicensesDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get relatedPackages => 'Verwandte Pakete'; - @override String get license => 'Lizenz'; - @override String licenseNumber({required Object number}) => 'Lizenz ${number}'; - @override String licensesCount({required Object count}) => '${count} Lizenzen'; + // Translations + @override + String get relatedPackages => 'Verwandte Pakete'; + @override + String get license => 'Lizenz'; + @override + String licenseNumber({required Object number}) => 'Lizenz ${number}'; + @override + String licensesCount({required Object count}) => '${count} Lizenzen'; } // Path: navigation class _StringsNavigationDe implements _StringsNavigationEn { - _StringsNavigationDe._(this._root); + _StringsNavigationDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get home => 'Start'; - @override String get search => 'Suche'; - @override String get libraries => 'Mediatheken'; - @override String get settings => 'Einstellungen'; + // Translations + @override + String get home => 'Start'; + @override + String get search => 'Suche'; + @override + String get libraries => 'Mediatheken'; + @override + String get settings => 'Einstellungen'; } // Path: playlists class _StringsPlaylistsDe implements _StringsPlaylistsEn { - _StringsPlaylistsDe._(this._root); + _StringsPlaylistsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Wiedergabelisten'; - @override String get noPlaylists => 'Keine Wiedergabelisten gefunden'; - @override String get create => 'Wiedergabeliste erstellen'; - @override String get newPlaylist => 'Neue Wiedergabeliste'; - @override String get playlistName => 'Name der Wiedergabeliste'; - @override String get enterPlaylistName => 'Name der Wiedergabeliste eingeben'; - @override String get edit => 'Wiedergabeliste bearbeiten'; - @override String get delete => 'Wiedergabeliste löschen'; - @override String get addTo => 'Zur Wiedergabeliste hinzufügen'; - @override String get addItems => 'Elemente hinzufügen'; - @override String get removeItem => 'Aus Wiedergabeliste entfernen'; - @override String get clearPlaylist => 'Wiedergabeliste leeren'; - @override String get playAll => 'Alle abspielen'; - @override String get shuffle => 'Zufällig'; - @override String get smartPlaylist => 'Intelligente Wiedergabeliste'; - @override String get regularPlaylist => 'Normale Wiedergabeliste'; - @override String itemCount({required Object count}) => '${count} Elemente'; - @override String get oneItem => '1 Element'; - @override String get emptyPlaylist => 'Diese Wiedergabeliste ist leer'; - @override String get deleteConfirm => 'Wiedergabeliste löschen?'; - @override String deleteMessage({required Object name}) => 'Soll "${name}" wirklich gelöscht werden?'; - @override String get created => 'Wiedergabeliste erstellt'; - @override String get updated => 'Wiedergabeliste aktualisiert'; - @override String get deleted => 'Wiedergabeliste gelöscht'; - @override String get itemAdded => 'Zur Wiedergabeliste hinzugefügt'; - @override String get itemRemoved => 'Aus Wiedergabeliste entfernt'; - @override String get selectPlaylist => 'Wiedergabeliste auswählen'; - @override String get createNewPlaylist => 'Neue Wiedergabeliste erstellen'; - @override String get errorCreating => 'Wiedergabeliste konnte nicht erstellt werden'; - @override String get errorDeleting => 'Wiedergabeliste konnte nicht gelöscht werden'; - @override String get errorLoading => 'Wiedergabelisten konnten nicht geladen werden'; - @override String get errorAdding => 'Konnte nicht zur Wiedergabeliste hinzugefügt werden'; - @override String get errorReordering => 'Element der Wiedergabeliste konnte nicht neu geordnet werden'; - @override String get errorRemoving => 'Konnte nicht aus der Wiedergabeliste entfernt werden'; - @override String get playlist => 'Wiedergabeliste'; + // Translations + @override + String get title => 'Wiedergabelisten'; + @override + String get noPlaylists => 'Keine Wiedergabelisten gefunden'; + @override + String get create => 'Wiedergabeliste erstellen'; + @override + String get newPlaylist => 'Neue Wiedergabeliste'; + @override + String get playlistName => 'Name der Wiedergabeliste'; + @override + String get enterPlaylistName => 'Name der Wiedergabeliste eingeben'; + @override + String get edit => 'Wiedergabeliste bearbeiten'; + @override + String get delete => 'Wiedergabeliste löschen'; + @override + String get addTo => 'Zur Wiedergabeliste hinzufügen'; + @override + String get addItems => 'Elemente hinzufügen'; + @override + String get removeItem => 'Aus Wiedergabeliste entfernen'; + @override + String get clearPlaylist => 'Wiedergabeliste leeren'; + @override + String get playAll => 'Alle abspielen'; + @override + String get shuffle => 'Zufällig'; + @override + String get smartPlaylist => 'Intelligente Wiedergabeliste'; + @override + String get regularPlaylist => 'Normale Wiedergabeliste'; + @override + String itemCount({required Object count}) => '${count} Elemente'; + @override + String get oneItem => '1 Element'; + @override + String get emptyPlaylist => 'Diese Wiedergabeliste ist leer'; + @override + String get deleteConfirm => 'Wiedergabeliste löschen?'; + @override + String deleteMessage({required Object name}) => + 'Soll "${name}" wirklich gelöscht werden?'; + @override + String get created => 'Wiedergabeliste erstellt'; + @override + String get updated => 'Wiedergabeliste aktualisiert'; + @override + String get deleted => 'Wiedergabeliste gelöscht'; + @override + String get itemAdded => 'Zur Wiedergabeliste hinzugefügt'; + @override + String get itemRemoved => 'Aus Wiedergabeliste entfernt'; + @override + String get selectPlaylist => 'Wiedergabeliste auswählen'; + @override + String get createNewPlaylist => 'Neue Wiedergabeliste erstellen'; + @override + String get errorCreating => 'Wiedergabeliste konnte nicht erstellt werden'; + @override + String get errorDeleting => 'Wiedergabeliste konnte nicht gelöscht werden'; + @override + String get errorLoading => 'Wiedergabelisten konnten nicht geladen werden'; + @override + String get errorAdding => + 'Konnte nicht zur Wiedergabeliste hinzugefügt werden'; + @override + String get errorReordering => + 'Element der Wiedergabeliste konnte nicht neu geordnet werden'; + @override + String get errorRemoving => + 'Konnte nicht aus der Wiedergabeliste entfernt werden'; + @override + String get playlist => 'Wiedergabeliste'; } // Path: collections class _StringsCollectionsDe implements _StringsCollectionsEn { - _StringsCollectionsDe._(this._root); + _StringsCollectionsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get title => 'Sammlungen'; - @override String get collection => 'Sammlung'; - @override String get empty => 'Sammlung ist leer'; - @override String get noItems => 'Keine Elemente in dieser Sammlung'; - @override String get unknownLibrarySection => 'Löschen nicht möglich: Unbekannte Bibliothekssektion'; - @override String get deleteCollection => 'Sammlung löschen'; - @override String deleteConfirm({required Object title}) => 'Sind Sie sicher, dass Sie "${title}" löschen möchten? Dies kann nicht rückgängig gemacht werden.'; - @override String get deleted => 'Sammlung gelöscht'; - @override String get deleteFailed => 'Sammlung konnte nicht gelöscht werden'; - @override String deleteFailedWithError({required Object error}) => 'Sammlung konnte nicht gelöscht werden: ${error}'; - @override String failedToLoadItems({required Object error}) => 'Sammlungselemente konnten nicht geladen werden: ${error}'; - @override String get addTo => 'Zur Sammlung hinzufügen'; - @override String get selectCollection => 'Sammlung auswählen'; - @override String get createNewCollection => 'Neue Sammlung erstellen'; - @override String get collectionName => 'Sammlungsname'; - @override String get enterCollectionName => 'Sammlungsnamen eingeben'; - @override String get addedToCollection => 'Zur Sammlung hinzugefügt'; - @override String get errorAddingToCollection => 'Fehler beim Hinzufügen zur Sammlung'; - @override String get created => 'Sammlung erstellt'; - @override String get removeFromCollection => 'Aus Sammlung entfernen'; - @override String removeFromCollectionConfirm({required Object title}) => '"${title}" aus dieser Sammlung entfernen?'; - @override String get removedFromCollection => 'Aus Sammlung entfernt'; - @override String get removeFromCollectionFailed => 'Entfernen aus Sammlung fehlgeschlagen'; - @override String removeFromCollectionError({required Object error}) => 'Fehler beim Entfernen aus der Sammlung: ${error}'; + // Translations + @override + String get title => 'Sammlungen'; + @override + String get collection => 'Sammlung'; + @override + String get empty => 'Sammlung ist leer'; + @override + String get noItems => 'Keine Elemente in dieser Sammlung'; + @override + String get unknownLibrarySection => + 'Löschen nicht möglich: Unbekannte Bibliothekssektion'; + @override + String get deleteCollection => 'Sammlung löschen'; + @override + String deleteConfirm({required Object title}) => + 'Sind Sie sicher, dass Sie "${title}" löschen möchten? Dies kann nicht rückgängig gemacht werden.'; + @override + String get deleted => 'Sammlung gelöscht'; + @override + String get deleteFailed => 'Sammlung konnte nicht gelöscht werden'; + @override + String deleteFailedWithError({required Object error}) => + 'Sammlung konnte nicht gelöscht werden: ${error}'; + @override + String failedToLoadItems({required Object error}) => + 'Sammlungselemente konnten nicht geladen werden: ${error}'; + @override + String get addTo => 'Zur Sammlung hinzufügen'; + @override + String get selectCollection => 'Sammlung auswählen'; + @override + String get createNewCollection => 'Neue Sammlung erstellen'; + @override + String get collectionName => 'Sammlungsname'; + @override + String get enterCollectionName => 'Sammlungsnamen eingeben'; + @override + String get addedToCollection => 'Zur Sammlung hinzugefügt'; + @override + String get errorAddingToCollection => 'Fehler beim Hinzufügen zur Sammlung'; + @override + String get created => 'Sammlung erstellt'; + @override + String get removeFromCollection => 'Aus Sammlung entfernen'; + @override + String removeFromCollectionConfirm({required Object title}) => + '"${title}" aus dieser Sammlung entfernen?'; + @override + String get removedFromCollection => 'Aus Sammlung entfernt'; + @override + String get removeFromCollectionFailed => + 'Entfernen aus Sammlung fehlgeschlagen'; + @override + String removeFromCollectionError({required Object error}) => + 'Fehler beim Entfernen aus der Sammlung: ${error}'; } // Path: libraries.tabs class _StringsLibrariesTabsDe implements _StringsLibrariesTabsEn { - _StringsLibrariesTabsDe._(this._root); + _StringsLibrariesTabsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get recommended => 'Empfohlen'; - @override String get browse => 'Durchsuchen'; - @override String get collections => 'Sammlungen'; - @override String get playlists => 'Wiedergabelisten'; + // Translations + @override + String get recommended => 'Empfohlen'; + @override + String get browse => 'Durchsuchen'; + @override + String get collections => 'Sammlungen'; + @override + String get playlists => 'Wiedergabelisten'; } // Path: libraries.groupings class _StringsLibrariesGroupingsDe implements _StringsLibrariesGroupingsEn { - _StringsLibrariesGroupingsDe._(this._root); + _StringsLibrariesGroupingsDe._(this._root); - @override final _StringsDe _root; // ignore: unused_field + @override + final _StringsDe _root; // ignore: unused_field - // Translations - @override String get all => 'Alle'; - @override String get movies => 'Filme'; - @override String get shows => 'Serien'; - @override String get seasons => 'Staffeln'; - @override String get episodes => 'Episoden'; - @override String get folders => 'Ordner'; + // Translations + @override + String get all => 'Alle'; + @override + String get movies => 'Filme'; + @override + String get shows => 'Serien'; + @override + String get seasons => 'Staffeln'; + @override + String get episodes => 'Episoden'; + @override + String get folders => 'Ordner'; } // Path: class _StringsIt implements Translations { - /// You can call this constructor and build your own translation instance of this locale. - /// Constructing via the enum [AppLocale.build] is preferred. - _StringsIt.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) - : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), - $meta = TranslationMetadata( - locale: AppLocale.it, - overrides: overrides ?? {}, - cardinalResolver: cardinalResolver, - ordinalResolver: ordinalResolver, - ) { - $meta.setFlatMapFunction(_flatMapFunction); - } + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsIt.build({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) : assert( + overrides == null, + 'Set "translation_overrides: true" in order to enable this feature.', + ), + $meta = TranslationMetadata( + locale: AppLocale.it, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } - /// Metadata for the translations of . - @override final TranslationMetadata $meta; + /// Metadata for the translations of . + @override + final TranslationMetadata $meta; - /// Access flat map - @override dynamic operator[](String key) => $meta.getTranslation(key); + /// Access flat map + @override + dynamic operator [](String key) => $meta.getTranslation(key); - @override late final _StringsIt _root = this; // ignore: unused_field + @override + late final _StringsIt _root = this; // ignore: unused_field - // Translations - @override late final _StringsAppIt app = _StringsAppIt._(_root); - @override late final _StringsAuthIt auth = _StringsAuthIt._(_root); - @override late final _StringsCommonIt common = _StringsCommonIt._(_root); - @override late final _StringsScreensIt screens = _StringsScreensIt._(_root); - @override late final _StringsUpdateIt update = _StringsUpdateIt._(_root); - @override late final _StringsSettingsIt settings = _StringsSettingsIt._(_root); - @override late final _StringsSearchIt search = _StringsSearchIt._(_root); - @override late final _StringsHotkeysIt hotkeys = _StringsHotkeysIt._(_root); - @override late final _StringsPinEntryIt pinEntry = _StringsPinEntryIt._(_root); - @override late final _StringsFileInfoIt fileInfo = _StringsFileInfoIt._(_root); - @override late final _StringsMediaMenuIt mediaMenu = _StringsMediaMenuIt._(_root); - @override late final _StringsAccessibilityIt accessibility = _StringsAccessibilityIt._(_root); - @override late final _StringsTooltipsIt tooltips = _StringsTooltipsIt._(_root); - @override late final _StringsVideoControlsIt videoControls = _StringsVideoControlsIt._(_root); - @override late final _StringsUserStatusIt userStatus = _StringsUserStatusIt._(_root); - @override late final _StringsMessagesIt messages = _StringsMessagesIt._(_root); - @override late final _StringsProfileIt profile = _StringsProfileIt._(_root); - @override late final _StringsSubtitlingStylingIt subtitlingStyling = _StringsSubtitlingStylingIt._(_root); - @override late final _StringsDialogIt dialog = _StringsDialogIt._(_root); - @override late final _StringsDiscoverIt discover = _StringsDiscoverIt._(_root); - @override late final _StringsErrorsIt errors = _StringsErrorsIt._(_root); - @override late final _StringsLibrariesIt libraries = _StringsLibrariesIt._(_root); - @override late final _StringsAboutIt about = _StringsAboutIt._(_root); - @override late final _StringsServerSelectionIt serverSelection = _StringsServerSelectionIt._(_root); - @override late final _StringsHubDetailIt hubDetail = _StringsHubDetailIt._(_root); - @override late final _StringsLogsIt logs = _StringsLogsIt._(_root); - @override late final _StringsLicensesIt licenses = _StringsLicensesIt._(_root); - @override late final _StringsNavigationIt navigation = _StringsNavigationIt._(_root); - @override late final _StringsPlaylistsIt playlists = _StringsPlaylistsIt._(_root); - @override late final _StringsCollectionsIt collections = _StringsCollectionsIt._(_root); + // Translations + @override + late final _StringsAppIt app = _StringsAppIt._(_root); + @override + late final _StringsAuthIt auth = _StringsAuthIt._(_root); + @override + late final _StringsCommonIt common = _StringsCommonIt._(_root); + @override + late final _StringsScreensIt screens = _StringsScreensIt._(_root); + @override + late final _StringsUpdateIt update = _StringsUpdateIt._(_root); + @override + late final _StringsSettingsIt settings = _StringsSettingsIt._(_root); + @override + late final _StringsSearchIt search = _StringsSearchIt._(_root); + @override + late final _StringsHotkeysIt hotkeys = _StringsHotkeysIt._(_root); + @override + late final _StringsPinEntryIt pinEntry = _StringsPinEntryIt._(_root); + @override + late final _StringsFileInfoIt fileInfo = _StringsFileInfoIt._(_root); + @override + late final _StringsMediaMenuIt mediaMenu = _StringsMediaMenuIt._(_root); + @override + late final _StringsAccessibilityIt accessibility = _StringsAccessibilityIt._( + _root, + ); + @override + late final _StringsTooltipsIt tooltips = _StringsTooltipsIt._(_root); + @override + late final _StringsVideoControlsIt videoControls = _StringsVideoControlsIt._( + _root, + ); + @override + late final _StringsUserStatusIt userStatus = _StringsUserStatusIt._(_root); + @override + late final _StringsMessagesIt messages = _StringsMessagesIt._(_root); + @override + late final _StringsProfileIt profile = _StringsProfileIt._(_root); + @override + late final _StringsSubtitlingStylingIt subtitlingStyling = + _StringsSubtitlingStylingIt._(_root); + @override + late final _StringsDialogIt dialog = _StringsDialogIt._(_root); + @override + late final _StringsDiscoverIt discover = _StringsDiscoverIt._(_root); + @override + late final _StringsErrorsIt errors = _StringsErrorsIt._(_root); + @override + late final _StringsLibrariesIt libraries = _StringsLibrariesIt._(_root); + @override + late final _StringsAboutIt about = _StringsAboutIt._(_root); + @override + late final _StringsServerSelectionIt serverSelection = + _StringsServerSelectionIt._(_root); + @override + late final _StringsHubDetailIt hubDetail = _StringsHubDetailIt._(_root); + @override + late final _StringsLogsIt logs = _StringsLogsIt._(_root); + @override + late final _StringsLicensesIt licenses = _StringsLicensesIt._(_root); + @override + late final _StringsNavigationIt navigation = _StringsNavigationIt._(_root); + @override + late final _StringsPlaylistsIt playlists = _StringsPlaylistsIt._(_root); + @override + late final _StringsCollectionsIt collections = _StringsCollectionsIt._(_root); } // Path: app class _StringsAppIt implements _StringsAppEn { - _StringsAppIt._(this._root); + _StringsAppIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Plezy'; - @override String get loading => 'Caricamento...'; + // Translations + @override + String get title => 'Plezy'; + @override + String get loading => 'Caricamento...'; } // Path: auth class _StringsAuthIt implements _StringsAuthEn { - _StringsAuthIt._(this._root); + _StringsAuthIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get signInWithPlex => 'Accedi con Plex'; - @override String get showQRCode => 'Mostra QR Code'; - @override String get cancel => 'Cancella'; - @override String get authenticate => 'Autenticazione'; - @override String get retry => 'Riprova'; - @override String get debugEnterToken => 'Debug: Inserisci Token Plex'; - @override String get plexTokenLabel => 'Token Auth Plex'; - @override String get plexTokenHint => 'Inserisci il tuo token di Plex.tv'; - @override String get authenticationTimeout => 'Autenticazione scaduta. Riprova.'; - @override String get scanQRCodeInstruction => 'Scansiona questo QR code con un dispositivo connesso a Plex per autenticarti.'; - @override String get waitingForAuth => 'In attesa di autenticazione...\nCompleta l\'accesso dal tuo browser.'; + // Translations + @override + String get signInWithPlex => 'Accedi con Plex'; + @override + String get showQRCode => 'Mostra QR Code'; + @override + String get cancel => 'Cancella'; + @override + String get authenticate => 'Autenticazione'; + @override + String get retry => 'Riprova'; + @override + String get debugEnterToken => 'Debug: Inserisci Token Plex'; + @override + String get plexTokenLabel => 'Token Auth Plex'; + @override + String get plexTokenHint => 'Inserisci il tuo token di Plex.tv'; + @override + String get authenticationTimeout => 'Autenticazione scaduta. Riprova.'; + @override + String get scanQRCodeInstruction => + 'Scansiona questo QR code con un dispositivo connesso a Plex per autenticarti.'; + @override + String get waitingForAuth => + 'In attesa di autenticazione...\nCompleta l\'accesso dal tuo browser.'; } // Path: common class _StringsCommonIt implements _StringsCommonEn { - _StringsCommonIt._(this._root); + _StringsCommonIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get cancel => 'Cancella'; - @override String get save => 'Salva'; - @override String get close => 'Chiudi'; - @override String get clear => 'Pulisci'; - @override String get reset => 'Ripristina'; - @override String get later => 'Più tardi'; - @override String get submit => 'Invia'; - @override String get confirm => 'Conferma'; - @override String get retry => 'Riprova'; - @override String get playNow => 'Riproduci ora'; - @override String get logout => 'Disconnetti'; - @override String get online => 'Online'; - @override String get offline => 'Offline'; - @override String get owned => 'Di proprietà'; - @override String get shared => 'Condiviso'; - @override String get current => 'CORRENTE'; - @override String get unknown => 'Sconosciuto'; - @override String get refresh => 'Aggiorna'; - @override String get yes => 'Sì'; - @override String get no => 'No'; - @override String get server => 'Server'; - @override String get delete => 'Elimina'; - @override String get shuffle => 'Casuale'; - @override String get addTo => 'Aggiungi a...'; + // Translations + @override + String get cancel => 'Cancella'; + @override + String get save => 'Salva'; + @override + String get close => 'Chiudi'; + @override + String get clear => 'Pulisci'; + @override + String get reset => 'Ripristina'; + @override + String get later => 'Più tardi'; + @override + String get submit => 'Invia'; + @override + String get confirm => 'Conferma'; + @override + String get retry => 'Riprova'; + @override + String get playNow => 'Riproduci ora'; + @override + String get logout => 'Disconnetti'; + @override + String get online => 'Online'; + @override + String get offline => 'Offline'; + @override + String get owned => 'Di proprietà'; + @override + String get shared => 'Condiviso'; + @override + String get current => 'CORRENTE'; + @override + String get unknown => 'Sconosciuto'; + @override + String get refresh => 'Aggiorna'; + @override + String get yes => 'Sì'; + @override + String get no => 'No'; + @override + String get server => 'Server'; + @override + String get delete => 'Elimina'; + @override + String get shuffle => 'Casuale'; + @override + String get addTo => 'Aggiungi a...'; } // Path: screens class _StringsScreensIt implements _StringsScreensEn { - _StringsScreensIt._(this._root); + _StringsScreensIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get licenses => 'Licenze'; - @override String get selectServer => 'Seleziona server'; - @override String get switchProfile => 'Cambia profilo'; - @override String get subtitleStyling => 'Stile sottotitoli'; - @override String get search => 'Cerca'; - @override String get logs => 'Registro'; + // Translations + @override + String get licenses => 'Licenze'; + @override + String get selectServer => 'Seleziona server'; + @override + String get switchProfile => 'Cambia profilo'; + @override + String get subtitleStyling => 'Stile sottotitoli'; + @override + String get search => 'Cerca'; + @override + String get logs => 'Registro'; } // Path: update class _StringsUpdateIt implements _StringsUpdateEn { - _StringsUpdateIt._(this._root); + _StringsUpdateIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get available => 'Aggiornamento disponibile'; - @override String versionAvailable({required Object version}) => 'Versione ${version} disponibile'; - @override String currentVersion({required Object version}) => 'Corrente: ${version}'; - @override String get skipVersion => 'Salta questa versione'; - @override String get viewRelease => 'Visualizza dettagli release'; - @override String get latestVersion => 'La versione installata è l\'ultima disponibile'; - @override String get checkFailed => 'Impossibile controllare gli aggiornamenti'; + // Translations + @override + String get available => 'Aggiornamento disponibile'; + @override + String versionAvailable({required Object version}) => + 'Versione ${version} disponibile'; + @override + String currentVersion({required Object version}) => 'Corrente: ${version}'; + @override + String get skipVersion => 'Salta questa versione'; + @override + String get viewRelease => 'Visualizza dettagli release'; + @override + String get latestVersion => 'La versione installata è l\'ultima disponibile'; + @override + String get checkFailed => 'Impossibile controllare gli aggiornamenti'; } // Path: settings class _StringsSettingsIt implements _StringsSettingsEn { - _StringsSettingsIt._(this._root); + _StringsSettingsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Impostazioni'; - @override String get language => 'Lingua'; - @override String get theme => 'Tema'; - @override String get appearance => 'Aspetto'; - @override String get videoPlayback => 'Riproduzione video'; - @override String get shufflePlay => 'Riproduzione casuale'; - @override String get advanced => 'Avanzate'; - @override String get useSeasonPostersDescription => 'Mostra il poster della stagione invece del poster della serie per gli episodi'; - @override String get showHeroSectionDescription => 'Visualizza il carosello dei contenuti in primo piano sulla schermata iniziale'; - @override String get secondsLabel => 'Secondi'; - @override String get minutesLabel => 'Minuti'; - @override String get secondsShort => 's'; - @override String get minutesShort => 'm'; - @override String durationHint({required Object min, required Object max}) => 'Inserisci durata (${min}-${max})'; - @override String get systemTheme => 'Sistema'; - @override String get systemThemeDescription => 'Segui le impostazioni di sistema'; - @override String get lightTheme => 'Chiaro'; - @override String get darkTheme => 'Scuro'; - @override String get libraryDensity => 'Densità libreria'; - @override String get compact => 'Compatta'; - @override String get compactDescription => 'Schede più piccole, più elementi visibili'; - @override String get normal => 'Normale'; - @override String get normalDescription => 'Dimensione predefinita'; - @override String get comfortable => 'Comoda'; - @override String get comfortableDescription => 'Schede più grandi, meno elementi visibili'; - @override String get viewMode => 'Modalità di visualizzazione'; - @override String get gridView => 'Griglia'; - @override String get gridViewDescription => 'Visualizza gli elementi in un layout a griglia'; - @override String get listView => 'Elenco'; - @override String get listViewDescription => 'Visualizza gli elementi in un layout a elenco'; - @override String get useSeasonPosters => 'Usa poster delle stagioni'; - @override String get showHeroSection => 'Mostra sezione principale'; - @override String get hardwareDecoding => 'Decodifica Hardware'; - @override String get hardwareDecodingDescription => 'Utilizza l\'accelerazione hardware quando disponibile'; - @override String get bufferSize => 'Dimensione buffer'; - @override String bufferSizeMB({required Object size}) => '${size}MB'; - @override String get subtitleStyling => 'Stile sottotitoli'; - @override String get subtitleStylingDescription => 'Personalizza l\'aspetto dei sottotitoli'; - @override String get smallSkipDuration => 'Durata skip breve'; - @override String get largeSkipDuration => 'Durata skip lungo'; - @override String secondsUnit({required Object seconds}) => '${seconds} secondi'; - @override String get defaultSleepTimer => 'Timer spegnimento predefinito'; - @override String minutesUnit({required Object minutes}) => '${minutes} minuti'; - @override String get rememberTrackSelections => 'Ricorda selezioni tracce per serie/film'; - @override String get rememberTrackSelectionsDescription => 'Salva automaticamente le preferenze delle lingue audio e sottotitoli quando cambi tracce durante la riproduzione'; - @override String get unwatchedOnly => 'Solo non guardati'; - @override String get unwatchedOnlyDescription => 'Includi solo gli episodi non guardati nella coda di riproduzione casuale'; - @override String get shuffleOrderNavigation => 'Navigazione in ordine casuale'; - @override String get shuffleOrderNavigationDescription => 'I pulsanti Avanti/Indietro seguono l\'ordine casuale'; - @override String get loopShuffleQueue => 'Coda di riproduzione casuale in loop'; - @override String get loopShuffleQueueDescription => 'Riavvia la coda quando raggiungi la fine'; - @override String get videoPlayerControls => 'Controlli del lettore video'; - @override String get keyboardShortcuts => 'Scorciatoie da tastiera'; - @override String get keyboardShortcutsDescription => 'Personalizza le scorciatoie da tastiera'; - @override String get debugLogging => 'Log di debug'; - @override String get debugLoggingDescription => 'Abilita il logging dettagliato per la risoluzione dei problemi'; - @override String get viewLogs => 'Visualizza log'; - @override String get viewLogsDescription => 'Visualizza i log dell\'applicazione'; - @override String get clearCache => 'Svuota cache'; - @override String get clearCacheDescription => 'Questa opzione cancellerà tutte le immagini e i dati memorizzati nella cache. Dopo aver cancellato la cache, l\'app potrebbe impiegare più tempo per caricare i contenuti.'; - @override String get clearCacheSuccess => 'Cache cancellata correttamente'; - @override String get resetSettings => 'Ripristina impostazioni'; - @override String get resetSettingsDescription => 'Questa opzione ripristinerà tutte le impostazioni ai valori predefiniti. Non può essere annullata.'; - @override String get resetSettingsSuccess => 'Impostazioni ripristinate correttamente'; - @override String get shortcutsReset => 'Scorciatoie ripristinate alle impostazioni predefinite'; - @override String get about => 'Informazioni'; - @override String get aboutDescription => 'Informazioni sull\'app e le licenze'; - @override String get updates => 'Aggiornamenti'; - @override String get updateAvailable => 'Aggiornamento disponibile'; - @override String get checkForUpdates => 'Controlla aggiornamenti'; - @override String get validationErrorEnterNumber => 'Inserisci un numero valido'; - @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'la durata deve essere compresa tra ${min} e ${max} ${unit}'; - @override String shortcutAlreadyAssigned({required Object action}) => 'Scorciatoia già assegnata a ${action}'; - @override String shortcutUpdated({required Object action}) => 'Scorciatoia aggiornata per ${action}'; + // Translations + @override + String get title => 'Impostazioni'; + @override + String get language => 'Lingua'; + @override + String get theme => 'Tema'; + @override + String get appearance => 'Aspetto'; + @override + String get videoPlayback => 'Riproduzione video'; + @override + String get shufflePlay => 'Riproduzione casuale'; + @override + String get advanced => 'Avanzate'; + @override + String get useSeasonPostersDescription => + 'Mostra il poster della stagione invece del poster della serie per gli episodi'; + @override + String get showHeroSectionDescription => + 'Visualizza il carosello dei contenuti in primo piano sulla schermata iniziale'; + @override + String get secondsLabel => 'Secondi'; + @override + String get minutesLabel => 'Minuti'; + @override + String get secondsShort => 's'; + @override + String get minutesShort => 'm'; + @override + String durationHint({required Object min, required Object max}) => + 'Inserisci durata (${min}-${max})'; + @override + String get systemTheme => 'Sistema'; + @override + String get systemThemeDescription => 'Segui le impostazioni di sistema'; + @override + String get lightTheme => 'Chiaro'; + @override + String get darkTheme => 'Scuro'; + @override + String get libraryDensity => 'Densità libreria'; + @override + String get compact => 'Compatta'; + @override + String get compactDescription => 'Schede più piccole, più elementi visibili'; + @override + String get normal => 'Normale'; + @override + String get normalDescription => 'Dimensione predefinita'; + @override + String get comfortable => 'Comoda'; + @override + String get comfortableDescription => + 'Schede più grandi, meno elementi visibili'; + @override + String get viewMode => 'Modalità di visualizzazione'; + @override + String get gridView => 'Griglia'; + @override + String get gridViewDescription => + 'Visualizza gli elementi in un layout a griglia'; + @override + String get listView => 'Elenco'; + @override + String get listViewDescription => + 'Visualizza gli elementi in un layout a elenco'; + @override + String get useSeasonPosters => 'Usa poster delle stagioni'; + @override + String get showHeroSection => 'Mostra sezione principale'; + @override + String get hardwareDecoding => 'Decodifica Hardware'; + @override + String get hardwareDecodingDescription => + 'Utilizza l\'accelerazione hardware quando disponibile'; + @override + String get bufferSize => 'Dimensione buffer'; + @override + String bufferSizeMB({required Object size}) => '${size}MB'; + @override + String get subtitleStyling => 'Stile sottotitoli'; + @override + String get subtitleStylingDescription => + 'Personalizza l\'aspetto dei sottotitoli'; + @override + String get smallSkipDuration => 'Durata skip breve'; + @override + String get largeSkipDuration => 'Durata skip lungo'; + @override + String secondsUnit({required Object seconds}) => '${seconds} secondi'; + @override + String get defaultSleepTimer => 'Timer spegnimento predefinito'; + @override + String minutesUnit({required Object minutes}) => '${minutes} minuti'; + @override + String get rememberTrackSelections => + 'Ricorda selezioni tracce per serie/film'; + @override + String get rememberTrackSelectionsDescription => + 'Salva automaticamente le preferenze delle lingue audio e sottotitoli quando cambi tracce durante la riproduzione'; + @override + String get unwatchedOnly => 'Solo non guardati'; + @override + String get unwatchedOnlyDescription => + 'Includi solo gli episodi non guardati nella coda di riproduzione casuale'; + @override + String get shuffleOrderNavigation => 'Navigazione in ordine casuale'; + @override + String get shuffleOrderNavigationDescription => + 'I pulsanti Avanti/Indietro seguono l\'ordine casuale'; + @override + String get loopShuffleQueue => 'Coda di riproduzione casuale in loop'; + @override + String get loopShuffleQueueDescription => + 'Riavvia la coda quando raggiungi la fine'; + @override + String get videoPlayerControls => 'Controlli del lettore video'; + @override + String get keyboardShortcuts => 'Scorciatoie da tastiera'; + @override + String get keyboardShortcutsDescription => + 'Personalizza le scorciatoie da tastiera'; + @override + String get debugLogging => 'Log di debug'; + @override + String get debugLoggingDescription => + 'Abilita il logging dettagliato per la risoluzione dei problemi'; + @override + String get viewLogs => 'Visualizza log'; + @override + String get viewLogsDescription => 'Visualizza i log dell\'applicazione'; + @override + String get clearCache => 'Svuota cache'; + @override + String get clearCacheDescription => + 'Questa opzione cancellerà tutte le immagini e i dati memorizzati nella cache. Dopo aver cancellato la cache, l\'app potrebbe impiegare più tempo per caricare i contenuti.'; + @override + String get clearCacheSuccess => 'Cache cancellata correttamente'; + @override + String get resetSettings => 'Ripristina impostazioni'; + @override + String get resetSettingsDescription => + 'Questa opzione ripristinerà tutte le impostazioni ai valori predefiniti. Non può essere annullata.'; + @override + String get resetSettingsSuccess => 'Impostazioni ripristinate correttamente'; + @override + String get shortcutsReset => + 'Scorciatoie ripristinate alle impostazioni predefinite'; + @override + String get about => 'Informazioni'; + @override + String get aboutDescription => 'Informazioni sull\'app e le licenze'; + @override + String get updates => 'Aggiornamenti'; + @override + String get updateAvailable => 'Aggiornamento disponibile'; + @override + String get checkForUpdates => 'Controlla aggiornamenti'; + @override + String get validationErrorEnterNumber => 'Inserisci un numero valido'; + @override + String validationErrorDuration({ + required Object min, + required Object max, + required Object unit, + }) => 'la durata deve essere compresa tra ${min} e ${max} ${unit}'; + @override + String shortcutAlreadyAssigned({required Object action}) => + 'Scorciatoia già assegnata a ${action}'; + @override + String shortcutUpdated({required Object action}) => + 'Scorciatoia aggiornata per ${action}'; } // Path: search class _StringsSearchIt implements _StringsSearchEn { - _StringsSearchIt._(this._root); + _StringsSearchIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get hint => 'Cerca film. spettacoli, musica...'; - @override String get tryDifferentTerm => 'Prova altri termini di ricerca'; - @override String get searchYourMedia => 'Cerca nei tuoi media'; - @override String get enterTitleActorOrKeyword => 'Inserisci un titolo, attore o parola chiave'; + // Translations + @override + String get hint => 'Cerca film. spettacoli, musica...'; + @override + String get tryDifferentTerm => 'Prova altri termini di ricerca'; + @override + String get searchYourMedia => 'Cerca nei tuoi media'; + @override + String get enterTitleActorOrKeyword => + 'Inserisci un titolo, attore o parola chiave'; } // Path: hotkeys class _StringsHotkeysIt implements _StringsHotkeysEn { - _StringsHotkeysIt._(this._root); + _StringsHotkeysIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String setShortcutFor({required Object actionName}) => 'Imposta scorciatoia per ${actionName}'; - @override String get clearShortcut => 'Elimina scorciatoia'; + // Translations + @override + String setShortcutFor({required Object actionName}) => + 'Imposta scorciatoia per ${actionName}'; + @override + String get clearShortcut => 'Elimina scorciatoia'; } // Path: pinEntry class _StringsPinEntryIt implements _StringsPinEntryEn { - _StringsPinEntryIt._(this._root); + _StringsPinEntryIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get enterPin => 'Inserisci PIN'; - @override String get showPin => 'Mostra PIN'; - @override String get hidePin => 'Nascondi PIN'; + // Translations + @override + String get enterPin => 'Inserisci PIN'; + @override + String get showPin => 'Mostra PIN'; + @override + String get hidePin => 'Nascondi PIN'; } // Path: fileInfo class _StringsFileInfoIt implements _StringsFileInfoEn { - _StringsFileInfoIt._(this._root); + _StringsFileInfoIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Info sul file'; - @override String get video => 'Video'; - @override String get audio => 'Audio'; - @override String get file => 'File'; - @override String get advanced => 'Avanzate'; - @override String get codec => 'Codec'; - @override String get resolution => 'Risoluzione'; - @override String get bitrate => 'Bitrate'; - @override String get frameRate => 'Frame Rate'; - @override String get aspectRatio => 'Aspect Ratio'; - @override String get profile => 'Profilo'; - @override String get bitDepth => 'Profondità colore'; - @override String get colorSpace => 'Spazio colore'; - @override String get colorRange => 'Gamma colori'; - @override String get colorPrimaries => 'Colori primari'; - @override String get chromaSubsampling => 'Sottocampionamento cromatico'; - @override String get channels => 'Canali'; - @override String get path => 'Percorso'; - @override String get size => 'Dimensione'; - @override String get container => 'Contenitore'; - @override String get duration => 'Durata'; - @override String get optimizedForStreaming => 'Ottimizzato per lo streaming'; - @override String get has64bitOffsets => 'Offset a 64-bit'; + // Translations + @override + String get title => 'Info sul file'; + @override + String get video => 'Video'; + @override + String get audio => 'Audio'; + @override + String get file => 'File'; + @override + String get advanced => 'Avanzate'; + @override + String get codec => 'Codec'; + @override + String get resolution => 'Risoluzione'; + @override + String get bitrate => 'Bitrate'; + @override + String get frameRate => 'Frame Rate'; + @override + String get aspectRatio => 'Aspect Ratio'; + @override + String get profile => 'Profilo'; + @override + String get bitDepth => 'Profondità colore'; + @override + String get colorSpace => 'Spazio colore'; + @override + String get colorRange => 'Gamma colori'; + @override + String get colorPrimaries => 'Colori primari'; + @override + String get chromaSubsampling => 'Sottocampionamento cromatico'; + @override + String get channels => 'Canali'; + @override + String get path => 'Percorso'; + @override + String get size => 'Dimensione'; + @override + String get container => 'Contenitore'; + @override + String get duration => 'Durata'; + @override + String get optimizedForStreaming => 'Ottimizzato per lo streaming'; + @override + String get has64bitOffsets => 'Offset a 64-bit'; } // Path: mediaMenu class _StringsMediaMenuIt implements _StringsMediaMenuEn { - _StringsMediaMenuIt._(this._root); + _StringsMediaMenuIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get markAsWatched => 'Segna come visto'; - @override String get markAsUnwatched => 'Segna come non visto'; - @override String get removeFromContinueWatching => 'Rimuovi da Continua a guardare'; - @override String get goToSeries => 'Vai alle serie'; - @override String get goToSeason => 'Vai alla stagione'; - @override String get shufflePlay => 'Riproduzione casuale'; - @override String get fileInfo => 'Info sul file'; + // Translations + @override + String get markAsWatched => 'Segna come visto'; + @override + String get markAsUnwatched => 'Segna come non visto'; + @override + String get removeFromContinueWatching => 'Rimuovi da Continua a guardare'; + @override + String get goToSeries => 'Vai alle serie'; + @override + String get goToSeason => 'Vai alla stagione'; + @override + String get shufflePlay => 'Riproduzione casuale'; + @override + String get fileInfo => 'Info sul file'; } // Path: accessibility class _StringsAccessibilityIt implements _StringsAccessibilityEn { - _StringsAccessibilityIt._(this._root); + _StringsAccessibilityIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String mediaCardMovie({required Object title}) => '${title}, film'; - @override String mediaCardShow({required Object title}) => '${title}, serie TV'; - @override String mediaCardEpisode({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - @override String mediaCardSeason({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - @override String get mediaCardWatched => 'visto'; - @override String mediaCardPartiallyWatched({required Object percent}) => '${percent} percento visto'; - @override String get mediaCardUnwatched => 'non visto'; - @override String get tapToPlay => 'Tocca per riprodurre'; + // Translations + @override + String mediaCardMovie({required Object title}) => '${title}, film'; + @override + String mediaCardShow({required Object title}) => '${title}, serie TV'; + @override + String mediaCardEpisode({ + required Object title, + required Object episodeInfo, + }) => '${title}, ${episodeInfo}'; + @override + String mediaCardSeason({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + @override + String get mediaCardWatched => 'visto'; + @override + String mediaCardPartiallyWatched({required Object percent}) => + '${percent} percento visto'; + @override + String get mediaCardUnwatched => 'non visto'; + @override + String get tapToPlay => 'Tocca per riprodurre'; } // Path: tooltips class _StringsTooltipsIt implements _StringsTooltipsEn { - _StringsTooltipsIt._(this._root); + _StringsTooltipsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get shufflePlay => 'Riproduzione casuale'; - @override String get markAsWatched => 'Segna come visto'; - @override String get markAsUnwatched => 'Segna come non visto'; + // Translations + @override + String get shufflePlay => 'Riproduzione casuale'; + @override + String get markAsWatched => 'Segna come visto'; + @override + String get markAsUnwatched => 'Segna come non visto'; } // Path: videoControls class _StringsVideoControlsIt implements _StringsVideoControlsEn { - _StringsVideoControlsIt._(this._root); + _StringsVideoControlsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get audioLabel => 'Audio'; - @override String get subtitlesLabel => 'Sottotitoli'; - @override String get resetToZero => 'Riporta a 0ms'; - @override String addTime({required Object amount, required Object unit}) => '+${amount}${unit}'; - @override String minusTime({required Object amount, required Object unit}) => '-${amount}${unit}'; - @override String playsLater({required Object label}) => '${label} riprodotto dopo'; - @override String playsEarlier({required Object label}) => '${label} riprodotto prima'; - @override String get noOffset => 'Nessun offset'; - @override String get letterbox => 'Letterbox'; - @override String get fillScreen => 'Riempi schermo'; - @override String get stretch => 'Allunga'; - @override String get lockRotation => 'Blocca rotazione'; - @override String get unlockRotation => 'Sblocca rotazione'; - @override String get sleepTimer => 'Timer di spegnimento'; - @override String get timerActive => 'Timer attivo'; - @override String playbackWillPauseIn({required Object duration}) => 'La riproduzione si interromperà tra ${duration}'; - @override String get sleepTimerCompleted => 'Timer di spegnimento completato - riproduzione in pausa'; - @override String get playButton => 'Riproduci'; - @override String get pauseButton => 'Pausa'; - @override String seekBackwardButton({required Object seconds}) => 'Riavvolgi di ${seconds} secondi'; - @override String seekForwardButton({required Object seconds}) => 'Avanza di ${seconds} secondi'; - @override String get previousButton => 'Episodio precedente'; - @override String get nextButton => 'Episodio successivo'; - @override String get previousChapterButton => 'Capitolo precedente'; - @override String get nextChapterButton => 'Capitolo successivo'; - @override String get volumeButton => 'Volume'; - @override String get muteButton => 'Silenzia'; - @override String get unmuteButton => 'Riattiva audio'; - @override String get settingsButton => 'Impostazioni video'; - @override String get audioTrackButton => 'Tracce audio'; - @override String get subtitlesButton => 'Sottotitoli'; - @override String get chaptersButton => 'Capitoli'; - @override String get versionsButton => 'Versioni video'; - @override String get aspectRatioButton => 'Proporzioni'; - @override String get fullscreenButton => 'Attiva schermo intero'; - @override String get exitFullscreenButton => 'Esci da schermo intero'; - @override String get rotationLockButton => 'Blocco rotazione'; - @override String get timelineSlider => 'Timeline video'; - @override String get volumeSlider => 'Livello volume'; - @override String get backButton => 'Indietro'; + // Translations + @override + String get audioLabel => 'Audio'; + @override + String get subtitlesLabel => 'Sottotitoli'; + @override + String get resetToZero => 'Riporta a 0ms'; + @override + String addTime({required Object amount, required Object unit}) => + '+${amount}${unit}'; + @override + String minusTime({required Object amount, required Object unit}) => + '-${amount}${unit}'; + @override + String playsLater({required Object label}) => '${label} riprodotto dopo'; + @override + String playsEarlier({required Object label}) => '${label} riprodotto prima'; + @override + String get noOffset => 'Nessun offset'; + @override + String get letterbox => 'Letterbox'; + @override + String get fillScreen => 'Riempi schermo'; + @override + String get stretch => 'Allunga'; + @override + String get lockRotation => 'Blocca rotazione'; + @override + String get unlockRotation => 'Sblocca rotazione'; + @override + String get sleepTimer => 'Timer di spegnimento'; + @override + String get timerActive => 'Timer attivo'; + @override + String playbackWillPauseIn({required Object duration}) => + 'La riproduzione si interromperà tra ${duration}'; + @override + String get sleepTimerCompleted => + 'Timer di spegnimento completato - riproduzione in pausa'; + @override + String get playButton => 'Riproduci'; + @override + String get pauseButton => 'Pausa'; + @override + String seekBackwardButton({required Object seconds}) => + 'Riavvolgi di ${seconds} secondi'; + @override + String seekForwardButton({required Object seconds}) => + 'Avanza di ${seconds} secondi'; + @override + String get previousButton => 'Episodio precedente'; + @override + String get nextButton => 'Episodio successivo'; + @override + String get previousChapterButton => 'Capitolo precedente'; + @override + String get nextChapterButton => 'Capitolo successivo'; + @override + String get volumeButton => 'Volume'; + @override + String get muteButton => 'Silenzia'; + @override + String get unmuteButton => 'Riattiva audio'; + @override + String get settingsButton => 'Impostazioni video'; + @override + String get audioTrackButton => 'Tracce audio'; + @override + String get subtitlesButton => 'Sottotitoli'; + @override + String get chaptersButton => 'Capitoli'; + @override + String get versionsButton => 'Versioni video'; + @override + String get aspectRatioButton => 'Proporzioni'; + @override + String get fullscreenButton => 'Attiva schermo intero'; + @override + String get exitFullscreenButton => 'Esci da schermo intero'; + @override + String get rotationLockButton => 'Blocco rotazione'; + @override + String get timelineSlider => 'Timeline video'; + @override + String get volumeSlider => 'Livello volume'; + @override + String get backButton => 'Indietro'; } // Path: userStatus class _StringsUserStatusIt implements _StringsUserStatusEn { - _StringsUserStatusIt._(this._root); + _StringsUserStatusIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get admin => 'Admin'; - @override String get restricted => 'Limitato'; - @override String get protected => 'Protetto'; - @override String get current => 'ATTUALE'; + // Translations + @override + String get admin => 'Admin'; + @override + String get restricted => 'Limitato'; + @override + String get protected => 'Protetto'; + @override + String get current => 'ATTUALE'; } // Path: messages class _StringsMessagesIt implements _StringsMessagesEn { - _StringsMessagesIt._(this._root); + _StringsMessagesIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get markedAsWatched => 'Segna come visto'; - @override String get markedAsUnwatched => 'Segna come non visto'; - @override String get removedFromContinueWatching => 'Rimosso da Continua a guardare'; - @override String errorLoading({required Object error}) => 'Errore: ${error}'; - @override String get fileInfoNotAvailable => 'Informazioni sul file non disponibili'; - @override String errorLoadingFileInfo({required Object error}) => 'Errore caricamento informazioni sul file: ${error}'; - @override String get errorLoadingSeries => 'Errore caricamento serie'; - @override String get errorLoadingSeason => 'Errore caricamento stagione'; - @override String get musicNotSupported => 'La riproduzione musicale non è ancora supportata'; - @override String get logsCleared => 'Log eliminati'; - @override String get logsCopied => 'Log copiati negli appunti'; - @override String get noLogsAvailable => 'Nessun log disponibile'; - @override String libraryScanning({required Object title}) => 'Scansione "${title}"...'; - @override String libraryScanStarted({required Object title}) => 'Scansione libreria iniziata per "${title}"'; - @override String libraryScanFailed({required Object error}) => 'Impossibile eseguire scansione della libreria: ${error}'; - @override String metadataRefreshing({required Object title}) => 'Aggiornamento metadati per "${title}"...'; - @override String metadataRefreshStarted({required Object title}) => 'Aggiornamento metadati per "${title}"'; - @override String metadataRefreshFailed({required Object error}) => 'Errore aggiornamento metadati: ${error}'; - @override String get noPlexToken => 'Nessun token Plex trovato. Riesegui l\'accesso.'; - @override String get logoutConfirm => 'Sei sicuro di volerti disconnettere?'; - @override String get noSeasonsFound => 'Nessuna stagione trovata'; - @override String get noEpisodesFound => 'Nessun episodio trovato nella prima stagione'; - @override String get noEpisodesFoundGeneral => 'Nessun episodio trovato'; - @override String get noResultsFound => 'Nessun risultato'; - @override String sleepTimerSet({required Object label}) => 'Imposta timer spegnimento per ${label}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Impossibile passare a ${displayName}'; - @override String get noItemsAvailable => 'Nessun elemento disponibile'; - @override String get failedToCreatePlayQueue => 'Impossibile creare la coda di riproduzione'; - @override String get failedToCreatePlayQueueNoItems => 'Impossibile creare la coda di riproduzione - nessun elemento'; - @override String failedPlayback({required Object action, required Object error}) => 'Impossibile ${action}: ${error}'; + // Translations + @override + String get markedAsWatched => 'Segna come visto'; + @override + String get markedAsUnwatched => 'Segna come non visto'; + @override + String get removedFromContinueWatching => 'Rimosso da Continua a guardare'; + @override + String errorLoading({required Object error}) => 'Errore: ${error}'; + @override + String get fileInfoNotAvailable => 'Informazioni sul file non disponibili'; + @override + String errorLoadingFileInfo({required Object error}) => + 'Errore caricamento informazioni sul file: ${error}'; + @override + String get errorLoadingSeries => 'Errore caricamento serie'; + @override + String get errorLoadingSeason => 'Errore caricamento stagione'; + @override + String get musicNotSupported => + 'La riproduzione musicale non è ancora supportata'; + @override + String get logsCleared => 'Log eliminati'; + @override + String get logsCopied => 'Log copiati negli appunti'; + @override + String get noLogsAvailable => 'Nessun log disponibile'; + @override + String libraryScanning({required Object title}) => 'Scansione "${title}"...'; + @override + String libraryScanStarted({required Object title}) => + 'Scansione libreria iniziata per "${title}"'; + @override + String libraryScanFailed({required Object error}) => + 'Impossibile eseguire scansione della libreria: ${error}'; + @override + String metadataRefreshing({required Object title}) => + 'Aggiornamento metadati per "${title}"...'; + @override + String metadataRefreshStarted({required Object title}) => + 'Aggiornamento metadati per "${title}"'; + @override + String metadataRefreshFailed({required Object error}) => + 'Errore aggiornamento metadati: ${error}'; + @override + String get noPlexToken => 'Nessun token Plex trovato. Riesegui l\'accesso.'; + @override + String get logoutConfirm => 'Sei sicuro di volerti disconnettere?'; + @override + String get noSeasonsFound => 'Nessuna stagione trovata'; + @override + String get noEpisodesFound => 'Nessun episodio trovato nella prima stagione'; + @override + String get noEpisodesFoundGeneral => 'Nessun episodio trovato'; + @override + String get noResultsFound => 'Nessun risultato'; + @override + String sleepTimerSet({required Object label}) => + 'Imposta timer spegnimento per ${label}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Impossibile passare a ${displayName}'; + @override + String get noItemsAvailable => 'Nessun elemento disponibile'; + @override + String get failedToCreatePlayQueue => + 'Impossibile creare la coda di riproduzione'; + @override + String get failedToCreatePlayQueueNoItems => + 'Impossibile creare la coda di riproduzione - nessun elemento'; + @override + String failedPlayback({required Object action, required Object error}) => + 'Impossibile ${action}: ${error}'; } // Path: profile class _StringsProfileIt implements _StringsProfileEn { - _StringsProfileIt._(this._root); + _StringsProfileIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get noUsersAvailable => 'Nessun utente disponibile'; + // Translations + @override + String get noUsersAvailable => 'Nessun utente disponibile'; } // Path: subtitlingStyling class _StringsSubtitlingStylingIt implements _StringsSubtitlingStylingEn { - _StringsSubtitlingStylingIt._(this._root); + _StringsSubtitlingStylingIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get stylingOptions => 'Opzioni stile'; - @override String get fontSize => 'Dimensione'; - @override String get textColor => 'Colore testo'; - @override String get borderSize => 'Dimensione bordo'; - @override String get borderColor => 'Colore bordo'; - @override String get backgroundOpacity => 'Opacità sfondo'; - @override String get backgroundColor => 'Colore sfondo'; + // Translations + @override + String get stylingOptions => 'Opzioni stile'; + @override + String get fontSize => 'Dimensione'; + @override + String get textColor => 'Colore testo'; + @override + String get borderSize => 'Dimensione bordo'; + @override + String get borderColor => 'Colore bordo'; + @override + String get backgroundOpacity => 'Opacità sfondo'; + @override + String get backgroundColor => 'Colore sfondo'; } // Path: dialog class _StringsDialogIt implements _StringsDialogEn { - _StringsDialogIt._(this._root); + _StringsDialogIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get confirmAction => 'Conferma azione'; - @override String get areYouSure => 'Sei sicuro di voler eseguire questa azione?'; - @override String get cancel => 'Cancella'; - @override String get playNow => 'Riproduci ora'; + // Translations + @override + String get confirmAction => 'Conferma azione'; + @override + String get areYouSure => 'Sei sicuro di voler eseguire questa azione?'; + @override + String get cancel => 'Cancella'; + @override + String get playNow => 'Riproduci ora'; } // Path: discover class _StringsDiscoverIt implements _StringsDiscoverEn { - _StringsDiscoverIt._(this._root); + _StringsDiscoverIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Esplora'; - @override String get switchProfile => 'Cambia profilo'; - @override String get switchServer => 'Cambia server'; - @override String get logout => 'Disconnetti'; - @override String get noContentAvailable => 'Nessun contenuto disponibile'; - @override String get addMediaToLibraries => 'Aggiungi alcuni file multimediali alle tue librerie'; - @override String get continueWatching => 'Continua a guardare'; - @override String get recentlyAdded => 'Aggiunti di recente'; - @override String get play => 'Riproduci'; - @override String get resume => 'Riprendi'; - @override String playEpisode({required Object season, required Object episode}) => 'Riproduci S${season}, E${episode}'; - @override String resumeEpisode({required Object season, required Object episode}) => 'Riprendi S${season}, E${episode}'; - @override String get pause => 'Pausa'; - @override String get overview => 'Panoramica'; - @override String get cast => 'Attori'; - @override String get seasons => 'Stagioni'; - @override String get studio => 'Studio'; - @override String get rating => 'Classificazione'; - @override String get watched => 'Guardato'; - @override String episodeCount({required Object count}) => '${count} episodi'; - @override String watchedProgress({required Object watched, required Object total}) => '${watched}/${total} guardati'; - @override String get movie => 'Film'; - @override String get tvShow => 'Serie TV'; - @override String minutesLeft({required Object minutes}) => '${minutes} minuti rimanenti'; + // Translations + @override + String get title => 'Esplora'; + @override + String get switchProfile => 'Cambia profilo'; + @override + String get switchServer => 'Cambia server'; + @override + String get logout => 'Disconnetti'; + @override + String get noContentAvailable => 'Nessun contenuto disponibile'; + @override + String get addMediaToLibraries => + 'Aggiungi alcuni file multimediali alle tue librerie'; + @override + String get continueWatching => 'Continua a guardare'; + @override + String get recentlyAdded => 'Aggiunti di recente'; + @override + String get play => 'Riproduci'; + @override + String get resume => 'Riprendi'; + @override + String playEpisode({required Object season, required Object episode}) => + 'Riproduci S${season}, E${episode}'; + @override + String resumeEpisode({required Object season, required Object episode}) => + 'Riprendi S${season}, E${episode}'; + @override + String get pause => 'Pausa'; + @override + String get overview => 'Panoramica'; + @override + String get cast => 'Attori'; + @override + String get seasons => 'Stagioni'; + @override + String get studio => 'Studio'; + @override + String get rating => 'Classificazione'; + @override + String get watched => 'Guardato'; + @override + String episodeCount({required Object count}) => '${count} episodi'; + @override + String watchedProgress({required Object watched, required Object total}) => + '${watched}/${total} guardati'; + @override + String get movie => 'Film'; + @override + String get tvShow => 'Serie TV'; + @override + String minutesLeft({required Object minutes}) => + '${minutes} minuti rimanenti'; } // Path: errors class _StringsErrorsIt implements _StringsErrorsEn { - _StringsErrorsIt._(this._root); + _StringsErrorsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String searchFailed({required Object error}) => 'Ricerca fallita: ${error}'; - @override String connectionTimeout({required Object context}) => 'Timeout connessione durante caricamento di ${context}'; - @override String get connectionFailed => 'Impossibile connettersi al server Plex.'; - @override String failedToLoad({required Object context, required Object error}) => 'Impossibile caricare ${context}: ${error}'; - @override String get noClientAvailable => 'Nessun client disponibile'; - @override String authenticationFailed({required Object error}) => 'Autenticazione fallita: ${error}'; - @override String get couldNotLaunchUrl => 'Impossibile avviare URL di autenticazione'; - @override String get pleaseEnterToken => 'Inserisci token'; - @override String get invalidToken => 'Token non valido'; - @override String failedToVerifyToken({required Object error}) => 'Verifica token fallita: ${error}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Impossibile passare a ${displayName}'; - @override String get connectionFailedGeneric => 'Connessione fallita'; + // Translations + @override + String searchFailed({required Object error}) => 'Ricerca fallita: ${error}'; + @override + String connectionTimeout({required Object context}) => + 'Timeout connessione durante caricamento di ${context}'; + @override + String get connectionFailed => 'Impossibile connettersi al server Plex.'; + @override + String failedToLoad({required Object context, required Object error}) => + 'Impossibile caricare ${context}: ${error}'; + @override + String get noClientAvailable => 'Nessun client disponibile'; + @override + String authenticationFailed({required Object error}) => + 'Autenticazione fallita: ${error}'; + @override + String get couldNotLaunchUrl => 'Impossibile avviare URL di autenticazione'; + @override + String get pleaseEnterToken => 'Inserisci token'; + @override + String get invalidToken => 'Token non valido'; + @override + String failedToVerifyToken({required Object error}) => + 'Verifica token fallita: ${error}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Impossibile passare a ${displayName}'; + @override + String get connectionFailedGeneric => 'Connessione fallita'; } // Path: libraries class _StringsLibrariesIt implements _StringsLibrariesEn { - _StringsLibrariesIt._(this._root); + _StringsLibrariesIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Librerie'; - @override String get scanLibraryFiles => 'Scansiona file libreria'; - @override String get scanLibrary => 'Scansiona libreria'; - @override String get analyze => 'Analizza'; - @override String get analyzeLibrary => 'Analizza libreria'; - @override String get refreshMetadata => 'Aggiorna metadati'; - @override String get emptyTrash => 'Svuota cestino'; - @override String emptyingTrash({required Object title}) => 'Svuotamento cestino per "${title}"...'; - @override String trashEmptied({required Object title}) => 'Cestino svuotato per "${title}"'; - @override String failedToEmptyTrash({required Object error}) => 'Impossibile svuotare cestino: ${error}'; - @override String analyzing({required Object title}) => 'Analisi "${title}"...'; - @override String analysisStarted({required Object title}) => 'Analisi iniziata per "${title}"'; - @override String failedToAnalyze({required Object error}) => 'Impossibile analizzare libreria: ${error}'; - @override String get noLibrariesFound => 'Nessuna libreria trovata'; - @override String get thisLibraryIsEmpty => 'Questa libreria è vuota'; - @override String get all => 'Tutto'; - @override String get clearAll => 'Cancella tutto'; - @override String scanLibraryConfirm({required Object title}) => 'Sei sicuro di voler scansionare "${title}"?'; - @override String analyzeLibraryConfirm({required Object title}) => 'Sei sicuro di voler analizzare "${title}"?'; - @override String refreshMetadataConfirm({required Object title}) => 'Sei sicuro di voler aggiornare i metadati per "${title}"?'; - @override String emptyTrashConfirm({required Object title}) => 'Sei sicuro di voler svuotare il cestino per "${title}"?'; - @override String get manageLibraries => 'Gestisci librerie'; - @override String get sort => 'Ordina'; - @override String get sortBy => 'Ordina per'; - @override String get filters => 'Filtri'; - @override String loadingLibraryWithCount({required Object count}) => 'Caricamento librerie... (${count} oggetti caricati)'; - @override String get confirmActionMessage => 'Sei sicuro di voler eseguire questa azione?'; - @override String get showLibrary => 'Mostra libreria'; - @override String get hideLibrary => 'Nascondi libreria'; - @override String get libraryOptions => 'Opzioni libreria'; - @override String get content => 'contenuto della libreria'; - @override String get selectLibrary => 'Seleziona libreria'; - @override String filtersWithCount({required Object count}) => 'Filtri (${count})'; - @override String get noRecommendations => 'Nessun consiglio disponibile'; - @override String get noCollections => 'Nessuna raccolta in questa libreria'; - @override String get noFoldersFound => 'Nessuna cartella trovata'; - @override String get folders => 'cartelle'; - @override late final _StringsLibrariesTabsIt tabs = _StringsLibrariesTabsIt._(_root); - @override late final _StringsLibrariesGroupingsIt groupings = _StringsLibrariesGroupingsIt._(_root); + // Translations + @override + String get title => 'Librerie'; + @override + String get scanLibraryFiles => 'Scansiona file libreria'; + @override + String get scanLibrary => 'Scansiona libreria'; + @override + String get analyze => 'Analizza'; + @override + String get analyzeLibrary => 'Analizza libreria'; + @override + String get refreshMetadata => 'Aggiorna metadati'; + @override + String get emptyTrash => 'Svuota cestino'; + @override + String emptyingTrash({required Object title}) => + 'Svuotamento cestino per "${title}"...'; + @override + String trashEmptied({required Object title}) => + 'Cestino svuotato per "${title}"'; + @override + String failedToEmptyTrash({required Object error}) => + 'Impossibile svuotare cestino: ${error}'; + @override + String analyzing({required Object title}) => 'Analisi "${title}"...'; + @override + String analysisStarted({required Object title}) => + 'Analisi iniziata per "${title}"'; + @override + String failedToAnalyze({required Object error}) => + 'Impossibile analizzare libreria: ${error}'; + @override + String get noLibrariesFound => 'Nessuna libreria trovata'; + @override + String get thisLibraryIsEmpty => 'Questa libreria è vuota'; + @override + String get all => 'Tutto'; + @override + String get clearAll => 'Cancella tutto'; + @override + String scanLibraryConfirm({required Object title}) => + 'Sei sicuro di voler scansionare "${title}"?'; + @override + String analyzeLibraryConfirm({required Object title}) => + 'Sei sicuro di voler analizzare "${title}"?'; + @override + String refreshMetadataConfirm({required Object title}) => + 'Sei sicuro di voler aggiornare i metadati per "${title}"?'; + @override + String emptyTrashConfirm({required Object title}) => + 'Sei sicuro di voler svuotare il cestino per "${title}"?'; + @override + String get manageLibraries => 'Gestisci librerie'; + @override + String get sort => 'Ordina'; + @override + String get sortBy => 'Ordina per'; + @override + String get filters => 'Filtri'; + @override + String loadingLibraryWithCount({required Object count}) => + 'Caricamento librerie... (${count} oggetti caricati)'; + @override + String get confirmActionMessage => + 'Sei sicuro di voler eseguire questa azione?'; + @override + String get showLibrary => 'Mostra libreria'; + @override + String get hideLibrary => 'Nascondi libreria'; + @override + String get libraryOptions => 'Opzioni libreria'; + @override + String get content => 'contenuto della libreria'; + @override + String get selectLibrary => 'Seleziona libreria'; + @override + String filtersWithCount({required Object count}) => 'Filtri (${count})'; + @override + String get noRecommendations => 'Nessun consiglio disponibile'; + @override + String get noCollections => 'Nessuna raccolta in questa libreria'; + @override + String get noFoldersFound => 'Nessuna cartella trovata'; + @override + String get folders => 'cartelle'; + @override + late final _StringsLibrariesTabsIt tabs = _StringsLibrariesTabsIt._(_root); + @override + late final _StringsLibrariesGroupingsIt groupings = + _StringsLibrariesGroupingsIt._(_root); } // Path: about class _StringsAboutIt implements _StringsAboutEn { - _StringsAboutIt._(this._root); + _StringsAboutIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Informazioni'; - @override String get openSourceLicenses => 'Licenze Open Source'; - @override String versionLabel({required Object version}) => 'Versione ${version}'; - @override String get appDescription => 'Un bellissimo client Plex per Flutter'; - @override String get viewLicensesDescription => 'Visualizza le licenze delle librerie di terze parti'; + // Translations + @override + String get title => 'Informazioni'; + @override + String get openSourceLicenses => 'Licenze Open Source'; + @override + String versionLabel({required Object version}) => 'Versione ${version}'; + @override + String get appDescription => 'Un bellissimo client Plex per Flutter'; + @override + String get viewLicensesDescription => + 'Visualizza le licenze delle librerie di terze parti'; } // Path: serverSelection class _StringsServerSelectionIt implements _StringsServerSelectionEn { - _StringsServerSelectionIt._(this._root); + _StringsServerSelectionIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get connectingToServer => 'Connessione al server...'; - @override String get connectingToAllServers => 'Connessione a tutti i server...'; - @override String get connectToAllServers => 'Connetti a tutti i server'; - @override String get allServerConnectionsFailed => 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.'; - @override String foundServers({required Object count}) => 'Trovati ${count} server'; - @override String get orSelectIndividually => 'Oppure seleziona un server singolarmente:'; - @override String get serverDebugCopied => 'Dati di debug del server copiati negli appunti'; - @override String get copyDebugData => 'Copia dati di debug'; - @override String get noServersFound => 'Nessun server trovato'; - @override String malformedServerData({required Object count}) => 'Trovato ${count} server con dati difettosi. Nessun server valido disponibile.'; - @override String get incompleteServerInfo => 'Alcuni server presentano informazioni incomplete e sono stati ignorati. Controlla il tuo account Plex.tv.'; - @override String get incompleteConnectionInfo => 'Le informazioni di connessione al server sono incomplete. Riprova.'; - @override String malformedServerInfo({required Object message}) => 'Le informazioni sul server sono errate: ${message}'; - @override String get networkConnectionFailed => 'Connessione di rete non riuscita. Controlla la tua connessione Internet e riprova.'; - @override String get authenticationFailed => 'Autenticazione fallita. Effettua nuovamente l\'accesso.'; - @override String get plexServiceUnavailable => 'Servizio Plex non disponibile. Riprova più tardi.'; - @override String failedToLoadServers({required Object error}) => 'Impossibile caricare i server: ${error}'; + // Translations + @override + String get connectingToServer => 'Connessione al server...'; + @override + String get connectingToAllServers => 'Connessione a tutti i server...'; + @override + String get connectToAllServers => 'Connetti a tutti i server'; + @override + String get allServerConnectionsFailed => + 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.'; + @override + String foundServers({required Object count}) => 'Trovati ${count} server'; + @override + String get orSelectIndividually => + 'Oppure seleziona un server singolarmente:'; + @override + String get serverDebugCopied => + 'Dati di debug del server copiati negli appunti'; + @override + String get copyDebugData => 'Copia dati di debug'; + @override + String get noServersFound => 'Nessun server trovato'; + @override + String malformedServerData({required Object count}) => + 'Trovato ${count} server con dati difettosi. Nessun server valido disponibile.'; + @override + String get incompleteServerInfo => + 'Alcuni server presentano informazioni incomplete e sono stati ignorati. Controlla il tuo account Plex.tv.'; + @override + String get incompleteConnectionInfo => + 'Le informazioni di connessione al server sono incomplete. Riprova.'; + @override + String malformedServerInfo({required Object message}) => + 'Le informazioni sul server sono errate: ${message}'; + @override + String get networkConnectionFailed => + 'Connessione di rete non riuscita. Controlla la tua connessione Internet e riprova.'; + @override + String get authenticationFailed => + 'Autenticazione fallita. Effettua nuovamente l\'accesso.'; + @override + String get plexServiceUnavailable => + 'Servizio Plex non disponibile. Riprova più tardi.'; + @override + String failedToLoadServers({required Object error}) => + 'Impossibile caricare i server: ${error}'; } // Path: hubDetail class _StringsHubDetailIt implements _StringsHubDetailEn { - _StringsHubDetailIt._(this._root); + _StringsHubDetailIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Titolo'; - @override String get releaseYear => 'Anno rilascio'; - @override String get dateAdded => 'Data aggiunta'; - @override String get rating => 'Valutazione'; - @override String get noItemsFound => 'Nessun elemento trovato'; + // Translations + @override + String get title => 'Titolo'; + @override + String get releaseYear => 'Anno rilascio'; + @override + String get dateAdded => 'Data aggiunta'; + @override + String get rating => 'Valutazione'; + @override + String get noItemsFound => 'Nessun elemento trovato'; } // Path: logs class _StringsLogsIt implements _StringsLogsEn { - _StringsLogsIt._(this._root); + _StringsLogsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Registro'; - @override String get clearLogs => 'Cancella log'; - @override String get copyLogs => 'Copia log'; - @override String get exportLogs => 'Esporta log'; - @override String get noLogsToShow => 'Nessun log da mostrare'; - @override String get error => 'Errore:'; - @override String get stackTrace => 'Traccia dello stack:'; + // Translations + @override + String get title => 'Registro'; + @override + String get clearLogs => 'Cancella log'; + @override + String get copyLogs => 'Copia log'; + @override + String get exportLogs => 'Esporta log'; + @override + String get noLogsToShow => 'Nessun log da mostrare'; + @override + String get error => 'Errore:'; + @override + String get stackTrace => 'Traccia dello stack:'; } // Path: licenses class _StringsLicensesIt implements _StringsLicensesEn { - _StringsLicensesIt._(this._root); + _StringsLicensesIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get relatedPackages => 'Pacchetti correlati'; - @override String get license => 'Licenza'; - @override String licenseNumber({required Object number}) => 'Licenza ${number}'; - @override String licensesCount({required Object count}) => '${count} licenze'; + // Translations + @override + String get relatedPackages => 'Pacchetti correlati'; + @override + String get license => 'Licenza'; + @override + String licenseNumber({required Object number}) => 'Licenza ${number}'; + @override + String licensesCount({required Object count}) => '${count} licenze'; } // Path: navigation class _StringsNavigationIt implements _StringsNavigationEn { - _StringsNavigationIt._(this._root); + _StringsNavigationIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get home => 'Home'; - @override String get search => 'Cerca'; - @override String get libraries => 'Librerie'; - @override String get settings => 'Impostazioni'; + // Translations + @override + String get home => 'Home'; + @override + String get search => 'Cerca'; + @override + String get libraries => 'Librerie'; + @override + String get settings => 'Impostazioni'; } // Path: playlists class _StringsPlaylistsIt implements _StringsPlaylistsEn { - _StringsPlaylistsIt._(this._root); + _StringsPlaylistsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Playlist'; - @override String get noPlaylists => 'Nessuna playlist trovata'; - @override String get create => 'Crea playlist'; - @override String get newPlaylist => 'Nuova playlist'; - @override String get playlistName => 'Nome playlist'; - @override String get enterPlaylistName => 'Inserisci nome playlist'; - @override String get edit => 'Modifica playlist'; - @override String get delete => 'Elimina playlist'; - @override String get addTo => 'Aggiungi a playlist'; - @override String get addItems => 'Aggiungi elementi'; - @override String get removeItem => 'Rimuovi da playlist'; - @override String get clearPlaylist => 'Svuota playlist'; - @override String get playAll => 'Riproduci tutto'; - @override String get shuffle => 'Casuale'; - @override String get smartPlaylist => 'Playlist intelligente'; - @override String get regularPlaylist => 'Playlist normale'; - @override String itemCount({required Object count}) => '${count} elementi'; - @override String get oneItem => '1 elemento'; - @override String get emptyPlaylist => 'Questa playlist è vuota'; - @override String get deleteConfirm => 'Eliminare playlist?'; - @override String deleteMessage({required Object name}) => 'Sei sicuro di voler eliminare "${name}"?'; - @override String get created => 'Playlist creata'; - @override String get updated => 'Playlist aggiornata'; - @override String get deleted => 'Playlist eliminata'; - @override String get itemAdded => 'Aggiunto alla playlist'; - @override String get itemRemoved => 'Rimosso dalla playlist'; - @override String get selectPlaylist => 'Seleziona playlist'; - @override String get createNewPlaylist => 'Crea nuova playlist'; - @override String get errorCreating => 'Errore durante la creazione della playlist'; - @override String get errorDeleting => 'Errore durante l\'eliminazione della playlist'; - @override String get errorLoading => 'Errore durante il caricamento delle playlist'; - @override String get errorAdding => 'Errore durante l\'aggiunta alla playlist'; - @override String get errorReordering => 'Errore durante il riordino dell\'elemento della playlist'; - @override String get errorRemoving => 'Errore durante la rimozione dalla playlist'; - @override String get playlist => 'Playlist'; + // Translations + @override + String get title => 'Playlist'; + @override + String get noPlaylists => 'Nessuna playlist trovata'; + @override + String get create => 'Crea playlist'; + @override + String get newPlaylist => 'Nuova playlist'; + @override + String get playlistName => 'Nome playlist'; + @override + String get enterPlaylistName => 'Inserisci nome playlist'; + @override + String get edit => 'Modifica playlist'; + @override + String get delete => 'Elimina playlist'; + @override + String get addTo => 'Aggiungi a playlist'; + @override + String get addItems => 'Aggiungi elementi'; + @override + String get removeItem => 'Rimuovi da playlist'; + @override + String get clearPlaylist => 'Svuota playlist'; + @override + String get playAll => 'Riproduci tutto'; + @override + String get shuffle => 'Casuale'; + @override + String get smartPlaylist => 'Playlist intelligente'; + @override + String get regularPlaylist => 'Playlist normale'; + @override + String itemCount({required Object count}) => '${count} elementi'; + @override + String get oneItem => '1 elemento'; + @override + String get emptyPlaylist => 'Questa playlist è vuota'; + @override + String get deleteConfirm => 'Eliminare playlist?'; + @override + String deleteMessage({required Object name}) => + 'Sei sicuro di voler eliminare "${name}"?'; + @override + String get created => 'Playlist creata'; + @override + String get updated => 'Playlist aggiornata'; + @override + String get deleted => 'Playlist eliminata'; + @override + String get itemAdded => 'Aggiunto alla playlist'; + @override + String get itemRemoved => 'Rimosso dalla playlist'; + @override + String get selectPlaylist => 'Seleziona playlist'; + @override + String get createNewPlaylist => 'Crea nuova playlist'; + @override + String get errorCreating => 'Errore durante la creazione della playlist'; + @override + String get errorDeleting => 'Errore durante l\'eliminazione della playlist'; + @override + String get errorLoading => 'Errore durante il caricamento delle playlist'; + @override + String get errorAdding => 'Errore durante l\'aggiunta alla playlist'; + @override + String get errorReordering => + 'Errore durante il riordino dell\'elemento della playlist'; + @override + String get errorRemoving => 'Errore durante la rimozione dalla playlist'; + @override + String get playlist => 'Playlist'; } // Path: collections class _StringsCollectionsIt implements _StringsCollectionsEn { - _StringsCollectionsIt._(this._root); + _StringsCollectionsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get title => 'Raccolte'; - @override String get collection => 'Raccolta'; - @override String get empty => 'La raccolta è vuota'; - @override String get noItems => 'Nessun elemento in questa raccolta'; - @override String get unknownLibrarySection => 'Impossibile eliminare: sezione libreria sconosciuta'; - @override String get deleteCollection => 'Elimina raccolta'; - @override String deleteConfirm({required Object title}) => 'Sei sicuro di voler eliminare "${title}"? Questa azione non può essere annullata.'; - @override String get deleted => 'Raccolta eliminata'; - @override String get deleteFailed => 'Impossibile eliminare la raccolta'; - @override String deleteFailedWithError({required Object error}) => 'Impossibile eliminare la raccolta: ${error}'; - @override String failedToLoadItems({required Object error}) => 'Impossibile caricare gli elementi della raccolta: ${error}'; - @override String get addTo => 'Aggiungi alla raccolta'; - @override String get selectCollection => 'Seleziona raccolta'; - @override String get createNewCollection => 'Crea nuova raccolta'; - @override String get collectionName => 'Nome raccolta'; - @override String get enterCollectionName => 'Inserisci nome raccolta'; - @override String get addedToCollection => 'Aggiunto alla raccolta'; - @override String get errorAddingToCollection => 'Errore nell\'aggiunta alla raccolta'; - @override String get created => 'Raccolta creata'; - @override String get removeFromCollection => 'Rimuovi dalla raccolta'; - @override String removeFromCollectionConfirm({required Object title}) => 'Rimuovere "${title}" da questa raccolta?'; - @override String get removedFromCollection => 'Rimosso dalla raccolta'; - @override String get removeFromCollectionFailed => 'Impossibile rimuovere dalla raccolta'; - @override String removeFromCollectionError({required Object error}) => 'Errore durante la rimozione dalla raccolta: ${error}'; + // Translations + @override + String get title => 'Raccolte'; + @override + String get collection => 'Raccolta'; + @override + String get empty => 'La raccolta è vuota'; + @override + String get noItems => 'Nessun elemento in questa raccolta'; + @override + String get unknownLibrarySection => + 'Impossibile eliminare: sezione libreria sconosciuta'; + @override + String get deleteCollection => 'Elimina raccolta'; + @override + String deleteConfirm({required Object title}) => + 'Sei sicuro di voler eliminare "${title}"? Questa azione non può essere annullata.'; + @override + String get deleted => 'Raccolta eliminata'; + @override + String get deleteFailed => 'Impossibile eliminare la raccolta'; + @override + String deleteFailedWithError({required Object error}) => + 'Impossibile eliminare la raccolta: ${error}'; + @override + String failedToLoadItems({required Object error}) => + 'Impossibile caricare gli elementi della raccolta: ${error}'; + @override + String get addTo => 'Aggiungi alla raccolta'; + @override + String get selectCollection => 'Seleziona raccolta'; + @override + String get createNewCollection => 'Crea nuova raccolta'; + @override + String get collectionName => 'Nome raccolta'; + @override + String get enterCollectionName => 'Inserisci nome raccolta'; + @override + String get addedToCollection => 'Aggiunto alla raccolta'; + @override + String get errorAddingToCollection => 'Errore nell\'aggiunta alla raccolta'; + @override + String get created => 'Raccolta creata'; + @override + String get removeFromCollection => 'Rimuovi dalla raccolta'; + @override + String removeFromCollectionConfirm({required Object title}) => + 'Rimuovere "${title}" da questa raccolta?'; + @override + String get removedFromCollection => 'Rimosso dalla raccolta'; + @override + String get removeFromCollectionFailed => + 'Impossibile rimuovere dalla raccolta'; + @override + String removeFromCollectionError({required Object error}) => + 'Errore durante la rimozione dalla raccolta: ${error}'; } // Path: libraries.tabs class _StringsLibrariesTabsIt implements _StringsLibrariesTabsEn { - _StringsLibrariesTabsIt._(this._root); + _StringsLibrariesTabsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get recommended => 'Consigliati'; - @override String get browse => 'Esplora'; - @override String get collections => 'Raccolte'; - @override String get playlists => 'Playlist'; + // Translations + @override + String get recommended => 'Consigliati'; + @override + String get browse => 'Esplora'; + @override + String get collections => 'Raccolte'; + @override + String get playlists => 'Playlist'; } // Path: libraries.groupings class _StringsLibrariesGroupingsIt implements _StringsLibrariesGroupingsEn { - _StringsLibrariesGroupingsIt._(this._root); + _StringsLibrariesGroupingsIt._(this._root); - @override final _StringsIt _root; // ignore: unused_field + @override + final _StringsIt _root; // ignore: unused_field - // Translations - @override String get all => 'Tutti'; - @override String get movies => 'Film'; - @override String get shows => 'Serie TV'; - @override String get seasons => 'Stagioni'; - @override String get episodes => 'Episodi'; - @override String get folders => 'Cartelle'; + // Translations + @override + String get all => 'Tutti'; + @override + String get movies => 'Film'; + @override + String get shows => 'Serie TV'; + @override + String get seasons => 'Stagioni'; + @override + String get episodes => 'Episodi'; + @override + String get folders => 'Cartelle'; } // Path: class _StringsNl implements Translations { - /// You can call this constructor and build your own translation instance of this locale. - /// Constructing via the enum [AppLocale.build] is preferred. - _StringsNl.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) - : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), - $meta = TranslationMetadata( - locale: AppLocale.nl, - overrides: overrides ?? {}, - cardinalResolver: cardinalResolver, - ordinalResolver: ordinalResolver, - ) { - $meta.setFlatMapFunction(_flatMapFunction); - } + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsNl.build({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) : assert( + overrides == null, + 'Set "translation_overrides: true" in order to enable this feature.', + ), + $meta = TranslationMetadata( + locale: AppLocale.nl, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } - /// Metadata for the translations of . - @override final TranslationMetadata $meta; + /// Metadata for the translations of . + @override + final TranslationMetadata $meta; - /// Access flat map - @override dynamic operator[](String key) => $meta.getTranslation(key); + /// Access flat map + @override + dynamic operator [](String key) => $meta.getTranslation(key); - @override late final _StringsNl _root = this; // ignore: unused_field + @override + late final _StringsNl _root = this; // ignore: unused_field - // Translations - @override late final _StringsAppNl app = _StringsAppNl._(_root); - @override late final _StringsAuthNl auth = _StringsAuthNl._(_root); - @override late final _StringsCommonNl common = _StringsCommonNl._(_root); - @override late final _StringsScreensNl screens = _StringsScreensNl._(_root); - @override late final _StringsUpdateNl update = _StringsUpdateNl._(_root); - @override late final _StringsSettingsNl settings = _StringsSettingsNl._(_root); - @override late final _StringsSearchNl search = _StringsSearchNl._(_root); - @override late final _StringsHotkeysNl hotkeys = _StringsHotkeysNl._(_root); - @override late final _StringsPinEntryNl pinEntry = _StringsPinEntryNl._(_root); - @override late final _StringsFileInfoNl fileInfo = _StringsFileInfoNl._(_root); - @override late final _StringsMediaMenuNl mediaMenu = _StringsMediaMenuNl._(_root); - @override late final _StringsAccessibilityNl accessibility = _StringsAccessibilityNl._(_root); - @override late final _StringsTooltipsNl tooltips = _StringsTooltipsNl._(_root); - @override late final _StringsVideoControlsNl videoControls = _StringsVideoControlsNl._(_root); - @override late final _StringsUserStatusNl userStatus = _StringsUserStatusNl._(_root); - @override late final _StringsMessagesNl messages = _StringsMessagesNl._(_root); - @override late final _StringsProfileNl profile = _StringsProfileNl._(_root); - @override late final _StringsSubtitlingStylingNl subtitlingStyling = _StringsSubtitlingStylingNl._(_root); - @override late final _StringsDialogNl dialog = _StringsDialogNl._(_root); - @override late final _StringsDiscoverNl discover = _StringsDiscoverNl._(_root); - @override late final _StringsErrorsNl errors = _StringsErrorsNl._(_root); - @override late final _StringsLibrariesNl libraries = _StringsLibrariesNl._(_root); - @override late final _StringsAboutNl about = _StringsAboutNl._(_root); - @override late final _StringsServerSelectionNl serverSelection = _StringsServerSelectionNl._(_root); - @override late final _StringsHubDetailNl hubDetail = _StringsHubDetailNl._(_root); - @override late final _StringsLogsNl logs = _StringsLogsNl._(_root); - @override late final _StringsLicensesNl licenses = _StringsLicensesNl._(_root); - @override late final _StringsNavigationNl navigation = _StringsNavigationNl._(_root); - @override late final _StringsPlaylistsNl playlists = _StringsPlaylistsNl._(_root); - @override late final _StringsCollectionsNl collections = _StringsCollectionsNl._(_root); + // Translations + @override + late final _StringsAppNl app = _StringsAppNl._(_root); + @override + late final _StringsAuthNl auth = _StringsAuthNl._(_root); + @override + late final _StringsCommonNl common = _StringsCommonNl._(_root); + @override + late final _StringsScreensNl screens = _StringsScreensNl._(_root); + @override + late final _StringsUpdateNl update = _StringsUpdateNl._(_root); + @override + late final _StringsSettingsNl settings = _StringsSettingsNl._(_root); + @override + late final _StringsSearchNl search = _StringsSearchNl._(_root); + @override + late final _StringsHotkeysNl hotkeys = _StringsHotkeysNl._(_root); + @override + late final _StringsPinEntryNl pinEntry = _StringsPinEntryNl._(_root); + @override + late final _StringsFileInfoNl fileInfo = _StringsFileInfoNl._(_root); + @override + late final _StringsMediaMenuNl mediaMenu = _StringsMediaMenuNl._(_root); + @override + late final _StringsAccessibilityNl accessibility = _StringsAccessibilityNl._( + _root, + ); + @override + late final _StringsTooltipsNl tooltips = _StringsTooltipsNl._(_root); + @override + late final _StringsVideoControlsNl videoControls = _StringsVideoControlsNl._( + _root, + ); + @override + late final _StringsUserStatusNl userStatus = _StringsUserStatusNl._(_root); + @override + late final _StringsMessagesNl messages = _StringsMessagesNl._(_root); + @override + late final _StringsProfileNl profile = _StringsProfileNl._(_root); + @override + late final _StringsSubtitlingStylingNl subtitlingStyling = + _StringsSubtitlingStylingNl._(_root); + @override + late final _StringsDialogNl dialog = _StringsDialogNl._(_root); + @override + late final _StringsDiscoverNl discover = _StringsDiscoverNl._(_root); + @override + late final _StringsErrorsNl errors = _StringsErrorsNl._(_root); + @override + late final _StringsLibrariesNl libraries = _StringsLibrariesNl._(_root); + @override + late final _StringsAboutNl about = _StringsAboutNl._(_root); + @override + late final _StringsServerSelectionNl serverSelection = + _StringsServerSelectionNl._(_root); + @override + late final _StringsHubDetailNl hubDetail = _StringsHubDetailNl._(_root); + @override + late final _StringsLogsNl logs = _StringsLogsNl._(_root); + @override + late final _StringsLicensesNl licenses = _StringsLicensesNl._(_root); + @override + late final _StringsNavigationNl navigation = _StringsNavigationNl._(_root); + @override + late final _StringsPlaylistsNl playlists = _StringsPlaylistsNl._(_root); + @override + late final _StringsCollectionsNl collections = _StringsCollectionsNl._(_root); } // Path: app class _StringsAppNl implements _StringsAppEn { - _StringsAppNl._(this._root); + _StringsAppNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Plezy'; - @override String get loading => 'Laden...'; + // Translations + @override + String get title => 'Plezy'; + @override + String get loading => 'Laden...'; } // Path: auth class _StringsAuthNl implements _StringsAuthEn { - _StringsAuthNl._(this._root); + _StringsAuthNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get signInWithPlex => 'Inloggen met Plex'; - @override String get showQRCode => 'Toon QR-code'; - @override String get cancel => 'Annuleren'; - @override String get authenticate => 'Authenticeren'; - @override String get retry => 'Opnieuw proberen'; - @override String get debugEnterToken => 'Debug: Voer Plex Token in'; - @override String get plexTokenLabel => 'Plex Authenticatietoken'; - @override String get plexTokenHint => 'Voer je Plex.tv token in'; - @override String get authenticationTimeout => 'Authenticatie verlopen. Probeer opnieuw.'; - @override String get scanQRCodeInstruction => 'Scan deze QR-code met een apparaat dat is ingelogd op Plex om te authenticeren.'; - @override String get waitingForAuth => 'Wachten op authenticatie...\nVoltooi het inloggen in je browser.'; + // Translations + @override + String get signInWithPlex => 'Inloggen met Plex'; + @override + String get showQRCode => 'Toon QR-code'; + @override + String get cancel => 'Annuleren'; + @override + String get authenticate => 'Authenticeren'; + @override + String get retry => 'Opnieuw proberen'; + @override + String get debugEnterToken => 'Debug: Voer Plex Token in'; + @override + String get plexTokenLabel => 'Plex Authenticatietoken'; + @override + String get plexTokenHint => 'Voer je Plex.tv token in'; + @override + String get authenticationTimeout => + 'Authenticatie verlopen. Probeer opnieuw.'; + @override + String get scanQRCodeInstruction => + 'Scan deze QR-code met een apparaat dat is ingelogd op Plex om te authenticeren.'; + @override + String get waitingForAuth => + 'Wachten op authenticatie...\nVoltooi het inloggen in je browser.'; } // Path: common class _StringsCommonNl implements _StringsCommonEn { - _StringsCommonNl._(this._root); + _StringsCommonNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get cancel => 'Annuleren'; - @override String get save => 'Opslaan'; - @override String get close => 'Sluiten'; - @override String get clear => 'Wissen'; - @override String get reset => 'Resetten'; - @override String get later => 'Later'; - @override String get submit => 'Verzenden'; - @override String get confirm => 'Bevestigen'; - @override String get retry => 'Opnieuw proberen'; - @override String get playNow => 'Nu afspelen'; - @override String get logout => 'Uitloggen'; - @override String get online => 'Online'; - @override String get offline => 'Offline'; - @override String get owned => 'Eigendom'; - @override String get shared => 'Gedeeld'; - @override String get current => 'HUIDIG'; - @override String get unknown => 'Onbekend'; - @override String get refresh => 'Vernieuwen'; - @override String get yes => 'Ja'; - @override String get no => 'Nee'; - @override String get server => 'Server'; - @override String get delete => 'Verwijderen'; - @override String get shuffle => 'Willekeurig'; - @override String get addTo => 'Toevoegen aan...'; + // Translations + @override + String get cancel => 'Annuleren'; + @override + String get save => 'Opslaan'; + @override + String get close => 'Sluiten'; + @override + String get clear => 'Wissen'; + @override + String get reset => 'Resetten'; + @override + String get later => 'Later'; + @override + String get submit => 'Verzenden'; + @override + String get confirm => 'Bevestigen'; + @override + String get retry => 'Opnieuw proberen'; + @override + String get playNow => 'Nu afspelen'; + @override + String get logout => 'Uitloggen'; + @override + String get online => 'Online'; + @override + String get offline => 'Offline'; + @override + String get owned => 'Eigendom'; + @override + String get shared => 'Gedeeld'; + @override + String get current => 'HUIDIG'; + @override + String get unknown => 'Onbekend'; + @override + String get refresh => 'Vernieuwen'; + @override + String get yes => 'Ja'; + @override + String get no => 'Nee'; + @override + String get server => 'Server'; + @override + String get delete => 'Verwijderen'; + @override + String get shuffle => 'Willekeurig'; + @override + String get addTo => 'Toevoegen aan...'; } // Path: screens class _StringsScreensNl implements _StringsScreensEn { - _StringsScreensNl._(this._root); + _StringsScreensNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get licenses => 'Licenties'; - @override String get selectServer => 'Selecteer server'; - @override String get switchProfile => 'Wissel van profiel'; - @override String get subtitleStyling => 'Ondertitel opmaak'; - @override String get search => 'Zoeken'; - @override String get logs => 'Logbestanden'; + // Translations + @override + String get licenses => 'Licenties'; + @override + String get selectServer => 'Selecteer server'; + @override + String get switchProfile => 'Wissel van profiel'; + @override + String get subtitleStyling => 'Ondertitel opmaak'; + @override + String get search => 'Zoeken'; + @override + String get logs => 'Logbestanden'; } // Path: update class _StringsUpdateNl implements _StringsUpdateEn { - _StringsUpdateNl._(this._root); + _StringsUpdateNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get available => 'Update beschikbaar'; - @override String versionAvailable({required Object version}) => 'Versie ${version} is beschikbaar'; - @override String currentVersion({required Object version}) => 'Huidig: ${version}'; - @override String get skipVersion => 'Deze versie overslaan'; - @override String get viewRelease => 'Bekijk release'; - @override String get latestVersion => 'Je hebt de nieuwste versie'; - @override String get checkFailed => 'Kon niet controleren op updates'; + // Translations + @override + String get available => 'Update beschikbaar'; + @override + String versionAvailable({required Object version}) => + 'Versie ${version} is beschikbaar'; + @override + String currentVersion({required Object version}) => 'Huidig: ${version}'; + @override + String get skipVersion => 'Deze versie overslaan'; + @override + String get viewRelease => 'Bekijk release'; + @override + String get latestVersion => 'Je hebt de nieuwste versie'; + @override + String get checkFailed => 'Kon niet controleren op updates'; } // Path: settings class _StringsSettingsNl implements _StringsSettingsEn { - _StringsSettingsNl._(this._root); + _StringsSettingsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Instellingen'; - @override String get language => 'Taal'; - @override String get theme => 'Thema'; - @override String get appearance => 'Uiterlijk'; - @override String get videoPlayback => 'Video afspelen'; - @override String get shufflePlay => 'Willekeurig afspelen'; - @override String get advanced => 'Geavanceerd'; - @override String get useSeasonPostersDescription => 'Toon seizoenposter in plaats van serieposter voor afleveringen'; - @override String get showHeroSectionDescription => 'Toon uitgelichte inhoud carrousel op startscherm'; - @override String get secondsLabel => 'Seconden'; - @override String get minutesLabel => 'Minuten'; - @override String get secondsShort => 's'; - @override String get minutesShort => 'm'; - @override String durationHint({required Object min, required Object max}) => 'Voer duur in (${min}-${max})'; - @override String get systemTheme => 'Systeem'; - @override String get systemThemeDescription => 'Volg systeeminstellingen'; - @override String get lightTheme => 'Licht'; - @override String get darkTheme => 'Donker'; - @override String get libraryDensity => 'Bibliotheek dichtheid'; - @override String get compact => 'Compact'; - @override String get compactDescription => 'Kleinere kaarten, meer items zichtbaar'; - @override String get normal => 'Normaal'; - @override String get normalDescription => 'Standaard grootte'; - @override String get comfortable => 'Comfortabel'; - @override String get comfortableDescription => 'Grotere kaarten, minder items zichtbaar'; - @override String get viewMode => 'Weergavemodus'; - @override String get gridView => 'Raster'; - @override String get gridViewDescription => 'Items weergeven in een rasterindeling'; - @override String get listView => 'Lijst'; - @override String get listViewDescription => 'Items weergeven in een lijstindeling'; - @override String get useSeasonPosters => 'Gebruik seizoenposters'; - @override String get showHeroSection => 'Toon hoofdsectie'; - @override String get hardwareDecoding => 'Hardware decodering'; - @override String get hardwareDecodingDescription => 'Gebruik hardware versnelling indien beschikbaar'; - @override String get bufferSize => 'Buffer grootte'; - @override String bufferSizeMB({required Object size}) => '${size}MB'; - @override String get subtitleStyling => 'Ondertitel opmaak'; - @override String get subtitleStylingDescription => 'Pas ondertitel uiterlijk aan'; - @override String get smallSkipDuration => 'Korte skip duur'; - @override String get largeSkipDuration => 'Lange skip duur'; - @override String secondsUnit({required Object seconds}) => '${seconds} seconden'; - @override String get defaultSleepTimer => 'Standaard slaap timer'; - @override String minutesUnit({required Object minutes}) => 'bij ${minutes} minuten'; - @override String get rememberTrackSelections => 'Onthoud track selecties per serie/film'; - @override String get rememberTrackSelectionsDescription => 'Bewaar automatisch audio- en ondertiteltaalvoorkeuren wanneer je tracks wijzigt tijdens afspelen'; - @override String get unwatchedOnly => 'Alleen ongekeken'; - @override String get unwatchedOnlyDescription => 'Alleen ongekeken afleveringen opnemen in willekeurige wachtrij'; - @override String get shuffleOrderNavigation => 'Willekeurige volgorde navigatie'; - @override String get shuffleOrderNavigationDescription => 'Volgende/vorige knoppen volgen willekeurige volgorde'; - @override String get loopShuffleQueue => 'Herhaal willekeurige wachtrij'; - @override String get loopShuffleQueueDescription => 'Start wachtrij opnieuw bij het einde'; - @override String get videoPlayerControls => 'Videospeler bediening'; - @override String get keyboardShortcuts => 'Toetsenbord sneltoetsen'; - @override String get keyboardShortcutsDescription => 'Pas toetsenbord sneltoetsen aan'; - @override String get debugLogging => 'Debug logging'; - @override String get debugLoggingDescription => 'Schakel gedetailleerde logging in voor probleemoplossing'; - @override String get viewLogs => 'Bekijk logs'; - @override String get viewLogsDescription => 'Bekijk applicatie logs'; - @override String get clearCache => 'Cache wissen'; - @override String get clearCacheDescription => 'Dit wist alle gecachte afbeeldingen en gegevens. De app kan langer duren om inhoud te laden na het wissen van de cache.'; - @override String get clearCacheSuccess => 'Cache succesvol gewist'; - @override String get resetSettings => 'Instellingen resetten'; - @override String get resetSettingsDescription => 'Dit reset alle instellingen naar hun standaard waarden. Deze actie kan niet ongedaan gemaakt worden.'; - @override String get resetSettingsSuccess => 'Instellingen succesvol gereset'; - @override String get shortcutsReset => 'Sneltoetsen gereset naar standaard'; - @override String get about => 'Over'; - @override String get aboutDescription => 'App informatie en licenties'; - @override String get updates => 'Updates'; - @override String get updateAvailable => 'Update beschikbaar'; - @override String get checkForUpdates => 'Controleer op updates'; - @override String get validationErrorEnterNumber => 'Voer een geldig nummer in'; - @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Duur moet tussen ${min} en ${max} ${unit} zijn'; - @override String shortcutAlreadyAssigned({required Object action}) => 'Sneltoets al toegewezen aan ${action}'; - @override String shortcutUpdated({required Object action}) => 'Sneltoets bijgewerkt voor ${action}'; + // Translations + @override + String get title => 'Instellingen'; + @override + String get language => 'Taal'; + @override + String get theme => 'Thema'; + @override + String get appearance => 'Uiterlijk'; + @override + String get videoPlayback => 'Video afspelen'; + @override + String get shufflePlay => 'Willekeurig afspelen'; + @override + String get advanced => 'Geavanceerd'; + @override + String get useSeasonPostersDescription => + 'Toon seizoenposter in plaats van serieposter voor afleveringen'; + @override + String get showHeroSectionDescription => + 'Toon uitgelichte inhoud carrousel op startscherm'; + @override + String get secondsLabel => 'Seconden'; + @override + String get minutesLabel => 'Minuten'; + @override + String get secondsShort => 's'; + @override + String get minutesShort => 'm'; + @override + String durationHint({required Object min, required Object max}) => + 'Voer duur in (${min}-${max})'; + @override + String get systemTheme => 'Systeem'; + @override + String get systemThemeDescription => 'Volg systeeminstellingen'; + @override + String get lightTheme => 'Licht'; + @override + String get darkTheme => 'Donker'; + @override + String get libraryDensity => 'Bibliotheek dichtheid'; + @override + String get compact => 'Compact'; + @override + String get compactDescription => 'Kleinere kaarten, meer items zichtbaar'; + @override + String get normal => 'Normaal'; + @override + String get normalDescription => 'Standaard grootte'; + @override + String get comfortable => 'Comfortabel'; + @override + String get comfortableDescription => + 'Grotere kaarten, minder items zichtbaar'; + @override + String get viewMode => 'Weergavemodus'; + @override + String get gridView => 'Raster'; + @override + String get gridViewDescription => 'Items weergeven in een rasterindeling'; + @override + String get listView => 'Lijst'; + @override + String get listViewDescription => 'Items weergeven in een lijstindeling'; + @override + String get useSeasonPosters => 'Gebruik seizoenposters'; + @override + String get showHeroSection => 'Toon hoofdsectie'; + @override + String get hardwareDecoding => 'Hardware decodering'; + @override + String get hardwareDecodingDescription => + 'Gebruik hardware versnelling indien beschikbaar'; + @override + String get bufferSize => 'Buffer grootte'; + @override + String bufferSizeMB({required Object size}) => '${size}MB'; + @override + String get subtitleStyling => 'Ondertitel opmaak'; + @override + String get subtitleStylingDescription => 'Pas ondertitel uiterlijk aan'; + @override + String get smallSkipDuration => 'Korte skip duur'; + @override + String get largeSkipDuration => 'Lange skip duur'; + @override + String secondsUnit({required Object seconds}) => '${seconds} seconden'; + @override + String get defaultSleepTimer => 'Standaard slaap timer'; + @override + String minutesUnit({required Object minutes}) => 'bij ${minutes} minuten'; + @override + String get rememberTrackSelections => + 'Onthoud track selecties per serie/film'; + @override + String get rememberTrackSelectionsDescription => + 'Bewaar automatisch audio- en ondertiteltaalvoorkeuren wanneer je tracks wijzigt tijdens afspelen'; + @override + String get unwatchedOnly => 'Alleen ongekeken'; + @override + String get unwatchedOnlyDescription => + 'Alleen ongekeken afleveringen opnemen in willekeurige wachtrij'; + @override + String get shuffleOrderNavigation => 'Willekeurige volgorde navigatie'; + @override + String get shuffleOrderNavigationDescription => + 'Volgende/vorige knoppen volgen willekeurige volgorde'; + @override + String get loopShuffleQueue => 'Herhaal willekeurige wachtrij'; + @override + String get loopShuffleQueueDescription => + 'Start wachtrij opnieuw bij het einde'; + @override + String get videoPlayerControls => 'Videospeler bediening'; + @override + String get keyboardShortcuts => 'Toetsenbord sneltoetsen'; + @override + String get keyboardShortcutsDescription => 'Pas toetsenbord sneltoetsen aan'; + @override + String get debugLogging => 'Debug logging'; + @override + String get debugLoggingDescription => + 'Schakel gedetailleerde logging in voor probleemoplossing'; + @override + String get viewLogs => 'Bekijk logs'; + @override + String get viewLogsDescription => 'Bekijk applicatie logs'; + @override + String get clearCache => 'Cache wissen'; + @override + String get clearCacheDescription => + 'Dit wist alle gecachte afbeeldingen en gegevens. De app kan langer duren om inhoud te laden na het wissen van de cache.'; + @override + String get clearCacheSuccess => 'Cache succesvol gewist'; + @override + String get resetSettings => 'Instellingen resetten'; + @override + String get resetSettingsDescription => + 'Dit reset alle instellingen naar hun standaard waarden. Deze actie kan niet ongedaan gemaakt worden.'; + @override + String get resetSettingsSuccess => 'Instellingen succesvol gereset'; + @override + String get shortcutsReset => 'Sneltoetsen gereset naar standaard'; + @override + String get about => 'Over'; + @override + String get aboutDescription => 'App informatie en licenties'; + @override + String get updates => 'Updates'; + @override + String get updateAvailable => 'Update beschikbaar'; + @override + String get checkForUpdates => 'Controleer op updates'; + @override + String get validationErrorEnterNumber => 'Voer een geldig nummer in'; + @override + String validationErrorDuration({ + required Object min, + required Object max, + required Object unit, + }) => 'Duur moet tussen ${min} en ${max} ${unit} zijn'; + @override + String shortcutAlreadyAssigned({required Object action}) => + 'Sneltoets al toegewezen aan ${action}'; + @override + String shortcutUpdated({required Object action}) => + 'Sneltoets bijgewerkt voor ${action}'; } // Path: search class _StringsSearchNl implements _StringsSearchEn { - _StringsSearchNl._(this._root); + _StringsSearchNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get hint => 'Zoek films, series, muziek...'; - @override String get tryDifferentTerm => 'Probeer een andere zoekterm'; - @override String get searchYourMedia => 'Zoek in je media'; - @override String get enterTitleActorOrKeyword => 'Voer een titel, acteur of trefwoord in'; + // Translations + @override + String get hint => 'Zoek films, series, muziek...'; + @override + String get tryDifferentTerm => 'Probeer een andere zoekterm'; + @override + String get searchYourMedia => 'Zoek in je media'; + @override + String get enterTitleActorOrKeyword => + 'Voer een titel, acteur of trefwoord in'; } // Path: hotkeys class _StringsHotkeysNl implements _StringsHotkeysEn { - _StringsHotkeysNl._(this._root); + _StringsHotkeysNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String setShortcutFor({required Object actionName}) => 'Stel sneltoets in voor ${actionName}'; - @override String get clearShortcut => 'Wis sneltoets'; + // Translations + @override + String setShortcutFor({required Object actionName}) => + 'Stel sneltoets in voor ${actionName}'; + @override + String get clearShortcut => 'Wis sneltoets'; } // Path: pinEntry class _StringsPinEntryNl implements _StringsPinEntryEn { - _StringsPinEntryNl._(this._root); + _StringsPinEntryNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get enterPin => 'Voer PIN in'; - @override String get showPin => 'Toon PIN'; - @override String get hidePin => 'Verberg PIN'; + // Translations + @override + String get enterPin => 'Voer PIN in'; + @override + String get showPin => 'Toon PIN'; + @override + String get hidePin => 'Verberg PIN'; } // Path: fileInfo class _StringsFileInfoNl implements _StringsFileInfoEn { - _StringsFileInfoNl._(this._root); + _StringsFileInfoNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Bestand info'; - @override String get video => 'Video'; - @override String get audio => 'Audio'; - @override String get file => 'Bestand'; - @override String get advanced => 'Geavanceerd'; - @override String get codec => 'Codec'; - @override String get resolution => 'Resolutie'; - @override String get bitrate => 'Bitrate'; - @override String get frameRate => 'Frame rate'; - @override String get aspectRatio => 'Beeldverhouding'; - @override String get profile => 'Profiel'; - @override String get bitDepth => 'Bit diepte'; - @override String get colorSpace => 'Kleurruimte'; - @override String get colorRange => 'Kleurbereik'; - @override String get colorPrimaries => 'Kleurprimaires'; - @override String get chromaSubsampling => 'Chroma subsampling'; - @override String get channels => 'Kanalen'; - @override String get path => 'Pad'; - @override String get size => 'Grootte'; - @override String get container => 'Container'; - @override String get duration => 'Duur'; - @override String get optimizedForStreaming => 'Geoptimaliseerd voor streaming'; - @override String get has64bitOffsets => '64-bit Offsets'; + // Translations + @override + String get title => 'Bestand info'; + @override + String get video => 'Video'; + @override + String get audio => 'Audio'; + @override + String get file => 'Bestand'; + @override + String get advanced => 'Geavanceerd'; + @override + String get codec => 'Codec'; + @override + String get resolution => 'Resolutie'; + @override + String get bitrate => 'Bitrate'; + @override + String get frameRate => 'Frame rate'; + @override + String get aspectRatio => 'Beeldverhouding'; + @override + String get profile => 'Profiel'; + @override + String get bitDepth => 'Bit diepte'; + @override + String get colorSpace => 'Kleurruimte'; + @override + String get colorRange => 'Kleurbereik'; + @override + String get colorPrimaries => 'Kleurprimaires'; + @override + String get chromaSubsampling => 'Chroma subsampling'; + @override + String get channels => 'Kanalen'; + @override + String get path => 'Pad'; + @override + String get size => 'Grootte'; + @override + String get container => 'Container'; + @override + String get duration => 'Duur'; + @override + String get optimizedForStreaming => 'Geoptimaliseerd voor streaming'; + @override + String get has64bitOffsets => '64-bit Offsets'; } // Path: mediaMenu class _StringsMediaMenuNl implements _StringsMediaMenuEn { - _StringsMediaMenuNl._(this._root); + _StringsMediaMenuNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get markAsWatched => 'Markeer als gekeken'; - @override String get markAsUnwatched => 'Markeer als ongekeken'; - @override String get removeFromContinueWatching => 'Verwijder uit Doorgaan met kijken'; - @override String get goToSeries => 'Ga naar serie'; - @override String get goToSeason => 'Ga naar seizoen'; - @override String get shufflePlay => 'Willekeurig afspelen'; - @override String get fileInfo => 'Bestand info'; + // Translations + @override + String get markAsWatched => 'Markeer als gekeken'; + @override + String get markAsUnwatched => 'Markeer als ongekeken'; + @override + String get removeFromContinueWatching => 'Verwijder uit Doorgaan met kijken'; + @override + String get goToSeries => 'Ga naar serie'; + @override + String get goToSeason => 'Ga naar seizoen'; + @override + String get shufflePlay => 'Willekeurig afspelen'; + @override + String get fileInfo => 'Bestand info'; } // Path: accessibility class _StringsAccessibilityNl implements _StringsAccessibilityEn { - _StringsAccessibilityNl._(this._root); + _StringsAccessibilityNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String mediaCardMovie({required Object title}) => '${title}, film'; - @override String mediaCardShow({required Object title}) => '${title}, TV-serie'; - @override String mediaCardEpisode({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - @override String mediaCardSeason({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - @override String get mediaCardWatched => 'bekeken'; - @override String mediaCardPartiallyWatched({required Object percent}) => '${percent} procent bekeken'; - @override String get mediaCardUnwatched => 'niet bekeken'; - @override String get tapToPlay => 'Tik om af te spelen'; + // Translations + @override + String mediaCardMovie({required Object title}) => '${title}, film'; + @override + String mediaCardShow({required Object title}) => '${title}, TV-serie'; + @override + String mediaCardEpisode({ + required Object title, + required Object episodeInfo, + }) => '${title}, ${episodeInfo}'; + @override + String mediaCardSeason({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + @override + String get mediaCardWatched => 'bekeken'; + @override + String mediaCardPartiallyWatched({required Object percent}) => + '${percent} procent bekeken'; + @override + String get mediaCardUnwatched => 'niet bekeken'; + @override + String get tapToPlay => 'Tik om af te spelen'; } // Path: tooltips class _StringsTooltipsNl implements _StringsTooltipsEn { - _StringsTooltipsNl._(this._root); + _StringsTooltipsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get shufflePlay => 'Willekeurig afspelen'; - @override String get markAsWatched => 'Markeer als gekeken'; - @override String get markAsUnwatched => 'Markeer als ongekeken'; + // Translations + @override + String get shufflePlay => 'Willekeurig afspelen'; + @override + String get markAsWatched => 'Markeer als gekeken'; + @override + String get markAsUnwatched => 'Markeer als ongekeken'; } // Path: videoControls class _StringsVideoControlsNl implements _StringsVideoControlsEn { - _StringsVideoControlsNl._(this._root); + _StringsVideoControlsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get audioLabel => 'Audio'; - @override String get subtitlesLabel => 'Ondertitels'; - @override String get resetToZero => 'Reset naar 0ms'; - @override String addTime({required Object amount, required Object unit}) => '+${amount}${unit}'; - @override String minusTime({required Object amount, required Object unit}) => '-${amount}${unit}'; - @override String playsLater({required Object label}) => '${label} speelt later af'; - @override String playsEarlier({required Object label}) => '${label} speelt eerder af'; - @override String get noOffset => 'Geen offset'; - @override String get letterbox => 'Letterbox'; - @override String get fillScreen => 'Vul scherm'; - @override String get stretch => 'Uitrekken'; - @override String get lockRotation => 'Vergrendel rotatie'; - @override String get unlockRotation => 'Ontgrendel rotatie'; - @override String get sleepTimer => 'Slaaptimer'; - @override String get timerActive => 'Timer actief'; - @override String playbackWillPauseIn({required Object duration}) => 'Afspelen wordt gepauzeerd over ${duration}'; - @override String get sleepTimerCompleted => 'Slaaptimer voltooid - afspelen gepauzeerd'; - @override String get playButton => 'Afspelen'; - @override String get pauseButton => 'Pauzeren'; - @override String seekBackwardButton({required Object seconds}) => 'Terugspoelen ${seconds} seconden'; - @override String seekForwardButton({required Object seconds}) => 'Vooruitspoelen ${seconds} seconden'; - @override String get previousButton => 'Vorige aflevering'; - @override String get nextButton => 'Volgende aflevering'; - @override String get previousChapterButton => 'Vorig hoofdstuk'; - @override String get nextChapterButton => 'Volgend hoofdstuk'; - @override String get volumeButton => 'Volume'; - @override String get muteButton => 'Dempen'; - @override String get unmuteButton => 'Dempen opheffen'; - @override String get settingsButton => 'Video-instellingen'; - @override String get audioTrackButton => 'Audiosporen'; - @override String get subtitlesButton => 'Ondertitels'; - @override String get chaptersButton => 'Hoofdstukken'; - @override String get versionsButton => 'Videoversies'; - @override String get aspectRatioButton => 'Beeldverhouding'; - @override String get fullscreenButton => 'Volledig scherm activeren'; - @override String get exitFullscreenButton => 'Volledig scherm verlaten'; - @override String get rotationLockButton => 'Rotatievergrendeling'; - @override String get timelineSlider => 'Videotijdlijn'; - @override String get volumeSlider => 'Volumeniveau'; - @override String get backButton => 'Terug'; + // Translations + @override + String get audioLabel => 'Audio'; + @override + String get subtitlesLabel => 'Ondertitels'; + @override + String get resetToZero => 'Reset naar 0ms'; + @override + String addTime({required Object amount, required Object unit}) => + '+${amount}${unit}'; + @override + String minusTime({required Object amount, required Object unit}) => + '-${amount}${unit}'; + @override + String playsLater({required Object label}) => '${label} speelt later af'; + @override + String playsEarlier({required Object label}) => '${label} speelt eerder af'; + @override + String get noOffset => 'Geen offset'; + @override + String get letterbox => 'Letterbox'; + @override + String get fillScreen => 'Vul scherm'; + @override + String get stretch => 'Uitrekken'; + @override + String get lockRotation => 'Vergrendel rotatie'; + @override + String get unlockRotation => 'Ontgrendel rotatie'; + @override + String get sleepTimer => 'Slaaptimer'; + @override + String get timerActive => 'Timer actief'; + @override + String playbackWillPauseIn({required Object duration}) => + 'Afspelen wordt gepauzeerd over ${duration}'; + @override + String get sleepTimerCompleted => 'Slaaptimer voltooid - afspelen gepauzeerd'; + @override + String get playButton => 'Afspelen'; + @override + String get pauseButton => 'Pauzeren'; + @override + String seekBackwardButton({required Object seconds}) => + 'Terugspoelen ${seconds} seconden'; + @override + String seekForwardButton({required Object seconds}) => + 'Vooruitspoelen ${seconds} seconden'; + @override + String get previousButton => 'Vorige aflevering'; + @override + String get nextButton => 'Volgende aflevering'; + @override + String get previousChapterButton => 'Vorig hoofdstuk'; + @override + String get nextChapterButton => 'Volgend hoofdstuk'; + @override + String get volumeButton => 'Volume'; + @override + String get muteButton => 'Dempen'; + @override + String get unmuteButton => 'Dempen opheffen'; + @override + String get settingsButton => 'Video-instellingen'; + @override + String get audioTrackButton => 'Audiosporen'; + @override + String get subtitlesButton => 'Ondertitels'; + @override + String get chaptersButton => 'Hoofdstukken'; + @override + String get versionsButton => 'Videoversies'; + @override + String get aspectRatioButton => 'Beeldverhouding'; + @override + String get fullscreenButton => 'Volledig scherm activeren'; + @override + String get exitFullscreenButton => 'Volledig scherm verlaten'; + @override + String get rotationLockButton => 'Rotatievergrendeling'; + @override + String get timelineSlider => 'Videotijdlijn'; + @override + String get volumeSlider => 'Volumeniveau'; + @override + String get backButton => 'Terug'; } // Path: userStatus class _StringsUserStatusNl implements _StringsUserStatusEn { - _StringsUserStatusNl._(this._root); + _StringsUserStatusNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get admin => 'Beheerder'; - @override String get restricted => 'Beperkt'; - @override String get protected => 'Beschermd'; - @override String get current => 'HUIDIG'; + // Translations + @override + String get admin => 'Beheerder'; + @override + String get restricted => 'Beperkt'; + @override + String get protected => 'Beschermd'; + @override + String get current => 'HUIDIG'; } // Path: messages class _StringsMessagesNl implements _StringsMessagesEn { - _StringsMessagesNl._(this._root); + _StringsMessagesNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get markedAsWatched => 'Gemarkeerd als gekeken'; - @override String get markedAsUnwatched => 'Gemarkeerd als ongekeken'; - @override String get removedFromContinueWatching => 'Verwijderd uit Doorgaan met kijken'; - @override String errorLoading({required Object error}) => 'Fout: ${error}'; - @override String get fileInfoNotAvailable => 'Bestand informatie niet beschikbaar'; - @override String errorLoadingFileInfo({required Object error}) => 'Fout bij laden bestand info: ${error}'; - @override String get errorLoadingSeries => 'Fout bij laden serie'; - @override String get errorLoadingSeason => 'Fout bij laden seizoen'; - @override String get musicNotSupported => 'Muziek afspelen wordt nog niet ondersteund'; - @override String get logsCleared => 'Logs gewist'; - @override String get logsCopied => 'Logs gekopieerd naar klembord'; - @override String get noLogsAvailable => 'Geen logs beschikbaar'; - @override String libraryScanning({required Object title}) => 'Scannen "${title}"...'; - @override String libraryScanStarted({required Object title}) => 'Bibliotheek scan gestart voor "${title}"'; - @override String libraryScanFailed({required Object error}) => 'Kon bibliotheek niet scannen: ${error}'; - @override String metadataRefreshing({required Object title}) => 'Metadata vernieuwen voor "${title}"...'; - @override String metadataRefreshStarted({required Object title}) => 'Metadata vernieuwen gestart voor "${title}"'; - @override String metadataRefreshFailed({required Object error}) => 'Kon metadata niet vernieuwen: ${error}'; - @override String get noPlexToken => 'Geen Plex token gevonden. Log opnieuw in.'; - @override String get logoutConfirm => 'Weet je zeker dat je wilt uitloggen?'; - @override String get noSeasonsFound => 'Geen seizoenen gevonden'; - @override String get noEpisodesFound => 'Geen afleveringen gevonden in eerste seizoen'; - @override String get noEpisodesFoundGeneral => 'Geen afleveringen gevonden'; - @override String get noResultsFound => 'Geen resultaten gevonden'; - @override String sleepTimerSet({required Object label}) => 'Slaap timer ingesteld voor ${label}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Kon niet wisselen naar ${displayName}'; - @override String get noItemsAvailable => 'Geen items beschikbaar'; - @override String get failedToCreatePlayQueue => 'Kan afspeelwachtrij niet maken'; - @override String get failedToCreatePlayQueueNoItems => 'Kan afspeelwachtrij niet maken - geen items'; - @override String failedPlayback({required Object action, required Object error}) => 'Afspelen van ${action} mislukt: ${error}'; + // Translations + @override + String get markedAsWatched => 'Gemarkeerd als gekeken'; + @override + String get markedAsUnwatched => 'Gemarkeerd als ongekeken'; + @override + String get removedFromContinueWatching => + 'Verwijderd uit Doorgaan met kijken'; + @override + String errorLoading({required Object error}) => 'Fout: ${error}'; + @override + String get fileInfoNotAvailable => 'Bestand informatie niet beschikbaar'; + @override + String errorLoadingFileInfo({required Object error}) => + 'Fout bij laden bestand info: ${error}'; + @override + String get errorLoadingSeries => 'Fout bij laden serie'; + @override + String get errorLoadingSeason => 'Fout bij laden seizoen'; + @override + String get musicNotSupported => 'Muziek afspelen wordt nog niet ondersteund'; + @override + String get logsCleared => 'Logs gewist'; + @override + String get logsCopied => 'Logs gekopieerd naar klembord'; + @override + String get noLogsAvailable => 'Geen logs beschikbaar'; + @override + String libraryScanning({required Object title}) => 'Scannen "${title}"...'; + @override + String libraryScanStarted({required Object title}) => + 'Bibliotheek scan gestart voor "${title}"'; + @override + String libraryScanFailed({required Object error}) => + 'Kon bibliotheek niet scannen: ${error}'; + @override + String metadataRefreshing({required Object title}) => + 'Metadata vernieuwen voor "${title}"...'; + @override + String metadataRefreshStarted({required Object title}) => + 'Metadata vernieuwen gestart voor "${title}"'; + @override + String metadataRefreshFailed({required Object error}) => + 'Kon metadata niet vernieuwen: ${error}'; + @override + String get noPlexToken => 'Geen Plex token gevonden. Log opnieuw in.'; + @override + String get logoutConfirm => 'Weet je zeker dat je wilt uitloggen?'; + @override + String get noSeasonsFound => 'Geen seizoenen gevonden'; + @override + String get noEpisodesFound => 'Geen afleveringen gevonden in eerste seizoen'; + @override + String get noEpisodesFoundGeneral => 'Geen afleveringen gevonden'; + @override + String get noResultsFound => 'Geen resultaten gevonden'; + @override + String sleepTimerSet({required Object label}) => + 'Slaap timer ingesteld voor ${label}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Kon niet wisselen naar ${displayName}'; + @override + String get noItemsAvailable => 'Geen items beschikbaar'; + @override + String get failedToCreatePlayQueue => 'Kan afspeelwachtrij niet maken'; + @override + String get failedToCreatePlayQueueNoItems => + 'Kan afspeelwachtrij niet maken - geen items'; + @override + String failedPlayback({required Object action, required Object error}) => + 'Afspelen van ${action} mislukt: ${error}'; } // Path: profile class _StringsProfileNl implements _StringsProfileEn { - _StringsProfileNl._(this._root); + _StringsProfileNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get noUsersAvailable => 'Geen gebruikers beschikbaar'; + // Translations + @override + String get noUsersAvailable => 'Geen gebruikers beschikbaar'; } // Path: subtitlingStyling class _StringsSubtitlingStylingNl implements _StringsSubtitlingStylingEn { - _StringsSubtitlingStylingNl._(this._root); + _StringsSubtitlingStylingNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get stylingOptions => 'Opmaak opties'; - @override String get fontSize => 'Lettergrootte'; - @override String get textColor => 'Tekstkleur'; - @override String get borderSize => 'Rand grootte'; - @override String get borderColor => 'Randkleur'; - @override String get backgroundOpacity => 'Achtergrond transparantie'; - @override String get backgroundColor => 'Achtergrondkleur'; + // Translations + @override + String get stylingOptions => 'Opmaak opties'; + @override + String get fontSize => 'Lettergrootte'; + @override + String get textColor => 'Tekstkleur'; + @override + String get borderSize => 'Rand grootte'; + @override + String get borderColor => 'Randkleur'; + @override + String get backgroundOpacity => 'Achtergrond transparantie'; + @override + String get backgroundColor => 'Achtergrondkleur'; } // Path: dialog class _StringsDialogNl implements _StringsDialogEn { - _StringsDialogNl._(this._root); + _StringsDialogNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get confirmAction => 'Bevestig actie'; - @override String get areYouSure => 'Weet je zeker dat je deze actie wilt uitvoeren?'; - @override String get cancel => 'Annuleren'; - @override String get playNow => 'Nu afspelen'; + // Translations + @override + String get confirmAction => 'Bevestig actie'; + @override + String get areYouSure => 'Weet je zeker dat je deze actie wilt uitvoeren?'; + @override + String get cancel => 'Annuleren'; + @override + String get playNow => 'Nu afspelen'; } // Path: discover class _StringsDiscoverNl implements _StringsDiscoverEn { - _StringsDiscoverNl._(this._root); + _StringsDiscoverNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Ontdekken'; - @override String get switchProfile => 'Wissel van profiel'; - @override String get switchServer => 'Wissel van server'; - @override String get logout => 'Uitloggen'; - @override String get noContentAvailable => 'Geen inhoud beschikbaar'; - @override String get addMediaToLibraries => 'Voeg wat media toe aan je bibliotheken'; - @override String get continueWatching => 'Verder kijken'; - @override String get recentlyAdded => 'Recent toegevoegd'; - @override String get play => 'Afspelen'; - @override String get resume => 'Hervatten'; - @override String playEpisode({required Object season, required Object episode}) => 'Speel S${season}, E${episode}'; - @override String resumeEpisode({required Object season, required Object episode}) => 'Hervat S${season}, E${episode}'; - @override String get pause => 'Pauzeren'; - @override String get overview => 'Overzicht'; - @override String get cast => 'Acteurs'; - @override String get seasons => 'Seizoenen'; - @override String get studio => 'Studio'; - @override String get rating => 'Leeftijd'; - @override String get watched => 'Bekeken'; - @override String episodeCount({required Object count}) => '${count} afleveringen'; - @override String watchedProgress({required Object watched, required Object total}) => '${watched}/${total} gekeken'; - @override String get movie => 'Film'; - @override String get tvShow => 'TV Serie'; - @override String minutesLeft({required Object minutes}) => '${minutes} min over'; + // Translations + @override + String get title => 'Ontdekken'; + @override + String get switchProfile => 'Wissel van profiel'; + @override + String get switchServer => 'Wissel van server'; + @override + String get logout => 'Uitloggen'; + @override + String get noContentAvailable => 'Geen inhoud beschikbaar'; + @override + String get addMediaToLibraries => 'Voeg wat media toe aan je bibliotheken'; + @override + String get continueWatching => 'Verder kijken'; + @override + String get recentlyAdded => 'Recent toegevoegd'; + @override + String get play => 'Afspelen'; + @override + String get resume => 'Hervatten'; + @override + String playEpisode({required Object season, required Object episode}) => + 'Speel S${season}, E${episode}'; + @override + String resumeEpisode({required Object season, required Object episode}) => + 'Hervat S${season}, E${episode}'; + @override + String get pause => 'Pauzeren'; + @override + String get overview => 'Overzicht'; + @override + String get cast => 'Acteurs'; + @override + String get seasons => 'Seizoenen'; + @override + String get studio => 'Studio'; + @override + String get rating => 'Leeftijd'; + @override + String get watched => 'Bekeken'; + @override + String episodeCount({required Object count}) => '${count} afleveringen'; + @override + String watchedProgress({required Object watched, required Object total}) => + '${watched}/${total} gekeken'; + @override + String get movie => 'Film'; + @override + String get tvShow => 'TV Serie'; + @override + String minutesLeft({required Object minutes}) => '${minutes} min over'; } // Path: errors class _StringsErrorsNl implements _StringsErrorsEn { - _StringsErrorsNl._(this._root); + _StringsErrorsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String searchFailed({required Object error}) => 'Zoeken mislukt: ${error}'; - @override String connectionTimeout({required Object context}) => 'Verbinding time-out tijdens laden ${context}'; - @override String get connectionFailed => 'Kan geen verbinding maken met Plex server'; - @override String failedToLoad({required Object context, required Object error}) => 'Kon ${context} niet laden: ${error}'; - @override String get noClientAvailable => 'Geen client beschikbaar'; - @override String authenticationFailed({required Object error}) => 'Authenticatie mislukt: ${error}'; - @override String get couldNotLaunchUrl => 'Kon auth URL niet openen'; - @override String get pleaseEnterToken => 'Voer een token in'; - @override String get invalidToken => 'Ongeldig token'; - @override String failedToVerifyToken({required Object error}) => 'Kon token niet verifiëren: ${error}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Kon niet wisselen naar ${displayName}'; - @override String get connectionFailedGeneric => 'Verbinding mislukt'; + // Translations + @override + String searchFailed({required Object error}) => 'Zoeken mislukt: ${error}'; + @override + String connectionTimeout({required Object context}) => + 'Verbinding time-out tijdens laden ${context}'; + @override + String get connectionFailed => 'Kan geen verbinding maken met Plex server'; + @override + String failedToLoad({required Object context, required Object error}) => + 'Kon ${context} niet laden: ${error}'; + @override + String get noClientAvailable => 'Geen client beschikbaar'; + @override + String authenticationFailed({required Object error}) => + 'Authenticatie mislukt: ${error}'; + @override + String get couldNotLaunchUrl => 'Kon auth URL niet openen'; + @override + String get pleaseEnterToken => 'Voer een token in'; + @override + String get invalidToken => 'Ongeldig token'; + @override + String failedToVerifyToken({required Object error}) => + 'Kon token niet verifiëren: ${error}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Kon niet wisselen naar ${displayName}'; + @override + String get connectionFailedGeneric => 'Verbinding mislukt'; } // Path: libraries class _StringsLibrariesNl implements _StringsLibrariesEn { - _StringsLibrariesNl._(this._root); + _StringsLibrariesNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Bibliotheken'; - @override String get scanLibraryFiles => 'Scan bibliotheek bestanden'; - @override String get scanLibrary => 'Scan bibliotheek'; - @override String get analyze => 'Analyseren'; - @override String get analyzeLibrary => 'Analyseer bibliotheek'; - @override String get refreshMetadata => 'Vernieuw metadata'; - @override String get emptyTrash => 'Prullenbak legen'; - @override String emptyingTrash({required Object title}) => 'Prullenbak legen voor "${title}"...'; - @override String trashEmptied({required Object title}) => 'Prullenbak geleegd voor "${title}"'; - @override String failedToEmptyTrash({required Object error}) => 'Kon prullenbak niet legen: ${error}'; - @override String analyzing({required Object title}) => 'Analyseren "${title}"...'; - @override String analysisStarted({required Object title}) => 'Analyse gestart voor "${title}"'; - @override String failedToAnalyze({required Object error}) => 'Kon bibliotheek niet analyseren: ${error}'; - @override String get noLibrariesFound => 'Geen bibliotheken gevonden'; - @override String get thisLibraryIsEmpty => 'Deze bibliotheek is leeg'; - @override String get all => 'Alles'; - @override String get clearAll => 'Alles wissen'; - @override String scanLibraryConfirm({required Object title}) => 'Weet je zeker dat je "${title}" wilt scannen?'; - @override String analyzeLibraryConfirm({required Object title}) => 'Weet je zeker dat je "${title}" wilt analyseren?'; - @override String refreshMetadataConfirm({required Object title}) => 'Weet je zeker dat je metadata wilt vernieuwen voor "${title}"?'; - @override String emptyTrashConfirm({required Object title}) => 'Weet je zeker dat je de prullenbak wilt legen voor "${title}"?'; - @override String get manageLibraries => 'Beheer bibliotheken'; - @override String get sort => 'Sorteren'; - @override String get sortBy => 'Sorteer op'; - @override String get filters => 'Filters'; - @override String loadingLibraryWithCount({required Object count}) => 'Bibliotheek laden... (${count} items geladen)'; - @override String get confirmActionMessage => 'Weet je zeker dat je deze actie wilt uitvoeren?'; - @override String get showLibrary => 'Toon bibliotheek'; - @override String get hideLibrary => 'Verberg bibliotheek'; - @override String get libraryOptions => 'Bibliotheek opties'; - @override String get content => 'bibliotheekinhoud'; - @override String get selectLibrary => 'Bibliotheek kiezen'; - @override String filtersWithCount({required Object count}) => 'Filters (${count})'; - @override String get noRecommendations => 'Geen aanbevelingen beschikbaar'; - @override String get noCollections => 'Geen collecties in deze bibliotheek'; - @override String get noFoldersFound => 'Geen mappen gevonden'; - @override String get folders => 'mappen'; - @override late final _StringsLibrariesTabsNl tabs = _StringsLibrariesTabsNl._(_root); - @override late final _StringsLibrariesGroupingsNl groupings = _StringsLibrariesGroupingsNl._(_root); + // Translations + @override + String get title => 'Bibliotheken'; + @override + String get scanLibraryFiles => 'Scan bibliotheek bestanden'; + @override + String get scanLibrary => 'Scan bibliotheek'; + @override + String get analyze => 'Analyseren'; + @override + String get analyzeLibrary => 'Analyseer bibliotheek'; + @override + String get refreshMetadata => 'Vernieuw metadata'; + @override + String get emptyTrash => 'Prullenbak legen'; + @override + String emptyingTrash({required Object title}) => + 'Prullenbak legen voor "${title}"...'; + @override + String trashEmptied({required Object title}) => + 'Prullenbak geleegd voor "${title}"'; + @override + String failedToEmptyTrash({required Object error}) => + 'Kon prullenbak niet legen: ${error}'; + @override + String analyzing({required Object title}) => 'Analyseren "${title}"...'; + @override + String analysisStarted({required Object title}) => + 'Analyse gestart voor "${title}"'; + @override + String failedToAnalyze({required Object error}) => + 'Kon bibliotheek niet analyseren: ${error}'; + @override + String get noLibrariesFound => 'Geen bibliotheken gevonden'; + @override + String get thisLibraryIsEmpty => 'Deze bibliotheek is leeg'; + @override + String get all => 'Alles'; + @override + String get clearAll => 'Alles wissen'; + @override + String scanLibraryConfirm({required Object title}) => + 'Weet je zeker dat je "${title}" wilt scannen?'; + @override + String analyzeLibraryConfirm({required Object title}) => + 'Weet je zeker dat je "${title}" wilt analyseren?'; + @override + String refreshMetadataConfirm({required Object title}) => + 'Weet je zeker dat je metadata wilt vernieuwen voor "${title}"?'; + @override + String emptyTrashConfirm({required Object title}) => + 'Weet je zeker dat je de prullenbak wilt legen voor "${title}"?'; + @override + String get manageLibraries => 'Beheer bibliotheken'; + @override + String get sort => 'Sorteren'; + @override + String get sortBy => 'Sorteer op'; + @override + String get filters => 'Filters'; + @override + String loadingLibraryWithCount({required Object count}) => + 'Bibliotheek laden... (${count} items geladen)'; + @override + String get confirmActionMessage => + 'Weet je zeker dat je deze actie wilt uitvoeren?'; + @override + String get showLibrary => 'Toon bibliotheek'; + @override + String get hideLibrary => 'Verberg bibliotheek'; + @override + String get libraryOptions => 'Bibliotheek opties'; + @override + String get content => 'bibliotheekinhoud'; + @override + String get selectLibrary => 'Bibliotheek kiezen'; + @override + String filtersWithCount({required Object count}) => 'Filters (${count})'; + @override + String get noRecommendations => 'Geen aanbevelingen beschikbaar'; + @override + String get noCollections => 'Geen collecties in deze bibliotheek'; + @override + String get noFoldersFound => 'Geen mappen gevonden'; + @override + String get folders => 'mappen'; + @override + late final _StringsLibrariesTabsNl tabs = _StringsLibrariesTabsNl._(_root); + @override + late final _StringsLibrariesGroupingsNl groupings = + _StringsLibrariesGroupingsNl._(_root); } // Path: about class _StringsAboutNl implements _StringsAboutEn { - _StringsAboutNl._(this._root); + _StringsAboutNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Over'; - @override String get openSourceLicenses => 'Open Source licenties'; - @override String versionLabel({required Object version}) => 'Versie ${version}'; - @override String get appDescription => 'Een mooie Plex client voor Flutter'; - @override String get viewLicensesDescription => 'Bekijk licenties van third-party bibliotheken'; + // Translations + @override + String get title => 'Over'; + @override + String get openSourceLicenses => 'Open Source licenties'; + @override + String versionLabel({required Object version}) => 'Versie ${version}'; + @override + String get appDescription => 'Een mooie Plex client voor Flutter'; + @override + String get viewLicensesDescription => + 'Bekijk licenties van third-party bibliotheken'; } // Path: serverSelection class _StringsServerSelectionNl implements _StringsServerSelectionEn { - _StringsServerSelectionNl._(this._root); + _StringsServerSelectionNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get connectingToServer => 'Verbinden met server...'; - @override String get connectingToAllServers => 'Verbinden met alle servers...'; - @override String get connectToAllServers => 'Verbind met alle servers'; - @override String get allServerConnectionsFailed => 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.'; - @override String foundServers({required Object count}) => '${count} server(s) gevonden'; - @override String get orSelectIndividually => 'Of selecteer een server individueel:'; - @override String get serverDebugCopied => 'Server debug gegevens gekopieerd naar klembord'; - @override String get copyDebugData => 'Kopieer debug gegevens'; - @override String get noServersFound => 'Geen servers gevonden'; - @override String malformedServerData({required Object count}) => '${count} server(s) gevonden met verkeerde data. Geen geldige servers beschikbaar.'; - @override String get incompleteServerInfo => 'Sommige servers hebben incomplete informatie en zijn overgeslagen. Controleer je Plex.tv account.'; - @override String get incompleteConnectionInfo => 'Server verbinding informatie is incompleet. Probeer opnieuw.'; - @override String malformedServerInfo({required Object message}) => 'Server informatie is verkeerd geformatteerd: ${message}'; - @override String get networkConnectionFailed => 'Netwerkverbinding mislukt. Controleer je internetverbinding en probeer opnieuw.'; - @override String get authenticationFailed => 'Authenticatie mislukt. Log opnieuw in.'; - @override String get plexServiceUnavailable => 'Plex service niet beschikbaar. Probeer later opnieuw.'; - @override String failedToLoadServers({required Object error}) => 'Kon servers niet laden: ${error}'; + // Translations + @override + String get connectingToServer => 'Verbinden met server...'; + @override + String get connectingToAllServers => 'Verbinden met alle servers...'; + @override + String get connectToAllServers => 'Verbind met alle servers'; + @override + String get allServerConnectionsFailed => + 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.'; + @override + String foundServers({required Object count}) => '${count} server(s) gevonden'; + @override + String get orSelectIndividually => 'Of selecteer een server individueel:'; + @override + String get serverDebugCopied => + 'Server debug gegevens gekopieerd naar klembord'; + @override + String get copyDebugData => 'Kopieer debug gegevens'; + @override + String get noServersFound => 'Geen servers gevonden'; + @override + String malformedServerData({required Object count}) => + '${count} server(s) gevonden met verkeerde data. Geen geldige servers beschikbaar.'; + @override + String get incompleteServerInfo => + 'Sommige servers hebben incomplete informatie en zijn overgeslagen. Controleer je Plex.tv account.'; + @override + String get incompleteConnectionInfo => + 'Server verbinding informatie is incompleet. Probeer opnieuw.'; + @override + String malformedServerInfo({required Object message}) => + 'Server informatie is verkeerd geformatteerd: ${message}'; + @override + String get networkConnectionFailed => + 'Netwerkverbinding mislukt. Controleer je internetverbinding en probeer opnieuw.'; + @override + String get authenticationFailed => 'Authenticatie mislukt. Log opnieuw in.'; + @override + String get plexServiceUnavailable => + 'Plex service niet beschikbaar. Probeer later opnieuw.'; + @override + String failedToLoadServers({required Object error}) => + 'Kon servers niet laden: ${error}'; } // Path: hubDetail class _StringsHubDetailNl implements _StringsHubDetailEn { - _StringsHubDetailNl._(this._root); + _StringsHubDetailNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Titel'; - @override String get releaseYear => 'Uitgavejaar'; - @override String get dateAdded => 'Datum toegevoegd'; - @override String get rating => 'Beoordeling'; - @override String get noItemsFound => 'Geen items gevonden'; + // Translations + @override + String get title => 'Titel'; + @override + String get releaseYear => 'Uitgavejaar'; + @override + String get dateAdded => 'Datum toegevoegd'; + @override + String get rating => 'Beoordeling'; + @override + String get noItemsFound => 'Geen items gevonden'; } // Path: logs class _StringsLogsNl implements _StringsLogsEn { - _StringsLogsNl._(this._root); + _StringsLogsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Logbestanden'; - @override String get clearLogs => 'Wis logs'; - @override String get copyLogs => 'Kopieer logs'; - @override String get exportLogs => 'Exporteer logs'; - @override String get noLogsToShow => 'Geen logs om te tonen'; - @override String get error => 'Fout:'; - @override String get stackTrace => 'Stacktracering:'; + // Translations + @override + String get title => 'Logbestanden'; + @override + String get clearLogs => 'Wis logs'; + @override + String get copyLogs => 'Kopieer logs'; + @override + String get exportLogs => 'Exporteer logs'; + @override + String get noLogsToShow => 'Geen logs om te tonen'; + @override + String get error => 'Fout:'; + @override + String get stackTrace => 'Stacktracering:'; } // Path: licenses class _StringsLicensesNl implements _StringsLicensesEn { - _StringsLicensesNl._(this._root); + _StringsLicensesNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get relatedPackages => 'Gerelateerde pakketten'; - @override String get license => 'Licentie'; - @override String licenseNumber({required Object number}) => 'Licentie ${number}'; - @override String licensesCount({required Object count}) => '${count} licenties'; + // Translations + @override + String get relatedPackages => 'Gerelateerde pakketten'; + @override + String get license => 'Licentie'; + @override + String licenseNumber({required Object number}) => 'Licentie ${number}'; + @override + String licensesCount({required Object count}) => '${count} licenties'; } // Path: navigation class _StringsNavigationNl implements _StringsNavigationEn { - _StringsNavigationNl._(this._root); + _StringsNavigationNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get home => 'Thuis'; - @override String get search => 'Zoeken'; - @override String get libraries => 'Bibliotheken'; - @override String get settings => 'Instellingen'; + // Translations + @override + String get home => 'Thuis'; + @override + String get search => 'Zoeken'; + @override + String get libraries => 'Bibliotheken'; + @override + String get settings => 'Instellingen'; } // Path: playlists class _StringsPlaylistsNl implements _StringsPlaylistsEn { - _StringsPlaylistsNl._(this._root); + _StringsPlaylistsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Afspeellijsten'; - @override String get noPlaylists => 'Geen afspeellijsten gevonden'; - @override String get create => 'Afspeellijst maken'; - @override String get newPlaylist => 'Nieuwe afspeellijst'; - @override String get playlistName => 'Naam afspeellijst'; - @override String get enterPlaylistName => 'Voer naam afspeellijst in'; - @override String get edit => 'Afspeellijst bewerken'; - @override String get delete => 'Afspeellijst verwijderen'; - @override String get addTo => 'Toevoegen aan afspeellijst'; - @override String get addItems => 'Items toevoegen'; - @override String get removeItem => 'Verwijderen uit afspeellijst'; - @override String get clearPlaylist => 'Afspeellijst wissen'; - @override String get playAll => 'Alles afspelen'; - @override String get shuffle => 'Willekeurig'; - @override String get smartPlaylist => 'Slimme afspeellijst'; - @override String get regularPlaylist => 'Normale afspeellijst'; - @override String itemCount({required Object count}) => '${count} items'; - @override String get oneItem => '1 item'; - @override String get emptyPlaylist => 'Deze afspeellijst is leeg'; - @override String get deleteConfirm => 'Afspeellijst verwijderen?'; - @override String deleteMessage({required Object name}) => 'Weet je zeker dat je "${name}" wilt verwijderen?'; - @override String get created => 'Afspeellijst gemaakt'; - @override String get updated => 'Afspeellijst bijgewerkt'; - @override String get deleted => 'Afspeellijst verwijderd'; - @override String get itemAdded => 'Toegevoegd aan afspeellijst'; - @override String get itemRemoved => 'Verwijderd uit afspeellijst'; - @override String get selectPlaylist => 'Selecteer afspeellijst'; - @override String get createNewPlaylist => 'Nieuwe afspeellijst maken'; - @override String get errorCreating => 'Fout bij maken afspeellijst'; - @override String get errorDeleting => 'Fout bij verwijderen afspeellijst'; - @override String get errorLoading => 'Fout bij laden afspeellijsten'; - @override String get errorAdding => 'Fout bij toevoegen aan afspeellijst'; - @override String get errorReordering => 'Fout bij herschikken van afspeellijstitem'; - @override String get errorRemoving => 'Fout bij verwijderen uit afspeellijst'; - @override String get playlist => 'Afspeellijst'; + // Translations + @override + String get title => 'Afspeellijsten'; + @override + String get noPlaylists => 'Geen afspeellijsten gevonden'; + @override + String get create => 'Afspeellijst maken'; + @override + String get newPlaylist => 'Nieuwe afspeellijst'; + @override + String get playlistName => 'Naam afspeellijst'; + @override + String get enterPlaylistName => 'Voer naam afspeellijst in'; + @override + String get edit => 'Afspeellijst bewerken'; + @override + String get delete => 'Afspeellijst verwijderen'; + @override + String get addTo => 'Toevoegen aan afspeellijst'; + @override + String get addItems => 'Items toevoegen'; + @override + String get removeItem => 'Verwijderen uit afspeellijst'; + @override + String get clearPlaylist => 'Afspeellijst wissen'; + @override + String get playAll => 'Alles afspelen'; + @override + String get shuffle => 'Willekeurig'; + @override + String get smartPlaylist => 'Slimme afspeellijst'; + @override + String get regularPlaylist => 'Normale afspeellijst'; + @override + String itemCount({required Object count}) => '${count} items'; + @override + String get oneItem => '1 item'; + @override + String get emptyPlaylist => 'Deze afspeellijst is leeg'; + @override + String get deleteConfirm => 'Afspeellijst verwijderen?'; + @override + String deleteMessage({required Object name}) => + 'Weet je zeker dat je "${name}" wilt verwijderen?'; + @override + String get created => 'Afspeellijst gemaakt'; + @override + String get updated => 'Afspeellijst bijgewerkt'; + @override + String get deleted => 'Afspeellijst verwijderd'; + @override + String get itemAdded => 'Toegevoegd aan afspeellijst'; + @override + String get itemRemoved => 'Verwijderd uit afspeellijst'; + @override + String get selectPlaylist => 'Selecteer afspeellijst'; + @override + String get createNewPlaylist => 'Nieuwe afspeellijst maken'; + @override + String get errorCreating => 'Fout bij maken afspeellijst'; + @override + String get errorDeleting => 'Fout bij verwijderen afspeellijst'; + @override + String get errorLoading => 'Fout bij laden afspeellijsten'; + @override + String get errorAdding => 'Fout bij toevoegen aan afspeellijst'; + @override + String get errorReordering => 'Fout bij herschikken van afspeellijstitem'; + @override + String get errorRemoving => 'Fout bij verwijderen uit afspeellijst'; + @override + String get playlist => 'Afspeellijst'; } // Path: collections class _StringsCollectionsNl implements _StringsCollectionsEn { - _StringsCollectionsNl._(this._root); + _StringsCollectionsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get title => 'Collecties'; - @override String get collection => 'Collectie'; - @override String get empty => 'Collectie is leeg'; - @override String get noItems => 'Geen items in deze collectie'; - @override String get unknownLibrarySection => 'Kan niet verwijderen: onbekende bibliotheeksectie'; - @override String get deleteCollection => 'Collectie verwijderen'; - @override String deleteConfirm({required Object title}) => 'Weet je zeker dat je "${title}" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.'; - @override String get deleted => 'Collectie verwijderd'; - @override String get deleteFailed => 'Collectie verwijderen mislukt'; - @override String deleteFailedWithError({required Object error}) => 'Collectie verwijderen mislukt: ${error}'; - @override String failedToLoadItems({required Object error}) => 'Collectie-items laden mislukt: ${error}'; - @override String get addTo => 'Toevoegen aan collectie'; - @override String get selectCollection => 'Selecteer collectie'; - @override String get createNewCollection => 'Nieuwe collectie maken'; - @override String get collectionName => 'Collectienaam'; - @override String get enterCollectionName => 'Voer collectienaam in'; - @override String get addedToCollection => 'Toegevoegd aan collectie'; - @override String get errorAddingToCollection => 'Fout bij toevoegen aan collectie'; - @override String get created => 'Collectie gemaakt'; - @override String get removeFromCollection => 'Verwijderen uit collectie'; - @override String removeFromCollectionConfirm({required Object title}) => '"${title}" uit deze collectie verwijderen?'; - @override String get removedFromCollection => 'Uit collectie verwijderd'; - @override String get removeFromCollectionFailed => 'Verwijderen uit collectie mislukt'; - @override String removeFromCollectionError({required Object error}) => 'Fout bij verwijderen uit collectie: ${error}'; + // Translations + @override + String get title => 'Collecties'; + @override + String get collection => 'Collectie'; + @override + String get empty => 'Collectie is leeg'; + @override + String get noItems => 'Geen items in deze collectie'; + @override + String get unknownLibrarySection => + 'Kan niet verwijderen: onbekende bibliotheeksectie'; + @override + String get deleteCollection => 'Collectie verwijderen'; + @override + String deleteConfirm({required Object title}) => + 'Weet je zeker dat je "${title}" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.'; + @override + String get deleted => 'Collectie verwijderd'; + @override + String get deleteFailed => 'Collectie verwijderen mislukt'; + @override + String deleteFailedWithError({required Object error}) => + 'Collectie verwijderen mislukt: ${error}'; + @override + String failedToLoadItems({required Object error}) => + 'Collectie-items laden mislukt: ${error}'; + @override + String get addTo => 'Toevoegen aan collectie'; + @override + String get selectCollection => 'Selecteer collectie'; + @override + String get createNewCollection => 'Nieuwe collectie maken'; + @override + String get collectionName => 'Collectienaam'; + @override + String get enterCollectionName => 'Voer collectienaam in'; + @override + String get addedToCollection => 'Toegevoegd aan collectie'; + @override + String get errorAddingToCollection => 'Fout bij toevoegen aan collectie'; + @override + String get created => 'Collectie gemaakt'; + @override + String get removeFromCollection => 'Verwijderen uit collectie'; + @override + String removeFromCollectionConfirm({required Object title}) => + '"${title}" uit deze collectie verwijderen?'; + @override + String get removedFromCollection => 'Uit collectie verwijderd'; + @override + String get removeFromCollectionFailed => 'Verwijderen uit collectie mislukt'; + @override + String removeFromCollectionError({required Object error}) => + 'Fout bij verwijderen uit collectie: ${error}'; } // Path: libraries.tabs class _StringsLibrariesTabsNl implements _StringsLibrariesTabsEn { - _StringsLibrariesTabsNl._(this._root); + _StringsLibrariesTabsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get recommended => 'Aanbevolen'; - @override String get browse => 'Bladeren'; - @override String get collections => 'Collecties'; - @override String get playlists => 'Afspeellijsten'; + // Translations + @override + String get recommended => 'Aanbevolen'; + @override + String get browse => 'Bladeren'; + @override + String get collections => 'Collecties'; + @override + String get playlists => 'Afspeellijsten'; } // Path: libraries.groupings class _StringsLibrariesGroupingsNl implements _StringsLibrariesGroupingsEn { - _StringsLibrariesGroupingsNl._(this._root); + _StringsLibrariesGroupingsNl._(this._root); - @override final _StringsNl _root; // ignore: unused_field + @override + final _StringsNl _root; // ignore: unused_field - // Translations - @override String get all => 'Alles'; - @override String get movies => 'Films'; - @override String get shows => 'Series'; - @override String get seasons => 'Seizoenen'; - @override String get episodes => 'Afleveringen'; - @override String get folders => 'Mappen'; + // Translations + @override + String get all => 'Alles'; + @override + String get movies => 'Films'; + @override + String get shows => 'Series'; + @override + String get seasons => 'Seizoenen'; + @override + String get episodes => 'Afleveringen'; + @override + String get folders => 'Mappen'; } // Path: class _StringsSv implements Translations { - /// You can call this constructor and build your own translation instance of this locale. - /// Constructing via the enum [AppLocale.build] is preferred. - _StringsSv.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) - : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), - $meta = TranslationMetadata( - locale: AppLocale.sv, - overrides: overrides ?? {}, - cardinalResolver: cardinalResolver, - ordinalResolver: ordinalResolver, - ) { - $meta.setFlatMapFunction(_flatMapFunction); - } + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsSv.build({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) : assert( + overrides == null, + 'Set "translation_overrides: true" in order to enable this feature.', + ), + $meta = TranslationMetadata( + locale: AppLocale.sv, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } - /// Metadata for the translations of . - @override final TranslationMetadata $meta; + /// Metadata for the translations of . + @override + final TranslationMetadata $meta; - /// Access flat map - @override dynamic operator[](String key) => $meta.getTranslation(key); + /// Access flat map + @override + dynamic operator [](String key) => $meta.getTranslation(key); - @override late final _StringsSv _root = this; // ignore: unused_field + @override + late final _StringsSv _root = this; // ignore: unused_field - // Translations - @override late final _StringsAppSv app = _StringsAppSv._(_root); - @override late final _StringsAuthSv auth = _StringsAuthSv._(_root); - @override late final _StringsCommonSv common = _StringsCommonSv._(_root); - @override late final _StringsScreensSv screens = _StringsScreensSv._(_root); - @override late final _StringsUpdateSv update = _StringsUpdateSv._(_root); - @override late final _StringsSettingsSv settings = _StringsSettingsSv._(_root); - @override late final _StringsSearchSv search = _StringsSearchSv._(_root); - @override late final _StringsHotkeysSv hotkeys = _StringsHotkeysSv._(_root); - @override late final _StringsPinEntrySv pinEntry = _StringsPinEntrySv._(_root); - @override late final _StringsFileInfoSv fileInfo = _StringsFileInfoSv._(_root); - @override late final _StringsMediaMenuSv mediaMenu = _StringsMediaMenuSv._(_root); - @override late final _StringsAccessibilitySv accessibility = _StringsAccessibilitySv._(_root); - @override late final _StringsTooltipsSv tooltips = _StringsTooltipsSv._(_root); - @override late final _StringsVideoControlsSv videoControls = _StringsVideoControlsSv._(_root); - @override late final _StringsUserStatusSv userStatus = _StringsUserStatusSv._(_root); - @override late final _StringsMessagesSv messages = _StringsMessagesSv._(_root); - @override late final _StringsProfileSv profile = _StringsProfileSv._(_root); - @override late final _StringsSubtitlingStylingSv subtitlingStyling = _StringsSubtitlingStylingSv._(_root); - @override late final _StringsDialogSv dialog = _StringsDialogSv._(_root); - @override late final _StringsDiscoverSv discover = _StringsDiscoverSv._(_root); - @override late final _StringsErrorsSv errors = _StringsErrorsSv._(_root); - @override late final _StringsLibrariesSv libraries = _StringsLibrariesSv._(_root); - @override late final _StringsAboutSv about = _StringsAboutSv._(_root); - @override late final _StringsServerSelectionSv serverSelection = _StringsServerSelectionSv._(_root); - @override late final _StringsHubDetailSv hubDetail = _StringsHubDetailSv._(_root); - @override late final _StringsLogsSv logs = _StringsLogsSv._(_root); - @override late final _StringsLicensesSv licenses = _StringsLicensesSv._(_root); - @override late final _StringsNavigationSv navigation = _StringsNavigationSv._(_root); - @override late final _StringsPlaylistsSv playlists = _StringsPlaylistsSv._(_root); - @override late final _StringsCollectionsSv collections = _StringsCollectionsSv._(_root); + // Translations + @override + late final _StringsAppSv app = _StringsAppSv._(_root); + @override + late final _StringsAuthSv auth = _StringsAuthSv._(_root); + @override + late final _StringsCommonSv common = _StringsCommonSv._(_root); + @override + late final _StringsScreensSv screens = _StringsScreensSv._(_root); + @override + late final _StringsUpdateSv update = _StringsUpdateSv._(_root); + @override + late final _StringsSettingsSv settings = _StringsSettingsSv._(_root); + @override + late final _StringsSearchSv search = _StringsSearchSv._(_root); + @override + late final _StringsHotkeysSv hotkeys = _StringsHotkeysSv._(_root); + @override + late final _StringsPinEntrySv pinEntry = _StringsPinEntrySv._(_root); + @override + late final _StringsFileInfoSv fileInfo = _StringsFileInfoSv._(_root); + @override + late final _StringsMediaMenuSv mediaMenu = _StringsMediaMenuSv._(_root); + @override + late final _StringsAccessibilitySv accessibility = _StringsAccessibilitySv._( + _root, + ); + @override + late final _StringsTooltipsSv tooltips = _StringsTooltipsSv._(_root); + @override + late final _StringsVideoControlsSv videoControls = _StringsVideoControlsSv._( + _root, + ); + @override + late final _StringsUserStatusSv userStatus = _StringsUserStatusSv._(_root); + @override + late final _StringsMessagesSv messages = _StringsMessagesSv._(_root); + @override + late final _StringsProfileSv profile = _StringsProfileSv._(_root); + @override + late final _StringsSubtitlingStylingSv subtitlingStyling = + _StringsSubtitlingStylingSv._(_root); + @override + late final _StringsDialogSv dialog = _StringsDialogSv._(_root); + @override + late final _StringsDiscoverSv discover = _StringsDiscoverSv._(_root); + @override + late final _StringsErrorsSv errors = _StringsErrorsSv._(_root); + @override + late final _StringsLibrariesSv libraries = _StringsLibrariesSv._(_root); + @override + late final _StringsAboutSv about = _StringsAboutSv._(_root); + @override + late final _StringsServerSelectionSv serverSelection = + _StringsServerSelectionSv._(_root); + @override + late final _StringsHubDetailSv hubDetail = _StringsHubDetailSv._(_root); + @override + late final _StringsLogsSv logs = _StringsLogsSv._(_root); + @override + late final _StringsLicensesSv licenses = _StringsLicensesSv._(_root); + @override + late final _StringsNavigationSv navigation = _StringsNavigationSv._(_root); + @override + late final _StringsPlaylistsSv playlists = _StringsPlaylistsSv._(_root); + @override + late final _StringsCollectionsSv collections = _StringsCollectionsSv._(_root); } // Path: app class _StringsAppSv implements _StringsAppEn { - _StringsAppSv._(this._root); + _StringsAppSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Plezy'; - @override String get loading => 'Laddar...'; + // Translations + @override + String get title => 'Plezy'; + @override + String get loading => 'Laddar...'; } // Path: auth class _StringsAuthSv implements _StringsAuthEn { - _StringsAuthSv._(this._root); + _StringsAuthSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get signInWithPlex => 'Logga in med Plex'; - @override String get showQRCode => 'Visa QR-kod'; - @override String get cancel => 'Avbryt'; - @override String get authenticate => 'Autentisera'; - @override String get retry => 'Försök igen'; - @override String get debugEnterToken => 'Debug: Ange Plex-token'; - @override String get plexTokenLabel => 'Plex-autentiseringstoken'; - @override String get plexTokenHint => 'Ange din Plex.tv-token'; - @override String get authenticationTimeout => 'Autentisering tog för lång tid. Försök igen.'; - @override String get scanQRCodeInstruction => 'Skanna denna QR-kod med en enhet inloggad på Plex för att autentisera.'; - @override String get waitingForAuth => 'Väntar på autentisering...\nVänligen slutför inloggning i din webbläsare.'; + // Translations + @override + String get signInWithPlex => 'Logga in med Plex'; + @override + String get showQRCode => 'Visa QR-kod'; + @override + String get cancel => 'Avbryt'; + @override + String get authenticate => 'Autentisera'; + @override + String get retry => 'Försök igen'; + @override + String get debugEnterToken => 'Debug: Ange Plex-token'; + @override + String get plexTokenLabel => 'Plex-autentiseringstoken'; + @override + String get plexTokenHint => 'Ange din Plex.tv-token'; + @override + String get authenticationTimeout => + 'Autentisering tog för lång tid. Försök igen.'; + @override + String get scanQRCodeInstruction => + 'Skanna denna QR-kod med en enhet inloggad på Plex för att autentisera.'; + @override + String get waitingForAuth => + 'Väntar på autentisering...\nVänligen slutför inloggning i din webbläsare.'; } // Path: common class _StringsCommonSv implements _StringsCommonEn { - _StringsCommonSv._(this._root); + _StringsCommonSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get cancel => 'Avbryt'; - @override String get save => 'Spara'; - @override String get close => 'Stäng'; - @override String get clear => 'Rensa'; - @override String get reset => 'Återställ'; - @override String get later => 'Senare'; - @override String get submit => 'Skicka'; - @override String get confirm => 'Bekräfta'; - @override String get retry => 'Försök igen'; - @override String get playNow => 'Spela nu'; - @override String get logout => 'Logga ut'; - @override String get online => 'Online'; - @override String get offline => 'Offline'; - @override String get owned => 'Egen'; - @override String get shared => 'Delad'; - @override String get current => 'NUVARANDE'; - @override String get unknown => 'Okänd'; - @override String get refresh => 'Uppdatera'; - @override String get yes => 'Ja'; - @override String get no => 'Nej'; - @override String get server => 'Server'; - @override String get delete => 'Ta bort'; - @override String get shuffle => 'Blanda'; - @override String get addTo => 'Lägg till i...'; + // Translations + @override + String get cancel => 'Avbryt'; + @override + String get save => 'Spara'; + @override + String get close => 'Stäng'; + @override + String get clear => 'Rensa'; + @override + String get reset => 'Återställ'; + @override + String get later => 'Senare'; + @override + String get submit => 'Skicka'; + @override + String get confirm => 'Bekräfta'; + @override + String get retry => 'Försök igen'; + @override + String get playNow => 'Spela nu'; + @override + String get logout => 'Logga ut'; + @override + String get online => 'Online'; + @override + String get offline => 'Offline'; + @override + String get owned => 'Egen'; + @override + String get shared => 'Delad'; + @override + String get current => 'NUVARANDE'; + @override + String get unknown => 'Okänd'; + @override + String get refresh => 'Uppdatera'; + @override + String get yes => 'Ja'; + @override + String get no => 'Nej'; + @override + String get server => 'Server'; + @override + String get delete => 'Ta bort'; + @override + String get shuffle => 'Blanda'; + @override + String get addTo => 'Lägg till i...'; } // Path: screens class _StringsScreensSv implements _StringsScreensEn { - _StringsScreensSv._(this._root); + _StringsScreensSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get licenses => 'Licenser'; - @override String get selectServer => 'Välj server'; - @override String get switchProfile => 'Byt profil'; - @override String get subtitleStyling => 'Undertext-styling'; - @override String get search => 'Sök'; - @override String get logs => 'Loggar'; + // Translations + @override + String get licenses => 'Licenser'; + @override + String get selectServer => 'Välj server'; + @override + String get switchProfile => 'Byt profil'; + @override + String get subtitleStyling => 'Undertext-styling'; + @override + String get search => 'Sök'; + @override + String get logs => 'Loggar'; } // Path: update class _StringsUpdateSv implements _StringsUpdateEn { - _StringsUpdateSv._(this._root); + _StringsUpdateSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get available => 'Uppdatering tillgänglig'; - @override String versionAvailable({required Object version}) => 'Version ${version} är tillgänglig'; - @override String currentVersion({required Object version}) => 'Nuvarande: ${version}'; - @override String get skipVersion => 'Hoppa över denna version'; - @override String get viewRelease => 'Visa release'; - @override String get latestVersion => 'Du har den senaste versionen'; - @override String get checkFailed => 'Misslyckades att kontrollera uppdateringar'; + // Translations + @override + String get available => 'Uppdatering tillgänglig'; + @override + String versionAvailable({required Object version}) => + 'Version ${version} är tillgänglig'; + @override + String currentVersion({required Object version}) => 'Nuvarande: ${version}'; + @override + String get skipVersion => 'Hoppa över denna version'; + @override + String get viewRelease => 'Visa release'; + @override + String get latestVersion => 'Du har den senaste versionen'; + @override + String get checkFailed => 'Misslyckades att kontrollera uppdateringar'; } // Path: settings class _StringsSettingsSv implements _StringsSettingsEn { - _StringsSettingsSv._(this._root); + _StringsSettingsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Inställningar'; - @override String get language => 'Språk'; - @override String get theme => 'Tema'; - @override String get appearance => 'Utseende'; - @override String get videoPlayback => 'Videouppspelning'; - @override String get shufflePlay => 'Blanda uppspelning'; - @override String get advanced => 'Avancerat'; - @override String get useSeasonPostersDescription => 'Visa säsongsaffisch istället för serieaffisch för avsnitt'; - @override String get showHeroSectionDescription => 'Visa utvalda innehållskarusell på startsidan'; - @override String get secondsLabel => 'Sekunder'; - @override String get minutesLabel => 'Minuter'; - @override String get secondsShort => 's'; - @override String get minutesShort => 'm'; - @override String durationHint({required Object min, required Object max}) => 'Ange tid (${min}-${max})'; - @override String get systemTheme => 'System'; - @override String get systemThemeDescription => 'Följ systeminställningar'; - @override String get lightTheme => 'Ljust'; - @override String get darkTheme => 'Mörkt'; - @override String get libraryDensity => 'Biblioteksdensitet'; - @override String get compact => 'Kompakt'; - @override String get compactDescription => 'Mindre kort, fler objekt synliga'; - @override String get normal => 'Normal'; - @override String get normalDescription => 'Standardstorlek'; - @override String get comfortable => 'Bekväm'; - @override String get comfortableDescription => 'Större kort, färre objekt synliga'; - @override String get viewMode => 'Visningsläge'; - @override String get gridView => 'Rutnät'; - @override String get gridViewDescription => 'Visa objekt i rutnätslayout'; - @override String get listView => 'Lista'; - @override String get listViewDescription => 'Visa objekt i listlayout'; - @override String get useSeasonPosters => 'Använd säsongsaffischer'; - @override String get showHeroSection => 'Visa hjältesektion'; - @override String get hardwareDecoding => 'Hårdvaruavkodning'; - @override String get hardwareDecodingDescription => 'Använd hårdvaruacceleration när tillgängligt'; - @override String get bufferSize => 'Bufferstorlek'; - @override String bufferSizeMB({required Object size}) => '${size}MB'; - @override String get subtitleStyling => 'Undertext-styling'; - @override String get subtitleStylingDescription => 'Anpassa undertextutseende'; - @override String get smallSkipDuration => 'Kort hoppvaraktighet'; - @override String get largeSkipDuration => 'Lång hoppvaraktighet'; - @override String secondsUnit({required Object seconds}) => '${seconds} sekunder'; - @override String get defaultSleepTimer => 'Standard sovtimer'; - @override String minutesUnit({required Object minutes}) => '${minutes} minuter'; - @override String get rememberTrackSelections => 'Kom ihåg spårval per serie/film'; - @override String get rememberTrackSelectionsDescription => 'Spara automatiskt ljud- och undertextspråkpreferenser när du ändrar spår under uppspelning'; - @override String get unwatchedOnly => 'Endast osedda'; - @override String get unwatchedOnlyDescription => 'Inkludera endast osedda avsnitt i blandningskön'; - @override String get shuffleOrderNavigation => 'Blandningsordning-navigation'; - @override String get shuffleOrderNavigationDescription => 'Nästa/föregående knappar följer blandad ordning'; - @override String get loopShuffleQueue => 'Loopa blandningskö'; - @override String get loopShuffleQueueDescription => 'Starta om kö när slutet nås'; - @override String get videoPlayerControls => 'Videospelar-kontroller'; - @override String get keyboardShortcuts => 'Tangentbordsgenvägar'; - @override String get keyboardShortcutsDescription => 'Anpassa tangentbordsgenvägar'; - @override String get debugLogging => 'Felsökningsloggning'; - @override String get debugLoggingDescription => 'Aktivera detaljerad loggning för felsökning'; - @override String get viewLogs => 'Visa loggar'; - @override String get viewLogsDescription => 'Visa applikationsloggar'; - @override String get clearCache => 'Rensa cache'; - @override String get clearCacheDescription => 'Detta rensar alla cachade bilder och data. Appen kan ta längre tid att ladda innehåll efter cache-rensning.'; - @override String get clearCacheSuccess => 'Cache rensad framgångsrikt'; - @override String get resetSettings => 'Återställ inställningar'; - @override String get resetSettingsDescription => 'Detta återställer alla inställningar till standardvärden. Denna åtgärd kan inte ångras.'; - @override String get resetSettingsSuccess => 'Inställningar återställda framgångsrikt'; - @override String get shortcutsReset => 'Genvägar återställda till standard'; - @override String get about => 'Om'; - @override String get aboutDescription => 'Appinformation och licenser'; - @override String get updates => 'Uppdateringar'; - @override String get updateAvailable => 'Uppdatering tillgänglig'; - @override String get checkForUpdates => 'Kontrollera uppdateringar'; - @override String get validationErrorEnterNumber => 'Vänligen ange ett giltigt nummer'; - @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => 'Tiden måste vara mellan ${min} och ${max} ${unit}'; - @override String shortcutAlreadyAssigned({required Object action}) => 'Genväg redan tilldelad ${action}'; - @override String shortcutUpdated({required Object action}) => 'Genväg uppdaterad för ${action}'; + // Translations + @override + String get title => 'Inställningar'; + @override + String get language => 'Språk'; + @override + String get theme => 'Tema'; + @override + String get appearance => 'Utseende'; + @override + String get videoPlayback => 'Videouppspelning'; + @override + String get shufflePlay => 'Blanda uppspelning'; + @override + String get advanced => 'Avancerat'; + @override + String get useSeasonPostersDescription => + 'Visa säsongsaffisch istället för serieaffisch för avsnitt'; + @override + String get showHeroSectionDescription => + 'Visa utvalda innehållskarusell på startsidan'; + @override + String get secondsLabel => 'Sekunder'; + @override + String get minutesLabel => 'Minuter'; + @override + String get secondsShort => 's'; + @override + String get minutesShort => 'm'; + @override + String durationHint({required Object min, required Object max}) => + 'Ange tid (${min}-${max})'; + @override + String get systemTheme => 'System'; + @override + String get systemThemeDescription => 'Följ systeminställningar'; + @override + String get lightTheme => 'Ljust'; + @override + String get darkTheme => 'Mörkt'; + @override + String get libraryDensity => 'Biblioteksdensitet'; + @override + String get compact => 'Kompakt'; + @override + String get compactDescription => 'Mindre kort, fler objekt synliga'; + @override + String get normal => 'Normal'; + @override + String get normalDescription => 'Standardstorlek'; + @override + String get comfortable => 'Bekväm'; + @override + String get comfortableDescription => 'Större kort, färre objekt synliga'; + @override + String get viewMode => 'Visningsläge'; + @override + String get gridView => 'Rutnät'; + @override + String get gridViewDescription => 'Visa objekt i rutnätslayout'; + @override + String get listView => 'Lista'; + @override + String get listViewDescription => 'Visa objekt i listlayout'; + @override + String get useSeasonPosters => 'Använd säsongsaffischer'; + @override + String get showHeroSection => 'Visa hjältesektion'; + @override + String get hardwareDecoding => 'Hårdvaruavkodning'; + @override + String get hardwareDecodingDescription => + 'Använd hårdvaruacceleration när tillgängligt'; + @override + String get bufferSize => 'Bufferstorlek'; + @override + String bufferSizeMB({required Object size}) => '${size}MB'; + @override + String get subtitleStyling => 'Undertext-styling'; + @override + String get subtitleStylingDescription => 'Anpassa undertextutseende'; + @override + String get smallSkipDuration => 'Kort hoppvaraktighet'; + @override + String get largeSkipDuration => 'Lång hoppvaraktighet'; + @override + String secondsUnit({required Object seconds}) => '${seconds} sekunder'; + @override + String get defaultSleepTimer => 'Standard sovtimer'; + @override + String minutesUnit({required Object minutes}) => '${minutes} minuter'; + @override + String get rememberTrackSelections => 'Kom ihåg spårval per serie/film'; + @override + String get rememberTrackSelectionsDescription => + 'Spara automatiskt ljud- och undertextspråkpreferenser när du ändrar spår under uppspelning'; + @override + String get unwatchedOnly => 'Endast osedda'; + @override + String get unwatchedOnlyDescription => + 'Inkludera endast osedda avsnitt i blandningskön'; + @override + String get shuffleOrderNavigation => 'Blandningsordning-navigation'; + @override + String get shuffleOrderNavigationDescription => + 'Nästa/föregående knappar följer blandad ordning'; + @override + String get loopShuffleQueue => 'Loopa blandningskö'; + @override + String get loopShuffleQueueDescription => 'Starta om kö när slutet nås'; + @override + String get videoPlayerControls => 'Videospelar-kontroller'; + @override + String get keyboardShortcuts => 'Tangentbordsgenvägar'; + @override + String get keyboardShortcutsDescription => 'Anpassa tangentbordsgenvägar'; + @override + String get debugLogging => 'Felsökningsloggning'; + @override + String get debugLoggingDescription => + 'Aktivera detaljerad loggning för felsökning'; + @override + String get viewLogs => 'Visa loggar'; + @override + String get viewLogsDescription => 'Visa applikationsloggar'; + @override + String get clearCache => 'Rensa cache'; + @override + String get clearCacheDescription => + 'Detta rensar alla cachade bilder och data. Appen kan ta längre tid att ladda innehåll efter cache-rensning.'; + @override + String get clearCacheSuccess => 'Cache rensad framgångsrikt'; + @override + String get resetSettings => 'Återställ inställningar'; + @override + String get resetSettingsDescription => + 'Detta återställer alla inställningar till standardvärden. Denna åtgärd kan inte ångras.'; + @override + String get resetSettingsSuccess => 'Inställningar återställda framgångsrikt'; + @override + String get shortcutsReset => 'Genvägar återställda till standard'; + @override + String get about => 'Om'; + @override + String get aboutDescription => 'Appinformation och licenser'; + @override + String get updates => 'Uppdateringar'; + @override + String get updateAvailable => 'Uppdatering tillgänglig'; + @override + String get checkForUpdates => 'Kontrollera uppdateringar'; + @override + String get validationErrorEnterNumber => 'Vänligen ange ett giltigt nummer'; + @override + String validationErrorDuration({ + required Object min, + required Object max, + required Object unit, + }) => 'Tiden måste vara mellan ${min} och ${max} ${unit}'; + @override + String shortcutAlreadyAssigned({required Object action}) => + 'Genväg redan tilldelad ${action}'; + @override + String shortcutUpdated({required Object action}) => + 'Genväg uppdaterad för ${action}'; } // Path: search class _StringsSearchSv implements _StringsSearchEn { - _StringsSearchSv._(this._root); + _StringsSearchSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get hint => 'Sök filmer, serier, musik...'; - @override String get tryDifferentTerm => 'Prova en annan sökterm'; - @override String get searchYourMedia => 'Sök i dina media'; - @override String get enterTitleActorOrKeyword => 'Ange en titel, skådespelare eller nyckelord'; + // Translations + @override + String get hint => 'Sök filmer, serier, musik...'; + @override + String get tryDifferentTerm => 'Prova en annan sökterm'; + @override + String get searchYourMedia => 'Sök i dina media'; + @override + String get enterTitleActorOrKeyword => + 'Ange en titel, skådespelare eller nyckelord'; } // Path: hotkeys class _StringsHotkeysSv implements _StringsHotkeysEn { - _StringsHotkeysSv._(this._root); + _StringsHotkeysSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String setShortcutFor({required Object actionName}) => 'Sätt genväg för ${actionName}'; - @override String get clearShortcut => 'Rensa genväg'; + // Translations + @override + String setShortcutFor({required Object actionName}) => + 'Sätt genväg för ${actionName}'; + @override + String get clearShortcut => 'Rensa genväg'; } // Path: pinEntry class _StringsPinEntrySv implements _StringsPinEntryEn { - _StringsPinEntrySv._(this._root); + _StringsPinEntrySv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get enterPin => 'Ange PIN'; - @override String get showPin => 'Visa PIN'; - @override String get hidePin => 'Dölj PIN'; + // Translations + @override + String get enterPin => 'Ange PIN'; + @override + String get showPin => 'Visa PIN'; + @override + String get hidePin => 'Dölj PIN'; } // Path: fileInfo class _StringsFileInfoSv implements _StringsFileInfoEn { - _StringsFileInfoSv._(this._root); + _StringsFileInfoSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Filinformation'; - @override String get video => 'Video'; - @override String get audio => 'Ljud'; - @override String get file => 'Fil'; - @override String get advanced => 'Avancerat'; - @override String get codec => 'Kodek'; - @override String get resolution => 'Upplösning'; - @override String get bitrate => 'Bithastighet'; - @override String get frameRate => 'Bildfrekvens'; - @override String get aspectRatio => 'Bildförhållande'; - @override String get profile => 'Profil'; - @override String get bitDepth => 'Bitdjup'; - @override String get colorSpace => 'Färgrymd'; - @override String get colorRange => 'Färgområde'; - @override String get colorPrimaries => 'Färggrunder'; - @override String get chromaSubsampling => 'Kroma-undersampling'; - @override String get channels => 'Kanaler'; - @override String get path => 'Sökväg'; - @override String get size => 'Storlek'; - @override String get container => 'Container'; - @override String get duration => 'Varaktighet'; - @override String get optimizedForStreaming => 'Optimerad för streaming'; - @override String get has64bitOffsets => '64-bit offset'; + // Translations + @override + String get title => 'Filinformation'; + @override + String get video => 'Video'; + @override + String get audio => 'Ljud'; + @override + String get file => 'Fil'; + @override + String get advanced => 'Avancerat'; + @override + String get codec => 'Kodek'; + @override + String get resolution => 'Upplösning'; + @override + String get bitrate => 'Bithastighet'; + @override + String get frameRate => 'Bildfrekvens'; + @override + String get aspectRatio => 'Bildförhållande'; + @override + String get profile => 'Profil'; + @override + String get bitDepth => 'Bitdjup'; + @override + String get colorSpace => 'Färgrymd'; + @override + String get colorRange => 'Färgområde'; + @override + String get colorPrimaries => 'Färggrunder'; + @override + String get chromaSubsampling => 'Kroma-undersampling'; + @override + String get channels => 'Kanaler'; + @override + String get path => 'Sökväg'; + @override + String get size => 'Storlek'; + @override + String get container => 'Container'; + @override + String get duration => 'Varaktighet'; + @override + String get optimizedForStreaming => 'Optimerad för streaming'; + @override + String get has64bitOffsets => '64-bit offset'; } // Path: mediaMenu class _StringsMediaMenuSv implements _StringsMediaMenuEn { - _StringsMediaMenuSv._(this._root); + _StringsMediaMenuSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get markAsWatched => 'Markera som sedd'; - @override String get markAsUnwatched => 'Markera som osedd'; - @override String get removeFromContinueWatching => 'Ta bort från Fortsätt titta'; - @override String get goToSeries => 'Gå till serie'; - @override String get goToSeason => 'Gå till säsong'; - @override String get shufflePlay => 'Blanda uppspelning'; - @override String get fileInfo => 'Filinformation'; + // Translations + @override + String get markAsWatched => 'Markera som sedd'; + @override + String get markAsUnwatched => 'Markera som osedd'; + @override + String get removeFromContinueWatching => 'Ta bort från Fortsätt titta'; + @override + String get goToSeries => 'Gå till serie'; + @override + String get goToSeason => 'Gå till säsong'; + @override + String get shufflePlay => 'Blanda uppspelning'; + @override + String get fileInfo => 'Filinformation'; } // Path: accessibility class _StringsAccessibilitySv implements _StringsAccessibilityEn { - _StringsAccessibilitySv._(this._root); + _StringsAccessibilitySv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String mediaCardMovie({required Object title}) => '${title}, film'; - @override String mediaCardShow({required Object title}) => '${title}, TV-serie'; - @override String mediaCardEpisode({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - @override String mediaCardSeason({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - @override String get mediaCardWatched => 'sedd'; - @override String mediaCardPartiallyWatched({required Object percent}) => '${percent} procent sedd'; - @override String get mediaCardUnwatched => 'osedd'; - @override String get tapToPlay => 'Tryck för att spela'; + // Translations + @override + String mediaCardMovie({required Object title}) => '${title}, film'; + @override + String mediaCardShow({required Object title}) => '${title}, TV-serie'; + @override + String mediaCardEpisode({ + required Object title, + required Object episodeInfo, + }) => '${title}, ${episodeInfo}'; + @override + String mediaCardSeason({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + @override + String get mediaCardWatched => 'sedd'; + @override + String mediaCardPartiallyWatched({required Object percent}) => + '${percent} procent sedd'; + @override + String get mediaCardUnwatched => 'osedd'; + @override + String get tapToPlay => 'Tryck för att spela'; } // Path: tooltips class _StringsTooltipsSv implements _StringsTooltipsEn { - _StringsTooltipsSv._(this._root); + _StringsTooltipsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get shufflePlay => 'Blanda uppspelning'; - @override String get markAsWatched => 'Markera som sedd'; - @override String get markAsUnwatched => 'Markera som osedd'; + // Translations + @override + String get shufflePlay => 'Blanda uppspelning'; + @override + String get markAsWatched => 'Markera som sedd'; + @override + String get markAsUnwatched => 'Markera som osedd'; } // Path: videoControls class _StringsVideoControlsSv implements _StringsVideoControlsEn { - _StringsVideoControlsSv._(this._root); + _StringsVideoControlsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get audioLabel => 'Ljud'; - @override String get subtitlesLabel => 'Undertexter'; - @override String get resetToZero => 'Återställ till 0ms'; - @override String addTime({required Object amount, required Object unit}) => '+${amount}${unit}'; - @override String minusTime({required Object amount, required Object unit}) => '-${amount}${unit}'; - @override String playsLater({required Object label}) => '${label} spelas senare'; - @override String playsEarlier({required Object label}) => '${label} spelas tidigare'; - @override String get noOffset => 'Ingen offset'; - @override String get letterbox => 'Letterbox'; - @override String get fillScreen => 'Fyll skärm'; - @override String get stretch => 'Sträck'; - @override String get lockRotation => 'Lås rotation'; - @override String get unlockRotation => 'Lås upp rotation'; - @override String get sleepTimer => 'Sovtimer'; - @override String get timerActive => 'Timer aktiv'; - @override String playbackWillPauseIn({required Object duration}) => 'Uppspelningen pausas om ${duration}'; - @override String get sleepTimerCompleted => 'Sovtimer slutförd - uppspelning pausad'; - @override String get playButton => 'Spela'; - @override String get pauseButton => 'Pausa'; - @override String seekBackwardButton({required Object seconds}) => 'Spola bakåt ${seconds} sekunder'; - @override String seekForwardButton({required Object seconds}) => 'Spola framåt ${seconds} sekunder'; - @override String get previousButton => 'Föregående avsnitt'; - @override String get nextButton => 'Nästa avsnitt'; - @override String get previousChapterButton => 'Föregående kapitel'; - @override String get nextChapterButton => 'Nästa kapitel'; - @override String get volumeButton => 'Volym'; - @override String get muteButton => 'Tysta'; - @override String get unmuteButton => 'Slå på ljud'; - @override String get settingsButton => 'Videoinställningar'; - @override String get audioTrackButton => 'Ljudspår'; - @override String get subtitlesButton => 'Undertexter'; - @override String get chaptersButton => 'Kapitel'; - @override String get versionsButton => 'Videoversioner'; - @override String get aspectRatioButton => 'Bildförhållande'; - @override String get fullscreenButton => 'Aktivera helskärm'; - @override String get exitFullscreenButton => 'Avsluta helskärm'; - @override String get rotationLockButton => 'Rotationslås'; - @override String get timelineSlider => 'Videotidslinje'; - @override String get volumeSlider => 'Volymnivå'; - @override String get backButton => 'Tillbaka'; + // Translations + @override + String get audioLabel => 'Ljud'; + @override + String get subtitlesLabel => 'Undertexter'; + @override + String get resetToZero => 'Återställ till 0ms'; + @override + String addTime({required Object amount, required Object unit}) => + '+${amount}${unit}'; + @override + String minusTime({required Object amount, required Object unit}) => + '-${amount}${unit}'; + @override + String playsLater({required Object label}) => '${label} spelas senare'; + @override + String playsEarlier({required Object label}) => '${label} spelas tidigare'; + @override + String get noOffset => 'Ingen offset'; + @override + String get letterbox => 'Letterbox'; + @override + String get fillScreen => 'Fyll skärm'; + @override + String get stretch => 'Sträck'; + @override + String get lockRotation => 'Lås rotation'; + @override + String get unlockRotation => 'Lås upp rotation'; + @override + String get sleepTimer => 'Sovtimer'; + @override + String get timerActive => 'Timer aktiv'; + @override + String playbackWillPauseIn({required Object duration}) => + 'Uppspelningen pausas om ${duration}'; + @override + String get sleepTimerCompleted => 'Sovtimer slutförd - uppspelning pausad'; + @override + String get playButton => 'Spela'; + @override + String get pauseButton => 'Pausa'; + @override + String seekBackwardButton({required Object seconds}) => + 'Spola bakåt ${seconds} sekunder'; + @override + String seekForwardButton({required Object seconds}) => + 'Spola framåt ${seconds} sekunder'; + @override + String get previousButton => 'Föregående avsnitt'; + @override + String get nextButton => 'Nästa avsnitt'; + @override + String get previousChapterButton => 'Föregående kapitel'; + @override + String get nextChapterButton => 'Nästa kapitel'; + @override + String get volumeButton => 'Volym'; + @override + String get muteButton => 'Tysta'; + @override + String get unmuteButton => 'Slå på ljud'; + @override + String get settingsButton => 'Videoinställningar'; + @override + String get audioTrackButton => 'Ljudspår'; + @override + String get subtitlesButton => 'Undertexter'; + @override + String get chaptersButton => 'Kapitel'; + @override + String get versionsButton => 'Videoversioner'; + @override + String get aspectRatioButton => 'Bildförhållande'; + @override + String get fullscreenButton => 'Aktivera helskärm'; + @override + String get exitFullscreenButton => 'Avsluta helskärm'; + @override + String get rotationLockButton => 'Rotationslås'; + @override + String get timelineSlider => 'Videotidslinje'; + @override + String get volumeSlider => 'Volymnivå'; + @override + String get backButton => 'Tillbaka'; } // Path: userStatus class _StringsUserStatusSv implements _StringsUserStatusEn { - _StringsUserStatusSv._(this._root); + _StringsUserStatusSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get admin => 'Admin'; - @override String get restricted => 'Begränsad'; - @override String get protected => 'Skyddad'; - @override String get current => 'NUVARANDE'; + // Translations + @override + String get admin => 'Admin'; + @override + String get restricted => 'Begränsad'; + @override + String get protected => 'Skyddad'; + @override + String get current => 'NUVARANDE'; } // Path: messages class _StringsMessagesSv implements _StringsMessagesEn { - _StringsMessagesSv._(this._root); + _StringsMessagesSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get markedAsWatched => 'Markerad som sedd'; - @override String get markedAsUnwatched => 'Markerad som osedd'; - @override String get removedFromContinueWatching => 'Borttagen från Fortsätt titta'; - @override String errorLoading({required Object error}) => 'Fel: ${error}'; - @override String get fileInfoNotAvailable => 'Filinformation inte tillgänglig'; - @override String errorLoadingFileInfo({required Object error}) => 'Fel vid laddning av filinformation: ${error}'; - @override String get errorLoadingSeries => 'Fel vid laddning av serie'; - @override String get errorLoadingSeason => 'Fel vid laddning av säsong'; - @override String get musicNotSupported => 'Musikuppspelning stöds inte ännu'; - @override String get logsCleared => 'Loggar rensade'; - @override String get logsCopied => 'Loggar kopierade till urklipp'; - @override String get noLogsAvailable => 'Inga loggar tillgängliga'; - @override String libraryScanning({required Object title}) => 'Skannar "${title}"...'; - @override String libraryScanStarted({required Object title}) => 'Biblioteksskanning startad för "${title}"'; - @override String libraryScanFailed({required Object error}) => 'Misslyckades att skanna bibliotek: ${error}'; - @override String metadataRefreshing({required Object title}) => 'Uppdaterar metadata för "${title}"...'; - @override String metadataRefreshStarted({required Object title}) => 'Metadata-uppdatering startad för "${title}"'; - @override String metadataRefreshFailed({required Object error}) => 'Misslyckades att uppdatera metadata: ${error}'; - @override String get noPlexToken => 'Ingen Plex-token hittad. Vänligen logga in igen.'; - @override String get logoutConfirm => 'Är du säker på att du vill logga ut?'; - @override String get noSeasonsFound => 'Inga säsonger hittades'; - @override String get noEpisodesFound => 'Inga avsnitt hittades i första säsongen'; - @override String get noEpisodesFoundGeneral => 'Inga avsnitt hittades'; - @override String get noResultsFound => 'Inga resultat hittades'; - @override String sleepTimerSet({required Object label}) => 'Sovtimer inställd för ${label}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Misslyckades att byta till ${displayName}'; - @override String get noItemsAvailable => 'Inga objekt tillgängliga'; - @override String get failedToCreatePlayQueue => 'Det gick inte att skapa uppspelningskö'; - @override String get failedToCreatePlayQueueNoItems => 'Det gick inte att skapa uppspelningskö – inga objekt'; - @override String failedPlayback({required Object action, required Object error}) => 'Kunde inte ${action}: ${error}'; + // Translations + @override + String get markedAsWatched => 'Markerad som sedd'; + @override + String get markedAsUnwatched => 'Markerad som osedd'; + @override + String get removedFromContinueWatching => 'Borttagen från Fortsätt titta'; + @override + String errorLoading({required Object error}) => 'Fel: ${error}'; + @override + String get fileInfoNotAvailable => 'Filinformation inte tillgänglig'; + @override + String errorLoadingFileInfo({required Object error}) => + 'Fel vid laddning av filinformation: ${error}'; + @override + String get errorLoadingSeries => 'Fel vid laddning av serie'; + @override + String get errorLoadingSeason => 'Fel vid laddning av säsong'; + @override + String get musicNotSupported => 'Musikuppspelning stöds inte ännu'; + @override + String get logsCleared => 'Loggar rensade'; + @override + String get logsCopied => 'Loggar kopierade till urklipp'; + @override + String get noLogsAvailable => 'Inga loggar tillgängliga'; + @override + String libraryScanning({required Object title}) => 'Skannar "${title}"...'; + @override + String libraryScanStarted({required Object title}) => + 'Biblioteksskanning startad för "${title}"'; + @override + String libraryScanFailed({required Object error}) => + 'Misslyckades att skanna bibliotek: ${error}'; + @override + String metadataRefreshing({required Object title}) => + 'Uppdaterar metadata för "${title}"...'; + @override + String metadataRefreshStarted({required Object title}) => + 'Metadata-uppdatering startad för "${title}"'; + @override + String metadataRefreshFailed({required Object error}) => + 'Misslyckades att uppdatera metadata: ${error}'; + @override + String get noPlexToken => 'Ingen Plex-token hittad. Vänligen logga in igen.'; + @override + String get logoutConfirm => 'Är du säker på att du vill logga ut?'; + @override + String get noSeasonsFound => 'Inga säsonger hittades'; + @override + String get noEpisodesFound => 'Inga avsnitt hittades i första säsongen'; + @override + String get noEpisodesFoundGeneral => 'Inga avsnitt hittades'; + @override + String get noResultsFound => 'Inga resultat hittades'; + @override + String sleepTimerSet({required Object label}) => + 'Sovtimer inställd för ${label}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Misslyckades att byta till ${displayName}'; + @override + String get noItemsAvailable => 'Inga objekt tillgängliga'; + @override + String get failedToCreatePlayQueue => + 'Det gick inte att skapa uppspelningskö'; + @override + String get failedToCreatePlayQueueNoItems => + 'Det gick inte att skapa uppspelningskö – inga objekt'; + @override + String failedPlayback({required Object action, required Object error}) => + 'Kunde inte ${action}: ${error}'; } // Path: profile class _StringsProfileSv implements _StringsProfileEn { - _StringsProfileSv._(this._root); + _StringsProfileSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get noUsersAvailable => 'Inga användare tillgängliga'; + // Translations + @override + String get noUsersAvailable => 'Inga användare tillgängliga'; } // Path: subtitlingStyling class _StringsSubtitlingStylingSv implements _StringsSubtitlingStylingEn { - _StringsSubtitlingStylingSv._(this._root); + _StringsSubtitlingStylingSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get stylingOptions => 'Stilalternativ'; - @override String get fontSize => 'Teckenstorlek'; - @override String get textColor => 'Textfärg'; - @override String get borderSize => 'Kantstorlek'; - @override String get borderColor => 'Kantfärg'; - @override String get backgroundOpacity => 'Bakgrundsopacitet'; - @override String get backgroundColor => 'Bakgrundsfärg'; + // Translations + @override + String get stylingOptions => 'Stilalternativ'; + @override + String get fontSize => 'Teckenstorlek'; + @override + String get textColor => 'Textfärg'; + @override + String get borderSize => 'Kantstorlek'; + @override + String get borderColor => 'Kantfärg'; + @override + String get backgroundOpacity => 'Bakgrundsopacitet'; + @override + String get backgroundColor => 'Bakgrundsfärg'; } // Path: dialog class _StringsDialogSv implements _StringsDialogEn { - _StringsDialogSv._(this._root); + _StringsDialogSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get confirmAction => 'Bekräfta åtgärd'; - @override String get areYouSure => 'Är du säker på att du vill utföra denna åtgärd?'; - @override String get cancel => 'Avbryt'; - @override String get playNow => 'Spela nu'; + // Translations + @override + String get confirmAction => 'Bekräfta åtgärd'; + @override + String get areYouSure => 'Är du säker på att du vill utföra denna åtgärd?'; + @override + String get cancel => 'Avbryt'; + @override + String get playNow => 'Spela nu'; } // Path: discover class _StringsDiscoverSv implements _StringsDiscoverEn { - _StringsDiscoverSv._(this._root); + _StringsDiscoverSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Upptäck'; - @override String get switchProfile => 'Byt profil'; - @override String get switchServer => 'Byt server'; - @override String get logout => 'Logga ut'; - @override String get noContentAvailable => 'Inget innehåll tillgängligt'; - @override String get addMediaToLibraries => 'Lägg till media till dina bibliotek'; - @override String get continueWatching => 'Fortsätt titta'; - @override String get recentlyAdded => 'Nyligen tillagda'; - @override String get play => 'Spela'; - @override String get resume => 'Återuppta'; - @override String playEpisode({required Object season, required Object episode}) => 'Spela S${season}, E${episode}'; - @override String resumeEpisode({required Object season, required Object episode}) => 'Återuppta S${season}, E${episode}'; - @override String get pause => 'Pausa'; - @override String get overview => 'Översikt'; - @override String get cast => 'Rollbesättning'; - @override String get seasons => 'Säsonger'; - @override String get studio => 'Studio'; - @override String get rating => 'Åldersgräns'; - @override String get watched => 'Tittad'; - @override String episodeCount({required Object count}) => '${count} avsnitt'; - @override String watchedProgress({required Object watched, required Object total}) => '${watched}/${total} sedda'; - @override String get movie => 'Film'; - @override String get tvShow => 'TV-serie'; - @override String minutesLeft({required Object minutes}) => '${minutes} min kvar'; + // Translations + @override + String get title => 'Upptäck'; + @override + String get switchProfile => 'Byt profil'; + @override + String get switchServer => 'Byt server'; + @override + String get logout => 'Logga ut'; + @override + String get noContentAvailable => 'Inget innehåll tillgängligt'; + @override + String get addMediaToLibraries => 'Lägg till media till dina bibliotek'; + @override + String get continueWatching => 'Fortsätt titta'; + @override + String get recentlyAdded => 'Nyligen tillagda'; + @override + String get play => 'Spela'; + @override + String get resume => 'Återuppta'; + @override + String playEpisode({required Object season, required Object episode}) => + 'Spela S${season}, E${episode}'; + @override + String resumeEpisode({required Object season, required Object episode}) => + 'Återuppta S${season}, E${episode}'; + @override + String get pause => 'Pausa'; + @override + String get overview => 'Översikt'; + @override + String get cast => 'Rollbesättning'; + @override + String get seasons => 'Säsonger'; + @override + String get studio => 'Studio'; + @override + String get rating => 'Åldersgräns'; + @override + String get watched => 'Tittad'; + @override + String episodeCount({required Object count}) => '${count} avsnitt'; + @override + String watchedProgress({required Object watched, required Object total}) => + '${watched}/${total} sedda'; + @override + String get movie => 'Film'; + @override + String get tvShow => 'TV-serie'; + @override + String minutesLeft({required Object minutes}) => '${minutes} min kvar'; } // Path: errors class _StringsErrorsSv implements _StringsErrorsEn { - _StringsErrorsSv._(this._root); + _StringsErrorsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String searchFailed({required Object error}) => 'Sökning misslyckades: ${error}'; - @override String connectionTimeout({required Object context}) => 'Anslutnings-timeout vid laddning ${context}'; - @override String get connectionFailed => 'Kan inte ansluta till Plex-server'; - @override String failedToLoad({required Object context, required Object error}) => 'Misslyckades att ladda ${context}: ${error}'; - @override String get noClientAvailable => 'Ingen klient tillgänglig'; - @override String authenticationFailed({required Object error}) => 'Autentisering misslyckades: ${error}'; - @override String get couldNotLaunchUrl => 'Kunde inte öppna autentiserings-URL'; - @override String get pleaseEnterToken => 'Vänligen ange en token'; - @override String get invalidToken => 'Ogiltig token'; - @override String failedToVerifyToken({required Object error}) => 'Misslyckades att verifiera token: ${error}'; - @override String failedToSwitchProfile({required Object displayName}) => 'Misslyckades att byta till ${displayName}'; - @override String get connectionFailedGeneric => 'Anslutning misslyckades'; + // Translations + @override + String searchFailed({required Object error}) => + 'Sökning misslyckades: ${error}'; + @override + String connectionTimeout({required Object context}) => + 'Anslutnings-timeout vid laddning ${context}'; + @override + String get connectionFailed => 'Kan inte ansluta till Plex-server'; + @override + String failedToLoad({required Object context, required Object error}) => + 'Misslyckades att ladda ${context}: ${error}'; + @override + String get noClientAvailable => 'Ingen klient tillgänglig'; + @override + String authenticationFailed({required Object error}) => + 'Autentisering misslyckades: ${error}'; + @override + String get couldNotLaunchUrl => 'Kunde inte öppna autentiserings-URL'; + @override + String get pleaseEnterToken => 'Vänligen ange en token'; + @override + String get invalidToken => 'Ogiltig token'; + @override + String failedToVerifyToken({required Object error}) => + 'Misslyckades att verifiera token: ${error}'; + @override + String failedToSwitchProfile({required Object displayName}) => + 'Misslyckades att byta till ${displayName}'; + @override + String get connectionFailedGeneric => 'Anslutning misslyckades'; } // Path: libraries class _StringsLibrariesSv implements _StringsLibrariesEn { - _StringsLibrariesSv._(this._root); + _StringsLibrariesSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Bibliotek'; - @override String get scanLibraryFiles => 'Skanna biblioteksfiler'; - @override String get scanLibrary => 'Skanna bibliotek'; - @override String get analyze => 'Analysera'; - @override String get analyzeLibrary => 'Analysera bibliotek'; - @override String get refreshMetadata => 'Uppdatera metadata'; - @override String get emptyTrash => 'Töm papperskorg'; - @override String emptyingTrash({required Object title}) => 'Tömmer papperskorg för "${title}"...'; - @override String trashEmptied({required Object title}) => 'Papperskorg tömd för "${title}"'; - @override String failedToEmptyTrash({required Object error}) => 'Misslyckades att tömma papperskorg: ${error}'; - @override String analyzing({required Object title}) => 'Analyserar "${title}"...'; - @override String analysisStarted({required Object title}) => 'Analys startad för "${title}"'; - @override String failedToAnalyze({required Object error}) => 'Misslyckades att analysera bibliotek: ${error}'; - @override String get noLibrariesFound => 'Inga bibliotek hittades'; - @override String get thisLibraryIsEmpty => 'Detta bibliotek är tomt'; - @override String get all => 'Alla'; - @override String get clearAll => 'Rensa alla'; - @override String scanLibraryConfirm({required Object title}) => 'Är du säker på att du vill skanna "${title}"?'; - @override String analyzeLibraryConfirm({required Object title}) => 'Är du säker på att du vill analysera "${title}"?'; - @override String refreshMetadataConfirm({required Object title}) => 'Är du säker på att du vill uppdatera metadata för "${title}"?'; - @override String emptyTrashConfirm({required Object title}) => 'Är du säker på att du vill tömma papperskorgen för "${title}"?'; - @override String get manageLibraries => 'Hantera bibliotek'; - @override String get sort => 'Sortera'; - @override String get sortBy => 'Sortera efter'; - @override String get filters => 'Filter'; - @override String loadingLibraryWithCount({required Object count}) => 'Laddar bibliotek... (${count} objekt laddade)'; - @override String get confirmActionMessage => 'Är du säker på att du vill utföra denna åtgärd?'; - @override String get showLibrary => 'Visa bibliotek'; - @override String get hideLibrary => 'Dölj bibliotek'; - @override String get libraryOptions => 'Biblioteksalternativ'; - @override String get content => 'bibliotekets innehåll'; - @override String get selectLibrary => 'Välj bibliotek'; - @override String filtersWithCount({required Object count}) => 'Filter (${count})'; - @override String get noRecommendations => 'Inga rekommendationer tillgängliga'; - @override String get noCollections => 'Inga samlingar i det här biblioteket'; - @override String get noFoldersFound => 'Inga mappar hittades'; - @override String get folders => 'mappar'; - @override late final _StringsLibrariesTabsSv tabs = _StringsLibrariesTabsSv._(_root); - @override late final _StringsLibrariesGroupingsSv groupings = _StringsLibrariesGroupingsSv._(_root); + // Translations + @override + String get title => 'Bibliotek'; + @override + String get scanLibraryFiles => 'Skanna biblioteksfiler'; + @override + String get scanLibrary => 'Skanna bibliotek'; + @override + String get analyze => 'Analysera'; + @override + String get analyzeLibrary => 'Analysera bibliotek'; + @override + String get refreshMetadata => 'Uppdatera metadata'; + @override + String get emptyTrash => 'Töm papperskorg'; + @override + String emptyingTrash({required Object title}) => + 'Tömmer papperskorg för "${title}"...'; + @override + String trashEmptied({required Object title}) => + 'Papperskorg tömd för "${title}"'; + @override + String failedToEmptyTrash({required Object error}) => + 'Misslyckades att tömma papperskorg: ${error}'; + @override + String analyzing({required Object title}) => 'Analyserar "${title}"...'; + @override + String analysisStarted({required Object title}) => + 'Analys startad för "${title}"'; + @override + String failedToAnalyze({required Object error}) => + 'Misslyckades att analysera bibliotek: ${error}'; + @override + String get noLibrariesFound => 'Inga bibliotek hittades'; + @override + String get thisLibraryIsEmpty => 'Detta bibliotek är tomt'; + @override + String get all => 'Alla'; + @override + String get clearAll => 'Rensa alla'; + @override + String scanLibraryConfirm({required Object title}) => + 'Är du säker på att du vill skanna "${title}"?'; + @override + String analyzeLibraryConfirm({required Object title}) => + 'Är du säker på att du vill analysera "${title}"?'; + @override + String refreshMetadataConfirm({required Object title}) => + 'Är du säker på att du vill uppdatera metadata för "${title}"?'; + @override + String emptyTrashConfirm({required Object title}) => + 'Är du säker på att du vill tömma papperskorgen för "${title}"?'; + @override + String get manageLibraries => 'Hantera bibliotek'; + @override + String get sort => 'Sortera'; + @override + String get sortBy => 'Sortera efter'; + @override + String get filters => 'Filter'; + @override + String loadingLibraryWithCount({required Object count}) => + 'Laddar bibliotek... (${count} objekt laddade)'; + @override + String get confirmActionMessage => + 'Är du säker på att du vill utföra denna åtgärd?'; + @override + String get showLibrary => 'Visa bibliotek'; + @override + String get hideLibrary => 'Dölj bibliotek'; + @override + String get libraryOptions => 'Biblioteksalternativ'; + @override + String get content => 'bibliotekets innehåll'; + @override + String get selectLibrary => 'Välj bibliotek'; + @override + String filtersWithCount({required Object count}) => 'Filter (${count})'; + @override + String get noRecommendations => 'Inga rekommendationer tillgängliga'; + @override + String get noCollections => 'Inga samlingar i det här biblioteket'; + @override + String get noFoldersFound => 'Inga mappar hittades'; + @override + String get folders => 'mappar'; + @override + late final _StringsLibrariesTabsSv tabs = _StringsLibrariesTabsSv._(_root); + @override + late final _StringsLibrariesGroupingsSv groupings = + _StringsLibrariesGroupingsSv._(_root); } // Path: about class _StringsAboutSv implements _StringsAboutEn { - _StringsAboutSv._(this._root); + _StringsAboutSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Om'; - @override String get openSourceLicenses => 'Öppen källkod-licenser'; - @override String versionLabel({required Object version}) => 'Version ${version}'; - @override String get appDescription => 'En vacker Plex-klient för Flutter'; - @override String get viewLicensesDescription => 'Visa licenser för tredjepartsbibliotek'; + // Translations + @override + String get title => 'Om'; + @override + String get openSourceLicenses => 'Öppen källkod-licenser'; + @override + String versionLabel({required Object version}) => 'Version ${version}'; + @override + String get appDescription => 'En vacker Plex-klient för Flutter'; + @override + String get viewLicensesDescription => + 'Visa licenser för tredjepartsbibliotek'; } // Path: serverSelection class _StringsServerSelectionSv implements _StringsServerSelectionEn { - _StringsServerSelectionSv._(this._root); + _StringsServerSelectionSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get connectingToServer => 'Ansluter till server...'; - @override String get connectingToAllServers => 'Ansluter till alla servrar...'; - @override String get connectToAllServers => 'Anslut till alla servrar'; - @override String get allServerConnectionsFailed => 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.'; - @override String foundServers({required Object count}) => 'Hittade ${count} server(ar)'; - @override String get orSelectIndividually => 'Eller välj en server individuellt:'; - @override String get serverDebugCopied => 'Server-felsökningsdata kopierad till urklipp'; - @override String get copyDebugData => 'Kopiera felsökningsdata'; - @override String get noServersFound => 'Inga servrar hittades'; - @override String malformedServerData({required Object count}) => 'Hittade ${count} server(ar) med felformaterad data. Inga giltiga servrar tillgängliga.'; - @override String get incompleteServerInfo => 'Vissa servrar har ofullständig information och hoppades över. Vänligen kontrollera ditt Plex.tv-konto.'; - @override String get incompleteConnectionInfo => 'Server-anslutningsinformation är ofullständig. Försök igen.'; - @override String malformedServerInfo({required Object message}) => 'Serverinformation är felformaterad: ${message}'; - @override String get networkConnectionFailed => 'Nätverksanslutning misslyckades. Kontrollera din internetanslutning och försök igen.'; - @override String get authenticationFailed => 'Autentisering misslyckades. Logga in igen.'; - @override String get plexServiceUnavailable => 'Plex-tjänst otillgänglig. Försök igen senare.'; - @override String failedToLoadServers({required Object error}) => 'Misslyckades att ladda servrar: ${error}'; + // Translations + @override + String get connectingToServer => 'Ansluter till server...'; + @override + String get connectingToAllServers => 'Ansluter till alla servrar...'; + @override + String get connectToAllServers => 'Anslut till alla servrar'; + @override + String get allServerConnectionsFailed => + 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.'; + @override + String foundServers({required Object count}) => 'Hittade ${count} server(ar)'; + @override + String get orSelectIndividually => 'Eller välj en server individuellt:'; + @override + String get serverDebugCopied => + 'Server-felsökningsdata kopierad till urklipp'; + @override + String get copyDebugData => 'Kopiera felsökningsdata'; + @override + String get noServersFound => 'Inga servrar hittades'; + @override + String malformedServerData({required Object count}) => + 'Hittade ${count} server(ar) med felformaterad data. Inga giltiga servrar tillgängliga.'; + @override + String get incompleteServerInfo => + 'Vissa servrar har ofullständig information och hoppades över. Vänligen kontrollera ditt Plex.tv-konto.'; + @override + String get incompleteConnectionInfo => + 'Server-anslutningsinformation är ofullständig. Försök igen.'; + @override + String malformedServerInfo({required Object message}) => + 'Serverinformation är felformaterad: ${message}'; + @override + String get networkConnectionFailed => + 'Nätverksanslutning misslyckades. Kontrollera din internetanslutning och försök igen.'; + @override + String get authenticationFailed => + 'Autentisering misslyckades. Logga in igen.'; + @override + String get plexServiceUnavailable => + 'Plex-tjänst otillgänglig. Försök igen senare.'; + @override + String failedToLoadServers({required Object error}) => + 'Misslyckades att ladda servrar: ${error}'; } // Path: hubDetail class _StringsHubDetailSv implements _StringsHubDetailEn { - _StringsHubDetailSv._(this._root); + _StringsHubDetailSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Titel'; - @override String get releaseYear => 'Utgivningsår'; - @override String get dateAdded => 'Datum tillagd'; - @override String get rating => 'Betyg'; - @override String get noItemsFound => 'Inga objekt hittades'; + // Translations + @override + String get title => 'Titel'; + @override + String get releaseYear => 'Utgivningsår'; + @override + String get dateAdded => 'Datum tillagd'; + @override + String get rating => 'Betyg'; + @override + String get noItemsFound => 'Inga objekt hittades'; } // Path: logs class _StringsLogsSv implements _StringsLogsEn { - _StringsLogsSv._(this._root); + _StringsLogsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Loggar'; - @override String get clearLogs => 'Rensa loggar'; - @override String get copyLogs => 'Kopiera loggar'; - @override String get exportLogs => 'Exportera loggar'; - @override String get noLogsToShow => 'Inga loggar att visa'; - @override String get error => 'Fel:'; - @override String get stackTrace => 'Stack trace:'; + // Translations + @override + String get title => 'Loggar'; + @override + String get clearLogs => 'Rensa loggar'; + @override + String get copyLogs => 'Kopiera loggar'; + @override + String get exportLogs => 'Exportera loggar'; + @override + String get noLogsToShow => 'Inga loggar att visa'; + @override + String get error => 'Fel:'; + @override + String get stackTrace => 'Stack trace:'; } // Path: licenses class _StringsLicensesSv implements _StringsLicensesEn { - _StringsLicensesSv._(this._root); + _StringsLicensesSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get relatedPackages => 'Relaterade paket'; - @override String get license => 'Licens'; - @override String licenseNumber({required Object number}) => 'Licens ${number}'; - @override String licensesCount({required Object count}) => '${count} licenser'; + // Translations + @override + String get relatedPackages => 'Relaterade paket'; + @override + String get license => 'Licens'; + @override + String licenseNumber({required Object number}) => 'Licens ${number}'; + @override + String licensesCount({required Object count}) => '${count} licenser'; } // Path: navigation class _StringsNavigationSv implements _StringsNavigationEn { - _StringsNavigationSv._(this._root); + _StringsNavigationSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get home => 'Hem'; - @override String get search => 'Sök'; - @override String get libraries => 'Bibliotek'; - @override String get settings => 'Inställningar'; + // Translations + @override + String get home => 'Hem'; + @override + String get search => 'Sök'; + @override + String get libraries => 'Bibliotek'; + @override + String get settings => 'Inställningar'; } // Path: playlists class _StringsPlaylistsSv implements _StringsPlaylistsEn { - _StringsPlaylistsSv._(this._root); + _StringsPlaylistsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Spellistor'; - @override String get noPlaylists => 'Inga spellistor hittades'; - @override String get create => 'Skapa spellista'; - @override String get newPlaylist => 'Ny spellista'; - @override String get playlistName => 'Spellistans namn'; - @override String get enterPlaylistName => 'Ange spellistans namn'; - @override String get edit => 'Redigera spellista'; - @override String get delete => 'Ta bort spellista'; - @override String get addTo => 'Lägg till i spellista'; - @override String get addItems => 'Lägg till objekt'; - @override String get removeItem => 'Ta bort från spellista'; - @override String get clearPlaylist => 'Rensa spellista'; - @override String get playAll => 'Spela alla'; - @override String get shuffle => 'Blanda'; - @override String get smartPlaylist => 'Smart spellista'; - @override String get regularPlaylist => 'Vanlig spellista'; - @override String itemCount({required Object count}) => '${count} objekt'; - @override String get oneItem => '1 objekt'; - @override String get emptyPlaylist => 'Denna spellista är tom'; - @override String get deleteConfirm => 'Ta bort spellista?'; - @override String deleteMessage({required Object name}) => 'Är du säker på att du vill ta bort "${name}"?'; - @override String get created => 'Spellista skapad'; - @override String get updated => 'Spellista uppdaterad'; - @override String get deleted => 'Spellista borttagen'; - @override String get itemAdded => 'Tillagd i spellista'; - @override String get itemRemoved => 'Borttagen från spellista'; - @override String get selectPlaylist => 'Välj spellista'; - @override String get createNewPlaylist => 'Skapa ny spellista'; - @override String get errorCreating => 'Det gick inte att skapa spellista'; - @override String get errorDeleting => 'Det gick inte att ta bort spellista'; - @override String get errorLoading => 'Det gick inte att ladda spellistor'; - @override String get errorAdding => 'Det gick inte att lägga till i spellista'; - @override String get errorReordering => 'Det gick inte att omordna spellisteobjekt'; - @override String get errorRemoving => 'Det gick inte att ta bort från spellista'; - @override String get playlist => 'Spellista'; + // Translations + @override + String get title => 'Spellistor'; + @override + String get noPlaylists => 'Inga spellistor hittades'; + @override + String get create => 'Skapa spellista'; + @override + String get newPlaylist => 'Ny spellista'; + @override + String get playlistName => 'Spellistans namn'; + @override + String get enterPlaylistName => 'Ange spellistans namn'; + @override + String get edit => 'Redigera spellista'; + @override + String get delete => 'Ta bort spellista'; + @override + String get addTo => 'Lägg till i spellista'; + @override + String get addItems => 'Lägg till objekt'; + @override + String get removeItem => 'Ta bort från spellista'; + @override + String get clearPlaylist => 'Rensa spellista'; + @override + String get playAll => 'Spela alla'; + @override + String get shuffle => 'Blanda'; + @override + String get smartPlaylist => 'Smart spellista'; + @override + String get regularPlaylist => 'Vanlig spellista'; + @override + String itemCount({required Object count}) => '${count} objekt'; + @override + String get oneItem => '1 objekt'; + @override + String get emptyPlaylist => 'Denna spellista är tom'; + @override + String get deleteConfirm => 'Ta bort spellista?'; + @override + String deleteMessage({required Object name}) => + 'Är du säker på att du vill ta bort "${name}"?'; + @override + String get created => 'Spellista skapad'; + @override + String get updated => 'Spellista uppdaterad'; + @override + String get deleted => 'Spellista borttagen'; + @override + String get itemAdded => 'Tillagd i spellista'; + @override + String get itemRemoved => 'Borttagen från spellista'; + @override + String get selectPlaylist => 'Välj spellista'; + @override + String get createNewPlaylist => 'Skapa ny spellista'; + @override + String get errorCreating => 'Det gick inte att skapa spellista'; + @override + String get errorDeleting => 'Det gick inte att ta bort spellista'; + @override + String get errorLoading => 'Det gick inte att ladda spellistor'; + @override + String get errorAdding => 'Det gick inte att lägga till i spellista'; + @override + String get errorReordering => 'Det gick inte att omordna spellisteobjekt'; + @override + String get errorRemoving => 'Det gick inte att ta bort från spellista'; + @override + String get playlist => 'Spellista'; } // Path: collections class _StringsCollectionsSv implements _StringsCollectionsEn { - _StringsCollectionsSv._(this._root); + _StringsCollectionsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get title => 'Samlingar'; - @override String get collection => 'Samling'; - @override String get empty => 'Samlingen är tom'; - @override String get noItems => 'Inga objekt i den här samlingen'; - @override String get unknownLibrarySection => 'Kan inte ta bort: okänd bibliotekssektion'; - @override String get deleteCollection => 'Ta bort samling'; - @override String deleteConfirm({required Object title}) => 'Är du säker på att du vill ta bort "${title}"? Detta går inte att ångra.'; - @override String get deleted => 'Samling borttagen'; - @override String get deleteFailed => 'Det gick inte att ta bort samlingen'; - @override String deleteFailedWithError({required Object error}) => 'Det gick inte att ta bort samlingen: ${error}'; - @override String failedToLoadItems({required Object error}) => 'Det gick inte att läsa in samlingsobjekt: ${error}'; - @override String get addTo => 'Lägg till i samling'; - @override String get selectCollection => 'Välj samling'; - @override String get createNewCollection => 'Skapa ny samling'; - @override String get collectionName => 'Samlingsnamn'; - @override String get enterCollectionName => 'Ange samlingsnamn'; - @override String get addedToCollection => 'Tillagd i samling'; - @override String get errorAddingToCollection => 'Fel vid tillägg i samling'; - @override String get created => 'Samling skapad'; - @override String get removeFromCollection => 'Ta bort från samling'; - @override String removeFromCollectionConfirm({required Object title}) => 'Ta bort "${title}" från denna samling?'; - @override String get removedFromCollection => 'Borttagen från samling'; - @override String get removeFromCollectionFailed => 'Misslyckades med att ta bort från samling'; - @override String removeFromCollectionError({required Object error}) => 'Fel vid borttagning från samling: ${error}'; + // Translations + @override + String get title => 'Samlingar'; + @override + String get collection => 'Samling'; + @override + String get empty => 'Samlingen är tom'; + @override + String get noItems => 'Inga objekt i den här samlingen'; + @override + String get unknownLibrarySection => + 'Kan inte ta bort: okänd bibliotekssektion'; + @override + String get deleteCollection => 'Ta bort samling'; + @override + String deleteConfirm({required Object title}) => + 'Är du säker på att du vill ta bort "${title}"? Detta går inte att ångra.'; + @override + String get deleted => 'Samling borttagen'; + @override + String get deleteFailed => 'Det gick inte att ta bort samlingen'; + @override + String deleteFailedWithError({required Object error}) => + 'Det gick inte att ta bort samlingen: ${error}'; + @override + String failedToLoadItems({required Object error}) => + 'Det gick inte att läsa in samlingsobjekt: ${error}'; + @override + String get addTo => 'Lägg till i samling'; + @override + String get selectCollection => 'Välj samling'; + @override + String get createNewCollection => 'Skapa ny samling'; + @override + String get collectionName => 'Samlingsnamn'; + @override + String get enterCollectionName => 'Ange samlingsnamn'; + @override + String get addedToCollection => 'Tillagd i samling'; + @override + String get errorAddingToCollection => 'Fel vid tillägg i samling'; + @override + String get created => 'Samling skapad'; + @override + String get removeFromCollection => 'Ta bort från samling'; + @override + String removeFromCollectionConfirm({required Object title}) => + 'Ta bort "${title}" från denna samling?'; + @override + String get removedFromCollection => 'Borttagen från samling'; + @override + String get removeFromCollectionFailed => + 'Misslyckades med att ta bort från samling'; + @override + String removeFromCollectionError({required Object error}) => + 'Fel vid borttagning från samling: ${error}'; } // Path: libraries.tabs class _StringsLibrariesTabsSv implements _StringsLibrariesTabsEn { - _StringsLibrariesTabsSv._(this._root); + _StringsLibrariesTabsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get recommended => 'Rekommenderat'; - @override String get browse => 'Bläddra'; - @override String get collections => 'Samlingar'; - @override String get playlists => 'Spellistor'; + // Translations + @override + String get recommended => 'Rekommenderat'; + @override + String get browse => 'Bläddra'; + @override + String get collections => 'Samlingar'; + @override + String get playlists => 'Spellistor'; } // Path: libraries.groupings class _StringsLibrariesGroupingsSv implements _StringsLibrariesGroupingsEn { - _StringsLibrariesGroupingsSv._(this._root); + _StringsLibrariesGroupingsSv._(this._root); - @override final _StringsSv _root; // ignore: unused_field + @override + final _StringsSv _root; // ignore: unused_field - // Translations - @override String get all => 'Alla'; - @override String get movies => 'Filmer'; - @override String get shows => 'Serier'; - @override String get seasons => 'Säsonger'; - @override String get episodes => 'Avsnitt'; - @override String get folders => 'Mappar'; + // Translations + @override + String get all => 'Alla'; + @override + String get movies => 'Filmer'; + @override + String get shows => 'Serier'; + @override + String get seasons => 'Säsonger'; + @override + String get episodes => 'Avsnitt'; + @override + String get folders => 'Mappar'; } // Path: class _StringsZh implements Translations { - /// You can call this constructor and build your own translation instance of this locale. - /// Constructing via the enum [AppLocale.build] is preferred. - _StringsZh.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) - : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), - $meta = TranslationMetadata( - locale: AppLocale.zh, - overrides: overrides ?? {}, - cardinalResolver: cardinalResolver, - ordinalResolver: ordinalResolver, - ) { - $meta.setFlatMapFunction(_flatMapFunction); - } + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsZh.build({ + Map? overrides, + PluralResolver? cardinalResolver, + PluralResolver? ordinalResolver, + }) : assert( + overrides == null, + 'Set "translation_overrides: true" in order to enable this feature.', + ), + $meta = TranslationMetadata( + locale: AppLocale.zh, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } - /// Metadata for the translations of . - @override final TranslationMetadata $meta; + /// Metadata for the translations of . + @override + final TranslationMetadata $meta; - /// Access flat map - @override dynamic operator[](String key) => $meta.getTranslation(key); + /// Access flat map + @override + dynamic operator [](String key) => $meta.getTranslation(key); - @override late final _StringsZh _root = this; // ignore: unused_field + @override + late final _StringsZh _root = this; // ignore: unused_field - // Translations - @override late final _StringsAppZh app = _StringsAppZh._(_root); - @override late final _StringsAuthZh auth = _StringsAuthZh._(_root); - @override late final _StringsCommonZh common = _StringsCommonZh._(_root); - @override late final _StringsScreensZh screens = _StringsScreensZh._(_root); - @override late final _StringsUpdateZh update = _StringsUpdateZh._(_root); - @override late final _StringsSettingsZh settings = _StringsSettingsZh._(_root); - @override late final _StringsSearchZh search = _StringsSearchZh._(_root); - @override late final _StringsHotkeysZh hotkeys = _StringsHotkeysZh._(_root); - @override late final _StringsPinEntryZh pinEntry = _StringsPinEntryZh._(_root); - @override late final _StringsFileInfoZh fileInfo = _StringsFileInfoZh._(_root); - @override late final _StringsMediaMenuZh mediaMenu = _StringsMediaMenuZh._(_root); - @override late final _StringsAccessibilityZh accessibility = _StringsAccessibilityZh._(_root); - @override late final _StringsTooltipsZh tooltips = _StringsTooltipsZh._(_root); - @override late final _StringsVideoControlsZh videoControls = _StringsVideoControlsZh._(_root); - @override late final _StringsUserStatusZh userStatus = _StringsUserStatusZh._(_root); - @override late final _StringsMessagesZh messages = _StringsMessagesZh._(_root); - @override late final _StringsProfileZh profile = _StringsProfileZh._(_root); - @override late final _StringsSubtitlingStylingZh subtitlingStyling = _StringsSubtitlingStylingZh._(_root); - @override late final _StringsDialogZh dialog = _StringsDialogZh._(_root); - @override late final _StringsDiscoverZh discover = _StringsDiscoverZh._(_root); - @override late final _StringsErrorsZh errors = _StringsErrorsZh._(_root); - @override late final _StringsLibrariesZh libraries = _StringsLibrariesZh._(_root); - @override late final _StringsAboutZh about = _StringsAboutZh._(_root); - @override late final _StringsServerSelectionZh serverSelection = _StringsServerSelectionZh._(_root); - @override late final _StringsHubDetailZh hubDetail = _StringsHubDetailZh._(_root); - @override late final _StringsLogsZh logs = _StringsLogsZh._(_root); - @override late final _StringsLicensesZh licenses = _StringsLicensesZh._(_root); - @override late final _StringsNavigationZh navigation = _StringsNavigationZh._(_root); - @override late final _StringsPlaylistsZh playlists = _StringsPlaylistsZh._(_root); - @override late final _StringsCollectionsZh collections = _StringsCollectionsZh._(_root); + // Translations + @override + late final _StringsAppZh app = _StringsAppZh._(_root); + @override + late final _StringsAuthZh auth = _StringsAuthZh._(_root); + @override + late final _StringsCommonZh common = _StringsCommonZh._(_root); + @override + late final _StringsScreensZh screens = _StringsScreensZh._(_root); + @override + late final _StringsUpdateZh update = _StringsUpdateZh._(_root); + @override + late final _StringsSettingsZh settings = _StringsSettingsZh._(_root); + @override + late final _StringsSearchZh search = _StringsSearchZh._(_root); + @override + late final _StringsHotkeysZh hotkeys = _StringsHotkeysZh._(_root); + @override + late final _StringsPinEntryZh pinEntry = _StringsPinEntryZh._(_root); + @override + late final _StringsFileInfoZh fileInfo = _StringsFileInfoZh._(_root); + @override + late final _StringsMediaMenuZh mediaMenu = _StringsMediaMenuZh._(_root); + @override + late final _StringsAccessibilityZh accessibility = _StringsAccessibilityZh._( + _root, + ); + @override + late final _StringsTooltipsZh tooltips = _StringsTooltipsZh._(_root); + @override + late final _StringsVideoControlsZh videoControls = _StringsVideoControlsZh._( + _root, + ); + @override + late final _StringsUserStatusZh userStatus = _StringsUserStatusZh._(_root); + @override + late final _StringsMessagesZh messages = _StringsMessagesZh._(_root); + @override + late final _StringsProfileZh profile = _StringsProfileZh._(_root); + @override + late final _StringsSubtitlingStylingZh subtitlingStyling = + _StringsSubtitlingStylingZh._(_root); + @override + late final _StringsDialogZh dialog = _StringsDialogZh._(_root); + @override + late final _StringsDiscoverZh discover = _StringsDiscoverZh._(_root); + @override + late final _StringsErrorsZh errors = _StringsErrorsZh._(_root); + @override + late final _StringsLibrariesZh libraries = _StringsLibrariesZh._(_root); + @override + late final _StringsAboutZh about = _StringsAboutZh._(_root); + @override + late final _StringsServerSelectionZh serverSelection = + _StringsServerSelectionZh._(_root); + @override + late final _StringsHubDetailZh hubDetail = _StringsHubDetailZh._(_root); + @override + late final _StringsLogsZh logs = _StringsLogsZh._(_root); + @override + late final _StringsLicensesZh licenses = _StringsLicensesZh._(_root); + @override + late final _StringsNavigationZh navigation = _StringsNavigationZh._(_root); + @override + late final _StringsPlaylistsZh playlists = _StringsPlaylistsZh._(_root); + @override + late final _StringsCollectionsZh collections = _StringsCollectionsZh._(_root); } // Path: app class _StringsAppZh implements _StringsAppEn { - _StringsAppZh._(this._root); + _StringsAppZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => 'Plezy'; - @override String get loading => '加载中...'; + // Translations + @override + String get title => 'Plezy'; + @override + String get loading => '加载中...'; } // Path: auth class _StringsAuthZh implements _StringsAuthEn { - _StringsAuthZh._(this._root); + _StringsAuthZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get signInWithPlex => '使用 Plex 登录'; - @override String get showQRCode => '显示二维码'; - @override String get cancel => '取消'; - @override String get authenticate => '验证'; - @override String get retry => '重试'; - @override String get debugEnterToken => '调试:输入 Plex Token'; - @override String get plexTokenLabel => 'Plex 授权令牌 (Auth Token)'; - @override String get plexTokenHint => '输入你的 Plex.tv 令牌'; - @override String get authenticationTimeout => '验证超时。请重试。'; - @override String get scanQRCodeInstruction => '请使用已登录 Plex 的设备扫描此二维码进行验证。'; - @override String get waitingForAuth => '等待验证中...\n请在你的浏览器中完成登录。'; + // Translations + @override + String get signInWithPlex => '使用 Plex 登录'; + @override + String get showQRCode => '显示二维码'; + @override + String get cancel => '取消'; + @override + String get authenticate => '验证'; + @override + String get retry => '重试'; + @override + String get debugEnterToken => '调试:输入 Plex Token'; + @override + String get plexTokenLabel => 'Plex 授权令牌 (Auth Token)'; + @override + String get plexTokenHint => '输入你的 Plex.tv 令牌'; + @override + String get authenticationTimeout => '验证超时。请重试。'; + @override + String get scanQRCodeInstruction => '请使用已登录 Plex 的设备扫描此二维码进行验证。'; + @override + String get waitingForAuth => '等待验证中...\n请在你的浏览器中完成登录。'; } // Path: common class _StringsCommonZh implements _StringsCommonEn { - _StringsCommonZh._(this._root); + _StringsCommonZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get cancel => '取消'; - @override String get save => '保存'; - @override String get close => '关闭'; - @override String get clear => '清除'; - @override String get reset => '重置'; - @override String get later => '稍后'; - @override String get submit => '提交'; - @override String get confirm => '确认'; - @override String get retry => '重试'; - @override String get playNow => '立即播放'; - @override String get logout => '登出'; - @override String get online => '在线'; - @override String get offline => '离线'; - @override String get owned => '拥有'; - @override String get shared => '共享'; - @override String get current => '当前'; - @override String get unknown => '未知'; - @override String get refresh => '刷新'; - @override String get yes => '是'; - @override String get no => '否'; - @override String get server => '服务器'; - @override String get delete => '删除'; - @override String get shuffle => '随机播放'; - @override String get addTo => '添加到...'; + // Translations + @override + String get cancel => '取消'; + @override + String get save => '保存'; + @override + String get close => '关闭'; + @override + String get clear => '清除'; + @override + String get reset => '重置'; + @override + String get later => '稍后'; + @override + String get submit => '提交'; + @override + String get confirm => '确认'; + @override + String get retry => '重试'; + @override + String get playNow => '立即播放'; + @override + String get logout => '登出'; + @override + String get online => '在线'; + @override + String get offline => '离线'; + @override + String get owned => '拥有'; + @override + String get shared => '共享'; + @override + String get current => '当前'; + @override + String get unknown => '未知'; + @override + String get refresh => '刷新'; + @override + String get yes => '是'; + @override + String get no => '否'; + @override + String get server => '服务器'; + @override + String get delete => '删除'; + @override + String get shuffle => '随机播放'; + @override + String get addTo => '添加到...'; } // Path: screens class _StringsScreensZh implements _StringsScreensEn { - _StringsScreensZh._(this._root); + _StringsScreensZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get licenses => '许可证'; - @override String get selectServer => '选择服务器'; - @override String get switchProfile => '切换配置文件'; - @override String get subtitleStyling => '字幕样式'; - @override String get search => '搜索'; - @override String get logs => '日志'; + // Translations + @override + String get licenses => '许可证'; + @override + String get selectServer => '选择服务器'; + @override + String get switchProfile => '切换配置文件'; + @override + String get subtitleStyling => '字幕样式'; + @override + String get search => '搜索'; + @override + String get logs => '日志'; } // Path: update class _StringsUpdateZh implements _StringsUpdateEn { - _StringsUpdateZh._(this._root); + _StringsUpdateZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get available => '有可用更新'; - @override String versionAvailable({required Object version}) => '版本 ${version} 已发布'; - @override String currentVersion({required Object version}) => '当前版本: ${version}'; - @override String get skipVersion => '跳过此版本'; - @override String get viewRelease => '查看发布详情'; - @override String get latestVersion => '已安装的版本是可用的最新版本'; - @override String get checkFailed => '无法检查更新'; + // Translations + @override + String get available => '有可用更新'; + @override + String versionAvailable({required Object version}) => '版本 ${version} 已发布'; + @override + String currentVersion({required Object version}) => '当前版本: ${version}'; + @override + String get skipVersion => '跳过此版本'; + @override + String get viewRelease => '查看发布详情'; + @override + String get latestVersion => '已安装的版本是可用的最新版本'; + @override + String get checkFailed => '无法检查更新'; } // Path: settings class _StringsSettingsZh implements _StringsSettingsEn { - _StringsSettingsZh._(this._root); + _StringsSettingsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '设置'; - @override String get language => '语言'; - @override String get theme => '主题'; - @override String get appearance => '外观'; - @override String get videoPlayback => '视频播放'; - @override String get shufflePlay => '随机播放'; - @override String get advanced => '高级'; - @override String get useSeasonPostersDescription => '为剧集显示季海报而非剧集海报'; - @override String get showHeroSectionDescription => '在主屏幕上显示精选内容轮播区'; - @override String get secondsLabel => '秒'; - @override String get minutesLabel => '分钟'; - @override String get secondsShort => 's'; - @override String get minutesShort => 'm'; - @override String durationHint({required Object min, required Object max}) => '输入时长 (${min}-${max})'; - @override String get systemTheme => '系统'; - @override String get systemThemeDescription => '跟随系统设置'; - @override String get lightTheme => '浅色'; - @override String get darkTheme => '深色'; - @override String get libraryDensity => '媒体库密度'; - @override String get compact => '紧凑'; - @override String get compactDescription => '卡片更小,显示更多项目'; - @override String get normal => '标准'; - @override String get normalDescription => '默认尺寸'; - @override String get comfortable => '舒适'; - @override String get comfortableDescription => '卡片更大,显示更少项目'; - @override String get viewMode => '视图模式'; - @override String get gridView => '网格视图'; - @override String get gridViewDescription => '以网格布局显示项目'; - @override String get listView => '列表视图'; - @override String get listViewDescription => '以列表布局显示项目'; - @override String get useSeasonPosters => '使用季海报'; - @override String get showHeroSection => '显示主要精选区'; - @override String get hardwareDecoding => '硬件解码'; - @override String get hardwareDecodingDescription => '如果可用,使用硬件加速'; - @override String get bufferSize => '缓冲区大小'; - @override String bufferSizeMB({required Object size}) => '${size}MB'; - @override String get subtitleStyling => '字幕样式'; - @override String get subtitleStylingDescription => '调整字幕外观'; - @override String get smallSkipDuration => '短跳过时长'; - @override String get largeSkipDuration => '长跳过时长'; - @override String secondsUnit({required Object seconds}) => '${seconds} 秒'; - @override String get defaultSleepTimer => '默认睡眠定时器'; - @override String minutesUnit({required Object minutes}) => '${minutes} 分钟'; - @override String get rememberTrackSelections => '记住每个剧集/电影的音轨选择'; - @override String get rememberTrackSelectionsDescription => '在播放过程中更改音轨时自动保存音频和字幕语言偏好'; - @override String get unwatchedOnly => '仅未观看'; - @override String get unwatchedOnlyDescription => '随机队列中仅包含未观看的剧集'; - @override String get shuffleOrderNavigation => '随机顺序导航'; - @override String get shuffleOrderNavigationDescription => '下一集/上一集按钮遵循随机播放顺序'; - @override String get loopShuffleQueue => '循环随机队列'; - @override String get loopShuffleQueueDescription => '在队列结束时重新开始'; - @override String get videoPlayerControls => '视频播放器控制'; - @override String get keyboardShortcuts => '键盘快捷键'; - @override String get keyboardShortcutsDescription => '自定义键盘快捷键'; - @override String get debugLogging => '调试日志'; - @override String get debugLoggingDescription => '启用详细日志记录以便故障排除'; - @override String get viewLogs => '查看日志'; - @override String get viewLogsDescription => '查看应用程序日志'; - @override String get clearCache => '清除缓存'; - @override String get clearCacheDescription => '这将清除所有缓存的图片和数据。清除缓存后,应用程序加载内容可能会变慢。'; - @override String get clearCacheSuccess => '缓存清除成功'; - @override String get resetSettings => '重置设置'; - @override String get resetSettingsDescription => '这会将所有设置重置为其默认值。此操作无法撤销。'; - @override String get resetSettingsSuccess => '设置重置成功'; - @override String get shortcutsReset => '快捷键已重置为默认值'; - @override String get about => '关于'; - @override String get aboutDescription => '应用程序信息和许可证'; - @override String get updates => '更新'; - @override String get updateAvailable => '有可用更新'; - @override String get checkForUpdates => '检查更新'; - @override String get validationErrorEnterNumber => '请输入一个有效的数字'; - @override String validationErrorDuration({required Object min, required Object max, required Object unit}) => '时长必须介于 ${min} 和 ${max} ${unit} 之间'; - @override String shortcutAlreadyAssigned({required Object action}) => '快捷键已被分配给 ${action}'; - @override String shortcutUpdated({required Object action}) => '快捷键已为 ${action} 更新'; + // Translations + @override + String get title => '设置'; + @override + String get language => '语言'; + @override + String get theme => '主题'; + @override + String get appearance => '外观'; + @override + String get videoPlayback => '视频播放'; + @override + String get shufflePlay => '随机播放'; + @override + String get advanced => '高级'; + @override + String get useSeasonPostersDescription => '为剧集显示季海报而非剧集海报'; + @override + String get showHeroSectionDescription => '在主屏幕上显示精选内容轮播区'; + @override + String get secondsLabel => '秒'; + @override + String get minutesLabel => '分钟'; + @override + String get secondsShort => 's'; + @override + String get minutesShort => 'm'; + @override + String durationHint({required Object min, required Object max}) => + '输入时长 (${min}-${max})'; + @override + String get systemTheme => '系统'; + @override + String get systemThemeDescription => '跟随系统设置'; + @override + String get lightTheme => '浅色'; + @override + String get darkTheme => '深色'; + @override + String get libraryDensity => '媒体库密度'; + @override + String get compact => '紧凑'; + @override + String get compactDescription => '卡片更小,显示更多项目'; + @override + String get normal => '标准'; + @override + String get normalDescription => '默认尺寸'; + @override + String get comfortable => '舒适'; + @override + String get comfortableDescription => '卡片更大,显示更少项目'; + @override + String get viewMode => '视图模式'; + @override + String get gridView => '网格视图'; + @override + String get gridViewDescription => '以网格布局显示项目'; + @override + String get listView => '列表视图'; + @override + String get listViewDescription => '以列表布局显示项目'; + @override + String get useSeasonPosters => '使用季海报'; + @override + String get showHeroSection => '显示主要精选区'; + @override + String get hardwareDecoding => '硬件解码'; + @override + String get hardwareDecodingDescription => '如果可用,使用硬件加速'; + @override + String get bufferSize => '缓冲区大小'; + @override + String bufferSizeMB({required Object size}) => '${size}MB'; + @override + String get subtitleStyling => '字幕样式'; + @override + String get subtitleStylingDescription => '调整字幕外观'; + @override + String get smallSkipDuration => '短跳过时长'; + @override + String get largeSkipDuration => '长跳过时长'; + @override + String secondsUnit({required Object seconds}) => '${seconds} 秒'; + @override + String get defaultSleepTimer => '默认睡眠定时器'; + @override + String minutesUnit({required Object minutes}) => '${minutes} 分钟'; + @override + String get rememberTrackSelections => '记住每个剧集/电影的音轨选择'; + @override + String get rememberTrackSelectionsDescription => '在播放过程中更改音轨时自动保存音频和字幕语言偏好'; + @override + String get unwatchedOnly => '仅未观看'; + @override + String get unwatchedOnlyDescription => '随机队列中仅包含未观看的剧集'; + @override + String get shuffleOrderNavigation => '随机顺序导航'; + @override + String get shuffleOrderNavigationDescription => '下一集/上一集按钮遵循随机播放顺序'; + @override + String get loopShuffleQueue => '循环随机队列'; + @override + String get loopShuffleQueueDescription => '在队列结束时重新开始'; + @override + String get videoPlayerControls => '视频播放器控制'; + @override + String get keyboardShortcuts => '键盘快捷键'; + @override + String get keyboardShortcutsDescription => '自定义键盘快捷键'; + @override + String get debugLogging => '调试日志'; + @override + String get debugLoggingDescription => '启用详细日志记录以便故障排除'; + @override + String get viewLogs => '查看日志'; + @override + String get viewLogsDescription => '查看应用程序日志'; + @override + String get clearCache => '清除缓存'; + @override + String get clearCacheDescription => '这将清除所有缓存的图片和数据。清除缓存后,应用程序加载内容可能会变慢。'; + @override + String get clearCacheSuccess => '缓存清除成功'; + @override + String get resetSettings => '重置设置'; + @override + String get resetSettingsDescription => '这会将所有设置重置为其默认值。此操作无法撤销。'; + @override + String get resetSettingsSuccess => '设置重置成功'; + @override + String get shortcutsReset => '快捷键已重置为默认值'; + @override + String get about => '关于'; + @override + String get aboutDescription => '应用程序信息和许可证'; + @override + String get updates => '更新'; + @override + String get updateAvailable => '有可用更新'; + @override + String get checkForUpdates => '检查更新'; + @override + String get validationErrorEnterNumber => '请输入一个有效的数字'; + @override + String validationErrorDuration({ + required Object min, + required Object max, + required Object unit, + }) => '时长必须介于 ${min} 和 ${max} ${unit} 之间'; + @override + String shortcutAlreadyAssigned({required Object action}) => + '快捷键已被分配给 ${action}'; + @override + String shortcutUpdated({required Object action}) => '快捷键已为 ${action} 更新'; } // Path: search class _StringsSearchZh implements _StringsSearchEn { - _StringsSearchZh._(this._root); + _StringsSearchZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get hint => '搜索电影、系列、音乐...'; - @override String get tryDifferentTerm => '尝试不同的搜索词'; - @override String get searchYourMedia => '搜索媒体'; - @override String get enterTitleActorOrKeyword => '输入标题、演员或关键词'; + // Translations + @override + String get hint => '搜索电影、系列、音乐...'; + @override + String get tryDifferentTerm => '尝试不同的搜索词'; + @override + String get searchYourMedia => '搜索媒体'; + @override + String get enterTitleActorOrKeyword => '输入标题、演员或关键词'; } // Path: hotkeys class _StringsHotkeysZh implements _StringsHotkeysEn { - _StringsHotkeysZh._(this._root); + _StringsHotkeysZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String setShortcutFor({required Object actionName}) => '为 ${actionName} 设置快捷键'; - @override String get clearShortcut => '清除快捷键'; + // Translations + @override + String setShortcutFor({required Object actionName}) => + '为 ${actionName} 设置快捷键'; + @override + String get clearShortcut => '清除快捷键'; } // Path: pinEntry class _StringsPinEntryZh implements _StringsPinEntryEn { - _StringsPinEntryZh._(this._root); + _StringsPinEntryZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get enterPin => '输入 PIN'; - @override String get showPin => '显示 PIN'; - @override String get hidePin => '隐藏 PIN'; + // Translations + @override + String get enterPin => '输入 PIN'; + @override + String get showPin => '显示 PIN'; + @override + String get hidePin => '隐藏 PIN'; } // Path: fileInfo class _StringsFileInfoZh implements _StringsFileInfoEn { - _StringsFileInfoZh._(this._root); + _StringsFileInfoZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '文件信息'; - @override String get video => '视频'; - @override String get audio => '音频'; - @override String get file => '文件'; - @override String get advanced => '高级'; - @override String get codec => '编解码器'; - @override String get resolution => '分辨率'; - @override String get bitrate => '比特率'; - @override String get frameRate => '帧率'; - @override String get aspectRatio => '宽高比'; - @override String get profile => '配置文件'; - @override String get bitDepth => '位深度'; - @override String get colorSpace => '色彩空间'; - @override String get colorRange => '色彩范围'; - @override String get colorPrimaries => '颜色原色'; - @override String get chromaSubsampling => '色度子采样'; - @override String get channels => '声道'; - @override String get path => '路径'; - @override String get size => '大小'; - @override String get container => '容器'; - @override String get duration => '时长'; - @override String get optimizedForStreaming => '已优化用于流媒体'; - @override String get has64bitOffsets => '64位偏移量'; + // Translations + @override + String get title => '文件信息'; + @override + String get video => '视频'; + @override + String get audio => '音频'; + @override + String get file => '文件'; + @override + String get advanced => '高级'; + @override + String get codec => '编解码器'; + @override + String get resolution => '分辨率'; + @override + String get bitrate => '比特率'; + @override + String get frameRate => '帧率'; + @override + String get aspectRatio => '宽高比'; + @override + String get profile => '配置文件'; + @override + String get bitDepth => '位深度'; + @override + String get colorSpace => '色彩空间'; + @override + String get colorRange => '色彩范围'; + @override + String get colorPrimaries => '颜色原色'; + @override + String get chromaSubsampling => '色度子采样'; + @override + String get channels => '声道'; + @override + String get path => '路径'; + @override + String get size => '大小'; + @override + String get container => '容器'; + @override + String get duration => '时长'; + @override + String get optimizedForStreaming => '已优化用于流媒体'; + @override + String get has64bitOffsets => '64位偏移量'; } // Path: mediaMenu class _StringsMediaMenuZh implements _StringsMediaMenuEn { - _StringsMediaMenuZh._(this._root); + _StringsMediaMenuZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get markAsWatched => '标记为已观看'; - @override String get markAsUnwatched => '标记为未观看'; - @override String get removeFromContinueWatching => '从继续观看中移除'; - @override String get goToSeries => '转到系列'; - @override String get goToSeason => '转到季'; - @override String get shufflePlay => '随机播放'; - @override String get fileInfo => '文件信息'; + // Translations + @override + String get markAsWatched => '标记为已观看'; + @override + String get markAsUnwatched => '标记为未观看'; + @override + String get removeFromContinueWatching => '从继续观看中移除'; + @override + String get goToSeries => '转到系列'; + @override + String get goToSeason => '转到季'; + @override + String get shufflePlay => '随机播放'; + @override + String get fileInfo => '文件信息'; } // Path: accessibility class _StringsAccessibilityZh implements _StringsAccessibilityEn { - _StringsAccessibilityZh._(this._root); + _StringsAccessibilityZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String mediaCardMovie({required Object title}) => '${title}, 电影'; - @override String mediaCardShow({required Object title}) => '${title}, 电视剧'; - @override String mediaCardEpisode({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - @override String mediaCardSeason({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - @override String get mediaCardWatched => '已观看'; - @override String mediaCardPartiallyWatched({required Object percent}) => '已观看 ${percent} 百分比'; - @override String get mediaCardUnwatched => '未观看'; - @override String get tapToPlay => '点击播放'; + // Translations + @override + String mediaCardMovie({required Object title}) => '${title}, 电影'; + @override + String mediaCardShow({required Object title}) => '${title}, 电视剧'; + @override + String mediaCardEpisode({ + required Object title, + required Object episodeInfo, + }) => '${title}, ${episodeInfo}'; + @override + String mediaCardSeason({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + @override + String get mediaCardWatched => '已观看'; + @override + String mediaCardPartiallyWatched({required Object percent}) => + '已观看 ${percent} 百分比'; + @override + String get mediaCardUnwatched => '未观看'; + @override + String get tapToPlay => '点击播放'; } // Path: tooltips class _StringsTooltipsZh implements _StringsTooltipsEn { - _StringsTooltipsZh._(this._root); + _StringsTooltipsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get shufflePlay => '随机播放'; - @override String get markAsWatched => '标记为已观看'; - @override String get markAsUnwatched => '标记为未观看'; + // Translations + @override + String get shufflePlay => '随机播放'; + @override + String get markAsWatched => '标记为已观看'; + @override + String get markAsUnwatched => '标记为未观看'; } // Path: videoControls class _StringsVideoControlsZh implements _StringsVideoControlsEn { - _StringsVideoControlsZh._(this._root); + _StringsVideoControlsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get audioLabel => '音频'; - @override String get subtitlesLabel => '字幕'; - @override String get resetToZero => '重置为 0ms'; - @override String addTime({required Object amount, required Object unit}) => '+${amount}${unit}'; - @override String minusTime({required Object amount, required Object unit}) => '-${amount}${unit}'; - @override String playsLater({required Object label}) => '${label} 播放较晚'; - @override String playsEarlier({required Object label}) => '${label} 播放较早'; - @override String get noOffset => '无偏移'; - @override String get letterbox => '信箱模式(Letterbox)'; - @override String get fillScreen => '填充屏幕'; - @override String get stretch => '拉伸'; - @override String get lockRotation => '锁定旋转'; - @override String get unlockRotation => '解锁旋转'; - @override String get sleepTimer => '睡眠定时器'; - @override String get timerActive => '定时器已激活'; - @override String playbackWillPauseIn({required Object duration}) => '播放将在 ${duration} 后暂停'; - @override String get sleepTimerCompleted => '睡眠定时器已完成 - 播放已暂停'; - @override String get playButton => '播放'; - @override String get pauseButton => '暂停'; - @override String seekBackwardButton({required Object seconds}) => '后退 ${seconds} 秒'; - @override String seekForwardButton({required Object seconds}) => '前进 ${seconds} 秒'; - @override String get previousButton => '上一集'; - @override String get nextButton => '下一集'; - @override String get previousChapterButton => '上一章节'; - @override String get nextChapterButton => '下一章节'; - @override String get volumeButton => '音量'; - @override String get muteButton => '静音'; - @override String get unmuteButton => '取消静音'; - @override String get settingsButton => '视频设置'; - @override String get audioTrackButton => '音轨'; - @override String get subtitlesButton => '字幕'; - @override String get chaptersButton => '章节'; - @override String get versionsButton => '视频版本'; - @override String get aspectRatioButton => '宽高比'; - @override String get fullscreenButton => '进入全屏'; - @override String get exitFullscreenButton => '退出全屏'; - @override String get rotationLockButton => '旋转锁定'; - @override String get timelineSlider => '视频时间轴'; - @override String get volumeSlider => '音量调节'; - @override String get backButton => '返回'; + // Translations + @override + String get audioLabel => '音频'; + @override + String get subtitlesLabel => '字幕'; + @override + String get resetToZero => '重置为 0ms'; + @override + String addTime({required Object amount, required Object unit}) => + '+${amount}${unit}'; + @override + String minusTime({required Object amount, required Object unit}) => + '-${amount}${unit}'; + @override + String playsLater({required Object label}) => '${label} 播放较晚'; + @override + String playsEarlier({required Object label}) => '${label} 播放较早'; + @override + String get noOffset => '无偏移'; + @override + String get letterbox => '信箱模式(Letterbox)'; + @override + String get fillScreen => '填充屏幕'; + @override + String get stretch => '拉伸'; + @override + String get lockRotation => '锁定旋转'; + @override + String get unlockRotation => '解锁旋转'; + @override + String get sleepTimer => '睡眠定时器'; + @override + String get timerActive => '定时器已激活'; + @override + String playbackWillPauseIn({required Object duration}) => + '播放将在 ${duration} 后暂停'; + @override + String get sleepTimerCompleted => '睡眠定时器已完成 - 播放已暂停'; + @override + String get playButton => '播放'; + @override + String get pauseButton => '暂停'; + @override + String seekBackwardButton({required Object seconds}) => '后退 ${seconds} 秒'; + @override + String seekForwardButton({required Object seconds}) => '前进 ${seconds} 秒'; + @override + String get previousButton => '上一集'; + @override + String get nextButton => '下一集'; + @override + String get previousChapterButton => '上一章节'; + @override + String get nextChapterButton => '下一章节'; + @override + String get volumeButton => '音量'; + @override + String get muteButton => '静音'; + @override + String get unmuteButton => '取消静音'; + @override + String get settingsButton => '视频设置'; + @override + String get audioTrackButton => '音轨'; + @override + String get subtitlesButton => '字幕'; + @override + String get chaptersButton => '章节'; + @override + String get versionsButton => '视频版本'; + @override + String get aspectRatioButton => '宽高比'; + @override + String get fullscreenButton => '进入全屏'; + @override + String get exitFullscreenButton => '退出全屏'; + @override + String get rotationLockButton => '旋转锁定'; + @override + String get timelineSlider => '视频时间轴'; + @override + String get volumeSlider => '音量调节'; + @override + String get backButton => '返回'; } // Path: userStatus class _StringsUserStatusZh implements _StringsUserStatusEn { - _StringsUserStatusZh._(this._root); + _StringsUserStatusZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get admin => '管理员'; - @override String get restricted => '受限'; - @override String get protected => '受保护'; - @override String get current => '当前'; + // Translations + @override + String get admin => '管理员'; + @override + String get restricted => '受限'; + @override + String get protected => '受保护'; + @override + String get current => '当前'; } // Path: messages class _StringsMessagesZh implements _StringsMessagesEn { - _StringsMessagesZh._(this._root); + _StringsMessagesZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get markedAsWatched => '已标记为已观看'; - @override String get markedAsUnwatched => '已标记为未观看'; - @override String get removedFromContinueWatching => '已从继续观看中移除'; - @override String errorLoading({required Object error}) => '错误: ${error}'; - @override String get fileInfoNotAvailable => '文件信息不可用'; - @override String errorLoadingFileInfo({required Object error}) => '加载文件信息时出错: ${error}'; - @override String get errorLoadingSeries => '加载系列时出错'; - @override String get errorLoadingSeason => '加载季时出错'; - @override String get musicNotSupported => '尚不支持播放音乐'; - @override String get logsCleared => '日志已清除'; - @override String get logsCopied => '日志已复制到剪贴板'; - @override String get noLogsAvailable => '没有可用日志'; - @override String libraryScanning({required Object title}) => '正在扫描 “${title}”...'; - @override String libraryScanStarted({required Object title}) => '已开始扫描 “${title}” 媒体库'; - @override String libraryScanFailed({required Object error}) => '无法扫描媒体库: ${error}'; - @override String metadataRefreshing({required Object title}) => '正在刷新 “${title}” 的元数据...'; - @override String metadataRefreshStarted({required Object title}) => '已开始刷新 “${title}” 的元数据'; - @override String metadataRefreshFailed({required Object error}) => '无法刷新元数据: ${error}'; - @override String get noPlexToken => '未找到 Plex 令牌。请重新登录。'; - @override String get logoutConfirm => '你确定要登出吗?'; - @override String get noSeasonsFound => '未找到季'; - @override String get noEpisodesFound => '在第一季中未找到剧集'; - @override String get noEpisodesFoundGeneral => '未找到剧集'; - @override String get noResultsFound => '未找到结果'; - @override String sleepTimerSet({required Object label}) => '睡眠定时器已设置为 ${label}'; - @override String failedToSwitchProfile({required Object displayName}) => '无法切换到 ${displayName}'; - @override String get noItemsAvailable => '没有可用的项目'; - @override String get failedToCreatePlayQueue => '创建播放队列失败'; - @override String get failedToCreatePlayQueueNoItems => '创建播放队列失败 - 没有项目'; - @override String failedPlayback({required Object action, required Object error}) => '无法${action}: ${error}'; + // Translations + @override + String get markedAsWatched => '已标记为已观看'; + @override + String get markedAsUnwatched => '已标记为未观看'; + @override + String get removedFromContinueWatching => '已从继续观看中移除'; + @override + String errorLoading({required Object error}) => '错误: ${error}'; + @override + String get fileInfoNotAvailable => '文件信息不可用'; + @override + String errorLoadingFileInfo({required Object error}) => '加载文件信息时出错: ${error}'; + @override + String get errorLoadingSeries => '加载系列时出错'; + @override + String get errorLoadingSeason => '加载季时出错'; + @override + String get musicNotSupported => '尚不支持播放音乐'; + @override + String get logsCleared => '日志已清除'; + @override + String get logsCopied => '日志已复制到剪贴板'; + @override + String get noLogsAvailable => '没有可用日志'; + @override + String libraryScanning({required Object title}) => '正在扫描 “${title}”...'; + @override + String libraryScanStarted({required Object title}) => '已开始扫描 “${title}” 媒体库'; + @override + String libraryScanFailed({required Object error}) => '无法扫描媒体库: ${error}'; + @override + String metadataRefreshing({required Object title}) => + '正在刷新 “${title}” 的元数据...'; + @override + String metadataRefreshStarted({required Object title}) => + '已开始刷新 “${title}” 的元数据'; + @override + String metadataRefreshFailed({required Object error}) => '无法刷新元数据: ${error}'; + @override + String get noPlexToken => '未找到 Plex 令牌。请重新登录。'; + @override + String get logoutConfirm => '你确定要登出吗?'; + @override + String get noSeasonsFound => '未找到季'; + @override + String get noEpisodesFound => '在第一季中未找到剧集'; + @override + String get noEpisodesFoundGeneral => '未找到剧集'; + @override + String get noResultsFound => '未找到结果'; + @override + String sleepTimerSet({required Object label}) => '睡眠定时器已设置为 ${label}'; + @override + String failedToSwitchProfile({required Object displayName}) => + '无法切换到 ${displayName}'; + @override + String get noItemsAvailable => '没有可用的项目'; + @override + String get failedToCreatePlayQueue => '创建播放队列失败'; + @override + String get failedToCreatePlayQueueNoItems => '创建播放队列失败 - 没有项目'; + @override + String failedPlayback({required Object action, required Object error}) => + '无法${action}: ${error}'; } // Path: profile class _StringsProfileZh implements _StringsProfileEn { - _StringsProfileZh._(this._root); + _StringsProfileZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get noUsersAvailable => '没有可用用户'; + // Translations + @override + String get noUsersAvailable => '没有可用用户'; } // Path: subtitlingStyling class _StringsSubtitlingStylingZh implements _StringsSubtitlingStylingEn { - _StringsSubtitlingStylingZh._(this._root); + _StringsSubtitlingStylingZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get stylingOptions => '样式选项'; - @override String get fontSize => '字号'; - @override String get textColor => '文本颜色'; - @override String get borderSize => '边框大小'; - @override String get borderColor => '边框颜色'; - @override String get backgroundOpacity => '背景不透明度'; - @override String get backgroundColor => '背景颜色'; + // Translations + @override + String get stylingOptions => '样式选项'; + @override + String get fontSize => '字号'; + @override + String get textColor => '文本颜色'; + @override + String get borderSize => '边框大小'; + @override + String get borderColor => '边框颜色'; + @override + String get backgroundOpacity => '背景不透明度'; + @override + String get backgroundColor => '背景颜色'; } // Path: dialog class _StringsDialogZh implements _StringsDialogEn { - _StringsDialogZh._(this._root); + _StringsDialogZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get confirmAction => '确认操作'; - @override String get areYouSure => '确定要执行此操作吗?'; - @override String get cancel => '取消'; - @override String get playNow => '立即播放'; + // Translations + @override + String get confirmAction => '确认操作'; + @override + String get areYouSure => '确定要执行此操作吗?'; + @override + String get cancel => '取消'; + @override + String get playNow => '立即播放'; } // Path: discover class _StringsDiscoverZh implements _StringsDiscoverEn { - _StringsDiscoverZh._(this._root); + _StringsDiscoverZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '发现'; - @override String get switchProfile => '切换配置文件'; - @override String get switchServer => '切换服务器'; - @override String get logout => '登出'; - @override String get noContentAvailable => '没有可用内容'; - @override String get addMediaToLibraries => '请向你的媒体库添加一些媒体'; - @override String get continueWatching => '继续观看'; - @override String get recentlyAdded => '最近添加'; - @override String get play => '播放'; - @override String get resume => '继续'; - @override String playEpisode({required Object season, required Object episode}) => '播放 S${season}, E${episode}'; - @override String resumeEpisode({required Object season, required Object episode}) => '继续 S${season}, E${episode}'; - @override String get pause => '暂停'; - @override String get overview => '概述'; - @override String get cast => '演员表'; - @override String get seasons => '季数'; - @override String get studio => '制作公司'; - @override String get rating => '年龄分级'; - @override String get watched => '已观看'; - @override String episodeCount({required Object count}) => '${count} 集'; - @override String watchedProgress({required Object watched, required Object total}) => '已观看 ${watched}/${total} 集'; - @override String get movie => '电影'; - @override String get tvShow => '电视剧'; - @override String minutesLeft({required Object minutes}) => '剩余 ${minutes} 分钟'; + // Translations + @override + String get title => '发现'; + @override + String get switchProfile => '切换配置文件'; + @override + String get switchServer => '切换服务器'; + @override + String get logout => '登出'; + @override + String get noContentAvailable => '没有可用内容'; + @override + String get addMediaToLibraries => '请向你的媒体库添加一些媒体'; + @override + String get continueWatching => '继续观看'; + @override + String get recentlyAdded => '最近添加'; + @override + String get play => '播放'; + @override + String get resume => '继续'; + @override + String playEpisode({required Object season, required Object episode}) => + '播放 S${season}, E${episode}'; + @override + String resumeEpisode({required Object season, required Object episode}) => + '继续 S${season}, E${episode}'; + @override + String get pause => '暂停'; + @override + String get overview => '概述'; + @override + String get cast => '演员表'; + @override + String get seasons => '季数'; + @override + String get studio => '制作公司'; + @override + String get rating => '年龄分级'; + @override + String get watched => '已观看'; + @override + String episodeCount({required Object count}) => '${count} 集'; + @override + String watchedProgress({required Object watched, required Object total}) => + '已观看 ${watched}/${total} 集'; + @override + String get movie => '电影'; + @override + String get tvShow => '电视剧'; + @override + String minutesLeft({required Object minutes}) => '剩余 ${minutes} 分钟'; } // Path: errors class _StringsErrorsZh implements _StringsErrorsEn { - _StringsErrorsZh._(this._root); + _StringsErrorsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String searchFailed({required Object error}) => '搜索失败: ${error}'; - @override String connectionTimeout({required Object context}) => '加载 ${context} 时连接超时'; - @override String get connectionFailed => '无法连接到 Plex 服务器'; - @override String failedToLoad({required Object context, required Object error}) => '无法加载 ${context}: ${error}'; - @override String get noClientAvailable => '没有可用客户端'; - @override String authenticationFailed({required Object error}) => '验证失败: ${error}'; - @override String get couldNotLaunchUrl => '无法打开授权 URL'; - @override String get pleaseEnterToken => '请输入一个令牌'; - @override String get invalidToken => '令牌无效'; - @override String failedToVerifyToken({required Object error}) => '无法验证令牌: ${error}'; - @override String failedToSwitchProfile({required Object displayName}) => '无法切换到 ${displayName}'; - @override String get connectionFailedGeneric => '连接失败'; + // Translations + @override + String searchFailed({required Object error}) => '搜索失败: ${error}'; + @override + String connectionTimeout({required Object context}) => '加载 ${context} 时连接超时'; + @override + String get connectionFailed => '无法连接到 Plex 服务器'; + @override + String failedToLoad({required Object context, required Object error}) => + '无法加载 ${context}: ${error}'; + @override + String get noClientAvailable => '没有可用客户端'; + @override + String authenticationFailed({required Object error}) => '验证失败: ${error}'; + @override + String get couldNotLaunchUrl => '无法打开授权 URL'; + @override + String get pleaseEnterToken => '请输入一个令牌'; + @override + String get invalidToken => '令牌无效'; + @override + String failedToVerifyToken({required Object error}) => '无法验证令牌: ${error}'; + @override + String failedToSwitchProfile({required Object displayName}) => + '无法切换到 ${displayName}'; + @override + String get connectionFailedGeneric => '连接失败'; } // Path: libraries class _StringsLibrariesZh implements _StringsLibrariesEn { - _StringsLibrariesZh._(this._root); + _StringsLibrariesZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '媒体库'; - @override String get scanLibraryFiles => '扫描媒体库文件'; - @override String get scanLibrary => '扫描媒体库'; - @override String get analyze => '分析'; - @override String get analyzeLibrary => '分析媒体库'; - @override String get refreshMetadata => '刷新元数据'; - @override String get emptyTrash => '清空回收站'; - @override String emptyingTrash({required Object title}) => '正在清空 “${title}” 的回收站...'; - @override String trashEmptied({required Object title}) => '已清空 “${title}” 的回收站'; - @override String failedToEmptyTrash({required Object error}) => '无法清空回收站: ${error}'; - @override String analyzing({required Object title}) => '正在分析 “${title}”...'; - @override String analysisStarted({required Object title}) => '已开始分析 “${title}”'; - @override String failedToAnalyze({required Object error}) => '无法分析媒体库: ${error}'; - @override String get noLibrariesFound => '未找到媒体库'; - @override String get thisLibraryIsEmpty => '此媒体库为空'; - @override String get all => '全部'; - @override String get clearAll => '全部清除'; - @override String scanLibraryConfirm({required Object title}) => '确定要扫描 “${title}” 吗?'; - @override String analyzeLibraryConfirm({required Object title}) => '确定要分析 “${title}” 吗?'; - @override String refreshMetadataConfirm({required Object title}) => '确定要刷新 “${title}” 的元数据吗?'; - @override String emptyTrashConfirm({required Object title}) => '确定要清空 “${title}” 的回收站吗?'; - @override String get manageLibraries => '管理媒体库'; - @override String get sort => '排序'; - @override String get sortBy => '排序依据'; - @override String get filters => '筛选器'; - @override String loadingLibraryWithCount({required Object count}) => '正在加载媒体库... (已加载 ${count} 个项目)'; - @override String get confirmActionMessage => '确定要执行此操作吗?'; - @override String get showLibrary => '显示媒体库'; - @override String get hideLibrary => '隐藏媒体库'; - @override String get libraryOptions => '媒体库选项'; - @override String get content => '媒体库内容'; - @override String get selectLibrary => '选择媒体库'; - @override String filtersWithCount({required Object count}) => '筛选器(${count})'; - @override String get noRecommendations => '暂无推荐'; - @override String get noCollections => '此媒体库中没有合集'; - @override String get noFoldersFound => '未找到文件夹'; - @override String get folders => '文件夹'; - @override late final _StringsLibrariesTabsZh tabs = _StringsLibrariesTabsZh._(_root); - @override late final _StringsLibrariesGroupingsZh groupings = _StringsLibrariesGroupingsZh._(_root); + // Translations + @override + String get title => '媒体库'; + @override + String get scanLibraryFiles => '扫描媒体库文件'; + @override + String get scanLibrary => '扫描媒体库'; + @override + String get analyze => '分析'; + @override + String get analyzeLibrary => '分析媒体库'; + @override + String get refreshMetadata => '刷新元数据'; + @override + String get emptyTrash => '清空回收站'; + @override + String emptyingTrash({required Object title}) => '正在清空 “${title}” 的回收站...'; + @override + String trashEmptied({required Object title}) => '已清空 “${title}” 的回收站'; + @override + String failedToEmptyTrash({required Object error}) => '无法清空回收站: ${error}'; + @override + String analyzing({required Object title}) => '正在分析 “${title}”...'; + @override + String analysisStarted({required Object title}) => '已开始分析 “${title}”'; + @override + String failedToAnalyze({required Object error}) => '无法分析媒体库: ${error}'; + @override + String get noLibrariesFound => '未找到媒体库'; + @override + String get thisLibraryIsEmpty => '此媒体库为空'; + @override + String get all => '全部'; + @override + String get clearAll => '全部清除'; + @override + String scanLibraryConfirm({required Object title}) => '确定要扫描 “${title}” 吗?'; + @override + String analyzeLibraryConfirm({required Object title}) => + '确定要分析 “${title}” 吗?'; + @override + String refreshMetadataConfirm({required Object title}) => + '确定要刷新 “${title}” 的元数据吗?'; + @override + String emptyTrashConfirm({required Object title}) => + '确定要清空 “${title}” 的回收站吗?'; + @override + String get manageLibraries => '管理媒体库'; + @override + String get sort => '排序'; + @override + String get sortBy => '排序依据'; + @override + String get filters => '筛选器'; + @override + String loadingLibraryWithCount({required Object count}) => + '正在加载媒体库... (已加载 ${count} 个项目)'; + @override + String get confirmActionMessage => '确定要执行此操作吗?'; + @override + String get showLibrary => '显示媒体库'; + @override + String get hideLibrary => '隐藏媒体库'; + @override + String get libraryOptions => '媒体库选项'; + @override + String get content => '媒体库内容'; + @override + String get selectLibrary => '选择媒体库'; + @override + String filtersWithCount({required Object count}) => '筛选器(${count})'; + @override + String get noRecommendations => '暂无推荐'; + @override + String get noCollections => '此媒体库中没有合集'; + @override + String get noFoldersFound => '未找到文件夹'; + @override + String get folders => '文件夹'; + @override + late final _StringsLibrariesTabsZh tabs = _StringsLibrariesTabsZh._(_root); + @override + late final _StringsLibrariesGroupingsZh groupings = + _StringsLibrariesGroupingsZh._(_root); } // Path: about class _StringsAboutZh implements _StringsAboutEn { - _StringsAboutZh._(this._root); + _StringsAboutZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '关于'; - @override String get openSourceLicenses => '开源许可证'; - @override String versionLabel({required Object version}) => '版本 ${version}'; - @override String get appDescription => '一款精美的 Flutter Plex 客户端'; - @override String get viewLicensesDescription => '查看第三方库的许可证'; + // Translations + @override + String get title => '关于'; + @override + String get openSourceLicenses => '开源许可证'; + @override + String versionLabel({required Object version}) => '版本 ${version}'; + @override + String get appDescription => '一款精美的 Flutter Plex 客户端'; + @override + String get viewLicensesDescription => '查看第三方库的许可证'; } // Path: serverSelection class _StringsServerSelectionZh implements _StringsServerSelectionEn { - _StringsServerSelectionZh._(this._root); + _StringsServerSelectionZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get connectingToServer => '正在连接服务器...'; - @override String get connectingToAllServers => '正在连接所有服务器...'; - @override String get connectToAllServers => '连接所有服务器'; - @override String get allServerConnectionsFailed => '无法连接到任何服务器。请检查你的网络并重试。'; - @override String foundServers({required Object count}) => '找到 ${count} 个服务器'; - @override String get orSelectIndividually => '或单独选择一个服务器:'; - @override String get serverDebugCopied => '服务器调试数据已复制到剪贴板'; - @override String get copyDebugData => '复制调试数据'; - @override String get noServersFound => '未找到服务器'; - @override String malformedServerData({required Object count}) => '找到 ${count} 个数据格式错误的服务器。没有可用的有效服务器。'; - @override String get incompleteServerInfo => '某些服务器信息不完整,已被跳过。请检查你的 Plex.tv 账户。'; - @override String get incompleteConnectionInfo => '服务器连接信息不完整。请重试。'; - @override String malformedServerInfo({required Object message}) => '服务器信息格式错误: ${message}'; - @override String get networkConnectionFailed => '网络连接失败。请检查你的网络连接并重试。'; - @override String get authenticationFailed => '验证失败。请重新登录。'; - @override String get plexServiceUnavailable => 'Plex 服务不可用。请稍后重试。'; - @override String failedToLoadServers({required Object error}) => '无法加载服务器: ${error}'; + // Translations + @override + String get connectingToServer => '正在连接服务器...'; + @override + String get connectingToAllServers => '正在连接所有服务器...'; + @override + String get connectToAllServers => '连接所有服务器'; + @override + String get allServerConnectionsFailed => '无法连接到任何服务器。请检查你的网络并重试。'; + @override + String foundServers({required Object count}) => '找到 ${count} 个服务器'; + @override + String get orSelectIndividually => '或单独选择一个服务器:'; + @override + String get serverDebugCopied => '服务器调试数据已复制到剪贴板'; + @override + String get copyDebugData => '复制调试数据'; + @override + String get noServersFound => '未找到服务器'; + @override + String malformedServerData({required Object count}) => + '找到 ${count} 个数据格式错误的服务器。没有可用的有效服务器。'; + @override + String get incompleteServerInfo => '某些服务器信息不完整,已被跳过。请检查你的 Plex.tv 账户。'; + @override + String get incompleteConnectionInfo => '服务器连接信息不完整。请重试。'; + @override + String malformedServerInfo({required Object message}) => + '服务器信息格式错误: ${message}'; + @override + String get networkConnectionFailed => '网络连接失败。请检查你的网络连接并重试。'; + @override + String get authenticationFailed => '验证失败。请重新登录。'; + @override + String get plexServiceUnavailable => 'Plex 服务不可用。请稍后重试。'; + @override + String failedToLoadServers({required Object error}) => '无法加载服务器: ${error}'; } // Path: hubDetail class _StringsHubDetailZh implements _StringsHubDetailEn { - _StringsHubDetailZh._(this._root); + _StringsHubDetailZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '标题'; - @override String get releaseYear => '发行年份'; - @override String get dateAdded => '添加日期'; - @override String get rating => '评分'; - @override String get noItemsFound => '未找到项目'; + // Translations + @override + String get title => '标题'; + @override + String get releaseYear => '发行年份'; + @override + String get dateAdded => '添加日期'; + @override + String get rating => '评分'; + @override + String get noItemsFound => '未找到项目'; } // Path: logs class _StringsLogsZh implements _StringsLogsEn { - _StringsLogsZh._(this._root); + _StringsLogsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '日志'; - @override String get clearLogs => '清除日志'; - @override String get copyLogs => '复制日志'; - @override String get exportLogs => '导出日志'; - @override String get noLogsToShow => '没有可显示的日志'; - @override String get error => '错误:'; - @override String get stackTrace => '堆栈跟踪 (Stack Trace):'; + // Translations + @override + String get title => '日志'; + @override + String get clearLogs => '清除日志'; + @override + String get copyLogs => '复制日志'; + @override + String get exportLogs => '导出日志'; + @override + String get noLogsToShow => '没有可显示的日志'; + @override + String get error => '错误:'; + @override + String get stackTrace => '堆栈跟踪 (Stack Trace):'; } // Path: licenses class _StringsLicensesZh implements _StringsLicensesEn { - _StringsLicensesZh._(this._root); + _StringsLicensesZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get relatedPackages => '相关软件包'; - @override String get license => '许可证'; - @override String licenseNumber({required Object number}) => '许可证 ${number}'; - @override String licensesCount({required Object count}) => '${count} 个许可证'; + // Translations + @override + String get relatedPackages => '相关软件包'; + @override + String get license => '许可证'; + @override + String licenseNumber({required Object number}) => '许可证 ${number}'; + @override + String licensesCount({required Object count}) => '${count} 个许可证'; } // Path: navigation class _StringsNavigationZh implements _StringsNavigationEn { - _StringsNavigationZh._(this._root); + _StringsNavigationZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get home => '主页'; - @override String get search => '搜索'; - @override String get libraries => '媒体库'; - @override String get settings => '设置'; + // Translations + @override + String get home => '主页'; + @override + String get search => '搜索'; + @override + String get libraries => '媒体库'; + @override + String get settings => '设置'; } // Path: playlists class _StringsPlaylistsZh implements _StringsPlaylistsEn { - _StringsPlaylistsZh._(this._root); + _StringsPlaylistsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '播放列表'; - @override String get noPlaylists => '未找到播放列表'; - @override String get create => '创建播放列表'; - @override String get newPlaylist => '新播放列表'; - @override String get playlistName => '播放列表名称'; - @override String get enterPlaylistName => '输入播放列表名称'; - @override String get edit => '编辑播放列表'; - @override String get delete => '删除播放列表'; - @override String get addTo => '添加到播放列表'; - @override String get addItems => '添加项目'; - @override String get removeItem => '从播放列表中移除'; - @override String get clearPlaylist => '清空播放列表'; - @override String get playAll => '全部播放'; - @override String get shuffle => '随机播放'; - @override String get smartPlaylist => '智能播放列表'; - @override String get regularPlaylist => '普通播放列表'; - @override String itemCount({required Object count}) => '${count} 个项目'; - @override String get oneItem => '1 个项目'; - @override String get emptyPlaylist => '此播放列表为空'; - @override String get deleteConfirm => '删除播放列表?'; - @override String deleteMessage({required Object name}) => '确定要删除 "${name}" 吗?'; - @override String get created => '播放列表已创建'; - @override String get updated => '播放列表已更新'; - @override String get deleted => '播放列表已删除'; - @override String get itemAdded => '已添加到播放列表'; - @override String get itemRemoved => '已从播放列表中移除'; - @override String get selectPlaylist => '选择播放列表'; - @override String get createNewPlaylist => '创建新播放列表'; - @override String get errorCreating => '创建播放列表失败'; - @override String get errorDeleting => '删除播放列表失败'; - @override String get errorLoading => '加载播放列表失败'; - @override String get errorAdding => '添加到播放列表失败'; - @override String get errorReordering => '重新排序播放列表项目失败'; - @override String get errorRemoving => '从播放列表中移除失败'; - @override String get playlist => '播放列表'; + // Translations + @override + String get title => '播放列表'; + @override + String get noPlaylists => '未找到播放列表'; + @override + String get create => '创建播放列表'; + @override + String get newPlaylist => '新播放列表'; + @override + String get playlistName => '播放列表名称'; + @override + String get enterPlaylistName => '输入播放列表名称'; + @override + String get edit => '编辑播放列表'; + @override + String get delete => '删除播放列表'; + @override + String get addTo => '添加到播放列表'; + @override + String get addItems => '添加项目'; + @override + String get removeItem => '从播放列表中移除'; + @override + String get clearPlaylist => '清空播放列表'; + @override + String get playAll => '全部播放'; + @override + String get shuffle => '随机播放'; + @override + String get smartPlaylist => '智能播放列表'; + @override + String get regularPlaylist => '普通播放列表'; + @override + String itemCount({required Object count}) => '${count} 个项目'; + @override + String get oneItem => '1 个项目'; + @override + String get emptyPlaylist => '此播放列表为空'; + @override + String get deleteConfirm => '删除播放列表?'; + @override + String deleteMessage({required Object name}) => '确定要删除 "${name}" 吗?'; + @override + String get created => '播放列表已创建'; + @override + String get updated => '播放列表已更新'; + @override + String get deleted => '播放列表已删除'; + @override + String get itemAdded => '已添加到播放列表'; + @override + String get itemRemoved => '已从播放列表中移除'; + @override + String get selectPlaylist => '选择播放列表'; + @override + String get createNewPlaylist => '创建新播放列表'; + @override + String get errorCreating => '创建播放列表失败'; + @override + String get errorDeleting => '删除播放列表失败'; + @override + String get errorLoading => '加载播放列表失败'; + @override + String get errorAdding => '添加到播放列表失败'; + @override + String get errorReordering => '重新排序播放列表项目失败'; + @override + String get errorRemoving => '从播放列表中移除失败'; + @override + String get playlist => '播放列表'; } // Path: collections class _StringsCollectionsZh implements _StringsCollectionsEn { - _StringsCollectionsZh._(this._root); + _StringsCollectionsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get title => '合集'; - @override String get collection => '合集'; - @override String get empty => '合集为空'; - @override String get noItems => '此合集没有项目'; - @override String get unknownLibrarySection => '无法删除:未知的媒体库分区'; - @override String get deleteCollection => '删除合集'; - @override String deleteConfirm({required Object title}) => '确定要删除"${title}"吗?此操作无法撤销。'; - @override String get deleted => '已删除合集'; - @override String get deleteFailed => '删除合集失败'; - @override String deleteFailedWithError({required Object error}) => '删除合集失败:${error}'; - @override String failedToLoadItems({required Object error}) => '加载合集项目失败:${error}'; - @override String get addTo => '添加到合集'; - @override String get selectCollection => '选择合集'; - @override String get createNewCollection => '创建新合集'; - @override String get collectionName => '合集名称'; - @override String get enterCollectionName => '输入合集名称'; - @override String get addedToCollection => '已添加到合集'; - @override String get errorAddingToCollection => '添加到合集失败'; - @override String get created => '已创建合集'; - @override String get removeFromCollection => '从合集移除'; - @override String removeFromCollectionConfirm({required Object title}) => '将“${title}”从此合集移除?'; - @override String get removedFromCollection => '已从合集移除'; - @override String get removeFromCollectionFailed => '从合集移除失败'; - @override String removeFromCollectionError({required Object error}) => '从合集移除时出错:${error}'; + // Translations + @override + String get title => '合集'; + @override + String get collection => '合集'; + @override + String get empty => '合集为空'; + @override + String get noItems => '此合集没有项目'; + @override + String get unknownLibrarySection => '无法删除:未知的媒体库分区'; + @override + String get deleteCollection => '删除合集'; + @override + String deleteConfirm({required Object title}) => '确定要删除"${title}"吗?此操作无法撤销。'; + @override + String get deleted => '已删除合集'; + @override + String get deleteFailed => '删除合集失败'; + @override + String deleteFailedWithError({required Object error}) => '删除合集失败:${error}'; + @override + String failedToLoadItems({required Object error}) => '加载合集项目失败:${error}'; + @override + String get addTo => '添加到合集'; + @override + String get selectCollection => '选择合集'; + @override + String get createNewCollection => '创建新合集'; + @override + String get collectionName => '合集名称'; + @override + String get enterCollectionName => '输入合集名称'; + @override + String get addedToCollection => '已添加到合集'; + @override + String get errorAddingToCollection => '添加到合集失败'; + @override + String get created => '已创建合集'; + @override + String get removeFromCollection => '从合集移除'; + @override + String removeFromCollectionConfirm({required Object title}) => + '将“${title}”从此合集移除?'; + @override + String get removedFromCollection => '已从合集移除'; + @override + String get removeFromCollectionFailed => '从合集移除失败'; + @override + String removeFromCollectionError({required Object error}) => + '从合集移除时出错:${error}'; } // Path: libraries.tabs class _StringsLibrariesTabsZh implements _StringsLibrariesTabsEn { - _StringsLibrariesTabsZh._(this._root); + _StringsLibrariesTabsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get recommended => '推荐'; - @override String get browse => '浏览'; - @override String get collections => '合集'; - @override String get playlists => '播放列表'; + // Translations + @override + String get recommended => '推荐'; + @override + String get browse => '浏览'; + @override + String get collections => '合集'; + @override + String get playlists => '播放列表'; } // Path: libraries.groupings class _StringsLibrariesGroupingsZh implements _StringsLibrariesGroupingsEn { - _StringsLibrariesGroupingsZh._(this._root); + _StringsLibrariesGroupingsZh._(this._root); - @override final _StringsZh _root; // ignore: unused_field + @override + final _StringsZh _root; // ignore: unused_field - // Translations - @override String get all => '全部'; - @override String get movies => '电影'; - @override String get shows => '剧集'; - @override String get seasons => '季'; - @override String get episodes => '集'; - @override String get folders => '文件夹'; + // Translations + @override + String get all => '全部'; + @override + String get movies => '电影'; + @override + String get shows => '剧集'; + @override + String get seasons => '季'; + @override + String get episodes => '集'; + @override + String get folders => '文件夹'; } /// Flat map(s) containing all translations. /// Only for edge cases! For simple maps, use the map function of this library. extension on Translations { - dynamic _flatMapFunction(String path) { - switch (path) { - case 'app.title': return 'Plezy'; - case 'app.loading': return 'Loading...'; - case 'auth.signInWithPlex': return 'Sign in with Plex'; - case 'auth.showQRCode': return 'Show QR Code'; - case 'auth.cancel': return 'Cancel'; - case 'auth.authenticate': return 'Authenticate'; - case 'auth.retry': return 'Retry'; - case 'auth.debugEnterToken': return 'Debug: Enter Plex Token'; - case 'auth.plexTokenLabel': return 'Plex Auth Token'; - case 'auth.plexTokenHint': return 'Enter your Plex.tv token'; - case 'auth.authenticationTimeout': return 'Authentication timed out. Please try again.'; - case 'auth.scanQRCodeInstruction': return 'Scan this QR code with a device logged into Plex to authenticate.'; - case 'auth.waitingForAuth': return 'Waiting for authentication...\nPlease complete sign-in in your browser.'; - case 'common.cancel': return 'Cancel'; - case 'common.save': return 'Save'; - case 'common.close': return 'Close'; - case 'common.clear': return 'Clear'; - case 'common.reset': return 'Reset'; - case 'common.later': return 'Later'; - case 'common.submit': return 'Submit'; - case 'common.confirm': return 'Confirm'; - case 'common.retry': return 'Retry'; - case 'common.playNow': return 'Play Now'; - case 'common.logout': return 'Logout'; - case 'common.online': return 'Online'; - case 'common.offline': return 'Offline'; - case 'common.owned': return 'Owned'; - case 'common.shared': return 'Shared'; - case 'common.current': return 'CURRENT'; - case 'common.unknown': return 'Unknown'; - case 'common.refresh': return 'Refresh'; - case 'common.yes': return 'Yes'; - case 'common.no': return 'No'; - case 'common.server': return 'Server'; - case 'common.delete': return 'Delete'; - case 'common.shuffle': return 'Shuffle'; - case 'common.addTo': return 'Add to...'; - case 'screens.licenses': return 'Licenses'; - case 'screens.switchProfile': return 'Switch Profile'; - case 'screens.subtitleStyling': return 'Subtitle Styling'; - case 'screens.search': return 'Search'; - case 'screens.logs': return 'Logs'; - case 'update.available': return 'Update Available'; - case 'update.versionAvailable': return ({required Object version}) => 'Version ${version} is available'; - case 'update.currentVersion': return ({required Object version}) => 'Current: ${version}'; - case 'update.skipVersion': return 'Skip This Version'; - case 'update.viewRelease': return 'View Release'; - case 'update.latestVersion': return 'You are on the latest version'; - case 'update.checkFailed': return 'Failed to check for updates'; - case 'settings.title': return 'Settings'; - case 'settings.language': return 'Language'; - case 'settings.theme': return 'Theme'; - case 'settings.appearance': return 'Appearance'; - case 'settings.videoPlayback': return 'Video Playback'; - case 'settings.shufflePlay': return 'Shuffle Play'; - case 'settings.advanced': return 'Advanced'; - case 'settings.useSeasonPostersDescription': return 'Show season poster instead of series poster for episodes'; - case 'settings.showHeroSectionDescription': return 'Display featured content carousel on home screen'; - case 'settings.secondsLabel': return 'Seconds'; - case 'settings.minutesLabel': return 'Minutes'; - case 'settings.secondsShort': return 's'; - case 'settings.minutesShort': return 'm'; - case 'settings.durationHint': return ({required Object min, required Object max}) => 'Enter duration (${min}-${max})'; - case 'settings.systemTheme': return 'System'; - case 'settings.systemThemeDescription': return 'Follow system settings'; - case 'settings.lightTheme': return 'Light'; - case 'settings.darkTheme': return 'Dark'; - case 'settings.libraryDensity': return 'Library Density'; - case 'settings.compact': return 'Compact'; - case 'settings.compactDescription': return 'Smaller cards, more items visible'; - case 'settings.normal': return 'Normal'; - case 'settings.normalDescription': return 'Default size'; - case 'settings.comfortable': return 'Comfortable'; - case 'settings.comfortableDescription': return 'Larger cards, fewer items visible'; - case 'settings.viewMode': return 'View Mode'; - case 'settings.gridView': return 'Grid'; - case 'settings.gridViewDescription': return 'Display items in a grid layout'; - case 'settings.listView': return 'List'; - case 'settings.listViewDescription': return 'Display items in a list layout'; - case 'settings.useSeasonPosters': return 'Use Season Posters'; - case 'settings.showHeroSection': return 'Show Hero Section'; - case 'settings.hardwareDecoding': return 'Hardware Decoding'; - case 'settings.hardwareDecodingDescription': return 'Use hardware acceleration when available'; - case 'settings.bufferSize': return 'Buffer Size'; - case 'settings.bufferSizeMB': return ({required Object size}) => '${size}MB'; - case 'settings.subtitleStyling': return 'Subtitle Styling'; - case 'settings.subtitleStylingDescription': return 'Customize subtitle appearance'; - case 'settings.smallSkipDuration': return 'Small Skip Duration'; - case 'settings.largeSkipDuration': return 'Large Skip Duration'; - case 'settings.secondsUnit': return ({required Object seconds}) => '${seconds} seconds'; - case 'settings.defaultSleepTimer': return 'Default Sleep Timer'; - case 'settings.minutesUnit': return ({required Object minutes}) => '${minutes} minutes'; - case 'settings.rememberTrackSelections': return 'Remember track selections per show/movie'; - case 'settings.rememberTrackSelectionsDescription': return 'Automatically save audio and subtitle language preferences when you change tracks during playback'; - case 'settings.unwatchedOnly': return 'Unwatched Only'; - case 'settings.unwatchedOnlyDescription': return 'Only include unwatched episodes in shuffle queue'; - case 'settings.shuffleOrderNavigation': return 'Shuffle Order Navigation'; - case 'settings.shuffleOrderNavigationDescription': return 'Next/previous buttons follow shuffled order'; - case 'settings.loopShuffleQueue': return 'Loop Shuffle Queue'; - case 'settings.loopShuffleQueueDescription': return 'Restart queue when reaching the end'; - case 'settings.videoPlayerControls': return 'Video Player Controls'; - case 'settings.keyboardShortcuts': return 'Keyboard Shortcuts'; - case 'settings.keyboardShortcutsDescription': return 'Customize keyboard shortcuts'; - case 'settings.debugLogging': return 'Debug Logging'; - case 'settings.debugLoggingDescription': return 'Enable detailed logging for troubleshooting'; - case 'settings.viewLogs': return 'View Logs'; - case 'settings.viewLogsDescription': return 'View application logs'; - case 'settings.clearCache': return 'Clear Cache'; - case 'settings.clearCacheDescription': return 'This will clear all cached images and data. The app may take longer to load content after clearing the cache.'; - case 'settings.clearCacheSuccess': return 'Cache cleared successfully'; - case 'settings.resetSettings': return 'Reset Settings'; - case 'settings.resetSettingsDescription': return 'This will reset all settings to their default values. This action cannot be undone.'; - case 'settings.resetSettingsSuccess': return 'Settings reset successfully'; - case 'settings.shortcutsReset': return 'Shortcuts reset to defaults'; - case 'settings.about': return 'About'; - case 'settings.aboutDescription': return 'App information and licenses'; - case 'settings.updates': return 'Updates'; - case 'settings.updateAvailable': return 'Update Available'; - case 'settings.checkForUpdates': return 'Check for Updates'; - case 'settings.validationErrorEnterNumber': return 'Please enter a valid number'; - case 'settings.validationErrorDuration': return ({required Object min, required Object max, required Object unit}) => 'Duration must be between ${min} and ${max} ${unit}'; - case 'settings.shortcutAlreadyAssigned': return ({required Object action}) => 'Shortcut already assigned to ${action}'; - case 'settings.shortcutUpdated': return ({required Object action}) => 'Shortcut updated for ${action}'; - case 'search.hint': return 'Search movies, shows, music...'; - case 'search.tryDifferentTerm': return 'Try a different search term'; - case 'search.searchYourMedia': return 'Search your media'; - case 'search.enterTitleActorOrKeyword': return 'Enter a title, actor, or keyword'; - case 'hotkeys.setShortcutFor': return ({required Object actionName}) => 'Set Shortcut for ${actionName}'; - case 'hotkeys.clearShortcut': return 'Clear shortcut'; - case 'pinEntry.enterPin': return 'Enter PIN'; - case 'pinEntry.showPin': return 'Show PIN'; - case 'pinEntry.hidePin': return 'Hide PIN'; - case 'fileInfo.title': return 'File Info'; - case 'fileInfo.video': return 'Video'; - case 'fileInfo.audio': return 'Audio'; - case 'fileInfo.file': return 'File'; - case 'fileInfo.advanced': return 'Advanced'; - case 'fileInfo.codec': return 'Codec'; - case 'fileInfo.resolution': return 'Resolution'; - case 'fileInfo.bitrate': return 'Bitrate'; - case 'fileInfo.frameRate': return 'Frame Rate'; - case 'fileInfo.aspectRatio': return 'Aspect Ratio'; - case 'fileInfo.profile': return 'Profile'; - case 'fileInfo.bitDepth': return 'Bit Depth'; - case 'fileInfo.colorSpace': return 'Color Space'; - case 'fileInfo.colorRange': return 'Color Range'; - case 'fileInfo.colorPrimaries': return 'Color Primaries'; - case 'fileInfo.chromaSubsampling': return 'Chroma Subsampling'; - case 'fileInfo.channels': return 'Channels'; - case 'fileInfo.path': return 'Path'; - case 'fileInfo.size': return 'Size'; - case 'fileInfo.container': return 'Container'; - case 'fileInfo.duration': return 'Duration'; - case 'fileInfo.optimizedForStreaming': return 'Optimized for Streaming'; - case 'fileInfo.has64bitOffsets': return '64-bit Offsets'; - case 'mediaMenu.markAsWatched': return 'Mark as Watched'; - case 'mediaMenu.markAsUnwatched': return 'Mark as Unwatched'; - case 'mediaMenu.removeFromContinueWatching': return 'Remove from Continue Watching'; - case 'mediaMenu.goToSeries': return 'Go to series'; - case 'mediaMenu.goToSeason': return 'Go to season'; - case 'mediaMenu.shufflePlay': return 'Shuffle Play'; - case 'mediaMenu.fileInfo': return 'File Info'; - case 'accessibility.mediaCardMovie': return ({required Object title}) => '${title}, movie'; - case 'accessibility.mediaCardShow': return ({required Object title}) => '${title}, TV show'; - case 'accessibility.mediaCardEpisode': return ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - case 'accessibility.mediaCardSeason': return ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - case 'accessibility.mediaCardWatched': return 'watched'; - case 'accessibility.mediaCardPartiallyWatched': return ({required Object percent}) => '${percent} percent watched'; - case 'accessibility.mediaCardUnwatched': return 'unwatched'; - case 'accessibility.tapToPlay': return 'Tap to play'; - case 'tooltips.shufflePlay': return 'Shuffle play'; - case 'tooltips.markAsWatched': return 'Mark as watched'; - case 'tooltips.markAsUnwatched': return 'Mark as unwatched'; - case 'videoControls.audioLabel': return 'Audio'; - case 'videoControls.subtitlesLabel': return 'Subtitles'; - case 'videoControls.resetToZero': return 'Reset to 0ms'; - case 'videoControls.addTime': return ({required Object amount, required Object unit}) => '+${amount}${unit}'; - case 'videoControls.minusTime': return ({required Object amount, required Object unit}) => '-${amount}${unit}'; - case 'videoControls.playsLater': return ({required Object label}) => '${label} plays later'; - case 'videoControls.playsEarlier': return ({required Object label}) => '${label} plays earlier'; - case 'videoControls.noOffset': return 'No offset'; - case 'videoControls.letterbox': return 'Letterbox'; - case 'videoControls.fillScreen': return 'Fill screen'; - case 'videoControls.stretch': return 'Stretch'; - case 'videoControls.lockRotation': return 'Lock rotation'; - case 'videoControls.unlockRotation': return 'Unlock rotation'; - case 'videoControls.sleepTimer': return 'Sleep Timer'; - case 'videoControls.timerActive': return 'Timer Active'; - case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Playback will pause in ${duration}'; - case 'videoControls.sleepTimerCompleted': return 'Sleep timer completed - playback paused'; - case 'videoControls.playButton': return 'Play'; - case 'videoControls.pauseButton': return 'Pause'; - case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Seek backward ${seconds} seconds'; - case 'videoControls.seekForwardButton': return ({required Object seconds}) => 'Seek forward ${seconds} seconds'; - case 'videoControls.previousButton': return 'Previous episode'; - case 'videoControls.nextButton': return 'Next episode'; - case 'videoControls.previousChapterButton': return 'Previous chapter'; - case 'videoControls.nextChapterButton': return 'Next chapter'; - case 'videoControls.volumeButton': return 'Volume'; - case 'videoControls.muteButton': return 'Mute'; - case 'videoControls.unmuteButton': return 'Unmute'; - case 'videoControls.settingsButton': return 'Video settings'; - case 'videoControls.audioTrackButton': return 'Audio tracks'; - case 'videoControls.subtitlesButton': return 'Subtitles'; - case 'videoControls.chaptersButton': return 'Chapters'; - case 'videoControls.versionsButton': return 'Video versions'; - case 'videoControls.aspectRatioButton': return 'Aspect ratio'; - case 'videoControls.fullscreenButton': return 'Enter fullscreen'; - case 'videoControls.exitFullscreenButton': return 'Exit fullscreen'; - case 'videoControls.rotationLockButton': return 'Rotation lock'; - case 'videoControls.timelineSlider': return 'Video timeline'; - case 'videoControls.volumeSlider': return 'Volume level'; - case 'videoControls.backButton': return 'Back'; - case 'userStatus.admin': return 'Admin'; - case 'userStatus.restricted': return 'Restricted'; - case 'userStatus.protected': return 'Protected'; - case 'userStatus.current': return 'CURRENT'; - case 'messages.markedAsWatched': return 'Marked as watched'; - case 'messages.markedAsUnwatched': return 'Marked as unwatched'; - case 'messages.removedFromContinueWatching': return 'Removed from Continue Watching'; - case 'messages.errorLoading': return ({required Object error}) => 'Error: ${error}'; - case 'messages.fileInfoNotAvailable': return 'File information not available'; - case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Error loading file info: ${error}'; - case 'messages.errorLoadingSeries': return 'Error loading series'; - case 'messages.errorLoadingSeason': return 'Error loading season'; - case 'messages.musicNotSupported': return 'Music playback is not yet supported'; - case 'messages.logsCleared': return 'Logs cleared'; - case 'messages.logsCopied': return 'Logs copied to clipboard'; - case 'messages.noLogsAvailable': return 'No logs available'; - case 'messages.libraryScanning': return ({required Object title}) => 'Scanning "${title}"...'; - case 'messages.libraryScanStarted': return ({required Object title}) => 'Library scan started for "${title}"'; - case 'messages.libraryScanFailed': return ({required Object error}) => 'Failed to scan library: ${error}'; - case 'messages.metadataRefreshing': return ({required Object title}) => 'Refreshing metadata for "${title}"...'; - case 'messages.metadataRefreshStarted': return ({required Object title}) => 'Metadata refresh started for "${title}"'; - case 'messages.metadataRefreshFailed': return ({required Object error}) => 'Failed to refresh metadata: ${error}'; - case 'messages.noPlexToken': return 'No Plex token found. Please login again.'; - case 'messages.logoutConfirm': return 'Are you sure you want to logout?'; - case 'messages.noSeasonsFound': return 'No seasons found'; - case 'messages.noEpisodesFound': return 'No episodes found in first season'; - case 'messages.noEpisodesFoundGeneral': return 'No episodes found'; - case 'messages.noResultsFound': return 'No results found'; - case 'messages.sleepTimerSet': return ({required Object label}) => 'Sleep timer set for ${label}'; - case 'messages.failedToSwitchProfile': return ({required Object displayName}) => 'Failed to switch to ${displayName}'; - case 'messages.noItemsAvailable': return 'No items available'; - case 'messages.failedToCreatePlayQueue': return 'Failed to create play queue'; - case 'messages.failedToCreatePlayQueueNoItems': return 'Failed to create play queue - no items'; - case 'messages.failedPlayback': return ({required Object action, required Object error}) => 'Failed to ${action}: ${error}'; - case 'profile.noUsersAvailable': return 'No users available'; - case 'subtitlingStyling.stylingOptions': return 'Styling Options'; - case 'subtitlingStyling.fontSize': return 'Font Size'; - case 'subtitlingStyling.textColor': return 'Text Color'; - case 'subtitlingStyling.borderSize': return 'Border Size'; - case 'subtitlingStyling.borderColor': return 'Border Color'; - case 'subtitlingStyling.backgroundOpacity': return 'Background Opacity'; - case 'subtitlingStyling.backgroundColor': return 'Background Color'; - case 'dialog.confirmAction': return 'Confirm Action'; - case 'dialog.areYouSure': return 'Are you sure you want to perform this action?'; - case 'dialog.cancel': return 'Cancel'; - case 'dialog.playNow': return 'Play Now'; - case 'discover.title': return 'Discover'; - case 'discover.switchProfile': return 'Switch Profile'; - case 'discover.logout': return 'Logout'; - case 'discover.noContentAvailable': return 'No content available'; - case 'discover.addMediaToLibraries': return 'Add some media to your libraries'; - case 'discover.continueWatching': return 'Continue Watching'; - case 'discover.recentlyAdded': return 'Recently Added'; - case 'discover.play': return 'Play'; - case 'discover.resume': return 'Resume'; - case 'discover.playEpisode': return ({required Object season, required Object episode}) => 'Play S${season}, E${episode}'; - case 'discover.resumeEpisode': return ({required Object season, required Object episode}) => 'Resume S${season}, E${episode}'; - case 'discover.pause': return 'Pause'; - case 'discover.overview': return 'Overview'; - case 'discover.cast': return 'Cast'; - case 'discover.seasons': return 'Seasons'; - case 'discover.studio': return 'Studio'; - case 'discover.rating': return 'Rating'; - case 'discover.watched': return 'Watched'; - case 'discover.episodeCount': return ({required Object count}) => '${count} episodes'; - case 'discover.watchedProgress': return ({required Object watched, required Object total}) => '${watched}/${total} watched'; - case 'discover.movie': return 'Movie'; - case 'discover.tvShow': return 'TV Show'; - case 'discover.minutesLeft': return ({required Object minutes}) => '${minutes} min left'; - case 'errors.searchFailed': return ({required Object error}) => 'Search failed: ${error}'; - case 'errors.connectionTimeout': return ({required Object context}) => 'Connection timeout while loading ${context}'; - case 'errors.connectionFailed': return 'Unable to connect to Plex server'; - case 'errors.failedToLoad': return ({required Object context, required Object error}) => 'Failed to load ${context}: ${error}'; - case 'errors.noClientAvailable': return 'No client available'; - case 'errors.authenticationFailed': return ({required Object error}) => 'Authentication failed: ${error}'; - case 'errors.couldNotLaunchUrl': return 'Could not launch auth URL'; - case 'errors.pleaseEnterToken': return 'Please enter a token'; - case 'errors.invalidToken': return 'Invalid token'; - case 'errors.failedToVerifyToken': return ({required Object error}) => 'Failed to verify token: ${error}'; - case 'errors.failedToSwitchProfile': return ({required Object displayName}) => 'Failed to switch to ${displayName}'; - case 'errors.connectionFailedGeneric': return 'Connection failed'; - case 'libraries.title': return 'Libraries'; - case 'libraries.scanLibraryFiles': return 'Scan Library Files'; - case 'libraries.scanLibrary': return 'Scan Library'; - case 'libraries.analyze': return 'Analyze'; - case 'libraries.analyzeLibrary': return 'Analyze Library'; - case 'libraries.refreshMetadata': return 'Refresh Metadata'; - case 'libraries.emptyTrash': return 'Empty Trash'; - case 'libraries.emptyingTrash': return ({required Object title}) => 'Emptying trash for "${title}"...'; - case 'libraries.trashEmptied': return ({required Object title}) => 'Trash emptied for "${title}"'; - case 'libraries.failedToEmptyTrash': return ({required Object error}) => 'Failed to empty trash: ${error}'; - case 'libraries.analyzing': return ({required Object title}) => 'Analyzing "${title}"...'; - case 'libraries.analysisStarted': return ({required Object title}) => 'Analysis started for "${title}"'; - case 'libraries.failedToAnalyze': return ({required Object error}) => 'Failed to analyze library: ${error}'; - case 'libraries.noLibrariesFound': return 'No libraries found'; - case 'libraries.thisLibraryIsEmpty': return 'This library is empty'; - case 'libraries.all': return 'All'; - case 'libraries.clearAll': return 'Clear All'; - case 'libraries.scanLibraryConfirm': return ({required Object title}) => 'Are you sure you want to scan "${title}"?'; - case 'libraries.analyzeLibraryConfirm': return ({required Object title}) => 'Are you sure you want to analyze "${title}"?'; - case 'libraries.refreshMetadataConfirm': return ({required Object title}) => 'Are you sure you want to refresh metadata for "${title}"?'; - case 'libraries.emptyTrashConfirm': return ({required Object title}) => 'Are you sure you want to empty trash for "${title}"?'; - case 'libraries.manageLibraries': return 'Manage Libraries'; - case 'libraries.sort': return 'Sort'; - case 'libraries.sortBy': return 'Sort By'; - case 'libraries.filters': return 'Filters'; - case 'libraries.loadingLibraryWithCount': return ({required Object count}) => 'Loading library... (${count} items loaded)'; - case 'libraries.confirmActionMessage': return 'Are you sure you want to perform this action?'; - case 'libraries.showLibrary': return 'Show library'; - case 'libraries.hideLibrary': return 'Hide library'; - case 'libraries.libraryOptions': return 'Library options'; - case 'libraries.content': return 'library content'; - case 'libraries.selectLibrary': return 'Select library'; - case 'libraries.filtersWithCount': return ({required Object count}) => 'Filters (${count})'; - case 'libraries.noRecommendations': return 'No recommendations available'; - case 'libraries.noCollections': return 'No collections in this library'; - case 'libraries.noFoldersFound': return 'No folders found'; - case 'libraries.folders': return 'folders'; - case 'libraries.tabs.recommended': return 'Recommended'; - case 'libraries.tabs.browse': return 'Browse'; - case 'libraries.tabs.collections': return 'Collections'; - case 'libraries.tabs.playlists': return 'Playlists'; - case 'libraries.groupings.all': return 'All'; - case 'libraries.groupings.movies': return 'Movies'; - case 'libraries.groupings.shows': return 'TV Shows'; - case 'libraries.groupings.seasons': return 'Seasons'; - case 'libraries.groupings.episodes': return 'Episodes'; - case 'libraries.groupings.folders': return 'Folders'; - case 'about.title': return 'About'; - case 'about.openSourceLicenses': return 'Open Source Licenses'; - case 'about.versionLabel': return ({required Object version}) => 'Version ${version}'; - case 'about.appDescription': return 'A beautiful Plex client for Flutter'; - case 'about.viewLicensesDescription': return 'View licenses of third-party libraries'; - case 'serverSelection.connectingToServer': return 'Connecting to server...'; - case 'serverSelection.connectingToAllServers': return 'Connecting to all servers...'; - case 'serverSelection.connectToAllServers': return 'Connect to All Servers'; - case 'serverSelection.allServerConnectionsFailed': return 'Failed to connect to any servers. Please check your network and try again.'; - case 'serverSelection.foundServers': return ({required Object count}) => 'Found ${count} server(s)'; - case 'serverSelection.orSelectIndividually': return 'Or select a server individually:'; - case 'serverSelection.serverDebugCopied': return 'Server debug data copied to clipboard'; - case 'serverSelection.copyDebugData': return 'Copy Debug Data'; - case 'serverSelection.noServersFound': return 'No servers found'; - case 'serverSelection.malformedServerData': return ({required Object count}) => 'Found ${count} server(s) with malformed data. No valid servers available.'; - case 'serverSelection.incompleteServerInfo': return 'Some servers have incomplete information and were skipped. Please check your Plex.tv account.'; - case 'serverSelection.incompleteConnectionInfo': return 'Server connection information is incomplete. Please try again.'; - case 'serverSelection.malformedServerInfo': return ({required Object message}) => 'Server information is malformed: ${message}'; - case 'serverSelection.networkConnectionFailed': return 'Network connection failed. Please check your internet connection and try again.'; - case 'serverSelection.authenticationFailed': return 'Authentication failed. Please sign in again.'; - case 'serverSelection.plexServiceUnavailable': return 'Plex service unavailable. Please try again later.'; - case 'serverSelection.failedToLoadServers': return ({required Object error}) => 'Failed to load servers: ${error}'; - case 'hubDetail.title': return 'Title'; - case 'hubDetail.releaseYear': return 'Release Year'; - case 'hubDetail.dateAdded': return 'Date Added'; - case 'hubDetail.rating': return 'Rating'; - case 'hubDetail.noItemsFound': return 'No items found'; - case 'logs.title': return 'Logs'; - case 'logs.clearLogs': return 'Clear Logs'; - case 'logs.copyLogs': return 'Copy Logs'; - case 'logs.exportLogs': return 'Export Logs'; - case 'logs.noLogsToShow': return 'No logs to show'; - case 'logs.error': return 'Error:'; - case 'logs.stackTrace': return 'Stack Trace:'; - case 'licenses.relatedPackages': return 'Related Packages'; - case 'licenses.license': return 'License'; - case 'licenses.licenseNumber': return ({required Object number}) => 'License ${number}'; - case 'licenses.licensesCount': return ({required Object count}) => '${count} licenses'; - case 'navigation.home': return 'Home'; - case 'navigation.search': return 'Search'; - case 'navigation.libraries': return 'Libraries'; - case 'navigation.settings': return 'Settings'; - case 'collections.title': return 'Collections'; - case 'collections.collection': return 'Collection'; - case 'collections.empty': return 'Collection is empty'; - case 'collections.noItems': return 'No items in this collection'; - case 'collections.unknownLibrarySection': return 'Cannot delete: Unknown library section'; - case 'collections.deleteCollection': return 'Delete Collection'; - case 'collections.deleteConfirm': return ({required Object title}) => 'Are you sure you want to delete "${title}"? This action cannot be undone.'; - case 'collections.deleted': return 'Collection deleted'; - case 'collections.deleteFailed': return 'Failed to delete collection'; - case 'collections.deleteFailedWithError': return ({required Object error}) => 'Failed to delete collection: ${error}'; - case 'collections.failedToLoadItems': return ({required Object error}) => 'Failed to load collection items: ${error}'; - case 'collections.addTo': return 'Add to collection'; - case 'collections.selectCollection': return 'Select Collection'; - case 'collections.createNewCollection': return 'Create New Collection'; - case 'collections.collectionName': return 'Collection Name'; - case 'collections.enterCollectionName': return 'Enter collection name'; - case 'collections.addedToCollection': return 'Added to collection'; - case 'collections.errorAddingToCollection': return 'Failed to add to collection'; - case 'collections.created': return 'Collection created'; - case 'collections.removeFromCollection': return 'Remove from collection'; - case 'collections.removeFromCollectionConfirm': return ({required Object title}) => 'Remove "${title}" from this collection?'; - case 'collections.removedFromCollection': return 'Removed from collection'; - case 'collections.removeFromCollectionFailed': return 'Failed to remove from collection'; - case 'collections.removeFromCollectionError': return ({required Object error}) => 'Error removing from collection: ${error}'; - case 'playlists.title': return 'Playlists'; - case 'playlists.playlist': return 'Playlist'; - case 'playlists.noPlaylists': return 'No playlists found'; - case 'playlists.create': return 'Create Playlist'; - case 'playlists.newPlaylist': return 'New Playlist'; - case 'playlists.playlistName': return 'Playlist Name'; - case 'playlists.enterPlaylistName': return 'Enter playlist name'; - case 'playlists.edit': return 'Edit Playlist'; - case 'playlists.delete': return 'Delete Playlist'; - case 'playlists.addTo': return 'Add to Playlist'; - case 'playlists.addItems': return 'Add Items'; - case 'playlists.removeItem': return 'Remove from Playlist'; - case 'playlists.clearPlaylist': return 'Clear Playlist'; - case 'playlists.playAll': return 'Play All'; - case 'playlists.shuffle': return 'Shuffle'; - case 'playlists.smartPlaylist': return 'Smart Playlist'; - case 'playlists.regularPlaylist': return 'Regular Playlist'; - case 'playlists.itemCount': return ({required Object count}) => '${count} items'; - case 'playlists.oneItem': return '1 item'; - case 'playlists.emptyPlaylist': return 'This playlist is empty'; - case 'playlists.deleteConfirm': return 'Delete Playlist?'; - case 'playlists.deleteMessage': return ({required Object name}) => 'Are you sure you want to delete "${name}"?'; - case 'playlists.created': return 'Playlist created'; - case 'playlists.updated': return 'Playlist updated'; - case 'playlists.deleted': return 'Playlist deleted'; - case 'playlists.itemAdded': return 'Added to playlist'; - case 'playlists.itemRemoved': return 'Removed from playlist'; - case 'playlists.selectPlaylist': return 'Select Playlist'; - case 'playlists.createNewPlaylist': return 'Create New Playlist'; - case 'playlists.errorCreating': return 'Failed to create playlist'; - case 'playlists.errorDeleting': return 'Failed to delete playlist'; - case 'playlists.errorLoading': return 'Failed to load playlists'; - case 'playlists.errorAdding': return 'Failed to add to playlist'; - case 'playlists.errorReordering': return 'Failed to reorder playlist item'; - case 'playlists.errorRemoving': return 'Failed to remove from playlist'; - default: return null; - } - } + dynamic _flatMapFunction(String path) { + switch (path) { + case 'app.title': + return 'Plezy'; + case 'app.loading': + return 'Loading...'; + case 'auth.signInWithPlex': + return 'Sign in with Plex'; + case 'auth.showQRCode': + return 'Show QR Code'; + case 'auth.cancel': + return 'Cancel'; + case 'auth.authenticate': + return 'Authenticate'; + case 'auth.retry': + return 'Retry'; + case 'auth.debugEnterToken': + return 'Debug: Enter Plex Token'; + case 'auth.plexTokenLabel': + return 'Plex Auth Token'; + case 'auth.plexTokenHint': + return 'Enter your Plex.tv token'; + case 'auth.authenticationTimeout': + return 'Authentication timed out. Please try again.'; + case 'auth.scanQRCodeInstruction': + return 'Scan this QR code with a device logged into Plex to authenticate.'; + case 'auth.waitingForAuth': + return 'Waiting for authentication...\nPlease complete sign-in in your browser.'; + case 'common.cancel': + return 'Cancel'; + case 'common.save': + return 'Save'; + case 'common.close': + return 'Close'; + case 'common.clear': + return 'Clear'; + case 'common.reset': + return 'Reset'; + case 'common.later': + return 'Later'; + case 'common.submit': + return 'Submit'; + case 'common.confirm': + return 'Confirm'; + case 'common.retry': + return 'Retry'; + case 'common.playNow': + return 'Play Now'; + case 'common.logout': + return 'Logout'; + case 'common.online': + return 'Online'; + case 'common.offline': + return 'Offline'; + case 'common.owned': + return 'Owned'; + case 'common.shared': + return 'Shared'; + case 'common.current': + return 'CURRENT'; + case 'common.unknown': + return 'Unknown'; + case 'common.refresh': + return 'Refresh'; + case 'common.yes': + return 'Yes'; + case 'common.no': + return 'No'; + case 'common.server': + return 'Server'; + case 'common.delete': + return 'Delete'; + case 'common.shuffle': + return 'Shuffle'; + case 'common.addTo': + return 'Add to...'; + case 'screens.licenses': + return 'Licenses'; + case 'screens.switchProfile': + return 'Switch Profile'; + case 'screens.subtitleStyling': + return 'Subtitle Styling'; + case 'screens.search': + return 'Search'; + case 'screens.logs': + return 'Logs'; + case 'update.available': + return 'Update Available'; + case 'update.versionAvailable': + return ({required Object version}) => 'Version ${version} is available'; + case 'update.currentVersion': + return ({required Object version}) => 'Current: ${version}'; + case 'update.skipVersion': + return 'Skip This Version'; + case 'update.viewRelease': + return 'View Release'; + case 'update.latestVersion': + return 'You are on the latest version'; + case 'update.checkFailed': + return 'Failed to check for updates'; + case 'settings.title': + return 'Settings'; + case 'settings.language': + return 'Language'; + case 'settings.theme': + return 'Theme'; + case 'settings.appearance': + return 'Appearance'; + case 'settings.videoPlayback': + return 'Video Playback'; + case 'settings.shufflePlay': + return 'Shuffle Play'; + case 'settings.advanced': + return 'Advanced'; + case 'settings.useSeasonPostersDescription': + return 'Show season poster instead of series poster for episodes'; + case 'settings.showHeroSectionDescription': + return 'Display featured content carousel on home screen'; + case 'settings.secondsLabel': + return 'Seconds'; + case 'settings.minutesLabel': + return 'Minutes'; + case 'settings.secondsShort': + return 's'; + case 'settings.minutesShort': + return 'm'; + case 'settings.durationHint': + return ({required Object min, required Object max}) => + 'Enter duration (${min}-${max})'; + case 'settings.systemTheme': + return 'System'; + case 'settings.systemThemeDescription': + return 'Follow system settings'; + case 'settings.lightTheme': + return 'Light'; + case 'settings.darkTheme': + return 'Dark'; + case 'settings.libraryDensity': + return 'Library Density'; + case 'settings.compact': + return 'Compact'; + case 'settings.compactDescription': + return 'Smaller cards, more items visible'; + case 'settings.normal': + return 'Normal'; + case 'settings.normalDescription': + return 'Default size'; + case 'settings.comfortable': + return 'Comfortable'; + case 'settings.comfortableDescription': + return 'Larger cards, fewer items visible'; + case 'settings.viewMode': + return 'View Mode'; + case 'settings.gridView': + return 'Grid'; + case 'settings.gridViewDescription': + return 'Display items in a grid layout'; + case 'settings.listView': + return 'List'; + case 'settings.listViewDescription': + return 'Display items in a list layout'; + case 'settings.useSeasonPosters': + return 'Use Season Posters'; + case 'settings.showHeroSection': + return 'Show Hero Section'; + case 'settings.hardwareDecoding': + return 'Hardware Decoding'; + case 'settings.hardwareDecodingDescription': + return 'Use hardware acceleration when available'; + case 'settings.bufferSize': + return 'Buffer Size'; + case 'settings.bufferSizeMB': + return ({required Object size}) => '${size}MB'; + case 'settings.subtitleStyling': + return 'Subtitle Styling'; + case 'settings.subtitleStylingDescription': + return 'Customize subtitle appearance'; + case 'settings.smallSkipDuration': + return 'Small Skip Duration'; + case 'settings.largeSkipDuration': + return 'Large Skip Duration'; + case 'settings.secondsUnit': + return ({required Object seconds}) => '${seconds} seconds'; + case 'settings.defaultSleepTimer': + return 'Default Sleep Timer'; + case 'settings.minutesUnit': + return ({required Object minutes}) => '${minutes} minutes'; + case 'settings.rememberTrackSelections': + return 'Remember track selections per show/movie'; + case 'settings.rememberTrackSelectionsDescription': + return 'Automatically save audio and subtitle language preferences when you change tracks during playback'; + case 'settings.unwatchedOnly': + return 'Unwatched Only'; + case 'settings.unwatchedOnlyDescription': + return 'Only include unwatched episodes in shuffle queue'; + case 'settings.shuffleOrderNavigation': + return 'Shuffle Order Navigation'; + case 'settings.shuffleOrderNavigationDescription': + return 'Next/previous buttons follow shuffled order'; + case 'settings.loopShuffleQueue': + return 'Loop Shuffle Queue'; + case 'settings.loopShuffleQueueDescription': + return 'Restart queue when reaching the end'; + case 'settings.videoPlayerControls': + return 'Video Player Controls'; + case 'settings.keyboardShortcuts': + return 'Keyboard Shortcuts'; + case 'settings.keyboardShortcutsDescription': + return 'Customize keyboard shortcuts'; + case 'settings.debugLogging': + return 'Debug Logging'; + case 'settings.debugLoggingDescription': + return 'Enable detailed logging for troubleshooting'; + case 'settings.viewLogs': + return 'View Logs'; + case 'settings.viewLogsDescription': + return 'View application logs'; + case 'settings.clearCache': + return 'Clear Cache'; + case 'settings.clearCacheDescription': + return 'This will clear all cached images and data. The app may take longer to load content after clearing the cache.'; + case 'settings.clearCacheSuccess': + return 'Cache cleared successfully'; + case 'settings.resetSettings': + return 'Reset Settings'; + case 'settings.resetSettingsDescription': + return 'This will reset all settings to their default values. This action cannot be undone.'; + case 'settings.resetSettingsSuccess': + return 'Settings reset successfully'; + case 'settings.shortcutsReset': + return 'Shortcuts reset to defaults'; + case 'settings.about': + return 'About'; + case 'settings.aboutDescription': + return 'App information and licenses'; + case 'settings.updates': + return 'Updates'; + case 'settings.updateAvailable': + return 'Update Available'; + case 'settings.checkForUpdates': + return 'Check for Updates'; + case 'settings.validationErrorEnterNumber': + return 'Please enter a valid number'; + case 'settings.validationErrorDuration': + return ({ + required Object min, + required Object max, + required Object unit, + }) => 'Duration must be between ${min} and ${max} ${unit}'; + case 'settings.shortcutAlreadyAssigned': + return ({required Object action}) => + 'Shortcut already assigned to ${action}'; + case 'settings.shortcutUpdated': + return ({required Object action}) => 'Shortcut updated for ${action}'; + case 'search.hint': + return 'Search movies, shows, music...'; + case 'search.tryDifferentTerm': + return 'Try a different search term'; + case 'search.searchYourMedia': + return 'Search your media'; + case 'search.enterTitleActorOrKeyword': + return 'Enter a title, actor, or keyword'; + case 'hotkeys.setShortcutFor': + return ({required Object actionName}) => + 'Set Shortcut for ${actionName}'; + case 'hotkeys.clearShortcut': + return 'Clear shortcut'; + case 'pinEntry.enterPin': + return 'Enter PIN'; + case 'pinEntry.showPin': + return 'Show PIN'; + case 'pinEntry.hidePin': + return 'Hide PIN'; + case 'fileInfo.title': + return 'File Info'; + case 'fileInfo.video': + return 'Video'; + case 'fileInfo.audio': + return 'Audio'; + case 'fileInfo.file': + return 'File'; + case 'fileInfo.advanced': + return 'Advanced'; + case 'fileInfo.codec': + return 'Codec'; + case 'fileInfo.resolution': + return 'Resolution'; + case 'fileInfo.bitrate': + return 'Bitrate'; + case 'fileInfo.frameRate': + return 'Frame Rate'; + case 'fileInfo.aspectRatio': + return 'Aspect Ratio'; + case 'fileInfo.profile': + return 'Profile'; + case 'fileInfo.bitDepth': + return 'Bit Depth'; + case 'fileInfo.colorSpace': + return 'Color Space'; + case 'fileInfo.colorRange': + return 'Color Range'; + case 'fileInfo.colorPrimaries': + return 'Color Primaries'; + case 'fileInfo.chromaSubsampling': + return 'Chroma Subsampling'; + case 'fileInfo.channels': + return 'Channels'; + case 'fileInfo.path': + return 'Path'; + case 'fileInfo.size': + return 'Size'; + case 'fileInfo.container': + return 'Container'; + case 'fileInfo.duration': + return 'Duration'; + case 'fileInfo.optimizedForStreaming': + return 'Optimized for Streaming'; + case 'fileInfo.has64bitOffsets': + return '64-bit Offsets'; + case 'mediaMenu.markAsWatched': + return 'Mark as Watched'; + case 'mediaMenu.markAsUnwatched': + return 'Mark as Unwatched'; + case 'mediaMenu.removeFromContinueWatching': + return 'Remove from Continue Watching'; + case 'mediaMenu.goToSeries': + return 'Go to series'; + case 'mediaMenu.goToSeason': + return 'Go to season'; + case 'mediaMenu.shufflePlay': + return 'Shuffle Play'; + case 'mediaMenu.fileInfo': + return 'File Info'; + case 'accessibility.mediaCardMovie': + return ({required Object title}) => '${title}, movie'; + case 'accessibility.mediaCardShow': + return ({required Object title}) => '${title}, TV show'; + case 'accessibility.mediaCardEpisode': + return ({required Object title, required Object episodeInfo}) => + '${title}, ${episodeInfo}'; + case 'accessibility.mediaCardSeason': + return ({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + case 'accessibility.mediaCardWatched': + return 'watched'; + case 'accessibility.mediaCardPartiallyWatched': + return ({required Object percent}) => '${percent} percent watched'; + case 'accessibility.mediaCardUnwatched': + return 'unwatched'; + case 'accessibility.tapToPlay': + return 'Tap to play'; + case 'tooltips.shufflePlay': + return 'Shuffle play'; + case 'tooltips.markAsWatched': + return 'Mark as watched'; + case 'tooltips.markAsUnwatched': + return 'Mark as unwatched'; + case 'videoControls.audioLabel': + return 'Audio'; + case 'videoControls.subtitlesLabel': + return 'Subtitles'; + case 'videoControls.resetToZero': + return 'Reset to 0ms'; + case 'videoControls.addTime': + return ({required Object amount, required Object unit}) => + '+${amount}${unit}'; + case 'videoControls.minusTime': + return ({required Object amount, required Object unit}) => + '-${amount}${unit}'; + case 'videoControls.playsLater': + return ({required Object label}) => '${label} plays later'; + case 'videoControls.playsEarlier': + return ({required Object label}) => '${label} plays earlier'; + case 'videoControls.noOffset': + return 'No offset'; + case 'videoControls.letterbox': + return 'Letterbox'; + case 'videoControls.fillScreen': + return 'Fill screen'; + case 'videoControls.stretch': + return 'Stretch'; + case 'videoControls.lockRotation': + return 'Lock rotation'; + case 'videoControls.unlockRotation': + return 'Unlock rotation'; + case 'videoControls.sleepTimer': + return 'Sleep Timer'; + case 'videoControls.timerActive': + return 'Timer Active'; + case 'videoControls.playbackWillPauseIn': + return ({required Object duration}) => + 'Playback will pause in ${duration}'; + case 'videoControls.sleepTimerCompleted': + return 'Sleep timer completed - playback paused'; + case 'videoControls.playButton': + return 'Play'; + case 'videoControls.pauseButton': + return 'Pause'; + case 'videoControls.seekBackwardButton': + return ({required Object seconds}) => + 'Seek backward ${seconds} seconds'; + case 'videoControls.seekForwardButton': + return ({required Object seconds}) => 'Seek forward ${seconds} seconds'; + case 'videoControls.previousButton': + return 'Previous episode'; + case 'videoControls.nextButton': + return 'Next episode'; + case 'videoControls.previousChapterButton': + return 'Previous chapter'; + case 'videoControls.nextChapterButton': + return 'Next chapter'; + case 'videoControls.volumeButton': + return 'Volume'; + case 'videoControls.muteButton': + return 'Mute'; + case 'videoControls.unmuteButton': + return 'Unmute'; + case 'videoControls.settingsButton': + return 'Video settings'; + case 'videoControls.audioTrackButton': + return 'Audio tracks'; + case 'videoControls.subtitlesButton': + return 'Subtitles'; + case 'videoControls.chaptersButton': + return 'Chapters'; + case 'videoControls.versionsButton': + return 'Video versions'; + case 'videoControls.aspectRatioButton': + return 'Aspect ratio'; + case 'videoControls.fullscreenButton': + return 'Enter fullscreen'; + case 'videoControls.exitFullscreenButton': + return 'Exit fullscreen'; + case 'videoControls.rotationLockButton': + return 'Rotation lock'; + case 'videoControls.timelineSlider': + return 'Video timeline'; + case 'videoControls.volumeSlider': + return 'Volume level'; + case 'videoControls.backButton': + return 'Back'; + case 'userStatus.admin': + return 'Admin'; + case 'userStatus.restricted': + return 'Restricted'; + case 'userStatus.protected': + return 'Protected'; + case 'userStatus.current': + return 'CURRENT'; + case 'messages.markedAsWatched': + return 'Marked as watched'; + case 'messages.markedAsUnwatched': + return 'Marked as unwatched'; + case 'messages.removedFromContinueWatching': + return 'Removed from Continue Watching'; + case 'messages.errorLoading': + return ({required Object error}) => 'Error: ${error}'; + case 'messages.fileInfoNotAvailable': + return 'File information not available'; + case 'messages.errorLoadingFileInfo': + return ({required Object error}) => 'Error loading file info: ${error}'; + case 'messages.errorLoadingSeries': + return 'Error loading series'; + case 'messages.errorLoadingSeason': + return 'Error loading season'; + case 'messages.musicNotSupported': + return 'Music playback is not yet supported'; + case 'messages.logsCleared': + return 'Logs cleared'; + case 'messages.logsCopied': + return 'Logs copied to clipboard'; + case 'messages.noLogsAvailable': + return 'No logs available'; + case 'messages.libraryScanning': + return ({required Object title}) => 'Scanning "${title}"...'; + case 'messages.libraryScanStarted': + return ({required Object title}) => + 'Library scan started for "${title}"'; + case 'messages.libraryScanFailed': + return ({required Object error}) => 'Failed to scan library: ${error}'; + case 'messages.metadataRefreshing': + return ({required Object title}) => + 'Refreshing metadata for "${title}"...'; + case 'messages.metadataRefreshStarted': + return ({required Object title}) => + 'Metadata refresh started for "${title}"'; + case 'messages.metadataRefreshFailed': + return ({required Object error}) => + 'Failed to refresh metadata: ${error}'; + case 'messages.noPlexToken': + return 'No Plex token found. Please login again.'; + case 'messages.logoutConfirm': + return 'Are you sure you want to logout?'; + case 'messages.noSeasonsFound': + return 'No seasons found'; + case 'messages.noEpisodesFound': + return 'No episodes found in first season'; + case 'messages.noEpisodesFoundGeneral': + return 'No episodes found'; + case 'messages.noResultsFound': + return 'No results found'; + case 'messages.sleepTimerSet': + return ({required Object label}) => 'Sleep timer set for ${label}'; + case 'messages.failedToSwitchProfile': + return ({required Object displayName}) => + 'Failed to switch to ${displayName}'; + case 'messages.noItemsAvailable': + return 'No items available'; + case 'messages.failedToCreatePlayQueue': + return 'Failed to create play queue'; + case 'messages.failedToCreatePlayQueueNoItems': + return 'Failed to create play queue - no items'; + case 'messages.failedPlayback': + return ({required Object action, required Object error}) => + 'Failed to ${action}: ${error}'; + case 'profile.noUsersAvailable': + return 'No users available'; + case 'subtitlingStyling.stylingOptions': + return 'Styling Options'; + case 'subtitlingStyling.fontSize': + return 'Font Size'; + case 'subtitlingStyling.textColor': + return 'Text Color'; + case 'subtitlingStyling.borderSize': + return 'Border Size'; + case 'subtitlingStyling.borderColor': + return 'Border Color'; + case 'subtitlingStyling.backgroundOpacity': + return 'Background Opacity'; + case 'subtitlingStyling.backgroundColor': + return 'Background Color'; + case 'dialog.confirmAction': + return 'Confirm Action'; + case 'dialog.areYouSure': + return 'Are you sure you want to perform this action?'; + case 'dialog.cancel': + return 'Cancel'; + case 'dialog.playNow': + return 'Play Now'; + case 'discover.title': + return 'Discover'; + case 'discover.switchProfile': + return 'Switch Profile'; + case 'discover.logout': + return 'Logout'; + case 'discover.noContentAvailable': + return 'No content available'; + case 'discover.addMediaToLibraries': + return 'Add some media to your libraries'; + case 'discover.continueWatching': + return 'Continue Watching'; + case 'discover.recentlyAdded': + return 'Recently Added'; + case 'discover.play': + return 'Play'; + case 'discover.resume': + return 'Resume'; + case 'discover.playEpisode': + return ({required Object season, required Object episode}) => + 'Play S${season}, E${episode}'; + case 'discover.resumeEpisode': + return ({required Object season, required Object episode}) => + 'Resume S${season}, E${episode}'; + case 'discover.pause': + return 'Pause'; + case 'discover.overview': + return 'Overview'; + case 'discover.cast': + return 'Cast'; + case 'discover.seasons': + return 'Seasons'; + case 'discover.studio': + return 'Studio'; + case 'discover.rating': + return 'Rating'; + case 'discover.watched': + return 'Watched'; + case 'discover.episodeCount': + return ({required Object count}) => '${count} episodes'; + case 'discover.watchedProgress': + return ({required Object watched, required Object total}) => + '${watched}/${total} watched'; + case 'discover.movie': + return 'Movie'; + case 'discover.tvShow': + return 'TV Show'; + case 'discover.minutesLeft': + return ({required Object minutes}) => '${minutes} min left'; + case 'errors.searchFailed': + return ({required Object error}) => 'Search failed: ${error}'; + case 'errors.connectionTimeout': + return ({required Object context}) => + 'Connection timeout while loading ${context}'; + case 'errors.connectionFailed': + return 'Unable to connect to Plex server'; + case 'errors.failedToLoad': + return ({required Object context, required Object error}) => + 'Failed to load ${context}: ${error}'; + case 'errors.noClientAvailable': + return 'No client available'; + case 'errors.authenticationFailed': + return ({required Object error}) => 'Authentication failed: ${error}'; + case 'errors.couldNotLaunchUrl': + return 'Could not launch auth URL'; + case 'errors.pleaseEnterToken': + return 'Please enter a token'; + case 'errors.invalidToken': + return 'Invalid token'; + case 'errors.failedToVerifyToken': + return ({required Object error}) => 'Failed to verify token: ${error}'; + case 'errors.failedToSwitchProfile': + return ({required Object displayName}) => + 'Failed to switch to ${displayName}'; + case 'errors.connectionFailedGeneric': + return 'Connection failed'; + case 'libraries.title': + return 'Libraries'; + case 'libraries.scanLibraryFiles': + return 'Scan Library Files'; + case 'libraries.scanLibrary': + return 'Scan Library'; + case 'libraries.analyze': + return 'Analyze'; + case 'libraries.analyzeLibrary': + return 'Analyze Library'; + case 'libraries.refreshMetadata': + return 'Refresh Metadata'; + case 'libraries.emptyTrash': + return 'Empty Trash'; + case 'libraries.emptyingTrash': + return ({required Object title}) => 'Emptying trash for "${title}"...'; + case 'libraries.trashEmptied': + return ({required Object title}) => 'Trash emptied for "${title}"'; + case 'libraries.failedToEmptyTrash': + return ({required Object error}) => 'Failed to empty trash: ${error}'; + case 'libraries.analyzing': + return ({required Object title}) => 'Analyzing "${title}"...'; + case 'libraries.analysisStarted': + return ({required Object title}) => 'Analysis started for "${title}"'; + case 'libraries.failedToAnalyze': + return ({required Object error}) => + 'Failed to analyze library: ${error}'; + case 'libraries.noLibrariesFound': + return 'No libraries found'; + case 'libraries.thisLibraryIsEmpty': + return 'This library is empty'; + case 'libraries.all': + return 'All'; + case 'libraries.clearAll': + return 'Clear All'; + case 'libraries.scanLibraryConfirm': + return ({required Object title}) => + 'Are you sure you want to scan "${title}"?'; + case 'libraries.analyzeLibraryConfirm': + return ({required Object title}) => + 'Are you sure you want to analyze "${title}"?'; + case 'libraries.refreshMetadataConfirm': + return ({required Object title}) => + 'Are you sure you want to refresh metadata for "${title}"?'; + case 'libraries.emptyTrashConfirm': + return ({required Object title}) => + 'Are you sure you want to empty trash for "${title}"?'; + case 'libraries.manageLibraries': + return 'Manage Libraries'; + case 'libraries.sort': + return 'Sort'; + case 'libraries.sortBy': + return 'Sort By'; + case 'libraries.filters': + return 'Filters'; + case 'libraries.loadingLibraryWithCount': + return ({required Object count}) => + 'Loading library... (${count} items loaded)'; + case 'libraries.confirmActionMessage': + return 'Are you sure you want to perform this action?'; + case 'libraries.showLibrary': + return 'Show library'; + case 'libraries.hideLibrary': + return 'Hide library'; + case 'libraries.libraryOptions': + return 'Library options'; + case 'libraries.content': + return 'library content'; + case 'libraries.selectLibrary': + return 'Select library'; + case 'libraries.filtersWithCount': + return ({required Object count}) => 'Filters (${count})'; + case 'libraries.noRecommendations': + return 'No recommendations available'; + case 'libraries.noCollections': + return 'No collections in this library'; + case 'libraries.noFoldersFound': + return 'No folders found'; + case 'libraries.folders': + return 'folders'; + case 'libraries.tabs.recommended': + return 'Recommended'; + case 'libraries.tabs.browse': + return 'Browse'; + case 'libraries.tabs.collections': + return 'Collections'; + case 'libraries.tabs.playlists': + return 'Playlists'; + case 'libraries.groupings.all': + return 'All'; + case 'libraries.groupings.movies': + return 'Movies'; + case 'libraries.groupings.shows': + return 'TV Shows'; + case 'libraries.groupings.seasons': + return 'Seasons'; + case 'libraries.groupings.episodes': + return 'Episodes'; + case 'libraries.groupings.folders': + return 'Folders'; + case 'about.title': + return 'About'; + case 'about.openSourceLicenses': + return 'Open Source Licenses'; + case 'about.versionLabel': + return ({required Object version}) => 'Version ${version}'; + case 'about.appDescription': + return 'A beautiful Plex client for Flutter'; + case 'about.viewLicensesDescription': + return 'View licenses of third-party libraries'; + case 'serverSelection.connectingToServer': + return 'Connecting to server...'; + case 'serverSelection.connectingToAllServers': + return 'Connecting to all servers...'; + case 'serverSelection.connectToAllServers': + return 'Connect to All Servers'; + case 'serverSelection.allServerConnectionsFailed': + return 'Failed to connect to any servers. Please check your network and try again.'; + case 'serverSelection.foundServers': + return ({required Object count}) => 'Found ${count} server(s)'; + case 'serverSelection.orSelectIndividually': + return 'Or select a server individually:'; + case 'serverSelection.serverDebugCopied': + return 'Server debug data copied to clipboard'; + case 'serverSelection.copyDebugData': + return 'Copy Debug Data'; + case 'serverSelection.noServersFound': + return 'No servers found'; + case 'serverSelection.malformedServerData': + return ({required Object count}) => + 'Found ${count} server(s) with malformed data. No valid servers available.'; + case 'serverSelection.incompleteServerInfo': + return 'Some servers have incomplete information and were skipped. Please check your Plex.tv account.'; + case 'serverSelection.incompleteConnectionInfo': + return 'Server connection information is incomplete. Please try again.'; + case 'serverSelection.malformedServerInfo': + return ({required Object message}) => + 'Server information is malformed: ${message}'; + case 'serverSelection.networkConnectionFailed': + return 'Network connection failed. Please check your internet connection and try again.'; + case 'serverSelection.authenticationFailed': + return 'Authentication failed. Please sign in again.'; + case 'serverSelection.plexServiceUnavailable': + return 'Plex service unavailable. Please try again later.'; + case 'serverSelection.failedToLoadServers': + return ({required Object error}) => 'Failed to load servers: ${error}'; + case 'hubDetail.title': + return 'Title'; + case 'hubDetail.releaseYear': + return 'Release Year'; + case 'hubDetail.dateAdded': + return 'Date Added'; + case 'hubDetail.rating': + return 'Rating'; + case 'hubDetail.noItemsFound': + return 'No items found'; + case 'logs.title': + return 'Logs'; + case 'logs.clearLogs': + return 'Clear Logs'; + case 'logs.copyLogs': + return 'Copy Logs'; + case 'logs.exportLogs': + return 'Export Logs'; + case 'logs.noLogsToShow': + return 'No logs to show'; + case 'logs.error': + return 'Error:'; + case 'logs.stackTrace': + return 'Stack Trace:'; + case 'licenses.relatedPackages': + return 'Related Packages'; + case 'licenses.license': + return 'License'; + case 'licenses.licenseNumber': + return ({required Object number}) => 'License ${number}'; + case 'licenses.licensesCount': + return ({required Object count}) => '${count} licenses'; + case 'navigation.home': + return 'Home'; + case 'navigation.search': + return 'Search'; + case 'navigation.libraries': + return 'Libraries'; + case 'navigation.settings': + return 'Settings'; + case 'collections.title': + return 'Collections'; + case 'collections.collection': + return 'Collection'; + case 'collections.empty': + return 'Collection is empty'; + case 'collections.noItems': + return 'No items in this collection'; + case 'collections.unknownLibrarySection': + return 'Cannot delete: Unknown library section'; + case 'collections.deleteCollection': + return 'Delete Collection'; + case 'collections.deleteConfirm': + return ({required Object title}) => + 'Are you sure you want to delete "${title}"? This action cannot be undone.'; + case 'collections.deleted': + return 'Collection deleted'; + case 'collections.deleteFailed': + return 'Failed to delete collection'; + case 'collections.deleteFailedWithError': + return ({required Object error}) => + 'Failed to delete collection: ${error}'; + case 'collections.failedToLoadItems': + return ({required Object error}) => + 'Failed to load collection items: ${error}'; + case 'collections.addTo': + return 'Add to collection'; + case 'collections.selectCollection': + return 'Select Collection'; + case 'collections.createNewCollection': + return 'Create New Collection'; + case 'collections.collectionName': + return 'Collection Name'; + case 'collections.enterCollectionName': + return 'Enter collection name'; + case 'collections.addedToCollection': + return 'Added to collection'; + case 'collections.errorAddingToCollection': + return 'Failed to add to collection'; + case 'collections.created': + return 'Collection created'; + case 'collections.removeFromCollection': + return 'Remove from collection'; + case 'collections.removeFromCollectionConfirm': + return ({required Object title}) => + 'Remove "${title}" from this collection?'; + case 'collections.removedFromCollection': + return 'Removed from collection'; + case 'collections.removeFromCollectionFailed': + return 'Failed to remove from collection'; + case 'collections.removeFromCollectionError': + return ({required Object error}) => + 'Error removing from collection: ${error}'; + case 'playlists.title': + return 'Playlists'; + case 'playlists.playlist': + return 'Playlist'; + case 'playlists.noPlaylists': + return 'No playlists found'; + case 'playlists.create': + return 'Create Playlist'; + case 'playlists.newPlaylist': + return 'New Playlist'; + case 'playlists.playlistName': + return 'Playlist Name'; + case 'playlists.enterPlaylistName': + return 'Enter playlist name'; + case 'playlists.edit': + return 'Edit Playlist'; + case 'playlists.delete': + return 'Delete Playlist'; + case 'playlists.addTo': + return 'Add to Playlist'; + case 'playlists.addItems': + return 'Add Items'; + case 'playlists.removeItem': + return 'Remove from Playlist'; + case 'playlists.clearPlaylist': + return 'Clear Playlist'; + case 'playlists.playAll': + return 'Play All'; + case 'playlists.shuffle': + return 'Shuffle'; + case 'playlists.smartPlaylist': + return 'Smart Playlist'; + case 'playlists.regularPlaylist': + return 'Regular Playlist'; + case 'playlists.itemCount': + return ({required Object count}) => '${count} items'; + case 'playlists.oneItem': + return '1 item'; + case 'playlists.emptyPlaylist': + return 'This playlist is empty'; + case 'playlists.deleteConfirm': + return 'Delete Playlist?'; + case 'playlists.deleteMessage': + return ({required Object name}) => + 'Are you sure you want to delete "${name}"?'; + case 'playlists.created': + return 'Playlist created'; + case 'playlists.updated': + return 'Playlist updated'; + case 'playlists.deleted': + return 'Playlist deleted'; + case 'playlists.itemAdded': + return 'Added to playlist'; + case 'playlists.itemRemoved': + return 'Removed from playlist'; + case 'playlists.selectPlaylist': + return 'Select Playlist'; + case 'playlists.createNewPlaylist': + return 'Create New Playlist'; + case 'playlists.errorCreating': + return 'Failed to create playlist'; + case 'playlists.errorDeleting': + return 'Failed to delete playlist'; + case 'playlists.errorLoading': + return 'Failed to load playlists'; + case 'playlists.errorAdding': + return 'Failed to add to playlist'; + case 'playlists.errorReordering': + return 'Failed to reorder playlist item'; + case 'playlists.errorRemoving': + return 'Failed to remove from playlist'; + default: + return null; + } + } } extension on _StringsDe { - dynamic _flatMapFunction(String path) { - switch (path) { - case 'app.title': return 'Plezy'; - case 'app.loading': return 'Lädt...'; - case 'auth.signInWithPlex': return 'Mit Plex anmelden'; - case 'auth.showQRCode': return 'QR-Code anzeigen'; - case 'auth.cancel': return 'Abbrechen'; - case 'auth.authenticate': return 'Authentifizieren'; - case 'auth.retry': return 'Erneut versuchen'; - case 'auth.debugEnterToken': return 'Debug: Plex-Token eingeben'; - case 'auth.plexTokenLabel': return 'Plex-Auth-Token'; - case 'auth.plexTokenHint': return 'Plex.tv-Token eingeben'; - case 'auth.authenticationTimeout': return 'Authentifizierung abgelaufen. Bitte erneut versuchen.'; - case 'auth.scanQRCodeInstruction': return 'Diesen QR-Code mit einem bei Plex angemeldeten Gerät scannen, um zu authentifizieren.'; - case 'auth.waitingForAuth': return 'Warte auf Authentifizierung...\nBitte Anmeldung im Browser abschließen.'; - case 'common.cancel': return 'Abbrechen'; - case 'common.save': return 'Speichern'; - case 'common.close': return 'Schließen'; - case 'common.clear': return 'Leeren'; - case 'common.reset': return 'Zurücksetzen'; - case 'common.later': return 'Später'; - case 'common.submit': return 'Senden'; - case 'common.confirm': return 'Bestätigen'; - case 'common.retry': return 'Erneut versuchen'; - case 'common.playNow': return 'Jetzt abspielen'; - case 'common.logout': return 'Abmelden'; - case 'common.online': return 'Online'; - case 'common.offline': return 'Offline'; - case 'common.owned': return 'Besitzer'; - case 'common.shared': return 'Geteilt'; - case 'common.current': return 'AKTUELL'; - case 'common.unknown': return 'Unbekannt'; - case 'common.refresh': return 'Aktualisieren'; - case 'common.yes': return 'Ja'; - case 'common.no': return 'Nein'; - case 'common.server': return 'Server'; - case 'common.delete': return 'Löschen'; - case 'common.shuffle': return 'Zufall'; - case 'common.addTo': return 'Hinzufügen zu...'; - case 'screens.licenses': return 'Lizenzen'; - case 'screens.selectServer': return 'Server auswählen'; - case 'screens.switchProfile': return 'Profil wechseln'; - case 'screens.subtitleStyling': return 'Untertitel-Stil'; - case 'screens.search': return 'Suche'; - case 'screens.logs': return 'Protokolle'; - case 'update.available': return 'Update verfügbar'; - case 'update.versionAvailable': return ({required Object version}) => 'Version ${version} ist verfügbar'; - case 'update.currentVersion': return ({required Object version}) => 'Aktuell: ${version}'; - case 'update.skipVersion': return 'Diese Version überspringen'; - case 'update.viewRelease': return 'Release anzeigen'; - case 'update.latestVersion': return 'Aktuellste Version installiert'; - case 'update.checkFailed': return 'Fehler bei der Updateprüfung'; - case 'settings.title': return 'Einstellungen'; - case 'settings.language': return 'Sprache'; - case 'settings.theme': return 'Design'; - case 'settings.appearance': return 'Darstellung'; - case 'settings.videoPlayback': return 'Videowiedergabe'; - case 'settings.shufflePlay': return 'Zufallswiedergabe'; - case 'settings.advanced': return 'Erweitert'; - case 'settings.useSeasonPostersDescription': return 'Staffelposter statt Serienposter für Episoden anzeigen'; - case 'settings.showHeroSectionDescription': return 'Bereich mit empfohlenen Inhalten auf der Startseite anzeigen'; - case 'settings.secondsLabel': return 'Sekunden'; - case 'settings.minutesLabel': return 'Minuten'; - case 'settings.secondsShort': return 's'; - case 'settings.minutesShort': return 'm'; - case 'settings.durationHint': return ({required Object min, required Object max}) => 'Dauer eingeben (${min}-${max})'; - case 'settings.systemTheme': return 'System'; - case 'settings.systemThemeDescription': return 'Systemeinstellungen folgen'; - case 'settings.lightTheme': return 'Hell'; - case 'settings.darkTheme': return 'Dunkel'; - case 'settings.libraryDensity': return 'Mediathekdichte'; - case 'settings.compact': return 'Kompakt'; - case 'settings.compactDescription': return 'Kleinere Karten, mehr Elemente sichtbar'; - case 'settings.normal': return 'Normal'; - case 'settings.normalDescription': return 'Standardgröße'; - case 'settings.comfortable': return 'Großzügig'; - case 'settings.comfortableDescription': return 'Größere Karten, weniger Elemente sichtbar'; - case 'settings.viewMode': return 'Ansichtsmodus'; - case 'settings.gridView': return 'Raster'; - case 'settings.gridViewDescription': return 'Elemente im Raster anzeigen'; - case 'settings.listView': return 'Liste'; - case 'settings.listViewDescription': return 'Elemente in Listenansicht anzeigen'; - case 'settings.useSeasonPosters': return 'Staffelposter verwenden'; - case 'settings.showHeroSection': return 'Hero-Bereich anzeigen'; - case 'settings.hardwareDecoding': return 'Hardware-Decodierung'; - case 'settings.hardwareDecodingDescription': return 'Hardwarebeschleunigung verwenden, sofern verfügbar'; - case 'settings.bufferSize': return 'Puffergröße'; - case 'settings.bufferSizeMB': return ({required Object size}) => '${size}MB'; - case 'settings.subtitleStyling': return 'Untertitel-Stil'; - case 'settings.subtitleStylingDescription': return 'Aussehen von Untertiteln anpassen'; - case 'settings.smallSkipDuration': return 'Kleine Sprungdauer'; - case 'settings.largeSkipDuration': return 'Große Sprungdauer'; - case 'settings.secondsUnit': return ({required Object seconds}) => '${seconds} Sekunden'; - case 'settings.defaultSleepTimer': return 'Standard-Sleep-Timer'; - case 'settings.minutesUnit': return ({required Object minutes}) => '${minutes} Minuten'; - case 'settings.rememberTrackSelections': return 'Spurauswahl pro Serie/Film merken'; - case 'settings.rememberTrackSelectionsDescription': return 'Audio- und Untertitelsprache automatisch speichern, wenn während der Wiedergabe geändert'; - case 'settings.unwatchedOnly': return 'Nur ungesehene'; - case 'settings.unwatchedOnlyDescription': return 'Nur ungesehene Episoden in die Shuffle-Warteschlange aufnehmen'; - case 'settings.shuffleOrderNavigation': return 'Navigation der Shuffle-Reihenfolge'; - case 'settings.shuffleOrderNavigationDescription': return 'Weiter/Zurück folgt der zufälligen Reihenfolge'; - case 'settings.loopShuffleQueue': return 'Shuffle-Warteschlange wiederholen'; - case 'settings.loopShuffleQueueDescription': return 'Warteschlange neu starten, wenn das Ende erreicht ist'; - case 'settings.videoPlayerControls': return 'Videoplayer-Steuerung'; - case 'settings.keyboardShortcuts': return 'Tastenkürzel'; - case 'settings.keyboardShortcutsDescription': return 'Tastenkürzel anpassen'; - case 'settings.debugLogging': return 'Debug-Protokollierung'; - case 'settings.debugLoggingDescription': return 'Detaillierte Protokolle zur Fehleranalyse aktivieren'; - case 'settings.viewLogs': return 'Protokolle anzeigen'; - case 'settings.viewLogsDescription': return 'App-Protokolle anzeigen'; - case 'settings.clearCache': return 'Cache löschen'; - case 'settings.clearCacheDescription': return 'Löscht alle zwischengespeicherten Bilder und Daten. Die App kann danach langsamer laden.'; - case 'settings.clearCacheSuccess': return 'Cache erfolgreich gelöscht'; - case 'settings.resetSettings': return 'Einstellungen zurücksetzen'; - case 'settings.resetSettingsDescription': return 'Alle Einstellungen auf Standard zurücksetzen. Dies kann nicht rückgängig gemacht werden.'; - case 'settings.resetSettingsSuccess': return 'Einstellungen erfolgreich zurückgesetzt'; - case 'settings.shortcutsReset': return 'Tastenkürzel auf Standard zurückgesetzt'; - case 'settings.about': return 'Über'; - case 'settings.aboutDescription': return 'App-Informationen und Lizenzen'; - case 'settings.updates': return 'Updates'; - case 'settings.updateAvailable': return 'Update verfügbar'; - case 'settings.checkForUpdates': return 'Nach Updates suchen'; - case 'settings.validationErrorEnterNumber': return 'Bitte eine gültige Zahl eingeben'; - case 'settings.validationErrorDuration': return ({required Object min, required Object max, required Object unit}) => 'Dauer muss zwischen ${min} und ${max} ${unit} liegen'; - case 'settings.shortcutAlreadyAssigned': return ({required Object action}) => 'Tastenkürzel bereits zugewiesen an ${action}'; - case 'settings.shortcutUpdated': return ({required Object action}) => 'Tastenkürzel aktualisiert für ${action}'; - case 'search.hint': return 'Filme, Serien, Musik suchen...'; - case 'search.tryDifferentTerm': return 'Anderen Suchbegriff versuchen'; - case 'search.searchYourMedia': return 'In den eigenen Medien suchen'; - case 'search.enterTitleActorOrKeyword': return 'Titel, Schauspieler oder Stichwort eingeben'; - case 'hotkeys.setShortcutFor': return ({required Object actionName}) => 'Tastenkürzel festlegen für ${actionName}'; - case 'hotkeys.clearShortcut': return 'Kürzel löschen'; - case 'pinEntry.enterPin': return 'PIN eingeben'; - case 'pinEntry.showPin': return 'PIN anzeigen'; - case 'pinEntry.hidePin': return 'PIN verbergen'; - case 'fileInfo.title': return 'Dateiinfo'; - case 'fileInfo.video': return 'Video'; - case 'fileInfo.audio': return 'Audio'; - case 'fileInfo.file': return 'Datei'; - case 'fileInfo.advanced': return 'Erweitert'; - case 'fileInfo.codec': return 'Codec'; - case 'fileInfo.resolution': return 'Auflösung'; - case 'fileInfo.bitrate': return 'Bitrate'; - case 'fileInfo.frameRate': return 'Bildrate'; - case 'fileInfo.aspectRatio': return 'Seitenverhältnis'; - case 'fileInfo.profile': return 'Profil'; - case 'fileInfo.bitDepth': return 'Farbtiefe'; - case 'fileInfo.colorSpace': return 'Farbraum'; - case 'fileInfo.colorRange': return 'Farbbereich'; - case 'fileInfo.colorPrimaries': return 'Primärfarben'; - case 'fileInfo.chromaSubsampling': return 'Chroma-Subsampling'; - case 'fileInfo.channels': return 'Kanäle'; - case 'fileInfo.path': return 'Pfad'; - case 'fileInfo.size': return 'Größe'; - case 'fileInfo.container': return 'Container'; - case 'fileInfo.duration': return 'Dauer'; - case 'fileInfo.optimizedForStreaming': return 'Für Streaming optimiert'; - case 'fileInfo.has64bitOffsets': return '64-Bit-Offsets'; - case 'mediaMenu.markAsWatched': return 'Als gesehen markieren'; - case 'mediaMenu.markAsUnwatched': return 'Als ungesehen markieren'; - case 'mediaMenu.removeFromContinueWatching': return 'Aus ‚Weiterschauen‘ entfernen'; - case 'mediaMenu.goToSeries': return 'Zur Serie'; - case 'mediaMenu.goToSeason': return 'Zur Staffel'; - case 'mediaMenu.shufflePlay': return 'Zufallswiedergabe'; - case 'mediaMenu.fileInfo': return 'Dateiinfo'; - case 'accessibility.mediaCardMovie': return ({required Object title}) => '${title}, Film'; - case 'accessibility.mediaCardShow': return ({required Object title}) => '${title}, Serie'; - case 'accessibility.mediaCardEpisode': return ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - case 'accessibility.mediaCardSeason': return ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - case 'accessibility.mediaCardWatched': return 'angesehen'; - case 'accessibility.mediaCardPartiallyWatched': return ({required Object percent}) => '${percent} Prozent angesehen'; - case 'accessibility.mediaCardUnwatched': return 'ungeschaut'; - case 'accessibility.tapToPlay': return 'Zum Abspielen tippen'; - case 'tooltips.shufflePlay': return 'Zufallswiedergabe'; - case 'tooltips.markAsWatched': return 'Als gesehen markieren'; - case 'tooltips.markAsUnwatched': return 'Als ungesehen markieren'; - case 'videoControls.audioLabel': return 'Audio'; - case 'videoControls.subtitlesLabel': return 'Untertitel'; - case 'videoControls.resetToZero': return 'Auf 0 ms zurücksetzen'; - case 'videoControls.addTime': return ({required Object amount, required Object unit}) => '+${amount}${unit}'; - case 'videoControls.minusTime': return ({required Object amount, required Object unit}) => '-${amount}${unit}'; - case 'videoControls.playsLater': return ({required Object label}) => '${label} spielt später'; - case 'videoControls.playsEarlier': return ({required Object label}) => '${label} spielt früher'; - case 'videoControls.noOffset': return 'Kein Offset'; - case 'videoControls.letterbox': return 'Letterbox'; - case 'videoControls.fillScreen': return 'Bild füllen'; - case 'videoControls.stretch': return 'Strecken'; - case 'videoControls.lockRotation': return 'Rotation sperren'; - case 'videoControls.unlockRotation': return 'Rotation entsperren'; - case 'videoControls.sleepTimer': return 'Schlaftimer'; - case 'videoControls.timerActive': return 'Schlaftimer aktiv'; - case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Wiedergabe wird in ${duration} pausiert'; - case 'videoControls.sleepTimerCompleted': return 'Schlaftimer abgelaufen – Wiedergabe pausiert'; - case 'videoControls.playButton': return 'Wiedergeben'; - case 'videoControls.pauseButton': return 'Pause'; - case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '${seconds} Sekunden zurück'; - case 'videoControls.seekForwardButton': return ({required Object seconds}) => '${seconds} Sekunden vor'; - case 'videoControls.previousButton': return 'Vorherige Episode'; - case 'videoControls.nextButton': return 'Nächste Episode'; - case 'videoControls.previousChapterButton': return 'Vorheriges Kapitel'; - case 'videoControls.nextChapterButton': return 'Nächstes Kapitel'; - case 'videoControls.volumeButton': return 'Lautstärke'; - case 'videoControls.muteButton': return 'Stumm schalten'; - case 'videoControls.unmuteButton': return 'Stummschaltung aufheben'; - case 'videoControls.settingsButton': return 'Videoeinstellungen'; - case 'videoControls.audioTrackButton': return 'Tonspuren'; - case 'videoControls.subtitlesButton': return 'Untertitel'; - case 'videoControls.chaptersButton': return 'Kapitel'; - case 'videoControls.versionsButton': return 'Videoversionen'; - case 'videoControls.aspectRatioButton': return 'Seitenverhältnis'; - case 'videoControls.fullscreenButton': return 'Vollbild aktivieren'; - case 'videoControls.exitFullscreenButton': return 'Vollbild verlassen'; - case 'videoControls.rotationLockButton': return 'Dreh­sperre'; - case 'videoControls.timelineSlider': return 'Video-Zeitleiste'; - case 'videoControls.volumeSlider': return 'Lautstärkepegel'; - case 'videoControls.backButton': return 'Zurück'; - case 'userStatus.admin': return 'Eigentümer'; - case 'userStatus.restricted': return 'Eingeschränkt'; - case 'userStatus.protected': return 'Geschützt'; - case 'userStatus.current': return 'AKTUELL'; - case 'messages.markedAsWatched': return 'Als gesehen markiert'; - case 'messages.markedAsUnwatched': return 'Als ungesehen markiert'; - case 'messages.removedFromContinueWatching': return 'Aus ‚Weiterschauen‘ entfernt'; - case 'messages.errorLoading': return ({required Object error}) => 'Fehler: ${error}'; - case 'messages.fileInfoNotAvailable': return 'Dateiinfo nicht verfügbar'; - case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Fehler beim Laden der Dateiinfo: ${error}'; - case 'messages.errorLoadingSeries': return 'Fehler beim Laden der Serie'; - case 'messages.errorLoadingSeason': return 'Fehler beim Laden der Staffel'; - case 'messages.musicNotSupported': return 'Musikwiedergabe wird noch nicht unterstützt'; - case 'messages.logsCleared': return 'Protokolle gelöscht'; - case 'messages.logsCopied': return 'Protokolle in Zwischenablage kopiert'; - case 'messages.noLogsAvailable': return 'Keine Protokolle verfügbar'; - case 'messages.libraryScanning': return ({required Object title}) => 'Scanne „${title}“...'; - case 'messages.libraryScanStarted': return ({required Object title}) => 'Mediathekscan gestartet für „${title}“'; - case 'messages.libraryScanFailed': return ({required Object error}) => 'Fehler beim Scannen der Mediathek: ${error}'; - case 'messages.metadataRefreshing': return ({required Object title}) => 'Metadaten werden aktualisiert für „${title}“...'; - case 'messages.metadataRefreshStarted': return ({required Object title}) => 'Metadaten-Aktualisierung gestartet für „${title}“'; - case 'messages.metadataRefreshFailed': return ({required Object error}) => 'Metadaten konnten nicht aktualisiert werden: ${error}'; - case 'messages.noPlexToken': return 'Kein Plex-Token gefunden. Bitte erneut anmelden.'; - case 'messages.logoutConfirm': return 'Abmeldung wirklich durchführen?'; - case 'messages.noSeasonsFound': return 'Keine Staffeln gefunden'; - case 'messages.noEpisodesFound': return 'Keine Episoden in der ersten Staffel gefunden'; - case 'messages.noEpisodesFoundGeneral': return 'Keine Episoden gefunden'; - case 'messages.noResultsFound': return 'Keine Ergebnisse gefunden'; - case 'messages.sleepTimerSet': return ({required Object label}) => 'Sleep-Timer gesetzt auf ${label}'; - case 'messages.failedToSwitchProfile': return ({required Object displayName}) => 'Profilwechsel zu ${displayName} fehlgeschlagen'; - case 'messages.noItemsAvailable': return 'Keine Elemente verfügbar'; - case 'messages.failedToCreatePlayQueue': return 'Wiedergabewarteschlange konnte nicht erstellt werden'; - case 'messages.failedToCreatePlayQueueNoItems': return 'Wiedergabewarteschlange konnte nicht erstellt werden – keine Elemente'; - case 'messages.failedPlayback': return ({required Object action, required Object error}) => 'Wiedergabe für ${action} fehlgeschlagen: ${error}'; - case 'profile.noUsersAvailable': return 'Keine Benutzer verfügbar'; - case 'subtitlingStyling.stylingOptions': return 'Stiloptionen'; - case 'subtitlingStyling.fontSize': return 'Schriftgröße'; - case 'subtitlingStyling.textColor': return 'Textfarbe'; - case 'subtitlingStyling.borderSize': return 'Rahmengröße'; - case 'subtitlingStyling.borderColor': return 'Rahmenfarbe'; - case 'subtitlingStyling.backgroundOpacity': return 'Hintergrunddeckkraft'; - case 'subtitlingStyling.backgroundColor': return 'Hintergrundfarbe'; - case 'dialog.confirmAction': return 'Aktion bestätigen'; - case 'dialog.areYouSure': return 'Aktion wirklich ausführen?'; - case 'dialog.cancel': return 'Abbrechen'; - case 'dialog.playNow': return 'Jetzt abspielen'; - case 'discover.title': return 'Entdecken'; - case 'discover.switchProfile': return 'Profil wechseln'; - case 'discover.switchServer': return 'Server wechseln'; - case 'discover.logout': return 'Abmelden'; - case 'discover.noContentAvailable': return 'Kein Inhalt verfügbar'; - case 'discover.addMediaToLibraries': return 'Medien zur Mediathek hinzufügen'; - case 'discover.continueWatching': return 'Weiterschauen'; - case 'discover.recentlyAdded': return 'Kürzlich hinzugefügt'; - case 'discover.play': return 'Abspielen'; - case 'discover.resume': return 'Fortsetzen'; - case 'discover.playEpisode': return ({required Object season, required Object episode}) => 'S${season}, E${episode} - Abspielen'; - case 'discover.resumeEpisode': return ({required Object season, required Object episode}) => 'S${season}, E${episode} - Fortsetzen'; - case 'discover.pause': return 'Pause'; - case 'discover.overview': return 'Übersicht'; - case 'discover.cast': return 'Besetzung'; - case 'discover.seasons': return 'Staffeln'; - case 'discover.studio': return 'Studio'; - case 'discover.rating': return 'Altersfreigabe'; - case 'discover.watched': return 'Gesehen'; - case 'discover.episodeCount': return ({required Object count}) => '${count} Episoden'; - case 'discover.watchedProgress': return ({required Object watched, required Object total}) => '${watched} von ${total} gesehen'; - case 'discover.movie': return 'Film'; - case 'discover.tvShow': return 'Serie'; - case 'discover.minutesLeft': return ({required Object minutes}) => '${minutes} Min übrig'; - case 'errors.searchFailed': return ({required Object error}) => 'Suche fehlgeschlagen: ${error}'; - case 'errors.connectionTimeout': return ({required Object context}) => 'Zeitüberschreitung beim Laden von ${context}'; - case 'errors.connectionFailed': return 'Verbindung zum Plex-Server fehlgeschlagen'; - case 'errors.failedToLoad': return ({required Object context, required Object error}) => 'Fehler beim Laden von ${context}: ${error}'; - case 'errors.noClientAvailable': return 'Kein Client verfügbar'; - case 'errors.authenticationFailed': return ({required Object error}) => 'Authentifizierung fehlgeschlagen: ${error}'; - case 'errors.couldNotLaunchUrl': return 'Auth-URL konnte nicht geöffnet werden'; - case 'errors.pleaseEnterToken': return 'Bitte Token eingeben'; - case 'errors.invalidToken': return 'Ungültiges Token'; - case 'errors.failedToVerifyToken': return ({required Object error}) => 'Token-Verifizierung fehlgeschlagen: ${error}'; - case 'errors.failedToSwitchProfile': return ({required Object displayName}) => 'Profilwechsel zu ${displayName} fehlgeschlagen'; - case 'errors.connectionFailedGeneric': return 'Verbindung fehlgeschlagen'; - case 'libraries.title': return 'Mediatheken'; - case 'libraries.scanLibraryFiles': return 'Mediatheksdateien scannen'; - case 'libraries.scanLibrary': return 'Mediathek scannen'; - case 'libraries.analyze': return 'Analysieren'; - case 'libraries.analyzeLibrary': return 'Mediathek analysieren'; - case 'libraries.refreshMetadata': return 'Metadaten aktualisieren'; - case 'libraries.emptyTrash': return 'Papierkorb leeren'; - case 'libraries.emptyingTrash': return ({required Object title}) => 'Papierkorb für „${title}“ wird geleert...'; - case 'libraries.trashEmptied': return ({required Object title}) => 'Papierkorb für „${title}“ geleert'; - case 'libraries.failedToEmptyTrash': return ({required Object error}) => 'Papierkorb konnte nicht geleert werden: ${error}'; - case 'libraries.analyzing': return ({required Object title}) => 'Analysiere „${title}“...'; - case 'libraries.analysisStarted': return ({required Object title}) => 'Analyse gestartet für „${title}“'; - case 'libraries.failedToAnalyze': return ({required Object error}) => 'Analyse der Mediathek fehlgeschlagen: ${error}'; - case 'libraries.noLibrariesFound': return 'Keine Mediatheken gefunden'; - case 'libraries.thisLibraryIsEmpty': return 'Diese Mediathek ist leer'; - case 'libraries.all': return 'Alle'; - case 'libraries.clearAll': return 'Alle löschen'; - case 'libraries.scanLibraryConfirm': return ({required Object title}) => '„${title}“ wirklich scannen?'; - case 'libraries.analyzeLibraryConfirm': return ({required Object title}) => '„${title}“ wirklich analysieren?'; - case 'libraries.refreshMetadataConfirm': return ({required Object title}) => 'Metadaten für „${title}“ wirklich aktualisieren?'; - case 'libraries.emptyTrashConfirm': return ({required Object title}) => 'Papierkorb für „${title}“ wirklich leeren?'; - case 'libraries.manageLibraries': return 'Mediatheken verwalten'; - case 'libraries.sort': return 'Sortieren'; - case 'libraries.sortBy': return 'Sortieren nach'; - case 'libraries.filters': return 'Filter'; - case 'libraries.loadingLibraryWithCount': return ({required Object count}) => 'Lade Mediathek... (${count} Elemente geladen)'; - case 'libraries.confirmActionMessage': return 'Aktion wirklich durchführen?'; - case 'libraries.showLibrary': return 'Mediathek anzeigen'; - case 'libraries.hideLibrary': return 'Mediathek ausblenden'; - case 'libraries.libraryOptions': return 'Mediatheksoptionen'; - case 'libraries.content': return 'Bibliotheksinhalt'; - case 'libraries.selectLibrary': return 'Bibliothek auswählen'; - case 'libraries.filtersWithCount': return ({required Object count}) => 'Filter (${count})'; - case 'libraries.noRecommendations': return 'Keine Empfehlungen verfügbar'; - case 'libraries.noCollections': return 'Keine Sammlungen in dieser Mediathek'; - case 'libraries.noFoldersFound': return 'Keine Ordner gefunden'; - case 'libraries.folders': return 'Ordner'; - case 'libraries.tabs.recommended': return 'Empfohlen'; - case 'libraries.tabs.browse': return 'Durchsuchen'; - case 'libraries.tabs.collections': return 'Sammlungen'; - case 'libraries.tabs.playlists': return 'Wiedergabelisten'; - case 'libraries.groupings.all': return 'Alle'; - case 'libraries.groupings.movies': return 'Filme'; - case 'libraries.groupings.shows': return 'Serien'; - case 'libraries.groupings.seasons': return 'Staffeln'; - case 'libraries.groupings.episodes': return 'Episoden'; - case 'libraries.groupings.folders': return 'Ordner'; - case 'about.title': return 'Über'; - case 'about.openSourceLicenses': return 'Open-Source-Lizenzen'; - case 'about.versionLabel': return ({required Object version}) => 'Version ${version}'; - case 'about.appDescription': return 'Ein schöner Plex-Client für Flutter'; - case 'about.viewLicensesDescription': return 'Lizenzen von Drittanbieter-Bibliotheken anzeigen'; - case 'serverSelection.connectingToServer': return 'Verbinde mit Server...'; - case 'serverSelection.connectingToAllServers': return 'Verbinde mit allen Servern...'; - case 'serverSelection.connectToAllServers': return 'Mit allen Servern verbinden'; - case 'serverSelection.allServerConnectionsFailed': return 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.'; - case 'serverSelection.foundServers': return ({required Object count}) => '${count} Server gefunden'; - case 'serverSelection.orSelectIndividually': return 'Oder einzelnen Server auswählen:'; - case 'serverSelection.serverDebugCopied': return 'Server-Debugdaten kopiert'; - case 'serverSelection.copyDebugData': return 'Debugdaten kopieren'; - case 'serverSelection.noServersFound': return 'Keine Server gefunden'; - case 'serverSelection.malformedServerData': return ({required Object count}) => '${count} Server mit fehlerhaften Daten gefunden. Keine gültigen Server verfügbar.'; - case 'serverSelection.incompleteServerInfo': return 'Einige Serverinformationen sind unvollständig und wurden übersprungen. Plex.tv-Konto prüfen.'; - case 'serverSelection.incompleteConnectionInfo': return 'Serververbindungsinformationen unvollständig. Bitte erneut versuchen.'; - case 'serverSelection.malformedServerInfo': return ({required Object message}) => 'Serverinformationen fehlerhaft: ${message}'; - case 'serverSelection.networkConnectionFailed': return 'Netzwerkverbindung fehlgeschlagen. Internetverbindung prüfen und erneut versuchen.'; - case 'serverSelection.authenticationFailed': return 'Authentifizierung fehlgeschlagen. Bitte erneut anmelden.'; - case 'serverSelection.plexServiceUnavailable': return 'Plex-Dienst nicht verfügbar. Bitte später erneut versuchen.'; - case 'serverSelection.failedToLoadServers': return ({required Object error}) => 'Server konnten nicht geladen werden: ${error}'; - case 'hubDetail.title': return 'Titel'; - case 'hubDetail.releaseYear': return 'Erscheinungsjahr'; - case 'hubDetail.dateAdded': return 'Hinzugefügt am'; - case 'hubDetail.rating': return 'Bewertung'; - case 'hubDetail.noItemsFound': return 'Keine Elemente gefunden'; - case 'logs.title': return 'Protokolle'; - case 'logs.clearLogs': return 'Protokolle löschen'; - case 'logs.copyLogs': return 'Protokolle kopieren'; - case 'logs.exportLogs': return 'Protokolle exportieren'; - case 'logs.noLogsToShow': return 'Keine Protokolle zum Anzeigen'; - case 'logs.error': return 'Fehler:'; - case 'logs.stackTrace': return 'Stacktrace:'; - case 'licenses.relatedPackages': return 'Verwandte Pakete'; - case 'licenses.license': return 'Lizenz'; - case 'licenses.licenseNumber': return ({required Object number}) => 'Lizenz ${number}'; - case 'licenses.licensesCount': return ({required Object count}) => '${count} Lizenzen'; - case 'navigation.home': return 'Start'; - case 'navigation.search': return 'Suche'; - case 'navigation.libraries': return 'Mediatheken'; - case 'navigation.settings': return 'Einstellungen'; - case 'playlists.title': return 'Wiedergabelisten'; - case 'playlists.noPlaylists': return 'Keine Wiedergabelisten gefunden'; - case 'playlists.create': return 'Wiedergabeliste erstellen'; - case 'playlists.newPlaylist': return 'Neue Wiedergabeliste'; - case 'playlists.playlistName': return 'Name der Wiedergabeliste'; - case 'playlists.enterPlaylistName': return 'Name der Wiedergabeliste eingeben'; - case 'playlists.edit': return 'Wiedergabeliste bearbeiten'; - case 'playlists.delete': return 'Wiedergabeliste löschen'; - case 'playlists.addTo': return 'Zur Wiedergabeliste hinzufügen'; - case 'playlists.addItems': return 'Elemente hinzufügen'; - case 'playlists.removeItem': return 'Aus Wiedergabeliste entfernen'; - case 'playlists.clearPlaylist': return 'Wiedergabeliste leeren'; - case 'playlists.playAll': return 'Alle abspielen'; - case 'playlists.shuffle': return 'Zufällig'; - case 'playlists.smartPlaylist': return 'Intelligente Wiedergabeliste'; - case 'playlists.regularPlaylist': return 'Normale Wiedergabeliste'; - case 'playlists.itemCount': return ({required Object count}) => '${count} Elemente'; - case 'playlists.oneItem': return '1 Element'; - case 'playlists.emptyPlaylist': return 'Diese Wiedergabeliste ist leer'; - case 'playlists.deleteConfirm': return 'Wiedergabeliste löschen?'; - case 'playlists.deleteMessage': return ({required Object name}) => 'Soll "${name}" wirklich gelöscht werden?'; - case 'playlists.created': return 'Wiedergabeliste erstellt'; - case 'playlists.updated': return 'Wiedergabeliste aktualisiert'; - case 'playlists.deleted': return 'Wiedergabeliste gelöscht'; - case 'playlists.itemAdded': return 'Zur Wiedergabeliste hinzugefügt'; - case 'playlists.itemRemoved': return 'Aus Wiedergabeliste entfernt'; - case 'playlists.selectPlaylist': return 'Wiedergabeliste auswählen'; - case 'playlists.createNewPlaylist': return 'Neue Wiedergabeliste erstellen'; - case 'playlists.errorCreating': return 'Wiedergabeliste konnte nicht erstellt werden'; - case 'playlists.errorDeleting': return 'Wiedergabeliste konnte nicht gelöscht werden'; - case 'playlists.errorLoading': return 'Wiedergabelisten konnten nicht geladen werden'; - case 'playlists.errorAdding': return 'Konnte nicht zur Wiedergabeliste hinzugefügt werden'; - case 'playlists.errorReordering': return 'Element der Wiedergabeliste konnte nicht neu geordnet werden'; - case 'playlists.errorRemoving': return 'Konnte nicht aus der Wiedergabeliste entfernt werden'; - case 'playlists.playlist': return 'Wiedergabeliste'; - case 'collections.title': return 'Sammlungen'; - case 'collections.collection': return 'Sammlung'; - case 'collections.empty': return 'Sammlung ist leer'; - case 'collections.noItems': return 'Keine Elemente in dieser Sammlung'; - case 'collections.unknownLibrarySection': return 'Löschen nicht möglich: Unbekannte Bibliothekssektion'; - case 'collections.deleteCollection': return 'Sammlung löschen'; - case 'collections.deleteConfirm': return ({required Object title}) => 'Sind Sie sicher, dass Sie "${title}" löschen möchten? Dies kann nicht rückgängig gemacht werden.'; - case 'collections.deleted': return 'Sammlung gelöscht'; - case 'collections.deleteFailed': return 'Sammlung konnte nicht gelöscht werden'; - case 'collections.deleteFailedWithError': return ({required Object error}) => 'Sammlung konnte nicht gelöscht werden: ${error}'; - case 'collections.failedToLoadItems': return ({required Object error}) => 'Sammlungselemente konnten nicht geladen werden: ${error}'; - case 'collections.addTo': return 'Zur Sammlung hinzufügen'; - case 'collections.selectCollection': return 'Sammlung auswählen'; - case 'collections.createNewCollection': return 'Neue Sammlung erstellen'; - case 'collections.collectionName': return 'Sammlungsname'; - case 'collections.enterCollectionName': return 'Sammlungsnamen eingeben'; - case 'collections.addedToCollection': return 'Zur Sammlung hinzugefügt'; - case 'collections.errorAddingToCollection': return 'Fehler beim Hinzufügen zur Sammlung'; - case 'collections.created': return 'Sammlung erstellt'; - case 'collections.removeFromCollection': return 'Aus Sammlung entfernen'; - case 'collections.removeFromCollectionConfirm': return ({required Object title}) => '"${title}" aus dieser Sammlung entfernen?'; - case 'collections.removedFromCollection': return 'Aus Sammlung entfernt'; - case 'collections.removeFromCollectionFailed': return 'Entfernen aus Sammlung fehlgeschlagen'; - case 'collections.removeFromCollectionError': return ({required Object error}) => 'Fehler beim Entfernen aus der Sammlung: ${error}'; - default: return null; - } - } + dynamic _flatMapFunction(String path) { + switch (path) { + case 'app.title': + return 'Plezy'; + case 'app.loading': + return 'Lädt...'; + case 'auth.signInWithPlex': + return 'Mit Plex anmelden'; + case 'auth.showQRCode': + return 'QR-Code anzeigen'; + case 'auth.cancel': + return 'Abbrechen'; + case 'auth.authenticate': + return 'Authentifizieren'; + case 'auth.retry': + return 'Erneut versuchen'; + case 'auth.debugEnterToken': + return 'Debug: Plex-Token eingeben'; + case 'auth.plexTokenLabel': + return 'Plex-Auth-Token'; + case 'auth.plexTokenHint': + return 'Plex.tv-Token eingeben'; + case 'auth.authenticationTimeout': + return 'Authentifizierung abgelaufen. Bitte erneut versuchen.'; + case 'auth.scanQRCodeInstruction': + return 'Diesen QR-Code mit einem bei Plex angemeldeten Gerät scannen, um zu authentifizieren.'; + case 'auth.waitingForAuth': + return 'Warte auf Authentifizierung...\nBitte Anmeldung im Browser abschließen.'; + case 'common.cancel': + return 'Abbrechen'; + case 'common.save': + return 'Speichern'; + case 'common.close': + return 'Schließen'; + case 'common.clear': + return 'Leeren'; + case 'common.reset': + return 'Zurücksetzen'; + case 'common.later': + return 'Später'; + case 'common.submit': + return 'Senden'; + case 'common.confirm': + return 'Bestätigen'; + case 'common.retry': + return 'Erneut versuchen'; + case 'common.playNow': + return 'Jetzt abspielen'; + case 'common.logout': + return 'Abmelden'; + case 'common.online': + return 'Online'; + case 'common.offline': + return 'Offline'; + case 'common.owned': + return 'Besitzer'; + case 'common.shared': + return 'Geteilt'; + case 'common.current': + return 'AKTUELL'; + case 'common.unknown': + return 'Unbekannt'; + case 'common.refresh': + return 'Aktualisieren'; + case 'common.yes': + return 'Ja'; + case 'common.no': + return 'Nein'; + case 'common.server': + return 'Server'; + case 'common.delete': + return 'Löschen'; + case 'common.shuffle': + return 'Zufall'; + case 'common.addTo': + return 'Hinzufügen zu...'; + case 'screens.licenses': + return 'Lizenzen'; + case 'screens.selectServer': + return 'Server auswählen'; + case 'screens.switchProfile': + return 'Profil wechseln'; + case 'screens.subtitleStyling': + return 'Untertitel-Stil'; + case 'screens.search': + return 'Suche'; + case 'screens.logs': + return 'Protokolle'; + case 'update.available': + return 'Update verfügbar'; + case 'update.versionAvailable': + return ({required Object version}) => + 'Version ${version} ist verfügbar'; + case 'update.currentVersion': + return ({required Object version}) => 'Aktuell: ${version}'; + case 'update.skipVersion': + return 'Diese Version überspringen'; + case 'update.viewRelease': + return 'Release anzeigen'; + case 'update.latestVersion': + return 'Aktuellste Version installiert'; + case 'update.checkFailed': + return 'Fehler bei der Updateprüfung'; + case 'settings.title': + return 'Einstellungen'; + case 'settings.language': + return 'Sprache'; + case 'settings.theme': + return 'Design'; + case 'settings.appearance': + return 'Darstellung'; + case 'settings.videoPlayback': + return 'Videowiedergabe'; + case 'settings.shufflePlay': + return 'Zufallswiedergabe'; + case 'settings.advanced': + return 'Erweitert'; + case 'settings.useSeasonPostersDescription': + return 'Staffelposter statt Serienposter für Episoden anzeigen'; + case 'settings.showHeroSectionDescription': + return 'Bereich mit empfohlenen Inhalten auf der Startseite anzeigen'; + case 'settings.secondsLabel': + return 'Sekunden'; + case 'settings.minutesLabel': + return 'Minuten'; + case 'settings.secondsShort': + return 's'; + case 'settings.minutesShort': + return 'm'; + case 'settings.durationHint': + return ({required Object min, required Object max}) => + 'Dauer eingeben (${min}-${max})'; + case 'settings.systemTheme': + return 'System'; + case 'settings.systemThemeDescription': + return 'Systemeinstellungen folgen'; + case 'settings.lightTheme': + return 'Hell'; + case 'settings.darkTheme': + return 'Dunkel'; + case 'settings.libraryDensity': + return 'Mediathekdichte'; + case 'settings.compact': + return 'Kompakt'; + case 'settings.compactDescription': + return 'Kleinere Karten, mehr Elemente sichtbar'; + case 'settings.normal': + return 'Normal'; + case 'settings.normalDescription': + return 'Standardgröße'; + case 'settings.comfortable': + return 'Großzügig'; + case 'settings.comfortableDescription': + return 'Größere Karten, weniger Elemente sichtbar'; + case 'settings.viewMode': + return 'Ansichtsmodus'; + case 'settings.gridView': + return 'Raster'; + case 'settings.gridViewDescription': + return 'Elemente im Raster anzeigen'; + case 'settings.listView': + return 'Liste'; + case 'settings.listViewDescription': + return 'Elemente in Listenansicht anzeigen'; + case 'settings.useSeasonPosters': + return 'Staffelposter verwenden'; + case 'settings.showHeroSection': + return 'Hero-Bereich anzeigen'; + case 'settings.hardwareDecoding': + return 'Hardware-Decodierung'; + case 'settings.hardwareDecodingDescription': + return 'Hardwarebeschleunigung verwenden, sofern verfügbar'; + case 'settings.bufferSize': + return 'Puffergröße'; + case 'settings.bufferSizeMB': + return ({required Object size}) => '${size}MB'; + case 'settings.subtitleStyling': + return 'Untertitel-Stil'; + case 'settings.subtitleStylingDescription': + return 'Aussehen von Untertiteln anpassen'; + case 'settings.smallSkipDuration': + return 'Kleine Sprungdauer'; + case 'settings.largeSkipDuration': + return 'Große Sprungdauer'; + case 'settings.secondsUnit': + return ({required Object seconds}) => '${seconds} Sekunden'; + case 'settings.defaultSleepTimer': + return 'Standard-Sleep-Timer'; + case 'settings.minutesUnit': + return ({required Object minutes}) => '${minutes} Minuten'; + case 'settings.rememberTrackSelections': + return 'Spurauswahl pro Serie/Film merken'; + case 'settings.rememberTrackSelectionsDescription': + return 'Audio- und Untertitelsprache automatisch speichern, wenn während der Wiedergabe geändert'; + case 'settings.unwatchedOnly': + return 'Nur ungesehene'; + case 'settings.unwatchedOnlyDescription': + return 'Nur ungesehene Episoden in die Shuffle-Warteschlange aufnehmen'; + case 'settings.shuffleOrderNavigation': + return 'Navigation der Shuffle-Reihenfolge'; + case 'settings.shuffleOrderNavigationDescription': + return 'Weiter/Zurück folgt der zufälligen Reihenfolge'; + case 'settings.loopShuffleQueue': + return 'Shuffle-Warteschlange wiederholen'; + case 'settings.loopShuffleQueueDescription': + return 'Warteschlange neu starten, wenn das Ende erreicht ist'; + case 'settings.videoPlayerControls': + return 'Videoplayer-Steuerung'; + case 'settings.keyboardShortcuts': + return 'Tastenkürzel'; + case 'settings.keyboardShortcutsDescription': + return 'Tastenkürzel anpassen'; + case 'settings.debugLogging': + return 'Debug-Protokollierung'; + case 'settings.debugLoggingDescription': + return 'Detaillierte Protokolle zur Fehleranalyse aktivieren'; + case 'settings.viewLogs': + return 'Protokolle anzeigen'; + case 'settings.viewLogsDescription': + return 'App-Protokolle anzeigen'; + case 'settings.clearCache': + return 'Cache löschen'; + case 'settings.clearCacheDescription': + return 'Löscht alle zwischengespeicherten Bilder und Daten. Die App kann danach langsamer laden.'; + case 'settings.clearCacheSuccess': + return 'Cache erfolgreich gelöscht'; + case 'settings.resetSettings': + return 'Einstellungen zurücksetzen'; + case 'settings.resetSettingsDescription': + return 'Alle Einstellungen auf Standard zurücksetzen. Dies kann nicht rückgängig gemacht werden.'; + case 'settings.resetSettingsSuccess': + return 'Einstellungen erfolgreich zurückgesetzt'; + case 'settings.shortcutsReset': + return 'Tastenkürzel auf Standard zurückgesetzt'; + case 'settings.about': + return 'Über'; + case 'settings.aboutDescription': + return 'App-Informationen und Lizenzen'; + case 'settings.updates': + return 'Updates'; + case 'settings.updateAvailable': + return 'Update verfügbar'; + case 'settings.checkForUpdates': + return 'Nach Updates suchen'; + case 'settings.validationErrorEnterNumber': + return 'Bitte eine gültige Zahl eingeben'; + case 'settings.validationErrorDuration': + return ({ + required Object min, + required Object max, + required Object unit, + }) => 'Dauer muss zwischen ${min} und ${max} ${unit} liegen'; + case 'settings.shortcutAlreadyAssigned': + return ({required Object action}) => + 'Tastenkürzel bereits zugewiesen an ${action}'; + case 'settings.shortcutUpdated': + return ({required Object action}) => + 'Tastenkürzel aktualisiert für ${action}'; + case 'search.hint': + return 'Filme, Serien, Musik suchen...'; + case 'search.tryDifferentTerm': + return 'Anderen Suchbegriff versuchen'; + case 'search.searchYourMedia': + return 'In den eigenen Medien suchen'; + case 'search.enterTitleActorOrKeyword': + return 'Titel, Schauspieler oder Stichwort eingeben'; + case 'hotkeys.setShortcutFor': + return ({required Object actionName}) => + 'Tastenkürzel festlegen für ${actionName}'; + case 'hotkeys.clearShortcut': + return 'Kürzel löschen'; + case 'pinEntry.enterPin': + return 'PIN eingeben'; + case 'pinEntry.showPin': + return 'PIN anzeigen'; + case 'pinEntry.hidePin': + return 'PIN verbergen'; + case 'fileInfo.title': + return 'Dateiinfo'; + case 'fileInfo.video': + return 'Video'; + case 'fileInfo.audio': + return 'Audio'; + case 'fileInfo.file': + return 'Datei'; + case 'fileInfo.advanced': + return 'Erweitert'; + case 'fileInfo.codec': + return 'Codec'; + case 'fileInfo.resolution': + return 'Auflösung'; + case 'fileInfo.bitrate': + return 'Bitrate'; + case 'fileInfo.frameRate': + return 'Bildrate'; + case 'fileInfo.aspectRatio': + return 'Seitenverhältnis'; + case 'fileInfo.profile': + return 'Profil'; + case 'fileInfo.bitDepth': + return 'Farbtiefe'; + case 'fileInfo.colorSpace': + return 'Farbraum'; + case 'fileInfo.colorRange': + return 'Farbbereich'; + case 'fileInfo.colorPrimaries': + return 'Primärfarben'; + case 'fileInfo.chromaSubsampling': + return 'Chroma-Subsampling'; + case 'fileInfo.channels': + return 'Kanäle'; + case 'fileInfo.path': + return 'Pfad'; + case 'fileInfo.size': + return 'Größe'; + case 'fileInfo.container': + return 'Container'; + case 'fileInfo.duration': + return 'Dauer'; + case 'fileInfo.optimizedForStreaming': + return 'Für Streaming optimiert'; + case 'fileInfo.has64bitOffsets': + return '64-Bit-Offsets'; + case 'mediaMenu.markAsWatched': + return 'Als gesehen markieren'; + case 'mediaMenu.markAsUnwatched': + return 'Als ungesehen markieren'; + case 'mediaMenu.removeFromContinueWatching': + return 'Aus ‚Weiterschauen‘ entfernen'; + case 'mediaMenu.goToSeries': + return 'Zur Serie'; + case 'mediaMenu.goToSeason': + return 'Zur Staffel'; + case 'mediaMenu.shufflePlay': + return 'Zufallswiedergabe'; + case 'mediaMenu.fileInfo': + return 'Dateiinfo'; + case 'accessibility.mediaCardMovie': + return ({required Object title}) => '${title}, Film'; + case 'accessibility.mediaCardShow': + return ({required Object title}) => '${title}, Serie'; + case 'accessibility.mediaCardEpisode': + return ({required Object title, required Object episodeInfo}) => + '${title}, ${episodeInfo}'; + case 'accessibility.mediaCardSeason': + return ({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + case 'accessibility.mediaCardWatched': + return 'angesehen'; + case 'accessibility.mediaCardPartiallyWatched': + return ({required Object percent}) => '${percent} Prozent angesehen'; + case 'accessibility.mediaCardUnwatched': + return 'ungeschaut'; + case 'accessibility.tapToPlay': + return 'Zum Abspielen tippen'; + case 'tooltips.shufflePlay': + return 'Zufallswiedergabe'; + case 'tooltips.markAsWatched': + return 'Als gesehen markieren'; + case 'tooltips.markAsUnwatched': + return 'Als ungesehen markieren'; + case 'videoControls.audioLabel': + return 'Audio'; + case 'videoControls.subtitlesLabel': + return 'Untertitel'; + case 'videoControls.resetToZero': + return 'Auf 0 ms zurücksetzen'; + case 'videoControls.addTime': + return ({required Object amount, required Object unit}) => + '+${amount}${unit}'; + case 'videoControls.minusTime': + return ({required Object amount, required Object unit}) => + '-${amount}${unit}'; + case 'videoControls.playsLater': + return ({required Object label}) => '${label} spielt später'; + case 'videoControls.playsEarlier': + return ({required Object label}) => '${label} spielt früher'; + case 'videoControls.noOffset': + return 'Kein Offset'; + case 'videoControls.letterbox': + return 'Letterbox'; + case 'videoControls.fillScreen': + return 'Bild füllen'; + case 'videoControls.stretch': + return 'Strecken'; + case 'videoControls.lockRotation': + return 'Rotation sperren'; + case 'videoControls.unlockRotation': + return 'Rotation entsperren'; + case 'videoControls.sleepTimer': + return 'Schlaftimer'; + case 'videoControls.timerActive': + return 'Schlaftimer aktiv'; + case 'videoControls.playbackWillPauseIn': + return ({required Object duration}) => + 'Wiedergabe wird in ${duration} pausiert'; + case 'videoControls.sleepTimerCompleted': + return 'Schlaftimer abgelaufen – Wiedergabe pausiert'; + case 'videoControls.playButton': + return 'Wiedergeben'; + case 'videoControls.pauseButton': + return 'Pause'; + case 'videoControls.seekBackwardButton': + return ({required Object seconds}) => '${seconds} Sekunden zurück'; + case 'videoControls.seekForwardButton': + return ({required Object seconds}) => '${seconds} Sekunden vor'; + case 'videoControls.previousButton': + return 'Vorherige Episode'; + case 'videoControls.nextButton': + return 'Nächste Episode'; + case 'videoControls.previousChapterButton': + return 'Vorheriges Kapitel'; + case 'videoControls.nextChapterButton': + return 'Nächstes Kapitel'; + case 'videoControls.volumeButton': + return 'Lautstärke'; + case 'videoControls.muteButton': + return 'Stumm schalten'; + case 'videoControls.unmuteButton': + return 'Stummschaltung aufheben'; + case 'videoControls.settingsButton': + return 'Videoeinstellungen'; + case 'videoControls.audioTrackButton': + return 'Tonspuren'; + case 'videoControls.subtitlesButton': + return 'Untertitel'; + case 'videoControls.chaptersButton': + return 'Kapitel'; + case 'videoControls.versionsButton': + return 'Videoversionen'; + case 'videoControls.aspectRatioButton': + return 'Seitenverhältnis'; + case 'videoControls.fullscreenButton': + return 'Vollbild aktivieren'; + case 'videoControls.exitFullscreenButton': + return 'Vollbild verlassen'; + case 'videoControls.rotationLockButton': + return 'Dreh­sperre'; + case 'videoControls.timelineSlider': + return 'Video-Zeitleiste'; + case 'videoControls.volumeSlider': + return 'Lautstärkepegel'; + case 'videoControls.backButton': + return 'Zurück'; + case 'userStatus.admin': + return 'Eigentümer'; + case 'userStatus.restricted': + return 'Eingeschränkt'; + case 'userStatus.protected': + return 'Geschützt'; + case 'userStatus.current': + return 'AKTUELL'; + case 'messages.markedAsWatched': + return 'Als gesehen markiert'; + case 'messages.markedAsUnwatched': + return 'Als ungesehen markiert'; + case 'messages.removedFromContinueWatching': + return 'Aus ‚Weiterschauen‘ entfernt'; + case 'messages.errorLoading': + return ({required Object error}) => 'Fehler: ${error}'; + case 'messages.fileInfoNotAvailable': + return 'Dateiinfo nicht verfügbar'; + case 'messages.errorLoadingFileInfo': + return ({required Object error}) => + 'Fehler beim Laden der Dateiinfo: ${error}'; + case 'messages.errorLoadingSeries': + return 'Fehler beim Laden der Serie'; + case 'messages.errorLoadingSeason': + return 'Fehler beim Laden der Staffel'; + case 'messages.musicNotSupported': + return 'Musikwiedergabe wird noch nicht unterstützt'; + case 'messages.logsCleared': + return 'Protokolle gelöscht'; + case 'messages.logsCopied': + return 'Protokolle in Zwischenablage kopiert'; + case 'messages.noLogsAvailable': + return 'Keine Protokolle verfügbar'; + case 'messages.libraryScanning': + return ({required Object title}) => 'Scanne „${title}“...'; + case 'messages.libraryScanStarted': + return ({required Object title}) => + 'Mediathekscan gestartet für „${title}“'; + case 'messages.libraryScanFailed': + return ({required Object error}) => + 'Fehler beim Scannen der Mediathek: ${error}'; + case 'messages.metadataRefreshing': + return ({required Object title}) => + 'Metadaten werden aktualisiert für „${title}“...'; + case 'messages.metadataRefreshStarted': + return ({required Object title}) => + 'Metadaten-Aktualisierung gestartet für „${title}“'; + case 'messages.metadataRefreshFailed': + return ({required Object error}) => + 'Metadaten konnten nicht aktualisiert werden: ${error}'; + case 'messages.noPlexToken': + return 'Kein Plex-Token gefunden. Bitte erneut anmelden.'; + case 'messages.logoutConfirm': + return 'Abmeldung wirklich durchführen?'; + case 'messages.noSeasonsFound': + return 'Keine Staffeln gefunden'; + case 'messages.noEpisodesFound': + return 'Keine Episoden in der ersten Staffel gefunden'; + case 'messages.noEpisodesFoundGeneral': + return 'Keine Episoden gefunden'; + case 'messages.noResultsFound': + return 'Keine Ergebnisse gefunden'; + case 'messages.sleepTimerSet': + return ({required Object label}) => 'Sleep-Timer gesetzt auf ${label}'; + case 'messages.failedToSwitchProfile': + return ({required Object displayName}) => + 'Profilwechsel zu ${displayName} fehlgeschlagen'; + case 'messages.noItemsAvailable': + return 'Keine Elemente verfügbar'; + case 'messages.failedToCreatePlayQueue': + return 'Wiedergabewarteschlange konnte nicht erstellt werden'; + case 'messages.failedToCreatePlayQueueNoItems': + return 'Wiedergabewarteschlange konnte nicht erstellt werden – keine Elemente'; + case 'messages.failedPlayback': + return ({required Object action, required Object error}) => + 'Wiedergabe für ${action} fehlgeschlagen: ${error}'; + case 'profile.noUsersAvailable': + return 'Keine Benutzer verfügbar'; + case 'subtitlingStyling.stylingOptions': + return 'Stiloptionen'; + case 'subtitlingStyling.fontSize': + return 'Schriftgröße'; + case 'subtitlingStyling.textColor': + return 'Textfarbe'; + case 'subtitlingStyling.borderSize': + return 'Rahmengröße'; + case 'subtitlingStyling.borderColor': + return 'Rahmenfarbe'; + case 'subtitlingStyling.backgroundOpacity': + return 'Hintergrunddeckkraft'; + case 'subtitlingStyling.backgroundColor': + return 'Hintergrundfarbe'; + case 'dialog.confirmAction': + return 'Aktion bestätigen'; + case 'dialog.areYouSure': + return 'Aktion wirklich ausführen?'; + case 'dialog.cancel': + return 'Abbrechen'; + case 'dialog.playNow': + return 'Jetzt abspielen'; + case 'discover.title': + return 'Entdecken'; + case 'discover.switchProfile': + return 'Profil wechseln'; + case 'discover.switchServer': + return 'Server wechseln'; + case 'discover.logout': + return 'Abmelden'; + case 'discover.noContentAvailable': + return 'Kein Inhalt verfügbar'; + case 'discover.addMediaToLibraries': + return 'Medien zur Mediathek hinzufügen'; + case 'discover.continueWatching': + return 'Weiterschauen'; + case 'discover.recentlyAdded': + return 'Kürzlich hinzugefügt'; + case 'discover.play': + return 'Abspielen'; + case 'discover.resume': + return 'Fortsetzen'; + case 'discover.playEpisode': + return ({required Object season, required Object episode}) => + 'S${season}, E${episode} - Abspielen'; + case 'discover.resumeEpisode': + return ({required Object season, required Object episode}) => + 'S${season}, E${episode} - Fortsetzen'; + case 'discover.pause': + return 'Pause'; + case 'discover.overview': + return 'Übersicht'; + case 'discover.cast': + return 'Besetzung'; + case 'discover.seasons': + return 'Staffeln'; + case 'discover.studio': + return 'Studio'; + case 'discover.rating': + return 'Altersfreigabe'; + case 'discover.watched': + return 'Gesehen'; + case 'discover.episodeCount': + return ({required Object count}) => '${count} Episoden'; + case 'discover.watchedProgress': + return ({required Object watched, required Object total}) => + '${watched} von ${total} gesehen'; + case 'discover.movie': + return 'Film'; + case 'discover.tvShow': + return 'Serie'; + case 'discover.minutesLeft': + return ({required Object minutes}) => '${minutes} Min übrig'; + case 'errors.searchFailed': + return ({required Object error}) => 'Suche fehlgeschlagen: ${error}'; + case 'errors.connectionTimeout': + return ({required Object context}) => + 'Zeitüberschreitung beim Laden von ${context}'; + case 'errors.connectionFailed': + return 'Verbindung zum Plex-Server fehlgeschlagen'; + case 'errors.failedToLoad': + return ({required Object context, required Object error}) => + 'Fehler beim Laden von ${context}: ${error}'; + case 'errors.noClientAvailable': + return 'Kein Client verfügbar'; + case 'errors.authenticationFailed': + return ({required Object error}) => + 'Authentifizierung fehlgeschlagen: ${error}'; + case 'errors.couldNotLaunchUrl': + return 'Auth-URL konnte nicht geöffnet werden'; + case 'errors.pleaseEnterToken': + return 'Bitte Token eingeben'; + case 'errors.invalidToken': + return 'Ungültiges Token'; + case 'errors.failedToVerifyToken': + return ({required Object error}) => + 'Token-Verifizierung fehlgeschlagen: ${error}'; + case 'errors.failedToSwitchProfile': + return ({required Object displayName}) => + 'Profilwechsel zu ${displayName} fehlgeschlagen'; + case 'errors.connectionFailedGeneric': + return 'Verbindung fehlgeschlagen'; + case 'libraries.title': + return 'Mediatheken'; + case 'libraries.scanLibraryFiles': + return 'Mediatheksdateien scannen'; + case 'libraries.scanLibrary': + return 'Mediathek scannen'; + case 'libraries.analyze': + return 'Analysieren'; + case 'libraries.analyzeLibrary': + return 'Mediathek analysieren'; + case 'libraries.refreshMetadata': + return 'Metadaten aktualisieren'; + case 'libraries.emptyTrash': + return 'Papierkorb leeren'; + case 'libraries.emptyingTrash': + return ({required Object title}) => + 'Papierkorb für „${title}“ wird geleert...'; + case 'libraries.trashEmptied': + return ({required Object title}) => 'Papierkorb für „${title}“ geleert'; + case 'libraries.failedToEmptyTrash': + return ({required Object error}) => + 'Papierkorb konnte nicht geleert werden: ${error}'; + case 'libraries.analyzing': + return ({required Object title}) => 'Analysiere „${title}“...'; + case 'libraries.analysisStarted': + return ({required Object title}) => 'Analyse gestartet für „${title}“'; + case 'libraries.failedToAnalyze': + return ({required Object error}) => + 'Analyse der Mediathek fehlgeschlagen: ${error}'; + case 'libraries.noLibrariesFound': + return 'Keine Mediatheken gefunden'; + case 'libraries.thisLibraryIsEmpty': + return 'Diese Mediathek ist leer'; + case 'libraries.all': + return 'Alle'; + case 'libraries.clearAll': + return 'Alle löschen'; + case 'libraries.scanLibraryConfirm': + return ({required Object title}) => '„${title}“ wirklich scannen?'; + case 'libraries.analyzeLibraryConfirm': + return ({required Object title}) => '„${title}“ wirklich analysieren?'; + case 'libraries.refreshMetadataConfirm': + return ({required Object title}) => + 'Metadaten für „${title}“ wirklich aktualisieren?'; + case 'libraries.emptyTrashConfirm': + return ({required Object title}) => + 'Papierkorb für „${title}“ wirklich leeren?'; + case 'libraries.manageLibraries': + return 'Mediatheken verwalten'; + case 'libraries.sort': + return 'Sortieren'; + case 'libraries.sortBy': + return 'Sortieren nach'; + case 'libraries.filters': + return 'Filter'; + case 'libraries.loadingLibraryWithCount': + return ({required Object count}) => + 'Lade Mediathek... (${count} Elemente geladen)'; + case 'libraries.confirmActionMessage': + return 'Aktion wirklich durchführen?'; + case 'libraries.showLibrary': + return 'Mediathek anzeigen'; + case 'libraries.hideLibrary': + return 'Mediathek ausblenden'; + case 'libraries.libraryOptions': + return 'Mediatheksoptionen'; + case 'libraries.content': + return 'Bibliotheksinhalt'; + case 'libraries.selectLibrary': + return 'Bibliothek auswählen'; + case 'libraries.filtersWithCount': + return ({required Object count}) => 'Filter (${count})'; + case 'libraries.noRecommendations': + return 'Keine Empfehlungen verfügbar'; + case 'libraries.noCollections': + return 'Keine Sammlungen in dieser Mediathek'; + case 'libraries.noFoldersFound': + return 'Keine Ordner gefunden'; + case 'libraries.folders': + return 'Ordner'; + case 'libraries.tabs.recommended': + return 'Empfohlen'; + case 'libraries.tabs.browse': + return 'Durchsuchen'; + case 'libraries.tabs.collections': + return 'Sammlungen'; + case 'libraries.tabs.playlists': + return 'Wiedergabelisten'; + case 'libraries.groupings.all': + return 'Alle'; + case 'libraries.groupings.movies': + return 'Filme'; + case 'libraries.groupings.shows': + return 'Serien'; + case 'libraries.groupings.seasons': + return 'Staffeln'; + case 'libraries.groupings.episodes': + return 'Episoden'; + case 'libraries.groupings.folders': + return 'Ordner'; + case 'about.title': + return 'Über'; + case 'about.openSourceLicenses': + return 'Open-Source-Lizenzen'; + case 'about.versionLabel': + return ({required Object version}) => 'Version ${version}'; + case 'about.appDescription': + return 'Ein schöner Plex-Client für Flutter'; + case 'about.viewLicensesDescription': + return 'Lizenzen von Drittanbieter-Bibliotheken anzeigen'; + case 'serverSelection.connectingToServer': + return 'Verbinde mit Server...'; + case 'serverSelection.connectingToAllServers': + return 'Verbinde mit allen Servern...'; + case 'serverSelection.connectToAllServers': + return 'Mit allen Servern verbinden'; + case 'serverSelection.allServerConnectionsFailed': + return 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.'; + case 'serverSelection.foundServers': + return ({required Object count}) => '${count} Server gefunden'; + case 'serverSelection.orSelectIndividually': + return 'Oder einzelnen Server auswählen:'; + case 'serverSelection.serverDebugCopied': + return 'Server-Debugdaten kopiert'; + case 'serverSelection.copyDebugData': + return 'Debugdaten kopieren'; + case 'serverSelection.noServersFound': + return 'Keine Server gefunden'; + case 'serverSelection.malformedServerData': + return ({required Object count}) => + '${count} Server mit fehlerhaften Daten gefunden. Keine gültigen Server verfügbar.'; + case 'serverSelection.incompleteServerInfo': + return 'Einige Serverinformationen sind unvollständig und wurden übersprungen. Plex.tv-Konto prüfen.'; + case 'serverSelection.incompleteConnectionInfo': + return 'Serververbindungsinformationen unvollständig. Bitte erneut versuchen.'; + case 'serverSelection.malformedServerInfo': + return ({required Object message}) => + 'Serverinformationen fehlerhaft: ${message}'; + case 'serverSelection.networkConnectionFailed': + return 'Netzwerkverbindung fehlgeschlagen. Internetverbindung prüfen und erneut versuchen.'; + case 'serverSelection.authenticationFailed': + return 'Authentifizierung fehlgeschlagen. Bitte erneut anmelden.'; + case 'serverSelection.plexServiceUnavailable': + return 'Plex-Dienst nicht verfügbar. Bitte später erneut versuchen.'; + case 'serverSelection.failedToLoadServers': + return ({required Object error}) => + 'Server konnten nicht geladen werden: ${error}'; + case 'hubDetail.title': + return 'Titel'; + case 'hubDetail.releaseYear': + return 'Erscheinungsjahr'; + case 'hubDetail.dateAdded': + return 'Hinzugefügt am'; + case 'hubDetail.rating': + return 'Bewertung'; + case 'hubDetail.noItemsFound': + return 'Keine Elemente gefunden'; + case 'logs.title': + return 'Protokolle'; + case 'logs.clearLogs': + return 'Protokolle löschen'; + case 'logs.copyLogs': + return 'Protokolle kopieren'; + case 'logs.exportLogs': + return 'Protokolle exportieren'; + case 'logs.noLogsToShow': + return 'Keine Protokolle zum Anzeigen'; + case 'logs.error': + return 'Fehler:'; + case 'logs.stackTrace': + return 'Stacktrace:'; + case 'licenses.relatedPackages': + return 'Verwandte Pakete'; + case 'licenses.license': + return 'Lizenz'; + case 'licenses.licenseNumber': + return ({required Object number}) => 'Lizenz ${number}'; + case 'licenses.licensesCount': + return ({required Object count}) => '${count} Lizenzen'; + case 'navigation.home': + return 'Start'; + case 'navigation.search': + return 'Suche'; + case 'navigation.libraries': + return 'Mediatheken'; + case 'navigation.settings': + return 'Einstellungen'; + case 'playlists.title': + return 'Wiedergabelisten'; + case 'playlists.noPlaylists': + return 'Keine Wiedergabelisten gefunden'; + case 'playlists.create': + return 'Wiedergabeliste erstellen'; + case 'playlists.newPlaylist': + return 'Neue Wiedergabeliste'; + case 'playlists.playlistName': + return 'Name der Wiedergabeliste'; + case 'playlists.enterPlaylistName': + return 'Name der Wiedergabeliste eingeben'; + case 'playlists.edit': + return 'Wiedergabeliste bearbeiten'; + case 'playlists.delete': + return 'Wiedergabeliste löschen'; + case 'playlists.addTo': + return 'Zur Wiedergabeliste hinzufügen'; + case 'playlists.addItems': + return 'Elemente hinzufügen'; + case 'playlists.removeItem': + return 'Aus Wiedergabeliste entfernen'; + case 'playlists.clearPlaylist': + return 'Wiedergabeliste leeren'; + case 'playlists.playAll': + return 'Alle abspielen'; + case 'playlists.shuffle': + return 'Zufällig'; + case 'playlists.smartPlaylist': + return 'Intelligente Wiedergabeliste'; + case 'playlists.regularPlaylist': + return 'Normale Wiedergabeliste'; + case 'playlists.itemCount': + return ({required Object count}) => '${count} Elemente'; + case 'playlists.oneItem': + return '1 Element'; + case 'playlists.emptyPlaylist': + return 'Diese Wiedergabeliste ist leer'; + case 'playlists.deleteConfirm': + return 'Wiedergabeliste löschen?'; + case 'playlists.deleteMessage': + return ({required Object name}) => + 'Soll "${name}" wirklich gelöscht werden?'; + case 'playlists.created': + return 'Wiedergabeliste erstellt'; + case 'playlists.updated': + return 'Wiedergabeliste aktualisiert'; + case 'playlists.deleted': + return 'Wiedergabeliste gelöscht'; + case 'playlists.itemAdded': + return 'Zur Wiedergabeliste hinzugefügt'; + case 'playlists.itemRemoved': + return 'Aus Wiedergabeliste entfernt'; + case 'playlists.selectPlaylist': + return 'Wiedergabeliste auswählen'; + case 'playlists.createNewPlaylist': + return 'Neue Wiedergabeliste erstellen'; + case 'playlists.errorCreating': + return 'Wiedergabeliste konnte nicht erstellt werden'; + case 'playlists.errorDeleting': + return 'Wiedergabeliste konnte nicht gelöscht werden'; + case 'playlists.errorLoading': + return 'Wiedergabelisten konnten nicht geladen werden'; + case 'playlists.errorAdding': + return 'Konnte nicht zur Wiedergabeliste hinzugefügt werden'; + case 'playlists.errorReordering': + return 'Element der Wiedergabeliste konnte nicht neu geordnet werden'; + case 'playlists.errorRemoving': + return 'Konnte nicht aus der Wiedergabeliste entfernt werden'; + case 'playlists.playlist': + return 'Wiedergabeliste'; + case 'collections.title': + return 'Sammlungen'; + case 'collections.collection': + return 'Sammlung'; + case 'collections.empty': + return 'Sammlung ist leer'; + case 'collections.noItems': + return 'Keine Elemente in dieser Sammlung'; + case 'collections.unknownLibrarySection': + return 'Löschen nicht möglich: Unbekannte Bibliothekssektion'; + case 'collections.deleteCollection': + return 'Sammlung löschen'; + case 'collections.deleteConfirm': + return ({required Object title}) => + 'Sind Sie sicher, dass Sie "${title}" löschen möchten? Dies kann nicht rückgängig gemacht werden.'; + case 'collections.deleted': + return 'Sammlung gelöscht'; + case 'collections.deleteFailed': + return 'Sammlung konnte nicht gelöscht werden'; + case 'collections.deleteFailedWithError': + return ({required Object error}) => + 'Sammlung konnte nicht gelöscht werden: ${error}'; + case 'collections.failedToLoadItems': + return ({required Object error}) => + 'Sammlungselemente konnten nicht geladen werden: ${error}'; + case 'collections.addTo': + return 'Zur Sammlung hinzufügen'; + case 'collections.selectCollection': + return 'Sammlung auswählen'; + case 'collections.createNewCollection': + return 'Neue Sammlung erstellen'; + case 'collections.collectionName': + return 'Sammlungsname'; + case 'collections.enterCollectionName': + return 'Sammlungsnamen eingeben'; + case 'collections.addedToCollection': + return 'Zur Sammlung hinzugefügt'; + case 'collections.errorAddingToCollection': + return 'Fehler beim Hinzufügen zur Sammlung'; + case 'collections.created': + return 'Sammlung erstellt'; + case 'collections.removeFromCollection': + return 'Aus Sammlung entfernen'; + case 'collections.removeFromCollectionConfirm': + return ({required Object title}) => + '"${title}" aus dieser Sammlung entfernen?'; + case 'collections.removedFromCollection': + return 'Aus Sammlung entfernt'; + case 'collections.removeFromCollectionFailed': + return 'Entfernen aus Sammlung fehlgeschlagen'; + case 'collections.removeFromCollectionError': + return ({required Object error}) => + 'Fehler beim Entfernen aus der Sammlung: ${error}'; + default: + return null; + } + } } extension on _StringsIt { - dynamic _flatMapFunction(String path) { - switch (path) { - case 'app.title': return 'Plezy'; - case 'app.loading': return 'Caricamento...'; - case 'auth.signInWithPlex': return 'Accedi con Plex'; - case 'auth.showQRCode': return 'Mostra QR Code'; - case 'auth.cancel': return 'Cancella'; - case 'auth.authenticate': return 'Autenticazione'; - case 'auth.retry': return 'Riprova'; - case 'auth.debugEnterToken': return 'Debug: Inserisci Token Plex'; - case 'auth.plexTokenLabel': return 'Token Auth Plex'; - case 'auth.plexTokenHint': return 'Inserisci il tuo token di Plex.tv'; - case 'auth.authenticationTimeout': return 'Autenticazione scaduta. Riprova.'; - case 'auth.scanQRCodeInstruction': return 'Scansiona questo QR code con un dispositivo connesso a Plex per autenticarti.'; - case 'auth.waitingForAuth': return 'In attesa di autenticazione...\nCompleta l\'accesso dal tuo browser.'; - case 'common.cancel': return 'Cancella'; - case 'common.save': return 'Salva'; - case 'common.close': return 'Chiudi'; - case 'common.clear': return 'Pulisci'; - case 'common.reset': return 'Ripristina'; - case 'common.later': return 'Più tardi'; - case 'common.submit': return 'Invia'; - case 'common.confirm': return 'Conferma'; - case 'common.retry': return 'Riprova'; - case 'common.playNow': return 'Riproduci ora'; - case 'common.logout': return 'Disconnetti'; - case 'common.online': return 'Online'; - case 'common.offline': return 'Offline'; - case 'common.owned': return 'Di proprietà'; - case 'common.shared': return 'Condiviso'; - case 'common.current': return 'CORRENTE'; - case 'common.unknown': return 'Sconosciuto'; - case 'common.refresh': return 'Aggiorna'; - case 'common.yes': return 'Sì'; - case 'common.no': return 'No'; - case 'common.server': return 'Server'; - case 'common.delete': return 'Elimina'; - case 'common.shuffle': return 'Casuale'; - case 'common.addTo': return 'Aggiungi a...'; - case 'screens.licenses': return 'Licenze'; - case 'screens.selectServer': return 'Seleziona server'; - case 'screens.switchProfile': return 'Cambia profilo'; - case 'screens.subtitleStyling': return 'Stile sottotitoli'; - case 'screens.search': return 'Cerca'; - case 'screens.logs': return 'Registro'; - case 'update.available': return 'Aggiornamento disponibile'; - case 'update.versionAvailable': return ({required Object version}) => 'Versione ${version} disponibile'; - case 'update.currentVersion': return ({required Object version}) => 'Corrente: ${version}'; - case 'update.skipVersion': return 'Salta questa versione'; - case 'update.viewRelease': return 'Visualizza dettagli release'; - case 'update.latestVersion': return 'La versione installata è l\'ultima disponibile'; - case 'update.checkFailed': return 'Impossibile controllare gli aggiornamenti'; - case 'settings.title': return 'Impostazioni'; - case 'settings.language': return 'Lingua'; - case 'settings.theme': return 'Tema'; - case 'settings.appearance': return 'Aspetto'; - case 'settings.videoPlayback': return 'Riproduzione video'; - case 'settings.shufflePlay': return 'Riproduzione casuale'; - case 'settings.advanced': return 'Avanzate'; - case 'settings.useSeasonPostersDescription': return 'Mostra il poster della stagione invece del poster della serie per gli episodi'; - case 'settings.showHeroSectionDescription': return 'Visualizza il carosello dei contenuti in primo piano sulla schermata iniziale'; - case 'settings.secondsLabel': return 'Secondi'; - case 'settings.minutesLabel': return 'Minuti'; - case 'settings.secondsShort': return 's'; - case 'settings.minutesShort': return 'm'; - case 'settings.durationHint': return ({required Object min, required Object max}) => 'Inserisci durata (${min}-${max})'; - case 'settings.systemTheme': return 'Sistema'; - case 'settings.systemThemeDescription': return 'Segui le impostazioni di sistema'; - case 'settings.lightTheme': return 'Chiaro'; - case 'settings.darkTheme': return 'Scuro'; - case 'settings.libraryDensity': return 'Densità libreria'; - case 'settings.compact': return 'Compatta'; - case 'settings.compactDescription': return 'Schede più piccole, più elementi visibili'; - case 'settings.normal': return 'Normale'; - case 'settings.normalDescription': return 'Dimensione predefinita'; - case 'settings.comfortable': return 'Comoda'; - case 'settings.comfortableDescription': return 'Schede più grandi, meno elementi visibili'; - case 'settings.viewMode': return 'Modalità di visualizzazione'; - case 'settings.gridView': return 'Griglia'; - case 'settings.gridViewDescription': return 'Visualizza gli elementi in un layout a griglia'; - case 'settings.listView': return 'Elenco'; - case 'settings.listViewDescription': return 'Visualizza gli elementi in un layout a elenco'; - case 'settings.useSeasonPosters': return 'Usa poster delle stagioni'; - case 'settings.showHeroSection': return 'Mostra sezione principale'; - case 'settings.hardwareDecoding': return 'Decodifica Hardware'; - case 'settings.hardwareDecodingDescription': return 'Utilizza l\'accelerazione hardware quando disponibile'; - case 'settings.bufferSize': return 'Dimensione buffer'; - case 'settings.bufferSizeMB': return ({required Object size}) => '${size}MB'; - case 'settings.subtitleStyling': return 'Stile sottotitoli'; - case 'settings.subtitleStylingDescription': return 'Personalizza l\'aspetto dei sottotitoli'; - case 'settings.smallSkipDuration': return 'Durata skip breve'; - case 'settings.largeSkipDuration': return 'Durata skip lungo'; - case 'settings.secondsUnit': return ({required Object seconds}) => '${seconds} secondi'; - case 'settings.defaultSleepTimer': return 'Timer spegnimento predefinito'; - case 'settings.minutesUnit': return ({required Object minutes}) => '${minutes} minuti'; - case 'settings.rememberTrackSelections': return 'Ricorda selezioni tracce per serie/film'; - case 'settings.rememberTrackSelectionsDescription': return 'Salva automaticamente le preferenze delle lingue audio e sottotitoli quando cambi tracce durante la riproduzione'; - case 'settings.unwatchedOnly': return 'Solo non guardati'; - case 'settings.unwatchedOnlyDescription': return 'Includi solo gli episodi non guardati nella coda di riproduzione casuale'; - case 'settings.shuffleOrderNavigation': return 'Navigazione in ordine casuale'; - case 'settings.shuffleOrderNavigationDescription': return 'I pulsanti Avanti/Indietro seguono l\'ordine casuale'; - case 'settings.loopShuffleQueue': return 'Coda di riproduzione casuale in loop'; - case 'settings.loopShuffleQueueDescription': return 'Riavvia la coda quando raggiungi la fine'; - case 'settings.videoPlayerControls': return 'Controlli del lettore video'; - case 'settings.keyboardShortcuts': return 'Scorciatoie da tastiera'; - case 'settings.keyboardShortcutsDescription': return 'Personalizza le scorciatoie da tastiera'; - case 'settings.debugLogging': return 'Log di debug'; - case 'settings.debugLoggingDescription': return 'Abilita il logging dettagliato per la risoluzione dei problemi'; - case 'settings.viewLogs': return 'Visualizza log'; - case 'settings.viewLogsDescription': return 'Visualizza i log dell\'applicazione'; - case 'settings.clearCache': return 'Svuota cache'; - case 'settings.clearCacheDescription': return 'Questa opzione cancellerà tutte le immagini e i dati memorizzati nella cache. Dopo aver cancellato la cache, l\'app potrebbe impiegare più tempo per caricare i contenuti.'; - case 'settings.clearCacheSuccess': return 'Cache cancellata correttamente'; - case 'settings.resetSettings': return 'Ripristina impostazioni'; - case 'settings.resetSettingsDescription': return 'Questa opzione ripristinerà tutte le impostazioni ai valori predefiniti. Non può essere annullata.'; - case 'settings.resetSettingsSuccess': return 'Impostazioni ripristinate correttamente'; - case 'settings.shortcutsReset': return 'Scorciatoie ripristinate alle impostazioni predefinite'; - case 'settings.about': return 'Informazioni'; - case 'settings.aboutDescription': return 'Informazioni sull\'app e le licenze'; - case 'settings.updates': return 'Aggiornamenti'; - case 'settings.updateAvailable': return 'Aggiornamento disponibile'; - case 'settings.checkForUpdates': return 'Controlla aggiornamenti'; - case 'settings.validationErrorEnterNumber': return 'Inserisci un numero valido'; - case 'settings.validationErrorDuration': return ({required Object min, required Object max, required Object unit}) => 'la durata deve essere compresa tra ${min} e ${max} ${unit}'; - case 'settings.shortcutAlreadyAssigned': return ({required Object action}) => 'Scorciatoia già assegnata a ${action}'; - case 'settings.shortcutUpdated': return ({required Object action}) => 'Scorciatoia aggiornata per ${action}'; - case 'search.hint': return 'Cerca film. spettacoli, musica...'; - case 'search.tryDifferentTerm': return 'Prova altri termini di ricerca'; - case 'search.searchYourMedia': return 'Cerca nei tuoi media'; - case 'search.enterTitleActorOrKeyword': return 'Inserisci un titolo, attore o parola chiave'; - case 'hotkeys.setShortcutFor': return ({required Object actionName}) => 'Imposta scorciatoia per ${actionName}'; - case 'hotkeys.clearShortcut': return 'Elimina scorciatoia'; - case 'pinEntry.enterPin': return 'Inserisci PIN'; - case 'pinEntry.showPin': return 'Mostra PIN'; - case 'pinEntry.hidePin': return 'Nascondi PIN'; - case 'fileInfo.title': return 'Info sul file'; - case 'fileInfo.video': return 'Video'; - case 'fileInfo.audio': return 'Audio'; - case 'fileInfo.file': return 'File'; - case 'fileInfo.advanced': return 'Avanzate'; - case 'fileInfo.codec': return 'Codec'; - case 'fileInfo.resolution': return 'Risoluzione'; - case 'fileInfo.bitrate': return 'Bitrate'; - case 'fileInfo.frameRate': return 'Frame Rate'; - case 'fileInfo.aspectRatio': return 'Aspect Ratio'; - case 'fileInfo.profile': return 'Profilo'; - case 'fileInfo.bitDepth': return 'Profondità colore'; - case 'fileInfo.colorSpace': return 'Spazio colore'; - case 'fileInfo.colorRange': return 'Gamma colori'; - case 'fileInfo.colorPrimaries': return 'Colori primari'; - case 'fileInfo.chromaSubsampling': return 'Sottocampionamento cromatico'; - case 'fileInfo.channels': return 'Canali'; - case 'fileInfo.path': return 'Percorso'; - case 'fileInfo.size': return 'Dimensione'; - case 'fileInfo.container': return 'Contenitore'; - case 'fileInfo.duration': return 'Durata'; - case 'fileInfo.optimizedForStreaming': return 'Ottimizzato per lo streaming'; - case 'fileInfo.has64bitOffsets': return 'Offset a 64-bit'; - case 'mediaMenu.markAsWatched': return 'Segna come visto'; - case 'mediaMenu.markAsUnwatched': return 'Segna come non visto'; - case 'mediaMenu.removeFromContinueWatching': return 'Rimuovi da Continua a guardare'; - case 'mediaMenu.goToSeries': return 'Vai alle serie'; - case 'mediaMenu.goToSeason': return 'Vai alla stagione'; - case 'mediaMenu.shufflePlay': return 'Riproduzione casuale'; - case 'mediaMenu.fileInfo': return 'Info sul file'; - case 'accessibility.mediaCardMovie': return ({required Object title}) => '${title}, film'; - case 'accessibility.mediaCardShow': return ({required Object title}) => '${title}, serie TV'; - case 'accessibility.mediaCardEpisode': return ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - case 'accessibility.mediaCardSeason': return ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - case 'accessibility.mediaCardWatched': return 'visto'; - case 'accessibility.mediaCardPartiallyWatched': return ({required Object percent}) => '${percent} percento visto'; - case 'accessibility.mediaCardUnwatched': return 'non visto'; - case 'accessibility.tapToPlay': return 'Tocca per riprodurre'; - case 'tooltips.shufflePlay': return 'Riproduzione casuale'; - case 'tooltips.markAsWatched': return 'Segna come visto'; - case 'tooltips.markAsUnwatched': return 'Segna come non visto'; - case 'videoControls.audioLabel': return 'Audio'; - case 'videoControls.subtitlesLabel': return 'Sottotitoli'; - case 'videoControls.resetToZero': return 'Riporta a 0ms'; - case 'videoControls.addTime': return ({required Object amount, required Object unit}) => '+${amount}${unit}'; - case 'videoControls.minusTime': return ({required Object amount, required Object unit}) => '-${amount}${unit}'; - case 'videoControls.playsLater': return ({required Object label}) => '${label} riprodotto dopo'; - case 'videoControls.playsEarlier': return ({required Object label}) => '${label} riprodotto prima'; - case 'videoControls.noOffset': return 'Nessun offset'; - case 'videoControls.letterbox': return 'Letterbox'; - case 'videoControls.fillScreen': return 'Riempi schermo'; - case 'videoControls.stretch': return 'Allunga'; - case 'videoControls.lockRotation': return 'Blocca rotazione'; - case 'videoControls.unlockRotation': return 'Sblocca rotazione'; - case 'videoControls.sleepTimer': return 'Timer di spegnimento'; - case 'videoControls.timerActive': return 'Timer attivo'; - case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'La riproduzione si interromperà tra ${duration}'; - case 'videoControls.sleepTimerCompleted': return 'Timer di spegnimento completato - riproduzione in pausa'; - case 'videoControls.playButton': return 'Riproduci'; - case 'videoControls.pauseButton': return 'Pausa'; - case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Riavvolgi di ${seconds} secondi'; - case 'videoControls.seekForwardButton': return ({required Object seconds}) => 'Avanza di ${seconds} secondi'; - case 'videoControls.previousButton': return 'Episodio precedente'; - case 'videoControls.nextButton': return 'Episodio successivo'; - case 'videoControls.previousChapterButton': return 'Capitolo precedente'; - case 'videoControls.nextChapterButton': return 'Capitolo successivo'; - case 'videoControls.volumeButton': return 'Volume'; - case 'videoControls.muteButton': return 'Silenzia'; - case 'videoControls.unmuteButton': return 'Riattiva audio'; - case 'videoControls.settingsButton': return 'Impostazioni video'; - case 'videoControls.audioTrackButton': return 'Tracce audio'; - case 'videoControls.subtitlesButton': return 'Sottotitoli'; - case 'videoControls.chaptersButton': return 'Capitoli'; - case 'videoControls.versionsButton': return 'Versioni video'; - case 'videoControls.aspectRatioButton': return 'Proporzioni'; - case 'videoControls.fullscreenButton': return 'Attiva schermo intero'; - case 'videoControls.exitFullscreenButton': return 'Esci da schermo intero'; - case 'videoControls.rotationLockButton': return 'Blocco rotazione'; - case 'videoControls.timelineSlider': return 'Timeline video'; - case 'videoControls.volumeSlider': return 'Livello volume'; - case 'videoControls.backButton': return 'Indietro'; - case 'userStatus.admin': return 'Admin'; - case 'userStatus.restricted': return 'Limitato'; - case 'userStatus.protected': return 'Protetto'; - case 'userStatus.current': return 'ATTUALE'; - case 'messages.markedAsWatched': return 'Segna come visto'; - case 'messages.markedAsUnwatched': return 'Segna come non visto'; - case 'messages.removedFromContinueWatching': return 'Rimosso da Continua a guardare'; - case 'messages.errorLoading': return ({required Object error}) => 'Errore: ${error}'; - case 'messages.fileInfoNotAvailable': return 'Informazioni sul file non disponibili'; - case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Errore caricamento informazioni sul file: ${error}'; - case 'messages.errorLoadingSeries': return 'Errore caricamento serie'; - case 'messages.errorLoadingSeason': return 'Errore caricamento stagione'; - case 'messages.musicNotSupported': return 'La riproduzione musicale non è ancora supportata'; - case 'messages.logsCleared': return 'Log eliminati'; - case 'messages.logsCopied': return 'Log copiati negli appunti'; - case 'messages.noLogsAvailable': return 'Nessun log disponibile'; - case 'messages.libraryScanning': return ({required Object title}) => 'Scansione "${title}"...'; - case 'messages.libraryScanStarted': return ({required Object title}) => 'Scansione libreria iniziata per "${title}"'; - case 'messages.libraryScanFailed': return ({required Object error}) => 'Impossibile eseguire scansione della libreria: ${error}'; - case 'messages.metadataRefreshing': return ({required Object title}) => 'Aggiornamento metadati per "${title}"...'; - case 'messages.metadataRefreshStarted': return ({required Object title}) => 'Aggiornamento metadati per "${title}"'; - case 'messages.metadataRefreshFailed': return ({required Object error}) => 'Errore aggiornamento metadati: ${error}'; - case 'messages.noPlexToken': return 'Nessun token Plex trovato. Riesegui l\'accesso.'; - case 'messages.logoutConfirm': return 'Sei sicuro di volerti disconnettere?'; - case 'messages.noSeasonsFound': return 'Nessuna stagione trovata'; - case 'messages.noEpisodesFound': return 'Nessun episodio trovato nella prima stagione'; - case 'messages.noEpisodesFoundGeneral': return 'Nessun episodio trovato'; - case 'messages.noResultsFound': return 'Nessun risultato'; - case 'messages.sleepTimerSet': return ({required Object label}) => 'Imposta timer spegnimento per ${label}'; - case 'messages.failedToSwitchProfile': return ({required Object displayName}) => 'Impossibile passare a ${displayName}'; - case 'messages.noItemsAvailable': return 'Nessun elemento disponibile'; - case 'messages.failedToCreatePlayQueue': return 'Impossibile creare la coda di riproduzione'; - case 'messages.failedToCreatePlayQueueNoItems': return 'Impossibile creare la coda di riproduzione - nessun elemento'; - case 'messages.failedPlayback': return ({required Object action, required Object error}) => 'Impossibile ${action}: ${error}'; - case 'profile.noUsersAvailable': return 'Nessun utente disponibile'; - case 'subtitlingStyling.stylingOptions': return 'Opzioni stile'; - case 'subtitlingStyling.fontSize': return 'Dimensione'; - case 'subtitlingStyling.textColor': return 'Colore testo'; - case 'subtitlingStyling.borderSize': return 'Dimensione bordo'; - case 'subtitlingStyling.borderColor': return 'Colore bordo'; - case 'subtitlingStyling.backgroundOpacity': return 'Opacità sfondo'; - case 'subtitlingStyling.backgroundColor': return 'Colore sfondo'; - case 'dialog.confirmAction': return 'Conferma azione'; - case 'dialog.areYouSure': return 'Sei sicuro di voler eseguire questa azione?'; - case 'dialog.cancel': return 'Cancella'; - case 'dialog.playNow': return 'Riproduci ora'; - case 'discover.title': return 'Esplora'; - case 'discover.switchProfile': return 'Cambia profilo'; - case 'discover.switchServer': return 'Cambia server'; - case 'discover.logout': return 'Disconnetti'; - case 'discover.noContentAvailable': return 'Nessun contenuto disponibile'; - case 'discover.addMediaToLibraries': return 'Aggiungi alcuni file multimediali alle tue librerie'; - case 'discover.continueWatching': return 'Continua a guardare'; - case 'discover.recentlyAdded': return 'Aggiunti di recente'; - case 'discover.play': return 'Riproduci'; - case 'discover.resume': return 'Riprendi'; - case 'discover.playEpisode': return ({required Object season, required Object episode}) => 'Riproduci S${season}, E${episode}'; - case 'discover.resumeEpisode': return ({required Object season, required Object episode}) => 'Riprendi S${season}, E${episode}'; - case 'discover.pause': return 'Pausa'; - case 'discover.overview': return 'Panoramica'; - case 'discover.cast': return 'Attori'; - case 'discover.seasons': return 'Stagioni'; - case 'discover.studio': return 'Studio'; - case 'discover.rating': return 'Classificazione'; - case 'discover.watched': return 'Guardato'; - case 'discover.episodeCount': return ({required Object count}) => '${count} episodi'; - case 'discover.watchedProgress': return ({required Object watched, required Object total}) => '${watched}/${total} guardati'; - case 'discover.movie': return 'Film'; - case 'discover.tvShow': return 'Serie TV'; - case 'discover.minutesLeft': return ({required Object minutes}) => '${minutes} minuti rimanenti'; - case 'errors.searchFailed': return ({required Object error}) => 'Ricerca fallita: ${error}'; - case 'errors.connectionTimeout': return ({required Object context}) => 'Timeout connessione durante caricamento di ${context}'; - case 'errors.connectionFailed': return 'Impossibile connettersi al server Plex.'; - case 'errors.failedToLoad': return ({required Object context, required Object error}) => 'Impossibile caricare ${context}: ${error}'; - case 'errors.noClientAvailable': return 'Nessun client disponibile'; - case 'errors.authenticationFailed': return ({required Object error}) => 'Autenticazione fallita: ${error}'; - case 'errors.couldNotLaunchUrl': return 'Impossibile avviare URL di autenticazione'; - case 'errors.pleaseEnterToken': return 'Inserisci token'; - case 'errors.invalidToken': return 'Token non valido'; - case 'errors.failedToVerifyToken': return ({required Object error}) => 'Verifica token fallita: ${error}'; - case 'errors.failedToSwitchProfile': return ({required Object displayName}) => 'Impossibile passare a ${displayName}'; - case 'errors.connectionFailedGeneric': return 'Connessione fallita'; - case 'libraries.title': return 'Librerie'; - case 'libraries.scanLibraryFiles': return 'Scansiona file libreria'; - case 'libraries.scanLibrary': return 'Scansiona libreria'; - case 'libraries.analyze': return 'Analizza'; - case 'libraries.analyzeLibrary': return 'Analizza libreria'; - case 'libraries.refreshMetadata': return 'Aggiorna metadati'; - case 'libraries.emptyTrash': return 'Svuota cestino'; - case 'libraries.emptyingTrash': return ({required Object title}) => 'Svuotamento cestino per "${title}"...'; - case 'libraries.trashEmptied': return ({required Object title}) => 'Cestino svuotato per "${title}"'; - case 'libraries.failedToEmptyTrash': return ({required Object error}) => 'Impossibile svuotare cestino: ${error}'; - case 'libraries.analyzing': return ({required Object title}) => 'Analisi "${title}"...'; - case 'libraries.analysisStarted': return ({required Object title}) => 'Analisi iniziata per "${title}"'; - case 'libraries.failedToAnalyze': return ({required Object error}) => 'Impossibile analizzare libreria: ${error}'; - case 'libraries.noLibrariesFound': return 'Nessuna libreria trovata'; - case 'libraries.thisLibraryIsEmpty': return 'Questa libreria è vuota'; - case 'libraries.all': return 'Tutto'; - case 'libraries.clearAll': return 'Cancella tutto'; - case 'libraries.scanLibraryConfirm': return ({required Object title}) => 'Sei sicuro di voler scansionare "${title}"?'; - case 'libraries.analyzeLibraryConfirm': return ({required Object title}) => 'Sei sicuro di voler analizzare "${title}"?'; - case 'libraries.refreshMetadataConfirm': return ({required Object title}) => 'Sei sicuro di voler aggiornare i metadati per "${title}"?'; - case 'libraries.emptyTrashConfirm': return ({required Object title}) => 'Sei sicuro di voler svuotare il cestino per "${title}"?'; - case 'libraries.manageLibraries': return 'Gestisci librerie'; - case 'libraries.sort': return 'Ordina'; - case 'libraries.sortBy': return 'Ordina per'; - case 'libraries.filters': return 'Filtri'; - case 'libraries.loadingLibraryWithCount': return ({required Object count}) => 'Caricamento librerie... (${count} oggetti caricati)'; - case 'libraries.confirmActionMessage': return 'Sei sicuro di voler eseguire questa azione?'; - case 'libraries.showLibrary': return 'Mostra libreria'; - case 'libraries.hideLibrary': return 'Nascondi libreria'; - case 'libraries.libraryOptions': return 'Opzioni libreria'; - case 'libraries.content': return 'contenuto della libreria'; - case 'libraries.selectLibrary': return 'Seleziona libreria'; - case 'libraries.filtersWithCount': return ({required Object count}) => 'Filtri (${count})'; - case 'libraries.noRecommendations': return 'Nessun consiglio disponibile'; - case 'libraries.noCollections': return 'Nessuna raccolta in questa libreria'; - case 'libraries.noFoldersFound': return 'Nessuna cartella trovata'; - case 'libraries.folders': return 'cartelle'; - case 'libraries.tabs.recommended': return 'Consigliati'; - case 'libraries.tabs.browse': return 'Esplora'; - case 'libraries.tabs.collections': return 'Raccolte'; - case 'libraries.tabs.playlists': return 'Playlist'; - case 'libraries.groupings.all': return 'Tutti'; - case 'libraries.groupings.movies': return 'Film'; - case 'libraries.groupings.shows': return 'Serie TV'; - case 'libraries.groupings.seasons': return 'Stagioni'; - case 'libraries.groupings.episodes': return 'Episodi'; - case 'libraries.groupings.folders': return 'Cartelle'; - case 'about.title': return 'Informazioni'; - case 'about.openSourceLicenses': return 'Licenze Open Source'; - case 'about.versionLabel': return ({required Object version}) => 'Versione ${version}'; - case 'about.appDescription': return 'Un bellissimo client Plex per Flutter'; - case 'about.viewLicensesDescription': return 'Visualizza le licenze delle librerie di terze parti'; - case 'serverSelection.connectingToServer': return 'Connessione al server...'; - case 'serverSelection.connectingToAllServers': return 'Connessione a tutti i server...'; - case 'serverSelection.connectToAllServers': return 'Connetti a tutti i server'; - case 'serverSelection.allServerConnectionsFailed': return 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.'; - case 'serverSelection.foundServers': return ({required Object count}) => 'Trovati ${count} server'; - case 'serverSelection.orSelectIndividually': return 'Oppure seleziona un server singolarmente:'; - case 'serverSelection.serverDebugCopied': return 'Dati di debug del server copiati negli appunti'; - case 'serverSelection.copyDebugData': return 'Copia dati di debug'; - case 'serverSelection.noServersFound': return 'Nessun server trovato'; - case 'serverSelection.malformedServerData': return ({required Object count}) => 'Trovato ${count} server con dati difettosi. Nessun server valido disponibile.'; - case 'serverSelection.incompleteServerInfo': return 'Alcuni server presentano informazioni incomplete e sono stati ignorati. Controlla il tuo account Plex.tv.'; - case 'serverSelection.incompleteConnectionInfo': return 'Le informazioni di connessione al server sono incomplete. Riprova.'; - case 'serverSelection.malformedServerInfo': return ({required Object message}) => 'Le informazioni sul server sono errate: ${message}'; - case 'serverSelection.networkConnectionFailed': return 'Connessione di rete non riuscita. Controlla la tua connessione Internet e riprova.'; - case 'serverSelection.authenticationFailed': return 'Autenticazione fallita. Effettua nuovamente l\'accesso.'; - case 'serverSelection.plexServiceUnavailable': return 'Servizio Plex non disponibile. Riprova più tardi.'; - case 'serverSelection.failedToLoadServers': return ({required Object error}) => 'Impossibile caricare i server: ${error}'; - case 'hubDetail.title': return 'Titolo'; - case 'hubDetail.releaseYear': return 'Anno rilascio'; - case 'hubDetail.dateAdded': return 'Data aggiunta'; - case 'hubDetail.rating': return 'Valutazione'; - case 'hubDetail.noItemsFound': return 'Nessun elemento trovato'; - case 'logs.title': return 'Registro'; - case 'logs.clearLogs': return 'Cancella log'; - case 'logs.copyLogs': return 'Copia log'; - case 'logs.exportLogs': return 'Esporta log'; - case 'logs.noLogsToShow': return 'Nessun log da mostrare'; - case 'logs.error': return 'Errore:'; - case 'logs.stackTrace': return 'Traccia dello stack:'; - case 'licenses.relatedPackages': return 'Pacchetti correlati'; - case 'licenses.license': return 'Licenza'; - case 'licenses.licenseNumber': return ({required Object number}) => 'Licenza ${number}'; - case 'licenses.licensesCount': return ({required Object count}) => '${count} licenze'; - case 'navigation.home': return 'Home'; - case 'navigation.search': return 'Cerca'; - case 'navigation.libraries': return 'Librerie'; - case 'navigation.settings': return 'Impostazioni'; - case 'playlists.title': return 'Playlist'; - case 'playlists.noPlaylists': return 'Nessuna playlist trovata'; - case 'playlists.create': return 'Crea playlist'; - case 'playlists.newPlaylist': return 'Nuova playlist'; - case 'playlists.playlistName': return 'Nome playlist'; - case 'playlists.enterPlaylistName': return 'Inserisci nome playlist'; - case 'playlists.edit': return 'Modifica playlist'; - case 'playlists.delete': return 'Elimina playlist'; - case 'playlists.addTo': return 'Aggiungi a playlist'; - case 'playlists.addItems': return 'Aggiungi elementi'; - case 'playlists.removeItem': return 'Rimuovi da playlist'; - case 'playlists.clearPlaylist': return 'Svuota playlist'; - case 'playlists.playAll': return 'Riproduci tutto'; - case 'playlists.shuffle': return 'Casuale'; - case 'playlists.smartPlaylist': return 'Playlist intelligente'; - case 'playlists.regularPlaylist': return 'Playlist normale'; - case 'playlists.itemCount': return ({required Object count}) => '${count} elementi'; - case 'playlists.oneItem': return '1 elemento'; - case 'playlists.emptyPlaylist': return 'Questa playlist è vuota'; - case 'playlists.deleteConfirm': return 'Eliminare playlist?'; - case 'playlists.deleteMessage': return ({required Object name}) => 'Sei sicuro di voler eliminare "${name}"?'; - case 'playlists.created': return 'Playlist creata'; - case 'playlists.updated': return 'Playlist aggiornata'; - case 'playlists.deleted': return 'Playlist eliminata'; - case 'playlists.itemAdded': return 'Aggiunto alla playlist'; - case 'playlists.itemRemoved': return 'Rimosso dalla playlist'; - case 'playlists.selectPlaylist': return 'Seleziona playlist'; - case 'playlists.createNewPlaylist': return 'Crea nuova playlist'; - case 'playlists.errorCreating': return 'Errore durante la creazione della playlist'; - case 'playlists.errorDeleting': return 'Errore durante l\'eliminazione della playlist'; - case 'playlists.errorLoading': return 'Errore durante il caricamento delle playlist'; - case 'playlists.errorAdding': return 'Errore durante l\'aggiunta alla playlist'; - case 'playlists.errorReordering': return 'Errore durante il riordino dell\'elemento della playlist'; - case 'playlists.errorRemoving': return 'Errore durante la rimozione dalla playlist'; - case 'playlists.playlist': return 'Playlist'; - case 'collections.title': return 'Raccolte'; - case 'collections.collection': return 'Raccolta'; - case 'collections.empty': return 'La raccolta è vuota'; - case 'collections.noItems': return 'Nessun elemento in questa raccolta'; - case 'collections.unknownLibrarySection': return 'Impossibile eliminare: sezione libreria sconosciuta'; - case 'collections.deleteCollection': return 'Elimina raccolta'; - case 'collections.deleteConfirm': return ({required Object title}) => 'Sei sicuro di voler eliminare "${title}"? Questa azione non può essere annullata.'; - case 'collections.deleted': return 'Raccolta eliminata'; - case 'collections.deleteFailed': return 'Impossibile eliminare la raccolta'; - case 'collections.deleteFailedWithError': return ({required Object error}) => 'Impossibile eliminare la raccolta: ${error}'; - case 'collections.failedToLoadItems': return ({required Object error}) => 'Impossibile caricare gli elementi della raccolta: ${error}'; - case 'collections.addTo': return 'Aggiungi alla raccolta'; - case 'collections.selectCollection': return 'Seleziona raccolta'; - case 'collections.createNewCollection': return 'Crea nuova raccolta'; - case 'collections.collectionName': return 'Nome raccolta'; - case 'collections.enterCollectionName': return 'Inserisci nome raccolta'; - case 'collections.addedToCollection': return 'Aggiunto alla raccolta'; - case 'collections.errorAddingToCollection': return 'Errore nell\'aggiunta alla raccolta'; - case 'collections.created': return 'Raccolta creata'; - case 'collections.removeFromCollection': return 'Rimuovi dalla raccolta'; - case 'collections.removeFromCollectionConfirm': return ({required Object title}) => 'Rimuovere "${title}" da questa raccolta?'; - case 'collections.removedFromCollection': return 'Rimosso dalla raccolta'; - case 'collections.removeFromCollectionFailed': return 'Impossibile rimuovere dalla raccolta'; - case 'collections.removeFromCollectionError': return ({required Object error}) => 'Errore durante la rimozione dalla raccolta: ${error}'; - default: return null; - } - } + dynamic _flatMapFunction(String path) { + switch (path) { + case 'app.title': + return 'Plezy'; + case 'app.loading': + return 'Caricamento...'; + case 'auth.signInWithPlex': + return 'Accedi con Plex'; + case 'auth.showQRCode': + return 'Mostra QR Code'; + case 'auth.cancel': + return 'Cancella'; + case 'auth.authenticate': + return 'Autenticazione'; + case 'auth.retry': + return 'Riprova'; + case 'auth.debugEnterToken': + return 'Debug: Inserisci Token Plex'; + case 'auth.plexTokenLabel': + return 'Token Auth Plex'; + case 'auth.plexTokenHint': + return 'Inserisci il tuo token di Plex.tv'; + case 'auth.authenticationTimeout': + return 'Autenticazione scaduta. Riprova.'; + case 'auth.scanQRCodeInstruction': + return 'Scansiona questo QR code con un dispositivo connesso a Plex per autenticarti.'; + case 'auth.waitingForAuth': + return 'In attesa di autenticazione...\nCompleta l\'accesso dal tuo browser.'; + case 'common.cancel': + return 'Cancella'; + case 'common.save': + return 'Salva'; + case 'common.close': + return 'Chiudi'; + case 'common.clear': + return 'Pulisci'; + case 'common.reset': + return 'Ripristina'; + case 'common.later': + return 'Più tardi'; + case 'common.submit': + return 'Invia'; + case 'common.confirm': + return 'Conferma'; + case 'common.retry': + return 'Riprova'; + case 'common.playNow': + return 'Riproduci ora'; + case 'common.logout': + return 'Disconnetti'; + case 'common.online': + return 'Online'; + case 'common.offline': + return 'Offline'; + case 'common.owned': + return 'Di proprietà'; + case 'common.shared': + return 'Condiviso'; + case 'common.current': + return 'CORRENTE'; + case 'common.unknown': + return 'Sconosciuto'; + case 'common.refresh': + return 'Aggiorna'; + case 'common.yes': + return 'Sì'; + case 'common.no': + return 'No'; + case 'common.server': + return 'Server'; + case 'common.delete': + return 'Elimina'; + case 'common.shuffle': + return 'Casuale'; + case 'common.addTo': + return 'Aggiungi a...'; + case 'screens.licenses': + return 'Licenze'; + case 'screens.selectServer': + return 'Seleziona server'; + case 'screens.switchProfile': + return 'Cambia profilo'; + case 'screens.subtitleStyling': + return 'Stile sottotitoli'; + case 'screens.search': + return 'Cerca'; + case 'screens.logs': + return 'Registro'; + case 'update.available': + return 'Aggiornamento disponibile'; + case 'update.versionAvailable': + return ({required Object version}) => 'Versione ${version} disponibile'; + case 'update.currentVersion': + return ({required Object version}) => 'Corrente: ${version}'; + case 'update.skipVersion': + return 'Salta questa versione'; + case 'update.viewRelease': + return 'Visualizza dettagli release'; + case 'update.latestVersion': + return 'La versione installata è l\'ultima disponibile'; + case 'update.checkFailed': + return 'Impossibile controllare gli aggiornamenti'; + case 'settings.title': + return 'Impostazioni'; + case 'settings.language': + return 'Lingua'; + case 'settings.theme': + return 'Tema'; + case 'settings.appearance': + return 'Aspetto'; + case 'settings.videoPlayback': + return 'Riproduzione video'; + case 'settings.shufflePlay': + return 'Riproduzione casuale'; + case 'settings.advanced': + return 'Avanzate'; + case 'settings.useSeasonPostersDescription': + return 'Mostra il poster della stagione invece del poster della serie per gli episodi'; + case 'settings.showHeroSectionDescription': + return 'Visualizza il carosello dei contenuti in primo piano sulla schermata iniziale'; + case 'settings.secondsLabel': + return 'Secondi'; + case 'settings.minutesLabel': + return 'Minuti'; + case 'settings.secondsShort': + return 's'; + case 'settings.minutesShort': + return 'm'; + case 'settings.durationHint': + return ({required Object min, required Object max}) => + 'Inserisci durata (${min}-${max})'; + case 'settings.systemTheme': + return 'Sistema'; + case 'settings.systemThemeDescription': + return 'Segui le impostazioni di sistema'; + case 'settings.lightTheme': + return 'Chiaro'; + case 'settings.darkTheme': + return 'Scuro'; + case 'settings.libraryDensity': + return 'Densità libreria'; + case 'settings.compact': + return 'Compatta'; + case 'settings.compactDescription': + return 'Schede più piccole, più elementi visibili'; + case 'settings.normal': + return 'Normale'; + case 'settings.normalDescription': + return 'Dimensione predefinita'; + case 'settings.comfortable': + return 'Comoda'; + case 'settings.comfortableDescription': + return 'Schede più grandi, meno elementi visibili'; + case 'settings.viewMode': + return 'Modalità di visualizzazione'; + case 'settings.gridView': + return 'Griglia'; + case 'settings.gridViewDescription': + return 'Visualizza gli elementi in un layout a griglia'; + case 'settings.listView': + return 'Elenco'; + case 'settings.listViewDescription': + return 'Visualizza gli elementi in un layout a elenco'; + case 'settings.useSeasonPosters': + return 'Usa poster delle stagioni'; + case 'settings.showHeroSection': + return 'Mostra sezione principale'; + case 'settings.hardwareDecoding': + return 'Decodifica Hardware'; + case 'settings.hardwareDecodingDescription': + return 'Utilizza l\'accelerazione hardware quando disponibile'; + case 'settings.bufferSize': + return 'Dimensione buffer'; + case 'settings.bufferSizeMB': + return ({required Object size}) => '${size}MB'; + case 'settings.subtitleStyling': + return 'Stile sottotitoli'; + case 'settings.subtitleStylingDescription': + return 'Personalizza l\'aspetto dei sottotitoli'; + case 'settings.smallSkipDuration': + return 'Durata skip breve'; + case 'settings.largeSkipDuration': + return 'Durata skip lungo'; + case 'settings.secondsUnit': + return ({required Object seconds}) => '${seconds} secondi'; + case 'settings.defaultSleepTimer': + return 'Timer spegnimento predefinito'; + case 'settings.minutesUnit': + return ({required Object minutes}) => '${minutes} minuti'; + case 'settings.rememberTrackSelections': + return 'Ricorda selezioni tracce per serie/film'; + case 'settings.rememberTrackSelectionsDescription': + return 'Salva automaticamente le preferenze delle lingue audio e sottotitoli quando cambi tracce durante la riproduzione'; + case 'settings.unwatchedOnly': + return 'Solo non guardati'; + case 'settings.unwatchedOnlyDescription': + return 'Includi solo gli episodi non guardati nella coda di riproduzione casuale'; + case 'settings.shuffleOrderNavigation': + return 'Navigazione in ordine casuale'; + case 'settings.shuffleOrderNavigationDescription': + return 'I pulsanti Avanti/Indietro seguono l\'ordine casuale'; + case 'settings.loopShuffleQueue': + return 'Coda di riproduzione casuale in loop'; + case 'settings.loopShuffleQueueDescription': + return 'Riavvia la coda quando raggiungi la fine'; + case 'settings.videoPlayerControls': + return 'Controlli del lettore video'; + case 'settings.keyboardShortcuts': + return 'Scorciatoie da tastiera'; + case 'settings.keyboardShortcutsDescription': + return 'Personalizza le scorciatoie da tastiera'; + case 'settings.debugLogging': + return 'Log di debug'; + case 'settings.debugLoggingDescription': + return 'Abilita il logging dettagliato per la risoluzione dei problemi'; + case 'settings.viewLogs': + return 'Visualizza log'; + case 'settings.viewLogsDescription': + return 'Visualizza i log dell\'applicazione'; + case 'settings.clearCache': + return 'Svuota cache'; + case 'settings.clearCacheDescription': + return 'Questa opzione cancellerà tutte le immagini e i dati memorizzati nella cache. Dopo aver cancellato la cache, l\'app potrebbe impiegare più tempo per caricare i contenuti.'; + case 'settings.clearCacheSuccess': + return 'Cache cancellata correttamente'; + case 'settings.resetSettings': + return 'Ripristina impostazioni'; + case 'settings.resetSettingsDescription': + return 'Questa opzione ripristinerà tutte le impostazioni ai valori predefiniti. Non può essere annullata.'; + case 'settings.resetSettingsSuccess': + return 'Impostazioni ripristinate correttamente'; + case 'settings.shortcutsReset': + return 'Scorciatoie ripristinate alle impostazioni predefinite'; + case 'settings.about': + return 'Informazioni'; + case 'settings.aboutDescription': + return 'Informazioni sull\'app e le licenze'; + case 'settings.updates': + return 'Aggiornamenti'; + case 'settings.updateAvailable': + return 'Aggiornamento disponibile'; + case 'settings.checkForUpdates': + return 'Controlla aggiornamenti'; + case 'settings.validationErrorEnterNumber': + return 'Inserisci un numero valido'; + case 'settings.validationErrorDuration': + return ({ + required Object min, + required Object max, + required Object unit, + }) => 'la durata deve essere compresa tra ${min} e ${max} ${unit}'; + case 'settings.shortcutAlreadyAssigned': + return ({required Object action}) => + 'Scorciatoia già assegnata a ${action}'; + case 'settings.shortcutUpdated': + return ({required Object action}) => + 'Scorciatoia aggiornata per ${action}'; + case 'search.hint': + return 'Cerca film. spettacoli, musica...'; + case 'search.tryDifferentTerm': + return 'Prova altri termini di ricerca'; + case 'search.searchYourMedia': + return 'Cerca nei tuoi media'; + case 'search.enterTitleActorOrKeyword': + return 'Inserisci un titolo, attore o parola chiave'; + case 'hotkeys.setShortcutFor': + return ({required Object actionName}) => + 'Imposta scorciatoia per ${actionName}'; + case 'hotkeys.clearShortcut': + return 'Elimina scorciatoia'; + case 'pinEntry.enterPin': + return 'Inserisci PIN'; + case 'pinEntry.showPin': + return 'Mostra PIN'; + case 'pinEntry.hidePin': + return 'Nascondi PIN'; + case 'fileInfo.title': + return 'Info sul file'; + case 'fileInfo.video': + return 'Video'; + case 'fileInfo.audio': + return 'Audio'; + case 'fileInfo.file': + return 'File'; + case 'fileInfo.advanced': + return 'Avanzate'; + case 'fileInfo.codec': + return 'Codec'; + case 'fileInfo.resolution': + return 'Risoluzione'; + case 'fileInfo.bitrate': + return 'Bitrate'; + case 'fileInfo.frameRate': + return 'Frame Rate'; + case 'fileInfo.aspectRatio': + return 'Aspect Ratio'; + case 'fileInfo.profile': + return 'Profilo'; + case 'fileInfo.bitDepth': + return 'Profondità colore'; + case 'fileInfo.colorSpace': + return 'Spazio colore'; + case 'fileInfo.colorRange': + return 'Gamma colori'; + case 'fileInfo.colorPrimaries': + return 'Colori primari'; + case 'fileInfo.chromaSubsampling': + return 'Sottocampionamento cromatico'; + case 'fileInfo.channels': + return 'Canali'; + case 'fileInfo.path': + return 'Percorso'; + case 'fileInfo.size': + return 'Dimensione'; + case 'fileInfo.container': + return 'Contenitore'; + case 'fileInfo.duration': + return 'Durata'; + case 'fileInfo.optimizedForStreaming': + return 'Ottimizzato per lo streaming'; + case 'fileInfo.has64bitOffsets': + return 'Offset a 64-bit'; + case 'mediaMenu.markAsWatched': + return 'Segna come visto'; + case 'mediaMenu.markAsUnwatched': + return 'Segna come non visto'; + case 'mediaMenu.removeFromContinueWatching': + return 'Rimuovi da Continua a guardare'; + case 'mediaMenu.goToSeries': + return 'Vai alle serie'; + case 'mediaMenu.goToSeason': + return 'Vai alla stagione'; + case 'mediaMenu.shufflePlay': + return 'Riproduzione casuale'; + case 'mediaMenu.fileInfo': + return 'Info sul file'; + case 'accessibility.mediaCardMovie': + return ({required Object title}) => '${title}, film'; + case 'accessibility.mediaCardShow': + return ({required Object title}) => '${title}, serie TV'; + case 'accessibility.mediaCardEpisode': + return ({required Object title, required Object episodeInfo}) => + '${title}, ${episodeInfo}'; + case 'accessibility.mediaCardSeason': + return ({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + case 'accessibility.mediaCardWatched': + return 'visto'; + case 'accessibility.mediaCardPartiallyWatched': + return ({required Object percent}) => '${percent} percento visto'; + case 'accessibility.mediaCardUnwatched': + return 'non visto'; + case 'accessibility.tapToPlay': + return 'Tocca per riprodurre'; + case 'tooltips.shufflePlay': + return 'Riproduzione casuale'; + case 'tooltips.markAsWatched': + return 'Segna come visto'; + case 'tooltips.markAsUnwatched': + return 'Segna come non visto'; + case 'videoControls.audioLabel': + return 'Audio'; + case 'videoControls.subtitlesLabel': + return 'Sottotitoli'; + case 'videoControls.resetToZero': + return 'Riporta a 0ms'; + case 'videoControls.addTime': + return ({required Object amount, required Object unit}) => + '+${amount}${unit}'; + case 'videoControls.minusTime': + return ({required Object amount, required Object unit}) => + '-${amount}${unit}'; + case 'videoControls.playsLater': + return ({required Object label}) => '${label} riprodotto dopo'; + case 'videoControls.playsEarlier': + return ({required Object label}) => '${label} riprodotto prima'; + case 'videoControls.noOffset': + return 'Nessun offset'; + case 'videoControls.letterbox': + return 'Letterbox'; + case 'videoControls.fillScreen': + return 'Riempi schermo'; + case 'videoControls.stretch': + return 'Allunga'; + case 'videoControls.lockRotation': + return 'Blocca rotazione'; + case 'videoControls.unlockRotation': + return 'Sblocca rotazione'; + case 'videoControls.sleepTimer': + return 'Timer di spegnimento'; + case 'videoControls.timerActive': + return 'Timer attivo'; + case 'videoControls.playbackWillPauseIn': + return ({required Object duration}) => + 'La riproduzione si interromperà tra ${duration}'; + case 'videoControls.sleepTimerCompleted': + return 'Timer di spegnimento completato - riproduzione in pausa'; + case 'videoControls.playButton': + return 'Riproduci'; + case 'videoControls.pauseButton': + return 'Pausa'; + case 'videoControls.seekBackwardButton': + return ({required Object seconds}) => 'Riavvolgi di ${seconds} secondi'; + case 'videoControls.seekForwardButton': + return ({required Object seconds}) => 'Avanza di ${seconds} secondi'; + case 'videoControls.previousButton': + return 'Episodio precedente'; + case 'videoControls.nextButton': + return 'Episodio successivo'; + case 'videoControls.previousChapterButton': + return 'Capitolo precedente'; + case 'videoControls.nextChapterButton': + return 'Capitolo successivo'; + case 'videoControls.volumeButton': + return 'Volume'; + case 'videoControls.muteButton': + return 'Silenzia'; + case 'videoControls.unmuteButton': + return 'Riattiva audio'; + case 'videoControls.settingsButton': + return 'Impostazioni video'; + case 'videoControls.audioTrackButton': + return 'Tracce audio'; + case 'videoControls.subtitlesButton': + return 'Sottotitoli'; + case 'videoControls.chaptersButton': + return 'Capitoli'; + case 'videoControls.versionsButton': + return 'Versioni video'; + case 'videoControls.aspectRatioButton': + return 'Proporzioni'; + case 'videoControls.fullscreenButton': + return 'Attiva schermo intero'; + case 'videoControls.exitFullscreenButton': + return 'Esci da schermo intero'; + case 'videoControls.rotationLockButton': + return 'Blocco rotazione'; + case 'videoControls.timelineSlider': + return 'Timeline video'; + case 'videoControls.volumeSlider': + return 'Livello volume'; + case 'videoControls.backButton': + return 'Indietro'; + case 'userStatus.admin': + return 'Admin'; + case 'userStatus.restricted': + return 'Limitato'; + case 'userStatus.protected': + return 'Protetto'; + case 'userStatus.current': + return 'ATTUALE'; + case 'messages.markedAsWatched': + return 'Segna come visto'; + case 'messages.markedAsUnwatched': + return 'Segna come non visto'; + case 'messages.removedFromContinueWatching': + return 'Rimosso da Continua a guardare'; + case 'messages.errorLoading': + return ({required Object error}) => 'Errore: ${error}'; + case 'messages.fileInfoNotAvailable': + return 'Informazioni sul file non disponibili'; + case 'messages.errorLoadingFileInfo': + return ({required Object error}) => + 'Errore caricamento informazioni sul file: ${error}'; + case 'messages.errorLoadingSeries': + return 'Errore caricamento serie'; + case 'messages.errorLoadingSeason': + return 'Errore caricamento stagione'; + case 'messages.musicNotSupported': + return 'La riproduzione musicale non è ancora supportata'; + case 'messages.logsCleared': + return 'Log eliminati'; + case 'messages.logsCopied': + return 'Log copiati negli appunti'; + case 'messages.noLogsAvailable': + return 'Nessun log disponibile'; + case 'messages.libraryScanning': + return ({required Object title}) => 'Scansione "${title}"...'; + case 'messages.libraryScanStarted': + return ({required Object title}) => + 'Scansione libreria iniziata per "${title}"'; + case 'messages.libraryScanFailed': + return ({required Object error}) => + 'Impossibile eseguire scansione della libreria: ${error}'; + case 'messages.metadataRefreshing': + return ({required Object title}) => + 'Aggiornamento metadati per "${title}"...'; + case 'messages.metadataRefreshStarted': + return ({required Object title}) => + 'Aggiornamento metadati per "${title}"'; + case 'messages.metadataRefreshFailed': + return ({required Object error}) => + 'Errore aggiornamento metadati: ${error}'; + case 'messages.noPlexToken': + return 'Nessun token Plex trovato. Riesegui l\'accesso.'; + case 'messages.logoutConfirm': + return 'Sei sicuro di volerti disconnettere?'; + case 'messages.noSeasonsFound': + return 'Nessuna stagione trovata'; + case 'messages.noEpisodesFound': + return 'Nessun episodio trovato nella prima stagione'; + case 'messages.noEpisodesFoundGeneral': + return 'Nessun episodio trovato'; + case 'messages.noResultsFound': + return 'Nessun risultato'; + case 'messages.sleepTimerSet': + return ({required Object label}) => + 'Imposta timer spegnimento per ${label}'; + case 'messages.failedToSwitchProfile': + return ({required Object displayName}) => + 'Impossibile passare a ${displayName}'; + case 'messages.noItemsAvailable': + return 'Nessun elemento disponibile'; + case 'messages.failedToCreatePlayQueue': + return 'Impossibile creare la coda di riproduzione'; + case 'messages.failedToCreatePlayQueueNoItems': + return 'Impossibile creare la coda di riproduzione - nessun elemento'; + case 'messages.failedPlayback': + return ({required Object action, required Object error}) => + 'Impossibile ${action}: ${error}'; + case 'profile.noUsersAvailable': + return 'Nessun utente disponibile'; + case 'subtitlingStyling.stylingOptions': + return 'Opzioni stile'; + case 'subtitlingStyling.fontSize': + return 'Dimensione'; + case 'subtitlingStyling.textColor': + return 'Colore testo'; + case 'subtitlingStyling.borderSize': + return 'Dimensione bordo'; + case 'subtitlingStyling.borderColor': + return 'Colore bordo'; + case 'subtitlingStyling.backgroundOpacity': + return 'Opacità sfondo'; + case 'subtitlingStyling.backgroundColor': + return 'Colore sfondo'; + case 'dialog.confirmAction': + return 'Conferma azione'; + case 'dialog.areYouSure': + return 'Sei sicuro di voler eseguire questa azione?'; + case 'dialog.cancel': + return 'Cancella'; + case 'dialog.playNow': + return 'Riproduci ora'; + case 'discover.title': + return 'Esplora'; + case 'discover.switchProfile': + return 'Cambia profilo'; + case 'discover.switchServer': + return 'Cambia server'; + case 'discover.logout': + return 'Disconnetti'; + case 'discover.noContentAvailable': + return 'Nessun contenuto disponibile'; + case 'discover.addMediaToLibraries': + return 'Aggiungi alcuni file multimediali alle tue librerie'; + case 'discover.continueWatching': + return 'Continua a guardare'; + case 'discover.recentlyAdded': + return 'Aggiunti di recente'; + case 'discover.play': + return 'Riproduci'; + case 'discover.resume': + return 'Riprendi'; + case 'discover.playEpisode': + return ({required Object season, required Object episode}) => + 'Riproduci S${season}, E${episode}'; + case 'discover.resumeEpisode': + return ({required Object season, required Object episode}) => + 'Riprendi S${season}, E${episode}'; + case 'discover.pause': + return 'Pausa'; + case 'discover.overview': + return 'Panoramica'; + case 'discover.cast': + return 'Attori'; + case 'discover.seasons': + return 'Stagioni'; + case 'discover.studio': + return 'Studio'; + case 'discover.rating': + return 'Classificazione'; + case 'discover.watched': + return 'Guardato'; + case 'discover.episodeCount': + return ({required Object count}) => '${count} episodi'; + case 'discover.watchedProgress': + return ({required Object watched, required Object total}) => + '${watched}/${total} guardati'; + case 'discover.movie': + return 'Film'; + case 'discover.tvShow': + return 'Serie TV'; + case 'discover.minutesLeft': + return ({required Object minutes}) => '${minutes} minuti rimanenti'; + case 'errors.searchFailed': + return ({required Object error}) => 'Ricerca fallita: ${error}'; + case 'errors.connectionTimeout': + return ({required Object context}) => + 'Timeout connessione durante caricamento di ${context}'; + case 'errors.connectionFailed': + return 'Impossibile connettersi al server Plex.'; + case 'errors.failedToLoad': + return ({required Object context, required Object error}) => + 'Impossibile caricare ${context}: ${error}'; + case 'errors.noClientAvailable': + return 'Nessun client disponibile'; + case 'errors.authenticationFailed': + return ({required Object error}) => 'Autenticazione fallita: ${error}'; + case 'errors.couldNotLaunchUrl': + return 'Impossibile avviare URL di autenticazione'; + case 'errors.pleaseEnterToken': + return 'Inserisci token'; + case 'errors.invalidToken': + return 'Token non valido'; + case 'errors.failedToVerifyToken': + return ({required Object error}) => 'Verifica token fallita: ${error}'; + case 'errors.failedToSwitchProfile': + return ({required Object displayName}) => + 'Impossibile passare a ${displayName}'; + case 'errors.connectionFailedGeneric': + return 'Connessione fallita'; + case 'libraries.title': + return 'Librerie'; + case 'libraries.scanLibraryFiles': + return 'Scansiona file libreria'; + case 'libraries.scanLibrary': + return 'Scansiona libreria'; + case 'libraries.analyze': + return 'Analizza'; + case 'libraries.analyzeLibrary': + return 'Analizza libreria'; + case 'libraries.refreshMetadata': + return 'Aggiorna metadati'; + case 'libraries.emptyTrash': + return 'Svuota cestino'; + case 'libraries.emptyingTrash': + return ({required Object title}) => + 'Svuotamento cestino per "${title}"...'; + case 'libraries.trashEmptied': + return ({required Object title}) => 'Cestino svuotato per "${title}"'; + case 'libraries.failedToEmptyTrash': + return ({required Object error}) => + 'Impossibile svuotare cestino: ${error}'; + case 'libraries.analyzing': + return ({required Object title}) => 'Analisi "${title}"...'; + case 'libraries.analysisStarted': + return ({required Object title}) => 'Analisi iniziata per "${title}"'; + case 'libraries.failedToAnalyze': + return ({required Object error}) => + 'Impossibile analizzare libreria: ${error}'; + case 'libraries.noLibrariesFound': + return 'Nessuna libreria trovata'; + case 'libraries.thisLibraryIsEmpty': + return 'Questa libreria è vuota'; + case 'libraries.all': + return 'Tutto'; + case 'libraries.clearAll': + return 'Cancella tutto'; + case 'libraries.scanLibraryConfirm': + return ({required Object title}) => + 'Sei sicuro di voler scansionare "${title}"?'; + case 'libraries.analyzeLibraryConfirm': + return ({required Object title}) => + 'Sei sicuro di voler analizzare "${title}"?'; + case 'libraries.refreshMetadataConfirm': + return ({required Object title}) => + 'Sei sicuro di voler aggiornare i metadati per "${title}"?'; + case 'libraries.emptyTrashConfirm': + return ({required Object title}) => + 'Sei sicuro di voler svuotare il cestino per "${title}"?'; + case 'libraries.manageLibraries': + return 'Gestisci librerie'; + case 'libraries.sort': + return 'Ordina'; + case 'libraries.sortBy': + return 'Ordina per'; + case 'libraries.filters': + return 'Filtri'; + case 'libraries.loadingLibraryWithCount': + return ({required Object count}) => + 'Caricamento librerie... (${count} oggetti caricati)'; + case 'libraries.confirmActionMessage': + return 'Sei sicuro di voler eseguire questa azione?'; + case 'libraries.showLibrary': + return 'Mostra libreria'; + case 'libraries.hideLibrary': + return 'Nascondi libreria'; + case 'libraries.libraryOptions': + return 'Opzioni libreria'; + case 'libraries.content': + return 'contenuto della libreria'; + case 'libraries.selectLibrary': + return 'Seleziona libreria'; + case 'libraries.filtersWithCount': + return ({required Object count}) => 'Filtri (${count})'; + case 'libraries.noRecommendations': + return 'Nessun consiglio disponibile'; + case 'libraries.noCollections': + return 'Nessuna raccolta in questa libreria'; + case 'libraries.noFoldersFound': + return 'Nessuna cartella trovata'; + case 'libraries.folders': + return 'cartelle'; + case 'libraries.tabs.recommended': + return 'Consigliati'; + case 'libraries.tabs.browse': + return 'Esplora'; + case 'libraries.tabs.collections': + return 'Raccolte'; + case 'libraries.tabs.playlists': + return 'Playlist'; + case 'libraries.groupings.all': + return 'Tutti'; + case 'libraries.groupings.movies': + return 'Film'; + case 'libraries.groupings.shows': + return 'Serie TV'; + case 'libraries.groupings.seasons': + return 'Stagioni'; + case 'libraries.groupings.episodes': + return 'Episodi'; + case 'libraries.groupings.folders': + return 'Cartelle'; + case 'about.title': + return 'Informazioni'; + case 'about.openSourceLicenses': + return 'Licenze Open Source'; + case 'about.versionLabel': + return ({required Object version}) => 'Versione ${version}'; + case 'about.appDescription': + return 'Un bellissimo client Plex per Flutter'; + case 'about.viewLicensesDescription': + return 'Visualizza le licenze delle librerie di terze parti'; + case 'serverSelection.connectingToServer': + return 'Connessione al server...'; + case 'serverSelection.connectingToAllServers': + return 'Connessione a tutti i server...'; + case 'serverSelection.connectToAllServers': + return 'Connetti a tutti i server'; + case 'serverSelection.allServerConnectionsFailed': + return 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.'; + case 'serverSelection.foundServers': + return ({required Object count}) => 'Trovati ${count} server'; + case 'serverSelection.orSelectIndividually': + return 'Oppure seleziona un server singolarmente:'; + case 'serverSelection.serverDebugCopied': + return 'Dati di debug del server copiati negli appunti'; + case 'serverSelection.copyDebugData': + return 'Copia dati di debug'; + case 'serverSelection.noServersFound': + return 'Nessun server trovato'; + case 'serverSelection.malformedServerData': + return ({required Object count}) => + 'Trovato ${count} server con dati difettosi. Nessun server valido disponibile.'; + case 'serverSelection.incompleteServerInfo': + return 'Alcuni server presentano informazioni incomplete e sono stati ignorati. Controlla il tuo account Plex.tv.'; + case 'serverSelection.incompleteConnectionInfo': + return 'Le informazioni di connessione al server sono incomplete. Riprova.'; + case 'serverSelection.malformedServerInfo': + return ({required Object message}) => + 'Le informazioni sul server sono errate: ${message}'; + case 'serverSelection.networkConnectionFailed': + return 'Connessione di rete non riuscita. Controlla la tua connessione Internet e riprova.'; + case 'serverSelection.authenticationFailed': + return 'Autenticazione fallita. Effettua nuovamente l\'accesso.'; + case 'serverSelection.plexServiceUnavailable': + return 'Servizio Plex non disponibile. Riprova più tardi.'; + case 'serverSelection.failedToLoadServers': + return ({required Object error}) => + 'Impossibile caricare i server: ${error}'; + case 'hubDetail.title': + return 'Titolo'; + case 'hubDetail.releaseYear': + return 'Anno rilascio'; + case 'hubDetail.dateAdded': + return 'Data aggiunta'; + case 'hubDetail.rating': + return 'Valutazione'; + case 'hubDetail.noItemsFound': + return 'Nessun elemento trovato'; + case 'logs.title': + return 'Registro'; + case 'logs.clearLogs': + return 'Cancella log'; + case 'logs.copyLogs': + return 'Copia log'; + case 'logs.exportLogs': + return 'Esporta log'; + case 'logs.noLogsToShow': + return 'Nessun log da mostrare'; + case 'logs.error': + return 'Errore:'; + case 'logs.stackTrace': + return 'Traccia dello stack:'; + case 'licenses.relatedPackages': + return 'Pacchetti correlati'; + case 'licenses.license': + return 'Licenza'; + case 'licenses.licenseNumber': + return ({required Object number}) => 'Licenza ${number}'; + case 'licenses.licensesCount': + return ({required Object count}) => '${count} licenze'; + case 'navigation.home': + return 'Home'; + case 'navigation.search': + return 'Cerca'; + case 'navigation.libraries': + return 'Librerie'; + case 'navigation.settings': + return 'Impostazioni'; + case 'playlists.title': + return 'Playlist'; + case 'playlists.noPlaylists': + return 'Nessuna playlist trovata'; + case 'playlists.create': + return 'Crea playlist'; + case 'playlists.newPlaylist': + return 'Nuova playlist'; + case 'playlists.playlistName': + return 'Nome playlist'; + case 'playlists.enterPlaylistName': + return 'Inserisci nome playlist'; + case 'playlists.edit': + return 'Modifica playlist'; + case 'playlists.delete': + return 'Elimina playlist'; + case 'playlists.addTo': + return 'Aggiungi a playlist'; + case 'playlists.addItems': + return 'Aggiungi elementi'; + case 'playlists.removeItem': + return 'Rimuovi da playlist'; + case 'playlists.clearPlaylist': + return 'Svuota playlist'; + case 'playlists.playAll': + return 'Riproduci tutto'; + case 'playlists.shuffle': + return 'Casuale'; + case 'playlists.smartPlaylist': + return 'Playlist intelligente'; + case 'playlists.regularPlaylist': + return 'Playlist normale'; + case 'playlists.itemCount': + return ({required Object count}) => '${count} elementi'; + case 'playlists.oneItem': + return '1 elemento'; + case 'playlists.emptyPlaylist': + return 'Questa playlist è vuota'; + case 'playlists.deleteConfirm': + return 'Eliminare playlist?'; + case 'playlists.deleteMessage': + return ({required Object name}) => + 'Sei sicuro di voler eliminare "${name}"?'; + case 'playlists.created': + return 'Playlist creata'; + case 'playlists.updated': + return 'Playlist aggiornata'; + case 'playlists.deleted': + return 'Playlist eliminata'; + case 'playlists.itemAdded': + return 'Aggiunto alla playlist'; + case 'playlists.itemRemoved': + return 'Rimosso dalla playlist'; + case 'playlists.selectPlaylist': + return 'Seleziona playlist'; + case 'playlists.createNewPlaylist': + return 'Crea nuova playlist'; + case 'playlists.errorCreating': + return 'Errore durante la creazione della playlist'; + case 'playlists.errorDeleting': + return 'Errore durante l\'eliminazione della playlist'; + case 'playlists.errorLoading': + return 'Errore durante il caricamento delle playlist'; + case 'playlists.errorAdding': + return 'Errore durante l\'aggiunta alla playlist'; + case 'playlists.errorReordering': + return 'Errore durante il riordino dell\'elemento della playlist'; + case 'playlists.errorRemoving': + return 'Errore durante la rimozione dalla playlist'; + case 'playlists.playlist': + return 'Playlist'; + case 'collections.title': + return 'Raccolte'; + case 'collections.collection': + return 'Raccolta'; + case 'collections.empty': + return 'La raccolta è vuota'; + case 'collections.noItems': + return 'Nessun elemento in questa raccolta'; + case 'collections.unknownLibrarySection': + return 'Impossibile eliminare: sezione libreria sconosciuta'; + case 'collections.deleteCollection': + return 'Elimina raccolta'; + case 'collections.deleteConfirm': + return ({required Object title}) => + 'Sei sicuro di voler eliminare "${title}"? Questa azione non può essere annullata.'; + case 'collections.deleted': + return 'Raccolta eliminata'; + case 'collections.deleteFailed': + return 'Impossibile eliminare la raccolta'; + case 'collections.deleteFailedWithError': + return ({required Object error}) => + 'Impossibile eliminare la raccolta: ${error}'; + case 'collections.failedToLoadItems': + return ({required Object error}) => + 'Impossibile caricare gli elementi della raccolta: ${error}'; + case 'collections.addTo': + return 'Aggiungi alla raccolta'; + case 'collections.selectCollection': + return 'Seleziona raccolta'; + case 'collections.createNewCollection': + return 'Crea nuova raccolta'; + case 'collections.collectionName': + return 'Nome raccolta'; + case 'collections.enterCollectionName': + return 'Inserisci nome raccolta'; + case 'collections.addedToCollection': + return 'Aggiunto alla raccolta'; + case 'collections.errorAddingToCollection': + return 'Errore nell\'aggiunta alla raccolta'; + case 'collections.created': + return 'Raccolta creata'; + case 'collections.removeFromCollection': + return 'Rimuovi dalla raccolta'; + case 'collections.removeFromCollectionConfirm': + return ({required Object title}) => + 'Rimuovere "${title}" da questa raccolta?'; + case 'collections.removedFromCollection': + return 'Rimosso dalla raccolta'; + case 'collections.removeFromCollectionFailed': + return 'Impossibile rimuovere dalla raccolta'; + case 'collections.removeFromCollectionError': + return ({required Object error}) => + 'Errore durante la rimozione dalla raccolta: ${error}'; + default: + return null; + } + } } extension on _StringsNl { - dynamic _flatMapFunction(String path) { - switch (path) { - case 'app.title': return 'Plezy'; - case 'app.loading': return 'Laden...'; - case 'auth.signInWithPlex': return 'Inloggen met Plex'; - case 'auth.showQRCode': return 'Toon QR-code'; - case 'auth.cancel': return 'Annuleren'; - case 'auth.authenticate': return 'Authenticeren'; - case 'auth.retry': return 'Opnieuw proberen'; - case 'auth.debugEnterToken': return 'Debug: Voer Plex Token in'; - case 'auth.plexTokenLabel': return 'Plex Authenticatietoken'; - case 'auth.plexTokenHint': return 'Voer je Plex.tv token in'; - case 'auth.authenticationTimeout': return 'Authenticatie verlopen. Probeer opnieuw.'; - case 'auth.scanQRCodeInstruction': return 'Scan deze QR-code met een apparaat dat is ingelogd op Plex om te authenticeren.'; - case 'auth.waitingForAuth': return 'Wachten op authenticatie...\nVoltooi het inloggen in je browser.'; - case 'common.cancel': return 'Annuleren'; - case 'common.save': return 'Opslaan'; - case 'common.close': return 'Sluiten'; - case 'common.clear': return 'Wissen'; - case 'common.reset': return 'Resetten'; - case 'common.later': return 'Later'; - case 'common.submit': return 'Verzenden'; - case 'common.confirm': return 'Bevestigen'; - case 'common.retry': return 'Opnieuw proberen'; - case 'common.playNow': return 'Nu afspelen'; - case 'common.logout': return 'Uitloggen'; - case 'common.online': return 'Online'; - case 'common.offline': return 'Offline'; - case 'common.owned': return 'Eigendom'; - case 'common.shared': return 'Gedeeld'; - case 'common.current': return 'HUIDIG'; - case 'common.unknown': return 'Onbekend'; - case 'common.refresh': return 'Vernieuwen'; - case 'common.yes': return 'Ja'; - case 'common.no': return 'Nee'; - case 'common.server': return 'Server'; - case 'common.delete': return 'Verwijderen'; - case 'common.shuffle': return 'Willekeurig'; - case 'common.addTo': return 'Toevoegen aan...'; - case 'screens.licenses': return 'Licenties'; - case 'screens.selectServer': return 'Selecteer server'; - case 'screens.switchProfile': return 'Wissel van profiel'; - case 'screens.subtitleStyling': return 'Ondertitel opmaak'; - case 'screens.search': return 'Zoeken'; - case 'screens.logs': return 'Logbestanden'; - case 'update.available': return 'Update beschikbaar'; - case 'update.versionAvailable': return ({required Object version}) => 'Versie ${version} is beschikbaar'; - case 'update.currentVersion': return ({required Object version}) => 'Huidig: ${version}'; - case 'update.skipVersion': return 'Deze versie overslaan'; - case 'update.viewRelease': return 'Bekijk release'; - case 'update.latestVersion': return 'Je hebt de nieuwste versie'; - case 'update.checkFailed': return 'Kon niet controleren op updates'; - case 'settings.title': return 'Instellingen'; - case 'settings.language': return 'Taal'; - case 'settings.theme': return 'Thema'; - case 'settings.appearance': return 'Uiterlijk'; - case 'settings.videoPlayback': return 'Video afspelen'; - case 'settings.shufflePlay': return 'Willekeurig afspelen'; - case 'settings.advanced': return 'Geavanceerd'; - case 'settings.useSeasonPostersDescription': return 'Toon seizoenposter in plaats van serieposter voor afleveringen'; - case 'settings.showHeroSectionDescription': return 'Toon uitgelichte inhoud carrousel op startscherm'; - case 'settings.secondsLabel': return 'Seconden'; - case 'settings.minutesLabel': return 'Minuten'; - case 'settings.secondsShort': return 's'; - case 'settings.minutesShort': return 'm'; - case 'settings.durationHint': return ({required Object min, required Object max}) => 'Voer duur in (${min}-${max})'; - case 'settings.systemTheme': return 'Systeem'; - case 'settings.systemThemeDescription': return 'Volg systeeminstellingen'; - case 'settings.lightTheme': return 'Licht'; - case 'settings.darkTheme': return 'Donker'; - case 'settings.libraryDensity': return 'Bibliotheek dichtheid'; - case 'settings.compact': return 'Compact'; - case 'settings.compactDescription': return 'Kleinere kaarten, meer items zichtbaar'; - case 'settings.normal': return 'Normaal'; - case 'settings.normalDescription': return 'Standaard grootte'; - case 'settings.comfortable': return 'Comfortabel'; - case 'settings.comfortableDescription': return 'Grotere kaarten, minder items zichtbaar'; - case 'settings.viewMode': return 'Weergavemodus'; - case 'settings.gridView': return 'Raster'; - case 'settings.gridViewDescription': return 'Items weergeven in een rasterindeling'; - case 'settings.listView': return 'Lijst'; - case 'settings.listViewDescription': return 'Items weergeven in een lijstindeling'; - case 'settings.useSeasonPosters': return 'Gebruik seizoenposters'; - case 'settings.showHeroSection': return 'Toon hoofdsectie'; - case 'settings.hardwareDecoding': return 'Hardware decodering'; - case 'settings.hardwareDecodingDescription': return 'Gebruik hardware versnelling indien beschikbaar'; - case 'settings.bufferSize': return 'Buffer grootte'; - case 'settings.bufferSizeMB': return ({required Object size}) => '${size}MB'; - case 'settings.subtitleStyling': return 'Ondertitel opmaak'; - case 'settings.subtitleStylingDescription': return 'Pas ondertitel uiterlijk aan'; - case 'settings.smallSkipDuration': return 'Korte skip duur'; - case 'settings.largeSkipDuration': return 'Lange skip duur'; - case 'settings.secondsUnit': return ({required Object seconds}) => '${seconds} seconden'; - case 'settings.defaultSleepTimer': return 'Standaard slaap timer'; - case 'settings.minutesUnit': return ({required Object minutes}) => 'bij ${minutes} minuten'; - case 'settings.rememberTrackSelections': return 'Onthoud track selecties per serie/film'; - case 'settings.rememberTrackSelectionsDescription': return 'Bewaar automatisch audio- en ondertiteltaalvoorkeuren wanneer je tracks wijzigt tijdens afspelen'; - case 'settings.unwatchedOnly': return 'Alleen ongekeken'; - case 'settings.unwatchedOnlyDescription': return 'Alleen ongekeken afleveringen opnemen in willekeurige wachtrij'; - case 'settings.shuffleOrderNavigation': return 'Willekeurige volgorde navigatie'; - case 'settings.shuffleOrderNavigationDescription': return 'Volgende/vorige knoppen volgen willekeurige volgorde'; - case 'settings.loopShuffleQueue': return 'Herhaal willekeurige wachtrij'; - case 'settings.loopShuffleQueueDescription': return 'Start wachtrij opnieuw bij het einde'; - case 'settings.videoPlayerControls': return 'Videospeler bediening'; - case 'settings.keyboardShortcuts': return 'Toetsenbord sneltoetsen'; - case 'settings.keyboardShortcutsDescription': return 'Pas toetsenbord sneltoetsen aan'; - case 'settings.debugLogging': return 'Debug logging'; - case 'settings.debugLoggingDescription': return 'Schakel gedetailleerde logging in voor probleemoplossing'; - case 'settings.viewLogs': return 'Bekijk logs'; - case 'settings.viewLogsDescription': return 'Bekijk applicatie logs'; - case 'settings.clearCache': return 'Cache wissen'; - case 'settings.clearCacheDescription': return 'Dit wist alle gecachte afbeeldingen en gegevens. De app kan langer duren om inhoud te laden na het wissen van de cache.'; - case 'settings.clearCacheSuccess': return 'Cache succesvol gewist'; - case 'settings.resetSettings': return 'Instellingen resetten'; - case 'settings.resetSettingsDescription': return 'Dit reset alle instellingen naar hun standaard waarden. Deze actie kan niet ongedaan gemaakt worden.'; - case 'settings.resetSettingsSuccess': return 'Instellingen succesvol gereset'; - case 'settings.shortcutsReset': return 'Sneltoetsen gereset naar standaard'; - case 'settings.about': return 'Over'; - case 'settings.aboutDescription': return 'App informatie en licenties'; - case 'settings.updates': return 'Updates'; - case 'settings.updateAvailable': return 'Update beschikbaar'; - case 'settings.checkForUpdates': return 'Controleer op updates'; - case 'settings.validationErrorEnterNumber': return 'Voer een geldig nummer in'; - case 'settings.validationErrorDuration': return ({required Object min, required Object max, required Object unit}) => 'Duur moet tussen ${min} en ${max} ${unit} zijn'; - case 'settings.shortcutAlreadyAssigned': return ({required Object action}) => 'Sneltoets al toegewezen aan ${action}'; - case 'settings.shortcutUpdated': return ({required Object action}) => 'Sneltoets bijgewerkt voor ${action}'; - case 'search.hint': return 'Zoek films, series, muziek...'; - case 'search.tryDifferentTerm': return 'Probeer een andere zoekterm'; - case 'search.searchYourMedia': return 'Zoek in je media'; - case 'search.enterTitleActorOrKeyword': return 'Voer een titel, acteur of trefwoord in'; - case 'hotkeys.setShortcutFor': return ({required Object actionName}) => 'Stel sneltoets in voor ${actionName}'; - case 'hotkeys.clearShortcut': return 'Wis sneltoets'; - case 'pinEntry.enterPin': return 'Voer PIN in'; - case 'pinEntry.showPin': return 'Toon PIN'; - case 'pinEntry.hidePin': return 'Verberg PIN'; - case 'fileInfo.title': return 'Bestand info'; - case 'fileInfo.video': return 'Video'; - case 'fileInfo.audio': return 'Audio'; - case 'fileInfo.file': return 'Bestand'; - case 'fileInfo.advanced': return 'Geavanceerd'; - case 'fileInfo.codec': return 'Codec'; - case 'fileInfo.resolution': return 'Resolutie'; - case 'fileInfo.bitrate': return 'Bitrate'; - case 'fileInfo.frameRate': return 'Frame rate'; - case 'fileInfo.aspectRatio': return 'Beeldverhouding'; - case 'fileInfo.profile': return 'Profiel'; - case 'fileInfo.bitDepth': return 'Bit diepte'; - case 'fileInfo.colorSpace': return 'Kleurruimte'; - case 'fileInfo.colorRange': return 'Kleurbereik'; - case 'fileInfo.colorPrimaries': return 'Kleurprimaires'; - case 'fileInfo.chromaSubsampling': return 'Chroma subsampling'; - case 'fileInfo.channels': return 'Kanalen'; - case 'fileInfo.path': return 'Pad'; - case 'fileInfo.size': return 'Grootte'; - case 'fileInfo.container': return 'Container'; - case 'fileInfo.duration': return 'Duur'; - case 'fileInfo.optimizedForStreaming': return 'Geoptimaliseerd voor streaming'; - case 'fileInfo.has64bitOffsets': return '64-bit Offsets'; - case 'mediaMenu.markAsWatched': return 'Markeer als gekeken'; - case 'mediaMenu.markAsUnwatched': return 'Markeer als ongekeken'; - case 'mediaMenu.removeFromContinueWatching': return 'Verwijder uit Doorgaan met kijken'; - case 'mediaMenu.goToSeries': return 'Ga naar serie'; - case 'mediaMenu.goToSeason': return 'Ga naar seizoen'; - case 'mediaMenu.shufflePlay': return 'Willekeurig afspelen'; - case 'mediaMenu.fileInfo': return 'Bestand info'; - case 'accessibility.mediaCardMovie': return ({required Object title}) => '${title}, film'; - case 'accessibility.mediaCardShow': return ({required Object title}) => '${title}, TV-serie'; - case 'accessibility.mediaCardEpisode': return ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - case 'accessibility.mediaCardSeason': return ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - case 'accessibility.mediaCardWatched': return 'bekeken'; - case 'accessibility.mediaCardPartiallyWatched': return ({required Object percent}) => '${percent} procent bekeken'; - case 'accessibility.mediaCardUnwatched': return 'niet bekeken'; - case 'accessibility.tapToPlay': return 'Tik om af te spelen'; - case 'tooltips.shufflePlay': return 'Willekeurig afspelen'; - case 'tooltips.markAsWatched': return 'Markeer als gekeken'; - case 'tooltips.markAsUnwatched': return 'Markeer als ongekeken'; - case 'videoControls.audioLabel': return 'Audio'; - case 'videoControls.subtitlesLabel': return 'Ondertitels'; - case 'videoControls.resetToZero': return 'Reset naar 0ms'; - case 'videoControls.addTime': return ({required Object amount, required Object unit}) => '+${amount}${unit}'; - case 'videoControls.minusTime': return ({required Object amount, required Object unit}) => '-${amount}${unit}'; - case 'videoControls.playsLater': return ({required Object label}) => '${label} speelt later af'; - case 'videoControls.playsEarlier': return ({required Object label}) => '${label} speelt eerder af'; - case 'videoControls.noOffset': return 'Geen offset'; - case 'videoControls.letterbox': return 'Letterbox'; - case 'videoControls.fillScreen': return 'Vul scherm'; - case 'videoControls.stretch': return 'Uitrekken'; - case 'videoControls.lockRotation': return 'Vergrendel rotatie'; - case 'videoControls.unlockRotation': return 'Ontgrendel rotatie'; - case 'videoControls.sleepTimer': return 'Slaaptimer'; - case 'videoControls.timerActive': return 'Timer actief'; - case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Afspelen wordt gepauzeerd over ${duration}'; - case 'videoControls.sleepTimerCompleted': return 'Slaaptimer voltooid - afspelen gepauzeerd'; - case 'videoControls.playButton': return 'Afspelen'; - case 'videoControls.pauseButton': return 'Pauzeren'; - case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Terugspoelen ${seconds} seconden'; - case 'videoControls.seekForwardButton': return ({required Object seconds}) => 'Vooruitspoelen ${seconds} seconden'; - case 'videoControls.previousButton': return 'Vorige aflevering'; - case 'videoControls.nextButton': return 'Volgende aflevering'; - case 'videoControls.previousChapterButton': return 'Vorig hoofdstuk'; - case 'videoControls.nextChapterButton': return 'Volgend hoofdstuk'; - case 'videoControls.volumeButton': return 'Volume'; - case 'videoControls.muteButton': return 'Dempen'; - case 'videoControls.unmuteButton': return 'Dempen opheffen'; - case 'videoControls.settingsButton': return 'Video-instellingen'; - case 'videoControls.audioTrackButton': return 'Audiosporen'; - case 'videoControls.subtitlesButton': return 'Ondertitels'; - case 'videoControls.chaptersButton': return 'Hoofdstukken'; - case 'videoControls.versionsButton': return 'Videoversies'; - case 'videoControls.aspectRatioButton': return 'Beeldverhouding'; - case 'videoControls.fullscreenButton': return 'Volledig scherm activeren'; - case 'videoControls.exitFullscreenButton': return 'Volledig scherm verlaten'; - case 'videoControls.rotationLockButton': return 'Rotatievergrendeling'; - case 'videoControls.timelineSlider': return 'Videotijdlijn'; - case 'videoControls.volumeSlider': return 'Volumeniveau'; - case 'videoControls.backButton': return 'Terug'; - case 'userStatus.admin': return 'Beheerder'; - case 'userStatus.restricted': return 'Beperkt'; - case 'userStatus.protected': return 'Beschermd'; - case 'userStatus.current': return 'HUIDIG'; - case 'messages.markedAsWatched': return 'Gemarkeerd als gekeken'; - case 'messages.markedAsUnwatched': return 'Gemarkeerd als ongekeken'; - case 'messages.removedFromContinueWatching': return 'Verwijderd uit Doorgaan met kijken'; - case 'messages.errorLoading': return ({required Object error}) => 'Fout: ${error}'; - case 'messages.fileInfoNotAvailable': return 'Bestand informatie niet beschikbaar'; - case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Fout bij laden bestand info: ${error}'; - case 'messages.errorLoadingSeries': return 'Fout bij laden serie'; - case 'messages.errorLoadingSeason': return 'Fout bij laden seizoen'; - case 'messages.musicNotSupported': return 'Muziek afspelen wordt nog niet ondersteund'; - case 'messages.logsCleared': return 'Logs gewist'; - case 'messages.logsCopied': return 'Logs gekopieerd naar klembord'; - case 'messages.noLogsAvailable': return 'Geen logs beschikbaar'; - case 'messages.libraryScanning': return ({required Object title}) => 'Scannen "${title}"...'; - case 'messages.libraryScanStarted': return ({required Object title}) => 'Bibliotheek scan gestart voor "${title}"'; - case 'messages.libraryScanFailed': return ({required Object error}) => 'Kon bibliotheek niet scannen: ${error}'; - case 'messages.metadataRefreshing': return ({required Object title}) => 'Metadata vernieuwen voor "${title}"...'; - case 'messages.metadataRefreshStarted': return ({required Object title}) => 'Metadata vernieuwen gestart voor "${title}"'; - case 'messages.metadataRefreshFailed': return ({required Object error}) => 'Kon metadata niet vernieuwen: ${error}'; - case 'messages.noPlexToken': return 'Geen Plex token gevonden. Log opnieuw in.'; - case 'messages.logoutConfirm': return 'Weet je zeker dat je wilt uitloggen?'; - case 'messages.noSeasonsFound': return 'Geen seizoenen gevonden'; - case 'messages.noEpisodesFound': return 'Geen afleveringen gevonden in eerste seizoen'; - case 'messages.noEpisodesFoundGeneral': return 'Geen afleveringen gevonden'; - case 'messages.noResultsFound': return 'Geen resultaten gevonden'; - case 'messages.sleepTimerSet': return ({required Object label}) => 'Slaap timer ingesteld voor ${label}'; - case 'messages.failedToSwitchProfile': return ({required Object displayName}) => 'Kon niet wisselen naar ${displayName}'; - case 'messages.noItemsAvailable': return 'Geen items beschikbaar'; - case 'messages.failedToCreatePlayQueue': return 'Kan afspeelwachtrij niet maken'; - case 'messages.failedToCreatePlayQueueNoItems': return 'Kan afspeelwachtrij niet maken - geen items'; - case 'messages.failedPlayback': return ({required Object action, required Object error}) => 'Afspelen van ${action} mislukt: ${error}'; - case 'profile.noUsersAvailable': return 'Geen gebruikers beschikbaar'; - case 'subtitlingStyling.stylingOptions': return 'Opmaak opties'; - case 'subtitlingStyling.fontSize': return 'Lettergrootte'; - case 'subtitlingStyling.textColor': return 'Tekstkleur'; - case 'subtitlingStyling.borderSize': return 'Rand grootte'; - case 'subtitlingStyling.borderColor': return 'Randkleur'; - case 'subtitlingStyling.backgroundOpacity': return 'Achtergrond transparantie'; - case 'subtitlingStyling.backgroundColor': return 'Achtergrondkleur'; - case 'dialog.confirmAction': return 'Bevestig actie'; - case 'dialog.areYouSure': return 'Weet je zeker dat je deze actie wilt uitvoeren?'; - case 'dialog.cancel': return 'Annuleren'; - case 'dialog.playNow': return 'Nu afspelen'; - case 'discover.title': return 'Ontdekken'; - case 'discover.switchProfile': return 'Wissel van profiel'; - case 'discover.switchServer': return 'Wissel van server'; - case 'discover.logout': return 'Uitloggen'; - case 'discover.noContentAvailable': return 'Geen inhoud beschikbaar'; - case 'discover.addMediaToLibraries': return 'Voeg wat media toe aan je bibliotheken'; - case 'discover.continueWatching': return 'Verder kijken'; - case 'discover.recentlyAdded': return 'Recent toegevoegd'; - case 'discover.play': return 'Afspelen'; - case 'discover.resume': return 'Hervatten'; - case 'discover.playEpisode': return ({required Object season, required Object episode}) => 'Speel S${season}, E${episode}'; - case 'discover.resumeEpisode': return ({required Object season, required Object episode}) => 'Hervat S${season}, E${episode}'; - case 'discover.pause': return 'Pauzeren'; - case 'discover.overview': return 'Overzicht'; - case 'discover.cast': return 'Acteurs'; - case 'discover.seasons': return 'Seizoenen'; - case 'discover.studio': return 'Studio'; - case 'discover.rating': return 'Leeftijd'; - case 'discover.watched': return 'Bekeken'; - case 'discover.episodeCount': return ({required Object count}) => '${count} afleveringen'; - case 'discover.watchedProgress': return ({required Object watched, required Object total}) => '${watched}/${total} gekeken'; - case 'discover.movie': return 'Film'; - case 'discover.tvShow': return 'TV Serie'; - case 'discover.minutesLeft': return ({required Object minutes}) => '${minutes} min over'; - case 'errors.searchFailed': return ({required Object error}) => 'Zoeken mislukt: ${error}'; - case 'errors.connectionTimeout': return ({required Object context}) => 'Verbinding time-out tijdens laden ${context}'; - case 'errors.connectionFailed': return 'Kan geen verbinding maken met Plex server'; - case 'errors.failedToLoad': return ({required Object context, required Object error}) => 'Kon ${context} niet laden: ${error}'; - case 'errors.noClientAvailable': return 'Geen client beschikbaar'; - case 'errors.authenticationFailed': return ({required Object error}) => 'Authenticatie mislukt: ${error}'; - case 'errors.couldNotLaunchUrl': return 'Kon auth URL niet openen'; - case 'errors.pleaseEnterToken': return 'Voer een token in'; - case 'errors.invalidToken': return 'Ongeldig token'; - case 'errors.failedToVerifyToken': return ({required Object error}) => 'Kon token niet verifiëren: ${error}'; - case 'errors.failedToSwitchProfile': return ({required Object displayName}) => 'Kon niet wisselen naar ${displayName}'; - case 'errors.connectionFailedGeneric': return 'Verbinding mislukt'; - case 'libraries.title': return 'Bibliotheken'; - case 'libraries.scanLibraryFiles': return 'Scan bibliotheek bestanden'; - case 'libraries.scanLibrary': return 'Scan bibliotheek'; - case 'libraries.analyze': return 'Analyseren'; - case 'libraries.analyzeLibrary': return 'Analyseer bibliotheek'; - case 'libraries.refreshMetadata': return 'Vernieuw metadata'; - case 'libraries.emptyTrash': return 'Prullenbak legen'; - case 'libraries.emptyingTrash': return ({required Object title}) => 'Prullenbak legen voor "${title}"...'; - case 'libraries.trashEmptied': return ({required Object title}) => 'Prullenbak geleegd voor "${title}"'; - case 'libraries.failedToEmptyTrash': return ({required Object error}) => 'Kon prullenbak niet legen: ${error}'; - case 'libraries.analyzing': return ({required Object title}) => 'Analyseren "${title}"...'; - case 'libraries.analysisStarted': return ({required Object title}) => 'Analyse gestart voor "${title}"'; - case 'libraries.failedToAnalyze': return ({required Object error}) => 'Kon bibliotheek niet analyseren: ${error}'; - case 'libraries.noLibrariesFound': return 'Geen bibliotheken gevonden'; - case 'libraries.thisLibraryIsEmpty': return 'Deze bibliotheek is leeg'; - case 'libraries.all': return 'Alles'; - case 'libraries.clearAll': return 'Alles wissen'; - case 'libraries.scanLibraryConfirm': return ({required Object title}) => 'Weet je zeker dat je "${title}" wilt scannen?'; - case 'libraries.analyzeLibraryConfirm': return ({required Object title}) => 'Weet je zeker dat je "${title}" wilt analyseren?'; - case 'libraries.refreshMetadataConfirm': return ({required Object title}) => 'Weet je zeker dat je metadata wilt vernieuwen voor "${title}"?'; - case 'libraries.emptyTrashConfirm': return ({required Object title}) => 'Weet je zeker dat je de prullenbak wilt legen voor "${title}"?'; - case 'libraries.manageLibraries': return 'Beheer bibliotheken'; - case 'libraries.sort': return 'Sorteren'; - case 'libraries.sortBy': return 'Sorteer op'; - case 'libraries.filters': return 'Filters'; - case 'libraries.loadingLibraryWithCount': return ({required Object count}) => 'Bibliotheek laden... (${count} items geladen)'; - case 'libraries.confirmActionMessage': return 'Weet je zeker dat je deze actie wilt uitvoeren?'; - case 'libraries.showLibrary': return 'Toon bibliotheek'; - case 'libraries.hideLibrary': return 'Verberg bibliotheek'; - case 'libraries.libraryOptions': return 'Bibliotheek opties'; - case 'libraries.content': return 'bibliotheekinhoud'; - case 'libraries.selectLibrary': return 'Bibliotheek kiezen'; - case 'libraries.filtersWithCount': return ({required Object count}) => 'Filters (${count})'; - case 'libraries.noRecommendations': return 'Geen aanbevelingen beschikbaar'; - case 'libraries.noCollections': return 'Geen collecties in deze bibliotheek'; - case 'libraries.noFoldersFound': return 'Geen mappen gevonden'; - case 'libraries.folders': return 'mappen'; - case 'libraries.tabs.recommended': return 'Aanbevolen'; - case 'libraries.tabs.browse': return 'Bladeren'; - case 'libraries.tabs.collections': return 'Collecties'; - case 'libraries.tabs.playlists': return 'Afspeellijsten'; - case 'libraries.groupings.all': return 'Alles'; - case 'libraries.groupings.movies': return 'Films'; - case 'libraries.groupings.shows': return 'Series'; - case 'libraries.groupings.seasons': return 'Seizoenen'; - case 'libraries.groupings.episodes': return 'Afleveringen'; - case 'libraries.groupings.folders': return 'Mappen'; - case 'about.title': return 'Over'; - case 'about.openSourceLicenses': return 'Open Source licenties'; - case 'about.versionLabel': return ({required Object version}) => 'Versie ${version}'; - case 'about.appDescription': return 'Een mooie Plex client voor Flutter'; - case 'about.viewLicensesDescription': return 'Bekijk licenties van third-party bibliotheken'; - case 'serverSelection.connectingToServer': return 'Verbinden met server...'; - case 'serverSelection.connectingToAllServers': return 'Verbinden met alle servers...'; - case 'serverSelection.connectToAllServers': return 'Verbind met alle servers'; - case 'serverSelection.allServerConnectionsFailed': return 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.'; - case 'serverSelection.foundServers': return ({required Object count}) => '${count} server(s) gevonden'; - case 'serverSelection.orSelectIndividually': return 'Of selecteer een server individueel:'; - case 'serverSelection.serverDebugCopied': return 'Server debug gegevens gekopieerd naar klembord'; - case 'serverSelection.copyDebugData': return 'Kopieer debug gegevens'; - case 'serverSelection.noServersFound': return 'Geen servers gevonden'; - case 'serverSelection.malformedServerData': return ({required Object count}) => '${count} server(s) gevonden met verkeerde data. Geen geldige servers beschikbaar.'; - case 'serverSelection.incompleteServerInfo': return 'Sommige servers hebben incomplete informatie en zijn overgeslagen. Controleer je Plex.tv account.'; - case 'serverSelection.incompleteConnectionInfo': return 'Server verbinding informatie is incompleet. Probeer opnieuw.'; - case 'serverSelection.malformedServerInfo': return ({required Object message}) => 'Server informatie is verkeerd geformatteerd: ${message}'; - case 'serverSelection.networkConnectionFailed': return 'Netwerkverbinding mislukt. Controleer je internetverbinding en probeer opnieuw.'; - case 'serverSelection.authenticationFailed': return 'Authenticatie mislukt. Log opnieuw in.'; - case 'serverSelection.plexServiceUnavailable': return 'Plex service niet beschikbaar. Probeer later opnieuw.'; - case 'serverSelection.failedToLoadServers': return ({required Object error}) => 'Kon servers niet laden: ${error}'; - case 'hubDetail.title': return 'Titel'; - case 'hubDetail.releaseYear': return 'Uitgavejaar'; - case 'hubDetail.dateAdded': return 'Datum toegevoegd'; - case 'hubDetail.rating': return 'Beoordeling'; - case 'hubDetail.noItemsFound': return 'Geen items gevonden'; - case 'logs.title': return 'Logbestanden'; - case 'logs.clearLogs': return 'Wis logs'; - case 'logs.copyLogs': return 'Kopieer logs'; - case 'logs.exportLogs': return 'Exporteer logs'; - case 'logs.noLogsToShow': return 'Geen logs om te tonen'; - case 'logs.error': return 'Fout:'; - case 'logs.stackTrace': return 'Stacktracering:'; - case 'licenses.relatedPackages': return 'Gerelateerde pakketten'; - case 'licenses.license': return 'Licentie'; - case 'licenses.licenseNumber': return ({required Object number}) => 'Licentie ${number}'; - case 'licenses.licensesCount': return ({required Object count}) => '${count} licenties'; - case 'navigation.home': return 'Thuis'; - case 'navigation.search': return 'Zoeken'; - case 'navigation.libraries': return 'Bibliotheken'; - case 'navigation.settings': return 'Instellingen'; - case 'playlists.title': return 'Afspeellijsten'; - case 'playlists.noPlaylists': return 'Geen afspeellijsten gevonden'; - case 'playlists.create': return 'Afspeellijst maken'; - case 'playlists.newPlaylist': return 'Nieuwe afspeellijst'; - case 'playlists.playlistName': return 'Naam afspeellijst'; - case 'playlists.enterPlaylistName': return 'Voer naam afspeellijst in'; - case 'playlists.edit': return 'Afspeellijst bewerken'; - case 'playlists.delete': return 'Afspeellijst verwijderen'; - case 'playlists.addTo': return 'Toevoegen aan afspeellijst'; - case 'playlists.addItems': return 'Items toevoegen'; - case 'playlists.removeItem': return 'Verwijderen uit afspeellijst'; - case 'playlists.clearPlaylist': return 'Afspeellijst wissen'; - case 'playlists.playAll': return 'Alles afspelen'; - case 'playlists.shuffle': return 'Willekeurig'; - case 'playlists.smartPlaylist': return 'Slimme afspeellijst'; - case 'playlists.regularPlaylist': return 'Normale afspeellijst'; - case 'playlists.itemCount': return ({required Object count}) => '${count} items'; - case 'playlists.oneItem': return '1 item'; - case 'playlists.emptyPlaylist': return 'Deze afspeellijst is leeg'; - case 'playlists.deleteConfirm': return 'Afspeellijst verwijderen?'; - case 'playlists.deleteMessage': return ({required Object name}) => 'Weet je zeker dat je "${name}" wilt verwijderen?'; - case 'playlists.created': return 'Afspeellijst gemaakt'; - case 'playlists.updated': return 'Afspeellijst bijgewerkt'; - case 'playlists.deleted': return 'Afspeellijst verwijderd'; - case 'playlists.itemAdded': return 'Toegevoegd aan afspeellijst'; - case 'playlists.itemRemoved': return 'Verwijderd uit afspeellijst'; - case 'playlists.selectPlaylist': return 'Selecteer afspeellijst'; - case 'playlists.createNewPlaylist': return 'Nieuwe afspeellijst maken'; - case 'playlists.errorCreating': return 'Fout bij maken afspeellijst'; - case 'playlists.errorDeleting': return 'Fout bij verwijderen afspeellijst'; - case 'playlists.errorLoading': return 'Fout bij laden afspeellijsten'; - case 'playlists.errorAdding': return 'Fout bij toevoegen aan afspeellijst'; - case 'playlists.errorReordering': return 'Fout bij herschikken van afspeellijstitem'; - case 'playlists.errorRemoving': return 'Fout bij verwijderen uit afspeellijst'; - case 'playlists.playlist': return 'Afspeellijst'; - case 'collections.title': return 'Collecties'; - case 'collections.collection': return 'Collectie'; - case 'collections.empty': return 'Collectie is leeg'; - case 'collections.noItems': return 'Geen items in deze collectie'; - case 'collections.unknownLibrarySection': return 'Kan niet verwijderen: onbekende bibliotheeksectie'; - case 'collections.deleteCollection': return 'Collectie verwijderen'; - case 'collections.deleteConfirm': return ({required Object title}) => 'Weet je zeker dat je "${title}" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.'; - case 'collections.deleted': return 'Collectie verwijderd'; - case 'collections.deleteFailed': return 'Collectie verwijderen mislukt'; - case 'collections.deleteFailedWithError': return ({required Object error}) => 'Collectie verwijderen mislukt: ${error}'; - case 'collections.failedToLoadItems': return ({required Object error}) => 'Collectie-items laden mislukt: ${error}'; - case 'collections.addTo': return 'Toevoegen aan collectie'; - case 'collections.selectCollection': return 'Selecteer collectie'; - case 'collections.createNewCollection': return 'Nieuwe collectie maken'; - case 'collections.collectionName': return 'Collectienaam'; - case 'collections.enterCollectionName': return 'Voer collectienaam in'; - case 'collections.addedToCollection': return 'Toegevoegd aan collectie'; - case 'collections.errorAddingToCollection': return 'Fout bij toevoegen aan collectie'; - case 'collections.created': return 'Collectie gemaakt'; - case 'collections.removeFromCollection': return 'Verwijderen uit collectie'; - case 'collections.removeFromCollectionConfirm': return ({required Object title}) => '"${title}" uit deze collectie verwijderen?'; - case 'collections.removedFromCollection': return 'Uit collectie verwijderd'; - case 'collections.removeFromCollectionFailed': return 'Verwijderen uit collectie mislukt'; - case 'collections.removeFromCollectionError': return ({required Object error}) => 'Fout bij verwijderen uit collectie: ${error}'; - default: return null; - } - } + dynamic _flatMapFunction(String path) { + switch (path) { + case 'app.title': + return 'Plezy'; + case 'app.loading': + return 'Laden...'; + case 'auth.signInWithPlex': + return 'Inloggen met Plex'; + case 'auth.showQRCode': + return 'Toon QR-code'; + case 'auth.cancel': + return 'Annuleren'; + case 'auth.authenticate': + return 'Authenticeren'; + case 'auth.retry': + return 'Opnieuw proberen'; + case 'auth.debugEnterToken': + return 'Debug: Voer Plex Token in'; + case 'auth.plexTokenLabel': + return 'Plex Authenticatietoken'; + case 'auth.plexTokenHint': + return 'Voer je Plex.tv token in'; + case 'auth.authenticationTimeout': + return 'Authenticatie verlopen. Probeer opnieuw.'; + case 'auth.scanQRCodeInstruction': + return 'Scan deze QR-code met een apparaat dat is ingelogd op Plex om te authenticeren.'; + case 'auth.waitingForAuth': + return 'Wachten op authenticatie...\nVoltooi het inloggen in je browser.'; + case 'common.cancel': + return 'Annuleren'; + case 'common.save': + return 'Opslaan'; + case 'common.close': + return 'Sluiten'; + case 'common.clear': + return 'Wissen'; + case 'common.reset': + return 'Resetten'; + case 'common.later': + return 'Later'; + case 'common.submit': + return 'Verzenden'; + case 'common.confirm': + return 'Bevestigen'; + case 'common.retry': + return 'Opnieuw proberen'; + case 'common.playNow': + return 'Nu afspelen'; + case 'common.logout': + return 'Uitloggen'; + case 'common.online': + return 'Online'; + case 'common.offline': + return 'Offline'; + case 'common.owned': + return 'Eigendom'; + case 'common.shared': + return 'Gedeeld'; + case 'common.current': + return 'HUIDIG'; + case 'common.unknown': + return 'Onbekend'; + case 'common.refresh': + return 'Vernieuwen'; + case 'common.yes': + return 'Ja'; + case 'common.no': + return 'Nee'; + case 'common.server': + return 'Server'; + case 'common.delete': + return 'Verwijderen'; + case 'common.shuffle': + return 'Willekeurig'; + case 'common.addTo': + return 'Toevoegen aan...'; + case 'screens.licenses': + return 'Licenties'; + case 'screens.selectServer': + return 'Selecteer server'; + case 'screens.switchProfile': + return 'Wissel van profiel'; + case 'screens.subtitleStyling': + return 'Ondertitel opmaak'; + case 'screens.search': + return 'Zoeken'; + case 'screens.logs': + return 'Logbestanden'; + case 'update.available': + return 'Update beschikbaar'; + case 'update.versionAvailable': + return ({required Object version}) => + 'Versie ${version} is beschikbaar'; + case 'update.currentVersion': + return ({required Object version}) => 'Huidig: ${version}'; + case 'update.skipVersion': + return 'Deze versie overslaan'; + case 'update.viewRelease': + return 'Bekijk release'; + case 'update.latestVersion': + return 'Je hebt de nieuwste versie'; + case 'update.checkFailed': + return 'Kon niet controleren op updates'; + case 'settings.title': + return 'Instellingen'; + case 'settings.language': + return 'Taal'; + case 'settings.theme': + return 'Thema'; + case 'settings.appearance': + return 'Uiterlijk'; + case 'settings.videoPlayback': + return 'Video afspelen'; + case 'settings.shufflePlay': + return 'Willekeurig afspelen'; + case 'settings.advanced': + return 'Geavanceerd'; + case 'settings.useSeasonPostersDescription': + return 'Toon seizoenposter in plaats van serieposter voor afleveringen'; + case 'settings.showHeroSectionDescription': + return 'Toon uitgelichte inhoud carrousel op startscherm'; + case 'settings.secondsLabel': + return 'Seconden'; + case 'settings.minutesLabel': + return 'Minuten'; + case 'settings.secondsShort': + return 's'; + case 'settings.minutesShort': + return 'm'; + case 'settings.durationHint': + return ({required Object min, required Object max}) => + 'Voer duur in (${min}-${max})'; + case 'settings.systemTheme': + return 'Systeem'; + case 'settings.systemThemeDescription': + return 'Volg systeeminstellingen'; + case 'settings.lightTheme': + return 'Licht'; + case 'settings.darkTheme': + return 'Donker'; + case 'settings.libraryDensity': + return 'Bibliotheek dichtheid'; + case 'settings.compact': + return 'Compact'; + case 'settings.compactDescription': + return 'Kleinere kaarten, meer items zichtbaar'; + case 'settings.normal': + return 'Normaal'; + case 'settings.normalDescription': + return 'Standaard grootte'; + case 'settings.comfortable': + return 'Comfortabel'; + case 'settings.comfortableDescription': + return 'Grotere kaarten, minder items zichtbaar'; + case 'settings.viewMode': + return 'Weergavemodus'; + case 'settings.gridView': + return 'Raster'; + case 'settings.gridViewDescription': + return 'Items weergeven in een rasterindeling'; + case 'settings.listView': + return 'Lijst'; + case 'settings.listViewDescription': + return 'Items weergeven in een lijstindeling'; + case 'settings.useSeasonPosters': + return 'Gebruik seizoenposters'; + case 'settings.showHeroSection': + return 'Toon hoofdsectie'; + case 'settings.hardwareDecoding': + return 'Hardware decodering'; + case 'settings.hardwareDecodingDescription': + return 'Gebruik hardware versnelling indien beschikbaar'; + case 'settings.bufferSize': + return 'Buffer grootte'; + case 'settings.bufferSizeMB': + return ({required Object size}) => '${size}MB'; + case 'settings.subtitleStyling': + return 'Ondertitel opmaak'; + case 'settings.subtitleStylingDescription': + return 'Pas ondertitel uiterlijk aan'; + case 'settings.smallSkipDuration': + return 'Korte skip duur'; + case 'settings.largeSkipDuration': + return 'Lange skip duur'; + case 'settings.secondsUnit': + return ({required Object seconds}) => '${seconds} seconden'; + case 'settings.defaultSleepTimer': + return 'Standaard slaap timer'; + case 'settings.minutesUnit': + return ({required Object minutes}) => 'bij ${minutes} minuten'; + case 'settings.rememberTrackSelections': + return 'Onthoud track selecties per serie/film'; + case 'settings.rememberTrackSelectionsDescription': + return 'Bewaar automatisch audio- en ondertiteltaalvoorkeuren wanneer je tracks wijzigt tijdens afspelen'; + case 'settings.unwatchedOnly': + return 'Alleen ongekeken'; + case 'settings.unwatchedOnlyDescription': + return 'Alleen ongekeken afleveringen opnemen in willekeurige wachtrij'; + case 'settings.shuffleOrderNavigation': + return 'Willekeurige volgorde navigatie'; + case 'settings.shuffleOrderNavigationDescription': + return 'Volgende/vorige knoppen volgen willekeurige volgorde'; + case 'settings.loopShuffleQueue': + return 'Herhaal willekeurige wachtrij'; + case 'settings.loopShuffleQueueDescription': + return 'Start wachtrij opnieuw bij het einde'; + case 'settings.videoPlayerControls': + return 'Videospeler bediening'; + case 'settings.keyboardShortcuts': + return 'Toetsenbord sneltoetsen'; + case 'settings.keyboardShortcutsDescription': + return 'Pas toetsenbord sneltoetsen aan'; + case 'settings.debugLogging': + return 'Debug logging'; + case 'settings.debugLoggingDescription': + return 'Schakel gedetailleerde logging in voor probleemoplossing'; + case 'settings.viewLogs': + return 'Bekijk logs'; + case 'settings.viewLogsDescription': + return 'Bekijk applicatie logs'; + case 'settings.clearCache': + return 'Cache wissen'; + case 'settings.clearCacheDescription': + return 'Dit wist alle gecachte afbeeldingen en gegevens. De app kan langer duren om inhoud te laden na het wissen van de cache.'; + case 'settings.clearCacheSuccess': + return 'Cache succesvol gewist'; + case 'settings.resetSettings': + return 'Instellingen resetten'; + case 'settings.resetSettingsDescription': + return 'Dit reset alle instellingen naar hun standaard waarden. Deze actie kan niet ongedaan gemaakt worden.'; + case 'settings.resetSettingsSuccess': + return 'Instellingen succesvol gereset'; + case 'settings.shortcutsReset': + return 'Sneltoetsen gereset naar standaard'; + case 'settings.about': + return 'Over'; + case 'settings.aboutDescription': + return 'App informatie en licenties'; + case 'settings.updates': + return 'Updates'; + case 'settings.updateAvailable': + return 'Update beschikbaar'; + case 'settings.checkForUpdates': + return 'Controleer op updates'; + case 'settings.validationErrorEnterNumber': + return 'Voer een geldig nummer in'; + case 'settings.validationErrorDuration': + return ({ + required Object min, + required Object max, + required Object unit, + }) => 'Duur moet tussen ${min} en ${max} ${unit} zijn'; + case 'settings.shortcutAlreadyAssigned': + return ({required Object action}) => + 'Sneltoets al toegewezen aan ${action}'; + case 'settings.shortcutUpdated': + return ({required Object action}) => + 'Sneltoets bijgewerkt voor ${action}'; + case 'search.hint': + return 'Zoek films, series, muziek...'; + case 'search.tryDifferentTerm': + return 'Probeer een andere zoekterm'; + case 'search.searchYourMedia': + return 'Zoek in je media'; + case 'search.enterTitleActorOrKeyword': + return 'Voer een titel, acteur of trefwoord in'; + case 'hotkeys.setShortcutFor': + return ({required Object actionName}) => + 'Stel sneltoets in voor ${actionName}'; + case 'hotkeys.clearShortcut': + return 'Wis sneltoets'; + case 'pinEntry.enterPin': + return 'Voer PIN in'; + case 'pinEntry.showPin': + return 'Toon PIN'; + case 'pinEntry.hidePin': + return 'Verberg PIN'; + case 'fileInfo.title': + return 'Bestand info'; + case 'fileInfo.video': + return 'Video'; + case 'fileInfo.audio': + return 'Audio'; + case 'fileInfo.file': + return 'Bestand'; + case 'fileInfo.advanced': + return 'Geavanceerd'; + case 'fileInfo.codec': + return 'Codec'; + case 'fileInfo.resolution': + return 'Resolutie'; + case 'fileInfo.bitrate': + return 'Bitrate'; + case 'fileInfo.frameRate': + return 'Frame rate'; + case 'fileInfo.aspectRatio': + return 'Beeldverhouding'; + case 'fileInfo.profile': + return 'Profiel'; + case 'fileInfo.bitDepth': + return 'Bit diepte'; + case 'fileInfo.colorSpace': + return 'Kleurruimte'; + case 'fileInfo.colorRange': + return 'Kleurbereik'; + case 'fileInfo.colorPrimaries': + return 'Kleurprimaires'; + case 'fileInfo.chromaSubsampling': + return 'Chroma subsampling'; + case 'fileInfo.channels': + return 'Kanalen'; + case 'fileInfo.path': + return 'Pad'; + case 'fileInfo.size': + return 'Grootte'; + case 'fileInfo.container': + return 'Container'; + case 'fileInfo.duration': + return 'Duur'; + case 'fileInfo.optimizedForStreaming': + return 'Geoptimaliseerd voor streaming'; + case 'fileInfo.has64bitOffsets': + return '64-bit Offsets'; + case 'mediaMenu.markAsWatched': + return 'Markeer als gekeken'; + case 'mediaMenu.markAsUnwatched': + return 'Markeer als ongekeken'; + case 'mediaMenu.removeFromContinueWatching': + return 'Verwijder uit Doorgaan met kijken'; + case 'mediaMenu.goToSeries': + return 'Ga naar serie'; + case 'mediaMenu.goToSeason': + return 'Ga naar seizoen'; + case 'mediaMenu.shufflePlay': + return 'Willekeurig afspelen'; + case 'mediaMenu.fileInfo': + return 'Bestand info'; + case 'accessibility.mediaCardMovie': + return ({required Object title}) => '${title}, film'; + case 'accessibility.mediaCardShow': + return ({required Object title}) => '${title}, TV-serie'; + case 'accessibility.mediaCardEpisode': + return ({required Object title, required Object episodeInfo}) => + '${title}, ${episodeInfo}'; + case 'accessibility.mediaCardSeason': + return ({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + case 'accessibility.mediaCardWatched': + return 'bekeken'; + case 'accessibility.mediaCardPartiallyWatched': + return ({required Object percent}) => '${percent} procent bekeken'; + case 'accessibility.mediaCardUnwatched': + return 'niet bekeken'; + case 'accessibility.tapToPlay': + return 'Tik om af te spelen'; + case 'tooltips.shufflePlay': + return 'Willekeurig afspelen'; + case 'tooltips.markAsWatched': + return 'Markeer als gekeken'; + case 'tooltips.markAsUnwatched': + return 'Markeer als ongekeken'; + case 'videoControls.audioLabel': + return 'Audio'; + case 'videoControls.subtitlesLabel': + return 'Ondertitels'; + case 'videoControls.resetToZero': + return 'Reset naar 0ms'; + case 'videoControls.addTime': + return ({required Object amount, required Object unit}) => + '+${amount}${unit}'; + case 'videoControls.minusTime': + return ({required Object amount, required Object unit}) => + '-${amount}${unit}'; + case 'videoControls.playsLater': + return ({required Object label}) => '${label} speelt later af'; + case 'videoControls.playsEarlier': + return ({required Object label}) => '${label} speelt eerder af'; + case 'videoControls.noOffset': + return 'Geen offset'; + case 'videoControls.letterbox': + return 'Letterbox'; + case 'videoControls.fillScreen': + return 'Vul scherm'; + case 'videoControls.stretch': + return 'Uitrekken'; + case 'videoControls.lockRotation': + return 'Vergrendel rotatie'; + case 'videoControls.unlockRotation': + return 'Ontgrendel rotatie'; + case 'videoControls.sleepTimer': + return 'Slaaptimer'; + case 'videoControls.timerActive': + return 'Timer actief'; + case 'videoControls.playbackWillPauseIn': + return ({required Object duration}) => + 'Afspelen wordt gepauzeerd over ${duration}'; + case 'videoControls.sleepTimerCompleted': + return 'Slaaptimer voltooid - afspelen gepauzeerd'; + case 'videoControls.playButton': + return 'Afspelen'; + case 'videoControls.pauseButton': + return 'Pauzeren'; + case 'videoControls.seekBackwardButton': + return ({required Object seconds}) => + 'Terugspoelen ${seconds} seconden'; + case 'videoControls.seekForwardButton': + return ({required Object seconds}) => + 'Vooruitspoelen ${seconds} seconden'; + case 'videoControls.previousButton': + return 'Vorige aflevering'; + case 'videoControls.nextButton': + return 'Volgende aflevering'; + case 'videoControls.previousChapterButton': + return 'Vorig hoofdstuk'; + case 'videoControls.nextChapterButton': + return 'Volgend hoofdstuk'; + case 'videoControls.volumeButton': + return 'Volume'; + case 'videoControls.muteButton': + return 'Dempen'; + case 'videoControls.unmuteButton': + return 'Dempen opheffen'; + case 'videoControls.settingsButton': + return 'Video-instellingen'; + case 'videoControls.audioTrackButton': + return 'Audiosporen'; + case 'videoControls.subtitlesButton': + return 'Ondertitels'; + case 'videoControls.chaptersButton': + return 'Hoofdstukken'; + case 'videoControls.versionsButton': + return 'Videoversies'; + case 'videoControls.aspectRatioButton': + return 'Beeldverhouding'; + case 'videoControls.fullscreenButton': + return 'Volledig scherm activeren'; + case 'videoControls.exitFullscreenButton': + return 'Volledig scherm verlaten'; + case 'videoControls.rotationLockButton': + return 'Rotatievergrendeling'; + case 'videoControls.timelineSlider': + return 'Videotijdlijn'; + case 'videoControls.volumeSlider': + return 'Volumeniveau'; + case 'videoControls.backButton': + return 'Terug'; + case 'userStatus.admin': + return 'Beheerder'; + case 'userStatus.restricted': + return 'Beperkt'; + case 'userStatus.protected': + return 'Beschermd'; + case 'userStatus.current': + return 'HUIDIG'; + case 'messages.markedAsWatched': + return 'Gemarkeerd als gekeken'; + case 'messages.markedAsUnwatched': + return 'Gemarkeerd als ongekeken'; + case 'messages.removedFromContinueWatching': + return 'Verwijderd uit Doorgaan met kijken'; + case 'messages.errorLoading': + return ({required Object error}) => 'Fout: ${error}'; + case 'messages.fileInfoNotAvailable': + return 'Bestand informatie niet beschikbaar'; + case 'messages.errorLoadingFileInfo': + return ({required Object error}) => + 'Fout bij laden bestand info: ${error}'; + case 'messages.errorLoadingSeries': + return 'Fout bij laden serie'; + case 'messages.errorLoadingSeason': + return 'Fout bij laden seizoen'; + case 'messages.musicNotSupported': + return 'Muziek afspelen wordt nog niet ondersteund'; + case 'messages.logsCleared': + return 'Logs gewist'; + case 'messages.logsCopied': + return 'Logs gekopieerd naar klembord'; + case 'messages.noLogsAvailable': + return 'Geen logs beschikbaar'; + case 'messages.libraryScanning': + return ({required Object title}) => 'Scannen "${title}"...'; + case 'messages.libraryScanStarted': + return ({required Object title}) => + 'Bibliotheek scan gestart voor "${title}"'; + case 'messages.libraryScanFailed': + return ({required Object error}) => + 'Kon bibliotheek niet scannen: ${error}'; + case 'messages.metadataRefreshing': + return ({required Object title}) => + 'Metadata vernieuwen voor "${title}"...'; + case 'messages.metadataRefreshStarted': + return ({required Object title}) => + 'Metadata vernieuwen gestart voor "${title}"'; + case 'messages.metadataRefreshFailed': + return ({required Object error}) => + 'Kon metadata niet vernieuwen: ${error}'; + case 'messages.noPlexToken': + return 'Geen Plex token gevonden. Log opnieuw in.'; + case 'messages.logoutConfirm': + return 'Weet je zeker dat je wilt uitloggen?'; + case 'messages.noSeasonsFound': + return 'Geen seizoenen gevonden'; + case 'messages.noEpisodesFound': + return 'Geen afleveringen gevonden in eerste seizoen'; + case 'messages.noEpisodesFoundGeneral': + return 'Geen afleveringen gevonden'; + case 'messages.noResultsFound': + return 'Geen resultaten gevonden'; + case 'messages.sleepTimerSet': + return ({required Object label}) => + 'Slaap timer ingesteld voor ${label}'; + case 'messages.failedToSwitchProfile': + return ({required Object displayName}) => + 'Kon niet wisselen naar ${displayName}'; + case 'messages.noItemsAvailable': + return 'Geen items beschikbaar'; + case 'messages.failedToCreatePlayQueue': + return 'Kan afspeelwachtrij niet maken'; + case 'messages.failedToCreatePlayQueueNoItems': + return 'Kan afspeelwachtrij niet maken - geen items'; + case 'messages.failedPlayback': + return ({required Object action, required Object error}) => + 'Afspelen van ${action} mislukt: ${error}'; + case 'profile.noUsersAvailable': + return 'Geen gebruikers beschikbaar'; + case 'subtitlingStyling.stylingOptions': + return 'Opmaak opties'; + case 'subtitlingStyling.fontSize': + return 'Lettergrootte'; + case 'subtitlingStyling.textColor': + return 'Tekstkleur'; + case 'subtitlingStyling.borderSize': + return 'Rand grootte'; + case 'subtitlingStyling.borderColor': + return 'Randkleur'; + case 'subtitlingStyling.backgroundOpacity': + return 'Achtergrond transparantie'; + case 'subtitlingStyling.backgroundColor': + return 'Achtergrondkleur'; + case 'dialog.confirmAction': + return 'Bevestig actie'; + case 'dialog.areYouSure': + return 'Weet je zeker dat je deze actie wilt uitvoeren?'; + case 'dialog.cancel': + return 'Annuleren'; + case 'dialog.playNow': + return 'Nu afspelen'; + case 'discover.title': + return 'Ontdekken'; + case 'discover.switchProfile': + return 'Wissel van profiel'; + case 'discover.switchServer': + return 'Wissel van server'; + case 'discover.logout': + return 'Uitloggen'; + case 'discover.noContentAvailable': + return 'Geen inhoud beschikbaar'; + case 'discover.addMediaToLibraries': + return 'Voeg wat media toe aan je bibliotheken'; + case 'discover.continueWatching': + return 'Verder kijken'; + case 'discover.recentlyAdded': + return 'Recent toegevoegd'; + case 'discover.play': + return 'Afspelen'; + case 'discover.resume': + return 'Hervatten'; + case 'discover.playEpisode': + return ({required Object season, required Object episode}) => + 'Speel S${season}, E${episode}'; + case 'discover.resumeEpisode': + return ({required Object season, required Object episode}) => + 'Hervat S${season}, E${episode}'; + case 'discover.pause': + return 'Pauzeren'; + case 'discover.overview': + return 'Overzicht'; + case 'discover.cast': + return 'Acteurs'; + case 'discover.seasons': + return 'Seizoenen'; + case 'discover.studio': + return 'Studio'; + case 'discover.rating': + return 'Leeftijd'; + case 'discover.watched': + return 'Bekeken'; + case 'discover.episodeCount': + return ({required Object count}) => '${count} afleveringen'; + case 'discover.watchedProgress': + return ({required Object watched, required Object total}) => + '${watched}/${total} gekeken'; + case 'discover.movie': + return 'Film'; + case 'discover.tvShow': + return 'TV Serie'; + case 'discover.minutesLeft': + return ({required Object minutes}) => '${minutes} min over'; + case 'errors.searchFailed': + return ({required Object error}) => 'Zoeken mislukt: ${error}'; + case 'errors.connectionTimeout': + return ({required Object context}) => + 'Verbinding time-out tijdens laden ${context}'; + case 'errors.connectionFailed': + return 'Kan geen verbinding maken met Plex server'; + case 'errors.failedToLoad': + return ({required Object context, required Object error}) => + 'Kon ${context} niet laden: ${error}'; + case 'errors.noClientAvailable': + return 'Geen client beschikbaar'; + case 'errors.authenticationFailed': + return ({required Object error}) => 'Authenticatie mislukt: ${error}'; + case 'errors.couldNotLaunchUrl': + return 'Kon auth URL niet openen'; + case 'errors.pleaseEnterToken': + return 'Voer een token in'; + case 'errors.invalidToken': + return 'Ongeldig token'; + case 'errors.failedToVerifyToken': + return ({required Object error}) => + 'Kon token niet verifiëren: ${error}'; + case 'errors.failedToSwitchProfile': + return ({required Object displayName}) => + 'Kon niet wisselen naar ${displayName}'; + case 'errors.connectionFailedGeneric': + return 'Verbinding mislukt'; + case 'libraries.title': + return 'Bibliotheken'; + case 'libraries.scanLibraryFiles': + return 'Scan bibliotheek bestanden'; + case 'libraries.scanLibrary': + return 'Scan bibliotheek'; + case 'libraries.analyze': + return 'Analyseren'; + case 'libraries.analyzeLibrary': + return 'Analyseer bibliotheek'; + case 'libraries.refreshMetadata': + return 'Vernieuw metadata'; + case 'libraries.emptyTrash': + return 'Prullenbak legen'; + case 'libraries.emptyingTrash': + return ({required Object title}) => + 'Prullenbak legen voor "${title}"...'; + case 'libraries.trashEmptied': + return ({required Object title}) => + 'Prullenbak geleegd voor "${title}"'; + case 'libraries.failedToEmptyTrash': + return ({required Object error}) => + 'Kon prullenbak niet legen: ${error}'; + case 'libraries.analyzing': + return ({required Object title}) => 'Analyseren "${title}"...'; + case 'libraries.analysisStarted': + return ({required Object title}) => 'Analyse gestart voor "${title}"'; + case 'libraries.failedToAnalyze': + return ({required Object error}) => + 'Kon bibliotheek niet analyseren: ${error}'; + case 'libraries.noLibrariesFound': + return 'Geen bibliotheken gevonden'; + case 'libraries.thisLibraryIsEmpty': + return 'Deze bibliotheek is leeg'; + case 'libraries.all': + return 'Alles'; + case 'libraries.clearAll': + return 'Alles wissen'; + case 'libraries.scanLibraryConfirm': + return ({required Object title}) => + 'Weet je zeker dat je "${title}" wilt scannen?'; + case 'libraries.analyzeLibraryConfirm': + return ({required Object title}) => + 'Weet je zeker dat je "${title}" wilt analyseren?'; + case 'libraries.refreshMetadataConfirm': + return ({required Object title}) => + 'Weet je zeker dat je metadata wilt vernieuwen voor "${title}"?'; + case 'libraries.emptyTrashConfirm': + return ({required Object title}) => + 'Weet je zeker dat je de prullenbak wilt legen voor "${title}"?'; + case 'libraries.manageLibraries': + return 'Beheer bibliotheken'; + case 'libraries.sort': + return 'Sorteren'; + case 'libraries.sortBy': + return 'Sorteer op'; + case 'libraries.filters': + return 'Filters'; + case 'libraries.loadingLibraryWithCount': + return ({required Object count}) => + 'Bibliotheek laden... (${count} items geladen)'; + case 'libraries.confirmActionMessage': + return 'Weet je zeker dat je deze actie wilt uitvoeren?'; + case 'libraries.showLibrary': + return 'Toon bibliotheek'; + case 'libraries.hideLibrary': + return 'Verberg bibliotheek'; + case 'libraries.libraryOptions': + return 'Bibliotheek opties'; + case 'libraries.content': + return 'bibliotheekinhoud'; + case 'libraries.selectLibrary': + return 'Bibliotheek kiezen'; + case 'libraries.filtersWithCount': + return ({required Object count}) => 'Filters (${count})'; + case 'libraries.noRecommendations': + return 'Geen aanbevelingen beschikbaar'; + case 'libraries.noCollections': + return 'Geen collecties in deze bibliotheek'; + case 'libraries.noFoldersFound': + return 'Geen mappen gevonden'; + case 'libraries.folders': + return 'mappen'; + case 'libraries.tabs.recommended': + return 'Aanbevolen'; + case 'libraries.tabs.browse': + return 'Bladeren'; + case 'libraries.tabs.collections': + return 'Collecties'; + case 'libraries.tabs.playlists': + return 'Afspeellijsten'; + case 'libraries.groupings.all': + return 'Alles'; + case 'libraries.groupings.movies': + return 'Films'; + case 'libraries.groupings.shows': + return 'Series'; + case 'libraries.groupings.seasons': + return 'Seizoenen'; + case 'libraries.groupings.episodes': + return 'Afleveringen'; + case 'libraries.groupings.folders': + return 'Mappen'; + case 'about.title': + return 'Over'; + case 'about.openSourceLicenses': + return 'Open Source licenties'; + case 'about.versionLabel': + return ({required Object version}) => 'Versie ${version}'; + case 'about.appDescription': + return 'Een mooie Plex client voor Flutter'; + case 'about.viewLicensesDescription': + return 'Bekijk licenties van third-party bibliotheken'; + case 'serverSelection.connectingToServer': + return 'Verbinden met server...'; + case 'serverSelection.connectingToAllServers': + return 'Verbinden met alle servers...'; + case 'serverSelection.connectToAllServers': + return 'Verbind met alle servers'; + case 'serverSelection.allServerConnectionsFailed': + return 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.'; + case 'serverSelection.foundServers': + return ({required Object count}) => '${count} server(s) gevonden'; + case 'serverSelection.orSelectIndividually': + return 'Of selecteer een server individueel:'; + case 'serverSelection.serverDebugCopied': + return 'Server debug gegevens gekopieerd naar klembord'; + case 'serverSelection.copyDebugData': + return 'Kopieer debug gegevens'; + case 'serverSelection.noServersFound': + return 'Geen servers gevonden'; + case 'serverSelection.malformedServerData': + return ({required Object count}) => + '${count} server(s) gevonden met verkeerde data. Geen geldige servers beschikbaar.'; + case 'serverSelection.incompleteServerInfo': + return 'Sommige servers hebben incomplete informatie en zijn overgeslagen. Controleer je Plex.tv account.'; + case 'serverSelection.incompleteConnectionInfo': + return 'Server verbinding informatie is incompleet. Probeer opnieuw.'; + case 'serverSelection.malformedServerInfo': + return ({required Object message}) => + 'Server informatie is verkeerd geformatteerd: ${message}'; + case 'serverSelection.networkConnectionFailed': + return 'Netwerkverbinding mislukt. Controleer je internetverbinding en probeer opnieuw.'; + case 'serverSelection.authenticationFailed': + return 'Authenticatie mislukt. Log opnieuw in.'; + case 'serverSelection.plexServiceUnavailable': + return 'Plex service niet beschikbaar. Probeer later opnieuw.'; + case 'serverSelection.failedToLoadServers': + return ({required Object error}) => 'Kon servers niet laden: ${error}'; + case 'hubDetail.title': + return 'Titel'; + case 'hubDetail.releaseYear': + return 'Uitgavejaar'; + case 'hubDetail.dateAdded': + return 'Datum toegevoegd'; + case 'hubDetail.rating': + return 'Beoordeling'; + case 'hubDetail.noItemsFound': + return 'Geen items gevonden'; + case 'logs.title': + return 'Logbestanden'; + case 'logs.clearLogs': + return 'Wis logs'; + case 'logs.copyLogs': + return 'Kopieer logs'; + case 'logs.exportLogs': + return 'Exporteer logs'; + case 'logs.noLogsToShow': + return 'Geen logs om te tonen'; + case 'logs.error': + return 'Fout:'; + case 'logs.stackTrace': + return 'Stacktracering:'; + case 'licenses.relatedPackages': + return 'Gerelateerde pakketten'; + case 'licenses.license': + return 'Licentie'; + case 'licenses.licenseNumber': + return ({required Object number}) => 'Licentie ${number}'; + case 'licenses.licensesCount': + return ({required Object count}) => '${count} licenties'; + case 'navigation.home': + return 'Thuis'; + case 'navigation.search': + return 'Zoeken'; + case 'navigation.libraries': + return 'Bibliotheken'; + case 'navigation.settings': + return 'Instellingen'; + case 'playlists.title': + return 'Afspeellijsten'; + case 'playlists.noPlaylists': + return 'Geen afspeellijsten gevonden'; + case 'playlists.create': + return 'Afspeellijst maken'; + case 'playlists.newPlaylist': + return 'Nieuwe afspeellijst'; + case 'playlists.playlistName': + return 'Naam afspeellijst'; + case 'playlists.enterPlaylistName': + return 'Voer naam afspeellijst in'; + case 'playlists.edit': + return 'Afspeellijst bewerken'; + case 'playlists.delete': + return 'Afspeellijst verwijderen'; + case 'playlists.addTo': + return 'Toevoegen aan afspeellijst'; + case 'playlists.addItems': + return 'Items toevoegen'; + case 'playlists.removeItem': + return 'Verwijderen uit afspeellijst'; + case 'playlists.clearPlaylist': + return 'Afspeellijst wissen'; + case 'playlists.playAll': + return 'Alles afspelen'; + case 'playlists.shuffle': + return 'Willekeurig'; + case 'playlists.smartPlaylist': + return 'Slimme afspeellijst'; + case 'playlists.regularPlaylist': + return 'Normale afspeellijst'; + case 'playlists.itemCount': + return ({required Object count}) => '${count} items'; + case 'playlists.oneItem': + return '1 item'; + case 'playlists.emptyPlaylist': + return 'Deze afspeellijst is leeg'; + case 'playlists.deleteConfirm': + return 'Afspeellijst verwijderen?'; + case 'playlists.deleteMessage': + return ({required Object name}) => + 'Weet je zeker dat je "${name}" wilt verwijderen?'; + case 'playlists.created': + return 'Afspeellijst gemaakt'; + case 'playlists.updated': + return 'Afspeellijst bijgewerkt'; + case 'playlists.deleted': + return 'Afspeellijst verwijderd'; + case 'playlists.itemAdded': + return 'Toegevoegd aan afspeellijst'; + case 'playlists.itemRemoved': + return 'Verwijderd uit afspeellijst'; + case 'playlists.selectPlaylist': + return 'Selecteer afspeellijst'; + case 'playlists.createNewPlaylist': + return 'Nieuwe afspeellijst maken'; + case 'playlists.errorCreating': + return 'Fout bij maken afspeellijst'; + case 'playlists.errorDeleting': + return 'Fout bij verwijderen afspeellijst'; + case 'playlists.errorLoading': + return 'Fout bij laden afspeellijsten'; + case 'playlists.errorAdding': + return 'Fout bij toevoegen aan afspeellijst'; + case 'playlists.errorReordering': + return 'Fout bij herschikken van afspeellijstitem'; + case 'playlists.errorRemoving': + return 'Fout bij verwijderen uit afspeellijst'; + case 'playlists.playlist': + return 'Afspeellijst'; + case 'collections.title': + return 'Collecties'; + case 'collections.collection': + return 'Collectie'; + case 'collections.empty': + return 'Collectie is leeg'; + case 'collections.noItems': + return 'Geen items in deze collectie'; + case 'collections.unknownLibrarySection': + return 'Kan niet verwijderen: onbekende bibliotheeksectie'; + case 'collections.deleteCollection': + return 'Collectie verwijderen'; + case 'collections.deleteConfirm': + return ({required Object title}) => + 'Weet je zeker dat je "${title}" wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.'; + case 'collections.deleted': + return 'Collectie verwijderd'; + case 'collections.deleteFailed': + return 'Collectie verwijderen mislukt'; + case 'collections.deleteFailedWithError': + return ({required Object error}) => + 'Collectie verwijderen mislukt: ${error}'; + case 'collections.failedToLoadItems': + return ({required Object error}) => + 'Collectie-items laden mislukt: ${error}'; + case 'collections.addTo': + return 'Toevoegen aan collectie'; + case 'collections.selectCollection': + return 'Selecteer collectie'; + case 'collections.createNewCollection': + return 'Nieuwe collectie maken'; + case 'collections.collectionName': + return 'Collectienaam'; + case 'collections.enterCollectionName': + return 'Voer collectienaam in'; + case 'collections.addedToCollection': + return 'Toegevoegd aan collectie'; + case 'collections.errorAddingToCollection': + return 'Fout bij toevoegen aan collectie'; + case 'collections.created': + return 'Collectie gemaakt'; + case 'collections.removeFromCollection': + return 'Verwijderen uit collectie'; + case 'collections.removeFromCollectionConfirm': + return ({required Object title}) => + '"${title}" uit deze collectie verwijderen?'; + case 'collections.removedFromCollection': + return 'Uit collectie verwijderd'; + case 'collections.removeFromCollectionFailed': + return 'Verwijderen uit collectie mislukt'; + case 'collections.removeFromCollectionError': + return ({required Object error}) => + 'Fout bij verwijderen uit collectie: ${error}'; + default: + return null; + } + } } extension on _StringsSv { - dynamic _flatMapFunction(String path) { - switch (path) { - case 'app.title': return 'Plezy'; - case 'app.loading': return 'Laddar...'; - case 'auth.signInWithPlex': return 'Logga in med Plex'; - case 'auth.showQRCode': return 'Visa QR-kod'; - case 'auth.cancel': return 'Avbryt'; - case 'auth.authenticate': return 'Autentisera'; - case 'auth.retry': return 'Försök igen'; - case 'auth.debugEnterToken': return 'Debug: Ange Plex-token'; - case 'auth.plexTokenLabel': return 'Plex-autentiseringstoken'; - case 'auth.plexTokenHint': return 'Ange din Plex.tv-token'; - case 'auth.authenticationTimeout': return 'Autentisering tog för lång tid. Försök igen.'; - case 'auth.scanQRCodeInstruction': return 'Skanna denna QR-kod med en enhet inloggad på Plex för att autentisera.'; - case 'auth.waitingForAuth': return 'Väntar på autentisering...\nVänligen slutför inloggning i din webbläsare.'; - case 'common.cancel': return 'Avbryt'; - case 'common.save': return 'Spara'; - case 'common.close': return 'Stäng'; - case 'common.clear': return 'Rensa'; - case 'common.reset': return 'Återställ'; - case 'common.later': return 'Senare'; - case 'common.submit': return 'Skicka'; - case 'common.confirm': return 'Bekräfta'; - case 'common.retry': return 'Försök igen'; - case 'common.playNow': return 'Spela nu'; - case 'common.logout': return 'Logga ut'; - case 'common.online': return 'Online'; - case 'common.offline': return 'Offline'; - case 'common.owned': return 'Egen'; - case 'common.shared': return 'Delad'; - case 'common.current': return 'NUVARANDE'; - case 'common.unknown': return 'Okänd'; - case 'common.refresh': return 'Uppdatera'; - case 'common.yes': return 'Ja'; - case 'common.no': return 'Nej'; - case 'common.server': return 'Server'; - case 'common.delete': return 'Ta bort'; - case 'common.shuffle': return 'Blanda'; - case 'common.addTo': return 'Lägg till i...'; - case 'screens.licenses': return 'Licenser'; - case 'screens.selectServer': return 'Välj server'; - case 'screens.switchProfile': return 'Byt profil'; - case 'screens.subtitleStyling': return 'Undertext-styling'; - case 'screens.search': return 'Sök'; - case 'screens.logs': return 'Loggar'; - case 'update.available': return 'Uppdatering tillgänglig'; - case 'update.versionAvailable': return ({required Object version}) => 'Version ${version} är tillgänglig'; - case 'update.currentVersion': return ({required Object version}) => 'Nuvarande: ${version}'; - case 'update.skipVersion': return 'Hoppa över denna version'; - case 'update.viewRelease': return 'Visa release'; - case 'update.latestVersion': return 'Du har den senaste versionen'; - case 'update.checkFailed': return 'Misslyckades att kontrollera uppdateringar'; - case 'settings.title': return 'Inställningar'; - case 'settings.language': return 'Språk'; - case 'settings.theme': return 'Tema'; - case 'settings.appearance': return 'Utseende'; - case 'settings.videoPlayback': return 'Videouppspelning'; - case 'settings.shufflePlay': return 'Blanda uppspelning'; - case 'settings.advanced': return 'Avancerat'; - case 'settings.useSeasonPostersDescription': return 'Visa säsongsaffisch istället för serieaffisch för avsnitt'; - case 'settings.showHeroSectionDescription': return 'Visa utvalda innehållskarusell på startsidan'; - case 'settings.secondsLabel': return 'Sekunder'; - case 'settings.minutesLabel': return 'Minuter'; - case 'settings.secondsShort': return 's'; - case 'settings.minutesShort': return 'm'; - case 'settings.durationHint': return ({required Object min, required Object max}) => 'Ange tid (${min}-${max})'; - case 'settings.systemTheme': return 'System'; - case 'settings.systemThemeDescription': return 'Följ systeminställningar'; - case 'settings.lightTheme': return 'Ljust'; - case 'settings.darkTheme': return 'Mörkt'; - case 'settings.libraryDensity': return 'Biblioteksdensitet'; - case 'settings.compact': return 'Kompakt'; - case 'settings.compactDescription': return 'Mindre kort, fler objekt synliga'; - case 'settings.normal': return 'Normal'; - case 'settings.normalDescription': return 'Standardstorlek'; - case 'settings.comfortable': return 'Bekväm'; - case 'settings.comfortableDescription': return 'Större kort, färre objekt synliga'; - case 'settings.viewMode': return 'Visningsläge'; - case 'settings.gridView': return 'Rutnät'; - case 'settings.gridViewDescription': return 'Visa objekt i rutnätslayout'; - case 'settings.listView': return 'Lista'; - case 'settings.listViewDescription': return 'Visa objekt i listlayout'; - case 'settings.useSeasonPosters': return 'Använd säsongsaffischer'; - case 'settings.showHeroSection': return 'Visa hjältesektion'; - case 'settings.hardwareDecoding': return 'Hårdvaruavkodning'; - case 'settings.hardwareDecodingDescription': return 'Använd hårdvaruacceleration när tillgängligt'; - case 'settings.bufferSize': return 'Bufferstorlek'; - case 'settings.bufferSizeMB': return ({required Object size}) => '${size}MB'; - case 'settings.subtitleStyling': return 'Undertext-styling'; - case 'settings.subtitleStylingDescription': return 'Anpassa undertextutseende'; - case 'settings.smallSkipDuration': return 'Kort hoppvaraktighet'; - case 'settings.largeSkipDuration': return 'Lång hoppvaraktighet'; - case 'settings.secondsUnit': return ({required Object seconds}) => '${seconds} sekunder'; - case 'settings.defaultSleepTimer': return 'Standard sovtimer'; - case 'settings.minutesUnit': return ({required Object minutes}) => '${minutes} minuter'; - case 'settings.rememberTrackSelections': return 'Kom ihåg spårval per serie/film'; - case 'settings.rememberTrackSelectionsDescription': return 'Spara automatiskt ljud- och undertextspråkpreferenser när du ändrar spår under uppspelning'; - case 'settings.unwatchedOnly': return 'Endast osedda'; - case 'settings.unwatchedOnlyDescription': return 'Inkludera endast osedda avsnitt i blandningskön'; - case 'settings.shuffleOrderNavigation': return 'Blandningsordning-navigation'; - case 'settings.shuffleOrderNavigationDescription': return 'Nästa/föregående knappar följer blandad ordning'; - case 'settings.loopShuffleQueue': return 'Loopa blandningskö'; - case 'settings.loopShuffleQueueDescription': return 'Starta om kö när slutet nås'; - case 'settings.videoPlayerControls': return 'Videospelar-kontroller'; - case 'settings.keyboardShortcuts': return 'Tangentbordsgenvägar'; - case 'settings.keyboardShortcutsDescription': return 'Anpassa tangentbordsgenvägar'; - case 'settings.debugLogging': return 'Felsökningsloggning'; - case 'settings.debugLoggingDescription': return 'Aktivera detaljerad loggning för felsökning'; - case 'settings.viewLogs': return 'Visa loggar'; - case 'settings.viewLogsDescription': return 'Visa applikationsloggar'; - case 'settings.clearCache': return 'Rensa cache'; - case 'settings.clearCacheDescription': return 'Detta rensar alla cachade bilder och data. Appen kan ta längre tid att ladda innehåll efter cache-rensning.'; - case 'settings.clearCacheSuccess': return 'Cache rensad framgångsrikt'; - case 'settings.resetSettings': return 'Återställ inställningar'; - case 'settings.resetSettingsDescription': return 'Detta återställer alla inställningar till standardvärden. Denna åtgärd kan inte ångras.'; - case 'settings.resetSettingsSuccess': return 'Inställningar återställda framgångsrikt'; - case 'settings.shortcutsReset': return 'Genvägar återställda till standard'; - case 'settings.about': return 'Om'; - case 'settings.aboutDescription': return 'Appinformation och licenser'; - case 'settings.updates': return 'Uppdateringar'; - case 'settings.updateAvailable': return 'Uppdatering tillgänglig'; - case 'settings.checkForUpdates': return 'Kontrollera uppdateringar'; - case 'settings.validationErrorEnterNumber': return 'Vänligen ange ett giltigt nummer'; - case 'settings.validationErrorDuration': return ({required Object min, required Object max, required Object unit}) => 'Tiden måste vara mellan ${min} och ${max} ${unit}'; - case 'settings.shortcutAlreadyAssigned': return ({required Object action}) => 'Genväg redan tilldelad ${action}'; - case 'settings.shortcutUpdated': return ({required Object action}) => 'Genväg uppdaterad för ${action}'; - case 'search.hint': return 'Sök filmer, serier, musik...'; - case 'search.tryDifferentTerm': return 'Prova en annan sökterm'; - case 'search.searchYourMedia': return 'Sök i dina media'; - case 'search.enterTitleActorOrKeyword': return 'Ange en titel, skådespelare eller nyckelord'; - case 'hotkeys.setShortcutFor': return ({required Object actionName}) => 'Sätt genväg för ${actionName}'; - case 'hotkeys.clearShortcut': return 'Rensa genväg'; - case 'pinEntry.enterPin': return 'Ange PIN'; - case 'pinEntry.showPin': return 'Visa PIN'; - case 'pinEntry.hidePin': return 'Dölj PIN'; - case 'fileInfo.title': return 'Filinformation'; - case 'fileInfo.video': return 'Video'; - case 'fileInfo.audio': return 'Ljud'; - case 'fileInfo.file': return 'Fil'; - case 'fileInfo.advanced': return 'Avancerat'; - case 'fileInfo.codec': return 'Kodek'; - case 'fileInfo.resolution': return 'Upplösning'; - case 'fileInfo.bitrate': return 'Bithastighet'; - case 'fileInfo.frameRate': return 'Bildfrekvens'; - case 'fileInfo.aspectRatio': return 'Bildförhållande'; - case 'fileInfo.profile': return 'Profil'; - case 'fileInfo.bitDepth': return 'Bitdjup'; - case 'fileInfo.colorSpace': return 'Färgrymd'; - case 'fileInfo.colorRange': return 'Färgområde'; - case 'fileInfo.colorPrimaries': return 'Färggrunder'; - case 'fileInfo.chromaSubsampling': return 'Kroma-undersampling'; - case 'fileInfo.channels': return 'Kanaler'; - case 'fileInfo.path': return 'Sökväg'; - case 'fileInfo.size': return 'Storlek'; - case 'fileInfo.container': return 'Container'; - case 'fileInfo.duration': return 'Varaktighet'; - case 'fileInfo.optimizedForStreaming': return 'Optimerad för streaming'; - case 'fileInfo.has64bitOffsets': return '64-bit offset'; - case 'mediaMenu.markAsWatched': return 'Markera som sedd'; - case 'mediaMenu.markAsUnwatched': return 'Markera som osedd'; - case 'mediaMenu.removeFromContinueWatching': return 'Ta bort från Fortsätt titta'; - case 'mediaMenu.goToSeries': return 'Gå till serie'; - case 'mediaMenu.goToSeason': return 'Gå till säsong'; - case 'mediaMenu.shufflePlay': return 'Blanda uppspelning'; - case 'mediaMenu.fileInfo': return 'Filinformation'; - case 'accessibility.mediaCardMovie': return ({required Object title}) => '${title}, film'; - case 'accessibility.mediaCardShow': return ({required Object title}) => '${title}, TV-serie'; - case 'accessibility.mediaCardEpisode': return ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - case 'accessibility.mediaCardSeason': return ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - case 'accessibility.mediaCardWatched': return 'sedd'; - case 'accessibility.mediaCardPartiallyWatched': return ({required Object percent}) => '${percent} procent sedd'; - case 'accessibility.mediaCardUnwatched': return 'osedd'; - case 'accessibility.tapToPlay': return 'Tryck för att spela'; - case 'tooltips.shufflePlay': return 'Blanda uppspelning'; - case 'tooltips.markAsWatched': return 'Markera som sedd'; - case 'tooltips.markAsUnwatched': return 'Markera som osedd'; - case 'videoControls.audioLabel': return 'Ljud'; - case 'videoControls.subtitlesLabel': return 'Undertexter'; - case 'videoControls.resetToZero': return 'Återställ till 0ms'; - case 'videoControls.addTime': return ({required Object amount, required Object unit}) => '+${amount}${unit}'; - case 'videoControls.minusTime': return ({required Object amount, required Object unit}) => '-${amount}${unit}'; - case 'videoControls.playsLater': return ({required Object label}) => '${label} spelas senare'; - case 'videoControls.playsEarlier': return ({required Object label}) => '${label} spelas tidigare'; - case 'videoControls.noOffset': return 'Ingen offset'; - case 'videoControls.letterbox': return 'Letterbox'; - case 'videoControls.fillScreen': return 'Fyll skärm'; - case 'videoControls.stretch': return 'Sträck'; - case 'videoControls.lockRotation': return 'Lås rotation'; - case 'videoControls.unlockRotation': return 'Lås upp rotation'; - case 'videoControls.sleepTimer': return 'Sovtimer'; - case 'videoControls.timerActive': return 'Timer aktiv'; - case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => 'Uppspelningen pausas om ${duration}'; - case 'videoControls.sleepTimerCompleted': return 'Sovtimer slutförd - uppspelning pausad'; - case 'videoControls.playButton': return 'Spela'; - case 'videoControls.pauseButton': return 'Pausa'; - case 'videoControls.seekBackwardButton': return ({required Object seconds}) => 'Spola bakåt ${seconds} sekunder'; - case 'videoControls.seekForwardButton': return ({required Object seconds}) => 'Spola framåt ${seconds} sekunder'; - case 'videoControls.previousButton': return 'Föregående avsnitt'; - case 'videoControls.nextButton': return 'Nästa avsnitt'; - case 'videoControls.previousChapterButton': return 'Föregående kapitel'; - case 'videoControls.nextChapterButton': return 'Nästa kapitel'; - case 'videoControls.volumeButton': return 'Volym'; - case 'videoControls.muteButton': return 'Tysta'; - case 'videoControls.unmuteButton': return 'Slå på ljud'; - case 'videoControls.settingsButton': return 'Videoinställningar'; - case 'videoControls.audioTrackButton': return 'Ljudspår'; - case 'videoControls.subtitlesButton': return 'Undertexter'; - case 'videoControls.chaptersButton': return 'Kapitel'; - case 'videoControls.versionsButton': return 'Videoversioner'; - case 'videoControls.aspectRatioButton': return 'Bildförhållande'; - case 'videoControls.fullscreenButton': return 'Aktivera helskärm'; - case 'videoControls.exitFullscreenButton': return 'Avsluta helskärm'; - case 'videoControls.rotationLockButton': return 'Rotationslås'; - case 'videoControls.timelineSlider': return 'Videotidslinje'; - case 'videoControls.volumeSlider': return 'Volymnivå'; - case 'videoControls.backButton': return 'Tillbaka'; - case 'userStatus.admin': return 'Admin'; - case 'userStatus.restricted': return 'Begränsad'; - case 'userStatus.protected': return 'Skyddad'; - case 'userStatus.current': return 'NUVARANDE'; - case 'messages.markedAsWatched': return 'Markerad som sedd'; - case 'messages.markedAsUnwatched': return 'Markerad som osedd'; - case 'messages.removedFromContinueWatching': return 'Borttagen från Fortsätt titta'; - case 'messages.errorLoading': return ({required Object error}) => 'Fel: ${error}'; - case 'messages.fileInfoNotAvailable': return 'Filinformation inte tillgänglig'; - case 'messages.errorLoadingFileInfo': return ({required Object error}) => 'Fel vid laddning av filinformation: ${error}'; - case 'messages.errorLoadingSeries': return 'Fel vid laddning av serie'; - case 'messages.errorLoadingSeason': return 'Fel vid laddning av säsong'; - case 'messages.musicNotSupported': return 'Musikuppspelning stöds inte ännu'; - case 'messages.logsCleared': return 'Loggar rensade'; - case 'messages.logsCopied': return 'Loggar kopierade till urklipp'; - case 'messages.noLogsAvailable': return 'Inga loggar tillgängliga'; - case 'messages.libraryScanning': return ({required Object title}) => 'Skannar "${title}"...'; - case 'messages.libraryScanStarted': return ({required Object title}) => 'Biblioteksskanning startad för "${title}"'; - case 'messages.libraryScanFailed': return ({required Object error}) => 'Misslyckades att skanna bibliotek: ${error}'; - case 'messages.metadataRefreshing': return ({required Object title}) => 'Uppdaterar metadata för "${title}"...'; - case 'messages.metadataRefreshStarted': return ({required Object title}) => 'Metadata-uppdatering startad för "${title}"'; - case 'messages.metadataRefreshFailed': return ({required Object error}) => 'Misslyckades att uppdatera metadata: ${error}'; - case 'messages.noPlexToken': return 'Ingen Plex-token hittad. Vänligen logga in igen.'; - case 'messages.logoutConfirm': return 'Är du säker på att du vill logga ut?'; - case 'messages.noSeasonsFound': return 'Inga säsonger hittades'; - case 'messages.noEpisodesFound': return 'Inga avsnitt hittades i första säsongen'; - case 'messages.noEpisodesFoundGeneral': return 'Inga avsnitt hittades'; - case 'messages.noResultsFound': return 'Inga resultat hittades'; - case 'messages.sleepTimerSet': return ({required Object label}) => 'Sovtimer inställd för ${label}'; - case 'messages.failedToSwitchProfile': return ({required Object displayName}) => 'Misslyckades att byta till ${displayName}'; - case 'messages.noItemsAvailable': return 'Inga objekt tillgängliga'; - case 'messages.failedToCreatePlayQueue': return 'Det gick inte att skapa uppspelningskö'; - case 'messages.failedToCreatePlayQueueNoItems': return 'Det gick inte att skapa uppspelningskö – inga objekt'; - case 'messages.failedPlayback': return ({required Object action, required Object error}) => 'Kunde inte ${action}: ${error}'; - case 'profile.noUsersAvailable': return 'Inga användare tillgängliga'; - case 'subtitlingStyling.stylingOptions': return 'Stilalternativ'; - case 'subtitlingStyling.fontSize': return 'Teckenstorlek'; - case 'subtitlingStyling.textColor': return 'Textfärg'; - case 'subtitlingStyling.borderSize': return 'Kantstorlek'; - case 'subtitlingStyling.borderColor': return 'Kantfärg'; - case 'subtitlingStyling.backgroundOpacity': return 'Bakgrundsopacitet'; - case 'subtitlingStyling.backgroundColor': return 'Bakgrundsfärg'; - case 'dialog.confirmAction': return 'Bekräfta åtgärd'; - case 'dialog.areYouSure': return 'Är du säker på att du vill utföra denna åtgärd?'; - case 'dialog.cancel': return 'Avbryt'; - case 'dialog.playNow': return 'Spela nu'; - case 'discover.title': return 'Upptäck'; - case 'discover.switchProfile': return 'Byt profil'; - case 'discover.switchServer': return 'Byt server'; - case 'discover.logout': return 'Logga ut'; - case 'discover.noContentAvailable': return 'Inget innehåll tillgängligt'; - case 'discover.addMediaToLibraries': return 'Lägg till media till dina bibliotek'; - case 'discover.continueWatching': return 'Fortsätt titta'; - case 'discover.recentlyAdded': return 'Nyligen tillagda'; - case 'discover.play': return 'Spela'; - case 'discover.resume': return 'Återuppta'; - case 'discover.playEpisode': return ({required Object season, required Object episode}) => 'Spela S${season}, E${episode}'; - case 'discover.resumeEpisode': return ({required Object season, required Object episode}) => 'Återuppta S${season}, E${episode}'; - case 'discover.pause': return 'Pausa'; - case 'discover.overview': return 'Översikt'; - case 'discover.cast': return 'Rollbesättning'; - case 'discover.seasons': return 'Säsonger'; - case 'discover.studio': return 'Studio'; - case 'discover.rating': return 'Åldersgräns'; - case 'discover.watched': return 'Tittad'; - case 'discover.episodeCount': return ({required Object count}) => '${count} avsnitt'; - case 'discover.watchedProgress': return ({required Object watched, required Object total}) => '${watched}/${total} sedda'; - case 'discover.movie': return 'Film'; - case 'discover.tvShow': return 'TV-serie'; - case 'discover.minutesLeft': return ({required Object minutes}) => '${minutes} min kvar'; - case 'errors.searchFailed': return ({required Object error}) => 'Sökning misslyckades: ${error}'; - case 'errors.connectionTimeout': return ({required Object context}) => 'Anslutnings-timeout vid laddning ${context}'; - case 'errors.connectionFailed': return 'Kan inte ansluta till Plex-server'; - case 'errors.failedToLoad': return ({required Object context, required Object error}) => 'Misslyckades att ladda ${context}: ${error}'; - case 'errors.noClientAvailable': return 'Ingen klient tillgänglig'; - case 'errors.authenticationFailed': return ({required Object error}) => 'Autentisering misslyckades: ${error}'; - case 'errors.couldNotLaunchUrl': return 'Kunde inte öppna autentiserings-URL'; - case 'errors.pleaseEnterToken': return 'Vänligen ange en token'; - case 'errors.invalidToken': return 'Ogiltig token'; - case 'errors.failedToVerifyToken': return ({required Object error}) => 'Misslyckades att verifiera token: ${error}'; - case 'errors.failedToSwitchProfile': return ({required Object displayName}) => 'Misslyckades att byta till ${displayName}'; - case 'errors.connectionFailedGeneric': return 'Anslutning misslyckades'; - case 'libraries.title': return 'Bibliotek'; - case 'libraries.scanLibraryFiles': return 'Skanna biblioteksfiler'; - case 'libraries.scanLibrary': return 'Skanna bibliotek'; - case 'libraries.analyze': return 'Analysera'; - case 'libraries.analyzeLibrary': return 'Analysera bibliotek'; - case 'libraries.refreshMetadata': return 'Uppdatera metadata'; - case 'libraries.emptyTrash': return 'Töm papperskorg'; - case 'libraries.emptyingTrash': return ({required Object title}) => 'Tömmer papperskorg för "${title}"...'; - case 'libraries.trashEmptied': return ({required Object title}) => 'Papperskorg tömd för "${title}"'; - case 'libraries.failedToEmptyTrash': return ({required Object error}) => 'Misslyckades att tömma papperskorg: ${error}'; - case 'libraries.analyzing': return ({required Object title}) => 'Analyserar "${title}"...'; - case 'libraries.analysisStarted': return ({required Object title}) => 'Analys startad för "${title}"'; - case 'libraries.failedToAnalyze': return ({required Object error}) => 'Misslyckades att analysera bibliotek: ${error}'; - case 'libraries.noLibrariesFound': return 'Inga bibliotek hittades'; - case 'libraries.thisLibraryIsEmpty': return 'Detta bibliotek är tomt'; - case 'libraries.all': return 'Alla'; - case 'libraries.clearAll': return 'Rensa alla'; - case 'libraries.scanLibraryConfirm': return ({required Object title}) => 'Är du säker på att du vill skanna "${title}"?'; - case 'libraries.analyzeLibraryConfirm': return ({required Object title}) => 'Är du säker på att du vill analysera "${title}"?'; - case 'libraries.refreshMetadataConfirm': return ({required Object title}) => 'Är du säker på att du vill uppdatera metadata för "${title}"?'; - case 'libraries.emptyTrashConfirm': return ({required Object title}) => 'Är du säker på att du vill tömma papperskorgen för "${title}"?'; - case 'libraries.manageLibraries': return 'Hantera bibliotek'; - case 'libraries.sort': return 'Sortera'; - case 'libraries.sortBy': return 'Sortera efter'; - case 'libraries.filters': return 'Filter'; - case 'libraries.loadingLibraryWithCount': return ({required Object count}) => 'Laddar bibliotek... (${count} objekt laddade)'; - case 'libraries.confirmActionMessage': return 'Är du säker på att du vill utföra denna åtgärd?'; - case 'libraries.showLibrary': return 'Visa bibliotek'; - case 'libraries.hideLibrary': return 'Dölj bibliotek'; - case 'libraries.libraryOptions': return 'Biblioteksalternativ'; - case 'libraries.content': return 'bibliotekets innehåll'; - case 'libraries.selectLibrary': return 'Välj bibliotek'; - case 'libraries.filtersWithCount': return ({required Object count}) => 'Filter (${count})'; - case 'libraries.noRecommendations': return 'Inga rekommendationer tillgängliga'; - case 'libraries.noCollections': return 'Inga samlingar i det här biblioteket'; - case 'libraries.noFoldersFound': return 'Inga mappar hittades'; - case 'libraries.folders': return 'mappar'; - case 'libraries.tabs.recommended': return 'Rekommenderat'; - case 'libraries.tabs.browse': return 'Bläddra'; - case 'libraries.tabs.collections': return 'Samlingar'; - case 'libraries.tabs.playlists': return 'Spellistor'; - case 'libraries.groupings.all': return 'Alla'; - case 'libraries.groupings.movies': return 'Filmer'; - case 'libraries.groupings.shows': return 'Serier'; - case 'libraries.groupings.seasons': return 'Säsonger'; - case 'libraries.groupings.episodes': return 'Avsnitt'; - case 'libraries.groupings.folders': return 'Mappar'; - case 'about.title': return 'Om'; - case 'about.openSourceLicenses': return 'Öppen källkod-licenser'; - case 'about.versionLabel': return ({required Object version}) => 'Version ${version}'; - case 'about.appDescription': return 'En vacker Plex-klient för Flutter'; - case 'about.viewLicensesDescription': return 'Visa licenser för tredjepartsbibliotek'; - case 'serverSelection.connectingToServer': return 'Ansluter till server...'; - case 'serverSelection.connectingToAllServers': return 'Ansluter till alla servrar...'; - case 'serverSelection.connectToAllServers': return 'Anslut till alla servrar'; - case 'serverSelection.allServerConnectionsFailed': return 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.'; - case 'serverSelection.foundServers': return ({required Object count}) => 'Hittade ${count} server(ar)'; - case 'serverSelection.orSelectIndividually': return 'Eller välj en server individuellt:'; - case 'serverSelection.serverDebugCopied': return 'Server-felsökningsdata kopierad till urklipp'; - case 'serverSelection.copyDebugData': return 'Kopiera felsökningsdata'; - case 'serverSelection.noServersFound': return 'Inga servrar hittades'; - case 'serverSelection.malformedServerData': return ({required Object count}) => 'Hittade ${count} server(ar) med felformaterad data. Inga giltiga servrar tillgängliga.'; - case 'serverSelection.incompleteServerInfo': return 'Vissa servrar har ofullständig information och hoppades över. Vänligen kontrollera ditt Plex.tv-konto.'; - case 'serverSelection.incompleteConnectionInfo': return 'Server-anslutningsinformation är ofullständig. Försök igen.'; - case 'serverSelection.malformedServerInfo': return ({required Object message}) => 'Serverinformation är felformaterad: ${message}'; - case 'serverSelection.networkConnectionFailed': return 'Nätverksanslutning misslyckades. Kontrollera din internetanslutning och försök igen.'; - case 'serverSelection.authenticationFailed': return 'Autentisering misslyckades. Logga in igen.'; - case 'serverSelection.plexServiceUnavailable': return 'Plex-tjänst otillgänglig. Försök igen senare.'; - case 'serverSelection.failedToLoadServers': return ({required Object error}) => 'Misslyckades att ladda servrar: ${error}'; - case 'hubDetail.title': return 'Titel'; - case 'hubDetail.releaseYear': return 'Utgivningsår'; - case 'hubDetail.dateAdded': return 'Datum tillagd'; - case 'hubDetail.rating': return 'Betyg'; - case 'hubDetail.noItemsFound': return 'Inga objekt hittades'; - case 'logs.title': return 'Loggar'; - case 'logs.clearLogs': return 'Rensa loggar'; - case 'logs.copyLogs': return 'Kopiera loggar'; - case 'logs.exportLogs': return 'Exportera loggar'; - case 'logs.noLogsToShow': return 'Inga loggar att visa'; - case 'logs.error': return 'Fel:'; - case 'logs.stackTrace': return 'Stack trace:'; - case 'licenses.relatedPackages': return 'Relaterade paket'; - case 'licenses.license': return 'Licens'; - case 'licenses.licenseNumber': return ({required Object number}) => 'Licens ${number}'; - case 'licenses.licensesCount': return ({required Object count}) => '${count} licenser'; - case 'navigation.home': return 'Hem'; - case 'navigation.search': return 'Sök'; - case 'navigation.libraries': return 'Bibliotek'; - case 'navigation.settings': return 'Inställningar'; - case 'playlists.title': return 'Spellistor'; - case 'playlists.noPlaylists': return 'Inga spellistor hittades'; - case 'playlists.create': return 'Skapa spellista'; - case 'playlists.newPlaylist': return 'Ny spellista'; - case 'playlists.playlistName': return 'Spellistans namn'; - case 'playlists.enterPlaylistName': return 'Ange spellistans namn'; - case 'playlists.edit': return 'Redigera spellista'; - case 'playlists.delete': return 'Ta bort spellista'; - case 'playlists.addTo': return 'Lägg till i spellista'; - case 'playlists.addItems': return 'Lägg till objekt'; - case 'playlists.removeItem': return 'Ta bort från spellista'; - case 'playlists.clearPlaylist': return 'Rensa spellista'; - case 'playlists.playAll': return 'Spela alla'; - case 'playlists.shuffle': return 'Blanda'; - case 'playlists.smartPlaylist': return 'Smart spellista'; - case 'playlists.regularPlaylist': return 'Vanlig spellista'; - case 'playlists.itemCount': return ({required Object count}) => '${count} objekt'; - case 'playlists.oneItem': return '1 objekt'; - case 'playlists.emptyPlaylist': return 'Denna spellista är tom'; - case 'playlists.deleteConfirm': return 'Ta bort spellista?'; - case 'playlists.deleteMessage': return ({required Object name}) => 'Är du säker på att du vill ta bort "${name}"?'; - case 'playlists.created': return 'Spellista skapad'; - case 'playlists.updated': return 'Spellista uppdaterad'; - case 'playlists.deleted': return 'Spellista borttagen'; - case 'playlists.itemAdded': return 'Tillagd i spellista'; - case 'playlists.itemRemoved': return 'Borttagen från spellista'; - case 'playlists.selectPlaylist': return 'Välj spellista'; - case 'playlists.createNewPlaylist': return 'Skapa ny spellista'; - case 'playlists.errorCreating': return 'Det gick inte att skapa spellista'; - case 'playlists.errorDeleting': return 'Det gick inte att ta bort spellista'; - case 'playlists.errorLoading': return 'Det gick inte att ladda spellistor'; - case 'playlists.errorAdding': return 'Det gick inte att lägga till i spellista'; - case 'playlists.errorReordering': return 'Det gick inte att omordna spellisteobjekt'; - case 'playlists.errorRemoving': return 'Det gick inte att ta bort från spellista'; - case 'playlists.playlist': return 'Spellista'; - case 'collections.title': return 'Samlingar'; - case 'collections.collection': return 'Samling'; - case 'collections.empty': return 'Samlingen är tom'; - case 'collections.noItems': return 'Inga objekt i den här samlingen'; - case 'collections.unknownLibrarySection': return 'Kan inte ta bort: okänd bibliotekssektion'; - case 'collections.deleteCollection': return 'Ta bort samling'; - case 'collections.deleteConfirm': return ({required Object title}) => 'Är du säker på att du vill ta bort "${title}"? Detta går inte att ångra.'; - case 'collections.deleted': return 'Samling borttagen'; - case 'collections.deleteFailed': return 'Det gick inte att ta bort samlingen'; - case 'collections.deleteFailedWithError': return ({required Object error}) => 'Det gick inte att ta bort samlingen: ${error}'; - case 'collections.failedToLoadItems': return ({required Object error}) => 'Det gick inte att läsa in samlingsobjekt: ${error}'; - case 'collections.addTo': return 'Lägg till i samling'; - case 'collections.selectCollection': return 'Välj samling'; - case 'collections.createNewCollection': return 'Skapa ny samling'; - case 'collections.collectionName': return 'Samlingsnamn'; - case 'collections.enterCollectionName': return 'Ange samlingsnamn'; - case 'collections.addedToCollection': return 'Tillagd i samling'; - case 'collections.errorAddingToCollection': return 'Fel vid tillägg i samling'; - case 'collections.created': return 'Samling skapad'; - case 'collections.removeFromCollection': return 'Ta bort från samling'; - case 'collections.removeFromCollectionConfirm': return ({required Object title}) => 'Ta bort "${title}" från denna samling?'; - case 'collections.removedFromCollection': return 'Borttagen från samling'; - case 'collections.removeFromCollectionFailed': return 'Misslyckades med att ta bort från samling'; - case 'collections.removeFromCollectionError': return ({required Object error}) => 'Fel vid borttagning från samling: ${error}'; - default: return null; - } - } + dynamic _flatMapFunction(String path) { + switch (path) { + case 'app.title': + return 'Plezy'; + case 'app.loading': + return 'Laddar...'; + case 'auth.signInWithPlex': + return 'Logga in med Plex'; + case 'auth.showQRCode': + return 'Visa QR-kod'; + case 'auth.cancel': + return 'Avbryt'; + case 'auth.authenticate': + return 'Autentisera'; + case 'auth.retry': + return 'Försök igen'; + case 'auth.debugEnterToken': + return 'Debug: Ange Plex-token'; + case 'auth.plexTokenLabel': + return 'Plex-autentiseringstoken'; + case 'auth.plexTokenHint': + return 'Ange din Plex.tv-token'; + case 'auth.authenticationTimeout': + return 'Autentisering tog för lång tid. Försök igen.'; + case 'auth.scanQRCodeInstruction': + return 'Skanna denna QR-kod med en enhet inloggad på Plex för att autentisera.'; + case 'auth.waitingForAuth': + return 'Väntar på autentisering...\nVänligen slutför inloggning i din webbläsare.'; + case 'common.cancel': + return 'Avbryt'; + case 'common.save': + return 'Spara'; + case 'common.close': + return 'Stäng'; + case 'common.clear': + return 'Rensa'; + case 'common.reset': + return 'Återställ'; + case 'common.later': + return 'Senare'; + case 'common.submit': + return 'Skicka'; + case 'common.confirm': + return 'Bekräfta'; + case 'common.retry': + return 'Försök igen'; + case 'common.playNow': + return 'Spela nu'; + case 'common.logout': + return 'Logga ut'; + case 'common.online': + return 'Online'; + case 'common.offline': + return 'Offline'; + case 'common.owned': + return 'Egen'; + case 'common.shared': + return 'Delad'; + case 'common.current': + return 'NUVARANDE'; + case 'common.unknown': + return 'Okänd'; + case 'common.refresh': + return 'Uppdatera'; + case 'common.yes': + return 'Ja'; + case 'common.no': + return 'Nej'; + case 'common.server': + return 'Server'; + case 'common.delete': + return 'Ta bort'; + case 'common.shuffle': + return 'Blanda'; + case 'common.addTo': + return 'Lägg till i...'; + case 'screens.licenses': + return 'Licenser'; + case 'screens.selectServer': + return 'Välj server'; + case 'screens.switchProfile': + return 'Byt profil'; + case 'screens.subtitleStyling': + return 'Undertext-styling'; + case 'screens.search': + return 'Sök'; + case 'screens.logs': + return 'Loggar'; + case 'update.available': + return 'Uppdatering tillgänglig'; + case 'update.versionAvailable': + return ({required Object version}) => + 'Version ${version} är tillgänglig'; + case 'update.currentVersion': + return ({required Object version}) => 'Nuvarande: ${version}'; + case 'update.skipVersion': + return 'Hoppa över denna version'; + case 'update.viewRelease': + return 'Visa release'; + case 'update.latestVersion': + return 'Du har den senaste versionen'; + case 'update.checkFailed': + return 'Misslyckades att kontrollera uppdateringar'; + case 'settings.title': + return 'Inställningar'; + case 'settings.language': + return 'Språk'; + case 'settings.theme': + return 'Tema'; + case 'settings.appearance': + return 'Utseende'; + case 'settings.videoPlayback': + return 'Videouppspelning'; + case 'settings.shufflePlay': + return 'Blanda uppspelning'; + case 'settings.advanced': + return 'Avancerat'; + case 'settings.useSeasonPostersDescription': + return 'Visa säsongsaffisch istället för serieaffisch för avsnitt'; + case 'settings.showHeroSectionDescription': + return 'Visa utvalda innehållskarusell på startsidan'; + case 'settings.secondsLabel': + return 'Sekunder'; + case 'settings.minutesLabel': + return 'Minuter'; + case 'settings.secondsShort': + return 's'; + case 'settings.minutesShort': + return 'm'; + case 'settings.durationHint': + return ({required Object min, required Object max}) => + 'Ange tid (${min}-${max})'; + case 'settings.systemTheme': + return 'System'; + case 'settings.systemThemeDescription': + return 'Följ systeminställningar'; + case 'settings.lightTheme': + return 'Ljust'; + case 'settings.darkTheme': + return 'Mörkt'; + case 'settings.libraryDensity': + return 'Biblioteksdensitet'; + case 'settings.compact': + return 'Kompakt'; + case 'settings.compactDescription': + return 'Mindre kort, fler objekt synliga'; + case 'settings.normal': + return 'Normal'; + case 'settings.normalDescription': + return 'Standardstorlek'; + case 'settings.comfortable': + return 'Bekväm'; + case 'settings.comfortableDescription': + return 'Större kort, färre objekt synliga'; + case 'settings.viewMode': + return 'Visningsläge'; + case 'settings.gridView': + return 'Rutnät'; + case 'settings.gridViewDescription': + return 'Visa objekt i rutnätslayout'; + case 'settings.listView': + return 'Lista'; + case 'settings.listViewDescription': + return 'Visa objekt i listlayout'; + case 'settings.useSeasonPosters': + return 'Använd säsongsaffischer'; + case 'settings.showHeroSection': + return 'Visa hjältesektion'; + case 'settings.hardwareDecoding': + return 'Hårdvaruavkodning'; + case 'settings.hardwareDecodingDescription': + return 'Använd hårdvaruacceleration när tillgängligt'; + case 'settings.bufferSize': + return 'Bufferstorlek'; + case 'settings.bufferSizeMB': + return ({required Object size}) => '${size}MB'; + case 'settings.subtitleStyling': + return 'Undertext-styling'; + case 'settings.subtitleStylingDescription': + return 'Anpassa undertextutseende'; + case 'settings.smallSkipDuration': + return 'Kort hoppvaraktighet'; + case 'settings.largeSkipDuration': + return 'Lång hoppvaraktighet'; + case 'settings.secondsUnit': + return ({required Object seconds}) => '${seconds} sekunder'; + case 'settings.defaultSleepTimer': + return 'Standard sovtimer'; + case 'settings.minutesUnit': + return ({required Object minutes}) => '${minutes} minuter'; + case 'settings.rememberTrackSelections': + return 'Kom ihåg spårval per serie/film'; + case 'settings.rememberTrackSelectionsDescription': + return 'Spara automatiskt ljud- och undertextspråkpreferenser när du ändrar spår under uppspelning'; + case 'settings.unwatchedOnly': + return 'Endast osedda'; + case 'settings.unwatchedOnlyDescription': + return 'Inkludera endast osedda avsnitt i blandningskön'; + case 'settings.shuffleOrderNavigation': + return 'Blandningsordning-navigation'; + case 'settings.shuffleOrderNavigationDescription': + return 'Nästa/föregående knappar följer blandad ordning'; + case 'settings.loopShuffleQueue': + return 'Loopa blandningskö'; + case 'settings.loopShuffleQueueDescription': + return 'Starta om kö när slutet nås'; + case 'settings.videoPlayerControls': + return 'Videospelar-kontroller'; + case 'settings.keyboardShortcuts': + return 'Tangentbordsgenvägar'; + case 'settings.keyboardShortcutsDescription': + return 'Anpassa tangentbordsgenvägar'; + case 'settings.debugLogging': + return 'Felsökningsloggning'; + case 'settings.debugLoggingDescription': + return 'Aktivera detaljerad loggning för felsökning'; + case 'settings.viewLogs': + return 'Visa loggar'; + case 'settings.viewLogsDescription': + return 'Visa applikationsloggar'; + case 'settings.clearCache': + return 'Rensa cache'; + case 'settings.clearCacheDescription': + return 'Detta rensar alla cachade bilder och data. Appen kan ta längre tid att ladda innehåll efter cache-rensning.'; + case 'settings.clearCacheSuccess': + return 'Cache rensad framgångsrikt'; + case 'settings.resetSettings': + return 'Återställ inställningar'; + case 'settings.resetSettingsDescription': + return 'Detta återställer alla inställningar till standardvärden. Denna åtgärd kan inte ångras.'; + case 'settings.resetSettingsSuccess': + return 'Inställningar återställda framgångsrikt'; + case 'settings.shortcutsReset': + return 'Genvägar återställda till standard'; + case 'settings.about': + return 'Om'; + case 'settings.aboutDescription': + return 'Appinformation och licenser'; + case 'settings.updates': + return 'Uppdateringar'; + case 'settings.updateAvailable': + return 'Uppdatering tillgänglig'; + case 'settings.checkForUpdates': + return 'Kontrollera uppdateringar'; + case 'settings.validationErrorEnterNumber': + return 'Vänligen ange ett giltigt nummer'; + case 'settings.validationErrorDuration': + return ({ + required Object min, + required Object max, + required Object unit, + }) => 'Tiden måste vara mellan ${min} och ${max} ${unit}'; + case 'settings.shortcutAlreadyAssigned': + return ({required Object action}) => 'Genväg redan tilldelad ${action}'; + case 'settings.shortcutUpdated': + return ({required Object action}) => 'Genväg uppdaterad för ${action}'; + case 'search.hint': + return 'Sök filmer, serier, musik...'; + case 'search.tryDifferentTerm': + return 'Prova en annan sökterm'; + case 'search.searchYourMedia': + return 'Sök i dina media'; + case 'search.enterTitleActorOrKeyword': + return 'Ange en titel, skådespelare eller nyckelord'; + case 'hotkeys.setShortcutFor': + return ({required Object actionName}) => + 'Sätt genväg för ${actionName}'; + case 'hotkeys.clearShortcut': + return 'Rensa genväg'; + case 'pinEntry.enterPin': + return 'Ange PIN'; + case 'pinEntry.showPin': + return 'Visa PIN'; + case 'pinEntry.hidePin': + return 'Dölj PIN'; + case 'fileInfo.title': + return 'Filinformation'; + case 'fileInfo.video': + return 'Video'; + case 'fileInfo.audio': + return 'Ljud'; + case 'fileInfo.file': + return 'Fil'; + case 'fileInfo.advanced': + return 'Avancerat'; + case 'fileInfo.codec': + return 'Kodek'; + case 'fileInfo.resolution': + return 'Upplösning'; + case 'fileInfo.bitrate': + return 'Bithastighet'; + case 'fileInfo.frameRate': + return 'Bildfrekvens'; + case 'fileInfo.aspectRatio': + return 'Bildförhållande'; + case 'fileInfo.profile': + return 'Profil'; + case 'fileInfo.bitDepth': + return 'Bitdjup'; + case 'fileInfo.colorSpace': + return 'Färgrymd'; + case 'fileInfo.colorRange': + return 'Färgområde'; + case 'fileInfo.colorPrimaries': + return 'Färggrunder'; + case 'fileInfo.chromaSubsampling': + return 'Kroma-undersampling'; + case 'fileInfo.channels': + return 'Kanaler'; + case 'fileInfo.path': + return 'Sökväg'; + case 'fileInfo.size': + return 'Storlek'; + case 'fileInfo.container': + return 'Container'; + case 'fileInfo.duration': + return 'Varaktighet'; + case 'fileInfo.optimizedForStreaming': + return 'Optimerad för streaming'; + case 'fileInfo.has64bitOffsets': + return '64-bit offset'; + case 'mediaMenu.markAsWatched': + return 'Markera som sedd'; + case 'mediaMenu.markAsUnwatched': + return 'Markera som osedd'; + case 'mediaMenu.removeFromContinueWatching': + return 'Ta bort från Fortsätt titta'; + case 'mediaMenu.goToSeries': + return 'Gå till serie'; + case 'mediaMenu.goToSeason': + return 'Gå till säsong'; + case 'mediaMenu.shufflePlay': + return 'Blanda uppspelning'; + case 'mediaMenu.fileInfo': + return 'Filinformation'; + case 'accessibility.mediaCardMovie': + return ({required Object title}) => '${title}, film'; + case 'accessibility.mediaCardShow': + return ({required Object title}) => '${title}, TV-serie'; + case 'accessibility.mediaCardEpisode': + return ({required Object title, required Object episodeInfo}) => + '${title}, ${episodeInfo}'; + case 'accessibility.mediaCardSeason': + return ({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + case 'accessibility.mediaCardWatched': + return 'sedd'; + case 'accessibility.mediaCardPartiallyWatched': + return ({required Object percent}) => '${percent} procent sedd'; + case 'accessibility.mediaCardUnwatched': + return 'osedd'; + case 'accessibility.tapToPlay': + return 'Tryck för att spela'; + case 'tooltips.shufflePlay': + return 'Blanda uppspelning'; + case 'tooltips.markAsWatched': + return 'Markera som sedd'; + case 'tooltips.markAsUnwatched': + return 'Markera som osedd'; + case 'videoControls.audioLabel': + return 'Ljud'; + case 'videoControls.subtitlesLabel': + return 'Undertexter'; + case 'videoControls.resetToZero': + return 'Återställ till 0ms'; + case 'videoControls.addTime': + return ({required Object amount, required Object unit}) => + '+${amount}${unit}'; + case 'videoControls.minusTime': + return ({required Object amount, required Object unit}) => + '-${amount}${unit}'; + case 'videoControls.playsLater': + return ({required Object label}) => '${label} spelas senare'; + case 'videoControls.playsEarlier': + return ({required Object label}) => '${label} spelas tidigare'; + case 'videoControls.noOffset': + return 'Ingen offset'; + case 'videoControls.letterbox': + return 'Letterbox'; + case 'videoControls.fillScreen': + return 'Fyll skärm'; + case 'videoControls.stretch': + return 'Sträck'; + case 'videoControls.lockRotation': + return 'Lås rotation'; + case 'videoControls.unlockRotation': + return 'Lås upp rotation'; + case 'videoControls.sleepTimer': + return 'Sovtimer'; + case 'videoControls.timerActive': + return 'Timer aktiv'; + case 'videoControls.playbackWillPauseIn': + return ({required Object duration}) => + 'Uppspelningen pausas om ${duration}'; + case 'videoControls.sleepTimerCompleted': + return 'Sovtimer slutförd - uppspelning pausad'; + case 'videoControls.playButton': + return 'Spela'; + case 'videoControls.pauseButton': + return 'Pausa'; + case 'videoControls.seekBackwardButton': + return ({required Object seconds}) => 'Spola bakåt ${seconds} sekunder'; + case 'videoControls.seekForwardButton': + return ({required Object seconds}) => + 'Spola framåt ${seconds} sekunder'; + case 'videoControls.previousButton': + return 'Föregående avsnitt'; + case 'videoControls.nextButton': + return 'Nästa avsnitt'; + case 'videoControls.previousChapterButton': + return 'Föregående kapitel'; + case 'videoControls.nextChapterButton': + return 'Nästa kapitel'; + case 'videoControls.volumeButton': + return 'Volym'; + case 'videoControls.muteButton': + return 'Tysta'; + case 'videoControls.unmuteButton': + return 'Slå på ljud'; + case 'videoControls.settingsButton': + return 'Videoinställningar'; + case 'videoControls.audioTrackButton': + return 'Ljudspår'; + case 'videoControls.subtitlesButton': + return 'Undertexter'; + case 'videoControls.chaptersButton': + return 'Kapitel'; + case 'videoControls.versionsButton': + return 'Videoversioner'; + case 'videoControls.aspectRatioButton': + return 'Bildförhållande'; + case 'videoControls.fullscreenButton': + return 'Aktivera helskärm'; + case 'videoControls.exitFullscreenButton': + return 'Avsluta helskärm'; + case 'videoControls.rotationLockButton': + return 'Rotationslås'; + case 'videoControls.timelineSlider': + return 'Videotidslinje'; + case 'videoControls.volumeSlider': + return 'Volymnivå'; + case 'videoControls.backButton': + return 'Tillbaka'; + case 'userStatus.admin': + return 'Admin'; + case 'userStatus.restricted': + return 'Begränsad'; + case 'userStatus.protected': + return 'Skyddad'; + case 'userStatus.current': + return 'NUVARANDE'; + case 'messages.markedAsWatched': + return 'Markerad som sedd'; + case 'messages.markedAsUnwatched': + return 'Markerad som osedd'; + case 'messages.removedFromContinueWatching': + return 'Borttagen från Fortsätt titta'; + case 'messages.errorLoading': + return ({required Object error}) => 'Fel: ${error}'; + case 'messages.fileInfoNotAvailable': + return 'Filinformation inte tillgänglig'; + case 'messages.errorLoadingFileInfo': + return ({required Object error}) => + 'Fel vid laddning av filinformation: ${error}'; + case 'messages.errorLoadingSeries': + return 'Fel vid laddning av serie'; + case 'messages.errorLoadingSeason': + return 'Fel vid laddning av säsong'; + case 'messages.musicNotSupported': + return 'Musikuppspelning stöds inte ännu'; + case 'messages.logsCleared': + return 'Loggar rensade'; + case 'messages.logsCopied': + return 'Loggar kopierade till urklipp'; + case 'messages.noLogsAvailable': + return 'Inga loggar tillgängliga'; + case 'messages.libraryScanning': + return ({required Object title}) => 'Skannar "${title}"...'; + case 'messages.libraryScanStarted': + return ({required Object title}) => + 'Biblioteksskanning startad för "${title}"'; + case 'messages.libraryScanFailed': + return ({required Object error}) => + 'Misslyckades att skanna bibliotek: ${error}'; + case 'messages.metadataRefreshing': + return ({required Object title}) => + 'Uppdaterar metadata för "${title}"...'; + case 'messages.metadataRefreshStarted': + return ({required Object title}) => + 'Metadata-uppdatering startad för "${title}"'; + case 'messages.metadataRefreshFailed': + return ({required Object error}) => + 'Misslyckades att uppdatera metadata: ${error}'; + case 'messages.noPlexToken': + return 'Ingen Plex-token hittad. Vänligen logga in igen.'; + case 'messages.logoutConfirm': + return 'Är du säker på att du vill logga ut?'; + case 'messages.noSeasonsFound': + return 'Inga säsonger hittades'; + case 'messages.noEpisodesFound': + return 'Inga avsnitt hittades i första säsongen'; + case 'messages.noEpisodesFoundGeneral': + return 'Inga avsnitt hittades'; + case 'messages.noResultsFound': + return 'Inga resultat hittades'; + case 'messages.sleepTimerSet': + return ({required Object label}) => 'Sovtimer inställd för ${label}'; + case 'messages.failedToSwitchProfile': + return ({required Object displayName}) => + 'Misslyckades att byta till ${displayName}'; + case 'messages.noItemsAvailable': + return 'Inga objekt tillgängliga'; + case 'messages.failedToCreatePlayQueue': + return 'Det gick inte att skapa uppspelningskö'; + case 'messages.failedToCreatePlayQueueNoItems': + return 'Det gick inte att skapa uppspelningskö – inga objekt'; + case 'messages.failedPlayback': + return ({required Object action, required Object error}) => + 'Kunde inte ${action}: ${error}'; + case 'profile.noUsersAvailable': + return 'Inga användare tillgängliga'; + case 'subtitlingStyling.stylingOptions': + return 'Stilalternativ'; + case 'subtitlingStyling.fontSize': + return 'Teckenstorlek'; + case 'subtitlingStyling.textColor': + return 'Textfärg'; + case 'subtitlingStyling.borderSize': + return 'Kantstorlek'; + case 'subtitlingStyling.borderColor': + return 'Kantfärg'; + case 'subtitlingStyling.backgroundOpacity': + return 'Bakgrundsopacitet'; + case 'subtitlingStyling.backgroundColor': + return 'Bakgrundsfärg'; + case 'dialog.confirmAction': + return 'Bekräfta åtgärd'; + case 'dialog.areYouSure': + return 'Är du säker på att du vill utföra denna åtgärd?'; + case 'dialog.cancel': + return 'Avbryt'; + case 'dialog.playNow': + return 'Spela nu'; + case 'discover.title': + return 'Upptäck'; + case 'discover.switchProfile': + return 'Byt profil'; + case 'discover.switchServer': + return 'Byt server'; + case 'discover.logout': + return 'Logga ut'; + case 'discover.noContentAvailable': + return 'Inget innehåll tillgängligt'; + case 'discover.addMediaToLibraries': + return 'Lägg till media till dina bibliotek'; + case 'discover.continueWatching': + return 'Fortsätt titta'; + case 'discover.recentlyAdded': + return 'Nyligen tillagda'; + case 'discover.play': + return 'Spela'; + case 'discover.resume': + return 'Återuppta'; + case 'discover.playEpisode': + return ({required Object season, required Object episode}) => + 'Spela S${season}, E${episode}'; + case 'discover.resumeEpisode': + return ({required Object season, required Object episode}) => + 'Återuppta S${season}, E${episode}'; + case 'discover.pause': + return 'Pausa'; + case 'discover.overview': + return 'Översikt'; + case 'discover.cast': + return 'Rollbesättning'; + case 'discover.seasons': + return 'Säsonger'; + case 'discover.studio': + return 'Studio'; + case 'discover.rating': + return 'Åldersgräns'; + case 'discover.watched': + return 'Tittad'; + case 'discover.episodeCount': + return ({required Object count}) => '${count} avsnitt'; + case 'discover.watchedProgress': + return ({required Object watched, required Object total}) => + '${watched}/${total} sedda'; + case 'discover.movie': + return 'Film'; + case 'discover.tvShow': + return 'TV-serie'; + case 'discover.minutesLeft': + return ({required Object minutes}) => '${minutes} min kvar'; + case 'errors.searchFailed': + return ({required Object error}) => 'Sökning misslyckades: ${error}'; + case 'errors.connectionTimeout': + return ({required Object context}) => + 'Anslutnings-timeout vid laddning ${context}'; + case 'errors.connectionFailed': + return 'Kan inte ansluta till Plex-server'; + case 'errors.failedToLoad': + return ({required Object context, required Object error}) => + 'Misslyckades att ladda ${context}: ${error}'; + case 'errors.noClientAvailable': + return 'Ingen klient tillgänglig'; + case 'errors.authenticationFailed': + return ({required Object error}) => + 'Autentisering misslyckades: ${error}'; + case 'errors.couldNotLaunchUrl': + return 'Kunde inte öppna autentiserings-URL'; + case 'errors.pleaseEnterToken': + return 'Vänligen ange en token'; + case 'errors.invalidToken': + return 'Ogiltig token'; + case 'errors.failedToVerifyToken': + return ({required Object error}) => + 'Misslyckades att verifiera token: ${error}'; + case 'errors.failedToSwitchProfile': + return ({required Object displayName}) => + 'Misslyckades att byta till ${displayName}'; + case 'errors.connectionFailedGeneric': + return 'Anslutning misslyckades'; + case 'libraries.title': + return 'Bibliotek'; + case 'libraries.scanLibraryFiles': + return 'Skanna biblioteksfiler'; + case 'libraries.scanLibrary': + return 'Skanna bibliotek'; + case 'libraries.analyze': + return 'Analysera'; + case 'libraries.analyzeLibrary': + return 'Analysera bibliotek'; + case 'libraries.refreshMetadata': + return 'Uppdatera metadata'; + case 'libraries.emptyTrash': + return 'Töm papperskorg'; + case 'libraries.emptyingTrash': + return ({required Object title}) => + 'Tömmer papperskorg för "${title}"...'; + case 'libraries.trashEmptied': + return ({required Object title}) => 'Papperskorg tömd för "${title}"'; + case 'libraries.failedToEmptyTrash': + return ({required Object error}) => + 'Misslyckades att tömma papperskorg: ${error}'; + case 'libraries.analyzing': + return ({required Object title}) => 'Analyserar "${title}"...'; + case 'libraries.analysisStarted': + return ({required Object title}) => 'Analys startad för "${title}"'; + case 'libraries.failedToAnalyze': + return ({required Object error}) => + 'Misslyckades att analysera bibliotek: ${error}'; + case 'libraries.noLibrariesFound': + return 'Inga bibliotek hittades'; + case 'libraries.thisLibraryIsEmpty': + return 'Detta bibliotek är tomt'; + case 'libraries.all': + return 'Alla'; + case 'libraries.clearAll': + return 'Rensa alla'; + case 'libraries.scanLibraryConfirm': + return ({required Object title}) => + 'Är du säker på att du vill skanna "${title}"?'; + case 'libraries.analyzeLibraryConfirm': + return ({required Object title}) => + 'Är du säker på att du vill analysera "${title}"?'; + case 'libraries.refreshMetadataConfirm': + return ({required Object title}) => + 'Är du säker på att du vill uppdatera metadata för "${title}"?'; + case 'libraries.emptyTrashConfirm': + return ({required Object title}) => + 'Är du säker på att du vill tömma papperskorgen för "${title}"?'; + case 'libraries.manageLibraries': + return 'Hantera bibliotek'; + case 'libraries.sort': + return 'Sortera'; + case 'libraries.sortBy': + return 'Sortera efter'; + case 'libraries.filters': + return 'Filter'; + case 'libraries.loadingLibraryWithCount': + return ({required Object count}) => + 'Laddar bibliotek... (${count} objekt laddade)'; + case 'libraries.confirmActionMessage': + return 'Är du säker på att du vill utföra denna åtgärd?'; + case 'libraries.showLibrary': + return 'Visa bibliotek'; + case 'libraries.hideLibrary': + return 'Dölj bibliotek'; + case 'libraries.libraryOptions': + return 'Biblioteksalternativ'; + case 'libraries.content': + return 'bibliotekets innehåll'; + case 'libraries.selectLibrary': + return 'Välj bibliotek'; + case 'libraries.filtersWithCount': + return ({required Object count}) => 'Filter (${count})'; + case 'libraries.noRecommendations': + return 'Inga rekommendationer tillgängliga'; + case 'libraries.noCollections': + return 'Inga samlingar i det här biblioteket'; + case 'libraries.noFoldersFound': + return 'Inga mappar hittades'; + case 'libraries.folders': + return 'mappar'; + case 'libraries.tabs.recommended': + return 'Rekommenderat'; + case 'libraries.tabs.browse': + return 'Bläddra'; + case 'libraries.tabs.collections': + return 'Samlingar'; + case 'libraries.tabs.playlists': + return 'Spellistor'; + case 'libraries.groupings.all': + return 'Alla'; + case 'libraries.groupings.movies': + return 'Filmer'; + case 'libraries.groupings.shows': + return 'Serier'; + case 'libraries.groupings.seasons': + return 'Säsonger'; + case 'libraries.groupings.episodes': + return 'Avsnitt'; + case 'libraries.groupings.folders': + return 'Mappar'; + case 'about.title': + return 'Om'; + case 'about.openSourceLicenses': + return 'Öppen källkod-licenser'; + case 'about.versionLabel': + return ({required Object version}) => 'Version ${version}'; + case 'about.appDescription': + return 'En vacker Plex-klient för Flutter'; + case 'about.viewLicensesDescription': + return 'Visa licenser för tredjepartsbibliotek'; + case 'serverSelection.connectingToServer': + return 'Ansluter till server...'; + case 'serverSelection.connectingToAllServers': + return 'Ansluter till alla servrar...'; + case 'serverSelection.connectToAllServers': + return 'Anslut till alla servrar'; + case 'serverSelection.allServerConnectionsFailed': + return 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.'; + case 'serverSelection.foundServers': + return ({required Object count}) => 'Hittade ${count} server(ar)'; + case 'serverSelection.orSelectIndividually': + return 'Eller välj en server individuellt:'; + case 'serverSelection.serverDebugCopied': + return 'Server-felsökningsdata kopierad till urklipp'; + case 'serverSelection.copyDebugData': + return 'Kopiera felsökningsdata'; + case 'serverSelection.noServersFound': + return 'Inga servrar hittades'; + case 'serverSelection.malformedServerData': + return ({required Object count}) => + 'Hittade ${count} server(ar) med felformaterad data. Inga giltiga servrar tillgängliga.'; + case 'serverSelection.incompleteServerInfo': + return 'Vissa servrar har ofullständig information och hoppades över. Vänligen kontrollera ditt Plex.tv-konto.'; + case 'serverSelection.incompleteConnectionInfo': + return 'Server-anslutningsinformation är ofullständig. Försök igen.'; + case 'serverSelection.malformedServerInfo': + return ({required Object message}) => + 'Serverinformation är felformaterad: ${message}'; + case 'serverSelection.networkConnectionFailed': + return 'Nätverksanslutning misslyckades. Kontrollera din internetanslutning och försök igen.'; + case 'serverSelection.authenticationFailed': + return 'Autentisering misslyckades. Logga in igen.'; + case 'serverSelection.plexServiceUnavailable': + return 'Plex-tjänst otillgänglig. Försök igen senare.'; + case 'serverSelection.failedToLoadServers': + return ({required Object error}) => + 'Misslyckades att ladda servrar: ${error}'; + case 'hubDetail.title': + return 'Titel'; + case 'hubDetail.releaseYear': + return 'Utgivningsår'; + case 'hubDetail.dateAdded': + return 'Datum tillagd'; + case 'hubDetail.rating': + return 'Betyg'; + case 'hubDetail.noItemsFound': + return 'Inga objekt hittades'; + case 'logs.title': + return 'Loggar'; + case 'logs.clearLogs': + return 'Rensa loggar'; + case 'logs.copyLogs': + return 'Kopiera loggar'; + case 'logs.exportLogs': + return 'Exportera loggar'; + case 'logs.noLogsToShow': + return 'Inga loggar att visa'; + case 'logs.error': + return 'Fel:'; + case 'logs.stackTrace': + return 'Stack trace:'; + case 'licenses.relatedPackages': + return 'Relaterade paket'; + case 'licenses.license': + return 'Licens'; + case 'licenses.licenseNumber': + return ({required Object number}) => 'Licens ${number}'; + case 'licenses.licensesCount': + return ({required Object count}) => '${count} licenser'; + case 'navigation.home': + return 'Hem'; + case 'navigation.search': + return 'Sök'; + case 'navigation.libraries': + return 'Bibliotek'; + case 'navigation.settings': + return 'Inställningar'; + case 'playlists.title': + return 'Spellistor'; + case 'playlists.noPlaylists': + return 'Inga spellistor hittades'; + case 'playlists.create': + return 'Skapa spellista'; + case 'playlists.newPlaylist': + return 'Ny spellista'; + case 'playlists.playlistName': + return 'Spellistans namn'; + case 'playlists.enterPlaylistName': + return 'Ange spellistans namn'; + case 'playlists.edit': + return 'Redigera spellista'; + case 'playlists.delete': + return 'Ta bort spellista'; + case 'playlists.addTo': + return 'Lägg till i spellista'; + case 'playlists.addItems': + return 'Lägg till objekt'; + case 'playlists.removeItem': + return 'Ta bort från spellista'; + case 'playlists.clearPlaylist': + return 'Rensa spellista'; + case 'playlists.playAll': + return 'Spela alla'; + case 'playlists.shuffle': + return 'Blanda'; + case 'playlists.smartPlaylist': + return 'Smart spellista'; + case 'playlists.regularPlaylist': + return 'Vanlig spellista'; + case 'playlists.itemCount': + return ({required Object count}) => '${count} objekt'; + case 'playlists.oneItem': + return '1 objekt'; + case 'playlists.emptyPlaylist': + return 'Denna spellista är tom'; + case 'playlists.deleteConfirm': + return 'Ta bort spellista?'; + case 'playlists.deleteMessage': + return ({required Object name}) => + 'Är du säker på att du vill ta bort "${name}"?'; + case 'playlists.created': + return 'Spellista skapad'; + case 'playlists.updated': + return 'Spellista uppdaterad'; + case 'playlists.deleted': + return 'Spellista borttagen'; + case 'playlists.itemAdded': + return 'Tillagd i spellista'; + case 'playlists.itemRemoved': + return 'Borttagen från spellista'; + case 'playlists.selectPlaylist': + return 'Välj spellista'; + case 'playlists.createNewPlaylist': + return 'Skapa ny spellista'; + case 'playlists.errorCreating': + return 'Det gick inte att skapa spellista'; + case 'playlists.errorDeleting': + return 'Det gick inte att ta bort spellista'; + case 'playlists.errorLoading': + return 'Det gick inte att ladda spellistor'; + case 'playlists.errorAdding': + return 'Det gick inte att lägga till i spellista'; + case 'playlists.errorReordering': + return 'Det gick inte att omordna spellisteobjekt'; + case 'playlists.errorRemoving': + return 'Det gick inte att ta bort från spellista'; + case 'playlists.playlist': + return 'Spellista'; + case 'collections.title': + return 'Samlingar'; + case 'collections.collection': + return 'Samling'; + case 'collections.empty': + return 'Samlingen är tom'; + case 'collections.noItems': + return 'Inga objekt i den här samlingen'; + case 'collections.unknownLibrarySection': + return 'Kan inte ta bort: okänd bibliotekssektion'; + case 'collections.deleteCollection': + return 'Ta bort samling'; + case 'collections.deleteConfirm': + return ({required Object title}) => + 'Är du säker på att du vill ta bort "${title}"? Detta går inte att ångra.'; + case 'collections.deleted': + return 'Samling borttagen'; + case 'collections.deleteFailed': + return 'Det gick inte att ta bort samlingen'; + case 'collections.deleteFailedWithError': + return ({required Object error}) => + 'Det gick inte att ta bort samlingen: ${error}'; + case 'collections.failedToLoadItems': + return ({required Object error}) => + 'Det gick inte att läsa in samlingsobjekt: ${error}'; + case 'collections.addTo': + return 'Lägg till i samling'; + case 'collections.selectCollection': + return 'Välj samling'; + case 'collections.createNewCollection': + return 'Skapa ny samling'; + case 'collections.collectionName': + return 'Samlingsnamn'; + case 'collections.enterCollectionName': + return 'Ange samlingsnamn'; + case 'collections.addedToCollection': + return 'Tillagd i samling'; + case 'collections.errorAddingToCollection': + return 'Fel vid tillägg i samling'; + case 'collections.created': + return 'Samling skapad'; + case 'collections.removeFromCollection': + return 'Ta bort från samling'; + case 'collections.removeFromCollectionConfirm': + return ({required Object title}) => + 'Ta bort "${title}" från denna samling?'; + case 'collections.removedFromCollection': + return 'Borttagen från samling'; + case 'collections.removeFromCollectionFailed': + return 'Misslyckades med att ta bort från samling'; + case 'collections.removeFromCollectionError': + return ({required Object error}) => + 'Fel vid borttagning från samling: ${error}'; + default: + return null; + } + } } extension on _StringsZh { - dynamic _flatMapFunction(String path) { - switch (path) { - case 'app.title': return 'Plezy'; - case 'app.loading': return '加载中...'; - case 'auth.signInWithPlex': return '使用 Plex 登录'; - case 'auth.showQRCode': return '显示二维码'; - case 'auth.cancel': return '取消'; - case 'auth.authenticate': return '验证'; - case 'auth.retry': return '重试'; - case 'auth.debugEnterToken': return '调试:输入 Plex Token'; - case 'auth.plexTokenLabel': return 'Plex 授权令牌 (Auth Token)'; - case 'auth.plexTokenHint': return '输入你的 Plex.tv 令牌'; - case 'auth.authenticationTimeout': return '验证超时。请重试。'; - case 'auth.scanQRCodeInstruction': return '请使用已登录 Plex 的设备扫描此二维码进行验证。'; - case 'auth.waitingForAuth': return '等待验证中...\n请在你的浏览器中完成登录。'; - case 'common.cancel': return '取消'; - case 'common.save': return '保存'; - case 'common.close': return '关闭'; - case 'common.clear': return '清除'; - case 'common.reset': return '重置'; - case 'common.later': return '稍后'; - case 'common.submit': return '提交'; - case 'common.confirm': return '确认'; - case 'common.retry': return '重试'; - case 'common.playNow': return '立即播放'; - case 'common.logout': return '登出'; - case 'common.online': return '在线'; - case 'common.offline': return '离线'; - case 'common.owned': return '拥有'; - case 'common.shared': return '共享'; - case 'common.current': return '当前'; - case 'common.unknown': return '未知'; - case 'common.refresh': return '刷新'; - case 'common.yes': return '是'; - case 'common.no': return '否'; - case 'common.server': return '服务器'; - case 'common.delete': return '删除'; - case 'common.shuffle': return '随机播放'; - case 'common.addTo': return '添加到...'; - case 'screens.licenses': return '许可证'; - case 'screens.selectServer': return '选择服务器'; - case 'screens.switchProfile': return '切换配置文件'; - case 'screens.subtitleStyling': return '字幕样式'; - case 'screens.search': return '搜索'; - case 'screens.logs': return '日志'; - case 'update.available': return '有可用更新'; - case 'update.versionAvailable': return ({required Object version}) => '版本 ${version} 已发布'; - case 'update.currentVersion': return ({required Object version}) => '当前版本: ${version}'; - case 'update.skipVersion': return '跳过此版本'; - case 'update.viewRelease': return '查看发布详情'; - case 'update.latestVersion': return '已安装的版本是可用的最新版本'; - case 'update.checkFailed': return '无法检查更新'; - case 'settings.title': return '设置'; - case 'settings.language': return '语言'; - case 'settings.theme': return '主题'; - case 'settings.appearance': return '外观'; - case 'settings.videoPlayback': return '视频播放'; - case 'settings.shufflePlay': return '随机播放'; - case 'settings.advanced': return '高级'; - case 'settings.useSeasonPostersDescription': return '为剧集显示季海报而非剧集海报'; - case 'settings.showHeroSectionDescription': return '在主屏幕上显示精选内容轮播区'; - case 'settings.secondsLabel': return '秒'; - case 'settings.minutesLabel': return '分钟'; - case 'settings.secondsShort': return 's'; - case 'settings.minutesShort': return 'm'; - case 'settings.durationHint': return ({required Object min, required Object max}) => '输入时长 (${min}-${max})'; - case 'settings.systemTheme': return '系统'; - case 'settings.systemThemeDescription': return '跟随系统设置'; - case 'settings.lightTheme': return '浅色'; - case 'settings.darkTheme': return '深色'; - case 'settings.libraryDensity': return '媒体库密度'; - case 'settings.compact': return '紧凑'; - case 'settings.compactDescription': return '卡片更小,显示更多项目'; - case 'settings.normal': return '标准'; - case 'settings.normalDescription': return '默认尺寸'; - case 'settings.comfortable': return '舒适'; - case 'settings.comfortableDescription': return '卡片更大,显示更少项目'; - case 'settings.viewMode': return '视图模式'; - case 'settings.gridView': return '网格视图'; - case 'settings.gridViewDescription': return '以网格布局显示项目'; - case 'settings.listView': return '列表视图'; - case 'settings.listViewDescription': return '以列表布局显示项目'; - case 'settings.useSeasonPosters': return '使用季海报'; - case 'settings.showHeroSection': return '显示主要精选区'; - case 'settings.hardwareDecoding': return '硬件解码'; - case 'settings.hardwareDecodingDescription': return '如果可用,使用硬件加速'; - case 'settings.bufferSize': return '缓冲区大小'; - case 'settings.bufferSizeMB': return ({required Object size}) => '${size}MB'; - case 'settings.subtitleStyling': return '字幕样式'; - case 'settings.subtitleStylingDescription': return '调整字幕外观'; - case 'settings.smallSkipDuration': return '短跳过时长'; - case 'settings.largeSkipDuration': return '长跳过时长'; - case 'settings.secondsUnit': return ({required Object seconds}) => '${seconds} 秒'; - case 'settings.defaultSleepTimer': return '默认睡眠定时器'; - case 'settings.minutesUnit': return ({required Object minutes}) => '${minutes} 分钟'; - case 'settings.rememberTrackSelections': return '记住每个剧集/电影的音轨选择'; - case 'settings.rememberTrackSelectionsDescription': return '在播放过程中更改音轨时自动保存音频和字幕语言偏好'; - case 'settings.unwatchedOnly': return '仅未观看'; - case 'settings.unwatchedOnlyDescription': return '随机队列中仅包含未观看的剧集'; - case 'settings.shuffleOrderNavigation': return '随机顺序导航'; - case 'settings.shuffleOrderNavigationDescription': return '下一集/上一集按钮遵循随机播放顺序'; - case 'settings.loopShuffleQueue': return '循环随机队列'; - case 'settings.loopShuffleQueueDescription': return '在队列结束时重新开始'; - case 'settings.videoPlayerControls': return '视频播放器控制'; - case 'settings.keyboardShortcuts': return '键盘快捷键'; - case 'settings.keyboardShortcutsDescription': return '自定义键盘快捷键'; - case 'settings.debugLogging': return '调试日志'; - case 'settings.debugLoggingDescription': return '启用详细日志记录以便故障排除'; - case 'settings.viewLogs': return '查看日志'; - case 'settings.viewLogsDescription': return '查看应用程序日志'; - case 'settings.clearCache': return '清除缓存'; - case 'settings.clearCacheDescription': return '这将清除所有缓存的图片和数据。清除缓存后,应用程序加载内容可能会变慢。'; - case 'settings.clearCacheSuccess': return '缓存清除成功'; - case 'settings.resetSettings': return '重置设置'; - case 'settings.resetSettingsDescription': return '这会将所有设置重置为其默认值。此操作无法撤销。'; - case 'settings.resetSettingsSuccess': return '设置重置成功'; - case 'settings.shortcutsReset': return '快捷键已重置为默认值'; - case 'settings.about': return '关于'; - case 'settings.aboutDescription': return '应用程序信息和许可证'; - case 'settings.updates': return '更新'; - case 'settings.updateAvailable': return '有可用更新'; - case 'settings.checkForUpdates': return '检查更新'; - case 'settings.validationErrorEnterNumber': return '请输入一个有效的数字'; - case 'settings.validationErrorDuration': return ({required Object min, required Object max, required Object unit}) => '时长必须介于 ${min} 和 ${max} ${unit} 之间'; - case 'settings.shortcutAlreadyAssigned': return ({required Object action}) => '快捷键已被分配给 ${action}'; - case 'settings.shortcutUpdated': return ({required Object action}) => '快捷键已为 ${action} 更新'; - case 'search.hint': return '搜索电影、系列、音乐...'; - case 'search.tryDifferentTerm': return '尝试不同的搜索词'; - case 'search.searchYourMedia': return '搜索媒体'; - case 'search.enterTitleActorOrKeyword': return '输入标题、演员或关键词'; - case 'hotkeys.setShortcutFor': return ({required Object actionName}) => '为 ${actionName} 设置快捷键'; - case 'hotkeys.clearShortcut': return '清除快捷键'; - case 'pinEntry.enterPin': return '输入 PIN'; - case 'pinEntry.showPin': return '显示 PIN'; - case 'pinEntry.hidePin': return '隐藏 PIN'; - case 'fileInfo.title': return '文件信息'; - case 'fileInfo.video': return '视频'; - case 'fileInfo.audio': return '音频'; - case 'fileInfo.file': return '文件'; - case 'fileInfo.advanced': return '高级'; - case 'fileInfo.codec': return '编解码器'; - case 'fileInfo.resolution': return '分辨率'; - case 'fileInfo.bitrate': return '比特率'; - case 'fileInfo.frameRate': return '帧率'; - case 'fileInfo.aspectRatio': return '宽高比'; - case 'fileInfo.profile': return '配置文件'; - case 'fileInfo.bitDepth': return '位深度'; - case 'fileInfo.colorSpace': return '色彩空间'; - case 'fileInfo.colorRange': return '色彩范围'; - case 'fileInfo.colorPrimaries': return '颜色原色'; - case 'fileInfo.chromaSubsampling': return '色度子采样'; - case 'fileInfo.channels': return '声道'; - case 'fileInfo.path': return '路径'; - case 'fileInfo.size': return '大小'; - case 'fileInfo.container': return '容器'; - case 'fileInfo.duration': return '时长'; - case 'fileInfo.optimizedForStreaming': return '已优化用于流媒体'; - case 'fileInfo.has64bitOffsets': return '64位偏移量'; - case 'mediaMenu.markAsWatched': return '标记为已观看'; - case 'mediaMenu.markAsUnwatched': return '标记为未观看'; - case 'mediaMenu.removeFromContinueWatching': return '从继续观看中移除'; - case 'mediaMenu.goToSeries': return '转到系列'; - case 'mediaMenu.goToSeason': return '转到季'; - case 'mediaMenu.shufflePlay': return '随机播放'; - case 'mediaMenu.fileInfo': return '文件信息'; - case 'accessibility.mediaCardMovie': return ({required Object title}) => '${title}, 电影'; - case 'accessibility.mediaCardShow': return ({required Object title}) => '${title}, 电视剧'; - case 'accessibility.mediaCardEpisode': return ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}'; - case 'accessibility.mediaCardSeason': return ({required Object title, required Object seasonInfo}) => '${title}, ${seasonInfo}'; - case 'accessibility.mediaCardWatched': return '已观看'; - case 'accessibility.mediaCardPartiallyWatched': return ({required Object percent}) => '已观看 ${percent} 百分比'; - case 'accessibility.mediaCardUnwatched': return '未观看'; - case 'accessibility.tapToPlay': return '点击播放'; - case 'tooltips.shufflePlay': return '随机播放'; - case 'tooltips.markAsWatched': return '标记为已观看'; - case 'tooltips.markAsUnwatched': return '标记为未观看'; - case 'videoControls.audioLabel': return '音频'; - case 'videoControls.subtitlesLabel': return '字幕'; - case 'videoControls.resetToZero': return '重置为 0ms'; - case 'videoControls.addTime': return ({required Object amount, required Object unit}) => '+${amount}${unit}'; - case 'videoControls.minusTime': return ({required Object amount, required Object unit}) => '-${amount}${unit}'; - case 'videoControls.playsLater': return ({required Object label}) => '${label} 播放较晚'; - case 'videoControls.playsEarlier': return ({required Object label}) => '${label} 播放较早'; - case 'videoControls.noOffset': return '无偏移'; - case 'videoControls.letterbox': return '信箱模式(Letterbox)'; - case 'videoControls.fillScreen': return '填充屏幕'; - case 'videoControls.stretch': return '拉伸'; - case 'videoControls.lockRotation': return '锁定旋转'; - case 'videoControls.unlockRotation': return '解锁旋转'; - case 'videoControls.sleepTimer': return '睡眠定时器'; - case 'videoControls.timerActive': return '定时器已激活'; - case 'videoControls.playbackWillPauseIn': return ({required Object duration}) => '播放将在 ${duration} 后暂停'; - case 'videoControls.sleepTimerCompleted': return '睡眠定时器已完成 - 播放已暂停'; - case 'videoControls.playButton': return '播放'; - case 'videoControls.pauseButton': return '暂停'; - case 'videoControls.seekBackwardButton': return ({required Object seconds}) => '后退 ${seconds} 秒'; - case 'videoControls.seekForwardButton': return ({required Object seconds}) => '前进 ${seconds} 秒'; - case 'videoControls.previousButton': return '上一集'; - case 'videoControls.nextButton': return '下一集'; - case 'videoControls.previousChapterButton': return '上一章节'; - case 'videoControls.nextChapterButton': return '下一章节'; - case 'videoControls.volumeButton': return '音量'; - case 'videoControls.muteButton': return '静音'; - case 'videoControls.unmuteButton': return '取消静音'; - case 'videoControls.settingsButton': return '视频设置'; - case 'videoControls.audioTrackButton': return '音轨'; - case 'videoControls.subtitlesButton': return '字幕'; - case 'videoControls.chaptersButton': return '章节'; - case 'videoControls.versionsButton': return '视频版本'; - case 'videoControls.aspectRatioButton': return '宽高比'; - case 'videoControls.fullscreenButton': return '进入全屏'; - case 'videoControls.exitFullscreenButton': return '退出全屏'; - case 'videoControls.rotationLockButton': return '旋转锁定'; - case 'videoControls.timelineSlider': return '视频时间轴'; - case 'videoControls.volumeSlider': return '音量调节'; - case 'videoControls.backButton': return '返回'; - case 'userStatus.admin': return '管理员'; - case 'userStatus.restricted': return '受限'; - case 'userStatus.protected': return '受保护'; - case 'userStatus.current': return '当前'; - case 'messages.markedAsWatched': return '已标记为已观看'; - case 'messages.markedAsUnwatched': return '已标记为未观看'; - case 'messages.removedFromContinueWatching': return '已从继续观看中移除'; - case 'messages.errorLoading': return ({required Object error}) => '错误: ${error}'; - case 'messages.fileInfoNotAvailable': return '文件信息不可用'; - case 'messages.errorLoadingFileInfo': return ({required Object error}) => '加载文件信息时出错: ${error}'; - case 'messages.errorLoadingSeries': return '加载系列时出错'; - case 'messages.errorLoadingSeason': return '加载季时出错'; - case 'messages.musicNotSupported': return '尚不支持播放音乐'; - case 'messages.logsCleared': return '日志已清除'; - case 'messages.logsCopied': return '日志已复制到剪贴板'; - case 'messages.noLogsAvailable': return '没有可用日志'; - case 'messages.libraryScanning': return ({required Object title}) => '正在扫描 “${title}”...'; - case 'messages.libraryScanStarted': return ({required Object title}) => '已开始扫描 “${title}” 媒体库'; - case 'messages.libraryScanFailed': return ({required Object error}) => '无法扫描媒体库: ${error}'; - case 'messages.metadataRefreshing': return ({required Object title}) => '正在刷新 “${title}” 的元数据...'; - case 'messages.metadataRefreshStarted': return ({required Object title}) => '已开始刷新 “${title}” 的元数据'; - case 'messages.metadataRefreshFailed': return ({required Object error}) => '无法刷新元数据: ${error}'; - case 'messages.noPlexToken': return '未找到 Plex 令牌。请重新登录。'; - case 'messages.logoutConfirm': return '你确定要登出吗?'; - case 'messages.noSeasonsFound': return '未找到季'; - case 'messages.noEpisodesFound': return '在第一季中未找到剧集'; - case 'messages.noEpisodesFoundGeneral': return '未找到剧集'; - case 'messages.noResultsFound': return '未找到结果'; - case 'messages.sleepTimerSet': return ({required Object label}) => '睡眠定时器已设置为 ${label}'; - case 'messages.failedToSwitchProfile': return ({required Object displayName}) => '无法切换到 ${displayName}'; - case 'messages.noItemsAvailable': return '没有可用的项目'; - case 'messages.failedToCreatePlayQueue': return '创建播放队列失败'; - case 'messages.failedToCreatePlayQueueNoItems': return '创建播放队列失败 - 没有项目'; - case 'messages.failedPlayback': return ({required Object action, required Object error}) => '无法${action}: ${error}'; - case 'profile.noUsersAvailable': return '没有可用用户'; - case 'subtitlingStyling.stylingOptions': return '样式选项'; - case 'subtitlingStyling.fontSize': return '字号'; - case 'subtitlingStyling.textColor': return '文本颜色'; - case 'subtitlingStyling.borderSize': return '边框大小'; - case 'subtitlingStyling.borderColor': return '边框颜色'; - case 'subtitlingStyling.backgroundOpacity': return '背景不透明度'; - case 'subtitlingStyling.backgroundColor': return '背景颜色'; - case 'dialog.confirmAction': return '确认操作'; - case 'dialog.areYouSure': return '确定要执行此操作吗?'; - case 'dialog.cancel': return '取消'; - case 'dialog.playNow': return '立即播放'; - case 'discover.title': return '发现'; - case 'discover.switchProfile': return '切换配置文件'; - case 'discover.switchServer': return '切换服务器'; - case 'discover.logout': return '登出'; - case 'discover.noContentAvailable': return '没有可用内容'; - case 'discover.addMediaToLibraries': return '请向你的媒体库添加一些媒体'; - case 'discover.continueWatching': return '继续观看'; - case 'discover.recentlyAdded': return '最近添加'; - case 'discover.play': return '播放'; - case 'discover.resume': return '继续'; - case 'discover.playEpisode': return ({required Object season, required Object episode}) => '播放 S${season}, E${episode}'; - case 'discover.resumeEpisode': return ({required Object season, required Object episode}) => '继续 S${season}, E${episode}'; - case 'discover.pause': return '暂停'; - case 'discover.overview': return '概述'; - case 'discover.cast': return '演员表'; - case 'discover.seasons': return '季数'; - case 'discover.studio': return '制作公司'; - case 'discover.rating': return '年龄分级'; - case 'discover.watched': return '已观看'; - case 'discover.episodeCount': return ({required Object count}) => '${count} 集'; - case 'discover.watchedProgress': return ({required Object watched, required Object total}) => '已观看 ${watched}/${total} 集'; - case 'discover.movie': return '电影'; - case 'discover.tvShow': return '电视剧'; - case 'discover.minutesLeft': return ({required Object minutes}) => '剩余 ${minutes} 分钟'; - case 'errors.searchFailed': return ({required Object error}) => '搜索失败: ${error}'; - case 'errors.connectionTimeout': return ({required Object context}) => '加载 ${context} 时连接超时'; - case 'errors.connectionFailed': return '无法连接到 Plex 服务器'; - case 'errors.failedToLoad': return ({required Object context, required Object error}) => '无法加载 ${context}: ${error}'; - case 'errors.noClientAvailable': return '没有可用客户端'; - case 'errors.authenticationFailed': return ({required Object error}) => '验证失败: ${error}'; - case 'errors.couldNotLaunchUrl': return '无法打开授权 URL'; - case 'errors.pleaseEnterToken': return '请输入一个令牌'; - case 'errors.invalidToken': return '令牌无效'; - case 'errors.failedToVerifyToken': return ({required Object error}) => '无法验证令牌: ${error}'; - case 'errors.failedToSwitchProfile': return ({required Object displayName}) => '无法切换到 ${displayName}'; - case 'errors.connectionFailedGeneric': return '连接失败'; - case 'libraries.title': return '媒体库'; - case 'libraries.scanLibraryFiles': return '扫描媒体库文件'; - case 'libraries.scanLibrary': return '扫描媒体库'; - case 'libraries.analyze': return '分析'; - case 'libraries.analyzeLibrary': return '分析媒体库'; - case 'libraries.refreshMetadata': return '刷新元数据'; - case 'libraries.emptyTrash': return '清空回收站'; - case 'libraries.emptyingTrash': return ({required Object title}) => '正在清空 “${title}” 的回收站...'; - case 'libraries.trashEmptied': return ({required Object title}) => '已清空 “${title}” 的回收站'; - case 'libraries.failedToEmptyTrash': return ({required Object error}) => '无法清空回收站: ${error}'; - case 'libraries.analyzing': return ({required Object title}) => '正在分析 “${title}”...'; - case 'libraries.analysisStarted': return ({required Object title}) => '已开始分析 “${title}”'; - case 'libraries.failedToAnalyze': return ({required Object error}) => '无法分析媒体库: ${error}'; - case 'libraries.noLibrariesFound': return '未找到媒体库'; - case 'libraries.thisLibraryIsEmpty': return '此媒体库为空'; - case 'libraries.all': return '全部'; - case 'libraries.clearAll': return '全部清除'; - case 'libraries.scanLibraryConfirm': return ({required Object title}) => '确定要扫描 “${title}” 吗?'; - case 'libraries.analyzeLibraryConfirm': return ({required Object title}) => '确定要分析 “${title}” 吗?'; - case 'libraries.refreshMetadataConfirm': return ({required Object title}) => '确定要刷新 “${title}” 的元数据吗?'; - case 'libraries.emptyTrashConfirm': return ({required Object title}) => '确定要清空 “${title}” 的回收站吗?'; - case 'libraries.manageLibraries': return '管理媒体库'; - case 'libraries.sort': return '排序'; - case 'libraries.sortBy': return '排序依据'; - case 'libraries.filters': return '筛选器'; - case 'libraries.loadingLibraryWithCount': return ({required Object count}) => '正在加载媒体库... (已加载 ${count} 个项目)'; - case 'libraries.confirmActionMessage': return '确定要执行此操作吗?'; - case 'libraries.showLibrary': return '显示媒体库'; - case 'libraries.hideLibrary': return '隐藏媒体库'; - case 'libraries.libraryOptions': return '媒体库选项'; - case 'libraries.content': return '媒体库内容'; - case 'libraries.selectLibrary': return '选择媒体库'; - case 'libraries.filtersWithCount': return ({required Object count}) => '筛选器(${count})'; - case 'libraries.noRecommendations': return '暂无推荐'; - case 'libraries.noCollections': return '此媒体库中没有合集'; - case 'libraries.noFoldersFound': return '未找到文件夹'; - case 'libraries.folders': return '文件夹'; - case 'libraries.tabs.recommended': return '推荐'; - case 'libraries.tabs.browse': return '浏览'; - case 'libraries.tabs.collections': return '合集'; - case 'libraries.tabs.playlists': return '播放列表'; - case 'libraries.groupings.all': return '全部'; - case 'libraries.groupings.movies': return '电影'; - case 'libraries.groupings.shows': return '剧集'; - case 'libraries.groupings.seasons': return '季'; - case 'libraries.groupings.episodes': return '集'; - case 'libraries.groupings.folders': return '文件夹'; - case 'about.title': return '关于'; - case 'about.openSourceLicenses': return '开源许可证'; - case 'about.versionLabel': return ({required Object version}) => '版本 ${version}'; - case 'about.appDescription': return '一款精美的 Flutter Plex 客户端'; - case 'about.viewLicensesDescription': return '查看第三方库的许可证'; - case 'serverSelection.connectingToServer': return '正在连接服务器...'; - case 'serverSelection.connectingToAllServers': return '正在连接所有服务器...'; - case 'serverSelection.connectToAllServers': return '连接所有服务器'; - case 'serverSelection.allServerConnectionsFailed': return '无法连接到任何服务器。请检查你的网络并重试。'; - case 'serverSelection.foundServers': return ({required Object count}) => '找到 ${count} 个服务器'; - case 'serverSelection.orSelectIndividually': return '或单独选择一个服务器:'; - case 'serverSelection.serverDebugCopied': return '服务器调试数据已复制到剪贴板'; - case 'serverSelection.copyDebugData': return '复制调试数据'; - case 'serverSelection.noServersFound': return '未找到服务器'; - case 'serverSelection.malformedServerData': return ({required Object count}) => '找到 ${count} 个数据格式错误的服务器。没有可用的有效服务器。'; - case 'serverSelection.incompleteServerInfo': return '某些服务器信息不完整,已被跳过。请检查你的 Plex.tv 账户。'; - case 'serverSelection.incompleteConnectionInfo': return '服务器连接信息不完整。请重试。'; - case 'serverSelection.malformedServerInfo': return ({required Object message}) => '服务器信息格式错误: ${message}'; - case 'serverSelection.networkConnectionFailed': return '网络连接失败。请检查你的网络连接并重试。'; - case 'serverSelection.authenticationFailed': return '验证失败。请重新登录。'; - case 'serverSelection.plexServiceUnavailable': return 'Plex 服务不可用。请稍后重试。'; - case 'serverSelection.failedToLoadServers': return ({required Object error}) => '无法加载服务器: ${error}'; - case 'hubDetail.title': return '标题'; - case 'hubDetail.releaseYear': return '发行年份'; - case 'hubDetail.dateAdded': return '添加日期'; - case 'hubDetail.rating': return '评分'; - case 'hubDetail.noItemsFound': return '未找到项目'; - case 'logs.title': return '日志'; - case 'logs.clearLogs': return '清除日志'; - case 'logs.copyLogs': return '复制日志'; - case 'logs.exportLogs': return '导出日志'; - case 'logs.noLogsToShow': return '没有可显示的日志'; - case 'logs.error': return '错误:'; - case 'logs.stackTrace': return '堆栈跟踪 (Stack Trace):'; - case 'licenses.relatedPackages': return '相关软件包'; - case 'licenses.license': return '许可证'; - case 'licenses.licenseNumber': return ({required Object number}) => '许可证 ${number}'; - case 'licenses.licensesCount': return ({required Object count}) => '${count} 个许可证'; - case 'navigation.home': return '主页'; - case 'navigation.search': return '搜索'; - case 'navigation.libraries': return '媒体库'; - case 'navigation.settings': return '设置'; - case 'playlists.title': return '播放列表'; - case 'playlists.noPlaylists': return '未找到播放列表'; - case 'playlists.create': return '创建播放列表'; - case 'playlists.newPlaylist': return '新播放列表'; - case 'playlists.playlistName': return '播放列表名称'; - case 'playlists.enterPlaylistName': return '输入播放列表名称'; - case 'playlists.edit': return '编辑播放列表'; - case 'playlists.delete': return '删除播放列表'; - case 'playlists.addTo': return '添加到播放列表'; - case 'playlists.addItems': return '添加项目'; - case 'playlists.removeItem': return '从播放列表中移除'; - case 'playlists.clearPlaylist': return '清空播放列表'; - case 'playlists.playAll': return '全部播放'; - case 'playlists.shuffle': return '随机播放'; - case 'playlists.smartPlaylist': return '智能播放列表'; - case 'playlists.regularPlaylist': return '普通播放列表'; - case 'playlists.itemCount': return ({required Object count}) => '${count} 个项目'; - case 'playlists.oneItem': return '1 个项目'; - case 'playlists.emptyPlaylist': return '此播放列表为空'; - case 'playlists.deleteConfirm': return '删除播放列表?'; - case 'playlists.deleteMessage': return ({required Object name}) => '确定要删除 "${name}" 吗?'; - case 'playlists.created': return '播放列表已创建'; - case 'playlists.updated': return '播放列表已更新'; - case 'playlists.deleted': return '播放列表已删除'; - case 'playlists.itemAdded': return '已添加到播放列表'; - case 'playlists.itemRemoved': return '已从播放列表中移除'; - case 'playlists.selectPlaylist': return '选择播放列表'; - case 'playlists.createNewPlaylist': return '创建新播放列表'; - case 'playlists.errorCreating': return '创建播放列表失败'; - case 'playlists.errorDeleting': return '删除播放列表失败'; - case 'playlists.errorLoading': return '加载播放列表失败'; - case 'playlists.errorAdding': return '添加到播放列表失败'; - case 'playlists.errorReordering': return '重新排序播放列表项目失败'; - case 'playlists.errorRemoving': return '从播放列表中移除失败'; - case 'playlists.playlist': return '播放列表'; - case 'collections.title': return '合集'; - case 'collections.collection': return '合集'; - case 'collections.empty': return '合集为空'; - case 'collections.noItems': return '此合集没有项目'; - case 'collections.unknownLibrarySection': return '无法删除:未知的媒体库分区'; - case 'collections.deleteCollection': return '删除合集'; - case 'collections.deleteConfirm': return ({required Object title}) => '确定要删除"${title}"吗?此操作无法撤销。'; - case 'collections.deleted': return '已删除合集'; - case 'collections.deleteFailed': return '删除合集失败'; - case 'collections.deleteFailedWithError': return ({required Object error}) => '删除合集失败:${error}'; - case 'collections.failedToLoadItems': return ({required Object error}) => '加载合集项目失败:${error}'; - case 'collections.addTo': return '添加到合集'; - case 'collections.selectCollection': return '选择合集'; - case 'collections.createNewCollection': return '创建新合集'; - case 'collections.collectionName': return '合集名称'; - case 'collections.enterCollectionName': return '输入合集名称'; - case 'collections.addedToCollection': return '已添加到合集'; - case 'collections.errorAddingToCollection': return '添加到合集失败'; - case 'collections.created': return '已创建合集'; - case 'collections.removeFromCollection': return '从合集移除'; - case 'collections.removeFromCollectionConfirm': return ({required Object title}) => '将“${title}”从此合集移除?'; - case 'collections.removedFromCollection': return '已从合集移除'; - case 'collections.removeFromCollectionFailed': return '从合集移除失败'; - case 'collections.removeFromCollectionError': return ({required Object error}) => '从合集移除时出错:${error}'; - default: return null; - } - } + dynamic _flatMapFunction(String path) { + switch (path) { + case 'app.title': + return 'Plezy'; + case 'app.loading': + return '加载中...'; + case 'auth.signInWithPlex': + return '使用 Plex 登录'; + case 'auth.showQRCode': + return '显示二维码'; + case 'auth.cancel': + return '取消'; + case 'auth.authenticate': + return '验证'; + case 'auth.retry': + return '重试'; + case 'auth.debugEnterToken': + return '调试:输入 Plex Token'; + case 'auth.plexTokenLabel': + return 'Plex 授权令牌 (Auth Token)'; + case 'auth.plexTokenHint': + return '输入你的 Plex.tv 令牌'; + case 'auth.authenticationTimeout': + return '验证超时。请重试。'; + case 'auth.scanQRCodeInstruction': + return '请使用已登录 Plex 的设备扫描此二维码进行验证。'; + case 'auth.waitingForAuth': + return '等待验证中...\n请在你的浏览器中完成登录。'; + case 'common.cancel': + return '取消'; + case 'common.save': + return '保存'; + case 'common.close': + return '关闭'; + case 'common.clear': + return '清除'; + case 'common.reset': + return '重置'; + case 'common.later': + return '稍后'; + case 'common.submit': + return '提交'; + case 'common.confirm': + return '确认'; + case 'common.retry': + return '重试'; + case 'common.playNow': + return '立即播放'; + case 'common.logout': + return '登出'; + case 'common.online': + return '在线'; + case 'common.offline': + return '离线'; + case 'common.owned': + return '拥有'; + case 'common.shared': + return '共享'; + case 'common.current': + return '当前'; + case 'common.unknown': + return '未知'; + case 'common.refresh': + return '刷新'; + case 'common.yes': + return '是'; + case 'common.no': + return '否'; + case 'common.server': + return '服务器'; + case 'common.delete': + return '删除'; + case 'common.shuffle': + return '随机播放'; + case 'common.addTo': + return '添加到...'; + case 'screens.licenses': + return '许可证'; + case 'screens.selectServer': + return '选择服务器'; + case 'screens.switchProfile': + return '切换配置文件'; + case 'screens.subtitleStyling': + return '字幕样式'; + case 'screens.search': + return '搜索'; + case 'screens.logs': + return '日志'; + case 'update.available': + return '有可用更新'; + case 'update.versionAvailable': + return ({required Object version}) => '版本 ${version} 已发布'; + case 'update.currentVersion': + return ({required Object version}) => '当前版本: ${version}'; + case 'update.skipVersion': + return '跳过此版本'; + case 'update.viewRelease': + return '查看发布详情'; + case 'update.latestVersion': + return '已安装的版本是可用的最新版本'; + case 'update.checkFailed': + return '无法检查更新'; + case 'settings.title': + return '设置'; + case 'settings.language': + return '语言'; + case 'settings.theme': + return '主题'; + case 'settings.appearance': + return '外观'; + case 'settings.videoPlayback': + return '视频播放'; + case 'settings.shufflePlay': + return '随机播放'; + case 'settings.advanced': + return '高级'; + case 'settings.useSeasonPostersDescription': + return '为剧集显示季海报而非剧集海报'; + case 'settings.showHeroSectionDescription': + return '在主屏幕上显示精选内容轮播区'; + case 'settings.secondsLabel': + return '秒'; + case 'settings.minutesLabel': + return '分钟'; + case 'settings.secondsShort': + return 's'; + case 'settings.minutesShort': + return 'm'; + case 'settings.durationHint': + return ({required Object min, required Object max}) => + '输入时长 (${min}-${max})'; + case 'settings.systemTheme': + return '系统'; + case 'settings.systemThemeDescription': + return '跟随系统设置'; + case 'settings.lightTheme': + return '浅色'; + case 'settings.darkTheme': + return '深色'; + case 'settings.libraryDensity': + return '媒体库密度'; + case 'settings.compact': + return '紧凑'; + case 'settings.compactDescription': + return '卡片更小,显示更多项目'; + case 'settings.normal': + return '标准'; + case 'settings.normalDescription': + return '默认尺寸'; + case 'settings.comfortable': + return '舒适'; + case 'settings.comfortableDescription': + return '卡片更大,显示更少项目'; + case 'settings.viewMode': + return '视图模式'; + case 'settings.gridView': + return '网格视图'; + case 'settings.gridViewDescription': + return '以网格布局显示项目'; + case 'settings.listView': + return '列表视图'; + case 'settings.listViewDescription': + return '以列表布局显示项目'; + case 'settings.useSeasonPosters': + return '使用季海报'; + case 'settings.showHeroSection': + return '显示主要精选区'; + case 'settings.hardwareDecoding': + return '硬件解码'; + case 'settings.hardwareDecodingDescription': + return '如果可用,使用硬件加速'; + case 'settings.bufferSize': + return '缓冲区大小'; + case 'settings.bufferSizeMB': + return ({required Object size}) => '${size}MB'; + case 'settings.subtitleStyling': + return '字幕样式'; + case 'settings.subtitleStylingDescription': + return '调整字幕外观'; + case 'settings.smallSkipDuration': + return '短跳过时长'; + case 'settings.largeSkipDuration': + return '长跳过时长'; + case 'settings.secondsUnit': + return ({required Object seconds}) => '${seconds} 秒'; + case 'settings.defaultSleepTimer': + return '默认睡眠定时器'; + case 'settings.minutesUnit': + return ({required Object minutes}) => '${minutes} 分钟'; + case 'settings.rememberTrackSelections': + return '记住每个剧集/电影的音轨选择'; + case 'settings.rememberTrackSelectionsDescription': + return '在播放过程中更改音轨时自动保存音频和字幕语言偏好'; + case 'settings.unwatchedOnly': + return '仅未观看'; + case 'settings.unwatchedOnlyDescription': + return '随机队列中仅包含未观看的剧集'; + case 'settings.shuffleOrderNavigation': + return '随机顺序导航'; + case 'settings.shuffleOrderNavigationDescription': + return '下一集/上一集按钮遵循随机播放顺序'; + case 'settings.loopShuffleQueue': + return '循环随机队列'; + case 'settings.loopShuffleQueueDescription': + return '在队列结束时重新开始'; + case 'settings.videoPlayerControls': + return '视频播放器控制'; + case 'settings.keyboardShortcuts': + return '键盘快捷键'; + case 'settings.keyboardShortcutsDescription': + return '自定义键盘快捷键'; + case 'settings.debugLogging': + return '调试日志'; + case 'settings.debugLoggingDescription': + return '启用详细日志记录以便故障排除'; + case 'settings.viewLogs': + return '查看日志'; + case 'settings.viewLogsDescription': + return '查看应用程序日志'; + case 'settings.clearCache': + return '清除缓存'; + case 'settings.clearCacheDescription': + return '这将清除所有缓存的图片和数据。清除缓存后,应用程序加载内容可能会变慢。'; + case 'settings.clearCacheSuccess': + return '缓存清除成功'; + case 'settings.resetSettings': + return '重置设置'; + case 'settings.resetSettingsDescription': + return '这会将所有设置重置为其默认值。此操作无法撤销。'; + case 'settings.resetSettingsSuccess': + return '设置重置成功'; + case 'settings.shortcutsReset': + return '快捷键已重置为默认值'; + case 'settings.about': + return '关于'; + case 'settings.aboutDescription': + return '应用程序信息和许可证'; + case 'settings.updates': + return '更新'; + case 'settings.updateAvailable': + return '有可用更新'; + case 'settings.checkForUpdates': + return '检查更新'; + case 'settings.validationErrorEnterNumber': + return '请输入一个有效的数字'; + case 'settings.validationErrorDuration': + return ({ + required Object min, + required Object max, + required Object unit, + }) => '时长必须介于 ${min} 和 ${max} ${unit} 之间'; + case 'settings.shortcutAlreadyAssigned': + return ({required Object action}) => '快捷键已被分配给 ${action}'; + case 'settings.shortcutUpdated': + return ({required Object action}) => '快捷键已为 ${action} 更新'; + case 'search.hint': + return '搜索电影、系列、音乐...'; + case 'search.tryDifferentTerm': + return '尝试不同的搜索词'; + case 'search.searchYourMedia': + return '搜索媒体'; + case 'search.enterTitleActorOrKeyword': + return '输入标题、演员或关键词'; + case 'hotkeys.setShortcutFor': + return ({required Object actionName}) => '为 ${actionName} 设置快捷键'; + case 'hotkeys.clearShortcut': + return '清除快捷键'; + case 'pinEntry.enterPin': + return '输入 PIN'; + case 'pinEntry.showPin': + return '显示 PIN'; + case 'pinEntry.hidePin': + return '隐藏 PIN'; + case 'fileInfo.title': + return '文件信息'; + case 'fileInfo.video': + return '视频'; + case 'fileInfo.audio': + return '音频'; + case 'fileInfo.file': + return '文件'; + case 'fileInfo.advanced': + return '高级'; + case 'fileInfo.codec': + return '编解码器'; + case 'fileInfo.resolution': + return '分辨率'; + case 'fileInfo.bitrate': + return '比特率'; + case 'fileInfo.frameRate': + return '帧率'; + case 'fileInfo.aspectRatio': + return '宽高比'; + case 'fileInfo.profile': + return '配置文件'; + case 'fileInfo.bitDepth': + return '位深度'; + case 'fileInfo.colorSpace': + return '色彩空间'; + case 'fileInfo.colorRange': + return '色彩范围'; + case 'fileInfo.colorPrimaries': + return '颜色原色'; + case 'fileInfo.chromaSubsampling': + return '色度子采样'; + case 'fileInfo.channels': + return '声道'; + case 'fileInfo.path': + return '路径'; + case 'fileInfo.size': + return '大小'; + case 'fileInfo.container': + return '容器'; + case 'fileInfo.duration': + return '时长'; + case 'fileInfo.optimizedForStreaming': + return '已优化用于流媒体'; + case 'fileInfo.has64bitOffsets': + return '64位偏移量'; + case 'mediaMenu.markAsWatched': + return '标记为已观看'; + case 'mediaMenu.markAsUnwatched': + return '标记为未观看'; + case 'mediaMenu.removeFromContinueWatching': + return '从继续观看中移除'; + case 'mediaMenu.goToSeries': + return '转到系列'; + case 'mediaMenu.goToSeason': + return '转到季'; + case 'mediaMenu.shufflePlay': + return '随机播放'; + case 'mediaMenu.fileInfo': + return '文件信息'; + case 'accessibility.mediaCardMovie': + return ({required Object title}) => '${title}, 电影'; + case 'accessibility.mediaCardShow': + return ({required Object title}) => '${title}, 电视剧'; + case 'accessibility.mediaCardEpisode': + return ({required Object title, required Object episodeInfo}) => + '${title}, ${episodeInfo}'; + case 'accessibility.mediaCardSeason': + return ({required Object title, required Object seasonInfo}) => + '${title}, ${seasonInfo}'; + case 'accessibility.mediaCardWatched': + return '已观看'; + case 'accessibility.mediaCardPartiallyWatched': + return ({required Object percent}) => '已观看 ${percent} 百分比'; + case 'accessibility.mediaCardUnwatched': + return '未观看'; + case 'accessibility.tapToPlay': + return '点击播放'; + case 'tooltips.shufflePlay': + return '随机播放'; + case 'tooltips.markAsWatched': + return '标记为已观看'; + case 'tooltips.markAsUnwatched': + return '标记为未观看'; + case 'videoControls.audioLabel': + return '音频'; + case 'videoControls.subtitlesLabel': + return '字幕'; + case 'videoControls.resetToZero': + return '重置为 0ms'; + case 'videoControls.addTime': + return ({required Object amount, required Object unit}) => + '+${amount}${unit}'; + case 'videoControls.minusTime': + return ({required Object amount, required Object unit}) => + '-${amount}${unit}'; + case 'videoControls.playsLater': + return ({required Object label}) => '${label} 播放较晚'; + case 'videoControls.playsEarlier': + return ({required Object label}) => '${label} 播放较早'; + case 'videoControls.noOffset': + return '无偏移'; + case 'videoControls.letterbox': + return '信箱模式(Letterbox)'; + case 'videoControls.fillScreen': + return '填充屏幕'; + case 'videoControls.stretch': + return '拉伸'; + case 'videoControls.lockRotation': + return '锁定旋转'; + case 'videoControls.unlockRotation': + return '解锁旋转'; + case 'videoControls.sleepTimer': + return '睡眠定时器'; + case 'videoControls.timerActive': + return '定时器已激活'; + case 'videoControls.playbackWillPauseIn': + return ({required Object duration}) => '播放将在 ${duration} 后暂停'; + case 'videoControls.sleepTimerCompleted': + return '睡眠定时器已完成 - 播放已暂停'; + case 'videoControls.playButton': + return '播放'; + case 'videoControls.pauseButton': + return '暂停'; + case 'videoControls.seekBackwardButton': + return ({required Object seconds}) => '后退 ${seconds} 秒'; + case 'videoControls.seekForwardButton': + return ({required Object seconds}) => '前进 ${seconds} 秒'; + case 'videoControls.previousButton': + return '上一集'; + case 'videoControls.nextButton': + return '下一集'; + case 'videoControls.previousChapterButton': + return '上一章节'; + case 'videoControls.nextChapterButton': + return '下一章节'; + case 'videoControls.volumeButton': + return '音量'; + case 'videoControls.muteButton': + return '静音'; + case 'videoControls.unmuteButton': + return '取消静音'; + case 'videoControls.settingsButton': + return '视频设置'; + case 'videoControls.audioTrackButton': + return '音轨'; + case 'videoControls.subtitlesButton': + return '字幕'; + case 'videoControls.chaptersButton': + return '章节'; + case 'videoControls.versionsButton': + return '视频版本'; + case 'videoControls.aspectRatioButton': + return '宽高比'; + case 'videoControls.fullscreenButton': + return '进入全屏'; + case 'videoControls.exitFullscreenButton': + return '退出全屏'; + case 'videoControls.rotationLockButton': + return '旋转锁定'; + case 'videoControls.timelineSlider': + return '视频时间轴'; + case 'videoControls.volumeSlider': + return '音量调节'; + case 'videoControls.backButton': + return '返回'; + case 'userStatus.admin': + return '管理员'; + case 'userStatus.restricted': + return '受限'; + case 'userStatus.protected': + return '受保护'; + case 'userStatus.current': + return '当前'; + case 'messages.markedAsWatched': + return '已标记为已观看'; + case 'messages.markedAsUnwatched': + return '已标记为未观看'; + case 'messages.removedFromContinueWatching': + return '已从继续观看中移除'; + case 'messages.errorLoading': + return ({required Object error}) => '错误: ${error}'; + case 'messages.fileInfoNotAvailable': + return '文件信息不可用'; + case 'messages.errorLoadingFileInfo': + return ({required Object error}) => '加载文件信息时出错: ${error}'; + case 'messages.errorLoadingSeries': + return '加载系列时出错'; + case 'messages.errorLoadingSeason': + return '加载季时出错'; + case 'messages.musicNotSupported': + return '尚不支持播放音乐'; + case 'messages.logsCleared': + return '日志已清除'; + case 'messages.logsCopied': + return '日志已复制到剪贴板'; + case 'messages.noLogsAvailable': + return '没有可用日志'; + case 'messages.libraryScanning': + return ({required Object title}) => '正在扫描 “${title}”...'; + case 'messages.libraryScanStarted': + return ({required Object title}) => '已开始扫描 “${title}” 媒体库'; + case 'messages.libraryScanFailed': + return ({required Object error}) => '无法扫描媒体库: ${error}'; + case 'messages.metadataRefreshing': + return ({required Object title}) => '正在刷新 “${title}” 的元数据...'; + case 'messages.metadataRefreshStarted': + return ({required Object title}) => '已开始刷新 “${title}” 的元数据'; + case 'messages.metadataRefreshFailed': + return ({required Object error}) => '无法刷新元数据: ${error}'; + case 'messages.noPlexToken': + return '未找到 Plex 令牌。请重新登录。'; + case 'messages.logoutConfirm': + return '你确定要登出吗?'; + case 'messages.noSeasonsFound': + return '未找到季'; + case 'messages.noEpisodesFound': + return '在第一季中未找到剧集'; + case 'messages.noEpisodesFoundGeneral': + return '未找到剧集'; + case 'messages.noResultsFound': + return '未找到结果'; + case 'messages.sleepTimerSet': + return ({required Object label}) => '睡眠定时器已设置为 ${label}'; + case 'messages.failedToSwitchProfile': + return ({required Object displayName}) => '无法切换到 ${displayName}'; + case 'messages.noItemsAvailable': + return '没有可用的项目'; + case 'messages.failedToCreatePlayQueue': + return '创建播放队列失败'; + case 'messages.failedToCreatePlayQueueNoItems': + return '创建播放队列失败 - 没有项目'; + case 'messages.failedPlayback': + return ({required Object action, required Object error}) => + '无法${action}: ${error}'; + case 'profile.noUsersAvailable': + return '没有可用用户'; + case 'subtitlingStyling.stylingOptions': + return '样式选项'; + case 'subtitlingStyling.fontSize': + return '字号'; + case 'subtitlingStyling.textColor': + return '文本颜色'; + case 'subtitlingStyling.borderSize': + return '边框大小'; + case 'subtitlingStyling.borderColor': + return '边框颜色'; + case 'subtitlingStyling.backgroundOpacity': + return '背景不透明度'; + case 'subtitlingStyling.backgroundColor': + return '背景颜色'; + case 'dialog.confirmAction': + return '确认操作'; + case 'dialog.areYouSure': + return '确定要执行此操作吗?'; + case 'dialog.cancel': + return '取消'; + case 'dialog.playNow': + return '立即播放'; + case 'discover.title': + return '发现'; + case 'discover.switchProfile': + return '切换配置文件'; + case 'discover.switchServer': + return '切换服务器'; + case 'discover.logout': + return '登出'; + case 'discover.noContentAvailable': + return '没有可用内容'; + case 'discover.addMediaToLibraries': + return '请向你的媒体库添加一些媒体'; + case 'discover.continueWatching': + return '继续观看'; + case 'discover.recentlyAdded': + return '最近添加'; + case 'discover.play': + return '播放'; + case 'discover.resume': + return '继续'; + case 'discover.playEpisode': + return ({required Object season, required Object episode}) => + '播放 S${season}, E${episode}'; + case 'discover.resumeEpisode': + return ({required Object season, required Object episode}) => + '继续 S${season}, E${episode}'; + case 'discover.pause': + return '暂停'; + case 'discover.overview': + return '概述'; + case 'discover.cast': + return '演员表'; + case 'discover.seasons': + return '季数'; + case 'discover.studio': + return '制作公司'; + case 'discover.rating': + return '年龄分级'; + case 'discover.watched': + return '已观看'; + case 'discover.episodeCount': + return ({required Object count}) => '${count} 集'; + case 'discover.watchedProgress': + return ({required Object watched, required Object total}) => + '已观看 ${watched}/${total} 集'; + case 'discover.movie': + return '电影'; + case 'discover.tvShow': + return '电视剧'; + case 'discover.minutesLeft': + return ({required Object minutes}) => '剩余 ${minutes} 分钟'; + case 'errors.searchFailed': + return ({required Object error}) => '搜索失败: ${error}'; + case 'errors.connectionTimeout': + return ({required Object context}) => '加载 ${context} 时连接超时'; + case 'errors.connectionFailed': + return '无法连接到 Plex 服务器'; + case 'errors.failedToLoad': + return ({required Object context, required Object error}) => + '无法加载 ${context}: ${error}'; + case 'errors.noClientAvailable': + return '没有可用客户端'; + case 'errors.authenticationFailed': + return ({required Object error}) => '验证失败: ${error}'; + case 'errors.couldNotLaunchUrl': + return '无法打开授权 URL'; + case 'errors.pleaseEnterToken': + return '请输入一个令牌'; + case 'errors.invalidToken': + return '令牌无效'; + case 'errors.failedToVerifyToken': + return ({required Object error}) => '无法验证令牌: ${error}'; + case 'errors.failedToSwitchProfile': + return ({required Object displayName}) => '无法切换到 ${displayName}'; + case 'errors.connectionFailedGeneric': + return '连接失败'; + case 'libraries.title': + return '媒体库'; + case 'libraries.scanLibraryFiles': + return '扫描媒体库文件'; + case 'libraries.scanLibrary': + return '扫描媒体库'; + case 'libraries.analyze': + return '分析'; + case 'libraries.analyzeLibrary': + return '分析媒体库'; + case 'libraries.refreshMetadata': + return '刷新元数据'; + case 'libraries.emptyTrash': + return '清空回收站'; + case 'libraries.emptyingTrash': + return ({required Object title}) => '正在清空 “${title}” 的回收站...'; + case 'libraries.trashEmptied': + return ({required Object title}) => '已清空 “${title}” 的回收站'; + case 'libraries.failedToEmptyTrash': + return ({required Object error}) => '无法清空回收站: ${error}'; + case 'libraries.analyzing': + return ({required Object title}) => '正在分析 “${title}”...'; + case 'libraries.analysisStarted': + return ({required Object title}) => '已开始分析 “${title}”'; + case 'libraries.failedToAnalyze': + return ({required Object error}) => '无法分析媒体库: ${error}'; + case 'libraries.noLibrariesFound': + return '未找到媒体库'; + case 'libraries.thisLibraryIsEmpty': + return '此媒体库为空'; + case 'libraries.all': + return '全部'; + case 'libraries.clearAll': + return '全部清除'; + case 'libraries.scanLibraryConfirm': + return ({required Object title}) => '确定要扫描 “${title}” 吗?'; + case 'libraries.analyzeLibraryConfirm': + return ({required Object title}) => '确定要分析 “${title}” 吗?'; + case 'libraries.refreshMetadataConfirm': + return ({required Object title}) => '确定要刷新 “${title}” 的元数据吗?'; + case 'libraries.emptyTrashConfirm': + return ({required Object title}) => '确定要清空 “${title}” 的回收站吗?'; + case 'libraries.manageLibraries': + return '管理媒体库'; + case 'libraries.sort': + return '排序'; + case 'libraries.sortBy': + return '排序依据'; + case 'libraries.filters': + return '筛选器'; + case 'libraries.loadingLibraryWithCount': + return ({required Object count}) => '正在加载媒体库... (已加载 ${count} 个项目)'; + case 'libraries.confirmActionMessage': + return '确定要执行此操作吗?'; + case 'libraries.showLibrary': + return '显示媒体库'; + case 'libraries.hideLibrary': + return '隐藏媒体库'; + case 'libraries.libraryOptions': + return '媒体库选项'; + case 'libraries.content': + return '媒体库内容'; + case 'libraries.selectLibrary': + return '选择媒体库'; + case 'libraries.filtersWithCount': + return ({required Object count}) => '筛选器(${count})'; + case 'libraries.noRecommendations': + return '暂无推荐'; + case 'libraries.noCollections': + return '此媒体库中没有合集'; + case 'libraries.noFoldersFound': + return '未找到文件夹'; + case 'libraries.folders': + return '文件夹'; + case 'libraries.tabs.recommended': + return '推荐'; + case 'libraries.tabs.browse': + return '浏览'; + case 'libraries.tabs.collections': + return '合集'; + case 'libraries.tabs.playlists': + return '播放列表'; + case 'libraries.groupings.all': + return '全部'; + case 'libraries.groupings.movies': + return '电影'; + case 'libraries.groupings.shows': + return '剧集'; + case 'libraries.groupings.seasons': + return '季'; + case 'libraries.groupings.episodes': + return '集'; + case 'libraries.groupings.folders': + return '文件夹'; + case 'about.title': + return '关于'; + case 'about.openSourceLicenses': + return '开源许可证'; + case 'about.versionLabel': + return ({required Object version}) => '版本 ${version}'; + case 'about.appDescription': + return '一款精美的 Flutter Plex 客户端'; + case 'about.viewLicensesDescription': + return '查看第三方库的许可证'; + case 'serverSelection.connectingToServer': + return '正在连接服务器...'; + case 'serverSelection.connectingToAllServers': + return '正在连接所有服务器...'; + case 'serverSelection.connectToAllServers': + return '连接所有服务器'; + case 'serverSelection.allServerConnectionsFailed': + return '无法连接到任何服务器。请检查你的网络并重试。'; + case 'serverSelection.foundServers': + return ({required Object count}) => '找到 ${count} 个服务器'; + case 'serverSelection.orSelectIndividually': + return '或单独选择一个服务器:'; + case 'serverSelection.serverDebugCopied': + return '服务器调试数据已复制到剪贴板'; + case 'serverSelection.copyDebugData': + return '复制调试数据'; + case 'serverSelection.noServersFound': + return '未找到服务器'; + case 'serverSelection.malformedServerData': + return ({required Object count}) => + '找到 ${count} 个数据格式错误的服务器。没有可用的有效服务器。'; + case 'serverSelection.incompleteServerInfo': + return '某些服务器信息不完整,已被跳过。请检查你的 Plex.tv 账户。'; + case 'serverSelection.incompleteConnectionInfo': + return '服务器连接信息不完整。请重试。'; + case 'serverSelection.malformedServerInfo': + return ({required Object message}) => '服务器信息格式错误: ${message}'; + case 'serverSelection.networkConnectionFailed': + return '网络连接失败。请检查你的网络连接并重试。'; + case 'serverSelection.authenticationFailed': + return '验证失败。请重新登录。'; + case 'serverSelection.plexServiceUnavailable': + return 'Plex 服务不可用。请稍后重试。'; + case 'serverSelection.failedToLoadServers': + return ({required Object error}) => '无法加载服务器: ${error}'; + case 'hubDetail.title': + return '标题'; + case 'hubDetail.releaseYear': + return '发行年份'; + case 'hubDetail.dateAdded': + return '添加日期'; + case 'hubDetail.rating': + return '评分'; + case 'hubDetail.noItemsFound': + return '未找到项目'; + case 'logs.title': + return '日志'; + case 'logs.clearLogs': + return '清除日志'; + case 'logs.copyLogs': + return '复制日志'; + case 'logs.exportLogs': + return '导出日志'; + case 'logs.noLogsToShow': + return '没有可显示的日志'; + case 'logs.error': + return '错误:'; + case 'logs.stackTrace': + return '堆栈跟踪 (Stack Trace):'; + case 'licenses.relatedPackages': + return '相关软件包'; + case 'licenses.license': + return '许可证'; + case 'licenses.licenseNumber': + return ({required Object number}) => '许可证 ${number}'; + case 'licenses.licensesCount': + return ({required Object count}) => '${count} 个许可证'; + case 'navigation.home': + return '主页'; + case 'navigation.search': + return '搜索'; + case 'navigation.libraries': + return '媒体库'; + case 'navigation.settings': + return '设置'; + case 'playlists.title': + return '播放列表'; + case 'playlists.noPlaylists': + return '未找到播放列表'; + case 'playlists.create': + return '创建播放列表'; + case 'playlists.newPlaylist': + return '新播放列表'; + case 'playlists.playlistName': + return '播放列表名称'; + case 'playlists.enterPlaylistName': + return '输入播放列表名称'; + case 'playlists.edit': + return '编辑播放列表'; + case 'playlists.delete': + return '删除播放列表'; + case 'playlists.addTo': + return '添加到播放列表'; + case 'playlists.addItems': + return '添加项目'; + case 'playlists.removeItem': + return '从播放列表中移除'; + case 'playlists.clearPlaylist': + return '清空播放列表'; + case 'playlists.playAll': + return '全部播放'; + case 'playlists.shuffle': + return '随机播放'; + case 'playlists.smartPlaylist': + return '智能播放列表'; + case 'playlists.regularPlaylist': + return '普通播放列表'; + case 'playlists.itemCount': + return ({required Object count}) => '${count} 个项目'; + case 'playlists.oneItem': + return '1 个项目'; + case 'playlists.emptyPlaylist': + return '此播放列表为空'; + case 'playlists.deleteConfirm': + return '删除播放列表?'; + case 'playlists.deleteMessage': + return ({required Object name}) => '确定要删除 "${name}" 吗?'; + case 'playlists.created': + return '播放列表已创建'; + case 'playlists.updated': + return '播放列表已更新'; + case 'playlists.deleted': + return '播放列表已删除'; + case 'playlists.itemAdded': + return '已添加到播放列表'; + case 'playlists.itemRemoved': + return '已从播放列表中移除'; + case 'playlists.selectPlaylist': + return '选择播放列表'; + case 'playlists.createNewPlaylist': + return '创建新播放列表'; + case 'playlists.errorCreating': + return '创建播放列表失败'; + case 'playlists.errorDeleting': + return '删除播放列表失败'; + case 'playlists.errorLoading': + return '加载播放列表失败'; + case 'playlists.errorAdding': + return '添加到播放列表失败'; + case 'playlists.errorReordering': + return '重新排序播放列表项目失败'; + case 'playlists.errorRemoving': + return '从播放列表中移除失败'; + case 'playlists.playlist': + return '播放列表'; + case 'collections.title': + return '合集'; + case 'collections.collection': + return '合集'; + case 'collections.empty': + return '合集为空'; + case 'collections.noItems': + return '此合集没有项目'; + case 'collections.unknownLibrarySection': + return '无法删除:未知的媒体库分区'; + case 'collections.deleteCollection': + return '删除合集'; + case 'collections.deleteConfirm': + return ({required Object title}) => '确定要删除"${title}"吗?此操作无法撤销。'; + case 'collections.deleted': + return '已删除合集'; + case 'collections.deleteFailed': + return '删除合集失败'; + case 'collections.deleteFailedWithError': + return ({required Object error}) => '删除合集失败:${error}'; + case 'collections.failedToLoadItems': + return ({required Object error}) => '加载合集项目失败:${error}'; + case 'collections.addTo': + return '添加到合集'; + case 'collections.selectCollection': + return '选择合集'; + case 'collections.createNewCollection': + return '创建新合集'; + case 'collections.collectionName': + return '合集名称'; + case 'collections.enterCollectionName': + return '输入合集名称'; + case 'collections.addedToCollection': + return '已添加到合集'; + case 'collections.errorAddingToCollection': + return '添加到合集失败'; + case 'collections.created': + return '已创建合集'; + case 'collections.removeFromCollection': + return '从合集移除'; + case 'collections.removeFromCollectionConfirm': + return ({required Object title}) => '将“${title}”从此合集移除?'; + case 'collections.removedFromCollection': + return '已从合集移除'; + case 'collections.removeFromCollectionFailed': + return '从合集移除失败'; + case 'collections.removeFromCollectionError': + return ({required Object error}) => '从合集移除时出错:${error}'; + default: + return null; + } + } } diff --git a/lib/main.dart b/lib/main.dart index e46d5840..9d86ac53 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -92,7 +92,8 @@ class MainApp extends StatelessWidget { ChangeNotifierProvider(create: (context) => PlexClientProvider()), // New multi-server providers ChangeNotifierProvider( - create: (context) => MultiServerProvider(serverManager, aggregationService), + create: (context) => + MultiServerProvider(serverManager, aggregationService), ), ChangeNotifierProvider(create: (context) => ServerStateProvider()), // Existing providers @@ -268,21 +269,22 @@ class _SetupScreenState extends State { final clientId = storage.getClientIdentifier(); // Connect to all servers in parallel - final connectedCount = await multiServerProvider.serverManager.connectToAllServers( - servers, - clientIdentifier: clientId, - timeout: const Duration(seconds: 10), - onServerConnected: (serverId, client) { - // Set first connected client in legacy provider for backward compatibility - final legacyProvider = Provider.of( - context, - listen: false, + final connectedCount = await multiServerProvider.serverManager + .connectToAllServers( + servers, + clientIdentifier: clientId, + timeout: const Duration(seconds: 10), + onServerConnected: (serverId, client) { + // Set first connected client in legacy provider for backward compatibility + final legacyProvider = Provider.of( + context, + listen: false, + ); + if (legacyProvider.client == null) { + legacyProvider.setClient(client); + } + }, ); - if (legacyProvider.client == null) { - legacyProvider.setClient(client); - } - }, - ); if (connectedCount > 0) { // At least one server connected successfully @@ -294,7 +296,8 @@ class _SetupScreenState extends State { // Navigate to main screen // Get first connected client for backward compatibility - final firstClient = multiServerProvider.serverManager.onlineClients.values.first; + final firstClient = + multiServerProvider.serverManager.onlineClients.values.first; Navigator.pushReplacement( context, diff --git a/lib/mixins/library_tab_state.dart b/lib/mixins/library_tab_state.dart index 426e1486..4c7a3fea 100644 --- a/lib/mixins/library_tab_state.dart +++ b/lib/mixins/library_tab_state.dart @@ -1,63 +1,15 @@ import 'package:flutter/material.dart'; +import '../client/plex_client.dart'; import '../models/plex_library.dart'; -import '../models/plex_metadata.dart'; +import '../utils/provider_extensions.dart'; -/// Mixin providing common state management for library tab screens -/// Standardizes loading, error handling, and lifecycle management +/// Mixin providing common functionality for library tab screens +/// Provides server-specific client resolution for multi-server support mixin LibraryTabStateMixin on State { - /// The list of items to display - List get items; - set items(List value); - - /// Whether data is currently loading - bool get isLoading; - set isLoading(bool value); - - /// Error message if loading failed - String? get errorMessage; - set errorMessage(String? value); - /// The library being displayed PlexLibrary get library; - /// Load or reload the content - Future loadContent(); - - /// Common lifecycle: reload if library changed - @mustCallSuper - void didUpdateLibrary(PlexLibrary oldLibrary) { - if (oldLibrary.key != library.key) { - loadContent(); - } - } - - /// Helper to set loading state - void setLoadingState(bool loading) { - if (mounted) { - setState(() { - isLoading = loading; - }); - } - } - - /// Helper to set error state - void setErrorState(String? error) { - if (mounted) { - setState(() { - errorMessage = error; - isLoading = false; - }); - } - } - - /// Helper to set success state with items - void setSuccessState(List newItems) { - if (mounted) { - setState(() { - items = newItems; - isLoading = false; - errorMessage = null; - }); - } - } + /// Get the correct PlexClient for this library's server + /// Throws an exception if no client is available + PlexClient getClientForLibrary() => context.getClientForLibrary(library); } diff --git a/lib/models/mixins/multi_server_fields.dart b/lib/models/mixins/multi_server_fields.dart new file mode 100644 index 00000000..1ba3e4a8 --- /dev/null +++ b/lib/models/mixins/multi_server_fields.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// Mixin that provides multi-server support fields for models. +/// +/// This mixin adds serverId and serverName fields that are excluded from +/// JSON serialization but can be used to track which server an item belongs to. +mixin MultiServerFields { + /// Server machine identifier (not from API) + @JsonKey(includeFromJson: false, includeToJson: false) + String? get serverId; + + /// Server display name (not from API) + @JsonKey(includeFromJson: false, includeToJson: false) + String? get serverName; +} diff --git a/lib/models/plex_library.dart b/lib/models/plex_library.dart index 632a1c3c..72bbd8bc 100644 --- a/lib/models/plex_library.dart +++ b/lib/models/plex_library.dart @@ -1,9 +1,11 @@ import 'package:json_annotation/json_annotation.dart'; +import 'mixins/multi_server_fields.dart'; + part 'plex_library.g.dart'; @JsonSerializable() -class PlexLibrary { +class PlexLibrary with MultiServerFields { final String key; final String title; final String type; @@ -15,11 +17,13 @@ class PlexLibrary { final int? createdAt; final int? hidden; - // Multi-server support fields + // Multi-server support fields (from MultiServerFields mixin) + @override @JsonKey(includeFromJson: false, includeToJson: false) - final String? serverId; // Server machine identifier (not from API) + final String? serverId; + @override @JsonKey(includeFromJson: false, includeToJson: false) - final String? serverName; // Server display name (not from API) + final String? serverName; /// Global unique identifier across all servers (serverId:key) String get globalKey => serverId != null ? '$serverId:$key' : key; diff --git a/lib/models/plex_metadata.dart b/lib/models/plex_metadata.dart index 98adfc41..0dbb1e49 100644 --- a/lib/models/plex_metadata.dart +++ b/lib/models/plex_metadata.dart @@ -1,11 +1,12 @@ import 'package:json_annotation/json_annotation.dart'; +import 'mixins/multi_server_fields.dart'; import 'plex_role.dart'; part 'plex_metadata.g.dart'; @JsonSerializable() -class PlexMetadata { +class PlexMetadata with MultiServerFields { final String ratingKey; final String key; final String? guid; @@ -45,11 +46,13 @@ class PlexMetadata { final int? playQueueItemID; // Play queue item ID (unique even for duplicates) final int? librarySectionID; // Library section ID this item belongs to - // Multi-server support fields + // Multi-server support fields (from MultiServerFields mixin) + @override @JsonKey(includeFromJson: false, includeToJson: false) - final String? serverId; // Server machine identifier (not from API) + final String? serverId; + @override @JsonKey(includeFromJson: false, includeToJson: false) - final String? serverName; // Server display name (not from API) + final String? serverName; // Transient field for clear logo (extracted from Image array) String? _clearLogo; diff --git a/lib/models/plex_playlist.dart b/lib/models/plex_playlist.dart index 0c352f1b..e1aaaa18 100644 --- a/lib/models/plex_playlist.dart +++ b/lib/models/plex_playlist.dart @@ -1,9 +1,11 @@ import 'package:json_annotation/json_annotation.dart'; +import 'mixins/multi_server_fields.dart'; + part 'plex_playlist.g.dart'; @JsonSerializable() -class PlexPlaylist { +class PlexPlaylist with MultiServerFields { final String ratingKey; final String key; final String type; // "playlist" @@ -22,9 +24,11 @@ class PlexPlaylist { final String? guid; final String? thumb; - // Multi-server support: Track which server this playlist belongs to + // Multi-server support fields (from MultiServerFields mixin) + @override @JsonKey(includeFromJson: false, includeToJson: false) final String? serverId; + @override @JsonKey(includeFromJson: false, includeToJson: false) final String? serverName; diff --git a/lib/providers/playback_state_provider.dart b/lib/providers/playback_state_provider.dart index cafb409c..5336634c 100644 --- a/lib/providers/playback_state_provider.dart +++ b/lib/providers/playback_state_provider.dart @@ -4,10 +4,10 @@ import '../models/play_queue_response.dart'; import '../client/plex_client.dart'; /// Playback mode types +/// +/// All playback now uses Plex play queues. enum PlaybackMode { - none, // No active playback queue - sequential, // Normal episode-to-episode playback (uses Plex API) - playQueue, // Play queue-based playback (playlists, collections, shuffle) + playQueue, // Play queue-based playback (sequential, shuffle, playlists, collections) } /// Result of trying to locate the current queue index. @@ -38,13 +38,13 @@ class PlaybackStateProvider with ChangeNotifier { // Legacy state for backward compatibility String? _contextKey; // The show/season/playlist ratingKey for this session - PlaybackMode _playbackMode = PlaybackMode.none; + PlaybackMode? _playbackMode; // Client reference for loading more items PlexClient? _client; - /// Current playback mode - PlaybackMode get playbackMode => _playbackMode; + /// Current playback mode (null if no queue active) + PlaybackMode? get playbackMode => _playbackMode; /// Whether shuffle mode is currently active bool get isShuffleActive => _playQueueShuffled; @@ -92,8 +92,10 @@ class PlaybackStateProvider with ChangeNotifier { /// Call this after creating a play queue via the API Future setPlaybackFromPlayQueue( PlayQueueResponse playQueue, - String? contextKey, - ) async { + String? contextKey, { + String? serverId, + String? serverName, + }) async { _playQueueId = playQueue.playQueueID; // Use size or items length as fallback if totalCount is null _playQueueTotalCount = @@ -102,31 +104,12 @@ class PlaybackStateProvider with ChangeNotifier { (playQueue.items?.length ?? 0); _playQueueShuffled = playQueue.playQueueShuffled; _currentPlayQueueItemID = playQueue.playQueueSelectedItemID; - _loadedItems = playQueue.items ?? []; - _contextKey = contextKey; - _playbackMode = PlaybackMode.playQueue; - notifyListeners(); - } - /// Legacy method for backward compatibility with shuffle play - /// This now creates a play queue on the server - @Deprecated('Use createPlayQueueFromUri instead') - void setShuffleQueue(List episodes, String contextKey) { - // This is kept for backward compatibility but should not be used - // New code should use the play queue API - _loadedItems = List.from(episodes); - _contextKey = contextKey; - _playbackMode = PlaybackMode.playQueue; - notifyListeners(); - } + // Preserve serverId/serverName on all items + _loadedItems = (playQueue.items ?? []).map((item) { + return item.copyWith(serverId: serverId, serverName: serverName); + }).toList(); - /// Legacy method for backward compatibility with playlist playback - /// This now creates a play queue on the server - @Deprecated('Use createPlayQueueFromUri instead') - void setPlaybackQueue(List items, String contextKey) { - // This is kept for backward compatibility but should not be used - // New code should use the play queue API - _loadedItems = List.from(items); _contextKey = contextKey; _playbackMode = PlaybackMode.playQueue; notifyListeners(); @@ -154,14 +137,15 @@ class PlaybackStateProvider with ChangeNotifier { if (response != null && response.items != null) { // Preserve serverId from existing items - final serverId = _loadedItems.isNotEmpty ? _loadedItems.first.serverId : null; - final serverName = _loadedItems.isNotEmpty ? _loadedItems.first.serverName : null; + final serverId = _loadedItems.isNotEmpty + ? _loadedItems.first.serverId + : null; + final serverName = _loadedItems.isNotEmpty + ? _loadedItems.first.serverName + : null; _loadedItems = response.items!.map((item) { - return item.copyWith( - serverId: serverId, - serverName: serverName, - ); + return item.copyWith(serverId: serverId, serverName: serverName); }).toList(); // Use size or items length as fallback if totalCount is null _playQueueTotalCount = @@ -255,14 +239,15 @@ class PlaybackStateProvider with ChangeNotifier { response.items != null && response.items!.isNotEmpty) { // Preserve serverId when looping - final serverId = _loadedItems.isNotEmpty ? _loadedItems.first.serverId : null; - final serverName = _loadedItems.isNotEmpty ? _loadedItems.first.serverName : null; + final serverId = _loadedItems.isNotEmpty + ? _loadedItems.first.serverId + : null; + final serverName = _loadedItems.isNotEmpty + ? _loadedItems.first.serverName + : null; _loadedItems = response.items!.map((item) { - return item.copyWith( - serverId: serverId, - serverName: serverName, - ); + return item.copyWith(serverId: serverId, serverName: serverName); }).toList(); final firstItem = _loadedItems.first; // Don't update _currentPlayQueueItemID here - let setCurrentItem do it when playback starts @@ -336,7 +321,7 @@ class PlaybackStateProvider with ChangeNotifier { _currentPlayQueueItemID = null; _loadedItems = []; _contextKey = null; - _playbackMode = PlaybackMode.none; + _playbackMode = null; notifyListeners(); } } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 61e98b6a..cd9ffa3c 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -7,9 +7,6 @@ class SettingsProvider extends ChangeNotifier { ViewMode _viewMode = ViewMode.grid; bool _useSeasonPoster = false; bool _showHeroSection = true; - bool _shuffleUnwatchedOnly = true; - bool _shuffleOrderNavigation = true; - bool _shuffleLoopQueue = false; SettingsProvider() { _initializeSettings(); @@ -21,9 +18,6 @@ class SettingsProvider extends ChangeNotifier { _viewMode = _settingsService.getViewMode(); _useSeasonPoster = _settingsService.getUseSeasonPoster(); _showHeroSection = _settingsService.getShowHeroSection(); - _shuffleUnwatchedOnly = _settingsService.getShuffleUnwatchedOnly(); - _shuffleOrderNavigation = _settingsService.getShuffleOrderNavigation(); - _shuffleLoopQueue = _settingsService.getShuffleLoopQueue(); notifyListeners(); } @@ -31,9 +25,6 @@ class SettingsProvider extends ChangeNotifier { ViewMode get viewMode => _viewMode; bool get useSeasonPoster => _useSeasonPoster; bool get showHeroSection => _showHeroSection; - bool get shuffleUnwatchedOnly => _shuffleUnwatchedOnly; - bool get shuffleOrderNavigation => _shuffleOrderNavigation; - bool get shuffleLoopQueue => _shuffleLoopQueue; Future setLibraryDensity(LibraryDensity density) async { if (_libraryDensity != density) { @@ -67,30 +58,6 @@ class SettingsProvider extends ChangeNotifier { } } - Future setShuffleUnwatchedOnly(bool value) async { - if (_shuffleUnwatchedOnly != value) { - _shuffleUnwatchedOnly = value; - await _settingsService.setShuffleUnwatchedOnly(value); - notifyListeners(); - } - } - - Future setShuffleOrderNavigation(bool value) async { - if (_shuffleOrderNavigation != value) { - _shuffleOrderNavigation = value; - await _settingsService.setShuffleOrderNavigation(value); - notifyListeners(); - } - } - - Future setShuffleLoopQueue(bool value) async { - if (_shuffleLoopQueue != value) { - _shuffleLoopQueue = value; - await _settingsService.setShuffleLoopQueue(value); - notifyListeners(); - } - } - String get libraryDensityDisplayName { switch (_libraryDensity) { case LibraryDensity.compact: diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index 9d282270..e24543d9 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -72,7 +72,8 @@ class _AuthScreenState extends State { // Connect to all servers if (!mounted) return; final multiServerProvider = context.read(); - final connectedCount = await multiServerProvider.serverManager.connectToAllServers(servers); + final connectedCount = await multiServerProvider.serverManager + .connectToAllServers(servers); if (connectedCount == 0) { setState(() { @@ -84,7 +85,8 @@ class _AuthScreenState extends State { // Get the first connected client for backward compatibility if (!mounted) return; - final firstClient = multiServerProvider.serverManager.onlineClients.values.first; + final firstClient = + multiServerProvider.serverManager.onlineClients.values.first; // Set it as the legacy client final plexClientProvider = context.read(); diff --git a/lib/screens/base_media_list_detail_screen.dart b/lib/screens/base_media_list_detail_screen.dart index 4fc3f7de..2859dd0d 100644 --- a/lib/screens/base_media_list_detail_screen.dart +++ b/lib/screens/base_media_list_detail_screen.dart @@ -1,14 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import '../client/plex_client.dart'; import '../models/plex_metadata.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; import '../utils/provider_extensions.dart'; import '../utils/collection_playlist_play_helper.dart'; import '../utils/app_logger.dart'; import '../mixins/refreshable.dart'; import '../mixins/item_updatable.dart'; +import '../i18n/strings.g.dart'; /// Abstract base class for screens displaying media lists (collections/playlists) /// Provides common state management and playback functionality @@ -32,6 +30,9 @@ abstract class BaseMediaListDetailScreen /// Message to show when list is empty String get emptyMessage; + /// Optional icon to show when list is empty + IconData? get emptyIcon => null; + /// Get the correct PlexClient for this media item's server PlexClient _getClientForMediaItem() { // Try to get serverId from the media item @@ -40,9 +41,8 @@ abstract class BaseMediaListDetailScreen // Check if mediaItem has serverId property if (mediaItem is PlexMetadata) { serverId = (mediaItem as PlexMetadata).serverId; - } else if (mediaItem != null && - mediaItem.runtimeType.toString().contains('PlexPlaylist')) { - // For playlists, use reflection-like access + } else if (mediaItem != null) { + // For playlists or other types, use dynamic access try { final dynamic item = mediaItem; serverId = item.serverId as String?; @@ -51,20 +51,7 @@ abstract class BaseMediaListDetailScreen } } - if (serverId == null) { - appLogger.w('Media item has no serverId, using legacy client'); - return context.read().client!; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client!; - } - - return client; + return context.getClientForServer(serverId); } @override @@ -119,4 +106,159 @@ abstract class BaseMediaListDetailScreen void refresh() { loadItems(); } + + /// Build common error/loading/empty state slivers + /// Returns a list of slivers to display based on current state + List buildStateSlivers() { + if (errorMessage != null) { + return [ + SliverFillRemaining( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.error_outline, size: 48, color: Colors.red), + const SizedBox(height: 16), + Text(errorMessage!), + const SizedBox(height: 16), + ElevatedButton( + onPressed: loadItems, + child: Text(t.common.retry), + ), + ], + ), + ), + ), + ]; + } + + if (items.isEmpty && isLoading) { + return [ + const SliverFillRemaining( + child: Center(child: CircularProgressIndicator()), + ), + ]; + } + + if (items.isEmpty) { + final icon = emptyIcon; + return [ + SliverFillRemaining( + child: Center( + child: icon != null + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, size: 64, color: Colors.grey), + const SizedBox(height: 16), + Text( + emptyMessage, + style: const TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + ], + ) + : Text(emptyMessage), + ), + ), + ]; + } + + return []; + } + + /// Build standard app bar actions (play, shuffle, delete) + /// Subclasses can override to customize actions + List buildAppBarActions({ + VoidCallback? onDelete, + String? deleteTooltip, + Color? deleteColor, + bool showDelete = true, + }) { + return [ + // Play button + if (items.isNotEmpty) + IconButton( + icon: const Icon(Icons.play_arrow), + tooltip: t.discover.play, + onPressed: playItems, + ), + // Shuffle button + if (items.isNotEmpty) + IconButton( + icon: const Icon(Icons.shuffle), + tooltip: t.common.shuffle, + onPressed: shufflePlayItems, + ), + // Delete button + if (showDelete && onDelete != null) + IconButton( + icon: const Icon(Icons.delete), + tooltip: deleteTooltip ?? t.common.delete, + onPressed: onDelete, + color: deleteColor ?? Colors.red, + ), + ]; + } +} + +/// Mixin that provides standard loadItems implementation for media lists +/// Handles the common pattern of fetching, tagging, and setting items +mixin StandardItemLoader + on BaseMediaListDetailScreen { + /// Fetch items from the API (must be implemented by subclass) + Future> fetchItems(); + + /// Get error message for failed load (can be overridden) + String getLoadErrorMessage(Object error) { + return 'Failed to load items: ${error.toString()}'; + } + + /// Get log message for successful load (can be overridden) + String getLoadSuccessMessage(int itemCount) { + return 'Loaded $itemCount items'; + } + + @override + Future loadItems() async { + if (mounted) { + setState(() { + isLoading = true; + errorMessage = null; + }); + } + + try { + final newItems = await fetchItems(); + + // Tag items with server info for correct client resolution + final serverId = (mediaItem as dynamic).serverId as String?; + final serverName = (mediaItem as dynamic).serverName as String?; + + final taggedItems = newItems + .map( + (item) => item.copyWith(serverId: serverId, serverName: serverName), + ) + .toList(); + + if (mounted) { + setState(() { + items = taggedItems; + isLoading = false; + }); + } + + appLogger.d(getLoadSuccessMessage(newItems.length)); + } catch (e) { + appLogger.e('Failed to load items', error: e); + if (mounted) { + setState(() { + errorMessage = getLoadErrorMessage(e); + isLoading = false; + }); + } + } + } } diff --git a/lib/screens/collection_detail_screen.dart b/lib/screens/collection_detail_screen.dart index c4330c9c..0b90d477 100644 --- a/lib/screens/collection_detail_screen.dart +++ b/lib/screens/collection_detail_screen.dart @@ -1,17 +1,13 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../client/plex_client.dart'; import '../models/plex_metadata.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; import '../providers/settings_provider.dart'; -import '../utils/app_logger.dart'; import '../widgets/media_card.dart'; import '../widgets/desktop_app_bar.dart'; import '../i18n/strings.g.dart'; import '../utils/grid_size_calculator.dart'; import '../utils/dialogs.dart'; -import '../utils/provider_extensions.dart'; +import '../utils/app_logger.dart'; import 'base_media_list_detail_screen.dart'; /// Screen to display the contents of a collection @@ -25,7 +21,8 @@ class CollectionDetailScreen extends StatefulWidget { } class _CollectionDetailScreenState - extends BaseMediaListDetailScreen { + extends BaseMediaListDetailScreen + with StandardItemLoader { @override PlexMetadata get mediaItem => widget.collection; @@ -35,69 +32,19 @@ class _CollectionDetailScreenState @override String get emptyMessage => t.collections.empty; - /// Get the correct PlexClient for this collection's server - PlexClient? _getClientForCollection() { - final serverId = widget.collection.serverId; - if (serverId == null) { - appLogger.w('Collection ${widget.collection.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + @override + Future> fetchItems() async { + return await client.getCollectionItems(widget.collection.ratingKey); } @override - Future loadItems() async { - if (mounted) { - setState(() { - isLoading = true; - errorMessage = null; - }); - } + String getLoadErrorMessage(Object error) { + return t.collections.failedToLoadItems(error: error.toString()); + } - try { - final client = this.client; - final newItems = await client.getCollectionItems( - widget.collection.ratingKey, - ); - - // Tag items with server info for correct client resolution - final taggedItems = newItems - .map( - (item) => item.copyWith( - serverId: widget.collection.serverId, - serverName: widget.collection.serverName, - ), - ) - .toList(); - - if (mounted) { - setState(() { - items = taggedItems; - isLoading = false; - }); - } - - appLogger.d( - 'Loaded ${newItems.length} items for collection: ${widget.collection.title}', - ); - } catch (e) { - appLogger.e('Failed to load collection items', error: e); - if (mounted) { - setState(() { - errorMessage = t.collections.failedToLoadItems(error: e.toString()); - isLoading = false; - }); - } - } + @override + String getLoadSuccessMessage(int itemCount) { + return 'Loaded $itemCount items for collection: ${widget.collection.title}'; } Future _deleteCollection() async { @@ -129,9 +76,6 @@ class _CollectionDetailScreenState if (!mounted) return; try { - final client = _getClientForCollection(); - if (client == null) return; - final success = await client.deleteCollection( sectionId.toString(), widget.collection.ratingKey, @@ -176,61 +120,10 @@ class _CollectionDetailScreenState CustomAppBar( title: Text(widget.collection.title), pinned: true, - actions: [ - // Play button - if (items.isNotEmpty) - IconButton( - icon: const Icon(Icons.play_arrow), - tooltip: t.discover.play, - onPressed: playItems, - ), - // Shuffle button - if (items.isNotEmpty) - IconButton( - icon: const Icon(Icons.shuffle), - tooltip: t.common.shuffle, - onPressed: shufflePlayItems, - ), - // Delete button - IconButton( - icon: const Icon(Icons.delete), - tooltip: t.common.delete, - onPressed: _deleteCollection, - color: Colors.red, - ), - ], + actions: buildAppBarActions(onDelete: _deleteCollection), ), - if (errorMessage != null) - SliverFillRemaining( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.error_outline, - size: 48, - color: Colors.red, - ), - const SizedBox(height: 16), - Text(errorMessage!), - const SizedBox(height: 16), - ElevatedButton( - onPressed: loadItems, - child: Text(t.common.retry), - ), - ], - ), - ), - ) - else if (items.isEmpty && isLoading) - const SliverFillRemaining( - child: Center(child: CircularProgressIndicator()), - ) - else if (items.isEmpty) - SliverFillRemaining( - child: Center(child: Text(t.collections.noItems)), - ) - else + ...buildStateSlivers(), + if (items.isNotEmpty) SliverPadding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), sliver: Consumer( diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index 25820746..88c9dd4a 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -55,23 +55,8 @@ class _DiscoverScreenState extends State bool _isAutoScrollPaused = false; /// Get the correct PlexClient for an item's server - /// If item is null, returns legacy client for backward compatibility PlexClient _getClientForItem(PlexMetadata? item) { - final serverId = item?.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - return context.read().client!; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client!; - } - - return client; + return context.getClientForServer(item?.serverId); } @override diff --git a/lib/screens/hub_detail_screen.dart b/lib/screens/hub_detail_screen.dart index 749efad7..09a9b790 100644 --- a/lib/screens/hub_detail_screen.dart +++ b/lib/screens/hub_detail_screen.dart @@ -4,8 +4,6 @@ import '../client/plex_client.dart'; import '../models/plex_hub.dart'; import '../models/plex_metadata.dart'; import '../models/plex_sort.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; import '../providers/settings_provider.dart'; import '../utils/provider_extensions.dart'; import '../utils/app_logger.dart'; @@ -39,21 +37,7 @@ class _HubDetailScreenState extends State with Refreshable { /// Get the correct PlexClient for this hub's server PlexClient _getClientForHub() { - final serverId = widget.hub.serverId; - if (serverId == null) { - appLogger.w('Hub ${widget.hub.title} has no serverId, using legacy client'); - return context.read().client!; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client!; - } - - return client; + return context.getClientForServer(widget.hub.serverId); } @override diff --git a/lib/screens/libraries_screen.dart b/lib/screens/libraries_screen.dart index 242d2873..730c9a22 100644 --- a/lib/screens/libraries_screen.dart +++ b/lib/screens/libraries_screen.dart @@ -5,11 +5,10 @@ import '../client/plex_client.dart'; import '../models/plex_library.dart'; import '../models/plex_metadata.dart'; import '../models/plex_sort.dart'; -import '../providers/plex_client_provider.dart'; import '../providers/hidden_libraries_provider.dart'; import '../providers/multi_server_provider.dart'; -import '../utils/provider_extensions.dart'; import '../utils/app_logger.dart'; +import '../utils/provider_extensions.dart'; import '../widgets/desktop_app_bar.dart'; import '../widgets/context_menu_wrapper.dart'; import '../widgets/server_badge.dart'; @@ -34,37 +33,10 @@ class LibrariesScreen extends StatefulWidget { class _LibrariesScreenState extends State with Refreshable, ItemUpdatable, SingleTickerProviderStateMixin { @override - PlexClient get client => _getClientForLibrary(null); + PlexClient get client => context.getClientForServer(null); late TabController _tabController; - /// Get the correct PlexClient for a library's server - /// Returns legacy client if libraryKey is null or library not found - PlexClient _getClientForLibrary(String? libraryGlobalKey) { - if (libraryGlobalKey == null) { - return context.read().client!; - } - - // Find the library to get its serverId - final library = _allLibraries.where((lib) => lib.globalKey == libraryGlobalKey).firstOrNull; - final serverId = library?.serverId; - - if (serverId == null) { - appLogger.w('Library $libraryGlobalKey has no serverId, using legacy client'); - return context.read().client!; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client!; - } - - return client; - } - // GlobalKeys for tabs to enable refresh final _recommendedTabKey = GlobalKey>(); final _browseTabKey = GlobalKey>(); @@ -99,7 +71,10 @@ class _LibrariesScreenState extends State // Save tab index when changed if (_selectedLibraryGlobalKey != null && !_tabController.indexIsChanging) { StorageService.getInstance().then((storage) { - storage.saveLibraryTab(_selectedLibraryGlobalKey!, _tabController.index); + storage.saveLibraryTab( + _selectedLibraryGlobalKey!, + _tabController.index, + ); }); } // Rebuild to update chip selection state @@ -162,7 +137,8 @@ class _LibrariesScreenState extends State final storage = await StorageService.getInstance(); // Fetch libraries from all servers - final allLibraries = await multiServerProvider.aggregationService.getLibrariesFromAllServers(); + final allLibraries = await multiServerProvider.aggregationService + .getLibrariesFromAllServers(); // Filter out music libraries (type: 'artist') since music playback is not yet supported // Only show movie and TV show libraries @@ -191,36 +167,36 @@ class _LibrariesScreenState extends State .toList(); // Load saved preferences - final savedLibraryKey = storage.getSelectedLibraryKey(); + final savedLibraryKey = storage.getSelectedLibraryKey(); - // Find the library by key in visible libraries - String? libraryGlobalKeyToLoad; - if (savedLibraryKey != null) { - // Check if saved library exists and is visible - final libraryExists = visibleLibraries.any( - (lib) => lib.globalKey == savedLibraryKey, - ); - if (libraryExists) { - libraryGlobalKeyToLoad = savedLibraryKey; + // Find the library by key in visible libraries + String? libraryGlobalKeyToLoad; + if (savedLibraryKey != null) { + // Check if saved library exists and is visible + final libraryExists = visibleLibraries.any( + (lib) => lib.globalKey == savedLibraryKey, + ); + if (libraryExists) { + libraryGlobalKeyToLoad = savedLibraryKey; + } + } + + // Fallback to first visible library if saved key not found + if (libraryGlobalKeyToLoad == null && visibleLibraries.isNotEmpty) { + libraryGlobalKeyToLoad = visibleLibraries.first.globalKey; + } + + if (libraryGlobalKeyToLoad != null && mounted) { + final savedFilters = storage.getLibraryFilters( + sectionId: libraryGlobalKeyToLoad, + ); + if (savedFilters.isNotEmpty) { + _selectedFilters = Map.from(savedFilters); + } + _loadLibraryContent(libraryGlobalKeyToLoad); } } - - // Fallback to first visible library if saved key not found - if (libraryGlobalKeyToLoad == null && visibleLibraries.isNotEmpty) { - libraryGlobalKeyToLoad = visibleLibraries.first.globalKey; - } - - if (libraryGlobalKeyToLoad != null && mounted) { - final savedFilters = storage.getLibraryFilters( - sectionId: libraryGlobalKeyToLoad, - ); - if (savedFilters.isNotEmpty) { - _selectedFilters = Map.from(savedFilters); - } - _loadLibraryContent(libraryGlobalKeyToLoad); - } - } - } catch (e) { + } catch (e) { _updateState(() { _errorMessage = _getErrorMessage(e, 'libraries'); _isLoadingLibraries = false; @@ -290,7 +266,7 @@ class _LibrariesScreenState extends State !_isInitialLoad && _selectedLibraryGlobalKey != libraryGlobalKey; // Get the correct client for this library's server - final client = _getClientForLibrary(libraryGlobalKey); + final client = context.getClientForLibrary(library); _updateState(() { _selectedLibraryGlobalKey = libraryGlobalKey; @@ -414,7 +390,7 @@ class _LibrariesScreenState extends State Future _loadSortOptions(PlexLibrary library) async { try { - final client = _getClientForLibrary(library.globalKey); + final client = context.getClientForLibrary(library); final sortOptions = await client.getLibrarySorts(library.key); @@ -530,7 +506,8 @@ class _LibrariesScreenState extends State await hiddenLibrariesProvider.unhideLibrary(library.key); } else { // Check if we're hiding the currently selected library - final isCurrentlySelected = _selectedLibraryGlobalKey == library.globalKey; + final isCurrentlySelected = + _selectedLibraryGlobalKey == library.globalKey; await hiddenLibrariesProvider.hideLibrary(library.key); @@ -648,7 +625,7 @@ class _LibrariesScreenState extends State required String Function(Object error) failureMessage, }) async { try { - final client = _getClientForLibrary(library.globalKey); + final client = context.getClientForLibrary(library); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -888,7 +865,9 @@ class _LibrariesScreenState extends State Text( selectedLibrary.serverName!, style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6), + color: Theme.of( + context, + ).textTheme.bodySmall?.color?.withValues(alpha: 0.6), ), ), ], @@ -920,7 +899,8 @@ class _LibrariesScreenState extends State body: CustomScrollView( slivers: [ DesktopSliverAppBar( - title: visibleLibraries.isNotEmpty && _selectedLibraryGlobalKey != null + title: + visibleLibraries.isNotEmpty && _selectedLibraryGlobalKey != null ? _buildLibraryDropdownTitle(visibleLibraries) : Text(t.libraries.title), floating: true, diff --git a/lib/screens/library_tabs/base_library_tab.dart b/lib/screens/library_tabs/base_library_tab.dart new file mode 100644 index 00000000..afdbfd32 --- /dev/null +++ b/lib/screens/library_tabs/base_library_tab.dart @@ -0,0 +1,157 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import '../../models/plex_library.dart'; +import '../../utils/app_logger.dart'; +import '../../mixins/library_tab_state.dart'; +import '../../mixins/refreshable.dart'; +import '../../widgets/content_state_builder.dart'; + +/// Base class for library tab screens that provides common state management +/// and lifecycle handling for tabs that display library content. +/// +/// Type parameter T: The type of items this tab displays +/// +/// Subclasses must implement: +/// - [loadData]: Load data from the Plex API +/// - [buildContent]: Build the UI for displaying loaded items +/// +/// Optional overrides: +/// - [emptyIcon]: Icon to show when there are no items +/// - [emptyMessage]: Message to show when there are no items +/// - [errorContext]: Context for error messages (defaults to "content") +/// - [getRefreshStream]: Stream to listen for refresh events +abstract class BaseLibraryTab extends StatefulWidget { + final PlexLibrary library; + final String? viewMode; + final String? density; + + const BaseLibraryTab({ + super.key, + required this.library, + this.viewMode, + this.density, + }); +} + +/// State mixin that provides the common implementation for library tabs +/// This preserves AutomaticKeepAliveClientMixin functionality +abstract class BaseLibraryTabState> + extends State + with AutomaticKeepAliveClientMixin, Refreshable, LibraryTabStateMixin { + @override + bool get wantKeepAlive => true; + + @override + PlexLibrary get library => widget.library; + + @override + void refresh() { + loadItems(); + } + + // State management + List _items = []; + bool _isLoading = false; + String? _errorMessage; + StreamSubscription? _refreshSubscription; + + // Getters for subclasses + List get items => _items; + bool get isLoading => _isLoading; + String? get errorMessage => _errorMessage; + + @override + void initState() { + super.initState(); + loadItems(); + + // Subscribe to refresh stream if provided + final refreshStream = getRefreshStream(); + if (refreshStream != null) { + _refreshSubscription = refreshStream.listen((_) { + if (mounted) { + loadItems(); + } + }); + } + } + + @override + void dispose() { + _refreshSubscription?.cancel(); + super.dispose(); + } + + @override + void didUpdateWidget(W oldWidget) { + super.didUpdateWidget(oldWidget); + // Reload if library changed + if (oldWidget.library.globalKey != widget.library.globalKey) { + loadItems(); + } + } + + /// Load items from the API + /// This is the main data loading function that subclasses must implement + Future> loadData(); + + /// Build the content widget given the loaded items + /// This is called by ContentStateBuilder when items are available + Widget buildContent(List items); + + /// Icon to display when there are no items (empty state) + IconData get emptyIcon; + + /// Message to display when there are no items (empty state) + String get emptyMessage; + + /// Context string for error messages (e.g., "playlists", "collections") + String get errorContext; + + /// Optional refresh stream to listen for external refresh events + /// Return null if no refresh stream is needed + Stream? getRefreshStream() => null; + + /// Load items with error handling and state management + Future loadItems() async { + setState(() { + _isLoading = true; + _errorMessage = null; + }); + + try { + final loadedItems = await loadData(); + + if (!mounted) return; + + setState(() { + _items = loadedItems; + _isLoading = false; + }); + } catch (e) { + if (!mounted) return; + + appLogger.e('Error loading $errorContext', error: e); + setState(() { + _errorMessage = 'Failed to load $errorContext: ${e.toString()}'; + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + super.build(context); // Required for AutomaticKeepAliveClientMixin + + return ContentStateBuilder( + isLoading: _isLoading, + errorMessage: _errorMessage, + items: _items, + emptyIcon: emptyIcon, + emptyMessage: emptyMessage, + onRetry: loadItems, + builder: (items) => + RefreshIndicator(onRefresh: loadItems, child: buildContent(items)), + ); + } +} diff --git a/lib/screens/library_tabs/library_browse_tab.dart b/lib/screens/library_tabs/library_browse_tab.dart index 191b4361..855bf68c 100644 --- a/lib/screens/library_tabs/library_browse_tab.dart +++ b/lib/screens/library_tabs/library_browse_tab.dart @@ -6,22 +6,22 @@ import '../../models/plex_library.dart'; import '../../models/plex_metadata.dart'; import '../../models/plex_filter.dart'; import '../../models/plex_sort.dart'; -import '../../providers/plex_client_provider.dart'; -import '../../providers/multi_server_provider.dart'; import '../../providers/settings_provider.dart'; -import '../../utils/provider_extensions.dart'; import '../../utils/error_message_utils.dart'; import '../../utils/grid_size_calculator.dart'; +import '../../utils/server_tagging_extensions.dart'; import '../../widgets/media_card.dart'; import '../../widgets/folder_tree_view.dart'; import '../../widgets/filters_bottom_sheet.dart'; import '../../widgets/sort_bottom_sheet.dart'; +import '../../widgets/empty_state_widget.dart'; +import '../../widgets/error_state_widget.dart'; import '../../services/storage_service.dart'; import '../../services/settings_service.dart' show ViewMode; import '../../mixins/item_updatable.dart'; +import '../../mixins/library_tab_state.dart'; import '../../mixins/refreshable.dart'; import '../../i18n/strings.g.dart'; -import '../../utils/app_logger.dart'; /// Browse tab for library screen /// Shows library items with grouping, filtering, and sorting @@ -42,32 +42,19 @@ class LibraryBrowseTab extends StatefulWidget { } class _LibraryBrowseTabState extends State - with AutomaticKeepAliveClientMixin, ItemUpdatable, Refreshable { + with + AutomaticKeepAliveClientMixin, + ItemUpdatable, + Refreshable, + LibraryTabStateMixin { @override bool get wantKeepAlive => true; @override - PlexClient get client => context.clientSafe; + PlexLibrary get library => widget.library; - /// Get the correct PlexClient for this library's server - PlexClient? _getClientForLibrary(BuildContext context) { - final serverId = widget.library.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - appLogger.w('Library ${widget.library.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; - } + @override + PlexClient get client => getClientForLibrary(); @override void refresh() { @@ -129,7 +116,7 @@ class _LibraryBrowseTabState extends State final currentRequestId = ++_requestId; // Extract context dependencies before async gap - use server-specific client - final client = _getClientForLibrary(context); + final client = getClientForLibrary(); setState(() { _isLoading = true; @@ -140,10 +127,6 @@ class _LibraryBrowseTabState extends State }); try { - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } - final storage = await StorageService.getInstance(); // Load filters and sorts for this library @@ -155,7 +138,9 @@ class _LibraryBrowseTabState extends State sectionId: widget.library.globalKey, ); final savedSort = storage.getLibrarySort(widget.library.globalKey); - final savedGrouping = storage.getLibraryGrouping(widget.library.globalKey); + final savedGrouping = storage.getLibraryGrouping( + widget.library.globalKey, + ); // Check if request was cancelled if (currentRequestId != _requestId) return; @@ -214,10 +199,7 @@ class _LibraryBrowseTabState extends State try { // Use server-specific client for this library - final client = _getClientForLibrary(context); - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } + final client = getClientForLibrary(); // Build filter params final filterParams = Map.from(_selectedFilters); @@ -246,14 +228,7 @@ class _LibraryBrowseTabState extends State ); // Tag items with server info for multi-server support - final taggedItems = items - .map( - (item) => item.copyWith( - serverId: widget.library.serverId, - serverName: widget.library.serverName, - ), - ) - .toList(); + final taggedItems = items.tagWithLibrary(widget.library); if (currentRequestId != _requestId) return; @@ -356,8 +331,11 @@ class _LibraryBrowseTabState extends State _selectedGrouping = value; }); - final storage = await StorageService.getInstance(); - await storage.saveLibraryGrouping(widget.library.globalKey, value); + final storage = await StorageService.getInstance(); + await storage.saveLibraryGrouping( + widget.library.globalKey, + value, + ); if (!mounted) return; @@ -529,33 +507,18 @@ class _LibraryBrowseTabState extends State } if (_errorMessage != null && _items.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error_outline, size: 48, color: Colors.red), - const SizedBox(height: 16), - Text(_errorMessage!), - const SizedBox(height: 16), - ElevatedButton( - onPressed: _loadContent, - child: Text(t.common.retry), - ), - ], - ), + return ErrorStateWidget( + message: _errorMessage!, + icon: Icons.error_outline, + onRetry: _loadContent, + retryLabel: t.common.retry, ); } if (_items.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.folder_open, size: 64, color: Colors.grey), - const SizedBox(height: 16), - Text(t.libraries.thisLibraryIsEmpty), - ], - ), + return EmptyStateWidget( + message: t.libraries.thisLibraryIsEmpty, + icon: Icons.folder_open, ); } diff --git a/lib/screens/library_tabs/library_collections_tab.dart b/lib/screens/library_tabs/library_collections_tab.dart index 61639367..b392543e 100644 --- a/lib/screens/library_tabs/library_collections_tab.dart +++ b/lib/screens/library_tabs/library_collections_tab.dart @@ -1,162 +1,53 @@ -import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../../client/plex_client.dart'; -import '../../models/plex_library.dart'; import '../../models/plex_metadata.dart'; -import '../../providers/plex_client_provider.dart'; -import '../../providers/multi_server_provider.dart'; -import '../../utils/app_logger.dart'; import '../../utils/library_refresh_notifier.dart'; +import '../../utils/server_tagging_extensions.dart'; import '../../i18n/strings.g.dart'; -import '../../mixins/refreshable.dart'; -import '../../widgets/content_state_builder.dart'; import '../../widgets/adaptive_media_grid.dart'; +import 'base_library_tab.dart'; /// Collections tab for library screen /// Shows collections for the current library -class LibraryCollectionsTab extends StatefulWidget { - final PlexLibrary library; - final String? viewMode; - final String? density; - +class LibraryCollectionsTab extends BaseLibraryTab { const LibraryCollectionsTab({ super.key, - required this.library, - this.viewMode, - this.density, + required super.library, + super.viewMode, + super.density, }); @override State createState() => _LibraryCollectionsTabState(); } -class _LibraryCollectionsTabState extends State - with AutomaticKeepAliveClientMixin, Refreshable { +class _LibraryCollectionsTabState + extends BaseLibraryTabState { @override - bool get wantKeepAlive => true; + IconData get emptyIcon => Icons.collections; @override - void refresh() { - _loadCollections(); - } - - /// Get the correct PlexClient for this library's server - PlexClient? _getClientForLibrary(BuildContext context) { - final serverId = widget.library.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - appLogger.w('Library ${widget.library.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; - } - - List _collections = []; - bool _isLoading = false; - String? _errorMessage; - StreamSubscription? _refreshSubscription; + String get emptyMessage => t.libraries.noCollections; @override - void initState() { - super.initState(); - _loadCollections(); + String get errorContext => t.collections.title; - // Listen for refresh notifications - _refreshSubscription = LibraryRefreshNotifier().collectionsStream.listen(( - _, - ) { - if (mounted) { - _loadCollections(); - } - }); + @override + Stream? getRefreshStream() => + LibraryRefreshNotifier().collectionsStream; + + @override + Future> loadData() async { + // Use server-specific client for this library + final client = getClientForLibrary(); + + final collections = await client.getLibraryCollections(widget.library.key); + + // Tag collections with server info + return collections.tagWithLibrary(widget.library); } @override - void dispose() { - _refreshSubscription?.cancel(); - super.dispose(); - } - - @override - void didUpdateWidget(LibraryCollectionsTab oldWidget) { - super.didUpdateWidget(oldWidget); - // Reload if library changed - if (oldWidget.library.globalKey != widget.library.globalKey) { - _loadCollections(); - } - } - - Future _loadCollections() async { - setState(() { - _isLoading = true; - _errorMessage = null; - }); - - try { - // Use server-specific client for this library - final client = _getClientForLibrary(context); - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } - - final collections = await client.getLibraryCollections( - widget.library.key, - ); - - if (!mounted) return; - - final taggedCollections = collections - .map( - (item) => item.copyWith( - serverId: widget.library.serverId, - serverName: widget.library.serverName, - ), - ) - .toList(); - - setState(() { - _collections = taggedCollections; - _isLoading = false; - }); - } catch (e) { - if (!mounted) return; - - appLogger.e('Error loading collections', error: e); - setState(() { - _errorMessage = t.errors.failedToLoad( - context: t.collections.title, - error: e.toString(), - ); - _isLoading = false; - }); - } - } - - @override - Widget build(BuildContext context) { - super.build(context); // Required for AutomaticKeepAliveClientMixin - - return ContentStateBuilder( - isLoading: _isLoading, - errorMessage: _errorMessage, - items: _collections, - emptyIcon: Icons.collections, - emptyMessage: t.libraries.noCollections, - onRetry: _loadCollections, - builder: (items) => RefreshIndicator( - onRefresh: _loadCollections, - child: AdaptiveMediaGrid(items: items, onRefresh: _loadCollections), - ), - ); + Widget buildContent(List items) { + return AdaptiveMediaGrid(items: items, onRefresh: loadItems); } } diff --git a/lib/screens/library_tabs/library_playlists_tab.dart b/lib/screens/library_tabs/library_playlists_tab.dart index bef70194..c4e06116 100644 --- a/lib/screens/library_tabs/library_playlists_tab.dart +++ b/lib/screens/library_tabs/library_playlists_tab.dart @@ -1,204 +1,99 @@ -import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../../client/plex_client.dart'; -import '../../models/plex_library.dart'; import '../../models/plex_playlist.dart'; -import '../../providers/plex_client_provider.dart'; -import '../../providers/multi_server_provider.dart'; import '../../providers/settings_provider.dart'; -import '../../utils/app_logger.dart'; import '../../utils/library_refresh_notifier.dart'; import '../../services/settings_service.dart' show ViewMode; import '../../utils/grid_size_calculator.dart'; +import '../../utils/server_tagging_extensions.dart'; import '../../widgets/media_card.dart'; import '../../i18n/strings.g.dart'; -import '../../mixins/refreshable.dart'; -import '../../widgets/content_state_builder.dart'; +import 'base_library_tab.dart'; /// Playlists tab for library screen /// Shows playlists that contain items from the current library -class LibraryPlaylistsTab extends StatefulWidget { - final PlexLibrary library; - final String? viewMode; - final String? density; - +class LibraryPlaylistsTab extends BaseLibraryTab { const LibraryPlaylistsTab({ super.key, - required this.library, - this.viewMode, - this.density, + required super.library, + super.viewMode, + super.density, }); @override State createState() => _LibraryPlaylistsTabState(); } -class _LibraryPlaylistsTabState extends State - with AutomaticKeepAliveClientMixin, Refreshable { +class _LibraryPlaylistsTabState + extends BaseLibraryTabState { @override - bool get wantKeepAlive => true; + IconData get emptyIcon => Icons.playlist_play; @override - void refresh() { - _loadPlaylists(); - } - - /// Get the correct PlexClient for this library's server - PlexClient? _getClientForLibrary(BuildContext context) { - final serverId = widget.library.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - appLogger.w('Library ${widget.library.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; - } - - List _playlists = []; - bool _isLoading = false; - String? _errorMessage; - StreamSubscription? _refreshSubscription; + String get emptyMessage => t.playlists.noPlaylists; @override - void initState() { - super.initState(); - _loadPlaylists(); + String get errorContext => t.playlists.title; - // Listen for refresh notifications - _refreshSubscription = LibraryRefreshNotifier().playlistsStream.listen((_) { - if (mounted) { - _loadPlaylists(); - } - }); + @override + Stream? getRefreshStream() => LibraryRefreshNotifier().playlistsStream; + + @override + Future> loadData() async { + // Use server-specific client for this library + final client = getClientForLibrary(); + + // Get playlists for this library + final playlists = await client.getLibraryPlaylists( + sectionId: widget.library.key, + playlistType: 'video', + ); + + // Tag playlists with server info + return playlists.tagWithLibrary(widget.library); } @override - void dispose() { - _refreshSubscription?.cancel(); - super.dispose(); - } - - @override - void didUpdateWidget(LibraryPlaylistsTab oldWidget) { - super.didUpdateWidget(oldWidget); - // Reload if library changed - if (oldWidget.library.globalKey != widget.library.globalKey) { - _loadPlaylists(); - } - } - - Future _loadPlaylists() async { - setState(() { - _isLoading = true; - _errorMessage = null; - }); - - try { - // Use server-specific client for this library - final client = _getClientForLibrary(context); - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } - - // Get playlists for this library - final playlists = await client.getLibraryPlaylists( - sectionId: widget.library.key, - playlistType: 'video', - ); - - if (!mounted) return; - - final taggedPlaylists = playlists - .map( - (playlist) => playlist.copyWith( - serverId: widget.library.serverId, - serverName: widget.library.serverName, + Widget buildContent(List items) { + return Consumer( + builder: (context, settingsProvider, child) { + if (settingsProvider.viewMode == ViewMode.list) { + return ListView.builder( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), + itemCount: items.length, + itemBuilder: (context, index) { + final playlist = items[index]; + return MediaCard( + key: Key(playlist.ratingKey), + item: playlist, + onListRefresh: loadItems, + ); + }, + ); + } else { + return GridView.builder( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: GridSizeCalculator.getMaxCrossAxisExtent( + context, + settingsProvider.libraryDensity, + ), + childAspectRatio: 2 / 3.3, + crossAxisSpacing: 0, + mainAxisSpacing: 0, ), - ) - .toList(); - - setState(() { - _playlists = taggedPlaylists; - _isLoading = false; - }); - } catch (e) { - if (!mounted) return; - - appLogger.e('Error loading playlists', error: e); - setState(() { - _errorMessage = t.errors.failedToLoad( - context: t.playlists.title, - error: e.toString(), - ); - _isLoading = false; - }); - } - } - - @override - Widget build(BuildContext context) { - super.build(context); // Required for AutomaticKeepAliveClientMixin - - return ContentStateBuilder( - isLoading: _isLoading, - errorMessage: _errorMessage, - items: _playlists, - emptyIcon: Icons.playlist_play, - emptyMessage: t.playlists.noPlaylists, - onRetry: _loadPlaylists, - builder: (items) => RefreshIndicator( - onRefresh: _loadPlaylists, - child: Consumer( - builder: (context, settingsProvider, child) { - if (settingsProvider.viewMode == ViewMode.list) { - return ListView.builder( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), - itemCount: items.length, - itemBuilder: (context, index) { - final playlist = items[index]; - return MediaCard( - key: Key(playlist.ratingKey), - item: playlist, - onListRefresh: _loadPlaylists, - ); - }, + itemCount: items.length, + itemBuilder: (context, index) { + final playlist = items[index]; + return MediaCard( + key: Key(playlist.ratingKey), + item: playlist, + onListRefresh: loadItems, ); - } else { - return GridView.builder( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 8), - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: GridSizeCalculator.getMaxCrossAxisExtent( - context, - settingsProvider.libraryDensity, - ), - childAspectRatio: 2 / 3.3, - crossAxisSpacing: 0, - mainAxisSpacing: 0, - ), - itemCount: items.length, - itemBuilder: (context, index) { - final playlist = items[index]; - return MediaCard( - key: Key(playlist.ratingKey), - item: playlist, - onListRefresh: _loadPlaylists, - ); - }, - ); - } - }, - ), - ), + }, + ); + } + }, ); } } diff --git a/lib/screens/library_tabs/library_recommended_tab.dart b/lib/screens/library_tabs/library_recommended_tab.dart index 1c26cfc0..15c9b13a 100644 --- a/lib/screens/library_tabs/library_recommended_tab.dart +++ b/lib/screens/library_tabs/library_recommended_tab.dart @@ -1,133 +1,65 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../../client/plex_client.dart'; -import '../../models/plex_library.dart'; import '../../models/plex_hub.dart'; -import '../../providers/plex_client_provider.dart'; -import '../../providers/multi_server_provider.dart'; -import '../../utils/app_logger.dart'; +import '../../utils/server_tagging_extensions.dart'; import '../../widgets/hub_section.dart'; import '../../i18n/strings.g.dart'; -import '../../mixins/refreshable.dart'; -import '../../widgets/content_state_builder.dart'; +import 'base_library_tab.dart'; /// Recommended tab for library screen /// Shows library-specific hubs and recommendations -class LibraryRecommendedTab extends StatefulWidget { - final PlexLibrary library; - - const LibraryRecommendedTab({super.key, required this.library}); +class LibraryRecommendedTab extends BaseLibraryTab { + const LibraryRecommendedTab({super.key, required super.library}); @override State createState() => _LibraryRecommendedTabState(); } -class _LibraryRecommendedTabState extends State - with AutomaticKeepAliveClientMixin, Refreshable { +class _LibraryRecommendedTabState + extends BaseLibraryTabState { @override - bool get wantKeepAlive => true; + IconData get emptyIcon => Icons.recommend; @override - void refresh() { - _loadHubs(); - } - - /// Get the correct PlexClient for this library's server - PlexClient? _getClientForLibrary(BuildContext context) { - final serverId = widget.library.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - appLogger.w('Library ${widget.library.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; - } - - List _hubs = []; - bool _isLoading = false; - String? _errorMessage; + String get emptyMessage => t.libraries.noRecommendations; @override - void initState() { - super.initState(); - _loadHubs(); + String get errorContext => t.libraries.tabs.recommended; + + @override + Future> loadData() async { + // Use server-specific client for this library + final client = getClientForLibrary(); + + final hubs = await client.getLibraryHubs(widget.library.key, limit: 12); + + // Tag hubs and items with server info + return hubs + .map( + (hub) => PlexHub( + hubKey: hub.hubKey, + title: hub.title, + type: hub.type, + hubIdentifier: hub.hubIdentifier, + size: hub.size, + more: hub.more, + items: hub.items.tagWithLibrary(widget.library), + serverId: widget.library.serverId, + serverName: widget.library.serverName, + ), + ) + .toList(); } @override - void didUpdateWidget(LibraryRecommendedTab oldWidget) { - super.didUpdateWidget(oldWidget); - // Reload if library changed - if (oldWidget.library.globalKey != widget.library.globalKey) { - _loadHubs(); - } - } - - Future _loadHubs() async { - setState(() { - _isLoading = true; - _errorMessage = null; - }); - - try { - // Use server-specific client for this library - final client = _getClientForLibrary(context); - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } - - final hubs = await client.getLibraryHubs(widget.library.key, limit: 12); - - if (!mounted) return; - - // Tag hubs and items with server info - final taggedHubs = hubs - .map( - (hub) => PlexHub( - hubKey: hub.hubKey, - title: hub.title, - type: hub.type, - hubIdentifier: hub.hubIdentifier, - size: hub.size, - more: hub.more, - items: hub.items - .map( - (item) => item.copyWith( - serverId: widget.library.serverId, - serverName: widget.library.serverName, - ), - ) - .toList(), - serverId: widget.library.serverId, - serverName: widget.library.serverName, - ), - ) - .toList(); - - setState(() { - _hubs = taggedHubs; - _isLoading = false; - }); - } catch (e) { - if (!mounted) return; - - appLogger.e('Error loading library hubs', error: e); - setState(() { - _errorMessage = t.errors.failedToLoad( - context: t.libraries.tabs.recommended, - error: e.toString(), - ); - _isLoading = false; - }); - } + Widget buildContent(List items) { + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: items.length, + itemBuilder: (context, index) { + final hub = items[index]; + return HubSection(hub: hub, icon: _getHubIcon(hub)); + }, + ); } IconData _getHubIcon(PlexHub hub) { @@ -149,29 +81,4 @@ class _LibraryRecommendedTabState extends State } return Icons.movie; } - - @override - Widget build(BuildContext context) { - super.build(context); // Required for AutomaticKeepAliveClientMixin - - return ContentStateBuilder( - isLoading: _isLoading, - errorMessage: _errorMessage, - items: _hubs, - emptyIcon: Icons.recommend, - emptyMessage: t.libraries.noRecommendations, - onRetry: _loadHubs, - builder: (items) => RefreshIndicator( - onRefresh: _loadHubs, - child: ListView.builder( - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: items.length, - itemBuilder: (context, index) { - final hub = items[index]; - return HubSection(hub: hub, icon: _getHubIcon(hub)); - }, - ), - ), - ); - } } diff --git a/lib/screens/media_detail_screen.dart b/lib/screens/media_detail_screen.dart index 198457bc..50222340 100644 --- a/lib/screens/media_detail_screen.dart +++ b/lib/screens/media_detail_screen.dart @@ -5,14 +5,12 @@ import 'package:provider/provider.dart'; import '../i18n/strings.g.dart'; import '../client/plex_client.dart'; import '../models/plex_metadata.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; +import '../providers/playback_state_provider.dart'; import '../theme/theme_helper.dart'; import '../utils/app_logger.dart'; import '../utils/content_rating_formatter.dart'; import '../utils/duration_formatter.dart'; import '../utils/provider_extensions.dart'; -import '../utils/shuffle_play_helper.dart'; import '../utils/video_player_navigation.dart'; import '../widgets/app_bar_back_button.dart'; import '../widgets/desktop_app_bar.dart'; @@ -52,23 +50,8 @@ class _MediaDetailScreenState extends State { } /// Get the correct PlexClient for this metadata's server - PlexClient? _getClientForMetadata(BuildContext context) { - final serverId = widget.metadata.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - appLogger.w('Metadata ${widget.metadata.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + PlexClient _getClientForMetadata(BuildContext context) { + return context.getClientForServer(widget.metadata.serverId); } Future _loadFullMetadata() async { @@ -150,10 +133,14 @@ class _MediaDetailScreenState extends State { final seasons = await client.getChildren(widget.metadata.ratingKey); // Preserve serverId for each season - final seasonsWithServerId = seasons.map((season) => season.copyWith( - serverId: widget.metadata.serverId, - serverName: widget.metadata.serverName, - )).toList(); + final seasonsWithServerId = seasons + .map( + (season) => season.copyWith( + serverId: widget.metadata.serverId, + serverName: widget.metadata.serverName, + ), + ) + .toList(); setState(() { _seasons = seasonsWithServerId; _isLoadingSeasons = false; @@ -191,10 +178,14 @@ class _MediaDetailScreenState extends State { if (metadata.type.toLowerCase() == 'show') { final seasons = await client.getChildren(widget.metadata.ratingKey); // Preserve serverId for each season - updatedSeasons = seasons.map((season) => season.copyWith( - serverId: widget.metadata.serverId, - serverName: widget.metadata.serverName, - )).toList(); + updatedSeasons = seasons + .map( + (season) => season.copyWith( + serverId: widget.metadata.serverId, + serverName: widget.metadata.serverName, + ), + ) + .toList(); } // Single setState to minimize rebuilds - scroll position is preserved by controller @@ -277,6 +268,101 @@ class _MediaDetailScreenState extends State { } } + /// Handle shuffle play using play queues + Future _handleShufflePlayWithQueue( + BuildContext context, + PlexMetadata metadata, + ) async { + final client = _getClientForMetadata(context); + if (client == null) return; + + final playbackState = context.read(); + final itemType = metadata.type.toLowerCase(); + + try { + // Show loading indicator + if (context.mounted) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + const Center(child: CircularProgressIndicator()), + ); + } + + // Determine the rating key for the play queue + String showRatingKey; + if (itemType == 'show') { + showRatingKey = metadata.ratingKey; + } else if (itemType == 'season') { + // For seasons, we need the show's rating key + // The season's parentRatingKey should point to the show + if (metadata.parentRatingKey == null) { + throw Exception('Season is missing parentRatingKey'); + } + showRatingKey = metadata.parentRatingKey!; + } else { + throw Exception('Shuffle play only works for shows and seasons'); + } + + // Create a shuffled play queue for the show + final playQueue = await client.createShowPlayQueue( + showRatingKey: showRatingKey, + shuffle: 1, + ); + + // Close loading indicator + if (context.mounted) { + Navigator.pop(context); + } + + if (playQueue == null || + playQueue.items == null || + playQueue.items!.isEmpty) { + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.messages.noEpisodesFound))); + } + return; + } + + // Initialize playback state with the play queue + await playbackState.setPlaybackFromPlayQueue( + playQueue, + showRatingKey, + serverId: metadata.serverId, + serverName: metadata.serverName, + ); + + // Set the client for the playback state provider + playbackState.setClient(client); + + // Navigate to the first episode in the shuffled queue + final firstEpisode = playQueue.items!.first.copyWith( + serverId: metadata.serverId, + serverName: metadata.serverName, + ); + + if (context.mounted) { + await navigateToVideoPlayer(context, metadata: firstEpisode); + // Refresh metadata when returning from video player + _loadFullMetadata(); + } + } catch (e) { + // Close loading indicator if it's still open + if (context.mounted && Navigator.canPop(context)) { + Navigator.pop(context); + } + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))), + ); + } + } + } + @override Widget build(BuildContext context) { // Use full metadata if loaded, otherwise use passed metadata @@ -382,7 +468,9 @@ class _MediaDetailScreenState extends State { width: 400, child: Builder( builder: (context) { - final client = _getClientForMetadata(context); + final client = _getClientForMetadata( + context, + ); if (client == null) { return Text( metadata.title, @@ -697,7 +785,10 @@ class _MediaDetailScreenState extends State { metadata.type.toLowerCase() == 'season') ...[ IconButton.filledTonal( onPressed: () async { - await handleShufflePlay(context, metadata); + await _handleShufflePlayWithQueue( + context, + metadata, + ); }, icon: const Icon(Icons.shuffle), tooltip: t.tooltips.shufflePlay, diff --git a/lib/screens/playlist_detail_screen.dart b/lib/screens/playlist_detail_screen.dart index 626c9ee8..3f2b4aa0 100644 --- a/lib/screens/playlist_detail_screen.dart +++ b/lib/screens/playlist_detail_screen.dart @@ -2,8 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../client/plex_client.dart'; import '../models/plex_playlist.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; +import '../models/plex_metadata.dart'; import '../providers/settings_provider.dart'; import '../providers/playback_state_provider.dart'; import '../utils/app_logger.dart'; @@ -28,7 +27,8 @@ class PlaylistDetailScreen extends StatefulWidget { } class _PlaylistDetailScreenState - extends BaseMediaListDetailScreen { + extends BaseMediaListDetailScreen + with StandardItemLoader { @override dynamic get mediaItem => widget.playlist; @@ -38,67 +38,22 @@ class _PlaylistDetailScreenState @override String get emptyMessage => t.playlists.emptyPlaylist; - /// Get the correct PlexClient for this playlist's server - PlexClient? _getClientForPlaylist() { - final serverId = widget.playlist.serverId; - if (serverId == null) { - appLogger.w('Playlist ${widget.playlist.title} has no serverId, using legacy client'); - return context.read().client; - } + @override + IconData get emptyIcon => Icons.playlist_play; - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + @override + Future> fetchItems() async { + return await client.getPlaylist(widget.playlist.ratingKey); } @override - Future loadItems() async { - if (mounted) { - setState(() { - isLoading = true; - errorMessage = null; - }); - } + String getLoadSuccessMessage(int itemCount) { + return 'Loaded $itemCount items for playlist: ${widget.playlist.title}'; + } - try { - final client = this.client; - final newItems = await client.getPlaylist(widget.playlist.ratingKey); - - // Tag items with server info for correct client resolution - final taggedItems = newItems - .map( - (item) => item.copyWith( - serverId: widget.playlist.serverId, - serverName: widget.playlist.serverName, - ), - ) - .toList(); - - if (mounted) { - setState(() { - items = taggedItems; - isLoading = false; - }); - } - - appLogger.d( - 'Loaded ${newItems.length} items for playlist: ${widget.playlist.title}', - ); - } catch (e) { - appLogger.e('Failed to load playlist items', error: e); - if (mounted) { - setState(() { - errorMessage = 'Failed to load playlist items: ${e.toString()}'; - isLoading = false; - }); - } - } + /// Get the correct PlexClient for this playlist's server + PlexClient _getClientForPlaylist() { + return context.getClientForServer(widget.playlist.serverId); } Future _deletePlaylist() async { @@ -254,7 +209,6 @@ class _PlaylistDetailScreenState try { final client = _getClientForPlaylist(); - if (client == null) return; final selectedItem = items[index]; @@ -284,6 +238,8 @@ class _PlaylistDetailScreenState await playbackState.setPlaybackFromPlayQueue( playQueue, widget.playlist.ratingKey, + serverId: widget.playlist.serverId, + serverName: widget.playlist.serverName, ); // Navigate to selected item (should be first in the queue response) @@ -343,113 +299,51 @@ class _PlaylistDetailScreenState ], ), pinned: true, - actions: [ - // Play button - if (items.isNotEmpty) - IconButton( - icon: const Icon(Icons.play_arrow), - tooltip: t.discover.play, - onPressed: playItems, - ), - // Shuffle button - if (items.isNotEmpty) - IconButton( - icon: const Icon(Icons.shuffle), - tooltip: t.playlists.shuffle, - onPressed: shufflePlayItems, - ), - // Delete button for non-smart playlists - if (!widget.playlist.smart) - IconButton( - icon: const Icon(Icons.delete), - tooltip: t.playlists.delete, - onPressed: _deletePlaylist, - color: Colors.red, - ), - ], - ), - if (errorMessage != null) - SliverFillRemaining( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.error_outline, - size: 48, - color: Colors.red, - ), - const SizedBox(height: 16), - Text(errorMessage!), - const SizedBox(height: 16), - ElevatedButton( - onPressed: loadItems, - child: Text(t.common.retry), - ), - ], - ), - ), - ) - else if (items.isEmpty && isLoading) - const SliverFillRemaining( - child: Center(child: CircularProgressIndicator()), - ) - else if (items.isEmpty) - SliverFillRemaining( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.playlist_play, - size: 64, - color: Colors.grey, - ), - const SizedBox(height: 16), - Text( - t.playlists.emptyPlaylist, - style: const TextStyle(fontSize: 16, color: Colors.grey), - ), - ], - ), - ), - ) - else if (widget.playlist.smart) - // Smart playlists: Use grid view (cannot be reordered) - SliverPadding( - padding: const EdgeInsets.fromLTRB(8, 0, 8, 8), - sliver: SliverGrid( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: GridSizeCalculator.getMaxCrossAxisExtent( - context, - context.watch().libraryDensity, - ), - childAspectRatio: 2 / 3.3, - crossAxisSpacing: 0, - mainAxisSpacing: 0, - ), - delegate: SliverChildBuilderDelegate((context, index) { - return MediaCard(item: items[index], onRefresh: updateItem); - }, childCount: items.length), - ), - ) - else - // Regular playlists: Use reorderable list view - SliverReorderableList( - itemBuilder: (context, index) { - final item = items[index]; - return PlaylistItemCard( - key: ValueKey(item.playlistItemID ?? item.ratingKey), - item: item, - index: index, - onRemove: () => _removeItem(index), - onTap: () => _playFromItem(index), - canReorder: !widget.playlist.smart, - ); - }, - itemCount: items.length, - onReorder: _onReorder, + actions: buildAppBarActions( + onDelete: widget.playlist.smart ? null : _deletePlaylist, + deleteTooltip: t.playlists.delete, + showDelete: !widget.playlist.smart, ), + ), + ...buildStateSlivers(), + if (items.isNotEmpty) + if (widget.playlist.smart) + // Smart playlists: Use grid view (cannot be reordered) + SliverPadding( + padding: const EdgeInsets.fromLTRB(8, 0, 8, 8), + sliver: SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: + GridSizeCalculator.getMaxCrossAxisExtent( + context, + context.watch().libraryDensity, + ), + childAspectRatio: 2 / 3.3, + crossAxisSpacing: 0, + mainAxisSpacing: 0, + ), + delegate: SliverChildBuilderDelegate((context, index) { + return MediaCard(item: items[index], onRefresh: updateItem); + }, childCount: items.length), + ), + ) + else + // Regular playlists: Use reorderable list view + SliverReorderableList( + itemBuilder: (context, index) { + final item = items[index]; + return PlaylistItemCard( + key: ValueKey(item.playlistItemID ?? item.ratingKey), + item: item, + index: index, + onRemove: () => _removeItem(index), + onTap: () => _playFromItem(index), + canReorder: !widget.playlist.smart, + ); + }, + itemCount: items.length, + onReorder: _onReorder, + ), ], ), ); diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index b6290c12..8d5687d8 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -25,11 +25,7 @@ class SearchScreen extends StatefulWidget { State createState() => _SearchScreenState(); } -class _SearchScreenState extends State - with Refreshable, ItemUpdatable { - @override - PlexClient get client => context.clientSafe; - +class _SearchScreenState extends State with Refreshable { final _searchController = TextEditingController(); List _searchResults = []; bool _isSearching = false; @@ -103,7 +99,8 @@ class _SearchScreenState extends State } // Search across all connected servers - final results = await multiServerProvider.aggregationService.searchAcrossServers(query); + final results = await multiServerProvider.aggregationService + .searchAcrossServers(query); if (mounted) { setState(() { _searchResults = results; @@ -146,13 +143,10 @@ class _SearchScreenState extends State }); } - @override - void updateItemInLists(String ratingKey, PlexMetadata updatedMetadata) { - final index = _searchResults.indexWhere( - (item) => item.ratingKey == ratingKey, - ); - if (index != -1) { - _searchResults[index] = updatedMetadata; + void updateItem(String ratingKey) { + // Trigger a refresh of the search to get updated metadata + if (_searchController.text.isNotEmpty) { + _performSearch(_searchController.text); } } diff --git a/lib/screens/season_detail_screen.dart b/lib/screens/season_detail_screen.dart index 3d095217..1c24a4bd 100644 --- a/lib/screens/season_detail_screen.dart +++ b/lib/screens/season_detail_screen.dart @@ -3,8 +3,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:provider/provider.dart'; import '../client/plex_client.dart'; import '../models/plex_metadata.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; +import '../utils/provider_extensions.dart'; import '../utils/video_player_navigation.dart'; import '../utils/duration_formatter.dart'; import '../utils/app_logger.dart'; @@ -36,21 +35,7 @@ class _SeasonDetailScreenState extends State /// Get the correct PlexClient for this season's server PlexClient _getClientForSeason(BuildContext context) { - final serverId = widget.season.serverId; - if (serverId == null) { - appLogger.w('Season ${widget.season.title} has no serverId, using legacy client'); - return context.read().client!; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client!; - } - - return client; + return context.getClientForServer(widget.season.serverId); } @override @@ -71,10 +56,14 @@ class _SeasonDetailScreenState extends State try { final episodes = await _client.getChildren(widget.season.ratingKey); // Preserve serverId for each episode - final episodesWithServerId = episodes.map((episode) => episode.copyWith( - serverId: widget.season.serverId, - serverName: widget.season.serverName, - )).toList(); + final episodesWithServerId = episodes + .map( + (episode) => episode.copyWith( + serverId: widget.season.serverId, + serverName: widget.season.serverName, + ), + ) + .toList(); setState(() { _episodes = episodesWithServerId; _isLoadingEpisodes = false; diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 88cc672b..27d039cb 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -79,8 +79,6 @@ class _SettingsScreenState extends State { const SizedBox(height: 24), _buildVideoPlaybackSection(), const SizedBox(height: 24), - _buildShufflePlaySection(), - const SizedBox(height: 24), _buildKeyboardShortcutsSection(), const SizedBox(height: 24), _buildAdvancedSection(), @@ -283,64 +281,6 @@ class _SettingsScreenState extends State { ); } - Widget _buildShufflePlaySection() { - return Card( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - t.settings.shufflePlay, - style: Theme.of( - context, - ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), - ), - ), - Consumer( - builder: (context, settingsProvider, child) { - return SwitchListTile( - secondary: const Icon(Icons.visibility_off), - title: Text(t.settings.unwatchedOnly), - subtitle: Text(t.settings.unwatchedOnlyDescription), - value: settingsProvider.shuffleUnwatchedOnly, - onChanged: (value) async { - await settingsProvider.setShuffleUnwatchedOnly(value); - }, - ); - }, - ), - Consumer( - builder: (context, settingsProvider, child) { - return SwitchListTile( - secondary: const Icon(Icons.shuffle), - title: Text(t.settings.shuffleOrderNavigation), - subtitle: Text(t.settings.shuffleOrderNavigationDescription), - value: settingsProvider.shuffleOrderNavigation, - onChanged: (value) async { - await settingsProvider.setShuffleOrderNavigation(value); - }, - ); - }, - ), - Consumer( - builder: (context, settingsProvider, child) { - return SwitchListTile( - secondary: const Icon(Icons.loop), - title: Text(t.settings.loopShuffleQueue), - subtitle: Text(t.settings.loopShuffleQueueDescription), - value: settingsProvider.shuffleLoopQueue, - onChanged: (value) async { - await settingsProvider.setShuffleLoopQueue(value); - }, - ); - }, - ), - ], - ), - ); - } - Widget _buildKeyboardShortcutsSection() { return Card( child: Column( diff --git a/lib/screens/subtitle_styling_screen.dart b/lib/screens/subtitle_styling_screen.dart index 4c10c7a1..2accf3d9 100644 --- a/lib/screens/subtitle_styling_screen.dart +++ b/lib/screens/subtitle_styling_screen.dart @@ -11,9 +11,105 @@ class SubtitleStylingScreen extends StatefulWidget { State createState() => _SubtitleStylingScreenState(); } -class _SubtitleStylingScreenState extends State { - static const EdgeInsets _sliderPadding = EdgeInsets.fromLTRB(16, 16, 16, 8); +// Composable widget for slider sections +class _StylingSliderSection extends StatelessWidget { + final String label; + final int value; + final double min; + final double max; + final int divisions; + final ValueChanged onChanged; + final ValueChanged? onChangeEnd; + final String Function(int)? valueFormatter; + const _StylingSliderSection({ + required this.label, + required this.value, + required this.min, + required this.max, + required this.divisions, + required this.onChanged, + this.onChangeEnd, + this.valueFormatter, + }); + + @override + Widget build(BuildContext context) { + final formattedValue = valueFormatter?.call(value) ?? value.toString(); + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(label), Text(formattedValue)], + ), + const SizedBox(height: 8), + Row( + children: [ + Text( + valueFormatter?.call(min.toInt()) ?? min.toInt().toString(), + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + Expanded( + child: Slider( + value: value.toDouble(), + min: min, + max: max, + divisions: divisions, + label: formattedValue, + onChanged: onChanged, + onChangeEnd: onChangeEnd, + ), + ), + Text( + valueFormatter?.call(max.toInt()) ?? max.toInt().toString(), + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ], + ), + ); + } +} + +// Composable widget for color picker tiles +class _ColorSettingTile extends StatelessWidget { + final String label; + final String currentColor; + final VoidCallback onTap; + final Color Function(String) hexToColor; + + const _ColorSettingTile({ + required this.label, + required this.currentColor, + required this.onTap, + required this.hexToColor, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: hexToColor(currentColor), + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), + ), + title: Text(label), + subtitle: Text(currentColor), + trailing: const Icon(Icons.chevron_right), + onTap: onTap, + ); + } +} + +class _SubtitleStylingScreenState extends State { late SettingsService _settingsService; bool _isLoading = true; @@ -134,66 +230,27 @@ class _SubtitleStylingScreenState extends State { ), ), // Font Size Slider - Padding( - padding: _sliderPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(t.subtitlingStyling.fontSize), - Text('$_fontSize'), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - const Text( - '30', - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - Expanded( - child: Slider( - value: _fontSize.toDouble(), - min: 30, - max: 80, - divisions: 50, - label: _fontSize.toString(), - onChanged: (value) { - setState(() { - _fontSize = value.toInt(); - }); - }, - onChangeEnd: (value) { - _settingsService.setSubtitleFontSize(_fontSize); - }, - ), - ), - const Text( - '80', - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - ], - ), - ], - ), + _StylingSliderSection( + label: t.subtitlingStyling.fontSize, + value: _fontSize, + min: 30, + max: 80, + divisions: 50, + onChanged: (value) { + setState(() { + _fontSize = value.toInt(); + }); + }, + onChangeEnd: (value) { + _settingsService.setSubtitleFontSize(_fontSize); + }, ), const Divider(), // Text Color - ListTile( - leading: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: _hexToColor(_textColor), - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(4), - ), - ), - title: Text(t.subtitlingStyling.textColor), - subtitle: Text(_textColor), - trailing: const Icon(Icons.chevron_right), + _ColorSettingTile( + label: t.subtitlingStyling.textColor, + currentColor: _textColor, + hexToColor: _hexToColor, onTap: () { _showColorPicker(t.subtitlingStyling.textColor, _textColor, ( color, @@ -207,66 +264,27 @@ class _SubtitleStylingScreenState extends State { ), const Divider(), // Border Size Slider - Padding( - padding: _sliderPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(t.subtitlingStyling.borderSize), - Text('$_borderSize'), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - const Text( - '0', - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - Expanded( - child: Slider( - value: _borderSize.toDouble(), - min: 0, - max: 5, - divisions: 5, - label: _borderSize.toString(), - onChanged: (value) { - setState(() { - _borderSize = value.toInt(); - }); - }, - onChangeEnd: (value) { - _settingsService.setSubtitleBorderSize(_borderSize); - }, - ), - ), - const Text( - '5', - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - ], - ), - ], - ), + _StylingSliderSection( + label: t.subtitlingStyling.borderSize, + value: _borderSize, + min: 0, + max: 5, + divisions: 5, + onChanged: (value) { + setState(() { + _borderSize = value.toInt(); + }); + }, + onChangeEnd: (value) { + _settingsService.setSubtitleBorderSize(_borderSize); + }, ), const Divider(), // Border Color - ListTile( - leading: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: _hexToColor(_borderColor), - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(4), - ), - ), - title: Text(t.subtitlingStyling.borderColor), - subtitle: Text(_borderColor), - trailing: const Icon(Icons.chevron_right), + _ColorSettingTile( + label: t.subtitlingStyling.borderColor, + currentColor: _borderColor, + hexToColor: _hexToColor, onTap: () { _showColorPicker(t.subtitlingStyling.borderColor, _borderColor, ( color, @@ -280,68 +298,28 @@ class _SubtitleStylingScreenState extends State { ), const Divider(), // Background Opacity Slider - Padding( - padding: _sliderPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(t.subtitlingStyling.backgroundOpacity), - Text('$_backgroundOpacity%'), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - const Text( - '0%', - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - Expanded( - child: Slider( - value: _backgroundOpacity.toDouble(), - min: 0, - max: 100, - divisions: 20, - label: '$_backgroundOpacity%', - onChanged: (value) { - setState(() { - _backgroundOpacity = value.toInt(); - }); - }, - onChangeEnd: (value) { - _settingsService.setSubtitleBackgroundOpacity( - _backgroundOpacity, - ); - }, - ), - ), - const Text( - '100%', - style: TextStyle(fontSize: 12, color: Colors.grey), - ), - ], - ), - ], - ), + _StylingSliderSection( + label: t.subtitlingStyling.backgroundOpacity, + value: _backgroundOpacity, + min: 0, + max: 100, + divisions: 20, + valueFormatter: (value) => '$value%', + onChanged: (value) { + setState(() { + _backgroundOpacity = value.toInt(); + }); + }, + onChangeEnd: (value) { + _settingsService.setSubtitleBackgroundOpacity(_backgroundOpacity); + }, ), const Divider(), // Background Color - ListTile( - leading: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: _hexToColor(_backgroundColor), - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(4), - ), - ), - title: Text(t.subtitlingStyling.backgroundColor), - subtitle: Text(_backgroundColor), - trailing: const Icon(Icons.chevron_right), + _ColorSettingTile( + label: t.subtitlingStyling.backgroundColor, + currentColor: _backgroundColor, + hexToColor: _hexToColor, onTap: () { _showColorPicker( t.subtitlingStyling.backgroundColor, diff --git a/lib/screens/video_player_screen.dart b/lib/screens/video_player_screen.dart index 7b32b2e7..fe250656 100644 --- a/lib/screens/video_player_screen.dart +++ b/lib/screens/video_player_screen.dart @@ -13,9 +13,9 @@ import '../models/plex_media_version.dart'; import '../models/plex_metadata.dart'; import '../models/plex_user_profile.dart'; import '../providers/playback_state_provider.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; -import '../providers/settings_provider.dart'; +import '../services/episode_navigation_service.dart'; +import '../services/media_controls_manager.dart'; +import '../services/playback_progress_tracker.dart'; import '../services/settings_service.dart'; import '../utils/app_logger.dart'; import '../utils/language_codes.dart'; @@ -51,51 +51,42 @@ class VideoPlayerScreenState extends State Player? player; VideoController? controller; bool _isPlayerInitialized = false; - Timer? _progressTimer; PlexMetadata? _nextEpisode; PlexMetadata? _previousEpisode; bool _isLoadingNext = false; bool _showPlayNextDialog = false; - PlexClient? _cachedClient; bool _isPhone = false; List _availableVersions = []; StreamSubscription? _logSubscription; StreamSubscription? _errorSubscription; StreamSubscription? _playingSubscription; StreamSubscription? _completedSubscription; - StreamSubscription? _positionSubscription; StreamSubscription? _mediaControlSubscription; StreamSubscription? _bufferingSubscription; + StreamSubscription? _trackLoadingSubscription; bool _isReplacingWithVideo = false; // Flag to skip orientation restoration during video-to-video navigation // App lifecycle state tracking bool _wasPlayingBeforeInactive = false; + // Services + MediaControlsManager? _mediaControlsManager; + PlaybackProgressTracker? _progressTracker; + final EpisodeNavigationService _episodeNavigation = + EpisodeNavigationService(); + /// Get the correct PlexClient for this metadata's server - PlexClient? _getClientForMetadata(BuildContext context) { - final serverId = widget.metadata.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - appLogger.w('Metadata ${widget.metadata.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + PlexClient _getClientForMetadata(BuildContext context) { + return context.getClientForServer(widget.metadata.serverId); } // BoxFit mode state: 0=contain (letterbox), 1=cover (fill screen), 2=fill (stretch) int _boxFitMode = 0; bool _isPinching = false; // Track if a pinch gesture is occurring - bool _isBuffering = false; // Track if video is currently buffering + final ValueNotifier _isBuffering = ValueNotifier( + false, + ); // Track if video is currently buffering // Video cropping state for fill screen mode Size? _playerSize; @@ -123,19 +114,23 @@ class VideoPlayerScreenState extends State try { final playbackState = context.read(); - // If this item doesn't have a playQueueItemID, it's a standalone item - // Clear any existing queue so next/previous work correctly for this content - if (widget.metadata.playQueueItemID == null) { - // Defer clearing until after the first frame to avoid calling - // notifyListeners() during build - WidgetsBinding.instance.addPostFrameCallback((_) { + // Defer both operations until after the first frame to avoid calling + // notifyListeners() during build + WidgetsBinding.instance.addPostFrameCallback((_) { + // If this item doesn't have a playQueueItemID, it's a standalone item + // Clear any existing queue so next/previous work correctly for this content + if (widget.metadata.playQueueItemID == null) { playbackState.clearShuffle(); - }); - } else { - playbackState.setCurrentItem(widget.metadata); - } + } else { + playbackState.setCurrentItem(widget.metadata); + } + }); } catch (e) { - // Provider might not be available yet + // Provider might not be available yet during initialization + appLogger.d( + 'Deferred playback state update (provider not ready)', + error: e, + ); } // Register app lifecycle observer @@ -149,14 +144,6 @@ class VideoPlayerScreenState extends State void didChangeDependencies() { super.didChangeDependencies(); - // Cache server-specific client for safe access in dispose() and during playback - try { - _cachedClient = _getClientForMetadata(context); - } catch (e) { - appLogger.w('Failed to cache PlexClient', error: e); - _cachedClient = null; - } - // Cache device type for safe access in dispose() try { _isPhone = PlatformDetector.isPhone(context); @@ -202,7 +189,17 @@ class VideoPlayerScreenState extends State case AppLifecycleState.resumed: // Restore media controls when app is resumed if (_isPlayerInitialized && mounted) { - _updateMediaMetadata(); + // Restore media metadata + final client = _getClientForMetadata(context); + if (client != null && _mediaControlsManager != null) { + _mediaControlsManager!.updateMetadata( + metadata: widget.metadata, + client: client, + duration: widget.metadata.duration != null + ? Duration(milliseconds: widget.metadata.duration!) + : null, + ); + } // Resume playback if it was playing before going inactive if (_wasPlayingBeforeInactive && player != null) { @@ -335,25 +332,16 @@ class VideoPlayerScreenState extends State // Listen to MPV errors _errorSubscription = player!.stream.error.listen(_onPlayerError); - // Listen to position updates for media controls - _positionSubscription = player!.stream.position.listen((_) { - _updateMediaControlsPosition(); - }); - // Listen to buffering state _bufferingSubscription = player!.stream.buffering.listen((isBuffering) { - if (mounted) { - setState(() { - _isBuffering = isBuffering; - }); - } + _isBuffering.value = isBuffering; }); - // Initialize OS media controls - _initializeMediaControls(); + // Initialize services + await _initializeServices(); - // Start periodic progress updates - _startProgressTracking(); + // Ensure play queue exists for sequential playback + await _ensurePlayQueue(); // Load next/previous episodes _loadAdjacentEpisodes(); @@ -367,80 +355,198 @@ class VideoPlayerScreenState extends State } } + /// Initialize the service layer + Future _initializeServices() async { + if (!mounted || player == null) return; + + final client = _getClientForMetadata(context); + if (client == null) return; + + // Initialize progress tracker + _progressTracker = PlaybackProgressTracker( + client: client, + metadata: widget.metadata, + player: player!, + ); + _progressTracker!.startTracking(); + + // Initialize media controls manager + _mediaControlsManager = MediaControlsManager(); + + // Set up media control event handling + _mediaControlSubscription = _mediaControlsManager!.controlEvents.listen(( + event, + ) { + if (event is PlayEvent) { + appLogger.d('Media control: Play event received'); + if (player != null) { + player!.play(); + _wasPlayingBeforeInactive = false; + appLogger.d( + 'Cleared _wasPlayingBeforeInactive due to manual play via media controls', + ); + _updateMediaControlsPlaybackState(); + } + } else if (event is PauseEvent) { + appLogger.d('Media control: Pause event received'); + if (player != null) { + player!.pause(); + appLogger.d('Video paused via media controls'); + _updateMediaControlsPlaybackState(); + } + } else if (event is SeekEvent) { + appLogger.d('Media control: Seek event received to ${event.position}'); + player?.seek(event.position); + } else if (event is NextTrackEvent) { + appLogger.d('Media control: Next track event received'); + if (_nextEpisode != null) { + _playNext(); + } + } else if (event is PreviousTrackEvent) { + appLogger.d('Media control: Previous track event received'); + if (_previousEpisode != null) { + _playPrevious(); + } + } + }); + + // Update media metadata + await _mediaControlsManager!.updateMetadata( + metadata: widget.metadata, + client: client, + duration: widget.metadata.duration != null + ? Duration(milliseconds: widget.metadata.duration!) + : null, + ); + + if (!mounted) return; + + // Set controls enabled based on content type + final playbackState = context.read(); + final isEpisode = widget.metadata.type.toLowerCase() == 'episode'; + final isInPlaylist = playbackState.isPlaylistActive; + + await _mediaControlsManager!.setControlsEnabled( + canGoNext: isEpisode || isInPlaylist, + canGoPrevious: isEpisode || isInPlaylist, + ); + + // Listen to playing state and update media controls + player!.stream.playing.listen((isPlaying) { + _updateMediaControlsPlaybackState(); + }); + + // Listen to position updates for media controls + player!.stream.position.listen((position) { + _mediaControlsManager?.updatePlaybackState( + isPlaying: player!.state.playing, + position: position, + speed: player!.state.rate, + ); + }); + } + + /// Ensure a play queue exists for sequential episode playback + Future _ensurePlayQueue() async { + if (!mounted) return; + + // Only create play queues for episodes + if (widget.metadata.type.toLowerCase() != 'episode') { + return; + } + + try { + final client = _getClientForMetadata(context); + if (client == null) return; + + final playbackState = context.read(); + + // Determine the show's rating key + // For episodes, grandparentRatingKey points to the show + final showRatingKey = widget.metadata.grandparentRatingKey; + if (showRatingKey == null) { + appLogger.d( + 'Episode missing grandparentRatingKey, skipping play queue creation', + ); + return; + } + + // Check if there's already an active queue for this show + final existingContextKey = playbackState.shuffleContextKey; + final isQueueActive = playbackState.isQueueActive; + + if (isQueueActive && existingContextKey == showRatingKey) { + // Queue already exists for this show, just update the current item + playbackState.setCurrentItem(widget.metadata); + appLogger.d('Using existing play queue for show $showRatingKey'); + return; + } + + // Create a new sequential play queue for the show + appLogger.d('Creating sequential play queue for show $showRatingKey'); + final playQueue = await client.createShowPlayQueue( + showRatingKey: showRatingKey, + shuffle: 0, // Sequential order + startingEpisodeKey: widget.metadata.ratingKey, + ); + + if (playQueue != null && + playQueue.items != null && + playQueue.items!.isNotEmpty) { + // Initialize playback state with the play queue + await playbackState.setPlaybackFromPlayQueue( + playQueue, + showRatingKey, + serverId: widget.metadata.serverId, + serverName: widget.metadata.serverName, + ); + + // Set the client for loading more items + playbackState.setClient(client); + + appLogger.d( + 'Sequential play queue created with ${playQueue.items!.length} items', + ); + } + } catch (e) { + // Non-critical: Sequential playback will fall back to non-queue navigation + appLogger.d( + 'Could not create play queue for sequential playback', + error: e, + ); + } + } + Future _loadAdjacentEpisodes() async { + if (!mounted) return; + try { // Use server-specific client for this metadata final client = _getClientForMetadata(context); if (client == null) return; - final playbackState = context.read(); - final settingsProvider = context.read(); - - PlexMetadata? next; - PlexMetadata? previous; - - // Check if playlist mode is active (takes priority) - if (playbackState.isPlaylistActive) { - // For playlists, always use the queue regardless of item type - // Playlists can contain both movies and episodes - next = await playbackState.getNextEpisode( - widget.metadata.ratingKey, - loopQueue: false, // Don't loop playlists by default - ); - previous = await playbackState.getPreviousEpisode( - widget.metadata.ratingKey, - ); - } - // Check if shuffle mode is active - else if (playbackState.isShuffleActive) { - // Only works for episodes in shuffle mode - if (widget.metadata.type.toLowerCase() != 'episode') { - return; - } - - // Get settings - final shuffleOrderNavigation = settingsProvider.shuffleOrderNavigation; - final loopQueue = settingsProvider.shuffleLoopQueue; - - if (shuffleOrderNavigation) { - // Use shuffled order for next/previous - next = await playbackState.getNextEpisode( - widget.metadata.ratingKey, - loopQueue: loopQueue, - ); - previous = await playbackState.getPreviousEpisode( - widget.metadata.ratingKey, - ); - } else { - // Use chronological order even in shuffle mode - next = await client.findAdjacentEpisode(widget.metadata, 1); - previous = await client.findAdjacentEpisode(widget.metadata, -1); - } - } - // Normal sequential playback - else { - // Only works for episodes in sequential mode - if (widget.metadata.type.toLowerCase() != 'episode') { - return; - } - - // Use normal sequential episode loading - next = await client.findAdjacentEpisode(widget.metadata, 1); - previous = await client.findAdjacentEpisode(widget.metadata, -1); - } + // Load adjacent episodes using the service + final adjacentEpisodes = await _episodeNavigation.loadAdjacentEpisodes( + context: context, + client: client, + metadata: widget.metadata, + ); if (mounted) { setState(() { - _nextEpisode = next; - _previousEpisode = previous; + _nextEpisode = adjacentEpisodes.next; + _previousEpisode = adjacentEpisodes.previous; }); } } catch (e) { - // Silently handle errors + // Non-critical: Failed to load next/previous episode metadata + appLogger.d('Could not load adjacent episodes', error: e); } } Future _startPlayback() async { + if (!mounted) return; + try { // Use server-specific client for this metadata final client = _getClientForMetadata(context); @@ -614,7 +720,36 @@ class VideoPlayerScreenState extends State } } + /// Calculates crop parameters for "fill screen" mode (BoxFit.cover) to eliminate letterboxing. + /// + /// This method is only active when [_boxFitMode] == 1 (cover mode). It determines how to + /// crop the video to completely fill the player area while maintaining aspect ratio. + /// + /// **How it works:** + /// 1. Compares video aspect ratio vs player aspect ratio + /// 2. Crops the dimension that would create letterboxing: + /// - Wide video (16:9) on tall player (4:3): crops left/right sides + /// - Tall video (4:3) on wide player (16:9): crops top/bottom + /// 3. Centers the crop within the video + /// 4. Calculates subtitle margin adjustments to keep subtitles visible + /// + /// **Subtitle positioning:** + /// MPV uses a 720p reference coordinate system for subtitle positioning. + /// When cropping zooms the video, subtitles need larger margins to avoid + /// being cropped or appearing too close to edges. + /// + /// Returns `null` if: + /// - Not in cover mode (_boxFitMode != 1) + /// - Player or video size is unknown + /// - Aspect ratios are too similar (< 0.01 difference) - no crop needed + /// + /// Returns a map containing: + /// - `width`, `height`: Dimensions of the cropped area in video pixels + /// - `x`, `y`: Crop offset from video's top-left corner in pixels + /// - `subMarginX`, `subMarginY`: Subtitle margins in MPV coordinate space (720p reference) + /// - `subScale`: Subtitle scaling factor (currently always 1.0) Map? _calculateCropParameters() { + // Only calculate for cover mode with known dimensions if (_boxFitMode != 1 || _playerSize == null || _videoSize == null) { return null; } @@ -622,37 +757,49 @@ class VideoPlayerScreenState extends State final playerAspect = _playerSize!.width / _playerSize!.height; final videoAspect = _videoSize!.width / _videoSize!.height; + // No cropping needed if aspect ratios are very similar if ((playerAspect - videoAspect).abs() < 0.01) return null; late final int cropW, cropH, cropX, cropY; if (videoAspect > playerAspect) { - // crop sides + // Video is wider than player - crop left/right sides + // Example: 16:9 video in 4:3 player final scale = _playerSize!.height / _videoSize!.height; cropH = _videoSize!.height.toInt(); cropW = (_playerSize!.width / scale).toInt(); - cropX = ((_videoSize!.width - cropW) ~/ 2); + cropX = ((_videoSize!.width - cropW) ~/ 2); // Center horizontally cropY = 0; } else { - // crop top/bottom — your case + // Video is taller than player - crop top/bottom + // Example: 4:3 video in 16:9 player (most common case) final scale = _playerSize!.width / _videoSize!.width; cropW = _videoSize!.width.toInt(); cropH = (_playerSize!.height / scale).toInt(); cropX = 0; - cropY = ((_videoSize!.height - cropH) ~/ 2); + cropY = ((_videoSize!.height - cropH) ~/ 2); // Center vertically } + // Subtitle positioning constants + /// MPV's subtitle coordinate system height (720p reference) const double kSubCoord = 720.0; - const double baseX = 20.0; - const double baseY = - 45.0; // slightly larger than 40 looks better in practice + /// Base horizontal subtitle margin to prevent edge clipping + const double baseX = 20.0; + + /// Base vertical subtitle margin, tuned to position subtitles + /// comfortably above the bottom while avoiding overscan areas + const double baseY = 45.0; + + // Calculate additional margin needed due to cropping + // When we crop, the visible area is "zoomed in", so subtitles need + // proportionally larger margins to maintain the same visual distance from edges double extraX = cropX > 0 ? (cropX / _videoSize!.width) * kSubCoord * videoAspect : 0.0; double extraY = cropY > 0 ? (cropY / _videoSize!.height) * kSubCoord : 0.0; - // Only increase the margin — never shrink it + // Apply additional margin (never reduce below base) int marginX = (baseX + extraX).round(); int marginY = (baseY + extraY).round(); @@ -757,8 +904,13 @@ class VideoPlayerScreenState extends State // Unregister app lifecycle observer WidgetsBinding.instance.removeObserver(this); - // Stop progress tracking - _progressTimer?.cancel(); + // Dispose value notifiers + _isBuffering.dispose(); + + // Stop progress tracking and send final state + _progressTracker?.sendProgress('stopped'); + _progressTracker?.stopTracking(); + _progressTracker?.dispose(); // Cancel debounce timer _resizeDebounceTimer?.cancel(); @@ -768,15 +920,13 @@ class VideoPlayerScreenState extends State _completedSubscription?.cancel(); _logSubscription?.cancel(); _errorSubscription?.cancel(); - _positionSubscription?.cancel(); _mediaControlSubscription?.cancel(); _bufferingSubscription?.cancel(); + _trackLoadingSubscription?.cancel(); - // Clear OS media controls completely - OsMediaControls.clear(); - - // Send final stopped state - _sendProgress('stopped'); + // Clear media controls and dispose manager + _mediaControlsManager?.clear(); + _mediaControlsManager?.dispose(); // Clear video filter and reset subtitle margins before disposing player try { @@ -788,7 +938,8 @@ class VideoPlayerScreenState extends State nativePlayer.setProperty('sub-scale', '1.0'); } } catch (e) { - // Ignore errors during cleanup + // Non-critical: Cleanup operations during disposal + appLogger.d('Error during player cleanup in dispose', error: e); } // Restore system UI and orientation preferences (skip if navigating to another video) @@ -821,15 +972,6 @@ class VideoPlayerScreenState extends State super.dispose(); } - void _startProgressTracking() { - // Send progress update every 10 seconds - _progressTimer = Timer.periodic(const Duration(seconds: 10), (timer) { - if (player?.state.playing ?? false) { - _sendProgress('playing'); - } - }); - } - /// Generic track matching for audio and subtitle tracks /// Returns the best matching track based on hierarchical criteria: /// 1. Exact match (id + title + language) @@ -1210,9 +1352,225 @@ class VideoPlayerScreenState extends State return variations.contains(trackBase); } + /// Log available tracks for debugging + void _logAvailableTracks( + List audioTracks, + List subtitleTracks, + ) { + appLogger.d('Available audio tracks: ${audioTracks.length}'); + for (var track in audioTracks) { + appLogger.d( + ' - ${track.title ?? "Track ${track.id}"} (${track.language ?? "unknown"}) ${track.isDefault == true ? "[DEFAULT]" : ""}', + ); + } + appLogger.d('Available subtitle tracks: ${subtitleTracks.length}'); + for (var track in subtitleTracks) { + appLogger.d( + ' - ${track.title ?? "Track ${track.id}"} (${track.language ?? "unknown"}) ${track.isDefault == true ? "[DEFAULT]" : ""}', + ); + } + } + + /// Select the best audio track based on priority: + /// Priority 1: Preferred track from navigation + /// Priority 2: Per-media language preference + /// Priority 3: User profile preferences + /// Priority 4: Default or first track + AudioTrack? _selectAudioTrack( + List availableTracks, + PlexUserProfile? profileSettings, + ) { + if (availableTracks.isEmpty) return null; + + AudioTrack? trackToSelect; + + // Priority 1: Try to match preferred track from navigation + if (widget.preferredAudioTrack != null) { + appLogger.d('Priority 1: Checking preferred track from navigation'); + appLogger.d( + ' Preferred: ${widget.preferredAudioTrack!.title ?? "Track ${widget.preferredAudioTrack!.id}"} (${widget.preferredAudioTrack!.language ?? "unknown"})', + ); + trackToSelect = _findBestAudioMatch( + availableTracks, + widget.preferredAudioTrack!, + ); + if (trackToSelect != null) { + appLogger.d(' Matched preferred track'); + return trackToSelect; + } + appLogger.d(' No match found for preferred track'); + } else { + appLogger.d('Priority 1: No preferred track from navigation'); + } + + // Priority 2: If no preferred track matched, try per-media language preference + if (widget.metadata.audioLanguage != null) { + appLogger.d('Priority 2: Checking per-media audio language preference'); + appLogger.d( + ' Per-media audio language: ${widget.metadata.audioLanguage}', + ); + + final matchedTrack = availableTracks.firstWhere( + (track) => + _languageMatches(track.language, widget.metadata.audioLanguage), + orElse: () => availableTracks.first, + ); + + if (_languageMatches( + matchedTrack.language, + widget.metadata.audioLanguage, + )) { + appLogger.d(' Matched per-media audio language preference'); + return matchedTrack; + } + appLogger.d(' No match found for per-media audio language'); + } else { + appLogger.d('Priority 2: No per-media audio language preference'); + } + + // Priority 3: If no preferred track matched, try user profile preferences + if (profileSettings != null) { + appLogger.d('Priority 3: Checking user profile preferences'); + trackToSelect = _findAudioTrackByProfile( + availableTracks, + profileSettings, + ); + if (trackToSelect != null) { + return trackToSelect; + } + } else { + appLogger.d('Priority 3: No user profile available'); + } + + // Priority 4: If no match, use default or first track + appLogger.d('Priority 4: Using default or first available track'); + trackToSelect = availableTracks.firstWhere( + (t) => t.isDefault == true, + orElse: () => availableTracks.first, + ); + final isDefault = trackToSelect.isDefault == true; + appLogger.d( + ' Selected ${isDefault ? "default" : "first"} track: ${trackToSelect.title ?? "Track ${trackToSelect.id}"} (${trackToSelect.language ?? "unknown"})', + ); + + return trackToSelect; + } + + /// Select the best subtitle track based on priority: + /// Priority 1: Preferred track from navigation + /// Priority 2: Per-media language preference + /// Priority 3: User profile preferences + /// Priority 4: Default track + /// Priority 5: Off + SubtitleTrack _selectSubtitleTrack( + List availableTracks, + PlexUserProfile? profileSettings, + AudioTrack? selectedAudioTrack, + ) { + SubtitleTrack? subtitleToSelect; + + // Priority 1: Try preferred track from navigation (always wins) + if (widget.preferredSubtitleTrack != null) { + appLogger.d('Priority 1: Checking preferred track from navigation'); + if (widget.preferredSubtitleTrack!.id == 'no') { + appLogger.d(' Preferred: OFF'); + return SubtitleTrack.no(); + } else if (availableTracks.isNotEmpty) { + appLogger.d( + ' Preferred: ${widget.preferredSubtitleTrack!.title ?? "Track ${widget.preferredSubtitleTrack!.id}"} (${widget.preferredSubtitleTrack!.language ?? "unknown"})', + ); + subtitleToSelect = _findBestSubtitleMatch( + availableTracks, + widget.preferredSubtitleTrack!, + ); + if (subtitleToSelect != null) { + appLogger.d(' Matched preferred track'); + return subtitleToSelect; + } + appLogger.d(' No match found for preferred track'); + } + } else { + appLogger.d('Priority 1: No preferred track from navigation'); + } + + // Priority 2: If no preferred match, try per-media language preference + if (widget.metadata.subtitleLanguage != null) { + appLogger.d( + 'Priority 2: Checking per-media subtitle language preference', + ); + appLogger.d( + ' Per-media subtitle language: ${widget.metadata.subtitleLanguage}', + ); + + // Check if subtitle should be disabled + if (widget.metadata.subtitleLanguage == 'none' || + widget.metadata.subtitleLanguage!.isEmpty) { + appLogger.d(' Per-media preference: Subtitles OFF'); + return SubtitleTrack.no(); + } else if (availableTracks.isNotEmpty) { + final matchedTrack = availableTracks.firstWhere( + (track) => _languageMatches( + track.language, + widget.metadata.subtitleLanguage, + ), + orElse: () => availableTracks.first, + ); + if (_languageMatches( + matchedTrack.language, + widget.metadata.subtitleLanguage, + )) { + appLogger.d(' Matched per-media subtitle language preference'); + return matchedTrack; + } + appLogger.d(' No match found for per-media subtitle language'); + } + } else { + appLogger.d('Priority 2: No per-media subtitle language preference'); + } + + // Priority 3: If no preferred match, apply user profile preferences + if (profileSettings != null && availableTracks.isNotEmpty) { + appLogger.d('Priority 3: Checking user profile preferences'); + subtitleToSelect = _findSubtitleTrackByProfile( + availableTracks, + profileSettings, + selectedAudioTrack: selectedAudioTrack, + ); + if (subtitleToSelect != null) { + return subtitleToSelect; + } + } else if (availableTracks.isNotEmpty) { + appLogger.d('Priority 3: No user profile available'); + } + + // Priority 4: If no profile match, check for default subtitle + if (availableTracks.isNotEmpty) { + appLogger.d('Priority 4: Checking for default subtitle track'); + final defaultTrack = availableTracks.firstWhere( + (t) => t.isDefault == true, + orElse: () => availableTracks.first, + ); + if (defaultTrack.isDefault == true) { + appLogger.d( + ' Found default track: ${defaultTrack.title ?? "Track ${defaultTrack.id}"} (${defaultTrack.language ?? "unknown"})', + ); + return defaultTrack; + } + appLogger.d(' No default subtitle track found'); + } + + // Priority 5: If still no subtitle selected, turn off + appLogger.d('Priority 5: No subtitle selected - Subtitles OFF'); + return SubtitleTrack.no(); + } + void _waitForTracksAndApply() async { - // Helper function to process tracks + if (!mounted) return; + + // Process tracks and apply selections Future processTracks(Tracks tracks) async { + if (!mounted) return; + appLogger.d('Starting track selection process'); // Get profile settings for track selection @@ -1226,212 +1584,36 @@ class VideoPlayerScreenState extends State .where((t) => t.id != 'auto' && t.id != 'no') .toList(); - appLogger.d('Available audio tracks: ${realAudioTracks.length}'); - for (var track in realAudioTracks) { - appLogger.d( - ' - ${track.title ?? "Track ${track.id}"} (${track.language ?? "unknown"}) ${track.isDefault == true ? "[DEFAULT]" : ""}', - ); - } - appLogger.d('Available subtitle tracks: ${realSubtitleTracks.length}'); - for (var track in realSubtitleTracks) { - appLogger.d( - ' - ${track.title ?? "Track ${track.id}"} (${track.language ?? "unknown"}) ${track.isDefault == true ? "[DEFAULT]" : ""}', - ); - } + // Log available tracks + _logAvailableTracks(realAudioTracks, realSubtitleTracks); - // Select audio track with priority: preferred > per-media > user profile > default > first + // Select and apply audio track appLogger.d('Audio track selection'); - if (realAudioTracks.isNotEmpty) { - AudioTrack? trackToSelect; - - // Priority 1: Try to match preferred track from navigation - if (widget.preferredAudioTrack != null) { - appLogger.d('Priority 1: Checking preferred track from navigation'); - appLogger.d( - ' Preferred: ${widget.preferredAudioTrack!.title ?? "Track ${widget.preferredAudioTrack!.id}"} (${widget.preferredAudioTrack!.language ?? "unknown"})', - ); - trackToSelect = _findBestAudioMatch( - realAudioTracks, - widget.preferredAudioTrack!, - ); - if (trackToSelect != null) { - appLogger.d(' Matched preferred track'); - } else { - appLogger.d(' No match found for preferred track'); - } - } else { - appLogger.d('Priority 1: No preferred track from navigation'); - } - - // Priority 2: If no preferred track matched, try per-media language preference - if (trackToSelect == null && widget.metadata.audioLanguage != null) { - appLogger.d( - 'Priority 2: Checking per-media audio language preference', - ); - appLogger.d( - ' Per-media audio language: ${widget.metadata.audioLanguage}', - ); - trackToSelect = realAudioTracks.firstWhere( - (track) => - _languageMatches(track.language, widget.metadata.audioLanguage), - orElse: () => realAudioTracks.first, - ); - if (_languageMatches( - trackToSelect.language, - widget.metadata.audioLanguage, - )) { - appLogger.d(' Matched per-media audio language preference'); - } else { - appLogger.d(' No match found for per-media audio language'); - trackToSelect = null; - } - } else if (trackToSelect == null) { - appLogger.d('Priority 2: No per-media audio language preference'); - } - - // Priority 3: If no preferred track matched, try user profile preferences - if (trackToSelect == null && profileSettings != null) { - appLogger.d('Priority 3: Checking user profile preferences'); - trackToSelect = _findAudioTrackByProfile( - realAudioTracks, - profileSettings, - ); - } else if (trackToSelect == null) { - appLogger.d('Priority 3: No user profile available'); - } - - // Priority 4: If no match, use default or first track - if (trackToSelect == null) { - appLogger.d('Priority 4: Using default or first available track'); - trackToSelect = realAudioTracks.firstWhere( - (t) => t.isDefault == true, - orElse: () => realAudioTracks.first, - ); - final isDefault = trackToSelect.isDefault == true; - appLogger.d( - ' Selected ${isDefault ? "default" : "first"} track: ${trackToSelect.title ?? "Track ${trackToSelect.id}"} (${trackToSelect.language ?? "unknown"})', - ); - } - + final selectedAudioTrack = _selectAudioTrack( + realAudioTracks, + profileSettings, + ); + if (selectedAudioTrack != null) { appLogger.i( - 'Final audio selection: ${trackToSelect.title ?? "Track ${trackToSelect.id}"} (${trackToSelect.language ?? "unknown"})', + 'Final audio selection: ${selectedAudioTrack.title ?? "Track ${selectedAudioTrack.id}"} (${selectedAudioTrack.language ?? "unknown"})', ); - player!.setAudioTrack(trackToSelect); + player!.setAudioTrack(selectedAudioTrack); } else { appLogger.d('No audio tracks available'); } - // Select subtitle track with priority: preferred > per-media > user profile > default > off + // Select and apply subtitle track appLogger.d('Subtitle track selection'); - SubtitleTrack? subtitleToSelect; - - // Priority 1: Try preferred track from navigation (always wins) - if (widget.preferredSubtitleTrack != null) { - appLogger.d('Priority 1: Checking preferred track from navigation'); - if (widget.preferredSubtitleTrack!.id == 'no') { - appLogger.d(' Preferred: OFF'); - subtitleToSelect = SubtitleTrack.no(); - appLogger.d(' Using preferred setting: Subtitles OFF'); - } else if (realSubtitleTracks.isNotEmpty) { - appLogger.d( - ' Preferred: ${widget.preferredSubtitleTrack!.title ?? "Track ${widget.preferredSubtitleTrack!.id}"} (${widget.preferredSubtitleTrack!.language ?? "unknown"})', - ); - subtitleToSelect = _findBestSubtitleMatch( - realSubtitleTracks, - widget.preferredSubtitleTrack!, - ); - if (subtitleToSelect != null) { - appLogger.d(' Matched preferred track'); - } else { - appLogger.d(' No match found for preferred track'); - } - } - } else { - appLogger.d('Priority 1: No preferred track from navigation'); - } - - // Priority 2: If no preferred match, try per-media language preference - if (subtitleToSelect == null && - widget.metadata.subtitleLanguage != null) { - appLogger.d( - 'Priority 2: Checking per-media subtitle language preference', - ); - appLogger.d( - ' Per-media subtitle language: ${widget.metadata.subtitleLanguage}', - ); - // Check if subtitle should be disabled - if (widget.metadata.subtitleLanguage == 'none' || - widget.metadata.subtitleLanguage!.isEmpty) { - appLogger.d(' Per-media preference: Subtitles OFF'); - subtitleToSelect = SubtitleTrack.no(); - } else if (realSubtitleTracks.isNotEmpty) { - final matchedTrack = realSubtitleTracks.firstWhere( - (track) => _languageMatches( - track.language, - widget.metadata.subtitleLanguage, - ), - orElse: () => realSubtitleTracks.first, - ); - if (_languageMatches( - matchedTrack.language, - widget.metadata.subtitleLanguage, - )) { - subtitleToSelect = matchedTrack; - appLogger.d(' Matched per-media subtitle language preference'); - } else { - appLogger.d(' No match found for per-media subtitle language'); - } - } - } else if (subtitleToSelect == null) { - appLogger.d('Priority 2: No per-media subtitle language preference'); - } - - // Priority 3: If no preferred match, apply user profile preferences - if (subtitleToSelect == null && - profileSettings != null && - realSubtitleTracks.isNotEmpty) { - appLogger.d('Priority 3: Checking user profile preferences'); - // Get the currently selected audio track - final currentAudioTrack = realAudioTracks.firstWhere( - (t) => t.id == player!.state.track.audio.id, - orElse: () => realAudioTracks.first, - ); - subtitleToSelect = _findSubtitleTrackByProfile( - realSubtitleTracks, - profileSettings, - selectedAudioTrack: currentAudioTrack, - ); - } else if (subtitleToSelect == null && realSubtitleTracks.isNotEmpty) { - appLogger.d('Priority 3: No user profile available'); - } - - // Priority 4: If no profile match, check for default subtitle - if (subtitleToSelect == null && realSubtitleTracks.isNotEmpty) { - appLogger.d('Priority 4: Checking for default subtitle track'); - final defaultTrackIndex = realSubtitleTracks.indexWhere( - (t) => t.isDefault == true, - ); - if (defaultTrackIndex != -1) { - subtitleToSelect = realSubtitleTracks[defaultTrackIndex]; - appLogger.d( - ' Found default track: ${subtitleToSelect.title ?? "Track ${subtitleToSelect.id}"} (${subtitleToSelect.language ?? "unknown"})', - ); - } else { - appLogger.d(' No default subtitle track found'); - } - } - - // If still no subtitle selected, turn off - if (subtitleToSelect == null) { - appLogger.d('Priority 5: No subtitle selected - Subtitles OFF'); - subtitleToSelect = SubtitleTrack.no(); - } - - final finalSubtitle = subtitleToSelect.id == 'no' + final selectedSubtitleTrack = _selectSubtitleTrack( + realSubtitleTracks, + profileSettings, + selectedAudioTrack, + ); + final finalSubtitle = selectedSubtitleTrack.id == 'no' ? 'OFF' - : '${subtitleToSelect.title ?? "Track ${subtitleToSelect.id}"} (${subtitleToSelect.language ?? "unknown"})'; + : '${selectedSubtitleTrack.title ?? "Track ${selectedSubtitleTrack.id}"} (${selectedSubtitleTrack.language ?? "unknown"})'; appLogger.i('Final subtitle selection: $finalSubtitle'); - player!.setSubtitleTrack(subtitleToSelect); + player!.setSubtitleTrack(selectedSubtitleTrack); // Set playback rate if preferred rate was provided if (widget.preferredPlaybackRate != null) { @@ -1453,52 +1635,34 @@ class VideoPlayerScreenState extends State // If not, listen to tracks stream for when they become available bool applied = false; - final subscription = player!.stream.tracks.listen((tracks) async { + _trackLoadingSubscription = player!.stream.tracks.listen((tracks) async { // Check if tracks are loaded (have at least one track) and not yet applied if (!applied && (tracks.audio.isNotEmpty || tracks.subtitle.isNotEmpty)) { applied = true; await processTracks(tracks); + // Cancel subscription after successful processing + _trackLoadingSubscription?.cancel(); + _trackLoadingSubscription = null; } }); - // Cancel subscription after timeout + // Cancel subscription after timeout if still waiting Future.delayed(const Duration(seconds: 5), () { - subscription.cancel(); + if (!applied) { + _trackLoadingSubscription?.cancel(); + _trackLoadingSubscription = null; + } }); } void _onPlayingStateChanged(bool isPlaying) { // Send timeline update when playback state changes - _sendProgress(isPlaying ? 'playing' : 'paused'); + _progressTracker?.sendProgress(isPlaying ? 'playing' : 'paused'); // Update OS media controls playback state _updateMediaControlsPlaybackState(); } - void _sendProgress(String state) { - // Don't send misleading data if player isn't available - if (player == null) return; - - final position = player!.state.position.inMilliseconds; - final duration = player!.state.duration.inMilliseconds; - - if (duration > 0) { - final client = _cachedClient; - if (client == null) return; - - client - .updateProgress( - widget.metadata.ratingKey, - time: position, - state: state, - duration: duration, - ) - .catchError((error) { - // Silently handle errors - don't interrupt playback - }); - } - } - void _onVideoCompleted(bool completed) { if (completed && _nextEpisode != null && !_showPlayNextDialog) { setState(() { @@ -1536,162 +1700,18 @@ class VideoPlayerScreenState extends State // OS Media Controls Integration - void _initializeMediaControls() async { - // Listen to media control events - _mediaControlSubscription = OsMediaControls.controlEvents.listen((event) { - if (event is PlayEvent) { - appLogger.d('Media control: Play event received'); - if (player != null) { - player!.play(); - // Clear the inactive state flag since user manually resumed via media controls - _wasPlayingBeforeInactive = false; - appLogger.d( - 'Cleared _wasPlayingBeforeInactive due to manual play via media controls', - ); - // Update media controls to reflect the new state - _updateMediaControlsPlaybackState(); - } - } else if (event is PauseEvent) { - appLogger.d('Media control: Pause event received'); - if (player != null) { - player!.pause(); - // Don't set _wasPlayingBeforeInactive here - this is a manual user action - // The flag should only be set during automatic app lifecycle pauses - appLogger.d('Video paused via media controls'); - // Update media controls to reflect the new state - _updateMediaControlsPlaybackState(); - } - } else if (event is SeekEvent) { - appLogger.d('Media control: Seek event received to ${event.position}'); - player?.seek(event.position); - } else if (event is NextTrackEvent) { - appLogger.d('Media control: Next track event received'); - if (_nextEpisode != null) { - _playNext(); - } - } else if (event is PreviousTrackEvent) { - appLogger.d('Media control: Previous track event received'); - if (_previousEpisode != null) { - _playPrevious(); - } - } - }); - - // Enable/disable next/previous track controls based on content type and playback mode - final playbackState = context.read(); - final isEpisode = widget.metadata.type.toLowerCase() == 'episode'; - final isInPlaylist = playbackState.isPlaylistActive; - - // Enable controls for episodes OR playlist items - if (isEpisode || isInPlaylist) { - // Enable next/previous track controls for episodes and playlist items - await OsMediaControls.enableControls([ - MediaControl.next, - MediaControl.previous, - ]); - } else { - // Disable next/previous track controls for standalone movies - await OsMediaControls.disableControls([ - MediaControl.next, - MediaControl.previous, - ]); - } - - // Set initial metadata - await _updateMediaMetadata(); - } - - Future _updateMediaMetadata() async { - if (!mounted) { - appLogger.w('Cannot update media metadata: widget not mounted'); - return; - } - - final metadata = widget.metadata; - // Use server-specific client for this metadata - final client = _getClientForMetadata(context); - - // Get artwork URL - String? artworkUrl; - if (client == null) { - appLogger.w( - 'Cannot get artwork URL for media controls: Plex client is null', - ); - } else { - final thumbUrl = metadata.type.toLowerCase() == 'episode' - ? metadata.grandparentThumb ?? metadata.thumb - : metadata.thumb; - - if (thumbUrl != null) { - try { - artworkUrl = client.getThumbnailUrl(thumbUrl); - appLogger.d('Artwork URL for media controls: $artworkUrl'); - } catch (e) { - appLogger.w('Failed to get artwork URL for media controls', error: e); - } - } else { - appLogger.d('No thumbnail URL available for media controls'); - } - } - - // Build title/artist based on content type - String title = metadata.title; - String? artist; - String? album; - - if (metadata.type.toLowerCase() == 'episode') { - title = metadata.title; - artist = metadata.grandparentTitle; // Show name - if (metadata.parentIndex != null) { - album = 'Season ${metadata.parentIndex}'; - } - } - - await OsMediaControls.setMetadata( - MediaMetadata( - title: title, - artist: artist, - album: album, - duration: metadata.duration != null - ? Duration(milliseconds: metadata.duration!) - : null, - artworkUrl: artworkUrl, - ), - ); - - // Set initial playback state - _updateMediaControlsPlaybackState(); - } - + /// Wrapper method to update media controls playback state void _updateMediaControlsPlaybackState() { if (player == null) return; - OsMediaControls.setPlaybackState( - MediaPlaybackState( - state: player!.state.playing - ? PlaybackState.playing - : PlaybackState.paused, - position: player!.state.position, - speed: player!.state.rate, - ), + _mediaControlsManager?.updatePlaybackState( + isPlaying: player!.state.playing, + position: player!.state.position, + speed: player!.state.rate, + force: true, // Force update since this is an explicit state change ); } - void _updateMediaControlsPosition() { - if (player == null) return; - - // Only update if playing to avoid excessive updates - if (player!.state.playing) { - OsMediaControls.setPlaybackState( - MediaPlaybackState( - state: PlaybackState.playing, - position: player!.state.position, - speed: player!.state.rate, - ), - ); - } - } - Future _playNext() async { if (_nextEpisode == null || _isLoadingNext) return; @@ -1828,18 +1848,31 @@ class VideoPlayerScreenState extends State return; } - // Capture current track selection BEFORE pausing - final currentAudioTrack = player!.state.track.audio; - final currentSubtitleTrack = player!.state.track.subtitle; + // Capture current state atomically to avoid race conditions + final currentPlayer = player; + if (currentPlayer == null) { + // Player already disposed, navigate without preserving settings + if (mounted) { + navigateToVideoPlayer( + context, + metadata: episodeMetadata, + usePushReplacement: true, + ); + } + return; + } + + final currentAudioTrack = currentPlayer.state.track.audio; + final currentSubtitleTrack = currentPlayer.state.track.subtitle; + final currentRate = currentPlayer.state.rate; // Pause and stop current playback - player!.pause(); - _progressTimer?.cancel(); - _sendProgress('stopped'); + currentPlayer.pause(); + _progressTracker?.sendProgress('stopped'); + _progressTracker?.stopTracking(); // Navigate to the episode using pushReplacement to destroy current player if (mounted) { - final currentRate = player!.state.rate; navigateToVideoPlayer( context, metadata: episodeMetadata, @@ -1861,6 +1894,9 @@ class VideoPlayerScreenState extends State ); } + // Cache platform detection to avoid multiple calls + final isMobile = PlatformDetector.isMobile(context); + return PopScope( canPop: false, // Disable swipe-back gesture to prevent interference with timeline scrubbing @@ -1877,19 +1913,19 @@ class VideoPlayerScreenState extends State .translucent, // Allow taps to pass through to controls onScaleStart: (details) { // Initialize pinch gesture tracking (mobile only) - if (!PlatformDetector.isMobile(context)) return; + if (!isMobile) return; _isPinching = false; }, onScaleUpdate: (details) { // Track if this is a pinch gesture (2+ fingers) on mobile - if (!PlatformDetector.isMobile(context)) return; + if (!isMobile) return; if (details.pointerCount >= 2) { _isPinching = true; } }, onScaleEnd: (details) { // Only toggle if we detected a pinch gesture on mobile - if (!PlatformDetector.isMobile(context)) return; + if (!isMobile) return; if (_isPinching) { _toggleContainCover(); _isPinching = false; @@ -2055,22 +2091,27 @@ class VideoPlayerScreenState extends State ), ), // Buffering indicator - if (_isBuffering) - Positioned.fill( - child: Center( - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.5), - shape: BoxShape.circle, - ), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, + ValueListenableBuilder( + valueListenable: _isBuffering, + builder: (context, isBuffering, child) { + if (!isBuffering) return const SizedBox.shrink(); + return Positioned.fill( + child: Center( + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.5), + shape: BoxShape.circle, + ), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), ), ), - ), - ), + ); + }, + ), ], ), ), diff --git a/lib/services/data_aggregation_service.dart b/lib/services/data_aggregation_service.dart index 160d7589..2d4080e1 100644 --- a/lib/services/data_aggregation_service.dart +++ b/lib/services/data_aggregation_service.dart @@ -1,10 +1,12 @@ import 'dart:async'; +import '../client/plex_client.dart'; import '../models/plex_hub.dart'; import '../models/plex_library.dart'; import '../models/plex_metadata.dart'; import '../utils/app_logger.dart'; import 'multi_server_manager.dart'; +import 'plex_auth_service.dart'; /// Service for aggregating data from multiple Plex servers class DataAggregationService { @@ -14,24 +16,9 @@ class DataAggregationService { /// Fetch libraries from all online servers and tag them with server info Future> getLibrariesFromAllServers() async { - final clients = _serverManager.onlineClients; - - if (clients.isEmpty) { - appLogger.w('No online servers available for fetching libraries'); - return []; - } - - appLogger.d('Fetching libraries from ${clients.length} servers'); - - final allLibraries = []; - - // Fetch from all servers in parallel - final libraryFutures = clients.entries.map((entry) async { - final serverId = entry.key; - final client = entry.value; - final server = _serverManager.getServer(serverId); - - try { + return _perServer( + operationName: 'fetching libraries', + operation: (serverId, client, server) async { final libraries = await client.getLibraries(); // Tag each library with server info @@ -51,75 +38,23 @@ class DataAggregationService { serverName: server?.name, ); }).toList(); - } catch (e, stackTrace) { - appLogger.e( - 'Failed to fetch libraries from server $serverId', - error: e, - stackTrace: stackTrace, - ); - // Mark server as offline - _serverManager.updateServerStatus(serverId, false); - return []; - } - }); - - final results = await Future.wait(libraryFutures); - - // Flatten results - for (final libraries in results) { - allLibraries.addAll(libraries); - } - - appLogger.i( - 'Fetched ${allLibraries.length} total libraries from all servers', + }, ); - - return allLibraries; } /// Fetch "On Deck" (Continue Watching) from all servers and merge by recency Future> getOnDeckFromAllServers({int? limit}) async { - final clients = _serverManager.onlineClients; - - if (clients.isEmpty) { - appLogger.w('No online servers available for fetching on deck'); - return []; - } - - appLogger.d('Fetching on deck from ${clients.length} servers'); - - final allOnDeck = []; - - // Fetch from all servers in parallel - final onDeckFutures = clients.entries.map((entry) async { - final serverId = entry.key; - final client = entry.value; - final server = _serverManager.getServer(serverId); - - try { + final allOnDeck = await _perServer( + operationName: 'fetching on deck', + operation: (serverId, client, server) async { final items = await client.getOnDeck(); // Tag each item with server info return items.map((item) { return item.copyWith(serverId: serverId, serverName: server?.name); }).toList(); - } catch (e, stackTrace) { - appLogger.e( - 'Failed to fetch on deck from server $serverId', - error: e, - stackTrace: stackTrace, - ); - _serverManager.updateServerStatus(serverId, false); - return []; - } - }); - - final results = await Future.wait(onDeckFutures); - - // Flatten results - for (final items in results) { - allOnDeck.addAll(items); - } + }, + ); // Sort by most recent (lastViewedAt is stored in viewOffset metadata) // For on deck items, we use updatedAt or addedAt as proxy for recency @@ -249,51 +184,21 @@ class DataAggregationService { String query, { int? limit, }) async { - final clients = _serverManager.onlineClients; - - if (clients.isEmpty) { - appLogger.w('No online servers available for search'); - return []; - } - if (query.trim().isEmpty) { return []; } - appLogger.d('Searching for "$query" across ${clients.length} servers'); - - final allResults = []; - - // Search all servers in parallel - final searchFutures = clients.entries.map((entry) async { - final serverId = entry.key; - final client = entry.value; - final server = _serverManager.getServer(serverId); - - try { + final allResults = await _perServer( + operationName: 'searching for "$query"', + operation: (serverId, client, server) async { final results = await client.search(query); // Tag each result with server info return results.map((item) { return item.copyWith(serverId: serverId, serverName: server?.name); }).toList(); - } catch (e, stackTrace) { - appLogger.e( - 'Failed to search on server $serverId', - error: e, - stackTrace: stackTrace, - ); - _serverManager.updateServerStatus(serverId, false); - return []; - } - }); - - final results = await Future.wait(searchFutures); - - // Flatten results - for (final items in results) { - allResults.addAll(items); - } + }, + ); // Apply limit if specified final result = limit != null && limit < allResults.length @@ -361,4 +266,63 @@ class DataAggregationService { return grouped; } + + // Private helper methods + + /// Higher-order helper for per-server fan-out operations + /// + /// Iterates over all online clients, executes the operation for each server, + /// handles errors, updates server status, and aggregates results. + /// + /// Type parameter `T` is the item type returned by the operation + /// [operationName] is used for logging (e.g., "fetching libraries") + /// [operation] is the async function to run per server, returning `List` + Future> _perServer({ + required String operationName, + required Future> Function( + String serverId, + PlexClient client, + PlexServer? server, + ) + operation, + }) async { + final clients = _serverManager.onlineClients; + + if (clients.isEmpty) { + appLogger.w('No online servers available for $operationName'); + return []; + } + + appLogger.d('$operationName from ${clients.length} servers'); + + final allResults = []; + + // Execute operation on all servers in parallel + final Iterable>> futures = clients.entries.map((entry) async { + final serverId = entry.key; + final client = entry.value; + final server = _serverManager.getServer(serverId); + + try { + return await operation(serverId, client, server); + } catch (e, stackTrace) { + appLogger.e( + 'Failed $operationName from server $serverId', + error: e, + stackTrace: stackTrace, + ); + _serverManager.updateServerStatus(serverId, false); + return []; + } + }); + + final List> results = await Future.wait>(futures); + + // Flatten results + for (final items in results) { + allResults.addAll(items); + } + + return allResults; + } } diff --git a/lib/services/episode_navigation_service.dart b/lib/services/episode_navigation_service.dart new file mode 100644 index 00000000..02c011c7 --- /dev/null +++ b/lib/services/episode_navigation_service.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:provider/provider.dart'; + +import '../client/plex_client.dart'; +import '../models/plex_metadata.dart'; +import '../providers/playback_state_provider.dart'; +import '../utils/app_logger.dart'; +import '../utils/video_player_navigation.dart'; + +/// Result of loading adjacent episodes +class AdjacentEpisodes { + final PlexMetadata? next; + final PlexMetadata? previous; + + AdjacentEpisodes({this.next, this.previous}); + + bool get hasNext => next != null; + bool get hasPrevious => previous != null; +} + +/// Manages episode navigation for TV show playback. +/// +/// Handles: +/// - Loading next/previous episodes from play queues +/// - Navigating between episodes while preserving track selections +/// - Supporting both sequential and shuffle playback modes +/// +/// All episode navigation uses Plex play queues for consistent behavior. +class EpisodeNavigationService { + /// Load the next and previous episodes for the current episode + /// + /// Returns null for episodes if: + /// - Not applicable (e.g., movie content) + /// - Next episode doesn't exist (end of season/series) + /// - Previous episode doesn't exist (first episode) + Future loadAdjacentEpisodes({ + required BuildContext context, + required PlexClient client, + required PlexMetadata metadata, + }) async { + try { + final playbackState = context.read(); + + // All episode navigation now uses play queues (sequential, shuffle, playlists) + // If no queue is active, navigation is not available + if (!playbackState.isQueueActive) { + return AdjacentEpisodes(); + } + + // Use the play queue for next/previous navigation + final next = await playbackState.getNextEpisode( + metadata.ratingKey, + loopQueue: false, + ); + final previous = await playbackState.getPreviousEpisode( + metadata.ratingKey, + ); + + final mode = playbackState.isShuffleActive ? 'Shuffle' : 'Sequential'; + appLogger.d( + '$mode mode - Next: ${next?.title}, Previous: ${previous?.title}', + ); + + return AdjacentEpisodes(next: next, previous: previous); + } catch (e) { + // Non-critical: Failed to load next/previous episode metadata + appLogger.d('Could not load adjacent episodes', error: e); + return AdjacentEpisodes(); + } + } + + /// Navigate to the next or previous episode + /// + /// Preserves the current audio track, subtitle track, and playback rate + /// selections when transitioning between episodes. + Future navigateToEpisode({ + required BuildContext context, + required PlexMetadata episode, + required Player? player, + bool usePushReplacement = true, + }) async { + if (!context.mounted) return; + + // Capture current player state before navigation + AudioTrack? currentAudioTrack; + SubtitleTrack? currentSubtitleTrack; + double? currentPlaybackRate; + + if (player != null) { + currentAudioTrack = player.state.track.audio; + currentSubtitleTrack = player.state.track.subtitle; + currentPlaybackRate = player.state.rate; + + appLogger.d( + 'Navigating to episode with preserved settings - Audio: ${currentAudioTrack.id}, Subtitle: ${currentSubtitleTrack.id}, Rate: ${currentPlaybackRate}x', + ); + } + + // Navigate to the new episode + if (context.mounted) { + navigateToVideoPlayer( + context, + metadata: episode, + preferredAudioTrack: currentAudioTrack, + preferredSubtitleTrack: currentSubtitleTrack, + preferredPlaybackRate: currentPlaybackRate, + usePushReplacement: usePushReplacement, + ); + } + } +} diff --git a/lib/services/media_controls_manager.dart b/lib/services/media_controls_manager.dart new file mode 100644 index 00000000..2532878a --- /dev/null +++ b/lib/services/media_controls_manager.dart @@ -0,0 +1,193 @@ +import 'dart:async'; + +import 'package:os_media_controls/os_media_controls.dart'; + +import '../client/plex_client.dart'; +import '../models/plex_metadata.dart'; +import '../utils/app_logger.dart'; + +/// Manages OS media controls integration for video playback. +/// +/// Handles: +/// - Metadata updates (title, artwork, etc.) +/// - Playback state updates (playing/paused, position, speed) +/// - Control event streaming (play, pause, next, previous, seek) +/// - Position update throttling to prevent excessive API calls +class MediaControlsManager { + /// Stream of control events from OS media controls + Stream get controlEvents => OsMediaControls.controlEvents; + + /// Last time position was updated (for throttling) + DateTime? _lastPositionUpdate; + + /// Throttle interval for position updates (default: 1 second) + final Duration throttleInterval; + + MediaControlsManager({this.throttleInterval = const Duration(seconds: 1)}); + + /// Update media metadata displayed in OS media controls + /// + /// This includes title, artist, artwork, and duration. + Future updateMetadata({ + required PlexMetadata metadata, + PlexClient? client, + Duration? duration, + }) async { + try { + // Build artwork URL if client is available + String? artworkUrl; + if (client != null && metadata.thumb != null) { + try { + artworkUrl = client.getThumbnailUrl(metadata.thumb!); + appLogger.d('Artwork URL for media controls: $artworkUrl'); + } catch (e) { + appLogger.w('Failed to build artwork URL', error: e); + } + } + + // Update OS media controls + await OsMediaControls.setMetadata( + MediaMetadata( + title: metadata.title, + artist: _buildArtist(metadata), + artworkUrl: artworkUrl, + duration: duration, + ), + ); + + appLogger.d('Updated media controls metadata: ${metadata.title}'); + } catch (e) { + appLogger.w('Failed to update media controls metadata', error: e); + } + } + + /// Update playback state in OS media controls + /// + /// Updates the current playing state, position, and playback speed. + /// Position updates are throttled to avoid excessive API calls. + Future updatePlaybackState({ + required bool isPlaying, + required Duration position, + required double speed, + bool force = false, + }) async { + try { + // Throttle position updates unless forced + if (!force) { + final now = DateTime.now(); + if (_lastPositionUpdate != null) { + final timeSinceLastUpdate = now.difference(_lastPositionUpdate!); + if (timeSinceLastUpdate < throttleInterval) { + return; // Skip this update + } + } + _lastPositionUpdate = now; + } + + // Only update if playing (avoid excessive updates when paused) + if (isPlaying) { + await OsMediaControls.setPlaybackState( + MediaPlaybackState( + state: PlaybackState.playing, + position: position, + speed: speed, + ), + ); + } else { + await OsMediaControls.setPlaybackState( + MediaPlaybackState( + state: PlaybackState.paused, + position: position, + speed: speed, + ), + ); + } + } catch (e) { + appLogger.w('Failed to update media controls playback state', error: e); + } + } + + /// Enable or disable next/previous track controls + /// + /// This should be called based on content type and playback mode. + /// For example: + /// - Episodes: Enable both if there are adjacent episodes + /// - Playlist items: Enable based on playlist position + /// - Movies: Usually disabled + Future setControlsEnabled({ + bool canGoNext = false, + bool canGoPrevious = false, + }) async { + try { + final controls = []; + if (canGoPrevious) controls.add(MediaControl.previous); + if (canGoNext) controls.add(MediaControl.next); + + if (controls.isNotEmpty) { + await OsMediaControls.enableControls(controls); + appLogger.d( + 'Media controls enabled - Previous: $canGoPrevious, Next: $canGoNext', + ); + } else { + await OsMediaControls.disableControls([ + MediaControl.previous, + MediaControl.next, + ]); + appLogger.d('Media controls disabled'); + } + } catch (e) { + appLogger.w('Failed to set media controls enabled state', error: e); + } + } + + /// Clear all media controls + /// + /// Should be called when playback stops or screen is disposed. + Future clear() async { + try { + await OsMediaControls.clear(); + _lastPositionUpdate = null; + appLogger.d('Media controls cleared'); + } catch (e) { + appLogger.w('Failed to clear media controls', error: e); + } + } + + /// Dispose resources + void dispose() { + _lastPositionUpdate = null; + } + + /// Build artist string from metadata + /// + /// For episodes: "Show Name - Season X Episode Y" + /// For movies: Director or studio + /// For other content: Fallback to year or empty + String _buildArtist(PlexMetadata metadata) { + if (metadata.type.toLowerCase() == 'episode') { + final parts = []; + + // Add show name + if (metadata.grandparentTitle != null) { + parts.add(metadata.grandparentTitle!); + } + + // Add season/episode info + if (metadata.parentIndex != null && metadata.index != null) { + parts.add('S${metadata.parentIndex} E${metadata.index}'); + } else if (metadata.parentTitle != null) { + parts.add(metadata.parentTitle!); + } + + return parts.join(' • '); + } else if (metadata.type.toLowerCase() == 'movie') { + // For movies, use director or studio + // Note: These fields may need to be added to PlexMetadata model + if (metadata.year != null) { + return metadata.year.toString(); + } + } + + return ''; + } +} diff --git a/lib/services/multi_server_manager.dart b/lib/services/multi_server_manager.dart index d0a950de..324ccacf 100644 --- a/lib/services/multi_server_manager.dart +++ b/lib/services/multi_server_manager.dart @@ -74,7 +74,8 @@ class MultiServerManager { appLogger.i('Connecting to ${servers.length} servers...'); // Use provided client ID or generate a unique one for this app instance - final effectiveClientId = clientIdentifier ?? DateTime.now().millisecondsSinceEpoch.toString(); + final effectiveClientId = + clientIdentifier ?? DateTime.now().millisecondsSinceEpoch.toString(); // Create connection tasks for all servers final connectionFutures = servers.map((server) async { @@ -135,13 +136,15 @@ class MultiServerManager { // Wait for all connections with timeout final results = await Future.wait( - connectionFutures.map((f) => f.timeout( - timeout, - onTimeout: () { - appLogger.w('Server connection timed out'); - return null; - }, - )), + connectionFutures.map( + (f) => f.timeout( + timeout, + onTimeout: () { + appLogger.w('Server connection timed out'); + return null; + }, + ), + ), ); // Count successful connections @@ -164,7 +167,8 @@ class MultiServerManager { Duration timeout = const Duration(seconds: 10), }) async { final serverId = server.clientIdentifier; - final effectiveClientId = clientIdentifier ?? DateTime.now().millisecondsSinceEpoch.toString(); + final effectiveClientId = + clientIdentifier ?? DateTime.now().millisecondsSinceEpoch.toString(); try { appLogger.d('Adding server: ${server.name}'); diff --git a/lib/services/playback_progress_tracker.dart b/lib/services/playback_progress_tracker.dart new file mode 100644 index 00000000..129b1454 --- /dev/null +++ b/lib/services/playback_progress_tracker.dart @@ -0,0 +1,121 @@ +import 'dart:async'; + +import 'package:media_kit/media_kit.dart'; + +import '../client/plex_client.dart'; +import '../models/plex_metadata.dart'; +import '../utils/app_logger.dart'; + +/// Tracks playback progress and reports it to the Plex server. +/// +/// Handles: +/// - Periodic timeline updates during playback +/// - Resume position tracking +/// - State change reporting (playing, paused, stopped) +class PlaybackProgressTracker { + final PlexClient client; + final PlexMetadata metadata; + final Player player; + + /// Timer for periodic progress updates + Timer? _progressTimer; + + /// Update interval (default: 10 seconds) + final Duration updateInterval; + + PlaybackProgressTracker({ + required this.client, + required this.metadata, + required this.player, + this.updateInterval = const Duration(seconds: 10), + }); + + /// Start tracking playback progress + /// + /// Begins periodic timeline updates to the Plex server. + void startTracking() { + if (_progressTimer != null) { + appLogger.w('Progress tracking already started'); + return; + } + + _progressTimer = Timer.periodic(updateInterval, (timer) { + if (player.state.playing) { + _sendProgress('playing'); + } + }); + + appLogger.d( + 'Started progress tracking (interval: ${updateInterval.inSeconds}s)', + ); + } + + /// Stop tracking playback progress + /// + /// Cancels the periodic timer. + void stopTracking() { + _progressTimer?.cancel(); + _progressTimer = null; + appLogger.d('Stopped progress tracking'); + } + + /// Send progress update to Plex server + /// + /// [state] can be 'playing', 'paused', or 'stopped' + Future sendProgress(String state) async { + await _sendProgress(state); + } + + Future _sendProgress(String state) async { + try { + final position = player.state.position; + final duration = player.state.duration; + + // Don't send progress if no duration (not ready) + if (duration.inMilliseconds == 0) { + return; + } + + // Build timeline query parameters + final queryParams = { + 'ratingKey': metadata.ratingKey, + 'key': '/library/metadata/${metadata.ratingKey}', + 'state': state, + 'time': position.inMilliseconds.toString(), + 'duration': duration.inMilliseconds.toString(), + }; + + // Add playQueueItemID if available (for playlist/shuffle playback) + if (metadata.playQueueItemID != null) { + queryParams['playQueueItemID'] = metadata.playQueueItemID.toString(); + } + + // Send timeline update + await client + .updateProgress( + metadata.ratingKey, + time: position.inMilliseconds, + state: state, + duration: duration.inMilliseconds, + ) + .catchError((error) { + // Silent handling - don't interrupt playback for progress errors + appLogger.d( + 'Failed to update progress (non-critical)', + error: error, + ); + }); + + appLogger.d( + 'Progress update sent: $state at ${position.inSeconds}s / ${duration.inSeconds}s', + ); + } catch (e) { + appLogger.d('Failed to send progress update (non-critical)', error: e); + } + } + + /// Dispose resources + void dispose() { + stopTracking(); + } +} diff --git a/lib/services/server_registry.dart b/lib/services/server_registry.dart index 3f827819..c871f501 100644 --- a/lib/services/server_registry.dart +++ b/lib/services/server_registry.dart @@ -138,13 +138,15 @@ class ServerRegistry { } /// Update server status (called when server connection status changes) - Future updateServerStatus(String serverId, { + Future updateServerStatus( + String serverId, { bool? online, DateTime? lastSeen, }) async { final servers = await getServers(); - final serverIndex = - servers.indexWhere((s) => s.clientIdentifier == serverId); + final serverIndex = servers.indexWhere( + (s) => s.clientIdentifier == serverId, + ); if (serverIndex == -1) { appLogger.w('Server not found for status update: $serverId'); diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 8dc02100..422bb120 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -39,9 +39,6 @@ class SettingsService { static const String _keySubtitleBackgroundColor = 'subtitle_background_color'; static const String _keySubtitleBackgroundOpacity = 'subtitle_background_opacity'; - static const String _keyShuffleUnwatchedOnly = 'shuffle_unwatched_only'; - static const String _keyShuffleOrderNavigation = 'shuffle_order_navigation'; - static const String _keyShuffleLoopQueue = 'shuffle_loop_queue'; static const String _keyAppLocale = 'app_locale'; static const String _keyRememberTrackSelections = 'remember_track_selections'; @@ -777,9 +774,14 @@ class SettingsService { final jsonString = _prefs.getString(_keyMediaVersionPreferences); if (jsonString == null) return {}; + final decoded = _decodeJsonStringToMap(jsonString); + return decoded.map((key, value) => MapEntry(key, value as int)); + } + + /// Helper to decode JSON string to Map with error handling + Map _decodeJsonStringToMap(String jsonString) { try { - final decoded = json.decode(jsonString) as Map; - return decoded.map((key, value) => MapEntry(key, value as int)); + return json.decode(jsonString) as Map; } catch (e) { return {}; } @@ -800,35 +802,6 @@ class SettingsService { ); } - // Shuffle Play Settings - - /// Shuffle Unwatched Only - Filter shuffle queue to unwatched episodes only - Future setShuffleUnwatchedOnly(bool enabled) async { - await _prefs.setBool(_keyShuffleUnwatchedOnly, enabled); - } - - bool getShuffleUnwatchedOnly() { - return _prefs.getBool(_keyShuffleUnwatchedOnly) ?? true; // Default: true - } - - /// Shuffle Order Navigation - Next/previous buttons follow shuffled order - Future setShuffleOrderNavigation(bool enabled) async { - await _prefs.setBool(_keyShuffleOrderNavigation, enabled); - } - - bool getShuffleOrderNavigation() { - return _prefs.getBool(_keyShuffleOrderNavigation) ?? true; // Default: true - } - - /// Shuffle Loop Queue - Restart queue when reaching the end - Future setShuffleLoopQueue(bool enabled) async { - await _prefs.setBool(_keyShuffleLoopQueue, enabled); - } - - bool getShuffleLoopQueue() { - return _prefs.getBool(_keyShuffleLoopQueue) ?? false; // Default: false - } - // Track Selection Settings /// Remember Track Selections - Save per-media audio/subtitle language preferences @@ -868,9 +841,6 @@ class SettingsService { _prefs.remove(_keySubtitleBorderColor), _prefs.remove(_keySubtitleBackgroundColor), _prefs.remove(_keySubtitleBackgroundOpacity), - _prefs.remove(_keyShuffleUnwatchedOnly), - _prefs.remove(_keyShuffleOrderNavigation), - _prefs.remove(_keyShuffleLoopQueue), _prefs.remove(_keyAppLocale), _prefs.remove(_keyRememberTrackSelections), ]); diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index c467d90f..b1db1fa5 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -89,14 +89,7 @@ class StorageService { } Map? getServerData() { - final jsonString = _prefs.getString(_keyServerData); - if (jsonString == null) return null; - - try { - return json.decode(jsonString) as Map; - } catch (e) { - return null; - } + return _readJsonMap(_keyServerData); } // Client Identifier @@ -194,12 +187,8 @@ class StorageService { _prefs.getString(_keyLibraryFilters); if (jsonString == null) return {}; - try { - final decoded = json.decode(jsonString) as Map; - return decoded.map((key, value) => MapEntry(key, value.toString())); - } catch (e) { - return {}; - } + final decoded = _decodeJsonStringToMap(jsonString); + return decoded.map((key, value) => MapEntry(key, value.toString())); } // Library Sort (per-library, stored individually with descending flag) @@ -213,15 +202,7 @@ class StorageService { } Map? getLibrarySort(String sectionId) { - final jsonString = _prefs.getString('library_sort_$sectionId'); - if (jsonString == null) return null; - - try { - return json.decode(jsonString) as Map; - } catch (e) { - // Legacy support: if it's just a string, return it as the key - return {'key': jsonString, 'descending': false}; - } + return _readJsonMap('library_sort_$sectionId', legacyStringOk: true); } // Library Grouping (per-library, e.g., 'movies', 'shows', 'seasons', 'episodes') @@ -301,14 +282,7 @@ class StorageService { } Map? getUserProfile() { - final jsonString = _prefs.getString(_keyUserProfile); - if (jsonString == null) return null; - - try { - return json.decode(jsonString) as Map; - } catch (e) { - return null; - } + return _readJsonMap(_keyUserProfile); } // Current User UUID @@ -340,14 +314,7 @@ class StorageService { return null; } - final jsonString = _prefs.getString(_keyHomeUsersCache); - if (jsonString == null) return null; - - try { - return json.decode(jsonString) as Map; - } catch (e) { - return null; - } + return _readJsonMap(_keyHomeUsersCache); } Future clearHomeUsersCache() async { @@ -364,10 +331,7 @@ class StorageService { // Clear all user-related data (for logout) Future clearUserData() async { - await Future.wait([ - clearCredentials(), - clearLibraryPreferences(), - ]); + await Future.wait([clearCredentials(), clearLibraryPreferences()]); } // Update current user after switching @@ -412,9 +376,43 @@ class StorageService { /// Clear all multi-server data Future clearMultiServerData() async { - await Future.wait([ - clearServersList(), - clearEnabledServers(), - ]); + await Future.wait([clearServersList(), clearEnabledServers()]); + } + + // Private helper methods + + /// Helper to read and decode JSON Map from preferences + /// + /// [key] - The preference key to read + /// [legacyStringOk] - If true, returns {'key': value, 'descending': false} + /// when value is a plain string (for legacy library sort) + Map? _readJsonMap( + String key, { + bool legacyStringOk = false, + }) { + final jsonString = _prefs.getString(key); + if (jsonString == null) return null; + + return _decodeJsonStringToMap(jsonString, legacyStringOk: legacyStringOk); + } + + /// Helper to decode JSON string to Map with error handling + /// + /// [jsonString] - The JSON string to decode + /// [legacyStringOk] - If true, returns {'key': value, 'descending': false} + /// when value is a plain string (for legacy library sort) + Map _decodeJsonStringToMap( + String jsonString, { + bool legacyStringOk = false, + }) { + try { + return json.decode(jsonString) as Map; + } catch (e) { + if (legacyStringOk) { + // Legacy support: if it's just a string, return it as the key + return {'key': jsonString, 'descending': false}; + } + return {}; + } } } diff --git a/lib/utils/collection_playlist_play_helper.dart b/lib/utils/collection_playlist_play_helper.dart index 02e102f6..bbf2cf47 100644 --- a/lib/utils/collection_playlist_play_helper.dart +++ b/lib/utils/collection_playlist_play_helper.dart @@ -25,8 +25,12 @@ Future playCollectionOrPlaylist({ } String ratingKey = item.ratingKey; - String? serverId = isCollection ? (item as PlexMetadata).serverId : (item as PlexPlaylist).serverId; - String? serverName = isCollection ? (item as PlexMetadata).serverName : (item as PlexPlaylist).serverName; + String? serverId = isCollection + ? (item as PlexMetadata).serverId + : (item as PlexPlaylist).serverId; + String? serverName = isCollection + ? (item as PlexMetadata).serverName + : (item as PlexPlaylist).serverName; final PlayQueueResponse? playQueue; if (isCollection) { @@ -63,17 +67,15 @@ Future playCollectionOrPlaylist({ // Preserve serverId for all items in the queue final itemsWithServerId = fetchedQueue.items!.map((queueItem) { - return queueItem.copyWith( - serverId: serverId, - serverName: serverName, - ); + return queueItem.copyWith(serverId: serverId, serverName: serverName); }).toList(); final queueWithServerId = PlayQueueResponse( playQueueID: fetchedQueue.playQueueID, playQueueSelectedItemID: fetchedQueue.playQueueSelectedItemID, playQueueSelectedItemOffset: fetchedQueue.playQueueSelectedItemOffset, - playQueueSelectedMetadataItemID: fetchedQueue.playQueueSelectedMetadataItemID, + playQueueSelectedMetadataItemID: + fetchedQueue.playQueueSelectedMetadataItemID, playQueueShuffled: fetchedQueue.playQueueShuffled, playQueueSourceURI: fetchedQueue.playQueueSourceURI, playQueueTotalCount: fetchedQueue.playQueueTotalCount, @@ -85,15 +87,17 @@ Future playCollectionOrPlaylist({ // Set play queue in provider final playbackState = context.read(); playbackState.setClient(client); - await playbackState.setPlaybackFromPlayQueue(queueWithServerId, ratingKey); + await playbackState.setPlaybackFromPlayQueue( + queueWithServerId, + ratingKey, + serverId: serverId, + serverName: serverName, + ); if (!context.mounted) return; // Navigate to first item - await navigateToVideoPlayer( - context, - metadata: itemsWithServerId.first, - ); + await navigateToVideoPlayer(context, metadata: itemsWithServerId.first); return; } } @@ -113,17 +117,15 @@ Future playCollectionOrPlaylist({ // Preserve serverId for all items in the queue final itemsWithServerId = playQueue.items!.map((queueItem) { - return queueItem.copyWith( - serverId: serverId, - serverName: serverName, - ); + return queueItem.copyWith(serverId: serverId, serverName: serverName); }).toList(); final queueWithServerId = PlayQueueResponse( playQueueID: playQueue.playQueueID, playQueueSelectedItemID: playQueue.playQueueSelectedItemID, playQueueSelectedItemOffset: playQueue.playQueueSelectedItemOffset, - playQueueSelectedMetadataItemID: playQueue.playQueueSelectedMetadataItemID, + playQueueSelectedMetadataItemID: + playQueue.playQueueSelectedMetadataItemID, playQueueShuffled: playQueue.playQueueShuffled, playQueueSourceURI: playQueue.playQueueSourceURI, playQueueTotalCount: playQueue.playQueueTotalCount, @@ -135,7 +137,12 @@ Future playCollectionOrPlaylist({ // Set play queue in provider final playbackState = context.read(); playbackState.setClient(client); - await playbackState.setPlaybackFromPlayQueue(queueWithServerId, ratingKey); + await playbackState.setPlaybackFromPlayQueue( + queueWithServerId, + ratingKey, + serverId: serverId, + serverName: serverName, + ); if (!context.mounted) return; diff --git a/lib/utils/provider_extensions.dart b/lib/utils/provider_extensions.dart index 973fd4c4..0c5c3636 100644 --- a/lib/utils/provider_extensions.dart +++ b/lib/utils/provider_extensions.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../client/plex_client.dart'; +import '../i18n/strings.g.dart'; +import '../models/plex_library.dart'; +import '../models/plex_user_profile.dart'; +import '../providers/hidden_libraries_provider.dart'; +import '../providers/multi_server_provider.dart'; import '../providers/plex_client_provider.dart'; import '../providers/user_profile_provider.dart'; -import '../providers/hidden_libraries_provider.dart'; -import '../client/plex_client.dart'; -import '../models/plex_user_profile.dart'; +import 'app_logger.dart'; extension ProviderExtensions on BuildContext { PlexClientProvider get plexClient => @@ -25,12 +29,48 @@ extension ProviderExtensions on BuildContext { HiddenLibrariesProvider watchHiddenLibraries() => Provider.of(this, listen: true); - // Direct client access (nullable) - PlexClient? get client => plexClient.client; - - // Null-safe client access - PlexClient get clientSafe => plexClient.client!; - // Direct profile settings access (nullable) PlexUserProfile? get profileSettings => userProfile.profileSettings; + + /// Get PlexClient for a specific server ID + /// If serverId is null, returns the first available online client + /// Throws an exception if no client is available + PlexClient getClientForServer(String? serverId) { + final multiServerProvider = Provider.of( + this, + listen: false, + ); + + if (serverId == null) { + // No serverId specified - try to get first online server + appLogger.w('No serverId provided, using first available online server'); + + if (!multiServerProvider.hasConnectedServers) { + throw Exception(t.errors.noClientAvailable); + } + + final firstServerId = multiServerProvider.onlineServerIds.first; + final client = multiServerProvider.getClientForServer(firstServerId); + if (client == null) { + throw Exception(t.errors.noClientAvailable); + } + + return client; + } + + final serverClient = multiServerProvider.getClientForServer(serverId); + + if (serverClient == null) { + appLogger.e('No client found for server $serverId'); + throw Exception(t.errors.noClientAvailable); + } + + return serverClient; + } + + /// Get PlexClient for a library + /// Throws an exception if no client is available + PlexClient getClientForLibrary(PlexLibrary library) { + return getClientForServer(library.serverId); + } } diff --git a/lib/utils/server_tagging_extensions.dart b/lib/utils/server_tagging_extensions.dart new file mode 100644 index 00000000..5284dd3d --- /dev/null +++ b/lib/utils/server_tagging_extensions.dart @@ -0,0 +1,39 @@ +import '../models/plex_metadata.dart'; +import '../models/plex_playlist.dart'; +import '../models/plex_library.dart'; + +/// Extension methods for tagging items with server information +/// Used for multi-server support to track which server each item belongs to + +extension PlexMetadataServerTagging on Iterable { + /// Tags all items in the collection with the given server ID and name + /// Returns a new list with all items updated + List tagWithServer(String? serverId, String? serverName) { + return map( + (item) => item.copyWith(serverId: serverId, serverName: serverName), + ).toList(); + } + + /// Tags all items in the collection with server info from a library + /// Returns a new list with all items updated + List tagWithLibrary(PlexLibrary library) { + return tagWithServer(library.serverId, library.serverName); + } +} + +extension PlexPlaylistServerTagging on Iterable { + /// Tags all playlists in the collection with the given server ID and name + /// Returns a new list with all playlists updated + List tagWithServer(String? serverId, String? serverName) { + return map( + (playlist) => + playlist.copyWith(serverId: serverId, serverName: serverName), + ).toList(); + } + + /// Tags all playlists in the collection with server info from a library + /// Returns a new list with all playlists updated + List tagWithLibrary(PlexLibrary library) { + return tagWithServer(library.serverId, library.serverName); + } +} diff --git a/lib/utils/shuffle_play_helper.dart b/lib/utils/shuffle_play_helper.dart deleted file mode 100644 index 4a07e7ba..00000000 --- a/lib/utils/shuffle_play_helper.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../client/plex_client.dart'; -import '../models/plex_metadata.dart'; -import '../providers/multi_server_provider.dart'; -import '../providers/playback_state_provider.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/settings_provider.dart'; -import '../utils/app_logger.dart'; -import '../utils/video_player_navigation.dart'; -import '../i18n/strings.g.dart'; - -/// Get the correct PlexClient for metadata's server -PlexClient? _getClientForMetadata(BuildContext context, PlexMetadata metadata) { - final serverId = metadata.serverId; - if (serverId == null) { - // Fallback to legacy client if no serverId - appLogger.w('Metadata ${metadata.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; -} - -/// Handle shuffle play action for shows and seasons -/// -/// Fetches episodes based on user settings (unwatched only or including watched), -/// shuffles them, and starts playback from the first shuffled episode. -/// The shuffle queue is stored in the PlaybackStateProvider for continuous shuffle playback. -Future handleShufflePlay( - BuildContext context, - PlexMetadata metadata, -) async { - final client = _getClientForMetadata(context, metadata); - if (client == null) return; - - final playbackState = context.read(); - final settingsProvider = context.read(); - final itemType = metadata.type.toLowerCase(); - - // Get shuffle setting - final unwatchedOnly = settingsProvider.shuffleUnwatchedOnly; - - try { - // Show loading indicator - if (context.mounted) { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center(child: CircularProgressIndicator()), - ); - } - - // Get episodes based on type and settings - List episodes; - if (itemType == 'show') { - if (unwatchedOnly) { - // Get only unwatched episodes - final fetchedEpisodes = await client.getAllUnwatchedEpisodes(metadata.ratingKey); - // Preserve serverId for each episode - episodes = fetchedEpisodes.map((ep) => ep.copyWith( - serverId: metadata.serverId, - serverName: metadata.serverName, - )).toList(); - } else { - // Get all episodes from all seasons - final allEpisodes = []; - final seasons = await client.getChildren(metadata.ratingKey); - - for (final season in seasons) { - if (season.type == 'season') { - final seasonEpisodes = await client.getChildren(season.ratingKey); - final episodesOnly = seasonEpisodes - .where((ep) => ep.type == 'episode') - .map((ep) => ep.copyWith( - serverId: metadata.serverId, - serverName: metadata.serverName, - )) - .toList(); - allEpisodes.addAll(episodesOnly); - } - } - episodes = allEpisodes; - } - } else { - // season - if (unwatchedOnly) { - // Get only unwatched episodes - final fetchedEpisodes = await client.getUnwatchedEpisodesInSeason( - metadata.ratingKey, - ); - // Preserve serverId for each episode - episodes = fetchedEpisodes.map((ep) => ep.copyWith( - serverId: metadata.serverId, - serverName: metadata.serverName, - )).toList(); - } else { - // Get all episodes in season - final seasonEpisodes = await client.getChildren(metadata.ratingKey); - episodes = seasonEpisodes - .where((ep) => ep.type == 'episode') - .map((ep) => ep.copyWith( - serverId: metadata.serverId, - serverName: metadata.serverName, - )) - .toList(); - } - } - - // Close loading indicator - if (context.mounted) { - Navigator.pop(context); - } - - if (episodes.isEmpty) { - if (context.mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(t.messages.noEpisodesFound))); - } - return; - } - - // Shuffle the episodes - episodes.shuffle(); - - // Store shuffle queue in provider - // ignore: deprecated_member_use_from_same_package - playbackState.setShuffleQueue(episodes, metadata.ratingKey); - - // Navigate to first episode - if (context.mounted) { - await navigateToVideoPlayer(context, metadata: episodes.first); - } - } catch (e) { - // Close loading indicator if it's still open - if (context.mounted && Navigator.canPop(context)) { - Navigator.pop(context); - } - - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))), - ); - } - } -} diff --git a/lib/widgets/bottom_sheet_header.dart b/lib/widgets/bottom_sheet_header.dart new file mode 100644 index 00000000..8b77c75a --- /dev/null +++ b/lib/widgets/bottom_sheet_header.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +/// A reusable header widget for bottom sheets +/// Provides consistent styling with title, optional leading widget, optional action, and close button +class BottomSheetHeader extends StatelessWidget { + /// The title text to display + final String title; + + /// Optional leading widget (e.g., icon or back button) + final Widget? leading; + + /// Optional action widget (e.g., clear button) + final Widget? action; + + /// Optional callback when close button is pressed + /// Defaults to Navigator.pop(context) + final VoidCallback? onClose; + + const BottomSheetHeader({ + super.key, + required this.title, + this.leading, + this.action, + this.onClose, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Theme.of(context).dividerColor), + ), + ), + child: Row( + children: [ + if (leading != null) ...[leading!, const SizedBox(width: 8)], + Expanded( + child: Text( + title, + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + if (action != null) action!, + IconButton( + icon: const Icon(Icons.close), + onPressed: onClose ?? () => Navigator.pop(context), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/content_state_builder.dart b/lib/widgets/content_state_builder.dart index 5ad59cee..d4c80289 100644 --- a/lib/widgets/content_state_builder.dart +++ b/lib/widgets/content_state_builder.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import '../i18n/strings.g.dart'; +import 'empty_state_widget.dart'; +import 'error_state_widget.dart'; /// A widget that handles loading, error, empty, and content states /// Provides a consistent UI pattern across the app for data-driven screens @@ -45,36 +47,17 @@ class ContentStateBuilder extends StatelessWidget { // Error state (only show error if items list is empty) if (errorMessage != null && items.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error_outline, size: 48, color: Colors.red), - const SizedBox(height: 16), - Text( - errorMessage!, - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.white70), - ), - const SizedBox(height: 16), - ElevatedButton(onPressed: onRetry, child: Text(t.common.retry)), - ], - ), + return ErrorStateWidget( + message: errorMessage!, + icon: Icons.error_outline, + onRetry: onRetry, + retryLabel: t.common.retry, ); } // Empty state if (items.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(emptyIcon, size: 64, color: Colors.grey), - const SizedBox(height: 16), - Text(emptyMessage, style: const TextStyle(color: Colors.white70)), - ], - ), - ); + return EmptyStateWidget(message: emptyMessage, icon: emptyIcon); } // Content state - delegate to builder diff --git a/lib/widgets/empty_state_widget.dart b/lib/widgets/empty_state_widget.dart index d8f83a8e..5ed02294 100644 --- a/lib/widgets/empty_state_widget.dart +++ b/lib/widgets/empty_state_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'state_message_widget.dart'; /// A reusable widget for displaying empty states throughout the app class EmptyStateWidget extends StatelessWidget { @@ -24,42 +25,12 @@ class EmptyStateWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Center( - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) ...[ - Icon( - icon, - size: 64, - color: Theme.of( - context, - ).colorScheme.onSurface.withValues(alpha: 0.4), - ), - const SizedBox(height: 16), - ], - Text( - message, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurface.withValues(alpha: 0.6), - ), - ), - if (onAction != null && actionLabel != null) ...[ - const SizedBox(height: 24), - FilledButton.icon( - onPressed: onAction, - icon: const Icon(Icons.add), - label: Text(actionLabel!), - ), - ], - ], - ), - ), + return StateMessageWidget( + message: message, + icon: icon, + onAction: onAction, + actionLabel: actionLabel, + actionIcon: Icons.add, ); } } diff --git a/lib/widgets/error_state_widget.dart b/lib/widgets/error_state_widget.dart index 31559bfe..2ec0e428 100644 --- a/lib/widgets/error_state_widget.dart +++ b/lib/widgets/error_state_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'state_message_widget.dart'; /// A reusable widget for displaying error states throughout the app class ErrorStateWidget extends StatelessWidget { @@ -24,34 +25,14 @@ class ErrorStateWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Center( - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) ...[ - Icon(icon, size: 64, color: Theme.of(context).colorScheme.error), - const SizedBox(height: 16), - ], - Text( - message, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Theme.of(context).colorScheme.error, - ), - ), - if (onRetry != null) ...[ - const SizedBox(height: 24), - FilledButton.icon( - onPressed: onRetry, - icon: const Icon(Icons.refresh), - label: Text(retryLabel ?? 'Retry'), - ), - ], - ], - ), - ), + return StateMessageWidget( + message: message, + icon: icon, + iconColor: Theme.of(context).colorScheme.error, + textColor: Theme.of(context).colorScheme.error, + onAction: onRetry, + actionLabel: retryLabel ?? 'Retry', + actionIcon: Icons.refresh, ); } } diff --git a/lib/widgets/filters_bottom_sheet.dart b/lib/widgets/filters_bottom_sheet.dart index 443179f4..54e463d9 100644 --- a/lib/widgets/filters_bottom_sheet.dart +++ b/lib/widgets/filters_bottom_sheet.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../models/plex_filter.dart'; import '../widgets/app_bar_back_button.dart'; +import '../widgets/bottom_sheet_header.dart'; import '../utils/provider_extensions.dart'; import '../i18n/strings.g.dart'; @@ -59,10 +60,7 @@ class _FiltersBottomSheetState extends State { }); try { - final client = context.client; - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } + final client = context.getClientForServer(null); final values = await client.getFilterValues(filter.key); setState(() { @@ -114,34 +112,11 @@ class _FiltersBottomSheetState extends State { return Column( children: [ // Header with back button - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Theme.of(context).dividerColor), - ), - ), - child: Row( - children: [ - AppBarBackButton( - style: BackButtonStyle.plain, - onPressed: _goBack, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - _currentFilter!.title, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], + BottomSheetHeader( + title: _currentFilter!.title, + leading: AppBarBackButton( + style: BackButtonStyle.plain, + onPressed: _goBack, ), ), @@ -209,27 +184,11 @@ class _FiltersBottomSheetState extends State { return Column( children: [ // Header - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Theme.of(context).dividerColor), - ), - ), - child: Row( - children: [ - const Icon(Icons.filter_alt), - const SizedBox(width: 12), - Text( - t.libraries.filters, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - if (_tempSelectedFilters.isNotEmpty) - TextButton.icon( + BottomSheetHeader( + title: t.libraries.filters, + leading: const Icon(Icons.filter_alt), + action: _tempSelectedFilters.isNotEmpty + ? TextButton.icon( onPressed: () { setState(() { _tempSelectedFilters.clear(); @@ -238,13 +197,8 @@ class _FiltersBottomSheetState extends State { }, icon: const Icon(Icons.clear_all), label: Text(t.libraries.clearAll), - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), + ) + : null, ), // All Filters (boolean toggles first, then regular filters) diff --git a/lib/widgets/folder_tree_view.dart b/lib/widgets/folder_tree_view.dart index 79905fd0..ffaabc77 100644 --- a/lib/widgets/folder_tree_view.dart +++ b/lib/widgets/folder_tree_view.dart @@ -1,15 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../client/plex_client.dart'; import '../models/plex_metadata.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; -import '../utils/app_logger.dart'; -import '../utils/video_player_navigation.dart'; import '../screens/media_detail_screen.dart'; import '../screens/season_detail_screen.dart'; -import 'folder_tree_item.dart'; +import '../utils/app_logger.dart'; +import '../utils/provider_extensions.dart'; +import '../utils/video_player_navigation.dart'; import '../i18n/strings.g.dart'; +import 'folder_tree_item.dart'; +import 'empty_state_widget.dart'; +import 'error_state_widget.dart'; /// Expandable tree view for browsing library folders /// Shows a hierarchical file/folder structure @@ -37,25 +36,6 @@ class _FolderTreeViewState extends State { bool _isLoadingRoot = false; String? _errorMessage; - /// Get the correct PlexClient for this library's server - PlexClient? _getClientForLibrary() { - final serverId = widget.serverId; - if (serverId == null) { - appLogger.w('Library ${widget.libraryKey} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; - } - @override void initState() { super.initState(); @@ -69,10 +49,7 @@ class _FolderTreeViewState extends State { }); try { - final client = _getClientForLibrary(); - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } + final client = context.getClientForServer(widget.serverId); final folders = await client.getLibraryFolders(widget.libraryKey); @@ -124,10 +101,7 @@ class _FolderTreeViewState extends State { }); try { - final client = _getClientForLibrary(); - if (client == null) { - throw Exception(t.errors.noClientAvailable); - } + final client = context.getClientForServer(widget.serverId); final children = await client.getFolderChildren(folder.key); @@ -135,10 +109,8 @@ class _FolderTreeViewState extends State { final taggedChildren = children .map( - (child) => child.copyWith( - serverId: widget.serverId, - serverName: null, - ), + (child) => + child.copyWith(serverId: widget.serverId, serverName: null), ) .toList(); @@ -270,33 +242,18 @@ class _FolderTreeViewState extends State { } if (_errorMessage != null) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error_outline, size: 48, color: Colors.red), - const SizedBox(height: 16), - Text(_errorMessage!), - const SizedBox(height: 16), - ElevatedButton( - onPressed: _loadRootFolders, - child: Text(t.common.retry), - ), - ], - ), + return ErrorStateWidget( + message: _errorMessage!, + icon: Icons.error_outline, + onRetry: _loadRootFolders, + retryLabel: t.common.retry, ); } if (_rootFolders.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.folder_open, size: 64, color: Colors.grey), - const SizedBox(height: 16), - Text(t.libraries.noFoldersFound), - ], - ), + return EmptyStateWidget( + message: t.libraries.noFoldersFound, + icon: Icons.folder_open, ); } diff --git a/lib/widgets/media_card.dart b/lib/widgets/media_card.dart index d1bc5304..936491b5 100644 --- a/lib/widgets/media_card.dart +++ b/lib/widgets/media_card.dart @@ -4,15 +4,12 @@ import 'package:provider/provider.dart'; import '../client/plex_client.dart'; import '../models/plex_metadata.dart'; import '../models/plex_playlist.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; import '../providers/settings_provider.dart'; import '../services/settings_service.dart'; import '../utils/provider_extensions.dart'; import '../utils/video_player_navigation.dart'; import '../utils/content_rating_formatter.dart'; import '../utils/duration_formatter.dart'; -import '../utils/app_logger.dart'; import '../screens/media_detail_screen.dart'; import '../screens/season_detail_screen.dart'; import '../screens/playlist_detail_screen.dart'; @@ -97,9 +94,6 @@ class _MediaCardState extends State { } void _handleTap(BuildContext context) async { - final client = context.client; - if (client == null) return; - // Handle playlists if (widget.item is PlexPlaylist) { await Navigator.push( @@ -656,7 +650,7 @@ class _MediaCardList extends StatelessWidget { } /// Helper to get the correct PlexClient for an item's server -PlexClient? _getClientForItem(BuildContext context, dynamic item) { +PlexClient _getClientForItem(BuildContext context, dynamic item) { String? serverId; if (item is PlexMetadata) { @@ -665,20 +659,7 @@ PlexClient? _getClientForItem(BuildContext context, dynamic item) { serverId = item.serverId; } - if (serverId == null) { - appLogger.w('Item has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + return context.getClientForServer(serverId); } Widget _buildPosterImage(BuildContext context, dynamic item) { diff --git a/lib/widgets/media_context_menu.dart b/lib/widgets/media_context_menu.dart index 8f3d7928..cbef4693 100644 --- a/lib/widgets/media_context_menu.dart +++ b/lib/widgets/media_context_menu.dart @@ -6,14 +6,15 @@ import '../models/plex_metadata.dart'; import '../models/plex_playlist.dart'; import '../providers/plex_client_provider.dart'; import '../providers/multi_server_provider.dart'; +import '../providers/playback_state_provider.dart'; import '../utils/provider_extensions.dart'; import '../utils/app_logger.dart'; import '../utils/collection_playlist_play_helper.dart'; import '../utils/library_refresh_notifier.dart'; +import '../utils/video_player_navigation.dart'; import '../screens/media_detail_screen.dart'; import '../screens/season_detail_screen.dart'; import '../widgets/file_info_bottom_sheet.dart'; -import '../utils/shuffle_play_helper.dart'; import '../i18n/strings.g.dart'; /// Helper class to store menu action data @@ -62,7 +63,7 @@ class _MediaContextMenuState extends State { } /// Get the correct PlexClient for this item's server - PlexClient? _getClientForItem() { + PlexClient _getClientForItem() { String? serverId; // Get serverId from the item (could be PlexMetadata or PlexPlaylist) @@ -72,25 +73,11 @@ class _MediaContextMenuState extends State { serverId = (widget.item as PlexPlaylist).serverId; } - if (serverId == null) { - appLogger.w('Item has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + return context.getClientForServer(serverId); } void _showContextMenu(BuildContext context) async { final client = _getClientForItem(); - if (client == null) return; final isPlaylist = widget.item is PlexPlaylist; final metadata = isPlaylist ? null : widget.item as PlexMetadata; @@ -405,7 +392,7 @@ class _MediaContextMenuState extends State { break; case 'shuffle_play': - await handleShufflePlay(context, metadata!); + await _handleShufflePlayWithQueue(context); break; case 'play': @@ -530,6 +517,97 @@ class _MediaContextMenuState extends State { } } + /// Handle shuffle play using play queues + Future _handleShufflePlayWithQueue(BuildContext context) async { + final client = _getClientForItem(); + if (client == null) return; + + final metadata = widget.item as PlexMetadata; + final playbackState = context.read(); + final itemType = metadata.type.toLowerCase(); + + try { + // Show loading indicator + if (context.mounted) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + const Center(child: CircularProgressIndicator()), + ); + } + + // Determine the rating key for the play queue + String showRatingKey; + if (itemType == 'show') { + showRatingKey = metadata.ratingKey; + } else if (itemType == 'season') { + // For seasons, we need the show's rating key + // The season's parentRatingKey should point to the show + if (metadata.parentRatingKey == null) { + throw Exception('Season is missing parentRatingKey'); + } + showRatingKey = metadata.parentRatingKey!; + } else { + throw Exception('Shuffle play only works for shows and seasons'); + } + + // Create a shuffled play queue for the show + final playQueue = await client.createShowPlayQueue( + showRatingKey: showRatingKey, + shuffle: 1, + ); + + // Close loading indicator + if (context.mounted) { + Navigator.pop(context); + } + + if (playQueue == null || + playQueue.items == null || + playQueue.items!.isEmpty) { + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(t.messages.noEpisodesFound))); + } + return; + } + + // Initialize playback state with the play queue + await playbackState.setPlaybackFromPlayQueue( + playQueue, + showRatingKey, + serverId: metadata.serverId, + serverName: metadata.serverName, + ); + + // Set the client for the playback state provider + playbackState.setClient(client); + + // Navigate to the first episode in the shuffled queue + final firstEpisode = playQueue.items!.first.copyWith( + serverId: metadata.serverId, + serverName: metadata.serverName, + ); + + if (context.mounted) { + await navigateToVideoPlayer(context, metadata: firstEpisode); + } + } catch (e) { + // Close loading indicator if it's still open + if (context.mounted && Navigator.canPop(context)) { + Navigator.pop(context); + } + + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(t.messages.errorLoading(error: e.toString()))), + ); + } + } + } + /// Show submenu for Add to... (Playlist or Collection) Future _showAddToSubmenu(BuildContext context) async { final useBottomSheet = Platform.isIOS || Platform.isAndroid; diff --git a/lib/widgets/playlist_item_card.dart b/lib/widgets/playlist_item_card.dart index 90d1845e..d612528a 100644 --- a/lib/widgets/playlist_item_card.dart +++ b/lib/widgets/playlist_item_card.dart @@ -3,10 +3,8 @@ import 'package:provider/provider.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../client/plex_client.dart'; import '../models/plex_metadata.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/multi_server_provider.dart'; import '../utils/duration_formatter.dart'; -import '../utils/app_logger.dart'; +import '../utils/provider_extensions.dart'; import '../i18n/strings.g.dart'; /// Custom list item widget for playlist items @@ -122,22 +120,8 @@ class PlaylistItemCard extends StatelessWidget { } /// Get the correct PlexClient for this item's server - PlexClient? _getClientForItem(BuildContext context) { - final serverId = item.serverId; - if (serverId == null) { - appLogger.w('Playlist item ${item.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + PlexClient _getClientForItem(BuildContext context) { + return context.getClientForServer(item.serverId); } Widget _buildPosterImage(BuildContext context) { @@ -146,9 +130,6 @@ class PlaylistItemCard extends StatelessWidget { return Builder( builder: (context) { final client = _getClientForItem(context); - if (client == null) { - return _buildPlaceholder(); - } return ClipRRect( borderRadius: BorderRadius.circular(6), diff --git a/lib/widgets/server_badge.dart b/lib/widgets/server_badge.dart index 6a705f9c..f74cb425 100644 --- a/lib/widgets/server_badge.dart +++ b/lib/widgets/server_badge.dart @@ -25,8 +25,8 @@ class ServerBadge extends StatelessWidget { } final theme = Theme.of(context); - final bgColor = backgroundColor ?? - theme.colorScheme.primaryContainer.withOpacity(0.8); + final bgColor = + backgroundColor ?? theme.colorScheme.primaryContainer.withOpacity(0.8); final fgColor = textColor ?? theme.colorScheme.onPrimaryContainer; final displayText = showFullName @@ -59,10 +59,7 @@ class ServerBadge extends StatelessWidget { } // Show tooltip with full server name on hover/long press - return Tooltip( - message: serverName!, - child: badge, - ); + return Tooltip(message: serverName!, child: badge); } } diff --git a/lib/widgets/sort_bottom_sheet.dart b/lib/widgets/sort_bottom_sheet.dart index 8c45770f..1606b430 100644 --- a/lib/widgets/sort_bottom_sheet.dart +++ b/lib/widgets/sort_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../models/plex_sort.dart'; +import '../widgets/bottom_sheet_header.dart'; import '../i18n/strings.g.dart'; class SortBottomSheet extends StatefulWidget { @@ -61,35 +62,14 @@ class _SortBottomSheetState extends State { builder: (context, scrollController) { return Column( children: [ - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Theme.of(context).dividerColor), - ), - ), - child: Row( - children: [ - Expanded( - child: Text( - t.libraries.sortBy, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ), - if (widget.onClear != null) - TextButton( + BottomSheetHeader( + title: t.libraries.sortBy, + action: widget.onClear != null + ? TextButton( onPressed: _handleClear, child: Text(t.common.clear), - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), + ) + : null, ), Expanded( child: RadioGroup( diff --git a/lib/widgets/state_message_widget.dart b/lib/widgets/state_message_widget.dart new file mode 100644 index 00000000..add274e9 --- /dev/null +++ b/lib/widgets/state_message_widget.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; + +/// Base widget for displaying state messages (empty, error, etc.) +/// Provides a consistent UI pattern for showing icons, messages, and actions +class StateMessageWidget extends StatelessWidget { + /// The message to display + final String message; + + /// Optional icon to display above the message + final IconData? icon; + + /// Optional color for the icon + final Color? iconColor; + + /// Optional color for the message text + final Color? textColor; + + /// Optional callback for action button + final VoidCallback? onAction; + + /// Optional label for the action button + final String? actionLabel; + + /// Optional icon for the action button + final IconData? actionIcon; + + const StateMessageWidget({ + super.key, + required this.message, + this.icon, + this.iconColor, + this.textColor, + this.onAction, + this.actionLabel, + this.actionIcon, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) ...[ + Icon( + icon, + size: 64, + color: + iconColor ?? + Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.4), + ), + const SizedBox(height: 16), + ], + Text( + message, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: + textColor ?? + Theme.of( + context, + ).colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + if (onAction != null && actionLabel != null) ...[ + const SizedBox(height: 24), + FilledButton.icon( + onPressed: onAction, + icon: Icon(actionIcon ?? Icons.refresh), + label: Text(actionLabel!), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/widgets/video_controls/sheets/audio_sync_sheet.dart b/lib/widgets/video_controls/sheets/audio_sync_sheet.dart deleted file mode 100644 index 6757124a..00000000 --- a/lib/widgets/video_controls/sheets/audio_sync_sheet.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:media_kit/media_kit.dart'; -import 'package:plezy/services/settings_service.dart'; -import '../../../i18n/strings.g.dart'; -import '../../../utils/duration_formatter.dart'; -import 'base_video_control_sheet.dart'; - -/// Bottom sheet for adjusting audio sync offset -class AudioSyncSheet extends StatefulWidget { - final Player player; - final int initialOffset; - - const AudioSyncSheet({ - super.key, - required this.player, - required this.initialOffset, - }); - - static void show(BuildContext context, Player player, int initialOffset) { - showModalBottomSheet( - context: context, - backgroundColor: Colors.grey[900], - isScrollControlled: true, - constraints: BaseVideoControlSheet.getBottomSheetConstraints(context), - builder: (context) => - AudioSyncSheet(player: player, initialOffset: initialOffset), - ); - } - - @override - State createState() => _AudioSyncSheetState(); -} - -class _AudioSyncSheetState extends State { - late double _currentOffset; - - @override - void initState() { - super.initState(); - _currentOffset = widget.initialOffset.toDouble(); - } - - void _applyOffset(double offsetMs) async { - // Convert milliseconds to seconds for media_kit - final offsetSeconds = offsetMs / 1000.0; - - // Apply to player using setProperty - await (widget.player.platform as dynamic).setProperty( - 'audio-delay', - offsetSeconds.toString(), - ); - - // Save to settings - final settings = await SettingsService.getInstance(); - await settings.setAudioSyncOffset(offsetMs.round()); - } - - void _resetOffset() { - setState(() { - _currentOffset = 0; - }); - _applyOffset(0); - } - - @override - Widget build(BuildContext context) { - return SafeArea( - child: SizedBox( - height: MediaQuery.of(context).size.height * 0.75, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - const Icon(Icons.sync, color: Colors.white), - const SizedBox(width: 12), - const Text( - 'Audio Sync', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - IconButton( - icon: const Icon(Icons.close, color: Colors.white), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ), - const Divider(color: Colors.white24, height: 1), - Expanded( - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Current offset display - Text( - formatSyncOffset(_currentOffset), - style: const TextStyle( - color: Colors.white, - fontSize: 48, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - _currentOffset > 0 - ? 'Audio plays later' - : _currentOffset < 0 - ? 'Audio plays earlier' - : 'No offset', - style: const TextStyle( - color: Colors.white70, - fontSize: 16, - ), - ), - const SizedBox(height: 48), - // Slider - Row( - children: [ - Text( - t.videoControls.minusTime(amount: "2", unit: "s"), - style: const TextStyle(color: Colors.white70), - ), - Expanded( - child: Slider( - value: _currentOffset, - min: -2000, - max: 2000, - divisions: 80, // 50ms steps - activeColor: Colors.blue, - inactiveColor: Colors.white24, - onChanged: (value) { - setState(() { - _currentOffset = value; - }); - }, - onChangeEnd: (value) { - _applyOffset(value); - }, - ), - ), - Text( - t.videoControls.addTime(amount: "2", unit: "s"), - style: const TextStyle(color: Colors.white70), - ), - ], - ), - const SizedBox(height: 24), - // Reset button - ElevatedButton.icon( - onPressed: _currentOffset != 0 ? _resetOffset : null, - icon: const Icon(Icons.restart_alt), - label: Text(t.videoControls.resetToZero), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey[800], - foregroundColor: Colors.white, - disabledBackgroundColor: Colors.grey[850], - disabledForegroundColor: Colors.white38, - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 12, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/widgets/video_controls/sheets/audio_track_sheet.dart b/lib/widgets/video_controls/sheets/audio_track_sheet.dart index e28043f7..76973f54 100644 --- a/lib/widgets/video_controls/sheets/audio_track_sheet.dart +++ b/lib/widgets/video_controls/sheets/audio_track_sheet.dart @@ -1,107 +1,40 @@ import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import '../../../i18n/strings.g.dart'; -import 'base_video_control_sheet.dart'; +import 'track_selection_sheet.dart'; /// Bottom sheet for selecting audio tracks -class AudioTrackSheet extends StatelessWidget { - final Player player; - final Function(AudioTrack)? onTrackChanged; - - const AudioTrackSheet({super.key, required this.player, this.onTrackChanged}); - +class AudioTrackSheet { static void show( BuildContext context, Player player, { Function(AudioTrack)? onTrackChanged, }) { - BaseVideoControlSheet.showSheet( + TrackSelectionSheet.show( context: context, - builder: (context) => - AudioTrackSheet(player: player, onTrackChanged: onTrackChanged), - ); - } - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: player.stream.tracks, - initialData: player.state.tracks, - builder: (context, snapshot) { - final tracks = snapshot.data; - final audioTracks = (tracks?.audio ?? []) - .where((track) => track.id != 'auto' && track.id != 'no') - .toList(); - - return BaseVideoControlSheet( - title: t.videoControls.audioLabel, - icon: Icons.audiotrack, - child: audioTracks.isEmpty - ? const Center( - child: Text( - 'No audio tracks available', - style: TextStyle(color: Colors.white70), - ), - ) - : StreamBuilder( - stream: player.stream.track, - initialData: player.state.track, - builder: (context, selectedSnapshot) { - // Use snapshot data or fall back to current state - final currentTrack = - selectedSnapshot.data ?? player.state.track; - final selectedTrack = currentTrack.audio; - final selectedId = selectedTrack.id; - - return ListView.builder( - itemCount: audioTracks.length, - itemBuilder: (context, index) { - final audioTrack = audioTracks[index]; - final isSelected = audioTrack.id == selectedId; - - final parts = []; - if (audioTrack.title != null && - audioTrack.title!.isNotEmpty) { - parts.add(audioTrack.title!); - } - if (audioTrack.language != null && - audioTrack.language!.isNotEmpty) { - parts.add(audioTrack.language!.toUpperCase()); - } - if (audioTrack.codec != null && - audioTrack.codec!.isNotEmpty) { - parts.add(audioTrack.codec!.toUpperCase()); - } - if (audioTrack.channelscount != null) { - parts.add('${audioTrack.channelscount}ch'); - } - - final label = parts.isEmpty - ? 'Audio Track ${index + 1}' - : parts.join(' · '); - - return ListTile( - title: Text( - label, - style: TextStyle( - color: isSelected ? Colors.blue : Colors.white, - ), - ), - trailing: isSelected - ? const Icon(Icons.check, color: Colors.blue) - : null, - onTap: () { - player.setAudioTrack(audioTrack); - onTrackChanged?.call(audioTrack); - Navigator.pop(context); - }, - ); - }, - ); - }, - ), - ); + player: player, + title: t.videoControls.audioLabel, + icon: Icons.audiotrack, + extractTracks: (tracks) => tracks?.audio ?? [], + getCurrentTrack: (track) => track.audio, + buildLabel: (audioTrack, index) { + final parts = []; + if (audioTrack.title != null && audioTrack.title!.isNotEmpty) { + parts.add(audioTrack.title!); + } + if (audioTrack.language != null && audioTrack.language!.isNotEmpty) { + parts.add(audioTrack.language!.toUpperCase()); + } + if (audioTrack.codec != null && audioTrack.codec!.isNotEmpty) { + parts.add(audioTrack.codec!.toUpperCase()); + } + if (audioTrack.channelscount != null) { + parts.add('${audioTrack.channelscount}ch'); + } + return parts.isEmpty ? 'Audio Track ${index + 1}' : parts.join(' · '); }, + setTrack: (track) => player.setAudioTrack(track), + onTrackChanged: onTrackChanged, ); } } diff --git a/lib/widgets/video_controls/sheets/base_video_control_sheet.dart b/lib/widgets/video_controls/sheets/base_video_control_sheet.dart index d02a875a..f15b756c 100644 --- a/lib/widgets/video_controls/sheets/base_video_control_sheet.dart +++ b/lib/widgets/video_controls/sheets/base_video_control_sheet.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'video_sheet_header.dart'; /// Base class for video control bottom sheets providing common UI structure class BaseVideoControlSheet extends StatelessWidget { @@ -6,6 +7,7 @@ class BaseVideoControlSheet extends StatelessWidget { final IconData icon; final Widget child; final Color? iconColor; + final VoidCallback? onBack; const BaseVideoControlSheet({ super.key, @@ -13,6 +15,7 @@ class BaseVideoControlSheet extends StatelessWidget { required this.icon, required this.child, this.iconColor, + this.onBack, }); /// Get consistent bottom sheet constraints across all video control sheets @@ -48,7 +51,12 @@ class BaseVideoControlSheet extends StatelessWidget { height: MediaQuery.of(context).size.height * 0.75, child: Column( children: [ - _buildHeader(context), + VideoSheetHeader( + title: title, + icon: icon, + iconColor: iconColor, + onBack: onBack, + ), const Divider(color: Colors.white24, height: 1), Expanded(child: child), ], @@ -56,29 +64,4 @@ class BaseVideoControlSheet extends StatelessWidget { ), ); } - - Widget _buildHeader(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Icon(icon, color: iconColor ?? Colors.white), - const SizedBox(width: 12), - Text( - title, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - IconButton( - icon: const Icon(Icons.close, color: Colors.white), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); - } } diff --git a/lib/widgets/video_controls/sheets/chapter_sheet.dart b/lib/widgets/video_controls/sheets/chapter_sheet.dart index fbf3aa4b..2fa21a73 100644 --- a/lib/widgets/video_controls/sheets/chapter_sheet.dart +++ b/lib/widgets/video_controls/sheets/chapter_sheet.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; -import 'package:provider/provider.dart'; import '../../../client/plex_client.dart'; import '../../../models/plex_media_info.dart'; -import '../../../providers/plex_client_provider.dart'; -import '../../../providers/multi_server_provider.dart'; import '../../../utils/duration_formatter.dart'; -import '../../../utils/app_logger.dart'; +import '../../../utils/provider_extensions.dart'; import 'base_video_control_sheet.dart'; /// Bottom sheet for selecting chapters @@ -43,21 +40,8 @@ class ChapterSheet extends StatelessWidget { } /// Get the correct PlexClient for the metadata's server - PlexClient? _getClientForChapters(BuildContext context) { - if (serverId == null) { - appLogger.w('Chapters have no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId!); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + PlexClient _getClientForChapters(BuildContext context) { + return context.getClientForServer(serverId); } @override diff --git a/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart b/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart index 90dd113f..a60c7865 100644 --- a/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart +++ b/lib/widgets/video_controls/sheets/subtitle_track_sheet.dart @@ -1,144 +1,51 @@ import 'package:flutter/material.dart'; import 'package:media_kit/media_kit.dart'; import '../../../i18n/strings.g.dart'; -import 'base_video_control_sheet.dart'; +import 'track_selection_sheet.dart'; /// Bottom sheet for selecting subtitle tracks -class SubtitleTrackSheet extends StatelessWidget { - final Player player; - final Function(SubtitleTrack)? onTrackChanged; - - const SubtitleTrackSheet({ - super.key, - required this.player, - this.onTrackChanged, - }); - +class SubtitleTrackSheet { static void show( BuildContext context, Player player, { Function(SubtitleTrack)? onTrackChanged, }) { - BaseVideoControlSheet.showSheet( + TrackSelectionSheet.show( context: context, - builder: (context) => - SubtitleTrackSheet(player: player, onTrackChanged: onTrackChanged), - ); - } - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: player.stream.tracks, - initialData: player.state.tracks, - builder: (context, snapshot) { - final tracks = snapshot.data; - final subtitles = (tracks?.subtitle ?? []) - .where((track) => track.id != 'auto' && track.id != 'no') - .toList(); - - return BaseVideoControlSheet( - title: t.videoControls.subtitlesLabel, - icon: Icons.subtitles, - child: subtitles.isEmpty - ? const Center( - child: Text( - 'No subtitles available', - style: TextStyle(color: Colors.white70), - ), - ) - : StreamBuilder( - stream: player.stream.track, - initialData: player.state.track, - builder: (context, selectedSnapshot) { - // Use snapshot data or fall back to current state - final currentTrack = - selectedSnapshot.data ?? player.state.track; - final selectedTrack = currentTrack.subtitle; - final selectedId = selectedTrack.id; - final isOffSelected = selectedId == 'no'; - - return ListView.builder( - itemCount: subtitles.length + 1, // +1 for "Off" option - itemBuilder: (context, index) { - // First item is "Off" - if (index == 0) { - return ListTile( - title: Text( - 'Off', - style: TextStyle( - color: isOffSelected - ? Colors.blue - : Colors.white, - ), - ), - trailing: isOffSelected - ? const Icon(Icons.check, color: Colors.blue) - : null, - onTap: () { - player.setSubtitleTrack(SubtitleTrack.no()); - onTrackChanged?.call(SubtitleTrack.no()); - Navigator.pop(context); - }, - ); - } - - // Subsequent items are subtitle tracks - final subtitle = subtitles[index - 1]; - final isSelected = subtitle.id == selectedId; - - // Build label with available info - final parts = []; - if (subtitle.title != null && - subtitle.title!.isNotEmpty) { - parts.add(subtitle.title!); - } - if (subtitle.language != null && - subtitle.language!.isNotEmpty) { - parts.add(subtitle.language!.toUpperCase()); - } - if (subtitle.codec != null && - subtitle.codec!.isNotEmpty) { - // Format codec names nicely - String codecName = subtitle.codec!.toUpperCase(); - if (codecName == 'SUBRIP') { - codecName = 'SRT'; - } else if (codecName == 'DVD_SUBTITLE') { - codecName = 'DVD'; - } else if (codecName == 'ASS' || codecName == 'SSA') { - codecName = codecName; // Keep as-is - } else if (codecName == 'WEBVTT') { - codecName = 'VTT'; - } - parts.add(codecName); - } - - final label = parts.isEmpty - ? 'Track $index' - : parts.join(' · '); - - return ListTile( - title: Text( - label, - style: TextStyle( - color: isSelected ? Colors.blue : Colors.white, - ), - ), - trailing: isSelected - ? const Icon(Icons.check, color: Colors.blue) - : null, - onTap: () { - player.setSubtitleTrack(subtitle); - onTrackChanged?.call(subtitle); - Navigator.pop(context); - }, - ); - }, - ); - }, - ), - ); + player: player, + title: t.videoControls.subtitlesLabel, + icon: Icons.subtitles, + extractTracks: (tracks) => tracks?.subtitle ?? [], + getCurrentTrack: (track) => track.subtitle, + buildLabel: (subtitle, index) { + final parts = []; + if (subtitle.title != null && subtitle.title!.isNotEmpty) { + parts.add(subtitle.title!); + } + if (subtitle.language != null && subtitle.language!.isNotEmpty) { + parts.add(subtitle.language!.toUpperCase()); + } + if (subtitle.codec != null && subtitle.codec!.isNotEmpty) { + // Format codec names nicely + String codecName = subtitle.codec!.toUpperCase(); + if (codecName == 'SUBRIP') { + codecName = 'SRT'; + } else if (codecName == 'DVD_SUBTITLE') { + codecName = 'DVD'; + } else if (codecName == 'ASS' || codecName == 'SSA') { + codecName = codecName; // Keep as-is + } else if (codecName == 'WEBVTT') { + codecName = 'VTT'; + } + parts.add(codecName); + } + return parts.isEmpty ? 'Track ${index + 1}' : parts.join(' · '); }, + setTrack: (track) => player.setSubtitleTrack(track), + onTrackChanged: onTrackChanged, + showOffOption: true, + createOffTrack: () => SubtitleTrack.no(), + isOffTrack: (track) => track.id == 'no', ); } } diff --git a/lib/widgets/video_controls/sheets/track_selection_sheet.dart b/lib/widgets/video_controls/sheets/track_selection_sheet.dart new file mode 100644 index 00000000..1439333b --- /dev/null +++ b/lib/widgets/video_controls/sheets/track_selection_sheet.dart @@ -0,0 +1,197 @@ +import 'package:flutter/material.dart'; +import 'package:media_kit/media_kit.dart'; +import 'base_video_control_sheet.dart'; + +/// Generic track selection sheet for audio and subtitle tracks +/// +/// Type parameter [T] should be either [AudioTrack] or [SubtitleTrack] +class TrackSelectionSheet extends StatelessWidget { + final Player player; + final String title; + final IconData icon; + final List Function(Tracks?) extractTracks; + final T Function(Track) getCurrentTrack; + final String Function(T track, int index) buildLabel; + final void Function(T track) setTrack; + final Function(T)? onTrackChanged; + final bool showOffOption; + final T Function()? createOffTrack; + final bool Function(T track)? isOffTrack; + + const TrackSelectionSheet({ + super.key, + required this.player, + required this.title, + required this.icon, + required this.extractTracks, + required this.getCurrentTrack, + required this.buildLabel, + required this.setTrack, + this.onTrackChanged, + this.showOffOption = false, + this.createOffTrack, + this.isOffTrack, + }); + + static void show({ + required BuildContext context, + required Player player, + required String title, + required IconData icon, + required List Function(Tracks?) extractTracks, + required T Function(Track) getCurrentTrack, + required String Function(T track, int index) buildLabel, + required void Function(T track) setTrack, + Function(T)? onTrackChanged, + bool showOffOption = false, + T Function()? createOffTrack, + bool Function(T track)? isOffTrack, + }) { + BaseVideoControlSheet.showSheet( + context: context, + builder: (context) => TrackSelectionSheet( + player: player, + title: title, + icon: icon, + extractTracks: extractTracks, + getCurrentTrack: getCurrentTrack, + buildLabel: buildLabel, + setTrack: setTrack, + onTrackChanged: onTrackChanged, + showOffOption: showOffOption, + createOffTrack: createOffTrack, + isOffTrack: isOffTrack, + ), + ); + } + + String _getEmptyMessage() { + if (T == SubtitleTrack) { + return 'No subtitles available'; + } else if (T == AudioTrack) { + return 'No audio tracks available'; + } + return 'No tracks available'; + } + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: player.stream.tracks, + initialData: player.state.tracks, + builder: (context, snapshot) { + final tracks = snapshot.data; + final availableTracks = extractTracks(tracks).where((track) { + // Filter out 'auto' and 'no' tracks from the list + if (track is AudioTrack) { + return track.id != 'auto' && track.id != 'no'; + } else if (track is SubtitleTrack) { + return track.id != 'auto' && track.id != 'no'; + } + return true; + }).toList(); + + return BaseVideoControlSheet( + title: title, + icon: icon, + child: availableTracks.isEmpty + ? Center( + child: Text( + _getEmptyMessage(), + style: const TextStyle(color: Colors.white70), + ), + ) + : StreamBuilder( + stream: player.stream.track, + initialData: player.state.track, + builder: (context, selectedSnapshot) { + final currentTrack = + selectedSnapshot.data ?? player.state.track; + final selectedTrack = getCurrentTrack(currentTrack); + + // Determine if "Off" is selected + final isOffSelected = + isOffTrack?.call(selectedTrack) ?? false; + + final itemCount = + availableTracks.length + (showOffOption ? 1 : 0); + + return ListView.builder( + itemCount: itemCount, + itemBuilder: (context, index) { + // First item is "Off" if enabled + if (showOffOption && index == 0) { + return ListTile( + title: Text( + 'Off', + style: TextStyle( + color: isOffSelected + ? Colors.blue + : Colors.white, + ), + ), + trailing: isOffSelected + ? const Icon(Icons.check, color: Colors.blue) + : null, + onTap: () { + if (createOffTrack != null) { + final offTrack = createOffTrack!(); + setTrack(offTrack); + onTrackChanged?.call(offTrack); + } + Navigator.pop(context); + }, + ); + } + + // Subsequent items are tracks + final trackIndex = showOffOption ? index - 1 : index; + final track = availableTracks[trackIndex]; + + // Check if this track is selected + String trackId; + if (track is AudioTrack) { + trackId = track.id; + } else if (track is SubtitleTrack) { + trackId = track.id; + } else { + trackId = ''; + } + + String selectedId; + if (selectedTrack is AudioTrack) { + selectedId = selectedTrack.id; + } else if (selectedTrack is SubtitleTrack) { + selectedId = selectedTrack.id; + } else { + selectedId = ''; + } + + final isSelected = trackId == selectedId; + final label = buildLabel(track, trackIndex); + + return ListTile( + title: Text( + label, + style: TextStyle( + color: isSelected ? Colors.blue : Colors.white, + ), + ), + trailing: isSelected + ? const Icon(Icons.check, color: Colors.blue) + : null, + onTap: () { + setTrack(track); + onTrackChanged?.call(track); + Navigator.pop(context); + }, + ); + }, + ); + }, + ), + ); + }, + ); + } +} diff --git a/lib/widgets/video_controls/sheets/video_settings_sheet.dart b/lib/widgets/video_controls/sheets/video_settings_sheet.dart index a0c301c7..37a6402b 100644 --- a/lib/widgets/video_controls/sheets/video_settings_sheet.dart +++ b/lib/widgets/video_controls/sheets/video_settings_sheet.dart @@ -7,6 +7,7 @@ import '../widgets/sync_offset_control.dart'; import '../widgets/sleep_timer_content.dart'; import '../../../i18n/strings.g.dart'; import 'base_video_control_sheet.dart'; +import 'video_sheet_header.dart'; enum _SettingsView { menu, speed, sleep, audioSync, subtitleSync, audioDevice } @@ -183,34 +184,11 @@ class _VideoSettingsSheetState extends State { _audioSyncOffset != 0 || _subtitleSyncOffset != 0); - return Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - // Back button or icon - if (_currentView != _SettingsView.menu) - IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: _navigateBack, - ) - else - Icon(_getIcon(), color: isIconActive ? Colors.amber : Colors.white), - const SizedBox(width: 12), - Text( - _getTitle(), - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - IconButton( - icon: const Icon(Icons.close, color: Colors.white), - onPressed: () => Navigator.pop(context), - ), - ], - ), + return VideoSheetHeader( + title: _getTitle(), + icon: _getIcon(), + iconColor: isIconActive ? Colors.amber : Colors.white, + onBack: _currentView != _SettingsView.menu ? _navigateBack : null, ); } diff --git a/lib/widgets/video_controls/sheets/video_sheet_header.dart b/lib/widgets/video_controls/sheets/video_sheet_header.dart new file mode 100644 index 00000000..c5170e0d --- /dev/null +++ b/lib/widgets/video_controls/sheets/video_sheet_header.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +/// Shared header widget for video control sheets +/// +/// Provides a consistent header with an icon/back button, title, and close button +class VideoSheetHeader extends StatelessWidget { + final String title; + final IconData? icon; + final Color? iconColor; + final VoidCallback? onBack; + final VoidCallback? onClose; + + const VideoSheetHeader({ + super.key, + required this.title, + this.icon, + this.iconColor, + this.onBack, + this.onClose, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + // Back button or icon + if (onBack != null) + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: onBack, + ) + else if (icon != null) + Icon(icon, color: iconColor ?? Colors.white), + const SizedBox(width: 12), + Text( + title, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: onClose ?? () => Navigator.pop(context), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/video_controls/video_controls.dart b/lib/widgets/video_controls/video_controls.dart index 10a0cd11..50b6f32f 100644 --- a/lib/widgets/video_controls/video_controls.dart +++ b/lib/widgets/video_controls/video_controls.dart @@ -106,23 +106,10 @@ class _PlexVideoControlsState extends State bool _isRotationLocked = true; // Default locked (landscape only) /// Get the correct PlexClient for this metadata's server - PlexClient? _getClientForMetadata() { - final serverId = widget.metadata.serverId; - if (serverId == null) { - appLogger.w('Metadata ${widget.metadata.title} has no serverId, using legacy client'); - return context.read().client; - } - - final multiServerProvider = context.read(); - final client = multiServerProvider.getClientForServer(serverId); - - if (client == null) { - appLogger.w('No client found for server $serverId, using legacy client'); - return context.read().client; - } - - return client; + PlexClient _getClientForMetadata() { + return context.getClientForServer(widget.metadata.serverId); } + // Double-tap feedback state bool _showDoubleTapFeedback = false; double _doubleTapFeedbackOpacity = 0.0;