feat(ratings): add tracker rating sheet

close #1031
This commit is contained in:
edde746
2026-05-16 18:03:30 +02:00
parent ee6e327b12
commit 310c4280fc
45 changed files with 2299 additions and 295 deletions
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Afspil fra begyndelsen",
"playVersion": "Afspil version..."
},
"rateSheet": {
"title": "Bedøm",
"server": "Server",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Angiv en score",
"notRated": "Ikke bedømt",
"liked": "Synes godt om",
"notLiked": "Ikke liket",
"saved": "Gemt",
"notAvailable": "Intet match fundet",
"noConnectedTrackers": "Forbind en tracker i Indstillinger for at bedømme der."
},
"accessibility": {
"mediaCardMovie": "${title}, film",
"mediaCardShow": "${title}, TV-serie",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Von Anfang an abspielen",
"playVersion": "Version abspielen..."
},
"rateSheet": {
"title": "Bewerten",
"server": "Server",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Eine Wertung festlegen",
"notRated": "Nicht bewertet",
"liked": "Gefällt mir",
"notLiked": "Nicht geliket",
"saved": "Gespeichert",
"notAvailable": "Keine Übereinstimmung gefunden",
"noConnectedTrackers": "Verbinde einen Tracker in den Einstellungen, um dort zu bewerten."
},
"accessibility": {
"mediaCardMovie": "${title}, Film",
"mediaCardShow": "${title}, Serie",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Play from Beginning",
"playVersion": "Play Version..."
},
"rateSheet": {
"title": "Rate",
"server": "Server",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Set a score",
"notRated": "Not rated",
"liked": "Liked",
"notLiked": "Not liked",
"saved": "Saved",
"notAvailable": "No match found",
"noConnectedTrackers": "Connect a tracker in Settings to rate there."
},
"accessibility": {
"mediaCardMovie": "${title}, movie",
"mediaCardShow": "${title}, TV show",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Reproducir desde el inicio",
"playVersion": "Reproducir versión..."
},
"rateSheet": {
"title": "Calificar",
"server": "Servidor",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Establecer puntuación",
"notRated": "Sin calificar",
"liked": "Me gusta",
"notLiked": "Sin Me gusta",
"saved": "Guardado",
"notAvailable": "No se encontró coincidencia",
"noConnectedTrackers": "Conecta un tracker en Ajustes para calificar allí."
},
"accessibility": {
"mediaCardMovie": "${title}, película",
"mediaCardShow": "${title}, serie de TV",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Lire depuis le début",
"playVersion": "Lire la version..."
},
"rateSheet": {
"title": "Noter",
"server": "Serveur",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Définir une note",
"notRated": "Non noté",
"liked": "Aimé",
"notLiked": "Non aimé",
"saved": "Enregistré",
"notAvailable": "Aucune correspondance trouvée",
"noConnectedTrackers": "Connectez un tracker dans Paramètres pour y noter."
},
"accessibility": {
"mediaCardMovie": "${title}, film",
"mediaCardShow": "${title}, show TV",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Riproduci dall'inizio",
"playVersion": "Riproduci versione..."
},
"rateSheet": {
"title": "Valuta",
"server": "Server",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Imposta un punteggio",
"notRated": "Non valutato",
"liked": "Mi piace",
"notLiked": "Non contrassegnato con Mi piace",
"saved": "Salvato",
"notAvailable": "Nessuna corrispondenza trovata",
"noConnectedTrackers": "Collega un tracker in Impostazioni per valutarlo lì."
},
"accessibility": {
"mediaCardMovie": "${title}, film",
"mediaCardShow": "${title}, serie TV",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "最初から再生",
"playVersion": "バージョンを再生..."
},
"rateSheet": {
"title": "評価",
"server": "サーバー",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "スコアを設定",
"notRated": "未評価",
"liked": "いいね済み",
"notLiked": "いいねなし",
"saved": "保存済み",
"notAvailable": "一致なし",
"noConnectedTrackers": "設定でトラッカーを接続すると、そこで評価できます。"
},
"accessibility": {
"mediaCardMovie": "${title}、映画",
"mediaCardShow": "${title}、テレビ番組",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "처음부터 재생",
"playVersion": "버전 재생..."
},
"rateSheet": {
"title": "평가",
"server": "서버",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "점수 설정",
"notRated": "평가 없음",
"liked": "좋아요",
"notLiked": "좋아요 아님",
"saved": "저장됨",
"notAvailable": "일치 항목 없음",
"noConnectedTrackers": "설정에서 트래커를 연결하면 거기에도 평가할 수 있습니다."
},
"accessibility": {
"mediaCardMovie": "${title}, 영화",
"mediaCardShow": "${title}, TV 프로그램",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Spill fra begynnelsen",
"playVersion": "Spill av versjon..."
},
"rateSheet": {
"title": "Vurder",
"server": "Server",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Sett en poengsum",
"notRated": "Ikke vurdert",
"liked": "Likt",
"notLiked": "Ikke likt",
"saved": "Lagret",
"notAvailable": "Ingen treff",
"noConnectedTrackers": "Koble til en sporer i Innstillinger for å vurdere der."
},
"accessibility": {
"mediaCardMovie": "${title}, film",
"mediaCardShow": "${title}, TV-serie",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Afspelen vanaf het begin",
"playVersion": "Versie afspelen..."
},
"rateSheet": {
"title": "Beoordelen",
"server": "Server",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Score instellen",
"notRated": "Niet beoordeeld",
"liked": "Geliket",
"notLiked": "Niet geliket",
"saved": "Opgeslagen",
"notAvailable": "Geen match gevonden",
"noConnectedTrackers": "Verbind een tracker in Instellingen om daar te beoordelen."
},
"accessibility": {
"mediaCardMovie": "${title}, film",
"mediaCardShow": "${title}, TV-serie",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Odtwórz od początku",
"playVersion": "Odtwórz wersję..."
},
"rateSheet": {
"title": "Oceń",
"server": "Serwer",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Ustaw ocenę",
"notRated": "Nie oceniono",
"liked": "Polubione",
"notLiked": "Nie polubiono",
"saved": "Zapisano",
"notAvailable": "Nie znaleziono dopasowania",
"noConnectedTrackers": "Połącz tracker w Ustawieniach, aby tam oceniać."
},
"accessibility": {
"mediaCardMovie": "${title}, film",
"mediaCardShow": "${title}, serial TV",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Reproduzir do início",
"playVersion": "Reproduzir versão..."
},
"rateSheet": {
"title": "Avaliar",
"server": "Servidor",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Definir pontuação",
"notRated": "Sem avaliação",
"liked": "Gostei",
"notLiked": "Não marcado como gostei",
"saved": "Salvo",
"notAvailable": "Nenhuma correspondência encontrada",
"noConnectedTrackers": "Conecte um rastreador em Configurações para avaliar lá."
},
"accessibility": {
"mediaCardMovie": "${title}, filme",
"mediaCardShow": "${title}, série de TV",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Воспроизвести сначала",
"playVersion": "Воспроизвести версию..."
},
"rateSheet": {
"title": "Оценить",
"server": "Сервер",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Установить оценку",
"notRated": "Без оценки",
"liked": "Понравилось",
"notLiked": "Не отмечено понравившимся",
"saved": "Сохранено",
"notAvailable": "Совпадений не найдено",
"noConnectedTrackers": "Подключите трекер в настройках, чтобы оценивать там."
},
"accessibility": {
"mediaCardMovie": "${title}, фильм",
"mediaCardShow": "${title}, сериал",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 15
/// Strings: 17295 (1153 per locale)
/// Strings: 17460 (1164 per locale)
///
/// Built on 2026-05-13 at 05:26 UTC
/// Built on 2026-05-16 at 13:41 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsDa extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysDa hotkeys = _TranslationsHotkeysDa._(_root);
@override late final _TranslationsFileInfoDa fileInfo = _TranslationsFileInfoDa._(_root);
@override late final _TranslationsMediaMenuDa mediaMenu = _TranslationsMediaMenuDa._(_root);
@override late final _TranslationsRateSheetDa rateSheet = _TranslationsRateSheetDa._(_root);
@override late final _TranslationsAccessibilityDa accessibility = _TranslationsAccessibilityDa._(_root);
@override late final _TranslationsTooltipsDa tooltips = _TranslationsTooltipsDa._(_root);
@override late final _TranslationsVideoControlsDa videoControls = _TranslationsVideoControlsDa._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuDa extends TranslationsMediaMenuEn {
@override String get playVersion => 'Afspil version...';
}
// Path: rateSheet
class _TranslationsRateSheetDa extends TranslationsRateSheetEn {
_TranslationsRateSheetDa._(TranslationsDa root) : this._root = root, super.internal(root);
final TranslationsDa _root; // ignore: unused_field
// Translations
@override String get title => 'Bedøm';
@override String get server => 'Server';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Angiv en score';
@override String get notRated => 'Ikke bedømt';
@override String get liked => 'Synes godt om';
@override String get notLiked => 'Ikke liket';
@override String get saved => 'Gemt';
@override String get notAvailable => 'Intet match fundet';
@override String get noConnectedTrackers => 'Forbind en tracker i Indstillinger for at bedømme der.';
}
// Path: accessibility
class _TranslationsAccessibilityDa extends TranslationsAccessibilityEn {
_TranslationsAccessibilityDa._(TranslationsDa root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsDa {
'mediaMenu.rate' => 'Bedøm',
'mediaMenu.playFromBeginning' => 'Afspil fra begyndelsen',
'mediaMenu.playVersion' => 'Afspil version...',
'rateSheet.title' => 'Bedøm',
'rateSheet.server' => 'Server',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Angiv en score',
'rateSheet.notRated' => 'Ikke bedømt',
'rateSheet.liked' => 'Synes godt om',
'rateSheet.notLiked' => 'Ikke liket',
'rateSheet.saved' => 'Gemt',
'rateSheet.notAvailable' => 'Intet match fundet',
'rateSheet.noConnectedTrackers' => 'Forbind en tracker i Indstillinger for at bedømme der.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV-serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsDa {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profiler',
'profiles.removeConnectionTitle' => 'Fjern forbindelse?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Fjern ${displayName}s adgang til ${connectionLabel}. Andre profiler beholder den.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Slet profil?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Fjern ${displayName} og forbindelserne. Servere forbliver tilgængelige.',
'profiles.profileNameLabel' => 'Profilnavn',
@@ -2284,8 +2318,6 @@ extension on TranslationsDa {
'profiles.confirmPinTitle' => 'Bekræft PIN',
'profiles.pinSet' => 'PIN angivet',
'profiles.changePin' => 'Skift',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Fjern',
'profiles.connectionsLabel' => 'Forbindelser',
'profiles.add' => 'Tilføj',
@@ -2787,6 +2819,8 @@ extension on TranslationsDa {
'metadataEdit.selectSquareArt' => 'Vælg kvadratisk billede',
'metadataEdit.fromUrl' => 'Fra URL',
'metadataEdit.uploadFile' => 'Upload fil',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Indtast billed-URL',
'metadataEdit.imageUrl' => 'Billed-URL',
'metadataEdit.metadataUpdated' => 'Metadata opdateret',
@@ -2798,8 +2832,6 @@ extension on TranslationsDa {
'metadataEdit.libraryDefault' => 'Biblioteksstandard',
'metadataEdit.accountDefault' => 'Kontostandard',
'metadataEdit.seriesDefault' => 'Seriestandard',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Episodesortering',
'metadataEdit.oldestFirst' => 'Ældste først',
'metadataEdit.newestFirst' => 'Nyeste først',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsDe extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysDe hotkeys = _TranslationsHotkeysDe._(_root);
@override late final _TranslationsFileInfoDe fileInfo = _TranslationsFileInfoDe._(_root);
@override late final _TranslationsMediaMenuDe mediaMenu = _TranslationsMediaMenuDe._(_root);
@override late final _TranslationsRateSheetDe rateSheet = _TranslationsRateSheetDe._(_root);
@override late final _TranslationsAccessibilityDe accessibility = _TranslationsAccessibilityDe._(_root);
@override late final _TranslationsTooltipsDe tooltips = _TranslationsTooltipsDe._(_root);
@override late final _TranslationsVideoControlsDe videoControls = _TranslationsVideoControlsDe._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuDe extends TranslationsMediaMenuEn {
@override String get playVersion => 'Version abspielen...';
}
// Path: rateSheet
class _TranslationsRateSheetDe extends TranslationsRateSheetEn {
_TranslationsRateSheetDe._(TranslationsDe root) : this._root = root, super.internal(root);
final TranslationsDe _root; // ignore: unused_field
// Translations
@override String get title => 'Bewerten';
@override String get server => 'Server';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Eine Wertung festlegen';
@override String get notRated => 'Nicht bewertet';
@override String get liked => 'Gefällt mir';
@override String get notLiked => 'Nicht geliket';
@override String get saved => 'Gespeichert';
@override String get notAvailable => 'Keine Übereinstimmung gefunden';
@override String get noConnectedTrackers => 'Verbinde einen Tracker in den Einstellungen, um dort zu bewerten.';
}
// Path: accessibility
class _TranslationsAccessibilityDe extends TranslationsAccessibilityEn {
_TranslationsAccessibilityDe._(TranslationsDe root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsDe {
'mediaMenu.rate' => 'Bewerten',
'mediaMenu.playFromBeginning' => 'Von Anfang an abspielen',
'mediaMenu.playVersion' => 'Version abspielen...',
'rateSheet.title' => 'Bewerten',
'rateSheet.server' => 'Server',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Eine Wertung festlegen',
'rateSheet.notRated' => 'Nicht bewertet',
'rateSheet.liked' => 'Gefällt mir',
'rateSheet.notLiked' => 'Nicht geliket',
'rateSheet.saved' => 'Gespeichert',
'rateSheet.notAvailable' => 'Keine Übereinstimmung gefunden',
'rateSheet.noConnectedTrackers' => 'Verbinde einen Tracker in den Einstellungen, um dort zu bewerten.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, Film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, Serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsDe {
'profiles.summaryMultiple' => ({required Object count}) => '${count} Profile',
'profiles.removeConnectionTitle' => 'Verbindung entfernen?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Zugriff von ${displayName} auf ${connectionLabel} entfernen. Andere Profile behalten ihn.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Profil löschen?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => '${displayName} und Verbindungen entfernen. Server bleiben verfügbar.',
'profiles.profileNameLabel' => 'Profilname',
@@ -2284,8 +2318,6 @@ extension on TranslationsDe {
'profiles.confirmPinTitle' => 'PIN bestätigen',
'profiles.pinSet' => 'PIN festgelegt',
'profiles.changePin' => 'Ändern',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Entfernen',
'profiles.connectionsLabel' => 'Verbindungen',
'profiles.add' => 'Hinzufügen',
@@ -2787,6 +2819,8 @@ extension on TranslationsDe {
'metadataEdit.selectSquareArt' => 'Quadratisches Bild auswählen',
'metadataEdit.fromUrl' => 'Von URL',
'metadataEdit.uploadFile' => 'Datei hochladen',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Bild-URL eingeben',
'metadataEdit.imageUrl' => 'Bild-URL',
'metadataEdit.metadataUpdated' => 'Metadaten aktualisiert',
@@ -2798,8 +2832,6 @@ extension on TranslationsDe {
'metadataEdit.libraryDefault' => 'Bibliotheksstandard',
'metadataEdit.accountDefault' => 'Kontostandard',
'metadataEdit.seriesDefault' => 'Serienstandard',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Episodensortierung',
'metadataEdit.oldestFirst' => 'Älteste zuerst',
'metadataEdit.newestFirst' => 'Neueste zuerst',
+58 -4
View File
@@ -50,6 +50,7 @@ class Translations with BaseTranslations<AppLocale, Translations> {
late final TranslationsHotkeysEn hotkeys = TranslationsHotkeysEn.internal(_root);
late final TranslationsFileInfoEn fileInfo = TranslationsFileInfoEn.internal(_root);
late final TranslationsMediaMenuEn mediaMenu = TranslationsMediaMenuEn.internal(_root);
late final TranslationsRateSheetEn rateSheet = TranslationsRateSheetEn.internal(_root);
late final TranslationsAccessibilityEn accessibility = TranslationsAccessibilityEn.internal(_root);
late final TranslationsTooltipsEn tooltips = TranslationsTooltipsEn.internal(_root);
late final TranslationsVideoControlsEn videoControls = TranslationsVideoControlsEn.internal(_root);
@@ -1093,6 +1094,48 @@ class TranslationsMediaMenuEn {
String get playVersion => 'Play Version...';
}
// Path: rateSheet
class TranslationsRateSheetEn {
TranslationsRateSheetEn.internal(this._root);
final Translations _root; // ignore: unused_field
// Translations
/// en: 'Rate'
String get title => 'Rate';
/// en: 'Server'
String get server => 'Server';
/// en: '${rating} / 5'
String starValue({required Object rating}) => '${rating} / 5';
/// en: '${score} / 10'
String scoreValue({required Object score}) => '${score} / 10';
/// en: 'Set a score'
String get setScore => 'Set a score';
/// en: 'Not rated'
String get notRated => 'Not rated';
/// en: 'Liked'
String get liked => 'Liked';
/// en: 'Not liked'
String get notLiked => 'Not liked';
/// en: 'Saved'
String get saved => 'Saved';
/// en: 'No match found'
String get notAvailable => 'No match found';
/// en: 'Connect a tracker in Settings to rate there.'
String get noConnectedTrackers => 'Connect a tracker in Settings to rate there.';
}
// Path: accessibility
class TranslationsAccessibilityEn {
TranslationsAccessibilityEn.internal(this._root);
@@ -4410,6 +4453,17 @@ extension on Translations {
'mediaMenu.rate' => 'Rate',
'mediaMenu.playFromBeginning' => 'Play from Beginning',
'mediaMenu.playVersion' => 'Play Version...',
'rateSheet.title' => 'Rate',
'rateSheet.server' => 'Server',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Set a score',
'rateSheet.notRated' => 'Not rated',
'rateSheet.liked' => 'Liked',
'rateSheet.notLiked' => 'Not liked',
'rateSheet.saved' => 'Saved',
'rateSheet.notAvailable' => 'No match found',
'rateSheet.noConnectedTrackers' => 'Connect a tracker in Settings to rate there.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, movie',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV show',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -4585,6 +4639,8 @@ extension on Translations {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profiles',
'profiles.removeConnectionTitle' => 'Remove connection?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Remove ${displayName}\'s access to ${connectionLabel}. Other profiles keep it.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Delete profile?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Remove ${displayName} and its connections. Servers stay available.',
'profiles.profileNameLabel' => 'Profile name',
@@ -4596,8 +4652,6 @@ extension on Translations {
'profiles.confirmPinTitle' => 'Confirm PIN',
'profiles.pinSet' => 'PIN set',
'profiles.changePin' => 'Change',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Remove',
'profiles.connectionsLabel' => 'Connections',
'profiles.add' => 'Add',
@@ -5099,6 +5153,8 @@ extension on Translations {
'metadataEdit.selectSquareArt' => 'Select Square Art',
'metadataEdit.fromUrl' => 'From URL',
'metadataEdit.uploadFile' => 'Upload File',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Enter image URL',
'metadataEdit.imageUrl' => 'Image URL',
'metadataEdit.metadataUpdated' => 'Metadata updated',
@@ -5110,8 +5166,6 @@ extension on Translations {
'metadataEdit.libraryDefault' => 'Library default',
'metadataEdit.accountDefault' => 'Account default',
'metadataEdit.seriesDefault' => 'Series default',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Episode Sorting',
'metadataEdit.oldestFirst' => 'Oldest first',
'metadataEdit.newestFirst' => 'Newest first',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysEs hotkeys = _TranslationsHotkeysEs._(_root);
@override late final _TranslationsFileInfoEs fileInfo = _TranslationsFileInfoEs._(_root);
@override late final _TranslationsMediaMenuEs mediaMenu = _TranslationsMediaMenuEs._(_root);
@override late final _TranslationsRateSheetEs rateSheet = _TranslationsRateSheetEs._(_root);
@override late final _TranslationsAccessibilityEs accessibility = _TranslationsAccessibilityEs._(_root);
@override late final _TranslationsTooltipsEs tooltips = _TranslationsTooltipsEs._(_root);
@override late final _TranslationsVideoControlsEs videoControls = _TranslationsVideoControlsEs._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuEs extends TranslationsMediaMenuEn {
@override String get playVersion => 'Reproducir versión...';
}
// Path: rateSheet
class _TranslationsRateSheetEs extends TranslationsRateSheetEn {
_TranslationsRateSheetEs._(TranslationsEs root) : this._root = root, super.internal(root);
final TranslationsEs _root; // ignore: unused_field
// Translations
@override String get title => 'Calificar';
@override String get server => 'Servidor';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Establecer puntuación';
@override String get notRated => 'Sin calificar';
@override String get liked => 'Me gusta';
@override String get notLiked => 'Sin Me gusta';
@override String get saved => 'Guardado';
@override String get notAvailable => 'No se encontró coincidencia';
@override String get noConnectedTrackers => 'Conecta un tracker en Ajustes para calificar allí.';
}
// Path: accessibility
class _TranslationsAccessibilityEs extends TranslationsAccessibilityEn {
_TranslationsAccessibilityEs._(TranslationsEs root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsEs {
'mediaMenu.rate' => 'Calificar',
'mediaMenu.playFromBeginning' => 'Reproducir desde el inicio',
'mediaMenu.playVersion' => 'Reproducir versión...',
'rateSheet.title' => 'Calificar',
'rateSheet.server' => 'Servidor',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Establecer puntuación',
'rateSheet.notRated' => 'Sin calificar',
'rateSheet.liked' => 'Me gusta',
'rateSheet.notLiked' => 'Sin Me gusta',
'rateSheet.saved' => 'Guardado',
'rateSheet.notAvailable' => 'No se encontró coincidencia',
'rateSheet.noConnectedTrackers' => 'Conecta un tracker en Ajustes para calificar allí.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, película',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, serie de TV',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsEs {
'profiles.summaryMultiple' => ({required Object count}) => '${count} perfiles',
'profiles.removeConnectionTitle' => '¿Eliminar conexión?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Eliminar el acceso de ${displayName} a ${connectionLabel}. Los demás perfiles lo conservan.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => '¿Eliminar perfil?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Eliminar ${displayName} y sus conexiones. Los servidores seguirán disponibles.',
'profiles.profileNameLabel' => 'Nombre del perfil',
@@ -2284,8 +2318,6 @@ extension on TranslationsEs {
'profiles.confirmPinTitle' => 'Confirmar PIN',
'profiles.pinSet' => 'PIN establecido',
'profiles.changePin' => 'Cambiar',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Eliminar',
'profiles.connectionsLabel' => 'Conexiones',
'profiles.add' => 'Añadir',
@@ -2787,6 +2819,8 @@ extension on TranslationsEs {
'metadataEdit.selectSquareArt' => 'Seleccionar imagen cuadrada',
'metadataEdit.fromUrl' => 'Desde URL',
'metadataEdit.uploadFile' => 'Subir archivo',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Introducir URL de imagen',
'metadataEdit.imageUrl' => 'URL de imagen',
'metadataEdit.metadataUpdated' => 'Metadatos actualizados',
@@ -2798,8 +2832,6 @@ extension on TranslationsEs {
'metadataEdit.libraryDefault' => 'Predeterminado de biblioteca',
'metadataEdit.accountDefault' => 'Predeterminado de cuenta',
'metadataEdit.seriesDefault' => 'Predeterminado de serie',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Orden de episodios',
'metadataEdit.oldestFirst' => 'Más antiguos primero',
'metadataEdit.newestFirst' => 'Más recientes primero',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsFr extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysFr hotkeys = _TranslationsHotkeysFr._(_root);
@override late final _TranslationsFileInfoFr fileInfo = _TranslationsFileInfoFr._(_root);
@override late final _TranslationsMediaMenuFr mediaMenu = _TranslationsMediaMenuFr._(_root);
@override late final _TranslationsRateSheetFr rateSheet = _TranslationsRateSheetFr._(_root);
@override late final _TranslationsAccessibilityFr accessibility = _TranslationsAccessibilityFr._(_root);
@override late final _TranslationsTooltipsFr tooltips = _TranslationsTooltipsFr._(_root);
@override late final _TranslationsVideoControlsFr videoControls = _TranslationsVideoControlsFr._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuFr extends TranslationsMediaMenuEn {
@override String get playVersion => 'Lire la version...';
}
// Path: rateSheet
class _TranslationsRateSheetFr extends TranslationsRateSheetEn {
_TranslationsRateSheetFr._(TranslationsFr root) : this._root = root, super.internal(root);
final TranslationsFr _root; // ignore: unused_field
// Translations
@override String get title => 'Noter';
@override String get server => 'Serveur';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Définir une note';
@override String get notRated => 'Non noté';
@override String get liked => 'Aimé';
@override String get notLiked => 'Non aimé';
@override String get saved => 'Enregistré';
@override String get notAvailable => 'Aucune correspondance trouvée';
@override String get noConnectedTrackers => 'Connectez un tracker dans Paramètres pour y noter.';
}
// Path: accessibility
class _TranslationsAccessibilityFr extends TranslationsAccessibilityEn {
_TranslationsAccessibilityFr._(TranslationsFr root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsFr {
'mediaMenu.rate' => 'Noter',
'mediaMenu.playFromBeginning' => 'Lire depuis le début',
'mediaMenu.playVersion' => 'Lire la version...',
'rateSheet.title' => 'Noter',
'rateSheet.server' => 'Serveur',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Définir une note',
'rateSheet.notRated' => 'Non noté',
'rateSheet.liked' => 'Aimé',
'rateSheet.notLiked' => 'Non aimé',
'rateSheet.saved' => 'Enregistré',
'rateSheet.notAvailable' => 'Aucune correspondance trouvée',
'rateSheet.noConnectedTrackers' => 'Connectez un tracker dans Paramètres pour y noter.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, show TV',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsFr {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profils',
'profiles.removeConnectionTitle' => 'Retirer la connexion ?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Supprimer l\'accès de ${displayName} à ${connectionLabel}. Les autres profils le conservent.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Supprimer le profil ?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Supprimer ${displayName} et ses connexions. Les serveurs restent disponibles.',
'profiles.profileNameLabel' => 'Nom du profil',
@@ -2284,8 +2318,6 @@ extension on TranslationsFr {
'profiles.confirmPinTitle' => 'Confirmer le PIN',
'profiles.pinSet' => 'PIN défini',
'profiles.changePin' => 'Modifier',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Retirer',
'profiles.connectionsLabel' => 'Connexions',
'profiles.add' => 'Ajouter',
@@ -2787,6 +2819,8 @@ extension on TranslationsFr {
'metadataEdit.selectSquareArt' => 'Sélectionner l\'image carrée',
'metadataEdit.fromUrl' => 'Depuis une URL',
'metadataEdit.uploadFile' => 'Importer un fichier',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Entrer l\'URL de l\'image',
'metadataEdit.imageUrl' => 'URL de l\'image',
'metadataEdit.metadataUpdated' => 'Métadonnées mises à jour',
@@ -2798,8 +2832,6 @@ extension on TranslationsFr {
'metadataEdit.libraryDefault' => 'Par défaut de la bibliothèque',
'metadataEdit.accountDefault' => 'Par défaut du compte',
'metadataEdit.seriesDefault' => 'Par défaut de la série',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Tri des épisodes',
'metadataEdit.oldestFirst' => 'Plus anciens en premier',
'metadataEdit.newestFirst' => 'Plus récents en premier',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsIt extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysIt hotkeys = _TranslationsHotkeysIt._(_root);
@override late final _TranslationsFileInfoIt fileInfo = _TranslationsFileInfoIt._(_root);
@override late final _TranslationsMediaMenuIt mediaMenu = _TranslationsMediaMenuIt._(_root);
@override late final _TranslationsRateSheetIt rateSheet = _TranslationsRateSheetIt._(_root);
@override late final _TranslationsAccessibilityIt accessibility = _TranslationsAccessibilityIt._(_root);
@override late final _TranslationsTooltipsIt tooltips = _TranslationsTooltipsIt._(_root);
@override late final _TranslationsVideoControlsIt videoControls = _TranslationsVideoControlsIt._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuIt extends TranslationsMediaMenuEn {
@override String get playVersion => 'Riproduci versione...';
}
// Path: rateSheet
class _TranslationsRateSheetIt extends TranslationsRateSheetEn {
_TranslationsRateSheetIt._(TranslationsIt root) : this._root = root, super.internal(root);
final TranslationsIt _root; // ignore: unused_field
// Translations
@override String get title => 'Valuta';
@override String get server => 'Server';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Imposta un punteggio';
@override String get notRated => 'Non valutato';
@override String get liked => 'Mi piace';
@override String get notLiked => 'Non contrassegnato con Mi piace';
@override String get saved => 'Salvato';
@override String get notAvailable => 'Nessuna corrispondenza trovata';
@override String get noConnectedTrackers => 'Collega un tracker in Impostazioni per valutarlo lì.';
}
// Path: accessibility
class _TranslationsAccessibilityIt extends TranslationsAccessibilityEn {
_TranslationsAccessibilityIt._(TranslationsIt root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsIt {
'mediaMenu.rate' => 'Valuta',
'mediaMenu.playFromBeginning' => 'Riproduci dall\'inizio',
'mediaMenu.playVersion' => 'Riproduci versione...',
'rateSheet.title' => 'Valuta',
'rateSheet.server' => 'Server',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Imposta un punteggio',
'rateSheet.notRated' => 'Non valutato',
'rateSheet.liked' => 'Mi piace',
'rateSheet.notLiked' => 'Non contrassegnato con Mi piace',
'rateSheet.saved' => 'Salvato',
'rateSheet.notAvailable' => 'Nessuna corrispondenza trovata',
'rateSheet.noConnectedTrackers' => 'Collega un tracker in Impostazioni per valutarlo lì.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, serie TV',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsIt {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profili',
'profiles.removeConnectionTitle' => 'Rimuovere la connessione?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Rimuovi l\'accesso di ${displayName} a ${connectionLabel}. Gli altri profili lo mantengono.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Eliminare il profilo?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Rimuovi ${displayName} e le sue connessioni. I server restano disponibili.',
'profiles.profileNameLabel' => 'Nome profilo',
@@ -2284,8 +2318,6 @@ extension on TranslationsIt {
'profiles.confirmPinTitle' => 'Conferma PIN',
'profiles.pinSet' => 'PIN impostato',
'profiles.changePin' => 'Cambia',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Rimuovi',
'profiles.connectionsLabel' => 'Connessioni',
'profiles.add' => 'Aggiungi',
@@ -2787,6 +2819,8 @@ extension on TranslationsIt {
'metadataEdit.selectSquareArt' => 'Seleziona immagine quadrata',
'metadataEdit.fromUrl' => 'Da URL',
'metadataEdit.uploadFile' => 'Carica file',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Inserisci URL immagine',
'metadataEdit.imageUrl' => 'URL immagine',
'metadataEdit.metadataUpdated' => 'Metadati aggiornati',
@@ -2798,8 +2832,6 @@ extension on TranslationsIt {
'metadataEdit.libraryDefault' => 'Predefinito libreria',
'metadataEdit.accountDefault' => 'Predefinito account',
'metadataEdit.seriesDefault' => 'Predefinito serie',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Ordinamento episodi',
'metadataEdit.oldestFirst' => 'Più vecchi prima',
'metadataEdit.newestFirst' => 'Più recenti prima',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsJa extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysJa hotkeys = _TranslationsHotkeysJa._(_root);
@override late final _TranslationsFileInfoJa fileInfo = _TranslationsFileInfoJa._(_root);
@override late final _TranslationsMediaMenuJa mediaMenu = _TranslationsMediaMenuJa._(_root);
@override late final _TranslationsRateSheetJa rateSheet = _TranslationsRateSheetJa._(_root);
@override late final _TranslationsAccessibilityJa accessibility = _TranslationsAccessibilityJa._(_root);
@override late final _TranslationsTooltipsJa tooltips = _TranslationsTooltipsJa._(_root);
@override late final _TranslationsVideoControlsJa videoControls = _TranslationsVideoControlsJa._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuJa extends TranslationsMediaMenuEn {
@override String get playVersion => 'バージョンを再生...';
}
// Path: rateSheet
class _TranslationsRateSheetJa extends TranslationsRateSheetEn {
_TranslationsRateSheetJa._(TranslationsJa root) : this._root = root, super.internal(root);
final TranslationsJa _root; // ignore: unused_field
// Translations
@override String get title => '評価';
@override String get server => 'サーバー';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'スコアを設定';
@override String get notRated => '未評価';
@override String get liked => 'いいね済み';
@override String get notLiked => 'いいねなし';
@override String get saved => '保存済み';
@override String get notAvailable => '一致なし';
@override String get noConnectedTrackers => '設定でトラッカーを接続すると、そこで評価できます。';
}
// Path: accessibility
class _TranslationsAccessibilityJa extends TranslationsAccessibilityEn {
_TranslationsAccessibilityJa._(TranslationsJa root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsJa {
'mediaMenu.rate' => '評価',
'mediaMenu.playFromBeginning' => '最初から再生',
'mediaMenu.playVersion' => 'バージョンを再生...',
'rateSheet.title' => '評価',
'rateSheet.server' => 'サーバー',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'スコアを設定',
'rateSheet.notRated' => '未評価',
'rateSheet.liked' => 'いいね済み',
'rateSheet.notLiked' => 'いいねなし',
'rateSheet.saved' => '保存済み',
'rateSheet.notAvailable' => '一致なし',
'rateSheet.noConnectedTrackers' => '設定でトラッカーを接続すると、そこで評価できます。',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}、映画',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}、テレビ番組',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsJa {
'profiles.summaryMultiple' => ({required Object count}) => '${count}個のプロファイル',
'profiles.removeConnectionTitle' => '接続を削除しますか?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => '${displayName}${connectionLabel}へのアクセスを削除します。他のプロフィールには残ります。',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'プロファイルを削除しますか?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => '${displayName}とその接続を削除します。サーバーは引き続き利用できます。',
'profiles.profileNameLabel' => 'プロファイル名',
@@ -2284,8 +2318,6 @@ extension on TranslationsJa {
'profiles.confirmPinTitle' => 'PINを確認',
'profiles.pinSet' => 'PIN設定済み',
'profiles.changePin' => '変更',
_ => null,
} ?? switch (path) {
'profiles.removePin' => '削除',
'profiles.connectionsLabel' => '接続',
'profiles.add' => '追加',
@@ -2787,6 +2819,8 @@ extension on TranslationsJa {
'metadataEdit.selectSquareArt' => '正方形アートを選択',
'metadataEdit.fromUrl' => 'URLから',
'metadataEdit.uploadFile' => 'ファイルをアップロード',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => '画像URLを入力',
'metadataEdit.imageUrl' => '画像URL',
'metadataEdit.metadataUpdated' => 'メタデータを更新しました',
@@ -2798,8 +2832,6 @@ extension on TranslationsJa {
'metadataEdit.libraryDefault' => 'ライブラリのデフォルト',
'metadataEdit.accountDefault' => 'アカウントのデフォルト',
'metadataEdit.seriesDefault' => 'シリーズのデフォルト',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'エピソードの並べ替え',
'metadataEdit.oldestFirst' => '古い順',
'metadataEdit.newestFirst' => '新しい順',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsKo extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysKo hotkeys = _TranslationsHotkeysKo._(_root);
@override late final _TranslationsFileInfoKo fileInfo = _TranslationsFileInfoKo._(_root);
@override late final _TranslationsMediaMenuKo mediaMenu = _TranslationsMediaMenuKo._(_root);
@override late final _TranslationsRateSheetKo rateSheet = _TranslationsRateSheetKo._(_root);
@override late final _TranslationsAccessibilityKo accessibility = _TranslationsAccessibilityKo._(_root);
@override late final _TranslationsTooltipsKo tooltips = _TranslationsTooltipsKo._(_root);
@override late final _TranslationsVideoControlsKo videoControls = _TranslationsVideoControlsKo._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuKo extends TranslationsMediaMenuEn {
@override String get playVersion => '버전 재생...';
}
// Path: rateSheet
class _TranslationsRateSheetKo extends TranslationsRateSheetEn {
_TranslationsRateSheetKo._(TranslationsKo root) : this._root = root, super.internal(root);
final TranslationsKo _root; // ignore: unused_field
// Translations
@override String get title => '평가';
@override String get server => '서버';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => '점수 설정';
@override String get notRated => '평가 없음';
@override String get liked => '좋아요';
@override String get notLiked => '좋아요 아님';
@override String get saved => '저장됨';
@override String get notAvailable => '일치 항목 없음';
@override String get noConnectedTrackers => '설정에서 트래커를 연결하면 거기에도 평가할 수 있습니다.';
}
// Path: accessibility
class _TranslationsAccessibilityKo extends TranslationsAccessibilityEn {
_TranslationsAccessibilityKo._(TranslationsKo root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsKo {
'mediaMenu.rate' => '평가',
'mediaMenu.playFromBeginning' => '처음부터 재생',
'mediaMenu.playVersion' => '버전 재생...',
'rateSheet.title' => '평가',
'rateSheet.server' => '서버',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => '점수 설정',
'rateSheet.notRated' => '평가 없음',
'rateSheet.liked' => '좋아요',
'rateSheet.notLiked' => '좋아요 아님',
'rateSheet.saved' => '저장됨',
'rateSheet.notAvailable' => '일치 항목 없음',
'rateSheet.noConnectedTrackers' => '설정에서 트래커를 연결하면 거기에도 평가할 수 있습니다.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, 영화',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV 프로그램',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsKo {
'profiles.summaryMultiple' => ({required Object count}) => '${count}개 프로필',
'profiles.removeConnectionTitle' => '연결을 제거하시겠습니까?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => '${displayName}${connectionLabel} 접근 권한을 제거합니다. 다른 프로필에는 유지됩니다.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => '프로필을 삭제하시겠습니까?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => '${displayName} 및 해당 연결을 제거합니다. 서버는 계속 사용할 수 있습니다.',
'profiles.profileNameLabel' => '프로필 이름',
@@ -2284,8 +2318,6 @@ extension on TranslationsKo {
'profiles.confirmPinTitle' => 'PIN 확인',
'profiles.pinSet' => 'PIN 설정됨',
'profiles.changePin' => '변경',
_ => null,
} ?? switch (path) {
'profiles.removePin' => '제거',
'profiles.connectionsLabel' => '연결',
'profiles.add' => '추가',
@@ -2787,6 +2819,8 @@ extension on TranslationsKo {
'metadataEdit.selectSquareArt' => '정사각형 아트 선택',
'metadataEdit.fromUrl' => 'URL에서',
'metadataEdit.uploadFile' => '파일 업로드',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => '이미지 URL 입력',
'metadataEdit.imageUrl' => '이미지 URL',
'metadataEdit.metadataUpdated' => '메타데이터가 업데이트되었습니다',
@@ -2798,8 +2832,6 @@ extension on TranslationsKo {
'metadataEdit.libraryDefault' => '라이브러리 기본값',
'metadataEdit.accountDefault' => '계정 기본값',
'metadataEdit.seriesDefault' => '시리즈 기본값',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => '에피소드 정렬',
'metadataEdit.oldestFirst' => '오래된 순',
'metadataEdit.newestFirst' => '최신 순',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsNb extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysNb hotkeys = _TranslationsHotkeysNb._(_root);
@override late final _TranslationsFileInfoNb fileInfo = _TranslationsFileInfoNb._(_root);
@override late final _TranslationsMediaMenuNb mediaMenu = _TranslationsMediaMenuNb._(_root);
@override late final _TranslationsRateSheetNb rateSheet = _TranslationsRateSheetNb._(_root);
@override late final _TranslationsAccessibilityNb accessibility = _TranslationsAccessibilityNb._(_root);
@override late final _TranslationsTooltipsNb tooltips = _TranslationsTooltipsNb._(_root);
@override late final _TranslationsVideoControlsNb videoControls = _TranslationsVideoControlsNb._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuNb extends TranslationsMediaMenuEn {
@override String get playVersion => 'Spill av versjon...';
}
// Path: rateSheet
class _TranslationsRateSheetNb extends TranslationsRateSheetEn {
_TranslationsRateSheetNb._(TranslationsNb root) : this._root = root, super.internal(root);
final TranslationsNb _root; // ignore: unused_field
// Translations
@override String get title => 'Vurder';
@override String get server => 'Server';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Sett en poengsum';
@override String get notRated => 'Ikke vurdert';
@override String get liked => 'Likt';
@override String get notLiked => 'Ikke likt';
@override String get saved => 'Lagret';
@override String get notAvailable => 'Ingen treff';
@override String get noConnectedTrackers => 'Koble til en sporer i Innstillinger for å vurdere der.';
}
// Path: accessibility
class _TranslationsAccessibilityNb extends TranslationsAccessibilityEn {
_TranslationsAccessibilityNb._(TranslationsNb root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsNb {
'mediaMenu.rate' => 'Vurder',
'mediaMenu.playFromBeginning' => 'Spill fra begynnelsen',
'mediaMenu.playVersion' => 'Spill av versjon...',
'rateSheet.title' => 'Vurder',
'rateSheet.server' => 'Server',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Sett en poengsum',
'rateSheet.notRated' => 'Ikke vurdert',
'rateSheet.liked' => 'Likt',
'rateSheet.notLiked' => 'Ikke likt',
'rateSheet.saved' => 'Lagret',
'rateSheet.notAvailable' => 'Ingen treff',
'rateSheet.noConnectedTrackers' => 'Koble til en sporer i Innstillinger for å vurdere der.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV-serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsNb {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profiler',
'profiles.removeConnectionTitle' => 'Fjerne tilkobling?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Fjern ${displayName}s tilgang til ${connectionLabel}. Andre profiler beholder den.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Slette profil?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Fjern ${displayName} og tilkoblingene. Servere forblir tilgjengelige.',
'profiles.profileNameLabel' => 'Profilnavn',
@@ -2284,8 +2318,6 @@ extension on TranslationsNb {
'profiles.confirmPinTitle' => 'Bekreft PIN',
'profiles.pinSet' => 'PIN satt',
'profiles.changePin' => 'Endre',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Fjern',
'profiles.connectionsLabel' => 'Tilkoblinger',
'profiles.add' => 'Legg til',
@@ -2787,6 +2819,8 @@ extension on TranslationsNb {
'metadataEdit.selectSquareArt' => 'Velg kvadratisk bilde',
'metadataEdit.fromUrl' => 'Fra URL',
'metadataEdit.uploadFile' => 'Last opp fil',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Skriv inn bilde-URL',
'metadataEdit.imageUrl' => 'Bilde-URL',
'metadataEdit.metadataUpdated' => 'Metadata oppdatert',
@@ -2798,8 +2832,6 @@ extension on TranslationsNb {
'metadataEdit.libraryDefault' => 'Bibliotekstandard',
'metadataEdit.accountDefault' => 'Kontostandard',
'metadataEdit.seriesDefault' => 'Seriestandard',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Episodesortering',
'metadataEdit.oldestFirst' => 'Eldste først',
'metadataEdit.newestFirst' => 'Nyeste først',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsNl extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysNl hotkeys = _TranslationsHotkeysNl._(_root);
@override late final _TranslationsFileInfoNl fileInfo = _TranslationsFileInfoNl._(_root);
@override late final _TranslationsMediaMenuNl mediaMenu = _TranslationsMediaMenuNl._(_root);
@override late final _TranslationsRateSheetNl rateSheet = _TranslationsRateSheetNl._(_root);
@override late final _TranslationsAccessibilityNl accessibility = _TranslationsAccessibilityNl._(_root);
@override late final _TranslationsTooltipsNl tooltips = _TranslationsTooltipsNl._(_root);
@override late final _TranslationsVideoControlsNl videoControls = _TranslationsVideoControlsNl._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuNl extends TranslationsMediaMenuEn {
@override String get playVersion => 'Versie afspelen...';
}
// Path: rateSheet
class _TranslationsRateSheetNl extends TranslationsRateSheetEn {
_TranslationsRateSheetNl._(TranslationsNl root) : this._root = root, super.internal(root);
final TranslationsNl _root; // ignore: unused_field
// Translations
@override String get title => 'Beoordelen';
@override String get server => 'Server';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Score instellen';
@override String get notRated => 'Niet beoordeeld';
@override String get liked => 'Geliket';
@override String get notLiked => 'Niet geliket';
@override String get saved => 'Opgeslagen';
@override String get notAvailable => 'Geen match gevonden';
@override String get noConnectedTrackers => 'Verbind een tracker in Instellingen om daar te beoordelen.';
}
// Path: accessibility
class _TranslationsAccessibilityNl extends TranslationsAccessibilityEn {
_TranslationsAccessibilityNl._(TranslationsNl root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsNl {
'mediaMenu.rate' => 'Beoordelen',
'mediaMenu.playFromBeginning' => 'Afspelen vanaf het begin',
'mediaMenu.playVersion' => 'Versie afspelen...',
'rateSheet.title' => 'Beoordelen',
'rateSheet.server' => 'Server',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Score instellen',
'rateSheet.notRated' => 'Niet beoordeeld',
'rateSheet.liked' => 'Geliket',
'rateSheet.notLiked' => 'Niet geliket',
'rateSheet.saved' => 'Opgeslagen',
'rateSheet.notAvailable' => 'Geen match gevonden',
'rateSheet.noConnectedTrackers' => 'Verbind een tracker in Instellingen om daar te beoordelen.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV-serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsNl {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profielen',
'profiles.removeConnectionTitle' => 'Verbinding verwijderen?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Verwijder ${displayName}s toegang tot ${connectionLabel}. Andere profielen behouden die.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Profiel verwijderen?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Verwijder ${displayName} en de verbindingen. Servers blijven beschikbaar.',
'profiles.profileNameLabel' => 'Profielnaam',
@@ -2284,8 +2318,6 @@ extension on TranslationsNl {
'profiles.confirmPinTitle' => 'PIN bevestigen',
'profiles.pinSet' => 'PIN ingesteld',
'profiles.changePin' => 'Wijzigen',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Verwijderen',
'profiles.connectionsLabel' => 'Verbindingen',
'profiles.add' => 'Toevoegen',
@@ -2787,6 +2819,8 @@ extension on TranslationsNl {
'metadataEdit.selectSquareArt' => 'Vierkante afbeelding selecteren',
'metadataEdit.fromUrl' => 'Vanaf URL',
'metadataEdit.uploadFile' => 'Bestand uploaden',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Voer afbeeldings-URL in',
'metadataEdit.imageUrl' => 'Afbeeldings-URL',
'metadataEdit.metadataUpdated' => 'Metadata bijgewerkt',
@@ -2798,8 +2832,6 @@ extension on TranslationsNl {
'metadataEdit.libraryDefault' => 'Bibliotheekstandaard',
'metadataEdit.accountDefault' => 'Accountstandaard',
'metadataEdit.seriesDefault' => 'Seriestandaard',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Afleveringen sorteren',
'metadataEdit.oldestFirst' => 'Oudste eerst',
'metadataEdit.newestFirst' => 'Nieuwste eerst',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsPl extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysPl hotkeys = _TranslationsHotkeysPl._(_root);
@override late final _TranslationsFileInfoPl fileInfo = _TranslationsFileInfoPl._(_root);
@override late final _TranslationsMediaMenuPl mediaMenu = _TranslationsMediaMenuPl._(_root);
@override late final _TranslationsRateSheetPl rateSheet = _TranslationsRateSheetPl._(_root);
@override late final _TranslationsAccessibilityPl accessibility = _TranslationsAccessibilityPl._(_root);
@override late final _TranslationsTooltipsPl tooltips = _TranslationsTooltipsPl._(_root);
@override late final _TranslationsVideoControlsPl videoControls = _TranslationsVideoControlsPl._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuPl extends TranslationsMediaMenuEn {
@override String get playVersion => 'Odtwórz wersję...';
}
// Path: rateSheet
class _TranslationsRateSheetPl extends TranslationsRateSheetEn {
_TranslationsRateSheetPl._(TranslationsPl root) : this._root = root, super.internal(root);
final TranslationsPl _root; // ignore: unused_field
// Translations
@override String get title => 'Oceń';
@override String get server => 'Serwer';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Ustaw ocenę';
@override String get notRated => 'Nie oceniono';
@override String get liked => 'Polubione';
@override String get notLiked => 'Nie polubiono';
@override String get saved => 'Zapisano';
@override String get notAvailable => 'Nie znaleziono dopasowania';
@override String get noConnectedTrackers => 'Połącz tracker w Ustawieniach, aby tam oceniać.';
}
// Path: accessibility
class _TranslationsAccessibilityPl extends TranslationsAccessibilityEn {
_TranslationsAccessibilityPl._(TranslationsPl root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsPl {
'mediaMenu.rate' => 'Oceń',
'mediaMenu.playFromBeginning' => 'Odtwórz od początku',
'mediaMenu.playVersion' => 'Odtwórz wersję...',
'rateSheet.title' => 'Oceń',
'rateSheet.server' => 'Serwer',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Ustaw ocenę',
'rateSheet.notRated' => 'Nie oceniono',
'rateSheet.liked' => 'Polubione',
'rateSheet.notLiked' => 'Nie polubiono',
'rateSheet.saved' => 'Zapisano',
'rateSheet.notAvailable' => 'Nie znaleziono dopasowania',
'rateSheet.noConnectedTrackers' => 'Połącz tracker w Ustawieniach, aby tam oceniać.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, serial TV',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsPl {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profili',
'profiles.removeConnectionTitle' => 'Usunąć połączenie?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Usuń dostęp ${displayName} do ${connectionLabel}. Inne profile go zachowają.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Usunąć profil?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Usuń ${displayName} i jego połączenia. Serwery pozostaną dostępne.',
'profiles.profileNameLabel' => 'Nazwa profilu',
@@ -2284,8 +2318,6 @@ extension on TranslationsPl {
'profiles.confirmPinTitle' => 'Potwierdź PIN',
'profiles.pinSet' => 'PIN ustawiony',
'profiles.changePin' => 'Zmień',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Usuń',
'profiles.connectionsLabel' => 'Połączenia',
'profiles.add' => 'Dodaj',
@@ -2787,6 +2819,8 @@ extension on TranslationsPl {
'metadataEdit.selectSquareArt' => 'Wybierz kwadratowy obraz',
'metadataEdit.fromUrl' => 'Z URL',
'metadataEdit.uploadFile' => 'Prześlij plik',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Wprowadź URL obrazu',
'metadataEdit.imageUrl' => 'URL obrazu',
'metadataEdit.metadataUpdated' => 'Metadane zaktualizowane',
@@ -2798,8 +2832,6 @@ extension on TranslationsPl {
'metadataEdit.libraryDefault' => 'Domyślne biblioteki',
'metadataEdit.accountDefault' => 'Domyślne konta',
'metadataEdit.seriesDefault' => 'Domyślne serialu',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Sortowanie odcinków',
'metadataEdit.oldestFirst' => 'Najstarsze najpierw',
'metadataEdit.newestFirst' => 'Najnowsze najpierw',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsPt extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysPt hotkeys = _TranslationsHotkeysPt._(_root);
@override late final _TranslationsFileInfoPt fileInfo = _TranslationsFileInfoPt._(_root);
@override late final _TranslationsMediaMenuPt mediaMenu = _TranslationsMediaMenuPt._(_root);
@override late final _TranslationsRateSheetPt rateSheet = _TranslationsRateSheetPt._(_root);
@override late final _TranslationsAccessibilityPt accessibility = _TranslationsAccessibilityPt._(_root);
@override late final _TranslationsTooltipsPt tooltips = _TranslationsTooltipsPt._(_root);
@override late final _TranslationsVideoControlsPt videoControls = _TranslationsVideoControlsPt._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuPt extends TranslationsMediaMenuEn {
@override String get playVersion => 'Reproduzir versão...';
}
// Path: rateSheet
class _TranslationsRateSheetPt extends TranslationsRateSheetEn {
_TranslationsRateSheetPt._(TranslationsPt root) : this._root = root, super.internal(root);
final TranslationsPt _root; // ignore: unused_field
// Translations
@override String get title => 'Avaliar';
@override String get server => 'Servidor';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Definir pontuação';
@override String get notRated => 'Sem avaliação';
@override String get liked => 'Gostei';
@override String get notLiked => 'Não marcado como gostei';
@override String get saved => 'Salvo';
@override String get notAvailable => 'Nenhuma correspondência encontrada';
@override String get noConnectedTrackers => 'Conecte um rastreador em Configurações para avaliar lá.';
}
// Path: accessibility
class _TranslationsAccessibilityPt extends TranslationsAccessibilityEn {
_TranslationsAccessibilityPt._(TranslationsPt root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsPt {
'mediaMenu.rate' => 'Avaliar',
'mediaMenu.playFromBeginning' => 'Reproduzir do início',
'mediaMenu.playVersion' => 'Reproduzir versão...',
'rateSheet.title' => 'Avaliar',
'rateSheet.server' => 'Servidor',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Definir pontuação',
'rateSheet.notRated' => 'Sem avaliação',
'rateSheet.liked' => 'Gostei',
'rateSheet.notLiked' => 'Não marcado como gostei',
'rateSheet.saved' => 'Salvo',
'rateSheet.notAvailable' => 'Nenhuma correspondência encontrada',
'rateSheet.noConnectedTrackers' => 'Conecte um rastreador em Configurações para avaliar lá.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, filme',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, série de TV',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsPt {
'profiles.summaryMultiple' => ({required Object count}) => '${count} perfis',
'profiles.removeConnectionTitle' => 'Remover conexão?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Remover acesso de ${displayName} a ${connectionLabel}. Outros perfis mantêm o acesso.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Excluir perfil?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Remover ${displayName} e suas conexões. Servidores continuam disponíveis.',
'profiles.profileNameLabel' => 'Nome do perfil',
@@ -2284,8 +2318,6 @@ extension on TranslationsPt {
'profiles.confirmPinTitle' => 'Confirmar PIN',
'profiles.pinSet' => 'PIN definido',
'profiles.changePin' => 'Alterar',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Remover',
'profiles.connectionsLabel' => 'Conexões',
'profiles.add' => 'Adicionar',
@@ -2787,6 +2819,8 @@ extension on TranslationsPt {
'metadataEdit.selectSquareArt' => 'Selecionar Imagem Quadrada',
'metadataEdit.fromUrl' => 'Da URL',
'metadataEdit.uploadFile' => 'Enviar Arquivo',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Insira a URL da imagem',
'metadataEdit.imageUrl' => 'URL da Imagem',
'metadataEdit.metadataUpdated' => 'Metadados atualizados',
@@ -2798,8 +2832,6 @@ extension on TranslationsPt {
'metadataEdit.libraryDefault' => 'Padrão da biblioteca',
'metadataEdit.accountDefault' => 'Padrão da conta',
'metadataEdit.seriesDefault' => 'Padrão da série',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Ordenação de Episódios',
'metadataEdit.oldestFirst' => 'Mais antigos primeiro',
'metadataEdit.newestFirst' => 'Mais recentes primeiro',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsRu extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysRu hotkeys = _TranslationsHotkeysRu._(_root);
@override late final _TranslationsFileInfoRu fileInfo = _TranslationsFileInfoRu._(_root);
@override late final _TranslationsMediaMenuRu mediaMenu = _TranslationsMediaMenuRu._(_root);
@override late final _TranslationsRateSheetRu rateSheet = _TranslationsRateSheetRu._(_root);
@override late final _TranslationsAccessibilityRu accessibility = _TranslationsAccessibilityRu._(_root);
@override late final _TranslationsTooltipsRu tooltips = _TranslationsTooltipsRu._(_root);
@override late final _TranslationsVideoControlsRu videoControls = _TranslationsVideoControlsRu._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuRu extends TranslationsMediaMenuEn {
@override String get playVersion => 'Воспроизвести версию...';
}
// Path: rateSheet
class _TranslationsRateSheetRu extends TranslationsRateSheetEn {
_TranslationsRateSheetRu._(TranslationsRu root) : this._root = root, super.internal(root);
final TranslationsRu _root; // ignore: unused_field
// Translations
@override String get title => 'Оценить';
@override String get server => 'Сервер';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Установить оценку';
@override String get notRated => 'Без оценки';
@override String get liked => 'Понравилось';
@override String get notLiked => 'Не отмечено понравившимся';
@override String get saved => 'Сохранено';
@override String get notAvailable => 'Совпадений не найдено';
@override String get noConnectedTrackers => 'Подключите трекер в настройках, чтобы оценивать там.';
}
// Path: accessibility
class _TranslationsAccessibilityRu extends TranslationsAccessibilityEn {
_TranslationsAccessibilityRu._(TranslationsRu root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsRu {
'mediaMenu.rate' => 'Оценить',
'mediaMenu.playFromBeginning' => 'Воспроизвести сначала',
'mediaMenu.playVersion' => 'Воспроизвести версию...',
'rateSheet.title' => 'Оценить',
'rateSheet.server' => 'Сервер',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Установить оценку',
'rateSheet.notRated' => 'Без оценки',
'rateSheet.liked' => 'Понравилось',
'rateSheet.notLiked' => 'Не отмечено понравившимся',
'rateSheet.saved' => 'Сохранено',
'rateSheet.notAvailable' => 'Совпадений не найдено',
'rateSheet.noConnectedTrackers' => 'Подключите трекер в настройках, чтобы оценивать там.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, фильм',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, сериал',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsRu {
'profiles.summaryMultiple' => ({required Object count}) => '${count} профилей',
'profiles.removeConnectionTitle' => 'Удалить соединение?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Удалить доступ ${displayName} к ${connectionLabel}. У других профилей он останется.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Удалить профиль?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Удалить ${displayName} и его подключения. Серверы останутся доступны.',
'profiles.profileNameLabel' => 'Имя профиля',
@@ -2284,8 +2318,6 @@ extension on TranslationsRu {
'profiles.confirmPinTitle' => 'Подтвердить PIN',
'profiles.pinSet' => 'PIN установлен',
'profiles.changePin' => 'Изменить',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Удалить',
'profiles.connectionsLabel' => 'Соединения',
'profiles.add' => 'Добавить',
@@ -2787,6 +2819,8 @@ extension on TranslationsRu {
'metadataEdit.selectSquareArt' => 'Выбрать квадратное изображение',
'metadataEdit.fromUrl' => 'По URL',
'metadataEdit.uploadFile' => 'Загрузить файл',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Введите URL изображения',
'metadataEdit.imageUrl' => 'URL изображения',
'metadataEdit.metadataUpdated' => 'Метаданные обновлены',
@@ -2798,8 +2832,6 @@ extension on TranslationsRu {
'metadataEdit.libraryDefault' => 'По умолчанию библиотеки',
'metadataEdit.accountDefault' => 'По умолчанию аккаунта',
'metadataEdit.seriesDefault' => 'По умолчанию сериала',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Сортировка эпизодов',
'metadataEdit.oldestFirst' => 'Сначала старые',
'metadataEdit.newestFirst' => 'Сначала новые',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsSv extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysSv hotkeys = _TranslationsHotkeysSv._(_root);
@override late final _TranslationsFileInfoSv fileInfo = _TranslationsFileInfoSv._(_root);
@override late final _TranslationsMediaMenuSv mediaMenu = _TranslationsMediaMenuSv._(_root);
@override late final _TranslationsRateSheetSv rateSheet = _TranslationsRateSheetSv._(_root);
@override late final _TranslationsAccessibilitySv accessibility = _TranslationsAccessibilitySv._(_root);
@override late final _TranslationsTooltipsSv tooltips = _TranslationsTooltipsSv._(_root);
@override late final _TranslationsVideoControlsSv videoControls = _TranslationsVideoControlsSv._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuSv extends TranslationsMediaMenuEn {
@override String get playVersion => 'Spela version...';
}
// Path: rateSheet
class _TranslationsRateSheetSv extends TranslationsRateSheetEn {
_TranslationsRateSheetSv._(TranslationsSv root) : this._root = root, super.internal(root);
final TranslationsSv _root; // ignore: unused_field
// Translations
@override String get title => 'Betygsätt';
@override String get server => 'Server';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => 'Ange betyg';
@override String get notRated => 'Inte betygsatt';
@override String get liked => 'Gillad';
@override String get notLiked => 'Inte gillad';
@override String get saved => 'Sparat';
@override String get notAvailable => 'Ingen matchning hittades';
@override String get noConnectedTrackers => 'Anslut en spårare i Inställningar för att betygsätta där.';
}
// Path: accessibility
class _TranslationsAccessibilitySv extends TranslationsAccessibilityEn {
_TranslationsAccessibilitySv._(TranslationsSv root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsSv {
'mediaMenu.rate' => 'Betygsätt',
'mediaMenu.playFromBeginning' => 'Spela från början',
'mediaMenu.playVersion' => 'Spela version...',
'rateSheet.title' => 'Betygsätt',
'rateSheet.server' => 'Server',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => 'Ange betyg',
'rateSheet.notRated' => 'Inte betygsatt',
'rateSheet.liked' => 'Gillad',
'rateSheet.notLiked' => 'Inte gillad',
'rateSheet.saved' => 'Sparat',
'rateSheet.notAvailable' => 'Ingen matchning hittades',
'rateSheet.noConnectedTrackers' => 'Anslut en spårare i Inställningar för att betygsätta där.',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, film',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, TV-serie',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsSv {
'profiles.summaryMultiple' => ({required Object count}) => '${count} profiler',
'profiles.removeConnectionTitle' => 'Ta bort anslutning?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => 'Ta bort ${displayName}s åtkomst till ${connectionLabel}. Andra profiler behåller den.',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => 'Ta bort profil?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => 'Ta bort ${displayName} och dess anslutningar. Servrar förblir tillgängliga.',
'profiles.profileNameLabel' => 'Profilnamn',
@@ -2284,8 +2318,6 @@ extension on TranslationsSv {
'profiles.confirmPinTitle' => 'Bekräfta PIN',
'profiles.pinSet' => 'PIN angiven',
'profiles.changePin' => 'Ändra',
_ => null,
} ?? switch (path) {
'profiles.removePin' => 'Ta bort',
'profiles.connectionsLabel' => 'Anslutningar',
'profiles.add' => 'Lägg till',
@@ -2787,6 +2819,8 @@ extension on TranslationsSv {
'metadataEdit.selectSquareArt' => 'Välj kvadratisk bild',
'metadataEdit.fromUrl' => 'Från URL',
'metadataEdit.uploadFile' => 'Ladda upp fil',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => 'Ange bild-URL',
'metadataEdit.imageUrl' => 'Bild-URL',
'metadataEdit.metadataUpdated' => 'Metadata uppdaterad',
@@ -2798,8 +2832,6 @@ extension on TranslationsSv {
'metadataEdit.libraryDefault' => 'Biblioteksstandard',
'metadataEdit.accountDefault' => 'Kontostandard',
'metadataEdit.seriesDefault' => 'Seriestandard',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => 'Avsnittsortering',
'metadataEdit.oldestFirst' => 'Äldst först',
'metadataEdit.newestFirst' => 'Nyast först',
+36 -4
View File
@@ -49,6 +49,7 @@ class TranslationsZh extends Translations with BaseTranslations<AppLocale, Trans
@override late final _TranslationsHotkeysZh hotkeys = _TranslationsHotkeysZh._(_root);
@override late final _TranslationsFileInfoZh fileInfo = _TranslationsFileInfoZh._(_root);
@override late final _TranslationsMediaMenuZh mediaMenu = _TranslationsMediaMenuZh._(_root);
@override late final _TranslationsRateSheetZh rateSheet = _TranslationsRateSheetZh._(_root);
@override late final _TranslationsAccessibilityZh accessibility = _TranslationsAccessibilityZh._(_root);
@override late final _TranslationsTooltipsZh tooltips = _TranslationsTooltipsZh._(_root);
@override late final _TranslationsVideoControlsZh videoControls = _TranslationsVideoControlsZh._(_root);
@@ -481,6 +482,26 @@ class _TranslationsMediaMenuZh extends TranslationsMediaMenuEn {
@override String get playVersion => '播放版本...';
}
// Path: rateSheet
class _TranslationsRateSheetZh extends TranslationsRateSheetEn {
_TranslationsRateSheetZh._(TranslationsZh root) : this._root = root, super.internal(root);
final TranslationsZh _root; // ignore: unused_field
// Translations
@override String get title => '评分';
@override String get server => '服务器';
@override String starValue({required Object rating}) => '${rating} / 5';
@override String scoreValue({required Object score}) => '${score} / 10';
@override String get setScore => '设置分数';
@override String get notRated => '未评分';
@override String get liked => '已喜欢';
@override String get notLiked => '未喜欢';
@override String get saved => '已保存';
@override String get notAvailable => '未找到匹配项';
@override String get noConnectedTrackers => '在设置中连接跟踪器即可在那里评分。';
}
// Path: accessibility
class _TranslationsAccessibilityZh extends TranslationsAccessibilityEn {
_TranslationsAccessibilityZh._(TranslationsZh root) : this._root = root, super.internal(root);
@@ -2098,6 +2119,17 @@ extension on TranslationsZh {
'mediaMenu.rate' => '评分',
'mediaMenu.playFromBeginning' => '从头播放',
'mediaMenu.playVersion' => '播放版本...',
'rateSheet.title' => '评分',
'rateSheet.server' => '服务器',
'rateSheet.starValue' => ({required Object rating}) => '${rating} / 5',
'rateSheet.scoreValue' => ({required Object score}) => '${score} / 10',
'rateSheet.setScore' => '设置分数',
'rateSheet.notRated' => '未评分',
'rateSheet.liked' => '已喜欢',
'rateSheet.notLiked' => '未喜欢',
'rateSheet.saved' => '已保存',
'rateSheet.notAvailable' => '未找到匹配项',
'rateSheet.noConnectedTrackers' => '在设置中连接跟踪器即可在那里评分。',
'accessibility.mediaCardMovie' => ({required Object title}) => '${title}, 电影',
'accessibility.mediaCardShow' => ({required Object title}) => '${title}, 电视剧',
'accessibility.mediaCardEpisode' => ({required Object title, required Object episodeInfo}) => '${title}, ${episodeInfo}',
@@ -2273,6 +2305,8 @@ extension on TranslationsZh {
'profiles.summaryMultiple' => ({required Object count}) => '${count} 个配置文件',
'profiles.removeConnectionTitle' => '移除连接?',
'profiles.removeConnectionMessage' => ({required Object displayName, required Object connectionLabel}) => '移除 ${displayName}${connectionLabel} 的访问。其他个人资料仍保留。',
_ => null,
} ?? switch (path) {
'profiles.deleteProfileTitle' => '删除配置文件?',
'profiles.deleteProfileMessage' => ({required Object displayName}) => '移除 ${displayName} 及其连接。服务器仍可用。',
'profiles.profileNameLabel' => '配置文件名称',
@@ -2284,8 +2318,6 @@ extension on TranslationsZh {
'profiles.confirmPinTitle' => '确认 PIN',
'profiles.pinSet' => '已设置 PIN',
'profiles.changePin' => '更改',
_ => null,
} ?? switch (path) {
'profiles.removePin' => '移除',
'profiles.connectionsLabel' => '连接',
'profiles.add' => '添加',
@@ -2787,6 +2819,8 @@ extension on TranslationsZh {
'metadataEdit.selectSquareArt' => '选择方形图片',
'metadataEdit.fromUrl' => '从 URL',
'metadataEdit.uploadFile' => '上传文件',
_ => null,
} ?? switch (path) {
'metadataEdit.enterImageUrl' => '输入图片 URL',
'metadataEdit.imageUrl' => '图片 URL',
'metadataEdit.metadataUpdated' => '元数据已更新',
@@ -2798,8 +2832,6 @@ extension on TranslationsZh {
'metadataEdit.libraryDefault' => '媒体库默认',
'metadataEdit.accountDefault' => '账户默认',
'metadataEdit.seriesDefault' => '剧集默认',
_ => null,
} ?? switch (path) {
'metadataEdit.episodeSorting' => '剧集排序',
'metadataEdit.oldestFirst' => '最旧优先',
'metadataEdit.newestFirst' => '最新优先',
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "Spela från början",
"playVersion": "Spela version..."
},
"rateSheet": {
"title": "Betygsätt",
"server": "Server",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "Ange betyg",
"notRated": "Inte betygsatt",
"liked": "Gillad",
"notLiked": "Inte gillad",
"saved": "Sparat",
"notAvailable": "Ingen matchning hittades",
"noConnectedTrackers": "Anslut en spårare i Inställningar för att betygsätta där."
},
"accessibility": {
"mediaCardMovie": "${title}, film",
"mediaCardShow": "${title}, TV-serie",
+13
View File
@@ -347,6 +347,19 @@
"playFromBeginning": "从头播放",
"playVersion": "播放版本..."
},
"rateSheet": {
"title": "评分",
"server": "服务器",
"starValue": "${rating} / 5",
"scoreValue": "${score} / 10",
"setScore": "设置分数",
"notRated": "未评分",
"liked": "已喜欢",
"notLiked": "未喜欢",
"saved": "已保存",
"notAvailable": "未找到匹配项",
"noConnectedTrackers": "在设置中连接跟踪器即可在那里评分。"
},
"accessibility": {
"mediaCardMovie": "${title}, 电影",
"mediaCardShow": "${title}, 电视剧",
+8 -52
View File
@@ -20,7 +20,6 @@ import '../focus/focusable_wrapper.dart';
import '../focus/key_event_utils.dart';
import '../focus/input_mode_tracker.dart';
import '../widgets/focus_builders.dart';
import '../exceptions/media_server_exceptions.dart';
import '../media/media_backend.dart';
import '../media/media_hub.dart';
import '../utils/provider_extensions.dart';
@@ -774,9 +773,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
return ListenableBuilder(
listenable: _ratingChipFocusNode,
builder: (context, _) {
final activate = isNumeric
? () => _showRatingDialog(context, metadata, starValue)
: () => _toggleLike(metadata);
final activate = () => _showRatingDialog(context, metadata);
final colorScheme = Theme.of(context).colorScheme;
final isKeyboardMode = InputModeTracker.isKeyboardMode(context);
final showFocus = _ratingChipFocusNode.hasFocus && isKeyboardMode;
@@ -831,57 +828,16 @@ class _MediaDetailScreenState extends State<MediaDetailScreen>
);
}
/// Like/unlike toggle for backends that only support binary ratings
/// (Jellyfin). Maps to [MediaServerClient.rate] with 10 (like) or -1
/// (clear) — the Jellyfin client routes those through POST/DELETE on
/// `/UserItems/{id}/Rating`.
Future<void> _toggleLike(MediaItem metadata) async {
final client = _getMediaClientForMetadata(context);
if (client == null) return;
final wasLiked = metadata.userRating != null && metadata.userRating! >= 6;
final newRating = wasLiked ? -1.0 : 10.0;
try {
await client.rate(metadata, newRating);
setStateIfMounted(() {
_fullMetadata = _fullMetadata?.copyWith(userRating: wasLiked ? 0 : 10);
});
} on MediaServerHttpException catch (e) {
appLogger.w('Failed to toggle rating', error: e);
if (mounted) showErrorSnackBar(context, t.errors.failedToRate);
}
}
void _showRatingDialog(BuildContext sheetContext, MediaItem metadata, double currentStarValue) {
void _showRatingDialog(BuildContext sheetContext, MediaItem metadata) {
OverlaySheetController.showAdaptive(
sheetContext,
builder: (context) => RatingBottomSheet(
currentRating: currentStarValue,
onRate: (stars) async {
final client = _getMediaClientForMetadata(this.context);
if (client == null) return;
final plexRating = stars * 2.0; // Convert 0-5 stars to 0-10 scale
try {
await client.rate(metadata, plexRating);
setStateIfMounted(() {
_fullMetadata = _fullMetadata?.copyWith(userRating: plexRating);
});
} on MediaServerHttpException catch (e) {
appLogger.w('Failed to set rating', error: e);
if (mounted) showErrorSnackBar(this.context, t.errors.failedToRate);
}
},
onClear: () async {
final client = _getMediaClientForMetadata(this.context);
if (client == null) return;
try {
await client.rate(metadata, -1);
setStateIfMounted(() {
_fullMetadata = _fullMetadata?.copyWith(userRating: 0);
});
} on MediaServerHttpException catch (e) {
appLogger.w('Failed to clear rating', error: e);
if (mounted) showErrorSnackBar(this.context, t.errors.failedToRate);
}
item: metadata,
serverClient: _getMediaClientForMetadata(this.context),
onServerRatingChanged: (rating) {
setStateIfMounted(() {
_fullMetadata = (_fullMetadata ?? widget.metadata).copyWith(userRating: rating);
});
},
),
);
@@ -50,6 +50,37 @@ class AnilistClient {
await query(mutation, variables: {'mediaId': mediaId, 'progress': progress, 'status': status});
}
Future<void> setMediaListScore({required int mediaId, required int score}) async {
const mutation = '''
mutation(\$mediaId: Int, \$scoreRaw: Int) {
SaveMediaListEntry(mediaId: \$mediaId, scoreRaw: \$scoreRaw) {
id
}
}
''';
await query(mutation, variables: {'mediaId': mediaId, 'scoreRaw': score.clamp(0, 10).toInt() * 10});
}
Future<int?> getMediaListScore(int mediaId) async {
const mediaQuery = '''
query(\$mediaId: Int) {
Media(id: \$mediaId, type: ANIME) {
mediaListEntry {
scoreRaw: score(format: POINT_100)
}
}
}
''';
final data = await query(mediaQuery, variables: {'mediaId': mediaId});
final media = data['Media'];
if (media is! Map) return null;
final entry = media['mediaListEntry'];
if (entry is! Map) return null;
final scoreRaw = flexibleInt(entry['scoreRaw']);
if (scoreRaw == null || scoreRaw <= 0) return null;
return (scoreRaw / 10).round().clamp(1, 10).toInt();
}
Future<int?> getAnimeEpisodeCount(int mediaId) async {
const mediaQuery = '''
query(\$mediaId: Int) {
@@ -5,6 +5,7 @@ import '../../../utils/app_logger.dart';
import '../../settings_service.dart';
import '../tracker.dart';
import '../tracker_constants.dart';
import '../tracker_id_resolver.dart';
import 'anilist_client.dart';
import 'anilist_session.dart';
@@ -63,6 +64,32 @@ class AnilistTracker extends TrackerBase {
appLogger.d('AniList: saved entry (anilist=$anilistId, progress=$watched, status=$status)');
}
Future<void> rate(TrackerRatingContext ctx, int score) async {
final client = _client;
final anilistId = ctx.ids.anime?.anilist;
if (client == null || anilistId == null) throw const TrackerRatingUnavailableException('AniList');
final clamped = score.clamp(1, 10).toInt();
await client.setMediaListScore(mediaId: anilistId, score: clamped);
appLogger.d('AniList: updated score (anilist=$anilistId, score=$clamped)');
}
Future<void> clearRating(TrackerRatingContext ctx) async {
final client = _client;
final anilistId = ctx.ids.anime?.anilist;
if (client == null || anilistId == null) throw const TrackerRatingUnavailableException('AniList');
await client.setMediaListScore(mediaId: anilistId, score: 0);
appLogger.d('AniList: cleared score (anilist=$anilistId)');
}
Future<int?> getRating(TrackerRatingContext ctx) async {
final client = _client;
final anilistId = ctx.ids.anime?.anilist;
if (client == null || anilistId == null) throw const TrackerRatingUnavailableException('AniList');
return client.getMediaListScore(anilistId);
}
Future<int?> _episodeCount(AnilistClient client, int anilistId) {
final existing = _episodeCountLoads[anilistId];
if (existing != null) return existing;
+14
View File
@@ -60,6 +60,20 @@ class MalClient {
await _request('PUT', '/anime/$animeId/my_list_status', formBody: fields);
}
Future<int?> getMyListScore(int animeId) async {
try {
final res = await _request('GET', '/anime/$animeId?fields=my_list_status');
if (res is! Map) return null;
final myListStatus = res['my_list_status'];
if (myListStatus is! Map) return null;
final score = flexibleInt(myListStatus['score']);
return score != null && score > 0 ? score : null;
} on MalApiException catch (e) {
if (e.statusCode == 404) return null;
rethrow;
}
}
Future<int?> getAnimeEpisodeCount(int animeId) async {
final res = await _request('GET', '/anime/$animeId?fields=num_episodes');
if (res is! Map) return null;
@@ -5,6 +5,7 @@ import '../../../utils/app_logger.dart';
import '../../settings_service.dart';
import '../tracker.dart';
import '../tracker_constants.dart';
import '../tracker_id_resolver.dart';
import 'mal_client.dart';
import 'mal_session.dart';
@@ -80,6 +81,32 @@ class MalTracker extends TrackerBase {
appLogger.d('MAL: updated list status (mal=$malId, fields=$fields)');
}
Future<void> rate(TrackerRatingContext ctx, int score) async {
final client = _client;
final malId = ctx.ids.anime?.mal;
if (client == null || malId == null) throw const TrackerRatingUnavailableException('MAL');
final clamped = score.clamp(1, 10).toInt();
await client.updateMyListStatus(malId, {'score': '$clamped'});
appLogger.d('MAL: updated score (mal=$malId, score=$clamped)');
}
Future<void> clearRating(TrackerRatingContext ctx) async {
final client = _client;
final malId = ctx.ids.anime?.mal;
if (client == null || malId == null) throw const TrackerRatingUnavailableException('MAL');
await client.updateMyListStatus(malId, {'score': '0'});
appLogger.d('MAL: cleared score (mal=$malId)');
}
Future<int?> getRating(TrackerRatingContext ctx) async {
final client = _client;
final malId = ctx.ids.anime?.mal;
if (client == null || malId == null) throw const TrackerRatingUnavailableException('MAL');
return client.getMyListScore(malId);
}
Future<int?> _episodeCount(MalClient client, int malId) {
final existing = _episodeCountLoads[malId];
if (existing != null) return existing;
@@ -39,6 +39,17 @@ class SimklClient {
/// ```
Future<void> addToHistory(Map<String, dynamic> body) => _request('POST', '/sync/history', body: body);
Future<void> addRatings(Map<String, dynamic> body) => _request('POST', '/sync/ratings', body: body);
Future<void> removeRatings(Map<String, dynamic> body) => _request('POST', '/sync/ratings/remove', body: body);
Future<List<dynamic>> getRatings(String type) async {
final res = await _request('GET', '/sync/ratings/$type');
if (res is List) return res;
if (res is Map && res[type] is List) return res[type] as List<dynamic>;
return const [];
}
Future<dynamic> _request(String method, String path, {Map<String, dynamic>? body}) async {
final uri = Uri.parse('${SimklConstants.apiBase}$path');
final headers = SimklConstants.headers(accessToken: session.accessToken);
+89 -8
View File
@@ -1,8 +1,14 @@
import 'package:http/http.dart' as http;
import '../../../models/trackers/anime_ids.dart';
import '../../../models/trackers/tracker_context.dart';
import '../../../utils/app_logger.dart';
import '../../../utils/external_ids.dart';
import '../../../utils/json_utils.dart';
import '../../settings_service.dart';
import '../tracker.dart';
import '../tracker_constants.dart';
import '../tracker_id_resolver.dart';
import 'simkl_client.dart';
import 'simkl_session.dart';
@@ -35,9 +41,11 @@ class SimklTracker extends TrackerBase {
@override
bool readEnabledSetting(SettingsService settings) => settings.read(SettingsService.enableSimklScrobble);
void rebindSession(SimklSession? session, {required void Function() onSessionInvalidated}) {
void rebindSession(SimklSession? session, {required void Function() onSessionInvalidated, http.Client? httpClient}) {
_client?.dispose();
_client = session != null ? SimklClient(session, onSessionInvalidated: onSessionInvalidated) : null;
_client = session != null
? SimklClient(session, onSessionInvalidated: onSessionInvalidated, httpClient: httpClient)
: null;
}
@override
@@ -45,7 +53,7 @@ class SimklTracker extends TrackerBase {
final client = _client;
if (client == null) return;
final ids = _buildIds(ctx);
final ids = _buildIds(external: ctx.external, anime: ctx.anime);
if (ids.isEmpty) return;
final body = ctx.isMovie
@@ -74,17 +82,90 @@ class SimklTracker extends TrackerBase {
appLogger.d('Simkl: marked watched (ids=$ids, isMovie=${ctx.isMovie})');
}
Future<int?> getRating(TrackerRatingContext ctx) async {
final client = _client;
if (client == null) throw const TrackerRatingUnavailableException('Simkl');
final ids = _buildIds(external: ctx.ids.external, anime: ctx.ids.anime);
if (ids.isEmpty) throw const TrackerRatingUnavailableException('Simkl');
final types = ctx.isMovie ? const ['movies'] : const ['shows', 'anime'];
for (final type in types) {
final entries = await client.getRatings(type);
for (final entry in entries) {
if (entry is! Map) continue;
final map = entry.cast<String, dynamic>();
final media = map[ctx.isMovie ? 'movie' : 'show'];
final remoteIds = _nestedIds(media) ?? _nestedIds(map);
if (!_idsMatch(remoteIds, ids)) continue;
final rating = flexibleInt(map['user_rating']) ?? flexibleInt(map['rating']);
return rating != null && rating > 0 ? rating.clamp(1, 10).toInt() : null;
}
}
return null;
}
Future<void> rate(TrackerRatingContext ctx, int score) async {
final client = _client;
if (client == null) throw const TrackerRatingUnavailableException('Simkl');
final ids = _buildIds(external: ctx.ids.external, anime: ctx.ids.anime);
if (ids.isEmpty) throw const TrackerRatingUnavailableException('Simkl');
final clamped = score.clamp(1, 10).toInt();
await client.addRatings(_ratingBody(ctx, ids, rating: clamped));
appLogger.d('Simkl: updated score (ids=$ids, score=$clamped)');
}
Future<void> clearRating(TrackerRatingContext ctx) async {
final client = _client;
if (client == null) throw const TrackerRatingUnavailableException('Simkl');
final ids = _buildIds(external: ctx.ids.external, anime: ctx.ids.anime);
if (ids.isEmpty) throw const TrackerRatingUnavailableException('Simkl');
await client.removeRatings(_ratingBody(ctx, ids));
appLogger.d('Simkl: cleared score (ids=$ids)');
}
Map<String, dynamic> _ratingBody(TrackerRatingContext ctx, Map<String, Object> ids, {int? rating}) {
final item = {'ids': ids, if (rating != null) 'rating': rating};
return ctx.isMovie
? {
'movies': [item],
}
: {
'shows': [item],
};
}
Map<String, dynamic>? _nestedIds(Object? value) {
if (value is! Map) return null;
final ids = value['ids'];
return ids is Map ? ids.cast<String, dynamic>() : null;
}
bool _idsMatch(Map<String, dynamic>? remoteIds, Map<String, Object> localIds) {
if (remoteIds == null) return false;
for (final entry in localIds.entries) {
final remote = remoteIds[entry.key];
if (remote == null) continue;
if (entry.value is String && remote.toString() == entry.value) return true;
final remoteInt = flexibleInt(remote);
final localInt = flexibleInt(entry.value);
if (remoteInt != null && localInt != null && remoteInt == localInt) return true;
}
return false;
}
/// Prefer Fribb's simkl_id for precision; otherwise send whatever Plex
/// exposes. Simkl accepts tvdb/imdb/tmdb in both movie and show shapes.
Map<String, Object> _buildIds(TrackerContext ctx) {
Map<String, Object> _buildIds({required ExternalIds external, required AnimeIds? anime}) {
final ids = <String, Object>{};
final simklId = ctx.anime?.simkl;
final simklId = anime?.simkl;
if (simklId != null) ids['simkl'] = simklId;
final tvdb = ctx.external.tvdb;
final tvdb = external.tvdb;
if (tvdb != null) ids['tvdb'] = tvdb;
final tmdb = ctx.external.tmdb;
final tmdb = external.tmdb;
if (tmdb != null) ids['tmdb'] = tmdb;
final imdb = ctx.external.imdb;
final imdb = external.imdb;
if (imdb != null) ids['imdb'] = imdb;
return ids;
}
+9
View File
@@ -32,6 +32,15 @@ abstract class Tracker {
Future<void> markWatched(TrackerContext ctx);
}
class TrackerRatingUnavailableException implements Exception {
final String trackerName;
const TrackerRatingUnavailableException(this.trackerName);
@override
String toString() => 'TrackerRatingUnavailableException($trackerName)';
}
/// Shared enabled-state bookkeeping. Subclasses override [hasActiveClient],
/// [readEnabledSetting], and [markWatched].
abstract class TrackerBase implements Tracker {
@@ -1,4 +1,5 @@
import '../../media/media_item.dart';
import '../../media/media_kind.dart';
import '../../media/media_server_client.dart';
import '../../models/trackers/anime_ids.dart';
import '../../models/trackers/fribb_mapping_row.dart';
@@ -27,6 +28,22 @@ class TrackerIds {
}
}
/// Resolved IDs for manually rating a media item on external trackers.
///
/// For TV items, [ids.external] is the show-level ID set. Trakt can then rate
/// the show/season/episode through nested season/episode numbers, while MAL,
/// AniList, and Simkl rate the mapped anime/show entry itself.
class TrackerRatingContext {
final TrackerIds ids;
final MediaKind kind;
final int? season;
final int? episodeNumber;
const TrackerRatingContext({required this.ids, required this.kind, this.season, this.episodeNumber});
bool get isMovie => kind == MediaKind.movie;
}
/// Resolves item ids → tracker external IDs. Returns both backend-native
/// external IDs (used by Trakt and by Simkl for non-anime matches) and Fribb
/// anime IDs (used by MAL/AniList, and by Simkl for anime precision).
@@ -100,6 +117,46 @@ class TrackerIdResolver {
return ids.withAnimeProgress(progress);
}
/// Resolve IDs for manual tracker ratings. Ratings can be attached to a
/// movie, show, season, or episode from the detail screen/context menu.
Future<TrackerRatingContext?> resolveForRating(MediaItem item) async {
switch (item.kind) {
case MediaKind.movie:
final ids = await resolveForMovie(item.id);
return ids == null ? null : TrackerRatingContext(ids: ids, kind: MediaKind.movie);
case MediaKind.show:
final ids = await _resolveShow(item.id);
return ids == null ? null : TrackerRatingContext(ids: ids, kind: MediaKind.show);
case MediaKind.season:
final showId = item.parentId;
final season = item.index ?? item.parentIndex;
if (showId == null || showId.isEmpty || season == null) return null;
final ids = await _resolveShow(showId, season: season);
return ids == null ? null : TrackerRatingContext(ids: ids, kind: MediaKind.season, season: season);
case MediaKind.episode:
final season = item.parentIndex;
final number = item.index;
if (season == null || number == null) return null;
final ids = await resolveShowForEpisode(item);
return ids == null
? null
: TrackerRatingContext(ids: ids, kind: MediaKind.episode, season: season, episodeNumber: number);
default:
return null;
}
}
Future<TrackerIds?> _resolveShow(String showId, {int? season}) async {
if (showId.isEmpty) return null;
final cacheKey = season != null ? '$showId#s$season' : showId;
if (_cache.containsKey(cacheKey)) return _cache[cacheKey];
final external = await _fetchExternalIds(showId);
final ids = await _build(external, isEpisodeSeason: season, isMovie: false);
_cache[cacheKey] = ids;
return ids;
}
void clearCache() {
_cache.clear();
_animeProgress.clearCache();
+10
View File
@@ -59,6 +59,16 @@ class TraktClient {
Future<void> removeFromHistory(TraktScrobbleRequest item) =>
_request('POST', '/sync/history/remove', body: item.toHistoryRemoveBody());
Future<void> addRatings(Map<String, dynamic> body) =>
_request('POST', '/sync/ratings', body: body, allowStatuses: const {200, 201});
Future<void> removeRatings(Map<String, dynamic> body) => _request('POST', '/sync/ratings/remove', body: body);
Future<List<dynamic>> getRatings(String type) async {
final res = await _request('GET', '/sync/ratings/$type');
return res is List ? res : const [];
}
/// Refresh the access token. Coalesces concurrent calls so
/// duplicate POSTs don't race when multiple in-flight requests hit 401.
Future<TraktSession> refresh() => _refreshCoalescer.run(_doRefresh);
+134 -2
View File
@@ -1,12 +1,16 @@
import 'dart:async';
import 'package:http/http.dart' as http;
import '../../media/media_item.dart';
import '../../media/media_kind.dart';
import '../../media/media_server_client.dart';
import '../../models/trakt/trakt_ids.dart';
import '../../models/trakt/trakt_scrobble_request.dart';
import '../../utils/app_logger.dart';
import '../../utils/json_utils.dart';
import '../settings_service.dart';
import '../trackers/tracker.dart';
import '../trackers/tracker_constants.dart';
import '../trackers/tracker_id_resolver.dart';
import 'trakt_client.dart';
@@ -66,12 +70,46 @@ class TraktScrobbleService {
/// Switch to a different account. Cancels any in-flight scrobble for the
/// previous account so we don't send a stop event to the wrong user.
void rebindToProfile(TraktSession? session, {required void Function() onSessionInvalidated}) {
void rebindToProfile(
TraktSession? session, {
required void Function() onSessionInvalidated,
http.Client? httpClient,
}) {
_client?.dispose();
_client = session != null ? TraktClient(session, onSessionInvalidated: onSessionInvalidated) : null;
_client = session != null
? TraktClient(session, onSessionInvalidated: onSessionInvalidated, httpClient: httpClient)
: null;
cancelInFlight();
}
Future<int?> getRating(TrackerRatingContext ctx) async {
final client = _client;
if (client == null) throw const TrackerRatingUnavailableException('Trakt');
final localIds = TraktIds.fromExternal(ctx.ids.external).toJson();
if (localIds.isEmpty) throw const TrackerRatingUnavailableException('Trakt');
final entries = await client.getRatings(_ratingType(ctx));
for (final entry in entries) {
if (entry is! Map) continue;
if (!_ratingEntryMatches(ctx, entry.cast<String, dynamic>(), localIds)) continue;
final rating = flexibleInt(entry['rating']);
return rating != null && rating > 0 ? rating.clamp(1, 10).toInt() : null;
}
return null;
}
Future<void> rate(TrackerRatingContext ctx, int score) async {
final client = _client;
if (client == null) throw const TrackerRatingUnavailableException('Trakt');
await client.addRatings(_ratingBody(ctx, rating: score.clamp(1, 10).toInt()));
}
Future<void> clearRating(TrackerRatingContext ctx) async {
final client = _client;
if (client == null) throw const TrackerRatingUnavailableException('Trakt');
await client.removeRatings(_ratingBody(ctx));
}
/// Drop the current scrobble state without sending a stop. Called on profile
/// switch and when the service is disabled mid-playback.
void cancelInFlight() {
@@ -86,6 +124,100 @@ class TraktScrobbleService {
bool get _canScrobble => _isEnabled && _client != null;
String _ratingType(TrackerRatingContext ctx) => switch (ctx.kind) {
MediaKind.movie => 'movies',
MediaKind.show => 'shows',
MediaKind.season => 'seasons',
MediaKind.episode => 'episodes',
_ => throw const TrackerRatingUnavailableException('Trakt'),
};
bool _ratingEntryMatches(TrackerRatingContext ctx, Map<String, dynamic> entry, Map<String, dynamic> localIds) {
final show = entry['show'];
final movie = entry['movie'];
return switch (ctx.kind) {
MediaKind.movie => _idsMatch(_nestedIds(movie), localIds),
MediaKind.show => _idsMatch(_nestedIds(show), localIds),
MediaKind.season => _idsMatch(_nestedIds(show), localIds) && _numberMatches(entry['season'], ctx.season),
MediaKind.episode =>
_idsMatch(_nestedIds(show), localIds) &&
_numberMatches(entry['episode'], ctx.episodeNumber) &&
_seasonMatches(entry['episode'], ctx.season),
_ => false,
};
}
Map<String, dynamic>? _nestedIds(Object? value) {
if (value is! Map) return null;
final ids = value['ids'];
return ids is Map ? ids.cast<String, dynamic>() : null;
}
bool _idsMatch(Map<String, dynamic>? remoteIds, Map<String, dynamic> localIds) {
if (remoteIds == null) return false;
for (final entry in localIds.entries) {
final local = entry.value;
if (local == null) continue;
final remote = remoteIds[entry.key];
if (remote == null) continue;
if (local is String && remote.toString() == local) return true;
final remoteInt = flexibleInt(remote);
final localInt = flexibleInt(local);
if (remoteInt != null && localInt != null && remoteInt == localInt) return true;
}
return false;
}
bool _numberMatches(Object? value, int? expected) {
if (expected == null || value is! Map) return false;
return flexibleInt(value['number']) == expected;
}
bool _seasonMatches(Object? value, int? expected) {
if (expected == null || value is! Map) return false;
return flexibleInt(value['season']) == expected;
}
Map<String, dynamic> _ratingBody(TrackerRatingContext ctx, {int? rating}) {
final ids = TraktIds.fromExternal(ctx.ids.external).toJson();
final item = {'ids': ids, if (rating != null) 'rating': rating};
return switch (ctx.kind) {
MediaKind.movie => {
'movies': [item],
},
MediaKind.show => {
'shows': [item],
},
MediaKind.season => {
'shows': [
{
'ids': ids,
'seasons': [
{'number': ctx.season, if (rating != null) 'rating': rating},
],
},
],
},
MediaKind.episode => {
'shows': [
{
'ids': ids,
'seasons': [
{
'number': ctx.season,
'episodes': [
{'number': ctx.episodeNumber, if (rating != null) 'rating': rating},
],
},
],
},
],
},
_ => throw const TrackerRatingUnavailableException('Trakt'),
};
}
Future<void> startPlayback(MediaItem metadata, MediaServerClient client, {bool isLive = false}) async {
if (!_canScrobble) return;
if (isLive) return;
+3 -23
View File
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:plezy/widgets/app_icon.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import '../exceptions/media_server_exceptions.dart';
import '../media/media_backend.dart';
import '../media/media_item.dart';
import '../media/media_kind.dart';
@@ -1116,32 +1115,13 @@ class MediaContextMenuState extends State<MediaContextMenu> {
}
Future<void> _showRatingSheet(BuildContext context, MediaItem item, MediaServerClient client) async {
final currentStarValue = (item.userRating != null && item.userRating! > 0) ? item.userRating! / 2.0 : 0.0;
await OverlaySheetController.showAdaptive(
context,
showDragHandle: true,
builder: (context) => RatingBottomSheet(
currentRating: currentStarValue,
onRate: (stars) async {
// 0-10 scale used by both Plex and Jellyfin rate endpoints.
final rating = stars * 2.0;
try {
await client.rate(item, rating);
widget.onRefresh?.call(item.id);
} on MediaServerHttpException catch (e) {
appLogger.w('Failed to set rating', error: e);
if (context.mounted) showErrorSnackBar(context, t.errors.failedToRate);
}
},
onClear: () async {
try {
await client.rate(item, -1);
widget.onRefresh?.call(item.id);
} on MediaServerHttpException catch (e) {
appLogger.w('Failed to clear rating', error: e);
if (context.mounted) showErrorSnackBar(context, t.errors.failedToRate);
}
},
item: item,
serverClient: client,
onServerRatingChanged: (_) => widget.onRefresh?.call(item.id),
),
);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,185 @@
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/models/trackers/anime_ids.dart';
import 'package:plezy/services/trackers/anilist/anilist_session.dart';
import 'package:plezy/services/trackers/anilist/anilist_tracker.dart';
import 'package:plezy/services/trackers/mal/mal_session.dart';
import 'package:plezy/services/trackers/mal/mal_tracker.dart';
import 'package:plezy/services/trackers/simkl/simkl_session.dart';
import 'package:plezy/services/trackers/simkl/simkl_tracker.dart';
import 'package:plezy/services/trackers/tracker_id_resolver.dart';
import 'package:plezy/services/trakt/trakt_scrobble_service.dart';
import 'package:plezy/services/trakt/trakt_session.dart';
import 'package:plezy/utils/external_ids.dart';
int _now() => DateTime.now().millisecondsSinceEpoch ~/ 1000;
TraktSession _traktSession() => TraktSession(
accessToken: 'token',
refreshToken: 'refresh',
expiresAt: _now() + 86400,
scope: 'public',
createdAt: _now(),
);
SimklSession _simklSession() => SimklSession(accessToken: 'token', createdAt: _now());
MalSession _malSession() =>
MalSession(accessToken: 'token', refreshToken: 'refresh', expiresAt: _now() + 86400, createdAt: _now());
AnilistSession _anilistSession() => AnilistSession(accessToken: 'token', expiresAt: _now() + 86400, createdAt: _now());
TrackerRatingContext _ctx({
required MediaKind kind,
ExternalIds external = const ExternalIds(tvdb: 123, tmdb: 456, imdb: 'tt789'),
AnimeIds? anime,
int? season,
int? episodeNumber,
}) {
return TrackerRatingContext(
ids: TrackerIds(external: external, anime: anime),
kind: kind,
season: season,
episodeNumber: episodeNumber,
);
}
void main() {
tearDown(() {
TraktScrobbleService.instance.rebindToProfile(null, onSessionInvalidated: () {});
SimklTracker.instance.rebindSession(null, onSessionInvalidated: () {});
MalTracker.instance.rebindSession(null, onSessionInvalidated: () {});
AnilistTracker.instance.rebindSession(null, onSessionInvalidated: () {});
});
test('Trakt fetches the current episode rating by show ids and episode number', () async {
final client = MockClient((request) async {
expect(request.method, 'GET');
expect(request.url.path, '/sync/ratings/episodes');
return http.Response(
json.encode([
{
'rating': 8,
'show': {
'ids': {'tvdb': 123},
},
'episode': {'season': 1, 'number': 2},
},
]),
200,
);
});
TraktScrobbleService.instance.rebindToProfile(_traktSession(), onSessionInvalidated: () {}, httpClient: client);
final score = await TraktScrobbleService.instance.getRating(
_ctx(kind: MediaKind.episode, season: 1, episodeNumber: 2),
);
expect(score, 8);
});
test('Simkl fetches the current show rating by external ids', () async {
final client = MockClient((request) async {
expect(request.method, 'GET');
expect(request.url.path, '/sync/ratings/shows');
return http.Response(
json.encode({
'shows': [
{
'user_rating': 7,
'show': {
'ids': {'tmdb': 456},
},
},
],
}),
200,
);
});
SimklTracker.instance.rebindSession(_simklSession(), onSessionInvalidated: () {}, httpClient: client);
final score = await SimklTracker.instance.getRating(_ctx(kind: MediaKind.show));
expect(score, 7);
});
test('Simkl checks the anime ratings bucket for non-movie anime', () async {
final paths = <String>[];
final client = MockClient((request) async {
expect(request.method, 'GET');
paths.add(request.url.path);
if (request.url.path == '/sync/ratings/shows') {
return http.Response(json.encode({'shows': []}), 200);
}
if (request.url.path == '/sync/ratings/anime') {
return http.Response(
json.encode({
'anime': [
{
'user_rating': 9,
'show': {
'ids': {'simkl': 987},
},
},
],
}),
200,
);
}
fail('Unexpected Simkl request: ${request.url.path}');
});
SimklTracker.instance.rebindSession(_simklSession(), onSessionInvalidated: () {}, httpClient: client);
final score = await SimklTracker.instance.getRating(_ctx(kind: MediaKind.show, anime: AnimeIds(simkl: 987)));
expect(score, 9);
expect(paths, ['/sync/ratings/shows', '/sync/ratings/anime']);
});
test('MAL fetches the current list score', () async {
final client = MockClient((request) async {
expect(request.method, 'GET');
expect(request.url.path, '/v2/anime/21');
expect(request.url.queryParameters['fields'], 'my_list_status');
return http.Response(
json.encode({
'my_list_status': {'score': 9},
}),
200,
);
});
MalTracker.instance.rebindSession(_malSession(), onSessionInvalidated: () {}, httpClient: client);
final score = await MalTracker.instance.getRating(_ctx(kind: MediaKind.show, anime: AnimeIds(mal: 21)));
expect(score, 9);
});
test('AniList fetches point-100 score and maps it to a 1-10 score', () async {
final client = MockClient((request) async {
final body = json.decode(request.body) as Map<String, dynamic>;
expect(body['variables'], {'mediaId': 21});
expect(body['query'], contains('mediaListEntry'));
expect(body['query'], contains('scoreRaw: score(format: POINT_100)'));
return http.Response(
json.encode({
'data': {
'Media': {
'mediaListEntry': {'scoreRaw': 80},
},
},
}),
200,
);
});
AnilistTracker.instance.rebindSession(_anilistSession(), onSessionInvalidated: () {}, httpClient: client);
final score = await AnilistTracker.instance.getRating(_ctx(kind: MediaKind.show, anime: AnimeIds(anilist: 21)));
expect(score, 8);
});
}