From 4d435865a85cfa7de037a897b3a367956056c691 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Thu, 26 Jun 2025 17:32:02 -0500 Subject: [PATCH 01/15] Added a TODO for later. --- API/Services/Plus/WantToReadSyncService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/API/Services/Plus/WantToReadSyncService.cs b/API/Services/Plus/WantToReadSyncService.cs index a6d536911..fb4db30e7 100644 --- a/API/Services/Plus/WantToReadSyncService.cs +++ b/API/Services/Plus/WantToReadSyncService.cs @@ -53,6 +53,7 @@ public class WantToReadSyncService : IWantToReadSyncService try { + // TODO: K+ can technically throw an exception when rate limit occurs or token is dead. We need to handle. _logger.LogInformation("Syncing want to read for user: {UserName}", user.UserName); var wantToReadSeries = await ( From 4b9bbc5d78b9b6f70ee2e81760881dba0d87480b Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Fri, 27 Jun 2025 01:16:45 +0200 Subject: [PATCH 02/15] [skip ci] Weblate Changes (#3855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Aindriú Mac Giolla Eoin Co-authored-by: Ascyra Co-authored-by: Dark77 Co-authored-by: Frozehunter Co-authored-by: Havokdan Co-authored-by: N'Num Yutthaphon Inchaiya Co-authored-by: lin49931104 Co-authored-by: 無情天 Co-authored-by: 안세훈 --- API/I18N/de.json | 4 +- API/I18N/hu.json | 6 +- API/I18N/ko.json | 4 +- API/I18N/zh_Hant.json | 12 +- UI/Web/src/assets/langs/de.json | 96 +++- UI/Web/src/assets/langs/ga.json | 11 +- UI/Web/src/assets/langs/hu.json | 69 ++- UI/Web/src/assets/langs/ko.json | 107 +++- UI/Web/src/assets/langs/pt_BR.json | 11 +- UI/Web/src/assets/langs/sk.json | 100 +++- UI/Web/src/assets/langs/th.json | 39 +- UI/Web/src/assets/langs/zh_Hans.json | 17 +- UI/Web/src/assets/langs/zh_Hant.json | 730 ++++++++++++++++++++++----- 13 files changed, 975 insertions(+), 231 deletions(-) diff --git a/API/I18N/de.json b/API/I18N/de.json index d91cc8f25..a6c865897 100644 --- a/API/I18N/de.json +++ b/API/I18N/de.json @@ -207,5 +207,7 @@ "sidenav-stream-only-delete-smart-filter": "Nur Smart-Filter-Streams können aus der Seitennavigation gelöscht werden", "dashboard-stream-only-delete-smart-filter": "Nur Smart-Filter-Streams können aus dem Dashboard gelöscht werden", "smart-filter-system-name": "Du kannst den Namen eines vom System bereitgestellten Streams nicht verwenden", - "smart-filter-name-required": "Name des Smart Filters erforderlich" + "smart-filter-name-required": "Name des Smart Filters erforderlich", + "aliases-have-overlap": "Ein oder mehrere Aliasnamen sind mit anderen Personen identisch und können nicht aktualisiert werden", + "generated-reading-profile-name": "Erstellt aus {0}" } diff --git a/API/I18N/hu.json b/API/I18N/hu.json index 7c9473116..21649e2ce 100644 --- a/API/I18N/hu.json +++ b/API/I18N/hu.json @@ -196,5 +196,9 @@ "check-scrobbling-tokens": "Ellenőrizd a Feldolgozó tokeneket", "process-scrobbling-events": "Feldolgozó események feldolgozása", "process-processed-scrobbling-events": "A feldolgozott Feldolgozó események felolgozása", - "generic-cover-volume-save": "Nem lehet borítóképet menteni a kötethez" + "generic-cover-volume-save": "Nem lehet borítóképet menteni a kötethez", + "person-doesnt-exist": "A személy nem létezik", + "person-name-required": "A személy neve kötelező, és nem lehet üres", + "email-taken": "Az e-mail már használatban van", + "person-name-unique": "A személy nevének egyedinek kell lennie" } diff --git a/API/I18N/ko.json b/API/I18N/ko.json index bb49c44ce..7fec9f60a 100644 --- a/API/I18N/ko.json +++ b/API/I18N/ko.json @@ -207,5 +207,7 @@ "dashboard-stream-only-delete-smart-filter": "대시보드에서 스마트 필터 스트림만 삭제할 수 있습니다", "sidenav-stream-only-delete-smart-filter": "사이드 메뉴에서 스마트 필터 스트림만 삭제할 수 있습니다", "smart-filter-name-required": "스마트 필터 이름이 필요합니다", - "smart-filter-system-name": "시스템 제공 스트림 이름은 사용할 수 없습니다" + "smart-filter-system-name": "시스템 제공 스트림 이름은 사용할 수 없습니다", + "aliases-have-overlap": "하나 이상의 별명이 다른 사용자와 중복되어 업데이트할 수 없습니다", + "generated-reading-profile-name": "{0}(으)로부터 생성됨" } diff --git a/API/I18N/zh_Hant.json b/API/I18N/zh_Hant.json index ce72ea451..31d4b69f6 100644 --- a/API/I18N/zh_Hant.json +++ b/API/I18N/zh_Hant.json @@ -193,7 +193,7 @@ "update-yearly-stats": "更新年度統計", "invalid-email": "使用者檔案中的電子郵件無效。請查看日誌以獲得任何連結。", "browse-external-sources": "瀏覽外部來源", - "sidenav-stream-doesnt-exist": "側邊導覽串流不存在", + "sidenav-stream-doesnt-exist": "側邊導覽列的串流不存在", "smart-filter-already-in-use": "已存在具有此智慧篩選器的串流", "external-source-already-exists": "外部來源已存在", "generic-cover-volume-save": "無法保存封面圖片", @@ -201,5 +201,13 @@ "person-doesnt-exist": "此人不存在", "person-name-required": "名稱為必填欄位,且不得留空", "person-name-unique": "名稱不得重複", - "person-image-doesnt-exist": "CoversDB 中不存在此人" + "person-image-doesnt-exist": "CoversDB 中不存在此人", + "email-taken": "電子郵件已被使用", + "sidenav-stream-only-delete-smart-filter": "只有智慧篩選器可以從側邊導覽列中刪除", + "dashboard-stream-only-delete-smart-filter": "只有智慧篩選器串流可以從儀表板中刪除", + "smart-filter-name-required": "智慧篩選器名稱名稱不可為空", + "smart-filter-system-name": "您不能使用系統保留的串流名稱", + "kavitaplus-restricted": "此功能僅限 Kavita+ 使用", + "aliases-have-overlap": "一個或多個別名與其他人物重複,無法更新", + "generated-reading-profile-name": "由 {0} 生成" } diff --git a/UI/Web/src/assets/langs/de.json b/UI/Web/src/assets/langs/de.json index 77663373a..fc6b6cbf7 100644 --- a/UI/Web/src/assets/langs/de.json +++ b/UI/Web/src/assets/langs/de.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "token-expired": "Ihr AniList-Token ist abgelaufen! Scrobbling-Ereignisse werden nicht verarbeitet, bis Sie auf der Seite Konten erneuert haben.", "generate-scrobble-events": "Backfill Ereignisse", - "scrobbling-disabled": "Scrobbeln ist in deinen Kontoeinstellungen deaktiviert." + "scrobbling-disabled": "Scrobbeln ist in deinen Kontoeinstellungen deaktiviert.", + "select-all-label": "Alles auswählen", + "delete-selected-label": "Markiertes löschen" }, "scrobble-event-type-pipe": { "chapter-read": "Lesefortschritt", @@ -83,7 +85,7 @@ }, "user-preferences": { "title": "Benutzer Dashboard", - "pref-description": "Dies sind globale Einstellungen, die an Ihr Konto gebunden sind.", + "pref-description": "Dies sind globale Einstellungen, die mit Ihrem Konto verknüpft sind. Die Reader-Einstellungen befinden sich in den Leseprofilen.", "account-tab": "{{tabs.account-tab}}", "preferences-tab": "{{tabs.preferences-tab}}", "theme-tab": "{{tabs.theme-tab}}", @@ -803,7 +805,8 @@ "more": "Mehr", "customize": "{{settings.customize}}", "edit": "{{common.edit}}", - "cancel-edit": "Schließen Neu ordnen" + "cancel-edit": "Schließen Neu ordnen", + "browse-people": "Personen suchen" }, "library-settings-modal": { "close": "{{common.close}}", @@ -856,7 +859,9 @@ "exclude-patterns-tooltip": "Konfigurieren Sie eine Reihe von Mustern (Glob-Syntax), die Kavita beim Scannen von Verzeichnissen abgleichen und von den Ergebnissen des Scanners ausschließen soll.", "help": "{{common.help}}", "allow-metadata-matching-tooltip": "Sollte Kavita Metadaten für Serien in dieser Bibliothek herunterladen. Dies geschieht nur, wenn der Server über ein aktives Kavita+-Abonnement verfügt.", - "allow-metadata-matching-label": "Erlaube Metadatenabgleich" + "allow-metadata-matching-label": "Erlaube Metadatenabgleich", + "enable-metadata-label": "Metadaten aktivieren (ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "Erlauben Sie Kavita, Metadaten-Dateien zu lesen, die Dateinamenanalyse überschreiben." }, "file-type-group-pipe": { "archive": "Archive", @@ -870,24 +875,24 @@ "margin-label": "{{manage-reading-profiles.margin-book-label}}", "reset-to-defaults": "Auf Standardwerte zurücksetzen", "reader-settings-title": "Reader-Einstellungen", - "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "reading-direction-label": "{{manage-reading-profiles.reading-direction-book-label}}", "right-to-left": "Von rechts nach links", "left-to-right": "Von links nach rechts", "horizontal": "Horizontal", "vertical": "Vertikal", - "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-label": "{{manage-reading-profiles.writing-style-label}}", "writing-style-tooltip": "Ändert die Richtung des Textes. Horizontal ist von links nach rechts, vertikal von oben nach unten.", "tap-to-paginate-label": "Paginierung antippen", "tap-to-paginate-tooltip": "Klicken Sie auf die Ränder des Bildschirms, um zu paginieren", "on": "An", "off": "Aus", - "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-label": "{{manage-reading-profiles.immersive-mode-label}}", "immersive-mode-tooltip": "Dadurch wird das Menü nach einem Klick auf das Reader-Dokument ausgeblendet und das Tippen zum Paginieren eingeschaltet", "fullscreen-label": "Vollbild", "fullscreen-tooltip": "Reader in den Vollbildmodus versetzen", "exit": "Beenden", "enter": "Enter", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-tooltip": "Blättern: Spiegelt die epub-Datei (in der Regel eine lange Bildlaufseite pro Kapitel).
1 Spalte: Erzeugt jeweils eine einzelne virtuelle Seite.
2 Spalten: Erstellt zwei virtuelle Seiten gleichzeitig, die nebeneinander angeordnet sind.", "layout-mode-option-scroll": "Scrollen", "layout-mode-option-1col": "1 Reihe", @@ -1471,7 +1476,9 @@ "all-filters": "Intelligente Filter", "nav-link-header": "Navigation Optionen", "close": "{{common.close}}", - "person-aka-status": "Entspricht einem Alias" + "person-aka-status": "Entspricht einem Alias", + "browse-genres": "Genres durchsuchen", + "browse-tags": "Tags durchsuchen" }, "promoted-icon": { "promoted": "{{common.promoted}}" @@ -1673,7 +1680,9 @@ "release-year": "Jahr der Veröffentlichung", "read-progress": "Zuletzt gelesen", "average-rating": "Durchschnittliche Bewertung", - "random": "Zufällig" + "random": "Zufällig", + "person-series-count": "Anzahl der Serien", + "person-name": "Name" }, "edit-series-modal": { "title": "{{seriesName}} Einzelheiten", @@ -2208,7 +2217,8 @@ "confirm-delete-multiple-volumes": "Sind sie sicher, dass Sie {{count}} Bände löschen wollen? Die Dateien auf der Festplatte werden dadurch nicht verändert.", "series-bound-to-reading-profile": "Serie gebunden an Leseprofil {{name}}", "library-bound-to-reading-profile": "Bibliothek, verknüpft mit Leseprofil {{name}}", - "series-added-want-to-read": "Serie aus der Liste „Möchte lesen“ hinzugefügt" + "series-added-want-to-read": "Serie aus der Liste „Möchte lesen“ hinzugefügt", + "external-match-rate-error": "Die Rate für die Suche nach {{seriesName}} ist abgelaufen. Versuch's in 5 Minuten nochmal." }, "read-time-pipe": { "less-than-hour": "<1 Stunde", @@ -2409,7 +2419,8 @@ "volume-nums": "Bände", "author-count": "{{num}} Autoren", "no-data": "Keine Daten", - "chapter-count": "{{num}} Kapitel" + "chapter-count": "{{num}} Kapitel", + "issue-count": "{{num}} Probleme" }, "confirm": { "alert": "Alarm", @@ -2528,7 +2539,9 @@ "actions-header": "Aktionen", "match-alt": "Entspricht {{seriesName}}", "dont-match-status-label": "{{dont-match-label}}", - "no-data": "{{common.no-data}}" + "no-data": "{{common.no-data}}", + "matched-state-label": "Match-Status", + "library-type": "Bibliotheksart" }, "manage-user-tokens": { "description": "Die Token von Benutzern, die Scrobbeln verwenden, müssen gelegentlich erneuert werden. Kavita sendet ihnen automatisch eine E-Mail, wenn eine E-Mail-Adresse eingerichtet ist und sie eine gültige E-Mail-Adresse haben.", @@ -2633,7 +2646,7 @@ }, "manage-reading-profiles": { "description": "Nicht alle Ihre Serien können auf dieselbe Weise gelesen werden. Richten Sie daher für jede Bibliothek oder Serie separate Leseprofile ein, damit Sie so nahtlos wie möglich zu Ihrer Serie zurückkehren können.", - "extra-tip": "Weisen Sie Leseprofile über das Aktionsmenü in Serien und Bibliotheken oder in großen Mengen zu. Wenn Sie die Einstellungen in einem Reader ändern, wird ein verstecktes Profil erstellt, das Ihre Auswahl für diese Serie speichert (nicht für PDFs). Dieses Profil wird entfernt, wenn Sie der Serie eines Ihrer eigenen Leseprofile zuweisen oder aktualisieren.", + "extra-tip": "Lese-Profile kannst du über das Aktionsmenü in Serien und Bibliotheken oder für mehrere gleichzeitig festlegen. Wenn du die Einstellungen in einem Reader änderst, wird ein verstecktes Profil erstellt, das deine Auswahl für diese Serie speichert (nicht für PDFs). Dieses Profil wird gelöscht, wenn du der Serie eines deiner eigenen Lese-Profile zuweist. Mehr Infos findest du auf der", "tap-to-paginate-tooltip": "Sollten die Seiten des E-Book-Reader-Bildschirms durch Antippen umblätterbar sein vorherige/nächste Seite", "show-screen-hints-tooltip": "Zeige eine Überlagerung an, um den Bereich und die Richtung der Paginierung besser zu verstehen", "font-size-book-tooltip": "Prozentuale Skalierung für die Schriftart im Buch", @@ -2694,7 +2707,10 @@ "pdf-theme-label": "Theme", "pdf-theme-tooltip": "Farbschema des Readers", "reading-profile-series-settings-title": "Serie", - "reading-profile-library-settings-title": "Bibliothek" + "reading-profile-library-settings-title": "Bibliothek", + "wiki-title": "Wiki", + "disable-width-override-label": "Breitenüberschreibung deaktivieren", + "disable-width-override-tooltip": "Verhindere, dass die Breitenüberschreibung wirksam wird, wenn dein Bildschirm mindestens den konfigurierten Haltepunkt erreicht oder kleiner ist" }, "bulk-set-reading-profile-modal": { "no-data": "Es wurden noch keine Sammlungen erstellt", @@ -2706,5 +2722,55 @@ "known-for-title": "Bekannt für", "src": "Person zusammenführen", "merge-warning": "Wenn Sie fortfahren, wird die ausgewählte Person entfernt. Der Name der ausgewählten Person wird als Alias hinzugefügt und alle ihre Rollen werden übertragen." + }, + "browse-people": { + "title": "Personen suchen", + "author-count": "{{num}} Personen", + "roles-label": "Rollen", + "sort-label": "Sortieren", + "name-label": "Name", + "issue-count-label": "Anzahl der Ausgabe", + "series-count-label": "Anzahl der Serien" + }, + "browse-genres": { + "title": "Genres durchsuchen", + "genre-count": "{{num}} Genres" + }, + "breakpoint-pipe": { + "never": "Niemals", + "tablet": "Tablet", + "desktop": "Desktop", + "mobile": "Mobil" + }, + "browse-title-pipe": { + "user-rating": "{{value}} Sternebewertung", + "tag": "Hat Tag {{value}}", + "translator": "Übersetzt von {{value}}", + "character": "Hat den Charakter {{value}}", + "inker": "Inked von {{value}}", + "penciller": "Gezeichnet von {{value}}", + "writer": "Verfasst von {{value}}", + "genre": "Hat Genre {{value}}", + "library": "Innerhalb der Bibliothek {{value}}", + "publisher": "Veröffentlicht von {{value}}", + "editor": "Von {{value}} bearbeitet", + "artist": "Gezeichnet von {{value}}", + "letterer": "Letter von {{value}}", + "colorist": "Gefärbt durch {{value}}", + "format": "Format von {{value}}", + "release-year": "Veröffentlicht in {{value}}", + "imprint": "Imprint von {{value}}", + "location": "An {{value}} Standorten", + "team": "Team {{value}}" + }, + "browse-tags": { + "title": "Tags durchsuchen", + "genre-count": "{{num}} Tags" + }, + "generic-filter-field-pipe": { + "person-name": "Name", + "person-series-count": "Anzahl der Serien", + "person-chapter-count": "Kapitelanzahl", + "person-role": "Rolle" } } diff --git a/UI/Web/src/assets/langs/ga.json b/UI/Web/src/assets/langs/ga.json index f04733b7a..0a6a7515b 100644 --- a/UI/Web/src/assets/langs/ga.json +++ b/UI/Web/src/assets/langs/ga.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "generate-scrobble-events": "Imeachtaí Aislíonta", "token-expired": "Tá do chomhartha AniList imithe in éag! Ní phróiseálfar imeachtaí scrobála go dtí go ndéanfaidh tú athnuachan ar leathanach na gCuntas.", - "scrobbling-disabled": "Tá scrobbling díchumasaithe ar do Shocruithe Cuntais." + "scrobbling-disabled": "Tá scrobbling díchumasaithe ar do Shocruithe Cuntais.", + "select-all-label": "Roghnaigh gach rud", + "delete-selected-label": "Scrios na roghanna" }, "scrobble-event-type-pipe": { "chapter-read": "Dul Chun Cinn léitheoireachta", @@ -858,7 +860,9 @@ "exclude-patterns-tooltip": "Cumraigh sraith patrúin (comhréir Glob) a mheaitseálann Kavita nuair a scanadh eolairí agus eisiamh ó thorthaí Scanóir.", "help": "{{common.help}}", "allow-metadata-matching-tooltip": "Ar cheart do Kavita meiteashonraí a íoslódáil do Shraith laistigh den Leabharlann seo. Ní tharlóidh sé seo ach amháin má tá Síntiús gníomhach Kavita+ ag an bhfreastalaí.", - "allow-metadata-matching-label": "Ceadaigh Meaitseáil Meiteashonraí" + "allow-metadata-matching-label": "Ceadaigh Meaitseáil Meiteashonraí", + "enable-metadata-tooltip": "Lig do Kavita comhaid meiteashonraí a léamh a sháraíonn parsáil ainm comhaid.", + "enable-metadata-label": "Cumasaigh Meiteashonraí (ComicInfo/Epub/PDF)" }, "file-type-group-pipe": { "archive": "Cartlann", @@ -2226,7 +2230,8 @@ "confirm-delete-multiple-volumes": "An bhfuil tú cinnte gur mian leat {{count}} imleabhar a scriosadh? Ní athróidh sé comhaid ar an diosca.", "series-added-want-to-read": "Sraith curtha leis ón liosta Ar Mhaith Leat Léamh", "series-bound-to-reading-profile": "Sraith atá ceangailte le Próifíl Léitheoireachta {{name}}", - "library-bound-to-reading-profile": "Leabharlann ceangailte le Próifíl Léitheoireachta {{name}}" + "library-bound-to-reading-profile": "Leabharlann ceangailte le Próifíl Léitheoireachta {{name}}", + "external-match-rate-error": "Rith Kavita as an ráta ag cuardach {{seriesName}}. Déan iarracht arís i gceann 5 nóiméad." }, "read-time-pipe": { "less-than-hour": "<1 Uair", diff --git a/UI/Web/src/assets/langs/hu.json b/UI/Web/src/assets/langs/hu.json index 7fae9daef..2c418e598 100644 --- a/UI/Web/src/assets/langs/hu.json +++ b/UI/Web/src/assets/langs/hu.json @@ -5,10 +5,10 @@ "password": "{{common.password}}", "password-validation": "{{validation.password-validation}}", "forgot-password": "Elfelejtett jelszó?", - "submit": "{{common.submit}}" + "submit": "{{common.submit}}Bejelentkezés" }, "dashboard": { - "no-libraries": "Még nincs könyvtár beálltva. Állíts be egyet a", + "no-libraries": "Még nincs könyvtár beálltva. \nÁllíts be egyet a", "server-settings-link": "Szerver beállítások", "not-granted": "Nincs hozzáférésed semmilyen könyvtárhoz.", "on-deck-title": "A fedélzeten", @@ -37,7 +37,7 @@ "series-header": "Sorozat", "data-header": "Adat", "is-processed-header": "Feldolgozva", - "no-data": "Nincs adat", + "no-data": "Nincs adat{{common.no-data}}", "volume-and-chapter-num": "Kötet {{v}} Fejezet {{n}}", "volume-num": "Kötet {{num}}", "chapter-num": "Fejezet {{num}}", @@ -48,7 +48,9 @@ "special": "{{entity-title.special}}", "scrobbling-disabled": "A scrobbling ki van kapcsolva a Fiók beállításaidban.", "description": "Itt található minden a fiókodhoz tartozó srobble esemény. Ahhoz, hogy az események létrejöjjenek, be kell állítanod egy scrobble szolgáltatót. A feldolgozott események egy hónap után törlődnek. Ha vannak fel nem dolgozott események, azok adatait valószínűleg nem sikerült egyeztetni a feltöltés során. Ilyen esetben vedd fel a kapcsolatot az adminoddal, hogy javításra kerüljenek.", - "token-expired": "Az AniList tokened lejárt! A scrobble események nem kerülnek feldolgozásra, amíg a Fiókok oldalon nem frissíted." + "token-expired": "Az AniList tokened lejárt! A scrobble események nem kerülnek feldolgozásra, amíg a Fiókok oldalon nem frissíted.", + "select-all-label": "Összes kijelölése", + "delete-selected-label": "Kijelölt(ek) törlése" }, "scrobble-event-type-pipe": { "chapter-read": "Olvasás folyamata", @@ -107,10 +109,16 @@ "success-toast": "Felhasználó beállításai frissítve", "locale-label": "Nyelv", "anilist-scrobbling-label": "AniList Scrobbling", - "want-to-read-sync-label": "Olvasási várólista szinkronizálás" + "want-to-read-sync-label": "Olvasási várólista szinkronizálás", + "clients-opds-url-label": "OPDS URL", + "clients-api-key-label": "API Kulcs", + "share-series-reviews-label": "Sorozatértékelés megosztása" }, "user-holds": { - "no-data": "{{typeahead.no-data}}" + "no-data": "{{typeahead.no-data}}", + "series-name-header": "{{manage-matched-metadata.series-name-header}}", + "created-header": "{{manage-media-issues.created-header}}", + "delete-label": "{{common.remove}}" }, "theme-manager": { "download": "{{changelog.download}}", @@ -118,20 +126,40 @@ "delete": "{{common.delete}}", "drag-n-drop": "{{cover-image-chooser.drag-n-drop}}", "upload": "{{cover-image-chooser.upload}}", - "add": "{{common.add}}" + "add": "{{common.add}}", + "title": "Téma menedzser", + "set-default": "Alapértelmezett", + "updated-toastr": "A webhely alapértelmezett beállításai {{name}}-re frissültek", + "active-theme": "Aktív", + "upload-continued": "css fájl", + "site-themes": "Webhelytémák", + "downloaded": "Letöltött", + "applied": "Alkalmaz", + "preview-title": "Előnézet", + "default-theme": "Alapértelmezett", + "downloadable": "Letölthető" }, "restriction-selector": { - "age-rating-label": "{{metadata-fields.age-rating-title}}" + "age-rating-label": "{{metadata-fields.age-rating-title}}", + "title": "Korhatár besorolás", + "not-applicable-for-admins": "Ez nem vonatkozik az adminokra.", + "no-restriction": "Nincs korlátozás" }, "site-theme-provider-pipe": { - "custom": "{{device-platform-pipe.custom}}" + "custom": "{{device-platform-pipe.custom}}", + "system": "Rendszer" }, "manage-devices": { "add": "{{common.add}}", "delete": "{{common.delete}}", "edit": "{{common.edit}}", "no-data": "{{typeahead.no-data}}", - "actions-header": "{{manage-users.actions-header}}" + "actions-header": "{{manage-users.actions-header}}", + "title": "Eszközkezelő", + "devices-title": "Eszközök", + "email-label": "Email", + "name-label": "Név", + "no-devices": "Még nincsenek beállítva eszközök" }, "edit-device-modal": { "device-name-label": "{{manage-devices.name-label}}", @@ -149,7 +177,12 @@ "edit": "{{common.edit}}", "cancel": "{{common.cancel}}", "save": "{{common.save}}", - "required-field": "{{validation.required-field}}" + "required-field": "{{validation.required-field}}", + "new-password-label": "Új jelszó", + "current-password-label": "Jelenlegi jelszó", + "confirm-password-label": "Jelszó megerősítése", + "passwords-must-match": "A jelszavaknak egyeznie kell", + "permission-error": "Nincs jogosultsága a jelszó megváltoztatására. Forduljon a szerver adminisztrátorához." }, "change-email": { "required-field": "{{validation.required-field}}", @@ -157,7 +190,13 @@ "reset": "{{common.reset}}", "edit": "{{common.edit}}", "cancel": "{{common.cancel}}", - "save": "{{common.save}}" + "save": "{{common.save}}", + "current-password-label": "Jelenlegi jelszó", + "email-not-confirmed": "Ez az e-mail nincs megerősítve", + "email-confirmed": "Ezt az e-mailt megerősítették", + "setup-user-account": "Felhasználói fiók beállítása", + "email-label": "Új e-mail", + "email-updated-title": "E-mail frissítve" }, "change-age-restriction": { "reset": "{{common.reset}}", @@ -750,5 +789,11 @@ "min-length": "A kritika minimum {{count}} karakter hosszúságú kell legyen", "delete": "{{common.delete}}", "required": "{{validation.required-field}}" + }, + "theme": { + "theme-dark": "Sötét", + "theme-black": "Fekete", + "theme-paper": "Papír", + "theme-white": "Fehér" } } diff --git a/UI/Web/src/assets/langs/ko.json b/UI/Web/src/assets/langs/ko.json index 42a181356..887a94e25 100644 --- a/UI/Web/src/assets/langs/ko.json +++ b/UI/Web/src/assets/langs/ko.json @@ -83,7 +83,7 @@ }, "user-preferences": { "title": "사용자 대시보드", - "pref-description": "계정에 연결된 전역 설정입니다.", + "pref-description": "계정에 연결된 전역 설정입니다. 리더 설정은 읽기 프로필에 있습니다.", "account-tab": "{{tabs.account-tab}}", "preferences-tab": "{{tabs.preferences-tab}}", "theme-tab": "{{tabs.theme-tab}}", @@ -611,7 +611,7 @@ "page-label": "페이지", "pagination-header": "섹션", "go-to-page": "페이지로 이동", - "go-to-last-page": "마지막 페이지로 이동", + "go-to-last-page": "마지막 페이지로", "prev-page": "이전 페이지", "next-page": "다음 페이지", "prev-chapter": "이전 챕터/볼륨", @@ -629,7 +629,10 @@ "incognito-mode-label": "시크릿 모드", "next": "다음", "previous": "이전", - "go-to-page-prompt": "{{totalPages}} 페이지가 있습니다. 어떤 페이지로 이동하시겠습니까?" + "go-to-page-prompt": "{{totalPages}} 페이지가 있습니다. 어떤 페이지로 이동하시겠습니까?", + "go-to-first-page": "첫 페이지로", + "go-to-section": "섹션으로 이동", + "go-to-section-prompt": "{{totalSections}} 섹션이 있습니다. 어떤 섹션으로 이동하시겠습니까?" }, "personal-table-of-contents": { "no-data": "아직 북마크된 항목 없음", @@ -677,7 +680,7 @@ "series-detail": { "page-settings-title": "페이지 설정", "close": "{{common.close}}", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-option-card": "카드", "layout-mode-option-list": "리스트", "continue-from": "계속읽기 {{title}}", @@ -801,7 +804,8 @@ "more": "더 보기", "customize": "{{settings.customize}}", "edit": "{{common.edit}}", - "cancel-edit": "재정렬 닫기" + "cancel-edit": "재정렬 닫기", + "browse-people": "사용자 탐색" }, "library-settings-modal": { "close": "{{common.close}}", @@ -864,30 +868,30 @@ }, "reader-settings": { "general-settings-title": "일반 설정", - "font-family-label": "{{user-preferences.font-family-label}}", - "font-size-label": "{{user-preferences.font-size-book-label}}", - "line-spacing-label": "{{user-preferences.line-height-book-label}}", - "margin-label": "{{user-preferences.margin-book-label}}", + "font-family-label": "{{manage-reading-profiles.font-family-label}}", + "font-size-label": "{{manage-reading-profiles.font-size-book-label}}", + "line-spacing-label": "{{manage-reading-profiles.line-height-book-label}}", + "margin-label": "{{manage-reading-profiles.margin-book-label}}", "reset-to-defaults": "기본값으로 재설정", "reader-settings-title": "리더 설정", - "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "reading-direction-label": "{{manage-reading-profiles.reading-direction-book-label}}", "right-to-left": "오른쪽에서 왼쪽", "left-to-right": "왼쪽에서 오른쪽", "horizontal": "가로", "vertical": "세로", - "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-label": "{{manage-reading-profiles.writing-style-label}}", "writing-style-tooltip": "텍스트의 방향을 변경합니다. 가로는 왼쪽에서 오른쪽으로, 세로는 위에서 아래로.", "tap-to-paginate-label": "탭 해서 페이지 넘김", "tap-to-paginate-tooltip": "화면 가장자리를 클릭하여 페이지를 넘김", "on": "켜기", "off": "끄기", - "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-label": "{{manage-reading-profiles.immersive-mode-label}}", "immersive-mode-tooltip": "이렇게 하면 리더 문서를 클릭하면 메뉴가 숨겨지고 탭하여 페이지 넘김이 켜집니다", "fullscreen-label": "전체 화면", "fullscreen-tooltip": "리더를 전체 화면 모드로 전환", "exit": "끄기", "enter": "켜기", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-tooltip": "스크롤: epub 파일을 미러링합니다(일반적으로 장당 하나의 긴 스크롤 페이지).
1열: 한 번에 하나의 가상 페이지를 생성합니다.
2열: 한 번에 두 개의 가상 페이지를 나란히 생성합니다.", "layout-mode-option-scroll": "스크롤", "layout-mode-option-1col": "1열", @@ -896,7 +900,10 @@ "theme-dark": "다크", "theme-black": "블랙", "theme-white": "화이트", - "theme-paper": "종이" + "theme-paper": "종이", + "update-parent": "{{name}}(으)로 저장", + "line-spacing-min-label": "1x", + "line-spacing-max-label": "2.5x" }, "table-of-contents": { "no-data": "이 책에는 메타데이터 또는 toc 파일에 설정된 목차가 없습니다" @@ -1593,9 +1600,9 @@ "height": "높이", "width": "너비", "width-override-label": "너비 재지정", - "off": "끄기", + "off": "{{reader-settings.off}}", "original": "원본", - "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "auto-close-menu-label": "{{manage-reading-profiles.auto-close-menu-label}}", "swipe-enabled-label": "스와이프 사용", "enable-comic-book-label": "만화책 에뮬레이션", "brightness-label": "밝기", @@ -1606,8 +1613,12 @@ "layout-mode-switched": "이중 레이아웃을 렌더링할 공간이 부족하여 레이아웃 모드가 단일로 전환됨", "no-next-chapter": "다음 챕터 없음", "no-prev-chapter": "이전 챕터 없음", - "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}", - "series-progress": "시리즈 진행률: {{percentage}}" + "emulate-comic-book-label": "{{manage-reading-profiles.emulate-comic-book-label}}", + "series-progress": "시리즈 진행률: {{percentage}}", + "create-new": "{{reader-settings.create-new}}", + "update-parent": "{{reader-settings.update-parent}}", + "loading": "{{reader-settings.loading}}", + "create-new-tooltip": "{{reader-settings.create-new-tooltip}}" }, "metadata-filter": { "filter-title": "{{common.filter}}", @@ -1664,7 +1675,8 @@ "release-year": "출시 연도", "read-progress": "마지막으로 읽음", "average-rating": "평균 평점", - "random": "랜덤" + "random": "랜덤", + "person-name": "이름" }, "edit-series-modal": { "title": "{{seriesName}} 세부정보", @@ -1801,8 +1813,8 @@ "cover-image-tab": "{{tabs.cover-tab}}", "tasks-tab": "{{tabs.tasks-tab}}", "info-tab": "{{tabs.info-tab}}", - "pages-label": "{{edit-chapter-modal.pages-count}}", - "words-label": "{{edit-chapter-modal.length-title}}", + "pages-label": "{{edit-chapter-modal.pages-label}}", + "words-label": "{{edit-chapter-modal.words-label}}", "pages-count": "{{edit-chapter-modal.pages-count}}", "words-count": "{{edit-chapter-modal.words-count}}", "reading-time-label": "{{edit-chapter-modal.reading-time-label}}", @@ -2023,7 +2035,7 @@ "reading-lists": "{{side-nav.reading-lists}}", "bookmarks": "{{side-nav.bookmarks}}", "all-series": "{{side-nav.all-series}}", - "browse-authors": "{{side-nav.browse-authors}}" + "browse-authors": "{{side-nav.browse-people}}" }, "filter-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -2407,7 +2419,8 @@ "browse-person-title": "{{name}}의 모든 작품", "all-roles": "역할", "browse-person-by-role-title": "{{role}}로서의 {{name}}의 모든 작품", - "anilist-url": "{{edit-person-modal.anilist-tooltip}}" + "anilist-url": "{{edit-person-modal.anilist-tooltip}}", + "no-info": "이 사람에 대한 정보 없음" }, "edit-person-modal": { "general-tab": "{{edit-series-modal.general-tab}}", @@ -2430,7 +2443,9 @@ "anilist-tooltip": "https://anilist.co/staff/{AniListId}/", "hardcover-id-label": "Hardcover Id", "hardcover-tooltip": "https://hardcover.app/authors/{HardcoverId}", - "description-label": "설명" + "description-label": "설명", + "aliases-label": "별명 편집", + "aliases-tab": "별명" }, "changelog-update-item": { "known-issues": "알려진 문제", @@ -2475,7 +2490,9 @@ "status-header": "상태", "series-name-header": "시리즈", "blacklist-status-label": "수동 일치 필요", - "all-status-label": "모두" + "all-status-label": "모두", + "matched-state-label": "일치 상태", + "library-type": "라이브러리 유형" }, "manage-user-tokens": { "username-header": "사용자 이름", @@ -2548,7 +2565,7 @@ "close": "{{common.close}}", "query-label": "쿼리", "no-results": "일치하는 항목을 찾을 수 없습니다. 지원되는 공급자의 URL을 추가하고 다시 시도해 보세요.", - "query-tooltip": "시리즈 이름, AniList/MyAnimeList url을 입력하세요. url은 직접 조회를 사용합니다." + "query-tooltip": "시리즈 이름, AniList/MyAnimeList/ComicBookRoundup URL을 입력하세요. URL은 직접 조회를 사용합니다." }, "match-series-result-item": { "volume-count": "{{server-stats.volume-count}}", @@ -2613,5 +2630,43 @@ "delete": "{{common.delete}}", "min-length": "리뷰는 최소 {{count}}자 이상이어야 합니다", "required": "{{validation.required-field}}" + }, + "browse-people": { + "author-count": "{{num}} 사용자", + "cover-image-description": "{{edit-series-modal.cover-image-description}}", + "roles-label": "역할", + "sort-label": "정렬", + "series-count": "{{common.series-count}}", + "title": "사용자 탐색", + "issue-count": "{{common.issue-count}}", + "name-label": "이름" + }, + "merge-person-modal": { + "save": "{{common.save}}", + "merge-warning": "진행할 경우, 선택된 사용자가 제거됩니다. 선택된 사용자의 이름은 별명으로 추가되며, 모든 역할이 이전됩니다.", + "src": "사용자 병합", + "alias-title": "새 별명", + "close": "{{common.close}}", + "title": "{{personName}}" + }, + "bulk-set-reading-profile-modal": { + "loading": "{{common.loading}}", + "clear": "{{common.clear}}", + "filter-label": "{{common.filter}}", + "close": "{{common.close}}", + "create": "{{common.create}}" + }, + "manage-reading-profiles": { + "reset": "{{common.reset}}" + }, + "browse-genres": { + "series-count": "{{common.series-count}}", + "issue-count": "{{common.issue-count}}" + }, + "browse-tags": { + "title": "태그 탐색", + "series-count": "{{common.series-count}}", + "issue-count": "{{common.issue-count}}", + "genre-count": "{{num}} 태그" } } diff --git a/UI/Web/src/assets/langs/pt_BR.json b/UI/Web/src/assets/langs/pt_BR.json index d4aa79571..9df1580a6 100644 --- a/UI/Web/src/assets/langs/pt_BR.json +++ b/UI/Web/src/assets/langs/pt_BR.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "generate-scrobble-events": "Eventos de Preenchimento", "token-expired": "Seu token AniList expirou! Os eventos de scrobbling não serão processados até que você renove na página Contas.", - "scrobbling-disabled": "O scrobbling está desativado nas configurações da sua conta." + "scrobbling-disabled": "O scrobbling está desativado nas configurações da sua conta.", + "select-all-label": "Selecionar tudo", + "delete-selected-label": "Excluir selecionado(s)" }, "scrobble-event-type-pipe": { "chapter-read": "Progresso da Leitura", @@ -858,7 +860,9 @@ "exclude-patterns-tooltip": "Configure um conjunto de padrões (sintaxe Glob) que Kavita corresponderá ao verificar diretórios e excluirá dos resultados do Scanner.", "help": "{{common.help}}", "allow-metadata-matching-label": "Permitir Correspondência de Metadados", - "allow-metadata-matching-tooltip": "Deve o Kavita baixar os metadados para Séries nesta Biblioteca. Isso só ocorrerá se o servidor tiver uma Assinatura Kavita+ ativa." + "allow-metadata-matching-tooltip": "Deve o Kavita baixar os metadados para Séries nesta Biblioteca. Isso só ocorrerá se o servidor tiver uma Assinatura Kavita+ ativa.", + "enable-metadata-label": "Habilitar metadados (ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "Permitir que o Kavita leia arquivos de metadados que substituem a análise de nomes de arquivos." }, "file-type-group-pipe": { "archive": "Arquivo", @@ -2226,7 +2230,8 @@ "confirm-delete-multiple-volumes": "Tem certeza de que deseja excluir {{count}} volumes? Isso não modificará os arquivos no disco.", "series-added-want-to-read": "Série adicionada da lista Quero Ler", "library-bound-to-reading-profile": "Biblioteca vinculada ao Perfil de Leitura {{name}}", - "series-bound-to-reading-profile": "Série vinculada ao Perfil de Leitura {{name}}" + "series-bound-to-reading-profile": "Série vinculada ao Perfil de Leitura {{name}}", + "external-match-rate-error": "Kavita ficou sem taxa ao procurar {{seriesName}}. Tente novamente em 5 minutos." }, "read-time-pipe": { "less-than-hour": "<1 Hora", diff --git a/UI/Web/src/assets/langs/sk.json b/UI/Web/src/assets/langs/sk.json index e88666343..22f16924b 100644 --- a/UI/Web/src/assets/langs/sk.json +++ b/UI/Web/src/assets/langs/sk.json @@ -50,7 +50,9 @@ "volume-num": "Zväzok {{num}}", "generate-scrobble-events": "Záložné udalosti", "token-expired": "Platnosť vášho tokenu AniList vypršala! Scrobbling udalosti sa spracujú až po obnovení na stránke Účty.", - "scrobbling-disabled": "Scrobblovanie je zakázané v nastaveniach vášho účtu." + "scrobbling-disabled": "Scrobblovanie je zakázané v nastaveniach vášho účtu.", + "select-all-label": "Vybrať všetko", + "delete-selected-label": "Odstrániť vybraté" }, "scrobble-event-type-pipe": { "chapter-read": "Pokrok v čítaní", @@ -800,7 +802,8 @@ "more": "Viac", "customize": "{{settings.customize}}", "edit": "{{common.edit}}", - "cancel-edit": "Zavrieť zmenu poradia" + "cancel-edit": "Zavrieť zmenu poradia", + "browse-people": "Prehľadávať ľudí" }, "library-settings-modal": { "close": "{{common.close}}", @@ -853,7 +856,9 @@ "type-tooltip": "Typ knižnice určuje, ako sa analyzujú názvy súborov a či používateľské rozhranie zobrazuje kapitoly (manga) vs. čísla (komiksy). Viac podrobností o rozdieloch medzi typmi knižníc nájdete na wiki.", "kavitaplus-eligible-label": "Kavita+ Oprávnené", "allow-metadata-matching-label": "Povoliť zhodu metadát", - "allow-metadata-matching-tooltip": "Mala by Kavita stiahnuť metadáta pre série v rámci tejto knižnice. Toto sa uskutoční len vtedy, ak má server aktívne predplatné Kavita+." + "allow-metadata-matching-tooltip": "Mala by Kavita stiahnuť metadáta pre série v rámci tejto knižnice. Toto sa uskutoční len vtedy, ak má server aktívne predplatné Kavita+.", + "enable-metadata-label": "Povoliť metadáta (ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "Povoliť Kavite čítať súbory s metadátami, ktoré prepíšu analýzu názvov súborov." }, "file-type-group-pipe": { "archive": "Archív", @@ -1461,7 +1466,9 @@ "all-filters": "Smart Filtre", "close": "{{common.close}}", "nav-link-header": "Možnosti navigácie", - "person-aka-status": "Zhoduje sa s aliasom" + "person-aka-status": "Zhoduje sa s aliasom", + "browse-tags": "Prehľadávať značky", + "browse-genres": "Prehľadávať žánre" }, "promoted-icon": { "promoted": "{{common.promoted}}" @@ -1500,7 +1507,7 @@ "title": "MAL záujmy Stack Importovanie", "nothing-found": "Nič sa nenašlo", "description": "Importujte svoje MAL záujmy Stacks a vytvorte zbierky v rámci Kavita", - "restack-count": "{{num}} Restacks", + "restack-count": "{{num}} Restackov", "track": "Skladba" }, "edit-chapter-progress": { @@ -1646,7 +1653,10 @@ "release-year": "Rok vydania", "read-progress": "Naposledy čítané", "average-rating": "Priemerné hodnotenie", - "random": "Náhodný" + "random": "Náhodný", + "person-chapter-count": "Počet kapitol", + "person-name": "Meno", + "person-series-count": "Počet sérií" }, "edit-series-modal": { "title": "Podrobnosti o {{seriesName}}", @@ -1993,7 +2003,7 @@ "reading-lists": "{{side-nav.reading-lists}}", "bookmarks": "{{side-nav.bookmarks}}", "all-series": "{{side-nav.all-series}}", - "browse-authors": "{{side-nav.browse-authors}}" + "browse-authors": "{{side-nav.browse-people}}" }, "filter-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -2171,7 +2181,8 @@ "library-bound-to-reading-profile": "Knižnica prepojená s čitateľským profilom {{name}}", "scrobble-gen-init": "Zaradená úloha na generovanie udalostí Scrobblingu z histórie čítania a hodnotení v minulosti a ich synchronizáciu s pripojenými službami.", "series-added-want-to-read": "Séria bola pridaná zo zoznamu Chcem si prečítať", - "series-bound-to-reading-profile": "Séria viazaná na čitateľský profil {{name}}" + "series-bound-to-reading-profile": "Séria viazaná na čitateľský profil {{name}}", + "external-match-rate-error": "Kavite došla sadzba pri hľadaní {{seriesName}}. Skúste to znova o 5 minút." }, "actionable": { "scan-library": "Skenovať knižnicu", @@ -2338,7 +2349,8 @@ "volume-nums": "Zväzky", "issue-nums": "Vydania", "chapter-count": "{{num}} kapitol", - "no-data": "Žiadne údaje" + "no-data": "Žiadne údaje", + "issue-count": "{{num}} Problémov" }, "person-detail": { "all-roles": "Roly", @@ -2511,7 +2523,9 @@ "library-name-header": "Knižnica", "actions-header": "Akcie", "dont-match-status-label": "{{dont-match-label}}", - "match-alt": "Zhoda {{seriesName}}" + "match-alt": "Zhoda {{seriesName}}", + "matched-state-label": "Stav zhody", + "library-type": "Typ knižnice" }, "match-series-modal": { "description": "Vyberte zhodu na opätovné prepojenie metadát Kavita+ a regeneráciu scrobble udalostí. \"Nezhoda\" sa dá použiť na obmedzenie Kavity v zhode metadát a scrobblingu.", @@ -2680,7 +2694,7 @@ "selection-tip": "Vyberte profil zo zoznamu alebo si vytvorte nový v pravom hornom rohu", "font-size-book-tooltip": "Percentuálna zmena mierky, ktorá sa má použiť na písmo v knihe", "line-height-book-tooltip": "Aké medzery medzi riadkami v knihe", - "extra-tip": "Priraďte čitateľské profily prostredníctvom ponuky akcií v sériách a knižniciach alebo hromadne. Pri zmene nastavení v čítačke sa vytvorí skrytý profil, ktorý si pamätá vaše voľby pre danú sériu (nie pre súbory PDF). Tento profil sa odstráni, keď k sérii priradíte alebo aktualizujete jeden z vlastných čitateľských profilov.", + "extra-tip": "Priraďte čitateľské profily prostredníctvom ponuky akcií v sériách a knižniciach alebo hromadne. Pri zmene nastavení v čítačke sa vytvorí skrytý profil, ktorý si pamätá vaše voľby pre danú sériu (nie pre súbory PDF). Tento profil sa odstráni, keď k sérii priradíte jeden z vlastných čitateľských profilov. Viac informácií nájdete na", "default-profile": "Predvolené", "add-tooltip": "Váš nový profil bude uložený po vykonaní zmien", "make-default": "Nastaviť ako predvolené", @@ -2713,7 +2727,10 @@ "font-family-tooltip": "Skupina písiem, ktoré sa majú načítať. Predvolené načíta predvolené písmo knihy", "color-theme-book-tooltip": "Akú farebnú tému použiť na obsah a ponuku čítačky kníh", "reading-mode-label": "Režim čítania", - "pdf-theme-label": "Téma" + "pdf-theme-label": "Téma", + "wiki-title": "wiki", + "disable-width-override-tooltip": "Zabráňte tomu, aby sa prepísanie šírky prejavilo, keď je obrazovka aspoň na úrovni nakonfigurovaného bodu zlomu alebo menšia", + "disable-width-override-label": "Zakázať prepísanie šírky" }, "bulk-set-reading-profile-modal": { "close": "{{common.close}}", @@ -2737,5 +2754,64 @@ "delete": "{{common.delete}}", "min-length": "Recenzia musí mať aspoň {{count}} znakov", "required": "{{validation.required-field}}" + }, + "browse-people": { + "title": "Prehľadávať ľudí", + "name-label": "Meno", + "series-count-label": "Počet sérií", + "roles-label": "Roly", + "sort-label": "Usporiadať", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}", + "cover-image-description": "{{edit-series-modal.cover-image-description}}", + "issue-count-label": "Počet vydaní", + "author-count": "{{num}} Ľudia" + }, + "browse-genres": { + "title": "Prehľadávať žánre", + "genre-count": "{{num}} Žánrov", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}" + }, + "browse-tags": { + "genre-count": "Značky ({{num}})", + "series-count": "{{common.series-count}}", + "title": "Prehľadávať značky", + "issue-count": "{{common.issue-count}}" + }, + "browse-title-pipe": { + "user-rating": "Hodnotenie hviezdičkami: {{value}}", + "translator": "Preložil/a {{value}}", + "character": "Má charakter {{value}}", + "publisher": "Publikované {{value}}", + "editor": "Upravené {{value}}", + "colorist": "Kolorizované {{value}}", + "inker": "Tlačené {{value}}", + "penciller": "Kreslené {{value}}", + "writer": "Napísal/a {{value}}", + "letterer": "Spísané {{value}}", + "format": "Formát {{value}}", + "release-year": "Vydané v {{value}}", + "imprint": "Odtlačok {{value}}", + "publication-status": "{{value}} funguje", + "age-rating": "Hodnotené ako {{value}}", + "tag": "Má značku {{value}}", + "genre": "Má žáner {{value}}", + "team": "Tím {{value}}", + "location": "V lokalite {{value}}", + "artist": "Nakreslené {{value}}", + "library": "V knižnici {{value}}" + }, + "generic-filter-field-pipe": { + "person-name": "Meno", + "person-series-count": "Počet sérií", + "person-chapter-count": "Počet kapitol", + "person-role": "Rola" + }, + "breakpoint-pipe": { + "never": "Nikdy", + "mobile": "Mobil", + "tablet": "Tablet", + "desktop": "Desktop" } } diff --git a/UI/Web/src/assets/langs/th.json b/UI/Web/src/assets/langs/th.json index d09001c7b..0f1ab1ca8 100644 --- a/UI/Web/src/assets/langs/th.json +++ b/UI/Web/src/assets/langs/th.json @@ -5,15 +5,16 @@ "password": "{{common.password}}", "password-validation": "{{validation.password-validation}}", "forgot-password": "ลืมรหัสผ่าน?", - "submit": "ตกลง" + "submit": "เข้าสู่ระบบ" }, "dashboard": { - "no-libraries": "ยังไม่ได้ตั้งค่ารายการหนังสือ กรุณาตั้งค่าใน", - "server-settings-link": "ตั้งค่าเซิฟเวอร์", - "not-granted": "คุณไม่ได้มีสิทธิ์ในการเข้าถึงรายการหนังสือ", + "no-libraries": "ยังไม่ได้ตั้งค่าห้องสมุด สร้างใหม่เพิ่มใน", + "server-settings-link": "ตั้งค่าเซิร์ฟเวอร์", + "not-granted": "คุณไม่ได้มีสิทธิ์ในการเข้าถึงห้องสมุด", "on-deck-title": "กำลังอ่าน", - "recently-updated-title": "ซีรี่ย์ที่เพิ่งอัพเดท", - "recently-added-title": "ซีรี่ย์ใหม่" + "recently-updated-title": "เรื่องที่อัพเดทล่าสุด", + "recently-added-title": "เรื่องที่เพิ่มใหม่", + "more-in-genre-title": "ดูเพิ่มใน {{genre}}" }, "edit-user": { "edit": "{{common.edit}}", @@ -22,32 +23,36 @@ "required": "{{validation.required-field}}", "email": "{{common.email}}", "cancel": "{{common.cancel}}", - "saving": "กำลังบันทึก…", - "update": "อัปเดท" + "saving": "กำลังจัดเก็บ…", + "update": "อัพเดท", + "invalid-email-warning": "อีเมล์ที่ไม่ถูกต้องจะปิดกั้นบางฟังก์ชั่นของ Kavita", + "account-detail-title": "รายละเอียดบัญชี" }, "user-scrobble-history": { "title": "ประวัติการทำ Scrobble", "description": "ที่นี่คุณสามารถดูรายการ Scrobble ที่เกิดขึ้นภายใต้บัญชีคุณได้ โดยรายการจะเกิดขึ้นก็ต่อเมื่อคุณได้ตั้งค่า Scrobble เรียบร้อยแล้ว รายการต่างๆ จะถูกลบเมื่อผ่านไปหนึ่งเดือน ถ้ามีรายการที่ยังไม่ถูกประมวลผล นั่นหมายความว่าอาจเกิดปัญหาที่ต้นทาง กรุณาติดต่อผู้ดูแลระบบเพื่อแก้ไขปัญหานี้", "filter-label": "{{common.filter}}", - "created-header": "สร้างแล้ว", + "created-header": "สร้างเมื่อ", "last-modified-header": "แก้ไขล่าสุด", - "type-header": "ชนิด", - "series-header": "ซีรีส์", + "type-header": "ประเภท", + "series-header": "เรื่อง", "data-header": "ข้อมูล", "is-processed-header": "กำลังดำเนินการ", - "no-data": "ไม่มีข้อมูล", - "volume-and-chapter-num": "เล่มที่ {{v}} บทที่ {{n}}", + "no-data": "{{common.no-data}}", + "volume-and-chapter-num": "เล่มที่ {{v}} ตอนที่ {{n}}", "volume-num": "เล่มที่ {{num}}", - "chapter-num": "บทที่ {{num}}", - "rating": "เรตติ้ง {{r}}", + "chapter-num": "ตอนที่ {{num}}", + "rating": "คะแนน {{r}}", "not-applicable": "ไม่สามารถใช้ได้", "processed": "ดำเนินการ", "not-processed": "ยังไม่ได้ประมวลผล", - "special": "{{entity-title.special}}" + "special": "{{entity-title.special}}", + "select-all-label": "เลือกทั้งหมด", + "delete-selected-label": "ลบที่เลือก" }, "scrobble-event-type-pipe": { "chapter-read": "ความคืบหน้าการอ่าน", - "score-updated": "อัปเดทเรตติ้ง", + "score-updated": "อัพเดทคะแนน", "want-to-read-add": "ต้องการอ่าน: เพิ่ม", "want-to-read-remove": "ต้องการอ่าน: นำออก", "review": "อัปเดทรีวิว" diff --git a/UI/Web/src/assets/langs/zh_Hans.json b/UI/Web/src/assets/langs/zh_Hans.json index e35b8a171..9fbdc165d 100644 --- a/UI/Web/src/assets/langs/zh_Hans.json +++ b/UI/Web/src/assets/langs/zh_Hans.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "token-expired": "您的 AniList 令牌已过期!除非您在“帐户”页面续订,否则不会处理 Scrobbling 事件。", "generate-scrobble-events": "回填事件", - "scrobbling-disabled": "您的帐户设置中已禁用 Scrobbling。" + "scrobbling-disabled": "您的帐户设置中已禁用 Scrobbling。", + "select-all-label": "全选", + "delete-selected-label": "删除选定项" }, "scrobble-event-type-pipe": { "chapter-read": "阅读进度", @@ -858,7 +860,9 @@ "exclude-patterns-tooltip": "配置一组模式(Glob语法),Kavita 将在扫描目录时匹配这些模式,并将其从扫描程序结果中排除。", "help": "{{common.help}}", "allow-metadata-matching-label": "允许元数据匹配", - "allow-metadata-matching-tooltip": "Kavita 是否应下载此库中系列的元数据。仅当服务器具有有效的 Kavita+ 订阅时才会发生这种情况。" + "allow-metadata-matching-tooltip": "Kavita 是否应下载此库中系列的元数据。仅当服务器具有有效的 Kavita+ 订阅时才会发生这种情况。", + "enable-metadata-label": "启用元数据(ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "允许 Kavita 读取覆盖文件名解析的元数据文件。" }, "file-type-group-pipe": { "archive": "档案", @@ -988,7 +992,7 @@ }, "edit-series-relation": { "description-part-1": "不确定添加哪一种关联?请查看", - "description-part-2": "维基百科的提示。", + "description-part-2": "Wiki 提示。", "target-series": "目标系列", "relationship": "关联", "remove": "{{common.remove}}", @@ -1019,7 +1023,7 @@ }, "manage-media-issues": { "description-part-1": "此列表包含在扫描或读取媒体期间发现的问题。您可以随时清除它并使用资料库(强制)扫描进行分析。一些常见错误及其含义的列表可在以下位置找到 ", - "description-part-2": "维基百科。", + "description-part-2": "wiki.", "filter-label": "{{common.filter}}", "clear-alerts": "清除警报", "file-header": "文件", @@ -2226,7 +2230,8 @@ "confirm-delete-multiple-volumes": "您确定要删除这 {{count}} 个卷吗?这不会修改磁盘上的文件。", "series-added-want-to-read": "从“想读”列表中添加的系列", "series-bound-to-reading-profile": "系列绑定到阅读配置文件 {{name}}", - "library-bound-to-reading-profile": "资料库绑定到阅读配置文件 {{name}}" + "library-bound-to-reading-profile": "资料库绑定到阅读配置文件 {{name}}", + "external-match-rate-error": "Kavita 查找 {{seriesName}} 的速度已超出范围。请过 5 分钟再试。" }, "read-time-pipe": { "less-than-hour": "<1 小时", @@ -2736,7 +2741,7 @@ "reset": "{{common.reset}}", "selection-tip": "从列表中选择一个配置文件,或在右上角创建一个新的配置文件", "add-tooltip": "您的新配置文件将在更改后保存", - "wiki-title": "维基百科", + "wiki-title": "wiki", "disable-width-override-label": "禁用宽度覆盖", "disable-width-override-tooltip": "当屏幕尺寸至少等于或小于配置的断点时,防止宽度覆盖生效" }, diff --git a/UI/Web/src/assets/langs/zh_Hant.json b/UI/Web/src/assets/langs/zh_Hant.json index b8a08eff9..935c32021 100644 --- a/UI/Web/src/assets/langs/zh_Hant.json +++ b/UI/Web/src/assets/langs/zh_Hant.json @@ -13,7 +13,7 @@ "not-granted": "您沒有權限訪問書庫。", "on-deck-title": "繼續閱讀", "recently-updated-title": "最近更新系列", - "recently-added-title": "新增系列", + "recently-added-title": "最近新增系列", "more-in-genre-title": "{{genre}} 更多的內容分類" }, "edit-user": { @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "scrobbling-disabled": "你的帳戶設定中已停用Scrobbling。", "generate-scrobble-events": "回填事件", - "token-expired": "你的 AniList 權杖已過期!在你於帳戶頁面更新之前,追蹤事件將無法處理。" + "token-expired": "您的 AniList 授權憑證已過期!在您於帳戶頁面更新之前,追蹤事件將無法處理。", + "select-all-label": "全選", + "delete-selected-label": "刪除所選項目" }, "scrobble-event-type-pipe": { "chapter-read": "閱讀進度", @@ -72,7 +74,8 @@ "your-review": "這是您的評論", "external-review": "外部評論", "local-review": "本地評論", - "rating-percentage": "評分{{r}}%" + "rating-percentage": "評分{{r}}%", + "critic": "評論者" }, "want-to-read": { "title": "想讀", @@ -81,8 +84,8 @@ "no-items-filtered": "沒有項目符合您當前的過濾條件。" }, "user-preferences": { - "title": "用戶控制台", - "pref-description": "這些是綁定到您帳戶的全局設置。", + "title": "用戶儀表板", + "pref-description": "這些是與您帳號綁定的全域設定,閱讀器的相關設定請至「閱讀設定檔」中調整。", "account-tab": "{{tabs.account-tab}}", "preferences-tab": "{{tabs.preferences-tab}}", "theme-tab": "{{tabs.theme-tab}}", @@ -90,7 +93,7 @@ "stats-tab": "{{tabs.stats-tab}}", "scrobbling-tab": "{{tabs.scrobbling-tab}}", "smart-filters-tab": "{{tabs.smart-filters-tab}}", - "success-toast": "更新使用者偏好", + "success-toast": "更新用戶偏好", "global-settings-title": "全域設定", "page-layout-mode-label": "頁面佈局模式", "page-layout-mode-tooltip": "在系列詳細信息頁面上將項目顯示為卡片或列表視圖。", @@ -102,8 +105,8 @@ "prompt-on-download-tooltip": "下載大小超過{{size}}MB時提示", "disable-animations-label": "禁用動畫", "disable-animations-tooltip": "關閉網站中的動畫,對於電子書閱讀器很有用。", - "collapse-series-relationships-label": "摺疊系列關係", - "collapse-series-relationships-tooltip": "Kavita是否展示沒有關係的系列或前傳", + "collapse-series-relationships-label": "摺疊系列關聯", + "collapse-series-relationships-tooltip": "Kavita是否展示沒有關聯的系列或前傳", "share-series-reviews-label": "分享系列評論", "share-series-reviews-tooltip": "是否對其他用戶顯示您對系列的評論", "clients-opds-alert": "此伺服器上未啟用 OPDS。 這不會影響 Tachiyomi 用戶。", @@ -113,12 +116,20 @@ "reset": "{{common.reset}}", "save": "{{common.save}}", "clients-opds-url-label": "OPDS 網址", - "clients-api-key-label": "API 密鑰" + "clients-api-key-label": "API 密鑰", + "kavitaplus-settings-title": "Kavita+", + "anilist-scrobbling-tooltip": "允許 Kavita 單向同步閱讀進度與評分至 AniList", + "want-to-read-sync-label": "想讀清單同步", + "want-to-read-sync-tooltip": "允許 Kavita 根據 AniList 和 MAL 的待讀清單系列,新增項目到您的「想讀」中", + "anilist-scrobbling-label": "AniList Scrobbling" }, "user-holds": { - "title": "記錄保留", - "description": "這是用戶自主管理的系列列表,不會發送給 Scrobble 服務提供商。 您可以隨時刪除系列,下一次 Scrobble 事件(閱讀進度、評分、想讀狀態)將觸發事件。", - "no-data": "{{typeahead.no-data}}" + "title": "暫停 Scrobble", + "description": "這個清單由用戶管理,其中包含不會同步到外部服務的系列。你可以隨時將任何系列從此清單中移除。一旦移除,下次該系列發生任何 Scrobble 事件(例如:閱讀進度、評分或想讀狀態變更),就會立即觸發同步功能。", + "no-data": "{{typeahead.no-data}}", + "series-name-header": "{{manage-matched-metadata.series-name-header}}", + "delete-label": "{{common.remove}}", + "created-header": "{{manage-media-issues.created-header}}" }, "theme-manager": { "title": "主題管理器", @@ -200,7 +211,7 @@ "cancel": "{{common.cancel}}", "save": "{{common.save}}", "required-field": "{{validation.required-field}}", - "passwords-must-match": "密碼必須匹配", + "passwords-must-match": "密碼必須一致", "permission-error": "您無權更改您的密碼。聯繫服務器管理員。" }, "change-email": { @@ -238,34 +249,41 @@ "regen-warning": "重新生成 API 密鑰將使現有使用端失效。", "no-key": "錯誤 - 未設置密鑰", "confirm-reset": "這將使您設置的任何 OPDS 配置無效。 你確定要繼續嗎?", - "key-reset": "API密鑰重置" + "key-reset": "API密鑰重置", + "reset": "重設" }, "scrobbling-providers": { "title": "Scrobbling供應商", "requires": "此功能需要有效的{{product}}授權", - "token-valid": "Token 有效", - "token-expired": "Token已過期", - "no-token-set": "未設定Token", - "token-set": "設定Token", + "token-valid": "授權憑證有效", + "token-expired": "授權憑證已過期", + "no-token-set": "未設定授權憑證", + "token-set": "設定授權憑證", "generate": "產生", "generic-instructions": "填寫有關不同外部服務的信息,以便讓 Kavita+ 能夠與它們互動。", - "instructions": "First time users should click on \"{{scrobbling-providers.generate}}\" below to allow Kavita+ to talk with {{service}}. Once you authorize the program, copy and paste the token in the input below. You can regenerate your token at any time.", + "instructions": "初次使用的用戶請點擊下方的「{{scrobbling-providers.generate}}」,以允許 Kavita+ 和 {{service}} 進行通訊。授權程式後,請將取得的憑證複製並貼到下方的輸入框中。您可以隨時重新產生新的憑證。", "mal-instructions": "Kavita 使用 MAL 客戶端 ID 進行身份驗證。為 Kavita 創建一個新的客戶端,一旦批准,提供客戶端 ID 和您的用戶名稱。", "scrobbling-applicable-label": "可以進行 Scrobbling", - "token-input-label": "Token:{{service}}", + "token-input-label": "{{service}} 授權憑證填寫處", "mal-token-input-label": "MAL 客戶端 ID", "mal-username-input-label": "MAL 用戶名稱", "edit": "{{common.edit}}", "cancel": "{{common.cancel}}", "save": "{{common.save}}", - "loading": "{{common.loading}}" + "loading": "{{common.loading}}", + "mal-used-for": "MyAnimeList 用於智慧收藏和同步「想讀」清單。", + "anilist-used-for": "AniList 用於 scrobbling (同步資料)和同步「想讀」清單。", + "anilist-first-later": "稍後", + "anilist-first-now": "現在", + "anilist-first-header": "AniList 授權憑證已儲存", + "anilist-first-description": "您的 AniList 授權憑證現在已設定完成。這個憑證的有效期限為一年,Kavita 會在到期前通知您來這裡更新憑證。設定完成後,Kavita 將會根據您的閱讀歷史、想讀清單和評分,自動產生scrobble(同步)事件。您可以選擇現在就開始同步,或稍後再進行。如果您有任何系列不希望被同步,請選擇「稍後」,並在這些系列上設定 「暫停 Scrobble」功能。您只需要執行這個任務一次,之後 Kavita 便會自動為您處理同步事宜。
如果您選擇「稍後」,可以在 Scrobbling 頁面找到相關按鈕。" }, "typeahead": { "locked-field": "已鎖定", "close": "{{common.close}}", "loading": "{{common.loading}}", "add-item": "新增 {{item}}…", - "no-data": "沒有資料", + "no-data": "{{common.no-data}}", "add-custom-item": ",輸入以添加自定義項目" }, "generic-list-modal": { @@ -364,7 +382,7 @@ "all-series-missing": "您的帳戶無法訪問列表中的所有系列,或者 Kavita 列表中沒有任何內容。", "chapter-missing": "{{series}}:Kavita缺少第{{chapter}}章,該項目將被跳過。", "empty-file": "cbl 檔案為空,無需執行任何操作。", - "name-conflict": "您的帳戶中已存在與 cbl 文件匹配的書單({{readingListName}})。", + "name-conflict": "您的帳戶中已存在與 cbl 文件相同的書單({{readingListName}})。", "series-collision": "該系列{{seriesLink}}與書庫中的系列名稱相同。", "series-missing": "Kavita中缺少系列{{series}},或者您的帳戶沒有權限。 該系列的所有項目都將跳過導入。", "volume-missing": "{{series}}:卷 {{volume}} 在 Kavita 中缺失或您的帳戶沒有權限,所有具有此卷號的項目將被跳過。", @@ -395,8 +413,8 @@ }, "relationship-pipe": { "adaptation": "改編", - "alternative-setting": "同世界觀", - "alternative-version": "IF線", + "alternative-setting": "架空世界/平行設定", + "alternative-version": "不同版本/IF線", "character": "客串角色", "contains": "包含", "doujinshi": "同人", @@ -424,7 +442,7 @@ "editor": "編輯", "inker": "勾線師", "letterer": "字體設計", - "penciller": "線稿師", + "penciller": "草稿師", "publisher": "出版社", "writer": "作者", "other": "其他", @@ -442,9 +460,9 @@ }, "library-type-pipe": { "book": "書籍", - "comic": "美漫", - "manga": "漫畫", - "comicVine": "Comic Vine", + "comic": "漫畫(舊版)", + "manga": "漫畫(日漫)", + "comicVine": "漫畫", "image": "圖片", "lightNovel": "輕小說" }, @@ -489,7 +507,7 @@ "all-filters": { "count": "{{count}} {{customize-dashboard-modal.title-smart-filters}}", "create": "{{common.create}}", - "title": "所有的智慧篩選" + "title": "所有的智慧篩選器" }, "out-of-date-modal": { "close": "{{common.close}}", @@ -528,11 +546,11 @@ "loading": "{{common.loading}}", "activate-email-label": "{{common.email}}", "activate-delete": "{{common.delete}}", - "activate-reset": "{{common.reset}}", + "activate-reset": "重設授權", "activate-save": "{{common.save}}", "activate-reset-tooltip": "使用您的授權來使先前的註冊失效。需要提供授權金鑰和電子郵件", "invalid-license-tooltip": "如果您的訂閱已結束,您必須發送電子郵件給支援團隊以創建新的訂閱", - "title": "Kavita+ 許可證", + "title": "Kavita+ 授權", "license-not-valid": "授權無效", "license-valid": "授權有效", "buy": "購買", @@ -546,11 +564,32 @@ "activate-discordId-label": "Discord 用戶 ID", "activate-discordId-tooltip": "將您的 Discord 帳戶與 Kavita+ 連結。這將授予您訪問隱藏頻道的權限,以幫助塑造 Kavita。", "discord-validation": "這不是有效的 Discord 用戶 ID。您的用戶 ID 不是您的 Discord 用戶名。", - "kavita+-desc-part-2": "高級福利", - "kavita+-desc-part-3": "今天!", - "kavita+-desc-part-1": "Kavita+ 是一項高級訂閱服務,可為此 Kavita 實例上的所有用戶解鎖功能。購買訂閱以解鎖 ", - "kavita+-requirement": "Kavita+ 僅與最新版本(最近的兩個版本)配合使用。任何不在此範圍內的版本可能無法正常運作。", - "help-label": "{{common.help}}" + "kavita+-desc-part-2": "「進階福利」", + "kavita+-desc-part-3": "就在今天!", + "kavita+-desc-part-1": "Kavita+ 是一項加值訂閱服務,能為此 Kavita 實例上的所有使用者解鎖更多功能。立即購買訂閱,享受 ", + "kavita+-requirement": "Kavita+ 僅支援 Kavita 的最新三個版本。超出此範圍的版本,其功能可能無法正常運作。", + "help-label": "{{common.help}}", + "supported-version-label": "授權支援版本", + "faq-title": "常見問題", + "info-title": "授權資訊", + "k+-unlocked-description": "歡迎使用 Kavita+!Kavita 會根據您的閱讀進度、評分以及「想讀」清單產生 Scrobble 事件。一旦您在帳戶中註冊了 Scrobbling 提供者,即可啟用此功能。請花些時間為不想要進行 Scrobble 的書籍系列設定「暫停 Scrobble」,以確保不會同步這些內容。", + "license-active-label": "授權已啟用", + "reset-label": "重設", + "expiration-label": "到期", + "valid-tooltip": "有效", + "invalid-tooltip": "無效", + "manage-tooltip": "可以更新付款資訊、取消/暫停訂閱等操作", + "email-label": "註冊電子郵件", + "license-mismatch": "此授權可能已註冊到其他 Kavita 實例。請重新註冊以修正此問題。", + "k+-license-overwrite": "您的授權已註冊到另一個 Kavita 實例。這情況可能發生在您重新安裝 Kavita 時,或極少數情況下因系統更新而導致。請選擇「覆寫」以強制此實例註冊 Kavita+。", + "delete-label": "移除授權", + "actions-title": "操作", + "reset-tooltip": "這將重設 Kavita+ 授權綁定的伺服器實例。需要您的電子郵件和授權金鑰", + "delete-tooltip": "這會將授權從 Kavita 移除,停止 Kavita 與 Kavita+ 之間的通訊。此操作不會取消您的訂閱。 若要取消訂閱,請先使用「管理」功能。", + "total-subbed-months-label": "總訂閱月數", + "overwrite": "覆寫", + "k+-already-registered-header": "授權已被註冊", + "k+-unlocked": "Kavita+ 已解鎖!" }, "book-line-overlay": { "copy": "複製", @@ -583,7 +622,10 @@ "go-to-page-prompt": "一共有 {{totalPages}} 頁,您想要跳轉到哪一頁?", "table-of-contents-header": "目錄", "close-reader": "關閉閱讀器", - "loading-book": "正在加載書籍…" + "loading-book": "正在加載書籍…", + "go-to-first-page": "前往第一頁", + "go-to-section-prompt": "共有 {{totalSections}} 個章節。您想前往?", + "go-to-section": "前往章節" }, "confirm-email": { "username-label": "{{common.username}}", @@ -619,7 +661,7 @@ }, "series-detail": { "close": "{{common.close}}", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "read": "{{common.read}}", "remove-from-want-to-read": "{{actionable.remove-from-want-to-read}}", "add-to-want-to-read": "{{actionable.add-to-want-to-read}}", @@ -652,7 +694,7 @@ "volume-num": "{{common.volume-num}}", "reading-lists-title": "{{side-nav.reading-lists}}", "time-to-read-alt": "{{sort-field-pipe.time-to-read}}", - "scrobbling-tooltip": "{{settings.scrobbling}}", + "scrobbling-tooltip": "{{settings.scrobbling}}: {{value}}", "layout-mode-option-card": "卡片", "read-options-alt": "閱讀選項", "page-settings-title": "頁面設定", @@ -673,7 +715,9 @@ "time-left-alt": "剩餘時間", "publication-status-tooltip": "出版狀態", "continue-incognito": "以無痕模式繼續", - "read-incognito": "無痕閱讀" + "read-incognito": "無痕閱讀", + "on": "{{reader-settings.on}}", + "off": "{{reader-settings.off}}" }, "metadata-fields": { "collections-title": "{{side-nav.collections}}", @@ -685,7 +729,7 @@ "editors-title": "編輯", "letterers-title": "字體設計師", "translators-title": "翻譯", - "pencillers-title": "草圖", + "pencillers-title": "草稿師", "publishers-title": "出版社", "teams-title": "團隊", "locations-title": "地點", @@ -701,7 +745,8 @@ "close": "{{common.close}}", "kavita-tooltip": "您的評分 + 總體評價", "kavita-rating-title": "您的評分", - "entry-label": "查看詳情" + "entry-label": "查看詳情", + "critic": "{{review-card.critic}}" }, "update-notification-modal": { "close": "{{common.close}}", @@ -728,7 +773,10 @@ "all-series": "所有系列", "more": "更多", "donate-tooltip": "您可以通過訂閱 Kavita+ 來移除這個", - "donate": "捐款" + "donate": "捐款", + "browse-people": "瀏覽人物", + "edit": "{{common.edit}}", + "cancel-edit": "取消調整順序" }, "library-settings-modal": { "close": "{{common.close}}", @@ -765,39 +813,43 @@ "edit-title": "編輯 {{name}}", "add-title": "添加書庫", "name-label": "名稱", - "type-tooltip": "書庫類型決定了檔案名的解析方式以及UI是否顯示章節(漫畫)或期刊(美漫),查看wiki了解有關書庫類型之間的更多差異。", + "type-tooltip": "書庫類型決定了檔案名的解析方式以及UI是否顯示章節(漫畫)或期數(美漫),查看wiki了解有關書庫類型之間的更多差異。", "folder-description": "將資料夾新增到您的書庫", "help-us-part-1": "請依照 ", "manage-reading-list-tooltip": "Kavita 可以用 ComicInfo.xml 或 opf 文件中的 StoryArc、StoryArcNumber 和 AlternativeSeries、AlternativeCount 標籤來創建書單", "allow-scrobbling-label": "允許Scrobbling", "allow-scrobbling-tooltip": "是否允許Kavita將閱讀事件、想讀狀態、評分和評論記錄至已配置的Scrobble服務提供商?這僅在伺服器具有有效的 Kavita+ 訂閱時才會生效。", "folder-watching-tooltip": "覆蓋此書庫的伺服器資料夾監控。如果關閉此功能,則不會監控此書庫包含的資料夾。如果多個書庫共享同一資料夾,那麼這些資料夾仍可能被監控。在每次觸發掃描之前,需等待10分鐘。", - "include-in-dashboard-tooltip": "是否應在控制台上包含來自此書庫的系列?這會影響所有清單,例如繼續閱讀、最近更新、最近添加或任何自訂添加。", + "include-in-dashboard-tooltip": "是否應將書庫中的系列包含在儀表板上? 這會影響所有串流,例如「繼續閱讀」、「最近更新」、「最近新增」或任何自訂新增的內容。", "force-scan": "強制掃描", "next": "下一步", "file-type-group-label": "檔案類型", "file-type-group-tooltip": "Kavita 應掃描哪些類型的檔案。例如,“壓縮檔”會包含所有 cb*、zip、rar 等檔案。", "kavitaplus-eligible-label": "Kavita+資格符合", - "kavitaplus-eligible-tooltip": "Kavita+ 是否會拉取資訊或支援 Scrobbling", - "include-in-dashboard-label": "顯示在控制台上" + "kavitaplus-eligible-tooltip": "支援 Kavita+ 的元資料功能或 Scrobbling", + "include-in-dashboard-label": "顯示在儀表板上", + "allow-metadata-matching-label": "允許元資料匹配", + "allow-metadata-matching-tooltip": "是否允許 Kavita 為此資料庫中的書籍系列下載元資料?此功能僅在伺服器擁有有效的 Kavita+ 訂閱時才會啟用。", + "enable-metadata-tooltip": "允許 Kavita 讀取元資料檔案,以覆蓋檔名解析結果。", + "enable-metadata-label": "啟用元資料(ComicInfo/EPUB/PDF)" }, "reader-settings": { - "font-family-label": "{{user-preferences.font-family-label}}", - "font-size-label": "{{user-preferences.font-size-book-label}}", - "line-spacing-label": "{{user-preferences.line-height-book-label}}", - "margin-label": "{{user-preferences.margin-book-label}}", - "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", - "writing-style-label": "{{user-preferences.writing-style-label}}", - "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "font-family-label": "{{manage-reading-profiles.font-family-label}}", + "font-size-label": "{{manage-reading-profiles.font-size-book-label}}", + "line-spacing-label": "{{manage-reading-profiles.line-height-book-label}}", + "margin-label": "{{manage-reading-profiles.margin-book-label}}", + "reading-direction-label": "{{manage-reading-profiles.reading-direction-book-label}}", + "writing-style-label": "{{manage-reading-profiles.writing-style-label}}", + "immersive-mode-label": "{{manage-reading-profiles.immersive-mode-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "on": "開", "vertical": "縱向", "off": "關", "right-to-left": "從右到左", - "immersive-mode-tooltip": "這將隱藏點擊閱讀器文檔後的選單,然後打開“點擊翻頁”功能", + "immersive-mode-tooltip": "這將隱藏點擊閱讀器文檔後的選單,然後打開「點擊翻頁」功能", "fullscreen-label": "全螢幕", "exit": "退出", - "writing-style-tooltip": "更改文字方向。橫向從左到右,縱向從上到下。", + "writing-style-tooltip": "變更文字的書寫方向。橫排為由左至右,直排為由上至下。", "general-settings-title": "一般設定", "reset-to-defaults": "恢復默認", "reader-settings-title": "閱讀器設定", @@ -815,7 +867,15 @@ "theme-white": "白", "theme-paper": "紙", "enter": "打開", - "layout-mode-option-scroll": "滾動" + "layout-mode-option-scroll": "滾動", + "update-parent": "儲存到 {{name}}", + "create-new-tooltip": "從您目前的隱含設定建立一個可管理的新設定檔", + "reading-profile-updated": "閱讀設定已更新", + "reading-profile-promoted": "閱讀設定已設為主要", + "loading": "載入中", + "create-new": "從隱含設定建立新設定檔", + "line-spacing-min-label": "1x", + "line-spacing-max-label": "2.5x" }, "bookmarks": { "title": "{{side-nav.bookmarks}}", @@ -855,12 +915,14 @@ "language-title": "{{edit-chapter-modal.language-label}}", "format-title": "{{metadata-filter.format-label}}", "release-title": "{{sort-field-pipe.release-year}}", - "length-title": "{{edit-chapter-modal.words-label}}" + "length-title": "{{edit-chapter-modal.words-label}}", + "age-rating-title": "{{metadata-fields.age-rating-title}}" }, "related-tab": { "reading-lists-title": "{{reading-lists.title}}", "collections-title": "{{side-nav.collections}}", - "relations-title": "{{tabs.related-tab}}" + "relations-title": "{{tabs.related-tab}}", + "bookmarks-title": "{{side-nav.bookmarks}}" }, "cover-image-chooser": { "reset": "{{common.reset}}", @@ -882,9 +944,9 @@ "parent": "{{relationship-pipe.parent}}", "description-part-2": "wiki 以獲取提示。", "target-series": "目標系列", - "description-part-1": "不確定要添加什麼關係?請參閱我們的", - "relationship": "關係", - "add-relationship": "添加關係" + "description-part-1": "不確定要添加什麼關聯?請參閱我們的", + "relationship": "關聯性", + "add-relationship": "添加關聯性" }, "bulk-add-to-collection": { "promoted": "{{common.promoted}}", @@ -910,10 +972,10 @@ "description-part-2": "wiki 中找到。", "comment-header": "評論", "file-header": "檔案", - "clear-alerts": "清除警報", + "clear-alerts": "清除警示", "description-part-1": "這張表格包含了掃描或閱讀媒體時發現的問題。您可以隨時清除它,並使用書庫(強制)掃描來進行分析。有關一些常見錯誤及其含義的列表,可以在 ", "created-header": "創建", - "no-data": "沒有資料" + "no-data": "{{common.no-data}}" }, "manage-email-settings": { "reset": "{{common.reset}}", @@ -926,7 +988,7 @@ "send-to-warning": "如果您希望“發送至裝置”功能正常運作,您必須完善電子郵件設定", "test": "測試", "host-name-label": "主機名稱", - "host-name-tooltip": "域名(使用反向代理的話)。此項為電子郵件功能所必需。如果沒有反向代理,請使用任意網址。", + "host-name-tooltip": "請輸入您反向代理的網域名稱,此項為郵件功能所必須。若未使用反向代理,也可填入任意 URL,如:http://externalip:port/", "host-name-validation": "主機名稱必須以 http(s) 開頭,且不能以 / 結尾", "host-tooltip": "您電子郵件伺服器的發送/SMTP地址", "host-label": "主機", @@ -984,7 +1046,7 @@ "reset-to-default": "{{common.reset-to-default}}", "reset": "{{common.reset}}", "save": "{{common.save}}", - "media-issue-title": "媒體問題", + "media-issue-title": "檔案問題", "encode-as-description-part-2": "我可以使用WebP嗎?", "encode-as-warning": "一旦您將檔案轉換為 WebP/AVIF,將無法再轉換回 PNG。您需要重新整理您的書庫封面以重新生成所有封面。書籤和圖示無法被轉換。", "media-warning": "您必須在儲存後,從“任務”標籤中觸發媒體轉換任務。", @@ -1033,8 +1095,8 @@ "notice": "注意:", "base-url-label": "基礎網址", "cache-size-tooltip": "允許用於快取大型 API 的記憶體大小。默認為 75MB。", - "on-deck-last-chapter-add-label": "保留於“繼續閱讀”的“最近新增章節”天數", - "on-deck-last-progress-label": "保留於“繼續閱讀”的“最近閱讀”天數", + "on-deck-last-chapter-add-label": "「繼續閱讀」章節更新逾期(天數)", + "on-deck-last-progress-label": "「繼續閱讀」閱讀進度逾期(天數)", "send-data": "發送數據", "opds-label": "OPDS", "max-logs-validation": "您不能擁有超過{{num}}條日誌", @@ -1045,7 +1107,7 @@ "min-cache-validation": "至少得有50MB。", "max-backup-validation": "您不能擁有超過 {{num}} 個備份", "base-url-validation": "基礎網址必須以/開頭,以/收尾", - "on-deck-last-chapter-add-tooltip": "最近有新增章節的書籍將自動加入“繼續閱讀”,超過天數沒有新增章節將從“繼續閱讀”中移除。", + "on-deck-last-chapter-add-tooltip": "最近有新增章節的書籍將自動加入「繼續閱讀」,超過天數沒有新增章節將從「繼續閱讀」中移除。", "folder-watching-label": "資料夾監控", "enable-folder-watching": "啟用資料夾監控", "opds-tooltip": "若開啟此功能,將允許所有用戶使用 OPDS 從伺服器閱讀和下載內容。", @@ -1056,9 +1118,9 @@ "log-label": "日誌保留天數", "logging-level-label": "日誌級別", "cache-size-label": "快取大小", - "base-url-tooltip": "如果您希望在基礎網址上托管 Kavita,例如:yourdomain.com/kavita,請使用此選項。此功能在使用非root使用者的 Docker 環境中不支援。", + "base-url-tooltip": "如果您希望在基礎網址上托管 Kavita,例如:yourdomain.com/kavita,請使用此選項。此功能在使用非root用戶的 Docker 環境中不支援。", "backup-label": "備份保留天數", - "on-deck-last-progress-tooltip": "最近閱讀進度有變動的書籍將自動加入“繼續閱讀”,超過天數未閱讀將從“繼續閱讀”中移除。", + "on-deck-last-progress-tooltip": "最近閱讀進度有變動的書籍將自動加入「繼續閱讀」,超過天數未閱讀將從「繼續閱讀」中移除。", "folder-watching-tooltip": "允許 Kavita 監控書庫資料夾,以檢測變更,並在變更發生時自動觸發掃描。這使內容能在不手動觸發掃描或等待夜間掃描的情況下更新。掃描觸發前將始終等待 10 分鐘。", "min-logs-validation": "您必需至少有一條日誌" }, @@ -1145,7 +1207,8 @@ "name-header": "名稱", "roles-header": "權限", "too-many-libraries": "很多", - "delete-user-alt": "刪除用戶{{user}}" + "delete-user-alt": "刪除用戶{{user}}", + "all-libraries": "所有書庫" }, "edit-collection-tags": { "required-field": "{{validation.required-field}}", @@ -1166,7 +1229,7 @@ "source-url-title": "來源網址:", "total-series-title": "全部系列:", "missing-series-title": "缺失系列:", - "promote-tooltip": "推廣意味著該標籤可以在伺服器範圍內被看到,而不僅限於管理員用戶。所有擁有此標籤的系列仍將受到用戶訪問限制。", + "promote-tooltip": "「推廣」 指的是該標籤將對伺服器上的所有使用者可見,而不僅限於管理員帳戶。即使標籤被推廣,所有帶有此標籤的系列仍然會受到使用者存取限制的約束。", "promote-label": "推廣", "title": "編輯 {{collectionName}} 收藏", "last-sync-tooltip": "Kavita 每天都會與上游内容提供商同步資料。" @@ -1181,9 +1244,9 @@ "highly-rated": "高評價" }, "settings": { - "kavitaplus-section-title": "{{settings.admin-kavitaplus}}", + "kavitaplus-section-title": "Kavita+", "admin-logs": "日誌", - "admin-media-issues": "媒體問題", + "admin-media-issues": "檔案問題", "admin-email": "電子郵件", "admin-tasks": "任務", "theme": "主題", @@ -1194,7 +1257,7 @@ "server-section-title": "伺服器", "admin-libraries": "書庫", "admin-system": "系統", - "admin-kavitaplus": "Kavita+", + "admin-kavitaplus": "授權", "mal-stack-import": "MAL Stack", "account-section-title": "帳戶", "admin-statistics": "統計", @@ -1206,7 +1269,13 @@ "scrobbling": "Scrobbling", "customize": "自訂", "cbl-import": "CBL 書單", - "info-section-title": "資訊" + "info-section-title": "資訊", + "reading-profiles": "閱讀設定檔", + "admin-manage-tokens": "管理用戶憑證", + "scrobble-holds": "暫停 Scrobble", + "admin-email-history": "電子郵件紀錄", + "admin-metadata": "管理元資料", + "admin-matched-metadata": "匹配的元資料" }, "collection-detail": { "item-count": "{{common.item-count}}", @@ -1233,15 +1302,17 @@ }, "reading-list-item": { "remove": "{{common.remove}}", - "read": "{{common.read}}" + "read": "{{common.read}}", + "released-label": "發布日期:{{date}}" }, "stream-list-item": { "remove": "{{common.remove}}", "external-source": "外部來源", "provided": "已提供的", - "smart-filter": "智慧篩選", + "smart-filter": "智慧篩選器", "library": "書庫", - "load-filter": "載入篩選器" + "load-filter": "載入篩選器", + "delete": "{{common.delete}}" }, "reading-list-detail": { "item-count": "{{common.item-count}}", @@ -1253,7 +1324,19 @@ "remove-read": "移除已閱讀", "continue": "繼續閱讀", "no-data": "未添加任何項目", - "incognito-alt": "(無痕模式)" + "incognito-alt": "(無痕模式)", + "cover-artists-title": "{{metadata-fields.cover-artists-title}}", + "details-tab": "{{series-detail.details-tab}}", + "publishers-title": "{{metadata-fields.publishers-title}}", + "storyline-tab": "{{series-detail.storyline-tab}}", + "more-alt": "{{series-detail.more-alt}}", + "writers-title": "{{metadata-fields.writers-title}}", + "edit-alt": "{{common.edit}}", + "items-title": "項目", + "date-range-title": "日期範圍", + "edit-label": "編輯模式", + "reorder-alt": "重新排序項目", + "dnd-warning": "在行動裝置上或當書單超過 100 項時,將無法使用拖放功能。" }, "events-widget": { "close": "{{common.close}}", @@ -1295,7 +1378,7 @@ }, "nav-header": { "help": "{{common.help}}", - "all-filters": "智慧篩選", + "all-filters": "智慧篩選器", "close": "{{common.close}}", "search-series-alt": "搜尋系列", "search-alt": "搜尋…", @@ -1306,7 +1389,10 @@ "skip-alt": "跳轉至主要內容", "no-data": "未找到結果", "announcements": "公告", - "nav-link-header": "導航選項" + "nav-link-header": "導航選項", + "browse-genres": "瀏覽類型", + "browse-tags": "瀏覽標籤", + "person-aka-status": "匹配到別名" }, "promoted-icon": { "promoted": "{{common.promoted}}" @@ -1359,7 +1445,7 @@ }, "import-cbl-modal": { "close": "{{common.close}}", - "cbl-repo": "您可以在社群repo中找到更多書單。", + "cbl-repo": "您可以在社群repository中找到更多書單。", "validate-warning": "CBL 檔案存在問題,匯入已停止。請修正這些問題後再試一次。", "import-description": "請先匯入一個 .cbl 檔案,Kavita 將在匯入之前進行多項檢查,某些步驟可能會因檔案問題而停止進行。", "validate-no-issue-description": "未發現 CBL 問題,請按“下一步”。", @@ -1378,15 +1464,15 @@ "help-label": "{{common.help}}" }, "manga-reader": { - "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", - "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}", + "auto-close-menu-label": "{{manage-reading-profiles.auto-close-menu-label}}", + "emulate-comic-book-label": "{{manage-reading-profiles.emulate-comic-book-label}}", "series-progress": "系列進度:{{percentage}}", "back": "返回", "left-to-right-alt": "從左到右", - "reading-direction-tooltip": "閱讀方向: ", + "reading-direction-tooltip": "翻頁方向: ", "reading-mode-tooltip": "閱讀模式", "width": "寬度", - "off": "關", + "off": "{{reader-settings.off}}", "original": "原始大小", "enable-comic-book-label": "模擬漫畫書", "width-override-label": "寬度控制", @@ -1414,7 +1500,13 @@ "fullscreen": "全螢幕", "image-scaling-label": "圖片縮放", "no-next-chapter": "已經到最後了", - "incognito-alt": "無痕模式已開啟,點擊以關閉。" + "incognito-alt": "無痕模式已開啟,點擊以關閉。", + "update-parent": "{{reader-settings.update-parent}}", + "create-new-tooltip": "{{reader-settings.create-new-tooltip}}", + "loading": "{{reader-settings.loading}}", + "create-new": "{{reader-settings.create-new}}", + "reading-profile-updated": "閱讀設定已更新", + "reading-profile-promoted": "閱讀設定檔已推廣" }, "metadata-filter": { "filter-title": "{{common.filter}}", @@ -1435,7 +1527,7 @@ "publisher-label": "出版社", "cover-artist-label": "封面藝術家", "writer-label": "作者", - "penciller-label": "線稿師", + "penciller-label": "草稿師", "inker-label": "勾線師", "read": "已讀", "series-name-tooltip": "系列名稱將依據名稱、排序名稱或本地化名稱進行篩選", @@ -1596,8 +1688,8 @@ "cover-image-tab": "{{tabs.cover-tab}}", "tasks-tab": "{{tabs.tasks-tab}}", "info-tab": "{{tabs.info-tab}}", - "pages-label": "{{edit-chapter-modal.pages-count}}", - "words-label": "{{edit-chapter-modal.length-title}}", + "pages-label": "{{edit-chapter-modal.pages-label}}", + "words-label": "{{edit-chapter-modal.words-label}}", "pages-count": "{{edit-chapter-modal.pages-count}}", "words-count": "{{edit-chapter-modal.words-count}}", "reading-time-label": "{{edit-chapter-modal.reading-time-label}}", @@ -1716,36 +1808,38 @@ "invalid-confirmation-url": "無效的確認網址" }, "customize-dashboard-modal": { - "title-smart-filters": "智慧篩選", + "title-smart-filters": "智慧篩選器", "close": "{{common.close}}", - "smart-filters": "智慧篩選", + "smart-filters": "智慧篩選器", "help": "{{common.help}}", "external-sources": "外部來源", "title-external-sources": "外部來源", - "title-dashboard": "自訂控制台", - "title-sidenav": "自訂側邊欄", - "sidenav": "側邊欄", - "dashboard": "控制台" + "title-dashboard": "自訂儀表板", + "title-sidenav": "自訂側邊導覽列", + "sidenav": "側邊導覽列", + "dashboard": "儀表板", + "description": "您可以透過重新排序、切換可見性,以及將智慧篩選器/外部來源綁定到首頁或側邊導覽列,來自訂 Kavita 的各個方面。" }, "customize-dashboard-streams": { "save": "{{common.save}}", "add": "{{common.add}}", "filter": "{{common.filter}}", "clear": "{{common.clear}}", - "no-data": "所有智慧篩選已添加到控制台,或尚未創建任何篩選。" + "no-data": "所有智慧篩選器都已新增至儀表板,或者尚未建立任何篩選器。", + "smart-filter-title": "{{customize-dashboard-modal.title-smart-filters}}" }, "customize-sidenav-streams": { "save": "{{common.save}}", "add": "{{common.add}}", "filter": "{{common.filter}}", "clear": "{{common.clear}}", - "smart-filters-title": "智慧篩選", + "smart-filters-title": "智慧篩選器", "external-sources-title": "{{customize-dashboard-modal.external-sources}}", - "reorder-when-filter-present": "You cannot reorder items via drag & drop while a filter is present. Use {{customize-sidenav-streams.order-numbers-label}}", + "reorder-when-filter-present": "當有篩選器存在時,您無法透過拖曳來重新排序項目。請使用「{{customize-sidenav-streams.order-numbers-label}}」來排序", "order-numbers-label": "{{reading-list-detail.order-numbers-label}}", "bulk-mode-label": "批量選擇", - "no-data-external-source": "所有外部來源已添加到側邊欄,或尚未創建任何來源。", - "no-data": "所有智慧篩選已加入側邊欄,或尚未創建任何篩選。" + "no-data-external-source": "所有外部來源已添加到側邊導覽列,或尚未創建任何來源。", + "no-data": "所有智慧篩選器已加入側邊導覽列,或尚未創建任何篩選器。" }, "manage-external-sources": { "clear": "{{common.clear}}", @@ -1760,7 +1854,15 @@ "filter": "{{common.filter}}", "clear": "{{common.clear}}", "errored": "篩選器中存在編碼錯誤的,您需要重新創建它。", - "no-data": "尚未建立智慧篩選" + "no-data": "尚未建立智慧篩選器", + "edit": "{{common.edit}}", + "save": "{{common.save}}", + "edit-smart-filter": "編輯 {{name}}", + "name-label": "名稱", + "cancel": "{{common.cancel}}", + "filter-name-unique": "智慧篩選器名稱必須唯一", + "close": "{{common.close}}", + "required-field": "智慧篩選器需要一個名稱" }, "edit-external-source-item": { "save": "{{common.save}}", @@ -1785,7 +1887,7 @@ "reading-lists": "{{side-nav.reading-lists}}", "bookmarks": "{{side-nav.bookmarks}}", "all-series": "{{side-nav.all-series}}", - "browse-authors": "{{side-nav.browse-authors}}" + "browse-authors": "{{side-nav.browse-people}}" }, "filter-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -1815,7 +1917,7 @@ "translators": "翻譯", "user-rating": "用戶評分", "file-path": "檔案路徑", - "penciller": "線稿師", + "penciller": "草稿師", "languages": "語言", "path": "路徑", "libraries": "書庫", @@ -1833,8 +1935,8 @@ "download-tooltip": "下載至裝置", "scan-library": "掃描書庫", "import-mal-stack": "匯入 MAL Stack", - "remove-from-on-deck-tooltip": "從“繼續閱讀”中移除系列", - "remove-from-on-deck": "從“繼續閱讀”中移除", + "remove-from-on-deck-tooltip": "從「繼續閱讀」中移除系列", + "remove-from-on-deck": "從「繼續閱讀」中移除", "customize": "自訂", "import-cbl-tooltip": "從 CBL 檔建立書單", "import-cbl": "匯入CBL", @@ -1885,7 +1987,19 @@ "details-tooltip": "TODO", "view-series": "查看系列", "read": "閱讀", - "view-series-tooltip": "TODO" + "view-series-tooltip": "TODO", + "reading-profiles": "閱讀設定檔", + "set-reading-profile": "設定閱讀偏好", + "set-reading-profile-tooltip": "將閱讀設定綁定到此書庫", + "reorder": "重新排序", + "merge": "合併", + "cleared-profile": "清除閱讀設定", + "match": "匹配", + "match-tooltip": "手動將系列與 Kavita+ 匹配", + "rename": "重新命名", + "clear-reading-profile-tooltip": "清除此書庫的閱讀設定", + "clear-reading-profile": "清除閱讀設定", + "rename-tooltip": "重新命名智能篩選器" }, "common": { "volume-num": "卷", @@ -1895,7 +2009,7 @@ "issue-hash-num": "期#", "book-nums": "書籍", "chapter-nums": "章節", - "issue-nums": "期刊", + "issue-nums": "期數", "read": "閱讀", "clear": "清空", "filter": "篩選", @@ -1926,7 +2040,10 @@ "add": "新增", "select-all": "全選", "chapter-num": "章", - "author-count": "{{num}}位作者" + "author-count": "{{num}}位作者", + "issue-count": "{{num}} 期", + "chapter-count": "{{num}} 章", + "no-data": "無資料" }, "download-indicator": { "progress": "{{percentage}}%已下載" @@ -1952,14 +2069,14 @@ "delete-device": "您確定要刪除該裝置嗎?", "device-updated": "裝置已更新", "device-created": "裝置已創建", - "k+-delete-key": "這將僅刪除 Kavita 的授權金鑰並允許顯示購買連結。這不會取消您的訂閱!僅在支援指示下使用此功能!", + "k+-delete-key": "這將僅刪除 Kavita 的授權金鑰並允許顯示購買連結。 這不會取消您的訂閱! 僅在支援指示下使用此功能!請先使用「管理」功能取消訂閱。", "stack-imported": "Stack 已匯入", "reading-list-imported": "書單已匯入", "list-doesnt-exist": "此列表不存在", "reading-list-updated": "已更新書單", "reset-base-url": "基礎網址重設", "file-send-to": "檔案已透過郵件發送給{{name}}", - "k+-license-saved": "授權密鑰已保存,但目前尚未生效。點擊檢查以重新驗證訂閱資訊,首次註冊可能需要一些時間。", + "k+-license-saved": "授權金鑰已保存,但目前尚未生效。點擊檢查以重新驗證訂閱資訊,首次註冊可能需要一些時間。", "theme-missing": "當前主題已不存在,請刷新頁面。", "email-not-sent": "記錄的電子郵件不是有效的電子郵件,無法發送。相關連結已記錄在日誌中。管理員可以提供此連結以完成流程。", "k+-reset-key": "這會使之前您授權的註冊無效,並允許您重新註冊 Kavita。", @@ -1967,19 +2084,19 @@ "forced-scan-queued": "已開始強制掃描{{name}}", "change-email-no-email": "電子郵件已更新", "confirm-library-type-change": "更改書庫類型將觸發新的掃描,並使用不同的解析規則,這可能導致系列重新創建,因此您可能會丟失進度和書籤。您應該在這之前備份資料。您確定要繼續嗎?", - "smart-filter-deleted": "智慧篩選已刪除", + "smart-filter-deleted": "智慧篩選器已刪除", "confirm-delete-volume": "您確定要刪除這卷嗎?這不會修改硬碟上的檔案。", "reading-list-promoted": "書單已推廣", "reading-list-unpromoted": "書單已取消推廣", "confirm-delete-theme": "移除此主題將從硬碟中刪除它。在移除之前,您可以從臨時目錄中獲取它", - "mal-token-required": "需要 MAL 令牌,請在用戶設定中配置", + "mal-token-required": "需要 MAL 授權憑證,請在用戶設定中配置", "chapter-deleted": "章節已刪除", "volume-deleted": "卷已刪除", "regen-cover": "已將重新生成封面圖片的任務加入佇列", "reading-list-deleted": "已刪除書單", "library-deleted": "已將書庫 {{name}}移除", "library-created": "書庫創建成功,已開始掃描。", - "smart-filter-updated": "已創建/更新智慧篩選", + "smart-filter-updated": "已創建/更新智慧篩選器", "no-next-chapter": "找不到下一個{{entity}}", "refresh-covers-queued": "{{name}}的封面更新正在佇列中", "account-migration-complete": "帳戶遷移完成", @@ -2002,11 +2119,11 @@ "load-prev-chapter": "上一個 {{entity}} 已加載", "confirm-delete-user": "您確定要刪除此用戶嗎?", "confirm-delete-multiple-series": "您確定要刪除{{count}}個系列嗎?這不會修改硬碟上的檔案。", - "anilist-token-updated": "已更新 AniList 令牌", - "confirm-delete-smart-filter": "您確定要刪除此智慧篩選?", + "anilist-token-updated": "已更新 AniList 授權憑證", + "confirm-delete-smart-filter": "您確定要刪除此智慧篩選器?", "copied-to-clipboard": "已複製到剪貼簿", "confirm-library-delete": "您確定要刪除“{{name}}”嗎?此操作無法撤銷。", - "anilist-token-expired": "您的 AniList 令牌已過期。在您於用戶設定 > 帳戶中重新生成之前,將無法處理紀錄功能", + "anilist-token-expired": "您的 AniList 授權憑證已過期。在您於「用戶設定」>「帳戶」中重新生成之前,將無法處理紀錄功能", "confirm-delete-reading-list": "您確定要刪除書單嗎?此操作無法撤銷。", "select-files-warning": "您需要先選擇文件才能繼續操作", "entity-read": "{{name}}已標記為已讀", @@ -2036,7 +2153,7 @@ "email-sent-to-no-existing": "現有的電子郵件無效。相關連結已記錄在日誌中。請向管理員索取該連結以完成電子郵件變更。", "age-restriction-updated": "年齡限制已更新", "alert-bad-theme": "主題中有無效或不安全的CSS。請聯繫管理員進行修正,系統將預設為深色主題。", - "confirm-regen-covers": "刷新封面將強制重新計算所有封面圖像。這是一個高負載的操作。您確定要執行此操作,而不是使用掃描代替嗎?", + "confirm-regen-covers": "刷新封面將強制重新計算所有封面圖片。這是一個高負載的操作。您確定要執行此操作,而不是使用掃描代替嗎?", "delete-review": "您確定要刪除您的評論嗎?", "generate-colorscape-queued": "為{{name}}生成色彩主題已加入佇列", "confirm-delete-chapter": "您確定要刪除這個章節嗎?這不會修改硬碟上的檔案。", @@ -2046,7 +2163,15 @@ "bulk-covers": "刷新多個書庫的封面,是一項繁重的工作,可能需要很長時間,您確定要繼續嗎?", "person-image-downloaded": "已下載人物封面並套用。", "confirm-delete-multiple-chapters": "您確定要刪除 {{count}} 章節/卷嗎?這不會修改硬碟上的檔案。", - "bulk-delete-libraries": "您確定要刪除 {{count}} 個書庫嗎?" + "bulk-delete-libraries": "您確定要刪除 {{count}} 個書庫嗎?", + "confirm-delete-multiple-volumes": "您確定要刪除 {{count}} 個卷嗎?這不會修改磁碟上的檔案。", + "series-bound-to-reading-profile": "系列已綁定至閱讀設定 {{name}}", + "library-bound-to-reading-profile": "書庫已綁定至閱讀設定 {{name}}", + "scrobble-gen-init": "已將任務排入隊列,以根據過去的閱讀記錄和評分產生 scrobble 事件,並同步到已連接的服務。", + "series-added-want-to-read": "系列已加入「想讀」", + "webtoon-override": "偵測到圖片為條漫風格,正在切換到條漫模式。", + "external-match-rate-error": "Kavita 查詢 {{seriesName}} 時,已超出查詢頻率限制。請於 5 分鐘後再試。", + "match-success": "系列匹配正確" }, "changelog": { "installed": "已安裝", @@ -2062,7 +2187,7 @@ "collection-created-label": "創建者:{{owner}}" }, "tabs": { - "smart-filters-tab": "智慧篩選", + "smart-filters-tab": "智慧篩選器", "account-tab": "帳戶", "volumes-tab": "卷", "specials-tab": "特集", @@ -2115,7 +2240,8 @@ }, "card-detail-layout": { "total-items": "共 {{count}} 項", - "jumpkey-count": "{{count}} 個系列" + "jumpkey-count": "{{count}} 個系列", + "no-data": "{{common.no-data}}" }, "read-more": { "read-less": "收起", @@ -2196,7 +2322,10 @@ "release-year": "發行年份", "random": "隨機", "read-progress": "最近閱讀", - "average-rating": "平均評分" + "average-rating": "平均評分", + "person-name": "名稱", + "person-series-count": "系列數", + "person-chapter-count": "章數" }, "manage-system": { "version-title": "版本", @@ -2277,15 +2406,19 @@ "title": "Kavita+ 元數據分析", "completed-series-label": "已完成的系列", "complete": "所有系列都有元數據", - "no-data": "沒有資料" + "no-data": "{{common.no-data}}" }, "next-expected-card": { "title": "~{{date}}", "upcoming-title": "即將推出" }, "confirm": { - "alert": "警報", - "confirm": "確認" + "alert": "警示", + "confirm": "確認", + "cancel": "{{common.cancel}}", + "ok": "確定", + "info": "訊息", + "prompt": "問題" }, "day-breakdown": { "no-data": "尚未有進度,快來開始閱讀", @@ -2315,7 +2448,10 @@ "known-for-title": "代表作品", "individual-role-title": "作為{{role}}", "browse-person-title": "{{name}}的所有作品", - "browse-person-by-role-title": "{{name}}作為{{role}}的所有作品" + "browse-person-by-role-title": "{{name}}作為{{role}}的所有作品", + "anilist-url": "{{edit-person-modal.anilist-tooltip}}", + "no-info": "尚無此人物的相關資訊", + "aka-title": "又名 " }, "edit-person-modal": { "save": "{{common.save}}", @@ -2338,7 +2474,11 @@ "hardcover-tooltip": "https://hardcover.app/authors/{HardcoverId}", "download-coversdb": "從 CoversDB 下載", "asin-tooltip": "https://www.amazon.com/stores/J.K.-Rowling/author/{ASIN}", - "cover-image-description-extra": "或者,您可以從 CoversDB 下載封面(如果有)。" + "cover-image-description-extra": "或者,您可以從 CoversDB 下載封面(如果有)。", + "alias-overlap": "此別名已指向其他人物,或者與此人的姓名相同,建議考慮合併資料。", + "aliases-tooltip": "當一個系列被標記為某個人的別名時,系統會將該系列歸屬於現有的人物,而不是建立一個新人物。刪除別名後,您需要重新掃描該系列,變更才會生效。", + "aliases-label": "編輯別名", + "aliases-tab": "別名" }, "reviews": { "user-reviews-local": "本地評論", @@ -2346,6 +2486,332 @@ }, "review-modal": { "title": "編輯評論", - "review-label": "評論" + "review-label": "評論", + "close": "{{common.close}}", + "save": "{{common.save}}", + "delete": "{{common.delete}}", + "min-length": "評論至少需要 {{count}} 個字元", + "required": "{{validation.required-field}}" + }, + "manage-reading-profiles": { + "wiki-title": "wiki", + "no-selected": "未選擇設定檔", + "confirm": "您是否確定要刪除名為「{{name}}」的閱讀設定檔?", + "image-reader-settings-title": "圖片閱讀器", + "reading-direction-label": "翻頁方向", + "scaling-option-label": "縮放選項", + "scaling-option-tooltip": "如何將影像縮放到螢幕大小。", + "page-splitting-label": "頁面分割", + "reading-mode-label": "閱讀模式", + "reading-mode-tooltip": "將閱讀器更改為縱向翻頁、橫向翻頁,或設置為無限滾動", + "layout-mode-label": "排版模式", + "background-color-tooltip": "圖片閱讀器的背景顏色", + "layout-mode-tooltip": "將單個影像或兩個並排影像顯示到螢幕上", + "background-color-label": "背景顏色", + "auto-close-menu-label": "自動關閉選單", + "auto-close-menu-tooltip": "選單是否應自動關閉", + "show-screen-hints-label": "顯示螢幕提示", + "emulate-comic-book-label": "模擬書籍效果", + "swipe-to-paginate-tooltip": "是否允許透過滑動螢幕來翻到上一頁或下一頁", + "profiles-title": "您的閱讀設定檔", + "add-tooltip": "您的設定檔將在變更後儲存", + "make-default": "設為預設", + "default-profile": "預設", + "add": "{{common.add}}", + "swipe-to-paginate-label": "使用滑動手勢翻頁", + "width-override-tooltip": "覆寫閱讀器中圖片的寬度", + "reading-direction-tooltip": "點擊畫面哪個方向會翻到下一頁。「由右至左」意味著您單擊屏幕左側以移至下一頁。", + "page-splitting-tooltip": "如何拆分全寬圖片(即左右影像合併)", + "show-screen-hints-tooltip": "顯示一個覆蓋層,以幫助理解翻頁區域和方向", + "extra-tip": "您可以透過系列或書庫的操作選單,或是批次方式來指派閱讀設定檔。當您在閱讀器中變更設定時,系統會自動為該系列建立一個隱藏的設定檔,用來記住您的偏好(不適用於 PDF)。當您自行為該系列指派一個閱讀設定檔後,這個隱藏設定檔會被移除。更多資訊請參考", + "description": "由於不同系列可能需要不同的閱讀方式,建議您針對每個書庫或系列建立個別的閱讀設定檔,確保閱讀體驗一致且順暢。", + "selection-tip": "從清單中選擇,或點擊右上角建立新的設定檔", + "emulate-comic-book-tooltip": "套用陰影效果,以模擬實體書籍", + "width-override-label": "{{manga-reader.width-override-label}}", + "reset": "{{common.reset}}", + "delete": "{{common.delete}}", + "disable-width-override-label": "停用寬度覆寫", + "allow-auto-webtoon-reader-label": "自動條漫閱讀模式", + "tap-to-paginate-tooltip": "書籍閱讀器螢幕兩側是否允許點擊,以切換到上一頁或下一頁", + "tap-to-paginate-label": "點擊翻頁", + "color-theme-book-label": "顏色主題", + "layout-mode-book-label": "排版模式", + "layout-mode-book-tooltip": "選擇內容的顯示方式。「捲動」會依書籍原始排版顯示;「單欄」或「雙欄」會根據裝置高度調整,一頁顯示一或兩欄文字", + "line-height-book-label": "行距", + "line-height-book-tooltip": "書籍中行與行之間的間距大小", + "font-size-book-tooltip": "套用於書籍字體的縮放百分比", + "pdf-theme-label": "主題", + "pdf-scroll-mode-tooltip": "設定您瀏覽頁面的方式。可選擇垂直/水平捲動,或點擊翻頁(不捲動)", + "pdf-spread-mode-label": "跨頁模式", + "pdf-theme-tooltip": "閱讀器的顏色主題", + "allow-auto-webtoon-reader-tooltip": "如果頁面看起來像條漫,則會自動切換到條漫閱讀模式。可能會有誤判的情況發生。", + "immersive-mode-tooltip": "啟用後,點擊閱讀頁面時會隱藏選單,並開啟點擊翻頁功能", + "pdf-scroll-mode-label": "捲動模式", + "pdf-reader-settings-title": "PDF 閱讀器", + "pdf-spread-mode-tooltip": "選擇頁面顯示方式。可單頁顯示,或雙頁對開(奇數跨頁/偶數跨頁)", + "reading-profile-library-settings-title": "書庫", + "reading-profile-series-settings-title": "系列", + "disable-width-override-tooltip": "當您的螢幕寬度達到或小於所設定的中斷點時,防止寬度覆寫生效", + "book-reader-settings-title": "書籍閱讀器", + "immersive-mode-label": "沉浸模式", + "reading-direction-book-label": "翻頁方向", + "reading-direction-book-tooltip": "點擊畫面哪個方向會翻到下一頁。「由右至左」意味著您單擊屏幕左側以移至下一頁。", + "font-family-label": "字型", + "font-family-tooltip": "選擇要使用的字型。若選擇「Default」,將採用書籍內建字型", + "writing-style-label": "書寫方向", + "writing-style-tooltip": "變更文字的書寫方向。橫排為由左至右,直排為由上至下。", + "color-theme-book-tooltip": "設定要應用於書籍閱讀器內容和選單的顏色主題", + "font-size-book-label": "字體大小", + "margin-book-tooltip": "設定螢幕左右兩側的間距大小。但在行動裝置上,不管設定為多少,間距都會被強制設為 0。", + "margin-book-label": "邊距" + }, + "manage-matched-metadata": { + "matched-state-label": "匹配狀態", + "library-type": "書庫類型", + "unmatched-status-label": "未匹配", + "blacklist-status-label": "需手動匹配", + "title": "已匹配的元資料", + "description": "這裡會列出所有可以匹配外部元資料的書籍系列。Kavita 每天會自動預取或更新系列資料,每 24 小時最多處理 50 個系列。", + "status-header": "狀態", + "dont-match-status-label": "{{dont-match-label}}", + "all-status-label": "全部狀態", + "dont-match-label": "不匹配", + "no-data": "{{common.no-data}}", + "match-alt": "匹配 {{seriesName}}", + "library-name-header": "書庫", + "actions-header": "操作", + "series-name-header": "系列", + "valid-until-header": "下次重新整理", + "matched-status-label": "已匹配" + }, + "browse-people": { + "series-count": "{{common.series-count}}", + "roles-label": "角色", + "sort-label": "排序", + "issue-count-label": "作品數", + "series-count-label": "系列數", + "title": "瀏覽人物", + "author-count": "{{num}} 人", + "cover-image-description": "{{edit-series-modal.cover-image-description}}", + "issue-count": "{{common.issue-count}}", + "name-label": "名稱" + }, + "browse-genres": { + "series-count": "{{common.series-count}}", + "issue-count": "{{common.issue-count}}", + "title": "瀏覽類型", + "genre-count": "{{num}} 種類型" + }, + "browse-tags": { + "series-count": "{{common.series-count}}", + "issue-count": "{{common.issue-count}}", + "title": "瀏覽標籤", + "genre-count": "{{num}} 個標籤" + }, + "bulk-set-reading-profile-modal": { + "loading": "{{common.loading}}", + "close": "{{common.close}}", + "clear": "{{common.clear}}", + "create": "{{common.create}}", + "filter-label": "{{common.filter}}", + "no-data": "尚未建立收藏", + "title": "閱讀設定", + "bound": "綁定" + }, + "metadata-setting-field-pipe": { + "people": "{{tabs.people-tab}}", + "summary": "{{filter-field-pipe.summary}}", + "start-date": "{{manage-metadata-settings.enable-start-date-label}}", + "genres": "{{metadata-fields.genres-title}}", + "publication-status": "{{edit-series-modal.publication-status-title}}", + "tags": "{{metadata-fields.tags-title}}", + "localized-name": "{{edit-series-modal.localized-name-label}}", + "age-rating": "{{metadata-fields.age-rating-title}}", + "covers": "封面", + "chapter-title": "章節標題", + "chapter-covers": "章節封面", + "chapter-publisher": "章節{{person-role-pipe.publisher}}", + "chapter-release-date": "章節發行日期", + "chapter-summary": "章節摘要" + }, + "manage-metadata-settings": { + "enable-start-date-tooltip": "允許將系列的開始日期寫入系列資訊", + "whitelist-label": "白名單標籤", + "age-rating-mapping-title": "年齡分級對應", + "blacklist-tooltip": "清單中的任何項目都將從類型和標籤處理中移除。您可以在此處新增不想寫入的類型/標籤。請確保它們以逗號分隔。", + "enable-chapter-summary-label": "{{manage-metadata-settings.summary-label}}", + "enable-chapter-title-label": "標題", + "localized-name-tooltip": "允許在欄位解鎖時寫入譯名。Kavita 會盡力做出最佳判斷。", + "enable-chapter-release-date-tooltip": "允許寫入章節/期數的發行日期", + "enable-chapter-publisher-tooltip": "允許寫入章節/期數的出版社", + "enable-chapter-summary-tooltip": "{{manage-metadata-settings.summary-tooltip}}", + "enable-chapter-publisher-label": "出版社", + "enable-chapter-cover-tooltip": "允許設定章節/期數的封面", + "enable-chapter-cover-label": "章節封面", + "description": "Kavita+ 具備下載並將部分元資料寫入資料庫的功能。此頁面可讓您切換哪些內容在啟用範圍內。", + "enabled-label": "啟用元資料下載", + "summary-label": "摘要", + "summary-tooltip": "允許在欄位解鎖時寫入摘要。", + "localized-name-label": "系列譯名", + "derive-publication-status-tooltip": "允許根據總章節/卷數來推斷出版狀態。", + "derive-publication-status-label": "出版狀態", + "enable-relations-label": "關聯性", + "enable-cover-image-label": "封面圖片", + "enable-cover-image-tooltip": "允許 Kavita 為系列寫入封面圖片", + "enable-genres-label": "類型", + "enable-genres-tooltip": "允許寫入系列類型。", + "enable-tags-tooltip": "允許寫入系列標籤。", + "enable-tags-label": "標籤", + "blacklist-label": "黑名單類型/標籤", + "first-last-name-tooltip": "確保人物的姓名寫法為名字在前,姓氏在後", + "enabled-tooltip": "允許 Kavita 下載元資料並寫入其資料庫。", + "enable-relations-tooltip": "允許 新增系列關聯性。", + "enable-people-label": "人物", + "enable-chapter-title-tooltip": "允許寫入章節/期數標題", + "enable-chapter-release-date-label": "發行日期", + "enable-start-date-label": "開始日期", + "enable-people-tooltip": "允許 新增.人物(角色、作者等)。所有人物都包含圖片。", + "whitelist-tooltip": "只允許寫入此清單中的特定字串作為標籤。 請確保這些字串以逗號分隔。", + "age-rating-mapping-description": "左側清單中的任何字串,如果存在於類型或標籤中,將設定該系列的年齡分級。", + "remove-source-tag-label": "移除來源標籤", + "genre": "類型", + "field-mapping-description": "設定規則,將在「類型 (Genre)」或「標籤 (Tag)」欄位中找到的特定字串,對應到新的「類型」或「標籤」,並可選擇是否從原始清單中移除。此功能僅在啟用「類型/標籤」寫入時才適用。", + "overrides-label": "覆寫", + "chapter-header": "章節欄位", + "field-mapping-title": "欄位對應", + "add-age-rating-mapping-label": "新增年齡分級對應", + "tag": "標籤", + "add-field-mapping-label": "新增欄位對應", + "first-last-name-label": "名/姓 命名方式", + "overrides-description": "允許 Kavita 寫入鎖定的欄位。", + "person-roles-label": "角色" + }, + "merge-person-modal": { + "title": "{{personName}}", + "close": "{{common.close}}", + "save": "{{common.save}}", + "src": "合併人物", + "alias-title": "新別名", + "known-for-title": "代表作", + "merge-warning": "如果您繼續,選定的人物將被移除。該人物的姓名將被新增為別名,且其所有角色都將被轉移。" + }, + "new-version-modal": { + "developer": "{{changelog.developer}}", + "theme": "{{changelog.theme}}", + "title": "Kavita 已更新!", + "added": "{{changelog.added}}", + "changed": "{{changelog.changed}}", + "description": "Kavita 有新版本囉!請重新整理以更新。", + "fixed": "{{changelog.fixed}}", + "known-issues": "{{changelog.known-issues}}", + "api": "{{changelog.api}}", + "close": "{{common.close}}", + "removed": "{{changelog.removed}}", + "refresh": "重新整理" + }, + "changelog-update-item": { + "download": "下載", + "fixed": "修正", + "developer": "開發者", + "theme": "主題", + "removed": "已移除", + "published-label": "發佈時間: ", + "api": "API", + "installed": "{{changelog.installed}}", + "feature-requests": "功能請求", + "added": "新增", + "changed": "變更", + "known-issues": "已知問題" + }, + "manage-user-tokens": { + "mal-header": "MAL", + "no-data": "{{common.no-data}}", + "token-set-label": "設定授權憑證", + "anilist-header": "AniList", + "description": "有設定同步憑證的用戶,可能需要不時地更新憑證。如果用戶有設定電子郵件,並且提供的是有效信箱,Kavita 會在憑證需要更新時自動發送電子郵件通知。", + "username-header": "用戶名", + "expires-label": "到期日: {{date}}" + }, + "email-history": { + "no-data": "{{common.no-data}}", + "not-sent-tooltip": "未發送", + "sent-tooltip": "發送", + "date-header": "發送日期", + "description": "這裡可以查看 Kavita 寄出的所有電子郵件以及收件使用者。", + "user-header": "發送到", + "sent-header": "成功", + "template-header": "範本" + }, + "match-series-modal": { + "save": "{{common.save}}", + "close": "{{common.close}}", + "query-label": "查詢", + "query-tooltip": "請輸入系列名稱,或是 AniList/MyAnimeList/ComicBookRoundup 的網址。若輸入網址,系統將會直接查詢對應資料。", + "dont-match-tooltip": "將此系列排除於匹配與 Scrobble 之外", + "no-results": "無法找到匹配項目。請嘗試新增支援的網址後再重試。", + "search": "搜尋", + "title": "匹配 {{seriesName}}", + "description": "選擇一個匹配項目以重新連結 Kavita+ 的元資料並重新產生 Scrobble 事件。若選擇 「不匹配」,則可禁止 Kavita 對該項目進行元資料匹配與 Scrobble 同步。", + "dont-match-label": "不匹配" + }, + "match-series-result-item": { + "volume-count": "{{server-stats.volume-count}}", + "chapter-count": "{{common.chapter-count}}", + "issue-count": "{{common.issue-count}}", + "releasing": "發行中", + "details": "查看頁面", + "updating-metadata-status": "更新元資料" + }, + "role-localized-pipe": { + "admin": "管理員", + "bookmark": "書籤", + "change-restriction": "更改限制條件", + "login": "登入", + "promote": "推廣", + "change-password": "更改密碼", + "download": "下載", + "read-only": "只讀" + }, + "log-level-pipe": { + "critical": "Critical", + "debug": "Debug", + "information": "Information", + "trace": "Trace", + "warning": "Warning" + }, + "browse-title-pipe": { + "age-rating": "分級為 {{value}}", + "publication-status": "{{value}} 部作品", + "tag": "含有標籤 {{value}}", + "user-rating": "{{value}} 星評分", + "translator": "由 {{value}} 翻譯", + "publisher": "由 {{value}} 出版", + "artist": "由 {{value}} 繪製", + "editor": "由 {{value}} 編輯", + "penciller": "由 {{value}} 繪製草稿", + "genre": "含有類型 {{value}}", + "format": "{{value}} 的格式", + "writer": "由 {{value}}編劇", + "team": "{{value}} 團隊", + "location": "位於 {{value}}", + "character": "含有角色 {{value}}", + "inker": "由 {{value}} 勾線", + "library": "於 {{value}} 書庫中", + "release-year": "於 {{value}} 發行", + "letterer": "由 {{value}} 設計字體", + "colorist": "由 {{value}} 上色", + "imprint": "{{value}} 出版品牌" + }, + "generic-filter-field-pipe": { + "person-name": "名稱", + "person-role": "角色", + "person-chapter-count": "章數", + "person-series-count": "系列數" + }, + "breakpoint-pipe": { + "desktop": "桌面版", + "never": "從不", + "mobile": "手機", + "tablet": "平板" } } From 2c9ad049ada234f70739cadd1aa5ca3794027462 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Fri, 27 Jun 2025 11:35:09 -0500 Subject: [PATCH 03/15] Fixed a bug where files tagged as specials (ComicInfo) were not propagating the title field to the UI. --- API.Tests/Services/ScannerServiceTests.cs | 31 +++++++++++++++++++ .../Series with Just a Special - Manga.json | 3 ++ API/Entities/Chapter.cs | 2 +- API/Helpers/Builders/ChapterBuilder.cs | 4 +-- API/Services/Plus/ExternalMetadataService.cs | 2 ++ .../Tasks/Scanner/Parser/DefaultParser.cs | 1 + 6 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 API.Tests/Services/Test Data/ScannerService/TestCases/Series with Just a Special - Manga.json diff --git a/API.Tests/Services/ScannerServiceTests.cs b/API.Tests/Services/ScannerServiceTests.cs index acc0345b1..838bd8d07 100644 --- a/API.Tests/Services/ScannerServiceTests.cs +++ b/API.Tests/Services/ScannerServiceTests.cs @@ -972,4 +972,35 @@ public class ScannerServiceTests : AbstractDbTest Assert.Contains(postLib.Series, x => x.Name == "Immoral Guild"); Assert.Contains(postLib.Series, x => x.Name == "Futoku No Guild"); } + + #region Just Parsing Tests + [Fact] + public async Task Special_WithTitle_HasTitleSet() + { + const string testcase = "Series with Just a Special - Manga.json"; + + // Get the first file and generate a ComicInfo + var infos = new Dictionary(); + infos.Add("just a bunch of junk.cbz", new ComicInfo() + { + Series = "test", + Title = "Special Title", + Format = "Special" + }); + + var library = await _scannerHelper.GenerateScannerData(testcase, infos); + + var scanner = _scannerHelper.CreateServices(); + await scanner.ScanLibrary(library.Id); + + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + + // Validate that there are 2 series + Assert.NotNull(postLib); + + + Assert.Equal("test", postLib.Series.First().Name); + Assert.Equal("Special Title", postLib.Series.First().Volumes[0].Chapters[0].Title); + } + #endregion } diff --git a/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Just a Special - Manga.json b/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Just a Special - Manga.json new file mode 100644 index 000000000..7b1c2b359 --- /dev/null +++ b/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Just a Special - Manga.json @@ -0,0 +1,3 @@ +[ + "test/just a bunch of junk.cbz" +] \ No newline at end of file diff --git a/API/Entities/Chapter.cs b/API/Entities/Chapter.cs index 61a70c8a2..50ace09ed 100644 --- a/API/Entities/Chapter.cs +++ b/API/Entities/Chapter.cs @@ -188,7 +188,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage MinNumber = Parser.DefaultChapterNumber; MaxNumber = Parser.DefaultChapterNumber; } - Title = (IsSpecial && info.Format is MangaFormat.Epub or MangaFormat.Pdf) + Title = IsSpecial ? info.Title : Parser.RemoveExtensionIfSupported(Range); diff --git a/API/Helpers/Builders/ChapterBuilder.cs b/API/Helpers/Builders/ChapterBuilder.cs index d9976d92a..e5891659f 100644 --- a/API/Helpers/Builders/ChapterBuilder.cs +++ b/API/Helpers/Builders/ChapterBuilder.cs @@ -39,9 +39,7 @@ public class ChapterBuilder : IEntityBuilder return builder.WithNumber(Parser.RemoveExtensionIfSupported(info.Chapters)!) .WithRange(specialTreatment ? info.Filename : info.Chapters) - .WithTitle(specialTreatment && info.Format is MangaFormat.Epub or MangaFormat.Pdf - ? info.Title - : specialTitle ?? string.Empty) + .WithTitle(info.Title ?? string.Empty) // NOTE: This originally had duplicate logic. I moved it further up int the pipeline. .WithIsSpecial(specialTreatment); } diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index 3c8023671..28deb2c97 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -1412,6 +1412,8 @@ public class ExternalMetadataService : IExternalMetadataService } await DownloadSeriesCovers(series, externalMetadata.CoverUrl); + + return true; } diff --git a/API/Services/Tasks/Scanner/Parser/DefaultParser.cs b/API/Services/Tasks/Scanner/Parser/DefaultParser.cs index 687617fd7..01e1cceea 100644 --- a/API/Services/Tasks/Scanner/Parser/DefaultParser.cs +++ b/API/Services/Tasks/Scanner/Parser/DefaultParser.cs @@ -128,6 +128,7 @@ public abstract class DefaultParser(IDirectoryService directoryService) : IDefau info.IsSpecial = true; info.Chapters = Parser.DefaultChapter; info.Volumes = Parser.SpecialVolume; + info.Title = !string.IsNullOrEmpty(info.ComicInfo.Title) ? info.ComicInfo.Title : info.Title; } // Patch is SeriesSort from ComicInfo From d909e03bafc5d9a0c0a3b91c63602a24487c4bcc Mon Sep 17 00:00:00 2001 From: Fesaa <77553571+Fesaa@users.noreply.github.com> Date: Sat, 28 Jun 2025 18:45:02 +0200 Subject: [PATCH 04/15] A few more bug fixes (#3876) Co-authored-by: Joseph Milazzo --- API/Controllers/ChapterController.cs | 7 + API/Controllers/KoreaderController.cs | 3 +- API/Controllers/SeriesController.cs | 2 + API/Controllers/UploadController.cs | 4 + API/Data/DataContext.cs | 22 +- ...162548_TrackKavitaPlusMetadata.Designer.cs | 3721 +++++++++++++++++ .../20250626162548_TrackKavitaPlusMetadata.cs | 40 + .../Migrations/DataContextModelSnapshot.cs | 12 + API/Data/Repositories/PersonRepository.cs | 4 +- API/Data/Repositories/SeriesRepository.cs | 6 + API/Entities/Chapter.cs | 8 +- API/Entities/Interfaces/IHasKPlusMetadata.cs | 12 + API/Entities/Metadata/SeriesMetadata.cs | 7 +- API/Extensions/IHasKPlusMetadataExtensions.cs | 21 + API/Services/KoreaderService.cs | 3 +- API/Services/Plus/ExternalMetadataService.cs | 90 +- API/Services/SeriesService.cs | 9 + UI/Web/src/app/_services/series.service.ts | 4 - .../external-rating.component.ts | 1 + .../rating-modal/rating-modal.component.ts | 7 +- .../_services/filter-utilities.service.ts | 2 +- 21 files changed, 3940 insertions(+), 45 deletions(-) create mode 100644 API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.Designer.cs create mode 100644 API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.cs create mode 100644 API/Entities/Interfaces/IHasKPlusMetadata.cs create mode 100644 API/Extensions/IHasKPlusMetadataExtensions.cs diff --git a/API/Controllers/ChapterController.cs b/API/Controllers/ChapterController.cs index 8de26cf97..94535d499 100644 --- a/API/Controllers/ChapterController.cs +++ b/API/Controllers/ChapterController.cs @@ -9,6 +9,7 @@ using API.DTOs; using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; +using API.Entities.MetadataMatching; using API.Entities.Person; using API.Extensions; using API.Helpers; @@ -208,6 +209,7 @@ public class ChapterController : BaseApiController if (chapter.AgeRating != dto.AgeRating) { chapter.AgeRating = dto.AgeRating; + chapter.KPlusOverrides.Remove(MetadataSettingField.AgeRating); } dto.Summary ??= string.Empty; @@ -215,6 +217,7 @@ public class ChapterController : BaseApiController if (chapter.Summary != dto.Summary.Trim()) { chapter.Summary = dto.Summary.Trim(); + chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterSummary); } if (chapter.Language != dto.Language) @@ -230,11 +233,13 @@ public class ChapterController : BaseApiController if (chapter.TitleName != dto.TitleName) { chapter.TitleName = dto.TitleName; + chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterTitle); } if (chapter.ReleaseDate != dto.ReleaseDate) { chapter.ReleaseDate = dto.ReleaseDate; + chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterReleaseDate); } if (!string.IsNullOrEmpty(dto.ISBN) && ArticleNumberHelper.IsValidIsbn10(dto.ISBN) || @@ -333,6 +338,8 @@ public class ChapterController : BaseApiController _unitOfWork ); + // TODO: Only remove field if changes were made + chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterPublisher); // Update publishers await PersonHelper.UpdateChapterPeopleAsync( chapter, diff --git a/API/Controllers/KoreaderController.cs b/API/Controllers/KoreaderController.cs index 1ce5e3202..8c4c41585 100644 --- a/API/Controllers/KoreaderController.cs +++ b/API/Controllers/KoreaderController.cs @@ -6,6 +6,7 @@ using API.Data; using API.Data.Repositories; using API.DTOs.Koreader; using API.Entities; +using API.Extensions; using API.Services; using Kavita.Common; using Microsoft.AspNetCore.Identity; @@ -94,7 +95,7 @@ public class KoreaderController : BaseApiController { var userId = await GetUserId(apiKey); var response = await _koreaderService.GetProgress(ebookHash, userId); - _logger.LogDebug("Koreader response progress: {Progress}", response.Progress); + _logger.LogDebug("Koreader response progress for User ({UserId}): {Progress}", userId, response.Progress.Sanitize()); return Ok(response); } diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index 84eacc838..389ff33a7 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -14,6 +14,7 @@ using API.DTOs.Recommendation; using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; +using API.Entities.MetadataMatching; using API.Extensions; using API.Helpers; using API.Services; @@ -224,6 +225,7 @@ public class SeriesController : BaseApiController needsRefreshMetadata = true; series.CoverImage = null; series.CoverImageLocked = false; + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.Covers); _logger.LogDebug("[SeriesCoverImageBug] Setting Series Cover Image to null: {SeriesId}", series.Id); series.ResetColorScape(); diff --git a/API/Controllers/UploadController.cs b/API/Controllers/UploadController.cs index 4b935a1bf..9652ba494 100644 --- a/API/Controllers/UploadController.cs +++ b/API/Controllers/UploadController.cs @@ -6,6 +6,7 @@ using API.Data; using API.Data.Repositories; using API.DTOs.Uploads; using API.Entities.Enums; +using API.Entities.MetadataMatching; using API.Extensions; using API.Services; using API.Services.Tasks.Metadata; @@ -112,8 +113,10 @@ public class UploadController : BaseApiController series.CoverImage = filePath; series.CoverImageLocked = lockState; + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.Covers); _imageService.UpdateColorScape(series); _unitOfWork.SeriesRepository.Update(series); + _unitOfWork.SeriesRepository.Update(series.Metadata); if (_unitOfWork.HasChanges()) { @@ -277,6 +280,7 @@ public class UploadController : BaseApiController chapter.CoverImage = filePath; chapter.CoverImageLocked = lockState; + chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterCovers); _unitOfWork.ChapterRepository.Update(chapter); var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(chapter.VolumeId); if (volume != null) diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs index aa8b67283..7d529b1da 100644 --- a/API/Data/DataContext.cs +++ b/API/Data/DataContext.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using API.DTOs.KavitaPlus.Metadata; using API.Entities; using API.Entities.Enums; using API.Entities.Enums.UserPreferences; @@ -18,7 +17,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Diagnostics; namespace API.Data; @@ -43,7 +41,7 @@ public sealed class DataContext : IdentityDbContext ServerSetting { get; set; } = null!; public DbSet AppUserPreferences { get; set; } = null!; public DbSet SeriesMetadata { get; set; } = null!; - [Obsolete] + [Obsolete("Use AppUserCollection")] public DbSet CollectionTag { get; set; } = null!; public DbSet AppUserBookmark { get; set; } = null!; public DbSet ReadingList { get; set; } = null!; @@ -72,7 +70,7 @@ public sealed class DataContext : IdentityDbContext ExternalSeriesMetadata { get; set; } = null!; public DbSet ExternalRecommendation { get; set; } = null!; public DbSet ManualMigrationHistory { get; set; } = null!; - [Obsolete] + [Obsolete("Use IsBlacklisted field on Series")] public DbSet SeriesBlacklist { get; set; } = null!; public DbSet AppUserCollection { get; set; } = null!; public DbSet ChapterPeople { get; set; } = null!; @@ -286,6 +284,22 @@ public sealed class DataContext : IdentityDbContext JsonSerializer.Serialize(v, JsonSerializerOptions.Default), v => JsonSerializer.Deserialize>(v, JsonSerializerOptions.Default) ?? new List()) .HasColumnType("TEXT"); + + builder.Entity() + .Property(sm => sm.KPlusOverrides) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions.Default) ?? + new List()) + .HasColumnType("TEXT") + .HasDefaultValue(new List()); + builder.Entity() + .Property(sm => sm.KPlusOverrides) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions.Default), + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions.Default) ?? new List()) + .HasColumnType("TEXT") + .HasDefaultValue(new List()); } #nullable enable diff --git a/API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.Designer.cs b/API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.Designer.cs new file mode 100644 index 000000000..b72239924 --- /dev/null +++ b/API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.Designer.cs @@ -0,0 +1,3721 @@ +// +using System; +using System.Collections.Generic; +using API.Data; +using API.Entities.MetadataMatching; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250626162548_TrackKavitaPlusMetadata")] + partial class TrackKavitaPlusMetadata + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("AgeRestriction") + .HasColumnType("INTEGER"); + + b.Property("AgeRestrictionIncludeUnknowns") + .HasColumnType("INTEGER"); + + b.Property("AniListAccessToken") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("ConfirmationToken") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("HasRunScrobbleEventGeneration") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("LastActiveUtc") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MalAccessToken") + .HasColumnType("TEXT"); + + b.Property("MalUserName") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventGenerationRan") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Page") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserBookmark"); + }); + + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserChapterRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastSyncUtc") + .HasColumnType("TEXT"); + + b.Property("MissingSeriesFromSource") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("SourceUrl") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSourceCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserCollection"); + }); + + modelBuilder.Entity("API.Entities.AppUserDashboardStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("IsProvided") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("SmartFilterId") + .HasColumnType("INTEGER"); + + b.Property("StreamType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(4); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SmartFilterId"); + + b.HasIndex("Visible"); + + b.ToTable("AppUserDashboardStream"); + }); + + modelBuilder.Entity("API.Entities.AppUserExternalSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Host") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserExternalSource"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserOnDeckRemoval"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAutomaticWebtoonReaderDetection") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AniListScrobblingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); + + b.Property("BlurUnreadSummaries") + .HasColumnType("INTEGER"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderImmersiveMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLayoutMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("BookReaderWritingStyle") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + + b.Property("CollapseSeriesRelationships") + .HasColumnType("INTEGER"); + + b.Property("EmulateBook") + .HasColumnType("INTEGER"); + + b.Property("GlobalPageLayoutMode") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("LayoutMode") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("en"); + + b.Property("NoTransitions") + .HasColumnType("INTEGER"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("PdfScrollMode") + .HasColumnType("INTEGER"); + + b.Property("PdfSpreadMode") + .HasColumnType("INTEGER"); + + b.Property("PdfTheme") + .HasColumnType("INTEGER"); + + b.Property("PromptForDownloadSize") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("ShareReviews") + .HasColumnType("INTEGER"); + + b.Property("ShowScreenHints") + .HasColumnType("INTEGER"); + + b.Property("SwipeToPaginate") + .HasColumnType("INTEGER"); + + b.Property("ThemeId") + .HasColumnType("INTEGER"); + + b.Property("WantToReadSync") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.HasKey("Id"); + + b.HasIndex("AppUserId") + .IsUnique(); + + b.HasIndex("ThemeId"); + + b.ToTable("AppUserPreferences"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PagesRead") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserProgresses"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserReadingProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAutomaticWebtoonReaderDetection") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderImmersiveMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLayoutMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("BookReaderWritingStyle") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + + b.Property("DisableWidthOverride") + .HasColumnType("INTEGER"); + + b.Property("EmulateBook") + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("LayoutMode") + .HasColumnType("INTEGER"); + + b.Property("LibraryIds") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("PdfScrollMode") + .HasColumnType("INTEGER"); + + b.Property("PdfSpreadMode") + .HasColumnType("INTEGER"); + + b.Property("PdfTheme") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("SeriesIds") + .HasColumnType("TEXT"); + + b.Property("ShowScreenHints") + .HasColumnType("INTEGER"); + + b.Property("SwipeToPaginate") + .HasColumnType("INTEGER"); + + b.Property("WidthOverride") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserReadingProfiles"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserSideNavStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSourceId") + .HasColumnType("INTEGER"); + + b.Property("IsProvided") + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("SmartFilterId") + .HasColumnType("INTEGER"); + + b.Property("StreamType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(5); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SmartFilterId"); + + b.HasIndex("Visible"); + + b.ToTable("AppUserSideNavStream"); + }); + + modelBuilder.Entity("API.Entities.AppUserSmartFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Filter") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserSmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PageNumber") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserTableOfContent"); + }); + + modelBuilder.Entity("API.Entities.AppUserWantToRead", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserWantToRead"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("AlternateCount") + .HasColumnType("INTEGER"); + + b.Property("AlternateNumber") + .HasColumnType("TEXT"); + + b.Property("AlternateSeries") + .HasColumnType("TEXT"); + + b.Property("AverageExternalRating") + .HasColumnType("REAL"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("ISBN") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("ISBNLocked") + .HasColumnType("INTEGER"); + + b.Property("ImprintLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("IsSpecial") + .HasColumnType("INTEGER"); + + b.Property("KPlusOverrides") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("[]"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("LocationLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MaxNumber") + .HasColumnType("REAL"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinNumber") + .HasColumnType("REAL"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("Range") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("ReleaseDateLocked") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SeriesGroup") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("REAL"); + + b.Property("SortOrderLocked") + .HasColumnType("INTEGER"); + + b.Property("StoryArc") + .HasColumnType("TEXT"); + + b.Property("StoryArcNumber") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TeamLocked") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TitleName") + .HasColumnType("TEXT"); + + b.Property("TitleNameLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("VolumeId"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("API.Entities.CollectionTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Promoted") + .IsUnique(); + + b.ToTable("CollectionTag"); + }); + + modelBuilder.Entity("API.Entities.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastUsed") + .HasColumnType("TEXT"); + + b.Property("LastUsedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("API.Entities.EmailHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("DeliveryStatus") + .HasColumnType("TEXT"); + + b.Property("EmailTemplate") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("SendDate") + .HasColumnType("TEXT"); + + b.Property("Sent") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("Sent", "AppUserId", "EmailTemplate", "SendDate"); + + b.ToTable("EmailHistory"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("FolderPath"); + }); + + modelBuilder.Entity("API.Entities.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Genre"); + }); + + modelBuilder.Entity("API.Entities.History.ManualMigrationHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("RanAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ManualMigrationHistory"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowMetadataMatching") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AllowScrobbling") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EnableMetadata") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("FolderWatching") + .HasColumnType("INTEGER"); + + b.Property("IncludeInDashboard") + .HasColumnType("INTEGER"); + + b.Property("IncludeInRecommended") + .HasColumnType("INTEGER"); + + b.Property("IncludeInSearch") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("ManageCollections") + .HasColumnType("INTEGER"); + + b.Property("ManageReadingLists") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryExcludePattern", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Pattern") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryExcludePattern"); + }); + + modelBuilder.Entity("API.Entities.LibraryFileTypeGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FileTypeGroup") + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryFileTypeGroup"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("KoreaderHash") + .HasColumnType("TEXT"); + + b.Property("LastFileAnalysis") + .HasColumnType("TEXT"); + + b.Property("LastFileAnalysisUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.MediaError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MediaError"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Authority") + .HasColumnType("INTEGER"); + + b.Property("AverageScore") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ProviderUrl") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("ExternalRating"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRecommendation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("ExternalRecommendation"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Authority") + .HasColumnType("INTEGER"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("BodyJustText") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("INTEGER"); + + b.Property("RawBody") + .HasColumnType("TEXT"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("SiteUrl") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("TotalVotes") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("ExternalReview"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("AverageExternalRating") + .HasColumnType("INTEGER"); + + b.Property("CbrId") + .HasColumnType("INTEGER"); + + b.Property("GoogleBooksId") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("ValidUntilUtc") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.ToTable("ExternalSeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastChecked") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("SeriesBlacklist"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("ImprintLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("KPlusOverrides") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("[]"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("LocationLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxCount") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatusLocked") + .HasColumnType("INTEGER"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYear") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYearLocked") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TeamLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.HasIndex("Id", "SeriesId") + .IsUnique(); + + b.ToTable("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RelationKind") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("TargetSeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.HasIndex("TargetSeriesId"); + + b.ToTable("SeriesRelation"); + }); + + modelBuilder.Entity("API.Entities.MetadataFieldMapping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DestinationType") + .HasColumnType("INTEGER"); + + b.Property("DestinationValue") + .HasColumnType("TEXT"); + + b.Property("ExcludeFromSource") + .HasColumnType("INTEGER"); + + b.Property("MetadataSettingsId") + .HasColumnType("INTEGER"); + + b.Property("SourceType") + .HasColumnType("INTEGER"); + + b.Property("SourceValue") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MetadataSettingsId"); + + b.ToTable("MetadataFieldMapping"); + }); + + modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRatingMappings") + .HasColumnType("TEXT"); + + b.Property("Blacklist") + .HasColumnType("TEXT"); + + b.Property("EnableChapterCoverImage") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterPublisher") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterReleaseDate") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterSummary") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterTitle") + .HasColumnType("INTEGER"); + + b.Property("EnableCoverImage") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("EnableGenres") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalizedName") + .HasColumnType("INTEGER"); + + b.Property("EnablePeople") + .HasColumnType("INTEGER"); + + b.Property("EnablePublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("EnableRelationships") + .HasColumnType("INTEGER"); + + b.Property("EnableStartDate") + .HasColumnType("INTEGER"); + + b.Property("EnableSummary") + .HasColumnType("INTEGER"); + + b.Property("EnableTags") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("FirstLastPeopleNaming") + .HasColumnType("INTEGER"); + + b.Property("Overrides") + .HasColumnType("TEXT"); + + b.PrimitiveCollection("PersonRoles") + .HasColumnType("TEXT"); + + b.Property("Whitelist") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MetadataSettings"); + }); + + modelBuilder.Entity("API.Entities.Person.ChapterPeople", b => + { + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("KavitaPlusConnection") + .HasColumnType("INTEGER"); + + b.Property("OrderWeight") + .HasColumnType("INTEGER"); + + b.HasKey("ChapterId", "PersonId", "Role"); + + b.HasIndex("PersonId"); + + b.ToTable("ChapterPeople"); + }); + + modelBuilder.Entity("API.Entities.Person.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("Asin") + .HasColumnType("TEXT"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("HardcoverId") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.PersonAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("NormalizedAlias") + .HasColumnType("TEXT"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PersonId"); + + b.ToTable("PersonAlias"); + }); + + modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b => + { + b.Property("SeriesMetadataId") + .HasColumnType("INTEGER"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("KavitaPlusConnection") + .HasColumnType("INTEGER"); + + b.Property("OrderWeight") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.HasKey("SeriesMetadataId", "PersonId", "Role"); + + b.HasIndex("PersonId"); + + b.ToTable("SeriesMetadataPeople"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EndingMonth") + .HasColumnType("INTEGER"); + + b.Property("EndingYear") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("StartingMonth") + .HasColumnType("INTEGER"); + + b.Property("StartingYear") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("ReadingList"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("ReadingListId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.HasIndex("ReadingListId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("VolumeId"); + + b.ToTable("ReadingListItem"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId1") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ScrobbleEventId1"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleError"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterNumber") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("ErrorDetails") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("IsErrored") + .HasColumnType("INTEGER"); + + b.Property("IsProcessed") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("ProcessDateUtc") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("ReviewBody") + .HasColumnType("TEXT"); + + b.Property("ReviewTitle") + .HasColumnType("TEXT"); + + b.Property("ScrobbleEventType") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeNumber") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("LibraryId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleEvent"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleHold"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("DontMatch") + .HasColumnType("INTEGER"); + + b.Property("FolderPath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("IsBlacklisted") + .HasColumnType("INTEGER"); + + b.Property("LastChapterAdded") + .HasColumnType("TEXT"); + + b.Property("LastChapterAddedUtc") + .HasColumnType("TEXT"); + + b.Property("LastFolderScanned") + .HasColumnType("TEXT"); + + b.Property("LastFolderScannedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("LocalizedName") + .HasColumnType("TEXT"); + + b.Property("LocalizedNameLocked") + .HasColumnType("INTEGER"); + + b.Property("LowestFolderPath") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedLocalizedName") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("SortNameLocked") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.ServerSetting", b => + { + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSetting"); + }); + + modelBuilder.Entity("API.Entities.ServerStatistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterCount") + .HasColumnType("INTEGER"); + + b.Property("FileCount") + .HasColumnType("INTEGER"); + + b.Property("GenreCount") + .HasColumnType("INTEGER"); + + b.Property("PersonCount") + .HasColumnType("INTEGER"); + + b.Property("SeriesCount") + .HasColumnType("INTEGER"); + + b.Property("TagCount") + .HasColumnType("INTEGER"); + + b.Property("UserCount") + .HasColumnType("INTEGER"); + + b.Property("VolumeCount") + .HasColumnType("INTEGER"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ServerStatistics"); + }); + + modelBuilder.Entity("API.Entities.SiteTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .HasColumnType("TEXT"); + + b.Property("CompatibleVersion") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("GitHubPath") + .HasColumnType("TEXT"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PreviewUrls") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ShaHash") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SiteTheme"); + }); + + modelBuilder.Entity("API.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Tag"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LookupName") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MaxNumber") + .HasColumnType("REAL"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinNumber") + .HasColumnType("REAL"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + modelBuilder.Entity("AppUserCollectionSeries", b => + { + b.Property("CollectionsId") + .HasColumnType("INTEGER"); + + b.Property("ItemsId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsId", "ItemsId"); + + b.HasIndex("ItemsId"); + + b.ToTable("AppUserCollectionSeries"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("ChapterGenre"); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("ChapterTag"); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.Property("CollectionTagsId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionTagsId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("CollectionTagSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => + { + b.Property("ExternalRatingsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalRatingsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalRatingExternalSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalRecommendationExternalSeriesMetadata", b => + { + b.Property("ExternalRecommendationsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalRecommendationsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalRecommendationExternalSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalReviewExternalSeriesMetadata", b => + { + b.Property("ExternalReviewsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalReviewsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalReviewExternalSeriesMetadata"); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("GenresId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("GenreSeriesMetadata"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("SeriesMetadatasId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("SeriesMetadataTag"); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Bookmarks") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ChapterRatings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Ratings") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserCollection", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Collections") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserDashboardStream", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("DashboardStreams") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUserSmartFilter", "SmartFilter") + .WithMany() + .HasForeignKey("SmartFilterId"); + + b.Navigation("AppUser"); + + b.Navigation("SmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserExternalSource", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ExternalSources") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithOne("UserPreferences") + .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.SiteTheme", "Theme") + .WithMany() + .HasForeignKey("ThemeId"); + + b.Navigation("AppUser"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Progresses") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", null) + .WithMany("UserProgress") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany("Progress") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Ratings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany("Ratings") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserReadingProfile", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ReadingProfiles") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.HasOne("API.Entities.AppRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUser", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("API.Entities.AppUserSideNavStream", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("SideNavStreams") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUserSmartFilter", "SmartFilter") + .WithMany() + .HasForeignKey("SmartFilterId"); + + b.Navigation("AppUser"); + + b.Navigation("SmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserSmartFilter", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("SmartFilters") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("TableOfContents") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserWantToRead", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("WantToRead") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.HasOne("API.Entities.Volume", "Volume") + .WithMany("Chapters") + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Device", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Devices") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.EmailHistory", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Folders") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryExcludePattern", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("LibraryExcludePatterns") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryFileTypeGroup", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("LibraryFileTypes") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Files") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalRatings") + .HasForeignKey("ChapterId"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalReviews") + .HasForeignKey("ChapterId"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("ExternalSeriesMetadata") + .HasForeignKey("API.Entities.Metadata.ExternalSeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesBlacklist", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("Metadata") + .HasForeignKey("API.Entities.Metadata.SeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Relations") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "TargetSeries") + .WithMany("RelationOf") + .HasForeignKey("TargetSeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + + b.Navigation("TargetSeries"); + }); + + modelBuilder.Entity("API.Entities.MetadataFieldMapping", b => + { + b.HasOne("API.Entities.MetadataMatching.MetadataSettings", "MetadataSettings") + .WithMany("FieldMappings") + .HasForeignKey("MetadataSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MetadataSettings"); + }); + + modelBuilder.Entity("API.Entities.Person.ChapterPeople", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("People") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("ChapterPeople") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.PersonAlias", b => + { + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("Aliases") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b => + { + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("SeriesMetadataPeople") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", "SeriesMetadata") + .WithMany("People") + .HasForeignKey("SeriesMetadataId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Person"); + + b.Navigation("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ReadingLists") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.ReadingList", "ReadingList") + .WithMany("Items") + .HasForeignKey("ReadingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Volume", "Volume") + .WithMany() + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("ReadingList"); + + b.Navigation("Series"); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.HasOne("API.Entities.Scrobble.ScrobbleEvent", "ScrobbleEvent") + .WithMany() + .HasForeignKey("ScrobbleEventId1"); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ScrobbleEvent"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", "Library") + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Library"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ScrobbleHolds") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Series") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Volumes") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("AppUserCollectionSeries", b => + { + b.HasOne("API.Entities.AppUserCollection", null) + .WithMany() + .HasForeignKey("CollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany() + .HasForeignKey("ItemsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("AppUsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", null) + .WithMany() + .HasForeignKey("LibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.HasOne("API.Entities.CollectionTag", null) + .WithMany() + .HasForeignKey("CollectionTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalRating", null) + .WithMany() + .HasForeignKey("ExternalRatingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalRecommendationExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalRecommendation", null) + .WithMany() + .HasForeignKey("ExternalRecommendationsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalReviewExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalReview", null) + .WithMany() + .HasForeignKey("ExternalReviewsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("Bookmarks"); + + b.Navigation("ChapterRatings"); + + b.Navigation("Collections"); + + b.Navigation("DashboardStreams"); + + b.Navigation("Devices"); + + b.Navigation("ExternalSources"); + + b.Navigation("Progresses"); + + b.Navigation("Ratings"); + + b.Navigation("ReadingLists"); + + b.Navigation("ReadingProfiles"); + + b.Navigation("ScrobbleHolds"); + + b.Navigation("SideNavStreams"); + + b.Navigation("SmartFilters"); + + b.Navigation("TableOfContents"); + + b.Navigation("UserPreferences"); + + b.Navigation("UserRoles"); + + b.Navigation("WantToRead"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Navigation("ExternalRatings"); + + b.Navigation("ExternalReviews"); + + b.Navigation("Files"); + + b.Navigation("People"); + + b.Navigation("Ratings"); + + b.Navigation("UserProgress"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + + b.Navigation("LibraryExcludePatterns"); + + b.Navigation("LibraryFileTypes"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Navigation("People"); + }); + + modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b => + { + b.Navigation("FieldMappings"); + }); + + modelBuilder.Entity("API.Entities.Person.Person", b => + { + b.Navigation("Aliases"); + + b.Navigation("ChapterPeople"); + + b.Navigation("SeriesMetadataPeople"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Navigation("ExternalSeriesMetadata"); + + b.Navigation("Metadata"); + + b.Navigation("Progress"); + + b.Navigation("Ratings"); + + b.Navigation("RelationOf"); + + b.Navigation("Relations"); + + b.Navigation("Volumes"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Navigation("Chapters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.cs b/API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.cs new file mode 100644 index 000000000..ac253e0a8 --- /dev/null +++ b/API/Data/Migrations/20250626162548_TrackKavitaPlusMetadata.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Data.Migrations +{ + /// + public partial class TrackKavitaPlusMetadata : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "KPlusOverrides", + table: "SeriesMetadata", + type: "TEXT", + nullable: true, + defaultValue: "[]"); + + migrationBuilder.AddColumn( + name: "KPlusOverrides", + table: "Chapter", + type: "TEXT", + nullable: true, + defaultValue: "[]"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "KPlusOverrides", + table: "SeriesMetadata"); + + migrationBuilder.DropColumn( + name: "KPlusOverrides", + table: "Chapter"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index eb786865b..106a86b4a 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -1,6 +1,8 @@ // using System; +using System.Collections.Generic; using API.Data; +using API.Entities.MetadataMatching; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -957,6 +959,11 @@ namespace API.Data.Migrations b.Property("IsSpecial") .HasColumnType("INTEGER"); + b.Property("KPlusOverrides") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("[]"); + b.Property("Language") .HasColumnType("TEXT"); @@ -1683,6 +1690,11 @@ namespace API.Data.Migrations b.Property("InkerLocked") .HasColumnType("INTEGER"); + b.Property("KPlusOverrides") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("[]"); + b.Property("Language") .HasColumnType("TEXT"); diff --git a/API/Data/Repositories/PersonRepository.cs b/API/Data/Repositories/PersonRepository.cs index 26045c74c..76ae94735 100644 --- a/API/Data/Repositories/PersonRepository.cs +++ b/API/Data/Repositories/PersonRepository.cs @@ -387,8 +387,8 @@ public class PersonRepository : IPersonRepository return await _context.Person .Includes(includes) - .Where(p => EF.Functions.Like(p.Name, $"%{searchQuery}%") - || p.Aliases.Any(pa => EF.Functions.Like(pa.Alias, $"%{searchQuery}%"))) + .Where(p => EF.Functions.Like(p.NormalizedName, $"%{searchQuery}%") + || p.Aliases.Any(pa => EF.Functions.Like(pa.NormalizedAlias, $"%{searchQuery}%"))) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index e2eab0976..0c4b8350a 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -82,6 +82,7 @@ public interface ISeriesRepository void Attach(Series series); void Attach(SeriesRelation relation); void Update(Series series); + void Update(SeriesMetadata seriesMetadata); void Remove(Series series); void Remove(IEnumerable series); void Detach(Series series); @@ -219,6 +220,11 @@ public class SeriesRepository : ISeriesRepository _context.Entry(series).State = EntityState.Modified; } + public void Update(SeriesMetadata seriesMetadata) + { + _context.Entry(seriesMetadata).State = EntityState.Modified; + } + public void Remove(Series series) { _context.Series.Remove(series); diff --git a/API/Entities/Chapter.cs b/API/Entities/Chapter.cs index 61a70c8a2..fe3646943 100644 --- a/API/Entities/Chapter.cs +++ b/API/Entities/Chapter.cs @@ -4,13 +4,14 @@ using System.Globalization; using API.Entities.Enums; using API.Entities.Interfaces; using API.Entities.Metadata; +using API.Entities.MetadataMatching; using API.Entities.Person; using API.Extensions; using API.Services.Tasks.Scanner.Parser; namespace API.Entities; -public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage +public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage, IHasKPlusMetadata { public int Id { get; set; } /// @@ -126,6 +127,11 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage public string WebLinks { get; set; } = string.Empty; public string ISBN { get; set; } = string.Empty; + /// + /// Tracks which metadata has been set by K+ + /// + public IList KPlusOverrides { get; set; } = []; + /// /// (Kavita+) Average rating from Kavita+ metadata /// diff --git a/API/Entities/Interfaces/IHasKPlusMetadata.cs b/API/Entities/Interfaces/IHasKPlusMetadata.cs new file mode 100644 index 000000000..062afd7e1 --- /dev/null +++ b/API/Entities/Interfaces/IHasKPlusMetadata.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using API.Entities.MetadataMatching; + +namespace API.Entities.Interfaces; + +public interface IHasKPlusMetadata +{ + /// + /// Tracks which metadata has been set by K+ + /// + public IList KPlusOverrides { get; set; } +} diff --git a/API/Entities/Metadata/SeriesMetadata.cs b/API/Entities/Metadata/SeriesMetadata.cs index 46e7241f5..8bb33fdc0 100644 --- a/API/Entities/Metadata/SeriesMetadata.cs +++ b/API/Entities/Metadata/SeriesMetadata.cs @@ -4,13 +4,14 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using API.Entities.Enums; using API.Entities.Interfaces; +using API.Entities.MetadataMatching; using API.Entities.Person; using Microsoft.EntityFrameworkCore; namespace API.Entities.Metadata; [Index(nameof(Id), nameof(SeriesId), IsUnique = true)] -public class SeriesMetadata : IHasConcurrencyToken +public class SeriesMetadata : IHasConcurrencyToken, IHasKPlusMetadata { public int Id { get; set; } @@ -42,6 +43,10 @@ public class SeriesMetadata : IHasConcurrencyToken /// /// This is not populated from Chapters of the Series public string WebLinks { get; set; } = string.Empty; + /// + /// Tracks which metadata has been set by K+ + /// + public IList KPlusOverrides { get; set; } = []; #region Locks diff --git a/API/Extensions/IHasKPlusMetadataExtensions.cs b/API/Extensions/IHasKPlusMetadataExtensions.cs new file mode 100644 index 000000000..84e35adc4 --- /dev/null +++ b/API/Extensions/IHasKPlusMetadataExtensions.cs @@ -0,0 +1,21 @@ +using API.Entities.Interfaces; +using API.Entities.MetadataMatching; + +namespace API.Extensions; + +public static class IHasKPlusMetadataExtensions +{ + + public static bool HasSetKPlusMetadata(this IHasKPlusMetadata hasKPlusMetadata, MetadataSettingField field) + { + return hasKPlusMetadata.KPlusOverrides.Contains(field); + } + + public static void AddKPlusOverride(this IHasKPlusMetadata hasKPlusMetadata, MetadataSettingField field) + { + if (hasKPlusMetadata.KPlusOverrides.Contains(field)) return; + + hasKPlusMetadata.KPlusOverrides.Add(field); + } + +} diff --git a/API/Services/KoreaderService.cs b/API/Services/KoreaderService.cs index 69b3948ed..a38e8c468 100644 --- a/API/Services/KoreaderService.cs +++ b/API/Services/KoreaderService.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using API.Data; using API.DTOs.Koreader; using API.DTOs.Progress; +using API.Extensions; using API.Helpers; using API.Helpers.Builders; using Kavita.Common; @@ -39,7 +40,7 @@ public class KoreaderService : IKoreaderService /// public async Task SaveProgress(KoreaderBookDto koreaderBookDto, int userId) { - _logger.LogDebug("Saving Koreader progress for {UserId}: {KoreaderProgress}", userId, koreaderBookDto.Progress); + _logger.LogDebug("Saving Koreader progress for User ({UserId}): {KoreaderProgress}", userId, koreaderBookDto.Progress.Sanitize()); var file = await _unitOfWork.MangaFileRepository.GetByKoreaderHash(koreaderBookDto.Document); if (file == null) throw new KavitaException(await _localizationService.Translate(userId, "file-missing")); diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index 3c8023671..0777e1baa 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -16,6 +16,7 @@ using API.DTOs.Scrobbling; using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; +using API.Entities.Interfaces; using API.Entities.Metadata; using API.Entities.MetadataMatching; using API.Entities.Person; @@ -526,6 +527,7 @@ public class ExternalMetadataService : IExternalMetadataService if (madeMetadataModification) { _unitOfWork.SeriesRepository.Update(series); + _unitOfWork.SeriesRepository.Update(series.Metadata); } } catch (Exception ex) @@ -782,7 +784,7 @@ public class ExternalMetadataService : IExternalMetadataService if (externalCharacters == null || externalCharacters.Count == 0) return false; - if (series.Metadata.CharacterLocked && !settings.HasOverride(MetadataSettingField.People)) + if (series.Metadata.CharacterLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.People)) { return false; } @@ -849,7 +851,7 @@ public class ExternalMetadataService : IExternalMetadataService } } - + series.Metadata.AddKPlusOverride(MetadataSettingField.People); return true; } @@ -864,7 +866,7 @@ public class ExternalMetadataService : IExternalMetadataService if (upstreamArtists.Count == 0) return false; - if (series.Metadata.CoverArtistLocked && !settings.HasOverride(MetadataSettingField.People)) + if (series.Metadata.CoverArtistLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.People)) { return false; } @@ -907,6 +909,7 @@ public class ExternalMetadataService : IExternalMetadataService await DownloadAndSetPersonCovers(upstreamArtists); + series.Metadata.AddKPlusOverride(MetadataSettingField.People); return true; } @@ -920,7 +923,7 @@ public class ExternalMetadataService : IExternalMetadataService if (upstreamWriters.Count == 0) return false; - if (series.Metadata.WriterLocked && !settings.HasOverride(MetadataSettingField.People)) + if (series.Metadata.WriterLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.People)) { return false; } @@ -963,7 +966,7 @@ public class ExternalMetadataService : IExternalMetadataService await _unitOfWork.CommitAsync(); await DownloadAndSetPersonCovers(upstreamWriters); - + series.Metadata.AddKPlusOverride(MetadataSettingField.People); return true; } @@ -973,7 +976,7 @@ public class ExternalMetadataService : IExternalMetadataService if (!settings.EnableTags || processedTags.Count == 0) return false; - if (series.Metadata.TagsLocked && !settings.HasOverride(MetadataSettingField.Tags)) + if (series.Metadata.TagsLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.Tags)) { return false; } @@ -990,6 +993,11 @@ public class ExternalMetadataService : IExternalMetadataService madeModification = true; }, () => series.Metadata.TagsLocked = true); + if (madeModification) + { + series.Metadata.AddKPlusOverride(MetadataSettingField.Tags); + } + return madeModification; } @@ -1014,7 +1022,7 @@ public class ExternalMetadataService : IExternalMetadataService if (!settings.EnableGenres || processedGenres.Count == 0) return false; - if (series.Metadata.GenresLocked && !settings.HasOverride(MetadataSettingField.Genres)) + if (series.Metadata.GenresLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.Genres)) { return false; } @@ -1035,6 +1043,12 @@ public class ExternalMetadataService : IExternalMetadataService { if (series.Metadata.Genres.FirstOrDefault(g => g.NormalizedTitle == genre.NormalizedTitle) != null) continue; series.Metadata.Genres.Add(genre); + madeModification = true; + } + + if (madeModification) + { + series.Metadata.AddKPlusOverride(MetadataSettingField.Genres); } return madeModification; @@ -1044,7 +1058,7 @@ public class ExternalMetadataService : IExternalMetadataService { if (!settings.EnablePublicationStatus) return false; - if (series.Metadata.PublicationStatusLocked && !settings.HasOverride(MetadataSettingField.PublicationStatus)) + if (series.Metadata.PublicationStatusLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.PublicationStatus)) { return false; } @@ -1058,6 +1072,7 @@ public class ExternalMetadataService : IExternalMetadataService series.Metadata.PublicationStatus = status; series.Metadata.PublicationStatusLocked = true; + series.Metadata.AddKPlusOverride(MetadataSettingField.PublicationStatus); return true; } catch (Exception ex) @@ -1071,7 +1086,7 @@ public class ExternalMetadataService : IExternalMetadataService private bool UpdateAgeRating(Series series, MetadataSettingsDto settings, IEnumerable allExternalTags) { - if (series.Metadata.AgeRatingLocked && !settings.HasOverride(MetadataSettingField.AgeRating)) + if (series.Metadata.AgeRatingLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.AgeRating)) { return false; } @@ -1087,6 +1102,7 @@ public class ExternalMetadataService : IExternalMetadataService if (series.Metadata.AgeRating <= ageRating) { series.Metadata.AgeRating = ageRating; + series.Metadata.AddKPlusOverride(MetadataSettingField.AgeRating); return true; } } @@ -1127,7 +1143,10 @@ public class ExternalMetadataService : IExternalMetadataService madeModification = UpdateChapterTitle(chapter, settings, potentialMatch.Title, series.Name) || madeModification; madeModification = UpdateChapterSummary(chapter, settings, potentialMatch.Summary) || madeModification; madeModification = UpdateChapterReleaseDate(chapter, settings, potentialMatch.ReleaseDate) || madeModification; - madeModification = await UpdateChapterPublisher(chapter, settings, potentialMatch.Publisher) || madeModification; + + var hasUpdatedPublisher = await UpdateChapterPublisher(chapter, settings, potentialMatch.Publisher); + if (hasUpdatedPublisher) chapter.AddKPlusOverride(MetadataSettingField.ChapterPublisher); + madeModification = hasUpdatedPublisher || madeModification; madeModification = await UpdateChapterPeople(chapter, settings, PersonRole.CoverArtist, potentialMatch.Artists) || madeModification; madeModification = await UpdateChapterPeople(chapter, settings, PersonRole.Writer, potentialMatch.Writers) || madeModification; @@ -1245,17 +1264,18 @@ public class ExternalMetadataService : IExternalMetadataService if (string.IsNullOrEmpty(summary)) return false; - if (chapter.SummaryLocked && !settings.HasOverride(MetadataSettingField.ChapterSummary)) + if (chapter.SummaryLocked && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterSummary)) { return false; } - if (!string.IsNullOrWhiteSpace(summary) && !settings.HasOverride(MetadataSettingField.ChapterSummary)) + if (!string.IsNullOrWhiteSpace(summary) && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterSummary)) { return false; } chapter.Summary = StringHelper.RemoveSourceInDescription(StringHelper.SquashBreaklines(summary)); + chapter.AddKPlusOverride(MetadataSettingField.ChapterSummary); return true; } @@ -1265,17 +1285,18 @@ public class ExternalMetadataService : IExternalMetadataService if (string.IsNullOrEmpty(title)) return false; - if (chapter.TitleNameLocked && !settings.HasOverride(MetadataSettingField.ChapterTitle)) + if (chapter.TitleNameLocked && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterTitle)) { return false; } - if (!title.Contains(seriesName) && !settings.HasOverride(MetadataSettingField.ChapterTitle)) + if (!title.Contains(seriesName) && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterTitle)) { return false; } chapter.TitleName = title; + chapter.AddKPlusOverride(MetadataSettingField.ChapterTitle); return true; } @@ -1285,17 +1306,18 @@ public class ExternalMetadataService : IExternalMetadataService if (releaseDate == null || releaseDate == DateTime.MinValue) return false; - if (chapter.ReleaseDateLocked && !settings.HasOverride(MetadataSettingField.ChapterReleaseDate)) + if (chapter.ReleaseDateLocked && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterReleaseDate)) { return false; } - if (!settings.HasOverride(MetadataSettingField.ChapterReleaseDate)) + if (!HasForceOverride(settings, chapter, MetadataSettingField.ChapterReleaseDate)) { return false; } chapter.ReleaseDate = releaseDate.Value; + chapter.AddKPlusOverride(MetadataSettingField.ChapterReleaseDate); return true; } @@ -1305,12 +1327,12 @@ public class ExternalMetadataService : IExternalMetadataService if (string.IsNullOrEmpty(publisher)) return false; - if (chapter.PublisherLocked && !settings.HasOverride(MetadataSettingField.ChapterPublisher)) + if (chapter.PublisherLocked && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterPublisher)) { return false; } - if (!string.IsNullOrWhiteSpace(publisher) && !settings.HasOverride(MetadataSettingField.ChapterPublisher)) + if (!string.IsNullOrWhiteSpace(publisher) && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterPublisher)) { return false; } @@ -1332,7 +1354,7 @@ public class ExternalMetadataService : IExternalMetadataService if (string.IsNullOrEmpty(coverUrl)) return false; - if (chapter.CoverImageLocked && !settings.HasOverride(MetadataSettingField.ChapterCovers)) + if (chapter.CoverImageLocked && !HasForceOverride(settings, chapter, MetadataSettingField.ChapterCovers)) { return false; } @@ -1343,6 +1365,7 @@ public class ExternalMetadataService : IExternalMetadataService } await DownloadChapterCovers(chapter, coverUrl); + chapter.AddKPlusOverride(MetadataSettingField.ChapterCovers); return true; } @@ -1352,7 +1375,7 @@ public class ExternalMetadataService : IExternalMetadataService if (staff?.Count == 0) return false; - if (chapter.IsPersonRoleLocked(role) && !settings.HasOverride(MetadataSettingField.People)) + if (chapter.IsPersonRoleLocked(role) && !HasForceOverride(settings, chapter, MetadataSettingField.People)) { return false; } @@ -1401,7 +1424,7 @@ public class ExternalMetadataService : IExternalMetadataService if (string.IsNullOrEmpty(externalMetadata.CoverUrl)) return false; - if (series.CoverImageLocked && !settings.HasOverride(MetadataSettingField.Covers)) + if (series.CoverImageLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.Covers)) { return false; } @@ -1412,6 +1435,7 @@ public class ExternalMetadataService : IExternalMetadataService } await DownloadSeriesCovers(series, externalMetadata.CoverUrl); + series.Metadata.AddKPlusOverride(MetadataSettingField.Covers); return true; } @@ -1422,17 +1446,18 @@ public class ExternalMetadataService : IExternalMetadataService if (!externalMetadata.StartDate.HasValue) return false; - if (series.Metadata.ReleaseYearLocked && !settings.HasOverride(MetadataSettingField.StartDate)) + if (series.Metadata.ReleaseYearLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.StartDate)) { return false; } - if (series.Metadata.ReleaseYear != 0 && !settings.HasOverride(MetadataSettingField.StartDate)) + if (series.Metadata.ReleaseYear != 0 && !HasForceOverride(settings, series.Metadata, MetadataSettingField.StartDate)) { return false; } series.Metadata.ReleaseYear = externalMetadata.StartDate.Value.Year; + series.Metadata.AddKPlusOverride(MetadataSettingField.StartDate); return true; } @@ -1440,12 +1465,12 @@ public class ExternalMetadataService : IExternalMetadataService { if (!settings.EnableLocalizedName) return false; - if (series.LocalizedNameLocked && !settings.HasOverride(MetadataSettingField.LocalizedName)) + if (series.LocalizedNameLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.LocalizedName)) { return false; } - if (!string.IsNullOrWhiteSpace(series.LocalizedName) && !settings.HasOverride(MetadataSettingField.LocalizedName)) + if (!string.IsNullOrWhiteSpace(series.LocalizedName) && !HasForceOverride(settings, series.Metadata, MetadataSettingField.LocalizedName)) { return false; } @@ -1471,6 +1496,7 @@ public class ExternalMetadataService : IExternalMetadataService } + series.Metadata.AddKPlusOverride(MetadataSettingField.LocalizedName); return true; } @@ -1480,17 +1506,18 @@ public class ExternalMetadataService : IExternalMetadataService if (string.IsNullOrEmpty(externalMetadata.Summary)) return false; - if (series.Metadata.SummaryLocked && !settings.HasOverride(MetadataSettingField.Summary)) + if (series.Metadata.SummaryLocked && !HasForceOverride(settings, series.Metadata, MetadataSettingField.Summary)) { return false; } - if (!string.IsNullOrWhiteSpace(series.Metadata.Summary) && !settings.HasOverride(MetadataSettingField.Summary)) + if (!string.IsNullOrWhiteSpace(series.Metadata.Summary) && !HasForceOverride(settings, series.Metadata, MetadataSettingField.Summary)) { return false; } series.Metadata.Summary = StringHelper.RemoveSourceInDescription(StringHelper.SquashBreaklines(externalMetadata.Summary)); + series.Metadata.AddKPlusOverride(MetadataSettingField.Summary); return true; } @@ -1509,7 +1536,8 @@ public class ExternalMetadataService : IExternalMetadataService { try { - await _coverDbService.SetSeriesCoverByUrl(series, coverUrl, false, true); + // Only choose the better image if we're overriding a user provided cover + await _coverDbService.SetSeriesCoverByUrl(series, coverUrl, false, !series.Metadata.HasSetKPlusMetadata(MetadataSettingField.Covers)); } catch (Exception ex) { @@ -1881,4 +1909,10 @@ public class ExternalMetadataService : IExternalMetadataService return null; } + + private static bool HasForceOverride(MetadataSettingsDto settings, IHasKPlusMetadata kPlusMetadata, + MetadataSettingField field) + { + return settings.HasOverride(field) || kPlusMetadata.HasSetKPlusMetadata(field); + } } diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index 426a8de3f..78e3c41f1 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -11,7 +11,9 @@ using API.DTOs.Person; using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; +using API.Entities.Interfaces; using API.Entities.Metadata; +using API.Entities.MetadataMatching; using API.Entities.Person; using API.Extensions; using API.Helpers; @@ -120,23 +122,27 @@ public class SeriesService : ISeriesService { series.Metadata.ReleaseYear = updateSeriesMetadataDto.SeriesMetadata.ReleaseYear; series.Metadata.ReleaseYearLocked = true; + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.StartDate); } if (series.Metadata.PublicationStatus != updateSeriesMetadataDto.SeriesMetadata.PublicationStatus) { series.Metadata.PublicationStatus = updateSeriesMetadataDto.SeriesMetadata.PublicationStatus; series.Metadata.PublicationStatusLocked = true; + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.PublicationStatus); } if (string.IsNullOrEmpty(updateSeriesMetadataDto.SeriesMetadata.Summary)) { updateSeriesMetadataDto.SeriesMetadata.Summary = string.Empty; + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.Summary); } if (series.Metadata.Summary != updateSeriesMetadataDto.SeriesMetadata.Summary.Trim()) { series.Metadata.Summary = updateSeriesMetadataDto.SeriesMetadata?.Summary.Trim() ?? string.Empty; series.Metadata.SummaryLocked = true; + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.Summary); } if (series.Metadata.Language != updateSeriesMetadataDto.SeriesMetadata?.Language) @@ -195,6 +201,7 @@ public class SeriesService : ISeriesService series.Metadata.AgeRating = updateSeriesMetadataDto.SeriesMetadata?.AgeRating ?? AgeRating.Unknown; series.Metadata.AgeRatingLocked = true; await _readingListService.UpdateReadingListAgeRatingForSeries(series.Id, series.Metadata.AgeRating); + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.AgeRating); } else { @@ -206,6 +213,7 @@ public class SeriesService : ISeriesService if (updatedRating > series.Metadata.AgeRating) { series.Metadata.AgeRating = updatedRating; + series.Metadata.KPlusOverrides.Remove(MetadataSettingField.AgeRating); } } } @@ -319,6 +327,7 @@ public class SeriesService : ISeriesService return true; } + _unitOfWork.SeriesRepository.Update(series.Metadata); await _unitOfWork.CommitAsync(); // Trigger code to clean up tags, collections, people, etc diff --git a/UI/Web/src/app/_services/series.service.ts b/UI/Web/src/app/_services/series.service.ts index 39e3b720b..9c436e636 100644 --- a/UI/Web/src/app/_services/series.service.ts +++ b/UI/Web/src/app/_services/series.service.ts @@ -81,10 +81,6 @@ export class SeriesService { return this.httpClient.post(this.baseUrl + 'series/delete-multiple', {seriesIds}, TextResonse).pipe(map(s => s === "true")); } - updateRating(seriesId: number, userRating: number) { - return this.httpClient.post(this.baseUrl + 'series/update-rating', {seriesId, userRating}); - } - updateSeries(model: any) { return this.httpClient.post(this.baseUrl + 'series/update', model); } diff --git a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts index d18939c4e..48bd0c580 100644 --- a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts +++ b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts @@ -78,6 +78,7 @@ export class ExternalRatingComponent implements OnInit { modalRef.componentInstance.userRating = this.userRating; modalRef.componentInstance.seriesId = this.seriesId; modalRef.componentInstance.hasUserRated = this.hasUserRated; + modalRef.componentInstance.chapterId = this.chapterId; modalRef.closed.subscribe((updated: {hasUserRated: boolean, userRating: number}) => { this.userRating = updated.userRating; diff --git a/UI/Web/src/app/series-detail/_components/rating-modal/rating-modal.component.ts b/UI/Web/src/app/series-detail/_components/rating-modal/rating-modal.component.ts index c50543e61..a108c137d 100644 --- a/UI/Web/src/app/series-detail/_components/rating-modal/rating-modal.component.ts +++ b/UI/Web/src/app/series-detail/_components/rating-modal/rating-modal.component.ts @@ -5,6 +5,7 @@ import {Breakpoint} from "../../../shared/_services/utility.service"; import {NgxStarsModule} from "ngx-stars"; import {ThemeService} from "../../../_services/theme.service"; import {SeriesService} from "../../../_services/series.service"; +import {ReviewService} from "../../../_services/review.service"; @Component({ selector: 'app-rating-modal', @@ -20,7 +21,7 @@ export class RatingModalComponent { protected readonly modal = inject(NgbActiveModal); protected readonly themeService = inject(ThemeService); - protected readonly seriesService = inject(SeriesService); + protected readonly reviewService = inject(ReviewService); protected readonly cdRef = inject(ChangeDetectorRef); protected readonly Breakpoint = Breakpoint; @@ -28,14 +29,16 @@ export class RatingModalComponent { @Input({required: true}) userRating!: number; @Input({required: true}) seriesId!: number; @Input({required: true}) hasUserRated!: boolean; + @Input() chapterId: number | undefined; starColor = this.themeService.getCssVariable('--rating-star-color'); updateRating(rating: number) { - this.seriesService.updateRating(this.seriesId, rating).subscribe(() => { + this.reviewService.updateRating(this.seriesId, rating, this.chapterId).subscribe(() => { this.userRating = rating; this.hasUserRated = true; this.cdRef.markForCheck(); + this.close(); }); } diff --git a/UI/Web/src/app/shared/_services/filter-utilities.service.ts b/UI/Web/src/app/shared/_services/filter-utilities.service.ts index 559a70ab1..66f5ceedd 100644 --- a/UI/Web/src/app/shared/_services/filter-utilities.service.ts +++ b/UI/Web/src/app/shared/_services/filter-utilities.service.ts @@ -190,7 +190,7 @@ export class FilterUtilitiesService { switch (type) { case 'series': return [ - FilterField.SeriesName, FilterField.Summary, FilterField.Path, FilterField.FilePath, PersonFilterField.Name + FilterField.SeriesName, FilterField.Summary, FilterField.Path, FilterField.FilePath ] as unknown as T[]; case 'person': return [ From 3ac816eaf729ff72f6ea6235df818aa57c054b8a Mon Sep 17 00:00:00 2001 From: majora2007 Date: Sat, 28 Jun 2025 16:45:47 +0000 Subject: [PATCH 05/15] Bump versions by dotnet-bump-version. --- Kavita.Common/Kavita.Common.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index adf4d3758..2ac06907e 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -3,7 +3,7 @@ net9.0 kavitareader.com Kavita - 0.8.6.19 + 0.8.6.20 en true From 6d4e207b654971e287005082e6479878f5df6a7c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 28 Jun 2025 16:46:53 +0000 Subject: [PATCH 06/15] Update OpenAPI documentation --- openapi.json | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/openapi.json b/openapi.json index 46adf1248..fe9270370 100644 --- a/openapi.json +++ b/openapi.json @@ -2,12 +2,12 @@ "openapi": "3.0.4", "info": { "title": "Kavita", - "description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.6.18", + "description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.6.19", "license": { "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.8.6.18" + "version": "0.8.6.19" }, "servers": [ { @@ -18338,6 +18338,32 @@ "type": "string", "nullable": true }, + "kPlusOverrides": { + "type": "array", + "items": { + "enum": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "type": "integer", + "description": "Represents which field that can be written to as an override when already locked", + "format": "int32" + }, + "description": "Tracks which metadata has been set by K+", + "nullable": true + }, "averageExternalRating": { "type": "number", "description": "(Kavita+) Average rating from Kavita+ metadata", @@ -24431,6 +24457,32 @@ "description": "A Comma-separated list of strings representing links from the series", "nullable": true }, + "kPlusOverrides": { + "type": "array", + "items": { + "enum": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "type": "integer", + "description": "Represents which field that can be written to as an override when already locked", + "format": "int32" + }, + "description": "Tracks which metadata has been set by K+", + "nullable": true + }, "languageLocked": { "type": "boolean" }, From e5d949161eaf028afa0827ef9827a060a89452bd Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sat, 28 Jun 2025 21:20:04 +0200 Subject: [PATCH 07/15] [skip ci] Weblate Changes (#3877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adam Havránek Co-authored-by: Duarte Silva Co-authored-by: Frozehunter Co-authored-by: Lyrq Co-authored-by: தமிழ்நேரம் --- API/I18N/pl.json | 4 +- API/I18N/pt.json | 4 +- API/I18N/ta.json | 9 +- UI/Web/src/assets/langs/cs.json | 11 +- UI/Web/src/assets/langs/de.json | 10 +- UI/Web/src/assets/langs/pl.json | 312 ++++++++++++++++++++++++++++---- UI/Web/src/assets/langs/pt.json | 125 ++++++++++--- 7 files changed, 398 insertions(+), 77 deletions(-) diff --git a/API/I18N/pl.json b/API/I18N/pl.json index 68a4a1a4f..5372fddc0 100644 --- a/API/I18N/pl.json +++ b/API/I18N/pl.json @@ -207,5 +207,7 @@ "smart-filter-name-required": "Inteligentny filtr wymaga nazwy", "sidenav-stream-only-delete-smart-filter": "Tylko inteligentne filtry mogą zostać usunięte z panelu bocznego", "dashboard-stream-only-delete-smart-filter": "Tylko inteligentne strumienie filtrów może zostać usunięte z głównego panelu", - "smart-filter-system-name": "Nie można użyć nazwy systemu dostarczanego strumieniem" + "smart-filter-system-name": "Nie można użyć nazwy systemu dostarczanego strumieniem", + "aliases-have-overlap": "Jeden lub więcej aliasów pokrywa się z innymi osobami, nie można ich zaktualizować", + "generated-reading-profile-name": "Wygenerowano z {0}" } diff --git a/API/I18N/pt.json b/API/I18N/pt.json index d0dd3345f..726c843bb 100644 --- a/API/I18N/pt.json +++ b/API/I18N/pt.json @@ -207,5 +207,7 @@ "sidenav-stream-only-delete-smart-filter": "Apenas os filtros inteligentes podem ser removidos da Navegação Lateral", "dashboard-stream-only-delete-smart-filter": "Apenas os filtros inteligentes podem ser removidos do painel", "smart-filter-system-name": "Não pode usar o nome de um fluxo disponibilizado pelo sistema", - "smart-filter-name-required": "Nome requerido para o filtro inteligente" + "smart-filter-name-required": "Nome requerido para o filtro inteligente", + "aliases-have-overlap": "Um ou mais pseudónimos sobrepõem-se com outras pessoas, não vai ser possível atualizar", + "generated-reading-profile-name": "Gerado de {0}" } diff --git a/API/I18N/ta.json b/API/I18N/ta.json index 83d6bd12f..cc20ab40f 100644 --- a/API/I18N/ta.json +++ b/API/I18N/ta.json @@ -202,5 +202,12 @@ "want-to-read": "படிக்க விரும்புகிறேன்", "browse-want-to-read": "உலாவு படிக்க விரும்புகிறது", "browse-recently-added": "உலாவு அண்மைக் காலத்தில் சேர்க்கப்பட்டது", - "reading-lists": "பட்டியல்களைப் படித்தல்" + "reading-lists": "பட்டியல்களைப் படித்தல்", + "sidenav-stream-only-delete-smart-filter": "சைடனாவிலிருந்து அறிவுள்ள வடிகட்டி நீரோடைகளை மட்டுமே நீக்க முடியும்", + "dashboard-stream-only-delete-smart-filter": "டாச்போர்டில் இருந்து அறிவுள்ள வடிகட்டி ச்ட்ரீம்களை மட்டுமே நீக்க முடியும்", + "smart-filter-name-required": "அறிவுள்ள வடிகட்டி பெயர் தேவை", + "smart-filter-system-name": "வழங்கப்பட்ட ச்ட்ரீமின் பெயரை நீங்கள் பயன்படுத்த முடியாது", + "kavitaplus-restricted": "இது கவிதா+ க்கு மட்டுமே", + "aliases-have-overlap": "ஒன்று அல்லது அதற்கு மேற்பட்ட மாற்றுப்பெயர்கள் மற்றவர்களுடன் ஒன்றுடன் ஒன்று உள்ளன, புதுப்பிக்க முடியாது", + "generated-reading-profile-name": "{0 இருந்து இலிருந்து உருவாக்கப்பட்டது" } diff --git a/UI/Web/src/assets/langs/cs.json b/UI/Web/src/assets/langs/cs.json index 05a877a23..f7056c32f 100644 --- a/UI/Web/src/assets/langs/cs.json +++ b/UI/Web/src/assets/langs/cs.json @@ -50,7 +50,9 @@ "generate-scrobble-events": "Zpětné plnění Události", "token-expired": "Platnost vašeho tokenu AniListu vypršela! Události Scrobbling nebudou zpracovány, dokud je neobnovíte na stránce Účty.", "not-read-warning": "Upstream poskytovatelé si vždy ponechají nejvyšší počet", - "scrobbling-disabled": "Skroblování je v nastavení účtu zakázáno." + "scrobbling-disabled": "Skroblování je v nastavení účtu zakázáno.", + "select-all-label": "Vybrat vše", + "delete-selected-label": "Odstranit vybrané" }, "scrobble-event-type-pipe": { "chapter-read": "Pokrok ve čtení", @@ -854,7 +856,9 @@ "file-type-group-tooltip": "Jaké typy souborů by měla Kavita vyhledávat. Například Archiv bude zahrnovat všechny soubory cb*, zip, rar atd.", "exclude-patterns-tooltip": "Konfigurace sady vzorů (syntaxe Glob), které bude Kavita při skenování adresářů porovnávat a vylučovat z výsledků skeneru.", "allow-metadata-matching-label": "Povolit přiřazování metadat", - "allow-metadata-matching-tooltip": "Měla by Kavita stáhnout metadata pro série v rámci této knihovny. K tomu dojde pouze v případě, že má server aktivní předplatné Kavita+." + "allow-metadata-matching-tooltip": "Měla by Kavita stáhnout metadata pro série v rámci této knihovny. K tomu dojde pouze v případě, že má server aktivní předplatné Kavita+.", + "enable-metadata-label": "Povolit metadata (ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "Povolit Kavitě číst soubory metadat, které přepisují analýzu názvů souborů." }, "reader-settings": { "font-family-label": "{{manage-reading-profiles.font-family-label}}", @@ -2436,7 +2440,8 @@ "confirm-delete-multiple-volumes": "Jste si jisti, že chcete odstranit {{count}} svazků? Soubory na disku se tím nezmění.", "series-added-want-to-read": "Série přidána ze seznamu Chci číst", "series-bound-to-reading-profile": "Série vázaná na čtenářský profil {{name}}", - "library-bound-to-reading-profile": "Knihovna vázaná na čtenářský profil {{name}}" + "library-bound-to-reading-profile": "Knihovna vázaná na čtenářský profil {{name}}", + "external-match-rate-error": "Kavita vyčerpala limit pro vyhledávání {{seriesName}}. Zkuste to znovu za 5 minut." }, "preferences": { "split-right-to-left": "Rozdělit zprava doleva", diff --git a/UI/Web/src/assets/langs/de.json b/UI/Web/src/assets/langs/de.json index fc6b6cbf7..5b18c44c0 100644 --- a/UI/Web/src/assets/langs/de.json +++ b/UI/Web/src/assets/langs/de.json @@ -1682,7 +1682,8 @@ "average-rating": "Durchschnittliche Bewertung", "random": "Zufällig", "person-series-count": "Anzahl der Serien", - "person-name": "Name" + "person-name": "Name", + "person-chapter-count": "Anzahl der Kapitel" }, "edit-series-modal": { "title": "{{seriesName}} Einzelheiten", @@ -2038,8 +2039,7 @@ "collections": "{{side-nav.collections}}", "reading-lists": "{{side-nav.reading-lists}}", "bookmarks": "{{side-nav.bookmarks}}", - "all-series": "{{side-nav.all-series}}", - "browse-authors": "{{side-nav.browse-authors}}" + "all-series": "{{side-nav.all-series}}" }, "filter-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -2761,7 +2761,9 @@ "release-year": "Veröffentlicht in {{value}}", "imprint": "Imprint von {{value}}", "location": "An {{value}} Standorten", - "team": "Team {{value}}" + "team": "Team {{value}}", + "publication-status": "{{value}} Werke", + "age-rating": "Bewertet mit {{value}}" }, "browse-tags": { "title": "Tags durchsuchen", diff --git a/UI/Web/src/assets/langs/pl.json b/UI/Web/src/assets/langs/pl.json index 8f242ef94..0308255e5 100644 --- a/UI/Web/src/assets/langs/pl.json +++ b/UI/Web/src/assets/langs/pl.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "scrobbling-disabled": "Scrobbling jest wyłączony w opcjach twojego Konta.", "token-expired": "Twój token AniList wygasł! Scrobbling nie będzie przetwarzany do momentu jego odnowienia na stronie Konta.", - "generate-scrobble-events": "Wydarzenia Backfill" + "generate-scrobble-events": "Wydarzenia Backfill", + "select-all-label": "Zaznacz wszystko", + "delete-selected-label": "Usuń zaznaczone" }, "scrobble-event-type-pipe": { "chapter-read": "Postęp czytania", @@ -72,7 +74,8 @@ "your-review": "To twoja recenzja", "external-review": "Recenzja zewnętrzna", "local-review": "Recenzja lokalna", - "rating-percentage": "Ocena {{r}}%" + "rating-percentage": "Ocena {{r}}%", + "critic": "krytyk" }, "want-to-read": { "title": "Chcę przeczytać", @@ -82,7 +85,7 @@ }, "user-preferences": { "title": "Panel użytkownika", - "pref-description": "To są ustawienia globalne, które powiązane są z twoim kontem.", + "pref-description": "To są ustawienia globalne, które powiązane są z twoim kontem. Ustawienia czytnika znajdują się w Profilach czytania.", "account-tab": "{{tabs.account-tab}}", "preferences-tab": "{{tabs.preferences-tab}}", "theme-tab": "{{tabs.theme-tab}}", @@ -628,7 +631,10 @@ "incognito-mode-label": "Tryb incognito", "next": "Dalej", "previous": "Poprzedni", - "go-to-page-prompt": "Jest {{totalPages}} stron. Na jaką stronę chcesz przejść?" + "go-to-page-prompt": "Jest {{totalPages}} stron. Na jaką stronę chcesz przejść?", + "go-to-first-page": "Idź do pierwszej strony", + "go-to-section": "Idź do sekcji", + "go-to-section-prompt": "Znajduje się {{totalSections}} sekcji. Do której sekcji chcesz przejść?" }, "personal-table-of-contents": { "no-data": "Nie dodano jeszcze nic do zakładek", @@ -676,7 +682,7 @@ "series-detail": { "page-settings-title": "Ustawienia strony", "close": "{{common.close}}", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-option-card": "Karta", "layout-mode-option-list": "Lista", "continue-from": "Kontynuuj {{title}}", @@ -763,7 +769,8 @@ "entry-label": "Zobacz szczegóły", "kavita-tooltip": "Twoja ocena + ogólna", "kavita-rating-title": "Twoja ocena", - "close": "{{common.close}}" + "close": "{{common.close}}", + "critic": "{{review-card.critic}}" }, "badge-expander": { "more-items": "i {{count}} więcej" @@ -799,7 +806,8 @@ "more": "Więcej", "customize": "{{settings.customize}}", "edit": "{{common.edit}}", - "cancel-edit": "Zamknij zmianę kolejności" + "cancel-edit": "Zamknij zmianę kolejności", + "browse-people": "Przeglądaj osoby" }, "library-settings-modal": { "close": "{{common.close}}", @@ -852,7 +860,9 @@ "exclude-patterns-tooltip": "Skonfiguruj zestaw wzorców (składnia Glob) które Kavita będzie dopasowywać podczas skanowania katalogów i wykluczać z wyników skanera.", "help": "{{common.help}}", "allow-metadata-matching-tooltip": "Czy Kavita powinna pobrać metadane dla serii w tej bibliotece.Nastąpi to tylko wtedy, gdy serwer ma aktywną subskrypcję Kavita+.", - "allow-metadata-matching-label": "Zezwól na dopasowywanie metadanych" + "allow-metadata-matching-label": "Zezwól na dopasowywanie metadanych", + "enable-metadata-label": "Włącz metadane (ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "Umożliwienie Kavicie odczytywania plików metadanych, które zastępują parsowanie nazw plików." }, "file-type-group-pipe": { "archive": "Archiwum", @@ -862,39 +872,47 @@ }, "reader-settings": { "general-settings-title": "Ustawienia ogólne", - "font-family-label": "{{user-preferences.font-family-label}}", - "font-size-label": "{{user-preferences.font-size-book-label}}", - "line-spacing-label": "{{user-preferences.line-height-book-label}}", - "margin-label": "{{user-preferences.margin-book-label}}", + "font-family-label": "{{manage-reading-profiles.font-family-label}}", + "font-size-label": "{{manage-reading-profiles.font-size-book-label}}", + "line-spacing-label": "{{manage-reading-profiles.line-height-book-label}}", + "margin-label": "{{manage-reading-profiles.margin-book-label}}", "reset-to-defaults": "Przywróć ustawienia domyślne", "reader-settings-title": "Ustawienia czytnika", - "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "reading-direction-label": "{{manage-reading-profiles.reading-direction-book-label}}", "right-to-left": "Od prawej do lewej", "left-to-right": "Od lewej do prawej", "horizontal": "Poziomo", "vertical": "Pionowo", - "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-label": "{{manage-reading-profiles.writing-style-label}}", "writing-style-tooltip": "Zmienia kierunek tekstu. Poziomo od lewej do prawej, pionowo od góry do dołu.", "tap-to-paginate-label": "Stuknij aby zmienić stronę", "tap-to-paginate-tooltip": "Kliknij przy krawędzi ekranu, aby zmienić stronę", "on": "Wł.", "off": "Wył.", - "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-label": "{{manage-reading-profiles.immersive-mode-label}}", "immersive-mode-tooltip": "Spowoduje to ukrycie menu po kliknięciu czytnika i włączenie funkcji podziału na strony", "fullscreen-label": "Pełny ekran", "fullscreen-tooltip": "Przełącz czytnik w tryb pełnoekranowy", "exit": "Wyjdź", "enter": "Wejdź", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-tooltip": "Scroll: Lustrzane odbicie pliku epub (zwykle jedna długa przewijana strona na rozdział).
1 Kolumna: Tworzy pojedynczą stronę wirtualną naraz.
2 Kolumny:Tworzy jednocześnie dwie wirtualne strony ułożone obok siebie.", "layout-mode-option-scroll": "Przewijanie", "layout-mode-option-1col": "1 Kolumna", "layout-mode-option-2col": "2 Kolumny", - "color-theme-title": "Motyw kolorów", + "color-theme-title": "Kolor motywu", "theme-dark": "Ciemny", "theme-black": "Czarny", "theme-white": "Biały", - "theme-paper": "Papier" + "theme-paper": "Papier", + "update-parent": "Zapisz do {{name}}", + "loading": "ładowanie", + "create-new": "Nowy profil z domyślnego", + "create-new-tooltip": "Utwórz nowy zarządzalny profil na podstawie obecnego domyślnego profilu", + "reading-profile-updated": "Zaktualizowano profil czytelnika", + "reading-profile-promoted": "Promowany profil czytelniczy", + "line-spacing-min-label": "1x", + "line-spacing-max-label": "2.5x" }, "table-of-contents": { "no-data": "Ta książka nie ma spisu treści ustawionego w metadanych ani pliku toc" @@ -1335,7 +1353,8 @@ "admin-manage-tokens": "Zarządzanie tokenami użytkownika", "admin-metadata": "Zarządzaj metadanymi", "scrobble-holds": "Wstrzymane pozycje scrobblowania", - "admin-email-history": "Historia e-mail" + "admin-email-history": "Historia e-mail", + "reading-profiles": "Profil czytelniczy" }, "collection-detail": { "no-data": "Brak elementów. Spróbuj dodać serię.", @@ -1459,7 +1478,10 @@ "logout": "Wyloguj", "all-filters": "Inteligentne filtry", "nav-link-header": "Opcje Nawigacji", - "close": "{{common.close}}" + "close": "{{common.close}}", + "person-aka-status": "Pasuje do aliasu", + "browse-genres": "Przeglądaj gatunki", + "browse-tags": "Przeglądaj tagi" }, "promoted-icon": { "promoted": "{{common.promoted}}" @@ -1591,9 +1613,9 @@ "height": "Wysokość", "width": "Szerokość", "width-override-label": "Nadpisanie szerokości", - "off": "Wył.", + "off": "{{reader-settings.off}}", "original": "Oryginał", - "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "auto-close-menu-label": "{{manage-reading-profiles.auto-close-menu-label}}", "swipe-enabled-label": "Włączone przeciąganie", "enable-comic-book-label": "Naśladuj komiks", "brightness-label": "Jasność", @@ -1604,8 +1626,14 @@ "layout-mode-switched": "Tryb układu został przełączony na Pojedynczy z powodu niewystarczającej ilości miejsca do renderowania podwójnego układu", "no-next-chapter": "Brak następnego rozdziału", "no-prev-chapter": "Brak poprzedniego rozdziału", - "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}", - "series-progress": "Postęp serii: {{percentage}}" + "emulate-comic-book-label": "{{manage-reading-profiles.emulate-comic-book-label}}", + "series-progress": "Postęp serii: {{percentage}}", + "update-parent": "{{reader-settings.update-parent}}", + "loading": "{{reader-settings.loading}}", + "create-new": "{{reader-settings.create-new}}", + "create-new-tooltip": "{{reader-settings.create-new-tooltip}}", + "reading-profile-updated": "Profil czytelniczy zaktualizowany", + "reading-profile-promoted": "Promowany profil czytelniczy" }, "metadata-filter": { "filter-title": "{{common.filter}}", @@ -1662,7 +1690,10 @@ "release-year": "Rok wydania", "read-progress": "Ostatnio czytano", "average-rating": "Średnia ocena", - "random": "Losowe" + "random": "Losowe", + "person-name": "Nazwa", + "person-series-count": "Liczba serii", + "person-chapter-count": "Liczba rozdziałów" }, "edit-series-modal": { "title": "{{seriesName}} szczegóły", @@ -1799,8 +1830,8 @@ "cover-image-tab": "{{tabs.cover-tab}}", "tasks-tab": "{{tabs.tasks-tab}}", "info-tab": "{{tabs.info-tab}}", - "pages-label": "{{edit-chapter-modal.pages-count}}", - "words-label": "{{edit-chapter-modal.length-title}}", + "pages-label": "{{edit-chapter-modal.pages-label}}", + "words-label": "{{edit-chapter-modal.words-label}}", "pages-count": "{{edit-chapter-modal.pages-count}}", "words-count": "{{edit-chapter-modal.words-count}}", "reading-time-label": "{{edit-chapter-modal.reading-time-label}}", @@ -2021,7 +2052,7 @@ "reading-lists": "{{side-nav.reading-lists}}", "bookmarks": "{{side-nav.bookmarks}}", "all-series": "{{side-nav.all-series}}", - "browse-authors": "{{side-nav.browse-authors}}" + "browse-authors": "{{side-nav.browse-people}}" }, "filter-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -2196,7 +2227,11 @@ "bulk-delete-libraries": "Jesteś pewien że chcesz usunąć {{count}} bibliotek?", "match-success": "Seria dopasowana prawidłowo", "scrobble-gen-init": "Ustawiono w kolejce zadanie generowania zdarzeń scrobble na podstawie wcześniejszej historii czytania i ocen, synchronizując je z połączonymi usługami.", - "confirm-delete-multiple-volumes": "Czy na pewno chcesz usunąć {{count}} tomów? Nie spowoduje to modyfikacji plików na dysku." + "confirm-delete-multiple-volumes": "Czy na pewno chcesz usunąć {{count}} tomów? Nie spowoduje to modyfikacji plików na dysku.", + "series-added-want-to-read": "Seria dodana z listy Chcę przeczytać", + "series-bound-to-reading-profile": "Seria powiązana z profilem czytelniczym {{name}}", + "library-bound-to-reading-profile": "Biblioteka powiązana z profilem czytelniczym {{name}}", + "external-match-rate-error": "Kavicie skończyła się liczba wyszukiwań {{seriesName}}. Spróbuj ponownie za 5 minut." }, "read-time-pipe": { "less-than-hour": "<1 godzina", @@ -2269,7 +2304,16 @@ "import-mal-stack": "Importuj MAL Stack", "match-tooltip": "Dopasuj serię ręcznie z Kavita+", "match": "Dopasuj", - "reorder": "Zmień kolejność" + "reorder": "Zmień kolejność", + "reading-profiles": "Profile Czytelnicze", + "set-reading-profile": "Ustaw profil czytelniczy", + "set-reading-profile-tooltip": "Powiąż profil czytelnika z tą biblioteką", + "clear-reading-profile": "Wyczyść profil czytelniczy", + "clear-reading-profile-tooltip": "Wyczyść profil czytelniczy z tej biblioteki", + "cleared-profile": "Wyczyść profil czytelniczy", + "rename": "Zmień nazwę", + "rename-tooltip": "Zmień nazwę filtru inteligentnego", + "merge": "Scal" }, "preferences": { "left-to-right": "Od lewej do prawej", @@ -2388,7 +2432,8 @@ "volume-nums": "Tomy", "author-count": "{{num}} autorów", "no-data": "Brak danych", - "chapter-count": "{{num}} Rozdziałów" + "chapter-count": "{{num}} Rozdziałów", + "issue-count": "{{num}} Wydań" }, "person-detail": { "all-roles": "Role", @@ -2396,14 +2441,17 @@ "browse-person-title": "Wszystkie dzieła {{name}}", "individual-role-title": "Jako {{role}}", "browse-person-by-role-title": "Wszystkie dzieła {{name}} jako {{role}}", - "anilist-url": "{{edit-person-modal.anilist-tooltip}}" + "anilist-url": "{{edit-person-modal.anilist-tooltip}}", + "aka-title": "Znany również jako ", + "no-info": "Brak informacji o tej osobie" }, "confirm": { "confirm": "Potwierdź", "alert": "Uwaga", "info": "Informacje", "cancel": "{{common.cancel}}", - "ok": "Ok" + "ok": "Ok", + "prompt": "Pytanie" }, "edit-person-modal": { "role-label": "Rola", @@ -2426,7 +2474,11 @@ "download-coversdb": "Pobierz z CoversDB", "name-label": "{{edit-series-modal.name-label}}", "required-field": "{{validations.required-field}}", - "cover-image-description": "{{edit-series-modal.cover-image-description}}" + "cover-image-description": "{{edit-series-modal.cover-image-description}}", + "aliases-tab": "Pseudonimy", + "aliases-label": "Edytuj pseudonimy", + "alias-overlap": "Ten pseudonim wskazuje już na inną osobę lub jest imieniem tej osoby, rozważ ich połączenie.", + "aliases-tooltip": "Gdy seria jest oznaczona pseudonimem osoby, osoba jest przypisywana zamiast tworzenia nowej osoby. Po usunięciu aliasu konieczne będzie ponowne przeskanowanie serii, aby zmiana została uwzględniona." }, "changelog-update-item": { "added": "Dodano", @@ -2471,7 +2523,9 @@ "all-status-label": "Wyszystko", "match-alt": "Dopasuj {{seriesName}}", "dont-match-label": "Nie pasuję", - "status-header": "tatus" + "status-header": "tatus", + "library-type": "Typ biblioteki", + "matched-state-label": "Dopasuj stan" }, "manage-metadata-settings": { "derive-publication-status-tooltip": "Umożliwienie uzyskania statusu publikacji na podstawie łącznej liczby rozdziałów/tomów.", @@ -2512,7 +2566,18 @@ "add-age-rating-mapping-label": "Dodaj filtr klasyfikacji wiekowej", "add-field-mapping-label": "Dodaj mapowanie pola", "overrides-label": "Zastąpienia", - "overrides-description": "Zezwól Kavicie na zapisywanie w zablokowanych polach." + "overrides-description": "Zezwól Kavicie na zapisywanie w zablokowanych polach.", + "enable-chapter-title-label": "Tytuł", + "enable-chapter-title-tooltip": "Zezwól na wpisanie tytułu rozdziału/wydania", + "enable-chapter-summary-label": "{{manage-metadata-settings.summary-label}}", + "enable-chapter-summary-tooltip": "{{manage-metadata-settings.summary-tooltip}}", + "enable-chapter-release-date-label": "Data wydania", + "enable-chapter-release-date-tooltip": "Zezwól na wpisanie daty wydania rozdziału/wydania", + "enable-chapter-publisher-label": "Wydawca", + "enable-chapter-publisher-tooltip": "Zezwól na wpisanie wydawcy rozdziału/wydania", + "enable-chapter-cover-label": "Okładka rozdziału", + "enable-chapter-cover-tooltip": "Zezwól na ustawienie okładki rozdziału/wydania", + "chapter-header": "Pola rozdziału" }, "match-series-modal": { "description": "Wybierz dopasowanie, aby przekierować metadane Kavita+ i zregenerować zdarzenia scrobblowania.Opcja Nie dopasowuj może być użyta do ograniczenia dopasowywania metadanych i scrobblowania przez Kavita+.", @@ -2523,7 +2588,7 @@ "title": "Dopasuj {{seriesName}}", "query-label": "Zapytanie", "close": "{{common.close}}", - "query-tooltip": "Wprowadź nazwę serii, adres URL AniList/MyAnimeList. Adresy URL będą użyte do bezpośredniego wyszukiwania.", + "query-tooltip": "Wprowadź nazwę serii, adres URL AniList/MyAnimeList//ComicBookRoundup. Adresy URL będą użyte do bezpośredniego wyszukiwania.", "search": "Szukaj" }, "manage-user-tokens": { @@ -2540,7 +2605,8 @@ "releasing": "Wydawane", "updating-metadata-status": "Aktualizacja metadanych", "details": "Pokaż stronę", - "chapter-count": "{{common.chapter-count}}" + "chapter-count": "{{common.chapter-count}}", + "issue-count": "{{common.issue-count}}" }, "metadata-setting-field-pipe": { "genres": "{{metadata-fields.genres-title}}", @@ -2551,7 +2617,12 @@ "covers": "Okładki", "age-rating": "{{metadata-fields.age-rating-title}}", "tags": "{{metadata-fields.tags-title}}", - "localized-name": "{{edit-series-modal.localized-name-label}}" + "localized-name": "{{edit-series-modal.localized-name-label}}", + "chapter-release-date": "Data wydania (rozdział)", + "chapter-summary": "Podsumowanie (Rozdział)", + "chapter-covers": "Okładki (Rozdział)", + "chapter-publisher": "{{person-role-pipe.publisher}} (Rozdział)", + "chapter-title": "Tytuł (Rozdział)" }, "email-history": { "sent-header": "Udane", @@ -2579,5 +2650,168 @@ "warning": "Ostrzeżenie", "critical": "Krytyczne", "trace": "Ślad" + }, + "reviews": { + "user-reviews-local": "Lokalne recenzje", + "user-reviews-plus": "Zewnętrzne recenzje" + }, + "review-modal": { + "title": "Edytuj recenzję", + "review-label": "Recenzja", + "close": "{{common.close}}", + "save": "{{common.save}}", + "delete": "{{common.delete}}", + "min-length": "Recenzja musi zawierać co najmniej {{count}} znaków", + "required": "{{validation.required-field}}" + }, + "browse-people": { + "title": "Przeglądaj osoby", + "author-count": "{{num}} Osób", + "cover-image-description": "{{edit-series-modal.cover-image-description}}", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}", + "roles-label": "Role", + "sort-label": "Sortuj", + "name-label": "Nazwa", + "issue-count-label": "Liczba wydań", + "series-count-label": "Liczba serii" + }, + "browse-genres": { + "title": "Przeglądaj gatunki", + "genre-count": "{{num}} Gatunków", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}" + }, + "browse-tags": { + "title": "Przeglądaj tagi", + "genre-count": "{{num}} Tagów", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}" + }, + "bulk-set-reading-profile-modal": { + "title": "Ustaw profil czytelniczy", + "close": "{{common.close}}", + "filter-label": "{{common.filter}}", + "clear": "{{common.clear}}", + "no-data": "Nie utworzono jeszcze żadnych kolekcji", + "loading": "{{common.loading}}", + "create": "{{common.create}}", + "bound": "Powiązany" + }, + "merge-person-modal": { + "title": "{{personName}}", + "close": "{{common.close}}", + "save": "{{common.save}}", + "src": "Połącz osoby", + "merge-warning": "Kontynuacja spowoduje usunięcie wybranej osoby. Imię i nazwisko wybranej osoby zostanie dodane jako pseudonim, a wszystkie jej role zostaną przeniesione.", + "alias-title": "Nowy pseudonim", + "known-for-title": "Znany z" + }, + "browse-title-pipe": { + "publication-status": "{{value}} działa", + "age-rating": "Przeznaczony dla {{value}}", + "user-rating": "{{value}} gwiazdek", + "tag": "Ma {{value}} tagów", + "translator": "Przetłumaczone przez {{value}}", + "character": "Posiada postać {{value}}", + "publisher": "Wydany przez {{value}}", + "editor": "Edytowany przez {{value}}", + "artist": "Rysowany przez {{value}}", + "letterer": "Literowany przez {{value}}", + "colorist": "Kolorowany przez {{value}}", + "inker": "Tuszowany przez {{value}}", + "writer": "Napisany przez {{value}}", + "genre": "Ma gatunek {{value}}", + "library": "Wewnątrz {{value}} biblioteki", + "format": "Format {{value}}", + "release-year": "Wydano w {{value}}", + "imprint": "Imprint {{value}}", + "team": "Zespół {{value}}", + "location": "W {{value}} lokalizacji", + "penciller": "Nakreślone ołówkiem przez {{value}}" + }, + "generic-filter-field-pipe": { + "person-role": "Rola", + "person-name": "Nazwa", + "person-series-count": "Liczba serii", + "person-chapter-count": "Liczba rozdziałów" + }, + "breakpoint-pipe": { + "never": "Nigdy", + "tablet": "Tablet", + "desktop": "Komputer stacjonarny", + "mobile": "Mobilny" + }, + "manage-reading-profiles": { + "description": "Nie wszystkie serie mogą być czytane w ten sam sposób, skonfiguruj odrębne profile czytelnicze dla każdej biblioteki lub serii, aby powrót do serii był jak najbardziej płynny.", + "extra-tip": "Przypisywanie profili czytelniczych za pomocą menu akcji w seriach i bibliotekach lub zbiorczo. Podczas zmiany ustawień w czytniku tworzony jest ukryty profil, który zapamiętuje wybory dla danej serii (nie dla plików PDF). Profil ten jest usuwany po przypisaniu jednego z własnych profili czytania do serii. Więcej informacji można znaleźć na", + "wiki-title": "wiki", + "profiles-title": "Twoje profile czytelnicze", + "default-profile": "Domyślny", + "add": "{{common.add}}", + "add-tooltip": "Nowy profil zostanie zapisany po wprowadzeniu w nim zmian", + "make-default": "Ustaw jako domyślny", + "no-selected": "Nie wybrano profilu", + "confirm": "Czy na pewno chcesz usunąć profil czytelniczy {{name}}?", + "selection-tip": "Wybierz profil z listy lub utwórz nowy w prawym górnym rogu", + "image-reader-settings-title": "Czytnik obrazów", + "reading-direction-label": "Kierunek czytania", + "reading-direction-tooltip": "Kierunek kliknięcia, aby przejść do następnej strony. Od prawej do lewej oznacza kliknięcie po lewej stronie ekranu, aby przejść do następnej strony.", + "scaling-option-label": "Opcje skalowania", + "scaling-option-tooltip": "Jak przeskalować obraz do ekranu.", + "page-splitting-label": "Dzielenie stron", + "page-splitting-tooltip": "Jak podzielić obraz o pełnej szerokości (tj. obrazy lewy i prawy są połączone)?", + "reading-mode-label": "Tryb czytania", + "reading-mode-tooltip": "Zmień czytnik, aby przewijał strony pionowo, poziomo lub w nieskończoność", + "layout-mode-label": "Tryb układu", + "layout-mode-tooltip": "Wyświetl pojedynczy obraz na ekranie lub dwóch obrazów obok siebie", + "background-color-label": "Kolor tła", + "background-color-tooltip": "Kolor tła czytnika obrazów", + "auto-close-menu-label": "Automatycznie zamknij menu", + "auto-close-menu-tooltip": "Czy menu powinno się automatycznie zamykać?", + "show-screen-hints-label": "Wyświetl podpowiedzi", + "show-screen-hints-tooltip": "Wyświetlanie nakładki ułatwiającej zrozumienie obszaru i kierunku przewijania stron", + "emulate-comic-book-label": "Naśladuj komiks", + "emulate-comic-book-tooltip": "Stosuje efekt cienia, aby naśladować czytanie z książki", + "swipe-to-paginate-label": "Przesuń, aby zmienić stronę", + "swipe-to-paginate-tooltip": "Czy przesunięcie palcem po ekranie powinno powodować przejście do następnej lub poprzedniej strony?", + "allow-auto-webtoon-reader-label": "Automatyczny tryb czytnika Webtoon", + "allow-auto-webtoon-reader-tooltip": "Przełącz się w tryb Webtoon Reader, jeśli strony wyglądają jak webtoon. Mogą wystąpić fałszywie pozytywne wyniki.", + "width-override-label": "{{manga-reader.width-override-label}}", + "width-override-tooltip": "Zastąp szerokość obrazów w czytniku", + "disable-width-override-label": "Wyłącz zastępowanie szerokości", + "disable-width-override-tooltip": "Zapobieganie zastępowaniu szerokości, gdy ekran ma co najmniej skonfigurowany punkt podziału lub jest mniejszy", + "reset": "{{common.reset}}", + "book-reader-settings-title": "Czytnik książek", + "tap-to-paginate-label": "Stuknij, aby zmienić stronę", + "tap-to-paginate-tooltip": "Czy boki ekranu czytnika książek powinny umożliwiać dotknięcie go w celu przejścia do poprzedniej/następnej strony?", + "immersive-mode-label": "Tryb immersyjny", + "immersive-mode-tooltip": "Spowoduje to ukrycie menu za kliknięciem dokumentu czytnika i włączenie funkcji przewijania stron", + "reading-direction-book-label": "Kierunek czytania", + "reading-direction-book-tooltip": "Kierunek kliknięcia, aby przejść do następnej strony. Od prawej do lewej oznacza kliknięcie po lewej stronie ekranu, aby przejść do następnej strony.", + "font-family-label": "Rodzina fontów", + "font-family-tooltip": "Rodzina fontów do załadowania. Domyślna spowoduje załadowanie domyślnego fontu książki", + "writing-style-label": "Styl pisania", + "writing-style-tooltip": "Zmienia kierunek tekstu. Poziomo od lewej do prawej, pionowo od góry do dołu.", + "layout-mode-book-label": "Tryb układu", + "layout-mode-book-tooltip": "Jak powinna być ułożona treść. Przewijanie jest zgodne z układem książki. 1 lub 2 kolumny pasują do wysokości urządzenia i mieszczą 1 lub 2 kolumny tekstu na stronę", + "color-theme-book-label": "Kolor motywu", + "color-theme-book-tooltip": "Jaki motyw kolorystyczny zastosować do treści i menu czytnika książek?", + "font-size-book-label": "Wielkość fontu", + "font-size-book-tooltip": "Procent skalowania zastosowany do fontu w książce", + "line-height-book-label": "Odstęp między wierszami", + "line-height-book-tooltip": "Jak duże odstępy między wierszami książki", + "margin-book-label": "Margines", + "margin-book-tooltip": "Odstępy po każdej stronie ekranu. Na urządzeniach mobilnych wartość ta zostanie zastąpiona wartością 0, niezależnie od tego ustawienia.", + "pdf-reader-settings-title": "Czytnik PDF", + "pdf-scroll-mode-label": "Tryb przewijania", + "pdf-scroll-mode-tooltip": "Sposób przewijania stron. Pionowo/poziomo i dotknij, aby przewinąć (bez przewijania)", + "pdf-spread-mode-label": "Tryb stron", + "pdf-spread-mode-tooltip": "Jak powinny być ułożone strony. Pojedyncze lub podwójne (nieparzyste/parzyste)", + "pdf-theme-label": "Motyw", + "pdf-theme-tooltip": "Kolor motywu czytnika", + "reading-profile-series-settings-title": "Serie", + "reading-profile-library-settings-title": "Biblioteka", + "delete": "{{common.delete}}" } } diff --git a/UI/Web/src/assets/langs/pt.json b/UI/Web/src/assets/langs/pt.json index d4922b083..638956242 100644 --- a/UI/Web/src/assets/langs/pt.json +++ b/UI/Web/src/assets/langs/pt.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "token-expired": "O token do AniList expirou! Os eventos de scrobbling não serão processados até que o token seja refrescado na página Contas.", "scrobbling-disabled": "O scrobbling está desabilitado nas suas Definições de Conta.", - "generate-scrobble-events": "Eventos de Scrobbling" + "generate-scrobble-events": "Eventos de Scrobbling", + "select-all-label": "Selecionar tudo", + "delete-selected-label": "Apagar selecionado" }, "scrobble-event-type-pipe": { "chapter-read": "Leitura Efetuada", @@ -72,7 +74,8 @@ "your-review": "Esta é a sua crítica", "external-review": "Crítica externa", "local-review": "Crítica Local", - "rating-percentage": "Classificação {{r}}%" + "rating-percentage": "Classificação {{r}}%", + "critic": "crítico" }, "want-to-read": { "title": "Leituras Futuras", @@ -82,7 +85,7 @@ }, "user-preferences": { "title": "Painel do Utilizador", - "pref-description": "Estas definições globais estão ligadas à sua conta.", + "pref-description": "Estas definições globais estão ligadas à sua conta. As definições do Leitor estão nos Perfis de Leitura", "account-tab": "{{tabs.account-tab}}", "preferences-tab": "{{tabs.preferences-tab}}", "theme-tab": "{{tabs.theme-tab}}", @@ -628,7 +631,10 @@ "incognito-mode-label": "Modo Incógnito", "next": "Seguinte", "previous": "Anterior", - "go-to-page-prompt": "Existem {{totalPages}} páginas. Para que página deseja ir?" + "go-to-page-prompt": "Existem {{totalPages}} páginas. Para que página deseja ir?", + "go-to-first-page": "Ir para a primeira página", + "go-to-section": "Ir para secção", + "go-to-section-prompt": "Existem {{totalSections}} secções. Para que secção deseja ir?" }, "personal-table-of-contents": { "no-data": "Ainda não existem marcadores", @@ -676,7 +682,7 @@ "series-detail": { "page-settings-title": "Definições de Página", "close": "{{common.close}}", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-option-card": "Cartão", "layout-mode-option-list": "Lista", "continue-from": "Continuar {{title}}", @@ -763,7 +769,8 @@ "entry-label": "Ver detalhes", "kavita-tooltip": "A Sua Classificação + Geral", "kavita-rating-title": "A Sua Classificação", - "close": "{{common.close}}" + "close": "{{common.close}}", + "critic": "{{review-card.critic}}" }, "badge-expander": { "more-items": "e mais {{count}}" @@ -799,7 +806,8 @@ "more": "Mais", "customize": "{{settings.customize}}", "edit": "{{common.edit}}", - "cancel-edit": "Fechar Reordenar" + "cancel-edit": "Fechar Reordenar", + "browse-people": "Explorar Pessoas" }, "library-settings-modal": { "close": "{{common.close}}", @@ -852,7 +860,9 @@ "exclude-patterns-tooltip": "Configura um conjunto de padrões (sintaxe Glob) que o Kavita irá excluir dos resultados da analise de directorias.", "help": "{{common.help}}", "allow-metadata-matching-label": "Permitir Correspondência de Metadados", - "allow-metadata-matching-tooltip": "Se o Kavita deve descarregar metadados para as Séries nesta Biblioteca. Isto irá acontecer apenas se o servidor tiver uma subscrição Kavita+ ativa." + "allow-metadata-matching-tooltip": "Se o Kavita deve descarregar metadados para as Séries nesta Biblioteca. Isto irá acontecer apenas se o servidor tiver uma subscrição Kavita+ ativa.", + "enable-metadata-label": "Permitir Metadados (ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "Permitir que o Kavita use metadados que substituem a análise dos nomes dos ficheiros." }, "file-type-group-pipe": { "archive": "Arquivo", @@ -862,30 +872,30 @@ }, "reader-settings": { "general-settings-title": "Definições Gerais", - "font-family-label": "{{user-preferences.font-family-label}}", - "font-size-label": "{{user-preferences.font-size-book-label}}", - "line-spacing-label": "{{user-preferences.line-height-book-label}}", - "margin-label": "{{user-preferences.margin-book-label}}", + "font-family-label": "{{manage-reading-profiles.font-family-label}}", + "font-size-label": "{{manage-reading-profiles.font-size-book-label}}", + "line-spacing-label": "{{manage-reading-profiles.line-height-book-label}}", + "margin-label": "{{manage-reading-profiles.margin-book-label}}", "reset-to-defaults": "Repor Definições por Defeito", "reader-settings-title": "Definições do Leitor", - "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "reading-direction-label": "{{manage-reading-profiles.reading-direction-book-label}}", "right-to-left": "Direita para a Esquerda", "left-to-right": "Esquerda para a Direita", "horizontal": "Horizontal", "vertical": "Vertical", - "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-label": "{{manage-reading-profiles.writing-style-label}}", "writing-style-tooltip": "Altera a direção do texto. Horizontal é da esquerda para direita, veritcal é do topo para o fundo.", "tap-to-paginate-label": "Paginação por Toque", "tap-to-paginate-tooltip": "Clicar as extremidades do ecrã para paginar", "on": "Ligado", "off": "Desligado", - "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-label": "{{manage-reading-profiles.immersive-mode-label}}", "immersive-mode-tooltip": "Esta opção ocultará o menu ao clicar no documento e ligará o Toque para Paginar", "fullscreen-label": "Ecrã Completo", "fullscreen-tooltip": "Colocar o leitor em modo ecrã completo", "exit": "Sair", "enter": "Entrar", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-tooltip": "Scroll: Espelha o ficheiro epub (geralmente é uma página longa por capítulo).
1 Coluna: Cria uma única página virtual.
2 Colunas: Cria duas páginas virtuais que são dispostas lado a lado.", "layout-mode-option-scroll": "Scroll", "layout-mode-option-1col": "1 Coluna", @@ -894,7 +904,10 @@ "theme-dark": "Escuro", "theme-black": "Preto", "theme-white": "Branco", - "theme-paper": "Papel" + "theme-paper": "Papel", + "update-parent": "Gravar para {{name}}", + "loading": "a carregar", + "create-new": "Novo perfil implícito" }, "table-of-contents": { "no-data": "Este livro não tem um Indíce definido nos metadados ou um ficheiro toc" @@ -1591,9 +1604,9 @@ "height": "Altura", "width": "Largura", "width-override-label": "Sobrepor Largura", - "off": "Desligado", + "off": "{{reader-settings.off}}", "original": "Original", - "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "auto-close-menu-label": "{{manage-reading-profiles.auto-close-menu-label}}", "swipe-enabled-label": "Deslizar Ativado", "enable-comic-book-label": "Emular livro de BD", "brightness-label": "Brilho", @@ -1604,7 +1617,7 @@ "layout-mode-switched": "O modo de layout foi mudado para Individual por não haver espaço suficiente para mostrar o layout duplo", "no-next-chapter": "Não há Capítulo Seguinte", "no-prev-chapter": "Não há Capítulo Anterior", - "emulate-comic-book-label": "{{user-preferences.emulate-comic-book-label}}", + "emulate-comic-book-label": "{{manage-reading-profiles.emulate-comic-book-label}}", "series-progress": "Progresso da Série: {{percentage}}" }, "metadata-filter": { @@ -1799,8 +1812,8 @@ "cover-image-tab": "{{tabs.cover-tab}}", "tasks-tab": "{{tabs.tasks-tab}}", "info-tab": "{{tabs.info-tab}}", - "pages-label": "{{edit-chapter-modal.pages-count}}", - "words-label": "{{edit-chapter-modal.length-title}}", + "pages-label": "{{edit-chapter-modal.pages-label}}", + "words-label": "{{edit-chapter-modal.words-label}}", "pages-count": "{{edit-chapter-modal.pages-count}}", "words-count": "{{edit-chapter-modal.words-count}}", "reading-time-label": "{{edit-chapter-modal.reading-time-label}}", @@ -2021,7 +2034,7 @@ "reading-lists": "{{side-nav.reading-lists}}", "bookmarks": "{{side-nav.bookmarks}}", "all-series": "{{side-nav.all-series}}", - "browse-authors": "{{side-nav.browse-authors}}" + "browse-authors": "{{side-nav.browse-people}}" }, "filter-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -2402,7 +2415,9 @@ "individual-role-title": "Como um {{role}}", "browse-person-title": "Todos as Obras de {{name}}", "known-for-title": "Conhecido Por", - "anilist-url": "{{edit-person-modal.anilist-tooltip}}" + "anilist-url": "{{edit-person-modal.anilist-tooltip}}", + "aka-title": "Também conhecido como ", + "no-info": "Sem informação sobre esta Pessoa" }, "edit-person-modal": { "cover-image-description": "{{edit-series-modal.cover-image-description}}", @@ -2443,7 +2458,7 @@ }, "match-series-modal": { "search": "Pesquisa", - "query-tooltip": "Introduza o nome da série, e o url do AniList/MyAnimeList. Os Urls irão usar uma pesquisa direta.", + "query-tooltip": "Introduza o nome da série, e o url do AniList/MyAnimeList/ComicBookRoundup. Os Urls irão usar uma pesquisa direta.", "save": "{{common.save}}", "title": "Corresponder {{seriesName}}", "close": "{{common.close}}", @@ -2472,7 +2487,8 @@ "chapter-count": "{{common.chapter-count}}", "details": "Ver página", "updating-metadata-status": "Metadados em Atualização", - "releasing": "Em Lançamento" + "releasing": "Em Lançamento", + "issue-count": "{{common.issue-count}}" }, "email-history": { "description": "Aqui pode encontrar todos os emails enviados pelo Kavita, e para que utilizadores.", @@ -2508,7 +2524,9 @@ "dont-match-status-label": "{{dont-match-label}}", "library-name-header": "Biblioteca", "actions-header": "Ações", - "match-alt": "Corresponder {{seriesName}}" + "match-alt": "Corresponder {{seriesName}}", + "library-type": "Tipo de Biblioteca", + "matched-state-label": "Estado da Correspondência" }, "manage-metadata-settings": { "description": "O Kavita+ tem a capacidade de descarregar e escrever alguns metadados na base de dados. Esta página permite selecionar o que está abrangido.", @@ -2549,7 +2567,18 @@ "first-last-name-label": "Convenção Primeiro Nome/Sobrenome", "first-last-name-tooltip": "Certificar que os nomes das Pessoas são escritos pela ordem Primeiro Nome e depois Sobrenome", "overrides-label": "Substituições", - "person-roles-label": "Papéis" + "person-roles-label": "Papéis", + "enable-chapter-title-label": "Título", + "enable-chapter-title-tooltip": "Permitir que o Título do Capítulo/Número seja gravado", + "enable-chapter-summary-label": "{{manage-metadata-settings.summary-label}}", + "enable-chapter-summary-tooltip": "{{manage-metadata-settings.summary-tooltip}}", + "enable-chapter-release-date-label": "Data de Lançamento", + "enable-chapter-release-date-tooltip": "Permitir que a Data de Lançamento do Capítulo/Número seja gravado", + "enable-chapter-publisher-label": "Editora", + "enable-chapter-publisher-tooltip": "Permitir que a Editor do Capítulo/Número seja gravado", + "enable-chapter-cover-label": "Capa do Capítulo", + "enable-chapter-cover-tooltip": "Permitir que a Capa do Capítulo/Número seja definida", + "chapter-header": "Campos do Capítulo" }, "metadata-setting-field-pipe": { "people": "{{tabs.people-tab}}", @@ -2578,5 +2607,45 @@ "trace": "Rastrear", "warning": "Aviso", "critical": "Crítico" + }, + "manage-reading-profiles": { + "reset": "{{common.reset}}" + }, + "reviews": { + "user-reviews-local": "Criticas Locais", + "user-reviews-plus": "Criticas Externas" + }, + "review-modal": { + "title": "Editar Crítica", + "review-label": "Crítica", + "close": "{{common.close}}", + "save": "{{common.save}}", + "delete": "{{common.delete}}", + "min-length": "A crítica tem de ter no mínimo {{count}} caracteres", + "required": "{{validation.required-field}}" + }, + "browse-people": { + "title": "Explorar Pessoas", + "author-count": "{{num}} Pessoas", + "cover-image-description": "{{edit-series-modal.cover-image-description}}", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}", + "roles-label": "Funções", + "sort-label": "Ordenar", + "name-label": "Nome", + "issue-count-label": "Número de Edições", + "series-count-label": "Número de Séries" + }, + "browse-genres": { + "title": "Explorar Géneros", + "genre-count": "{{num}} Géneros", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}" + }, + "browse-tags": { + "title": "Explorar Etiquetas", + "genre-count": "{{num}} Etiquetas", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}" } } From ff179084009f0c74afa4f7bc401a55079d688d8a Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Mon, 30 Jun 2025 14:20:19 +0200 Subject: [PATCH 08/15] [skip ci] Weblate Changes (#3883) Co-authored-by: Duarte Silva Co-authored-by: Gregory.Open Co-authored-by: Havokdan Co-authored-by: lin49931104 --- API/I18N/fr.json | 4 +- UI/Web/src/assets/langs/fr.json | 169 +++++++++++++++++----- UI/Web/src/assets/langs/pt.json | 206 ++++++++++++++++++++++++--- UI/Web/src/assets/langs/pt_BR.json | 22 +-- UI/Web/src/assets/langs/zh_Hant.json | 2 +- 5 files changed, 338 insertions(+), 65 deletions(-) diff --git a/API/I18N/fr.json b/API/I18N/fr.json index 6b3dc735a..2b9a4f81b 100644 --- a/API/I18N/fr.json +++ b/API/I18N/fr.json @@ -207,5 +207,7 @@ "sidenav-stream-only-delete-smart-filter": "Seuls les flux de filtres intelligents peuvent être supprimés de la SideNav", "dashboard-stream-only-delete-smart-filter": "Seuls les flux de filtres intelligents peuvent être supprimés du tableau de bord", "smart-filter-name-required": "Nom du filtre intelligent requis", - "smart-filter-system-name": "Vous ne pouvez pas utiliser le nom d'un flux fourni par le système" + "smart-filter-system-name": "Vous ne pouvez pas utiliser le nom d'un flux fourni par le système", + "aliases-have-overlap": "Un ou plusieurs alias se chevauchent avec d'autres personnes et ne peuvent pas être mis à jour", + "generated-reading-profile-name": "Généré à partir de {0}" } diff --git a/UI/Web/src/assets/langs/fr.json b/UI/Web/src/assets/langs/fr.json index 780da243e..9dbda1bbf 100644 --- a/UI/Web/src/assets/langs/fr.json +++ b/UI/Web/src/assets/langs/fr.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "token-expired": "Votre jeton AniList a expiré ! Les événements de scrobbling ne seront pas pris en compte tant que vous n'aurez pas renouvelé votre compte sur la page Comptes.", "scrobbling-disabled": "Le suivi de lecture est désactivé dans les paramètres de votre compte.", - "generate-scrobble-events": "Rattraper les Événements" + "generate-scrobble-events": "Rattraper les Événements", + "select-all-label": "Sélectionner tout", + "delete-selected-label": "Supprimer la sélection" }, "scrobble-event-type-pipe": { "chapter-read": "Progression de la lecture", @@ -83,7 +85,7 @@ }, "user-preferences": { "title": "Tableau de bord Utilisateur", - "pref-description": "Voici les paramètres globaux liés à votre compte.", + "pref-description": "Voici les paramètres globaux liés à votre compte. Les paramètres du lecteur se trouvent dans les profils de lecture.", "account-tab": "{{tabs.account-tab}}", "preferences-tab": "{{tabs.preferences-tab}}", "theme-tab": "{{tabs.theme-tab}}", @@ -629,7 +631,10 @@ "incognito-mode-label": "Navigation privée", "next": "Suivant", "previous": "Précédent", - "go-to-page-prompt": "Il y a {{totalPages}} pages. À quelle page voulez-vous aller ?" + "go-to-page-prompt": "Il y a {{totalPages}} pages. À quelle page voulez-vous aller ?", + "go-to-first-page": "Aller à la première page", + "go-to-section": "Aller à la section", + "go-to-section-prompt": "Il y a {{totalSections}} sections au total. Dans quelle section voulez-vous aller ?" }, "personal-table-of-contents": { "no-data": "Rien n'a encore été mis en marque-page", @@ -677,7 +682,7 @@ "series-detail": { "page-settings-title": "Paramètres des pages", "close": "{{common.close}}", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-option-card": "Carte", "layout-mode-option-list": "Liste", "continue-from": "Continuer {{title}}", @@ -764,7 +769,8 @@ "entry-label": "Voir les détails", "kavita-tooltip": "Votre évaluation + évaluation globale", "kavita-rating-title": "Votre évaluation", - "close": "{{common.close}}" + "close": "{{common.close}}", + "critic": "{{review-card.critic}}" }, "badge-expander": { "more-items": "et {{count}} de plus" @@ -800,7 +806,8 @@ "more": "Plus", "customize": "{{settings.customize}}", "edit": "{{common.edit}}", - "cancel-edit": "Fermer la réorganisation" + "cancel-edit": "Fermer la réorganisation", + "browse-people": "Parcourir les personnes" }, "library-settings-modal": { "close": "{{common.close}}", @@ -853,7 +860,9 @@ "exclude-patterns-tooltip": "Configurez un ensemble de modèles (syntaxe Global) que Kavita fera correspondre lors du scan des répertoires et exclura des résultats du scanner.", "help": "{{common.help}}", "allow-metadata-matching-label": "Permettre la correspondance des métadonnées", - "allow-metadata-matching-tooltip": "Kavita doit-il télécharger les métadonnées des séries de cette bibliothèque ? Cela ne se produira que si le serveur dispose d'un abonnement actif à Kavita+." + "allow-metadata-matching-tooltip": "Kavita doit-il télécharger les métadonnées des séries de cette bibliothèque ? Cela ne se produira que si le serveur dispose d'un abonnement actif à Kavita+.", + "enable-metadata-label": "Activer les métadonnées (ComicInfo/Epub/PDF)", + "enable-metadata-tooltip": "Permet à Kavita de lire les fichiers de métadonnées. Ceux-ci remplacent l'analyse des noms de fichiers." }, "file-type-group-pipe": { "archive": "Archive", @@ -863,30 +872,30 @@ }, "reader-settings": { "general-settings-title": "Réglages généraux", - "font-family-label": "{{user-preferences.font-family-label}}", - "font-size-label": "{{user-preferences.font-size-book-label}}", - "line-spacing-label": "{{user-preferences.line-height-book-label}}", - "margin-label": "{{user-preferences.margin-book-label}}", + "font-family-label": "{{manage-reading-profiles.font-family-label}}", + "font-size-label": "{{manage-reading-profiles.font-size-book-label}}", + "line-spacing-label": "{{manage-reading-profiles.line-height-book-label}}", + "margin-label": "{{manage-reading-profiles.margin-book-label}}", "reset-to-defaults": "Réinitialiser les paramètres par défaut", "reader-settings-title": "Paramètres de lecture", - "reading-direction-label": "{{user-preferences.reading-direction-book-label}}", + "reading-direction-label": "{{manage-reading-profiles.reading-direction-book-label}}", "right-to-left": "De droite à gauche", "left-to-right": "De gauche à droite", "horizontal": "Horizontal", "vertical": "Vertical", - "writing-style-label": "{{user-preferences.writing-style-label}}", + "writing-style-label": "{{manage-reading-profiles.writing-style-label}}", "writing-style-tooltip": "Change la direction du texte. L'horizontale est de gauche à droite, la verticale de haut en bas.", "tap-to-paginate-label": "Appuyez sur Pagination", "tap-to-paginate-tooltip": "Cliquez sur les bords de l'écran pour paginer", "on": "Activé", "off": "Désactivé", - "immersive-mode-label": "{{user-preferences.immersive-mode-label}}", + "immersive-mode-label": "{{manage-reading-profiles.immersive-mode-label}}", "immersive-mode-tooltip": "Cela masquera le menu après un clic sur le document en lecture et activera le toucher pour lister les pages", "fullscreen-label": "Plein écran", "fullscreen-tooltip": "Mettre le lecteur en plein écran", "exit": "Sortir", "enter": "Entrer", - "layout-mode-label": "{{user-preferences.layout-mode-book-label}}", + "layout-mode-label": "{{manage-reading-profiles.layout-mode-book-label}}", "layout-mode-tooltip": "Défilement : met en miroir le fichier epub (généralement une longue page défilante par chapitre).
1 colonne : crée une seule page virtuelle à la fois.
2 colonnes : crée deux pages virtuelles à la fois disposées côte à côte.", "layout-mode-option-scroll": "Faire défiler", "layout-mode-option-1col": "1 Colonne", @@ -895,7 +904,15 @@ "theme-dark": "Sombre", "theme-black": "Noir", "theme-white": "Blanc", - "theme-paper": "Papier" + "theme-paper": "Papier", + "update-parent": "Enregistrer dans {{name}}", + "loading": "chargement", + "create-new": "Nouveau profil à partir d'un profil implicite", + "create-new-tooltip": "Créez un nouveau profil gérable à partir de votre profil implicite actuel", + "reading-profile-updated": "Profil de lecture mis à jour", + "reading-profile-promoted": "Profil de lecture promu", + "line-spacing-min-label": "1x", + "line-spacing-max-label": "2.5x" }, "table-of-contents": { "no-data": "Ce livre n'a pas de table des matières définie dans les métadonnées ou dans un fichier toc" @@ -1336,7 +1353,8 @@ "admin-email-history": "Historique des e-mails", "admin-matched-metadata": "Métadonnées correspondantes", "admin-manage-tokens": "Gérer les jetons utilisateur", - "admin-metadata": "Gérer les métadonnées" + "admin-metadata": "Gérer les métadonnées", + "reading-profiles": "Profils de lecture" }, "collection-detail": { "no-data": "Il n'y a pas d'éléments. Essayez d'ajouter une série.", @@ -1460,7 +1478,10 @@ "logout": "Déconnexion", "all-filters": "Filtres intelligents", "nav-link-header": "Options de navigation", - "close": "{{common.close}}" + "close": "{{common.close}}", + "person-aka-status": "Correspond à un alias", + "browse-genres": "Parcourir les genres", + "browse-tags": "Parcourir les étiquettes" }, "promoted-icon": { "promoted": "{{common.promoted}}" @@ -1592,9 +1613,9 @@ "height": "Hauteur", "width": "Largeur", "width-override-label": "Remplacement de la largeur", - "off": "Désactivé", + "off": "{{reader-settings.off}}", "original": "Original", - "auto-close-menu-label": "{{user-preferences.auto-close-menu-label}}", + "auto-close-menu-label": "{{manage-reading-profiles.auto-close-menu-label}}", "swipe-enabled-label": "Balayage activé", "enable-comic-book-label": "Imiter le comportement de la bande dessinée", "brightness-label": "Luminosité", @@ -1606,7 +1627,13 @@ "no-next-chapter": "Pas de prochain chapitre", "no-prev-chapter": "Pas de chapitre précédent", "emulate-comic-book-label": "{{manage-reading-profiles.emulate-comic-book-label}}", - "series-progress": "Progression de la série : {{percentage}}" + "series-progress": "Progression de la série : {{percentage}}", + "update-parent": "{{reader-settings.update-parent}}", + "loading": "{{reader-settings.loading}}", + "create-new": "{{reader-settings.create-new}}", + "create-new-tooltip": "{{reader-settings.create-new-tooltip}}", + "reading-profile-updated": "Profil de lecture mis à jour", + "reading-profile-promoted": "Profil de lecture promu" }, "metadata-filter": { "filter-title": "{{common.filter}}", @@ -1663,7 +1690,10 @@ "release-year": "Année de sortie", "read-progress": "Dernière lecture", "average-rating": "Note moyenne", - "random": "Aléatoire" + "random": "Aléatoire", + "person-name": "Nom", + "person-series-count": "Nombre de séries", + "person-chapter-count": "Nombre de chapitres" }, "edit-series-modal": { "title": "{{seriesName}} Détails", @@ -1800,8 +1830,8 @@ "cover-image-tab": "{{tabs.cover-tab}}", "tasks-tab": "{{tabs.tasks-tab}}", "info-tab": "{{tabs.info-tab}}", - "pages-label": "{{edit-chapter-modal.pages-count}}", - "words-label": "{{edit-chapter-modal.length-title}}", + "pages-label": "{{edit-chapter-modal.pages-label}}", + "words-label": "{{edit-chapter-modal.words-label}}", "pages-count": "{{edit-chapter-modal.pages-count}}", "words-count": "{{edit-chapter-modal.words-count}}", "reading-time-label": "{{edit-chapter-modal.reading-time-label}}", @@ -2022,7 +2052,7 @@ "reading-lists": "{{side-nav.reading-lists}}", "bookmarks": "{{side-nav.bookmarks}}", "all-series": "{{side-nav.all-series}}", - "browse-authors": "{{side-nav.browse-authors}}" + "browse-authors": "{{side-nav.browse-people}}" }, "filter-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -2395,7 +2425,8 @@ "confirm": "Confirmer", "ok": "Ok", "cancel": "{{common.cancel}}", - "info": "Info" + "info": "Info", + "prompt": "Question" }, "person-detail": { "all-roles": "Roles", @@ -2403,7 +2434,9 @@ "known-for-title": "Connu pour", "individual-role-title": "En tant que {{role}}", "browse-person-title": "Toutes les œuvres de {{name}}", - "anilist-url": "{{edit-person-modal.anilist-tooltip}}" + "anilist-url": "{{edit-person-modal.anilist-tooltip}}", + "aka-title": "Également connu sous le nom de ", + "no-info": "Aucune information sur cette personne" }, "edit-person-modal": { "save": "{{common.save}}", @@ -2426,7 +2459,11 @@ "required-field": "{{validations.required-field}}", "cover-image-description": "{{edit-series-modal.cover-image-description}}", "cover-image-description-extra": "Vous pouvez également télécharger une couverture à partir de CoversDB, si elle est disponible.", - "download-coversdb": "Télécharger à partir de CoversDB" + "download-coversdb": "Télécharger à partir de CoversDB", + "aliases-tab": "Alias", + "aliases-label": "Modifier les alias", + "alias-overlap": "Cet alias pointe déjà vers une autre personne ou est le nom de cette personne, envisagez de les fusionner.", + "aliases-tooltip": "Lorsqu'une série est étiquetée avec l'alias d'une personne, cette personne est attribuée plutôt que d'en créer une nouvelle. Lorsque vous supprimez un alias, vous devez rescanner la série pour que la modification soit prise en compte." }, "changelog-update-item": { "added": "Ajouté", @@ -2447,7 +2484,7 @@ "description": "Sélectionnez une correspondance pour recâbler les métadonnées de Kavita+ et régénérer les événements de scrobbling. L'option Ne pas faire correspondre peut être utilisée pour empêcher Kavita de faire correspondre les métadonnées et le scrobbler.", "close": "{{common.close}}", "save": "{{common.save}}", - "query-tooltip": "Entrez le nom de la série, l'URL AniList/MyAnimeList. Les URL utiliseront une recherche directe.", + "query-tooltip": "Entrez le nom de la série, l'URL AniList/MyAnimeList/ComicBookRoundup. Les URL utiliseront une recherche directe.", "dont-match-tooltip": "Exclure cette série de la correspondance et du scrobbling", "dont-match-label": "Ne pas correspondre", "search": "Recherche", @@ -2492,7 +2529,9 @@ "library-name-header": "Bibliothèque", "match-alt": "Correspondance {{seriesName}}", "actions-header": "Actions", - "dont-match-status-label": "{{dont-match-label}}" + "dont-match-status-label": "{{dont-match-label}}", + "library-type": "Type de bibliothèque", + "matched-state-label": "État de correspondance" }, "manage-metadata-settings": { "enable-relations-label": "Relations", @@ -2538,14 +2577,21 @@ "enable-chapter-title-label": "Titre", "enable-chapter-release-date-label": "Date de diffusion", "enable-chapter-summary-tooltip": "{{manage-metadata-settings.summary-tooltip}}", - "enable-chapter-summary-label": "{{manage-metadata-settings.summary-label}}" + "enable-chapter-summary-label": "{{manage-metadata-settings.summary-label}}", + "enable-chapter-release-date-tooltip": "Permettre la saisie de la date de publication d'un chapitre ou d'un numéro", + "enable-chapter-publisher-label": "Éditeur", + "enable-chapter-publisher-tooltip": "Permettre la saisie de l'éditeur des chapitres ou des numéros", + "enable-chapter-cover-label": "Couverture du chapitre", + "chapter-header": "Champs du chapitre", + "enable-chapter-cover-tooltip": "Permettre de définir la couverture d'un chapitre ou d'un numéro" }, "match-series-result-item": { "chapter-count": "{{common.chapter-count}}", "updating-metadata-status": "Mise à jour des métadonnées", "volume-count": "{{server-stats.volume-count}}", "releasing": "Sorties", - "details": "Voir la page" + "details": "Voir la page", + "issue-count": "{{common.issue-count}}" }, "metadata-setting-field-pipe": { "age-rating": "{{metadata-fields.age-rating-title}}", @@ -2597,5 +2643,64 @@ "review-label": "Avis", "title": "Editer Avis", "min-length": "L'avis doit faire au moins {{count}} caractères" + }, + "browse-people": { + "title": "Parcourir les personnes", + "author-count": "{{num}} Personnes", + "cover-image-description": "{{edit-series-modal.cover-image-description}}", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}", + "roles-label": "Rôles", + "sort-label": "Trier", + "name-label": "Nom", + "issue-count-label": "Nombre de numéros", + "series-count-label": "Nombre de séries" + }, + "browse-genres": { + "title": "Parcourir les genres", + "genre-count": "{{num}} Genres", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}" + }, + "browse-tags": { + "title": "Parcourir les étiquettes", + "genre-count": "{{num}} Etiquettes", + "issue-count": "{{common.issue-count}}", + "series-count": "{{common.series-count}}" + }, + "bulk-set-reading-profile-modal": { + "title": "Définir le profil de lecture", + "close": "{{common.close}}", + "filter-label": "{{common.filter}}", + "clear": "{{common.clear}}", + "no-data": "Aucune collection n'a encore été créée", + "loading": "{{common.loading}}", + "create": "{{common.create}}", + "bound": "Liaison" + }, + "merge-person-modal": { + "title": "{{personName}}", + "close": "{{common.close}}", + "save": "{{common.save}}", + "src": "Fusionner une personne", + "merge-warning": "Si vous poursuivez, la personne sélectionnée sera supprimée. Le nom de la personne sélectionnée sera ajouté en tant qu'alias et tous ses rôles seront transférés.", + "alias-title": "Nouveaux alias", + "known-for-title": "Connu pour" + }, + "browse-title-pipe": { + "publication-status": "{{value}} travaux", + "age-rating": "Noté {{value}}", + "user-rating": "{{value}} classement par étoiles", + "tag": "A l'étiquette {{value}}", + "translator": "Traduit par {{value}}", + "character": "Possède le caractère {{value}}", + "publisher": "Publié par {{value}}", + "editor": "Edité par {{value}}", + "artist": "Dessiné par {{value}}", + "letterer": "Lettré par {{value}}", + "colorist": "Coloré par {{value}}", + "inker": "Encré par {{value}}", + "penciller": "Crayonné par {{value}}", + "writer": "Ecrit par {{value}}" } } diff --git a/UI/Web/src/assets/langs/pt.json b/UI/Web/src/assets/langs/pt.json index 638956242..75bf126d0 100644 --- a/UI/Web/src/assets/langs/pt.json +++ b/UI/Web/src/assets/langs/pt.json @@ -85,7 +85,7 @@ }, "user-preferences": { "title": "Painel do Utilizador", - "pref-description": "Estas definições globais estão ligadas à sua conta. As definições do Leitor estão nos Perfis de Leitura", + "pref-description": "Estas definições globais estão ligadas à sua conta. As definições do Leitor estão nos Perfis de Leitura.", "account-tab": "{{tabs.account-tab}}", "preferences-tab": "{{tabs.preferences-tab}}", "theme-tab": "{{tabs.theme-tab}}", @@ -822,7 +822,7 @@ "library-name-unique": "O nome da biblioteca tem de ser único", "last-scanned-label": "Último Scan:", "type-label": "Tipo", - "type-tooltip": "O tipo de biblioteca indica como os nomes dos ficheiros são processados e se o UI mostra Capítulos (Manga) vs Números (BDs). Verifique o wiki para mais detalhes relativamente às diferenças entre os dois tipos de bibliotecas.", + "type-tooltip": "O tipo de biblioteca indica como os nomes dos ficheiros são processados e se o UI mostra Capítulos (Manga) vs Edições (BDs). Verifique o wiki para mais detalhes relativamente às diferenças entre os dois tipos de bibliotecas.", "kavitaplus-eligible-label": "Kavita+ Elegível", "kavitaplus-eligible-tooltip": "Suporta as funcionalidades de metadados do Kavita+ ou Scrobbling", "folder-description": "Adicione pastas à sua biblioteca", @@ -907,7 +907,12 @@ "theme-paper": "Papel", "update-parent": "Gravar para {{name}}", "loading": "a carregar", - "create-new": "Novo perfil implícito" + "create-new": "Novo perfil implícito", + "create-new-tooltip": "Crie um novo perfil a partir do seu perfil implícito atual", + "reading-profile-updated": "Perfil de leitura atualizado", + "reading-profile-promoted": "Perfil de leitura promovido", + "line-spacing-min-label": "1x", + "line-spacing-max-label": "2.5x" }, "table-of-contents": { "no-data": "Este livro não tem um Indíce definido nos metadados ou um ficheiro toc" @@ -1348,7 +1353,8 @@ "admin-matched-metadata": "Metadados com Correspondência", "admin-manage-tokens": "Gerir Tokens de Utilizador", "scrobble-holds": "Pausas de Scrobble", - "admin-metadata": "Gerir Metadados" + "admin-metadata": "Gerir Metadados", + "reading-profiles": "Perfis de Leitura" }, "collection-detail": { "no-data": "Não existem itens. Tente adicionar uma série.", @@ -1472,7 +1478,10 @@ "logout": "Terminar Sessão", "all-filters": "Filtros Inteligentes", "nav-link-header": "Opções de Navegação", - "close": "{{common.close}}" + "close": "{{common.close}}", + "person-aka-status": "Corresponde um pseudónimo", + "browse-genres": "Explorar Géneros", + "browse-tags": "Explorar Etiquetas" }, "promoted-icon": { "promoted": "{{common.promoted}}" @@ -1618,7 +1627,13 @@ "no-next-chapter": "Não há Capítulo Seguinte", "no-prev-chapter": "Não há Capítulo Anterior", "emulate-comic-book-label": "{{manage-reading-profiles.emulate-comic-book-label}}", - "series-progress": "Progresso da Série: {{percentage}}" + "series-progress": "Progresso da Série: {{percentage}}", + "update-parent": "{{reader-settings.update-parent}}", + "loading": "{{reader-settings.loading}}", + "create-new": "{{reader-settings.create-new}}", + "create-new-tooltip": "{{reader-settings.create-new-tooltip}}", + "reading-profile-updated": "Perfil de leitura atualizado", + "reading-profile-promoted": "Perfil de leitura promovido" }, "metadata-filter": { "filter-title": "{{common.filter}}", @@ -1675,7 +1690,10 @@ "release-year": "Ano de Lançamento", "read-progress": "Última Leitura", "average-rating": "Classificação Média", - "random": "Aleatório" + "random": "Aleatório", + "person-name": "Nome", + "person-series-count": "Número de Séries", + "person-chapter-count": "Número de Capítulos" }, "edit-series-modal": { "title": "Detalhes de {{seriesName}}", @@ -2208,7 +2226,12 @@ "bulk-delete-libraries": "Tem a certeza que deseja eliminar {{count}} bibliotecas?", "match-success": "Séries correspondidas com sucesso", "webtoon-override": "O modo Webtoon vai ser usado porque as imagens representam um webtoon.", - "scrobble-gen-init": "Foi colocada uma tarefa em fila para gerar eventos de scrobble a partir do historial de leitura e classificações existentes, sincronizando-as com os serviços que estão configurados." + "scrobble-gen-init": "Foi colocada uma tarefa em fila para gerar eventos de scrobble a partir do historial de leitura e classificações existentes, sincronizando-as com os serviços que estão configurados.", + "series-added-want-to-read": "Série adicionada da lista Leituras Futuras", + "confirm-delete-multiple-volumes": "Tem a certeza que deseja apagar {{count}} volumes? Os ficheiros em disco não serão alterados.", + "series-bound-to-reading-profile": "Série vinculada ao Perfil de Leitura {{name}}", + "library-bound-to-reading-profile": "Biblioteca vinculada ao Perfil de Leitura {{name}}", + "external-match-rate-error": "O Kavita ficou sem taxa à procura de {{seriesName}}. Tente novamente em 5 minutos." }, "read-time-pipe": { "less-than-hour": "<1 Hora", @@ -2281,7 +2304,16 @@ "copy-settings": "Copiar Definições De", "match": "Correspondência", "match-tooltip": "Fazer a associação manual das Séries com o Kavita+", - "reorder": "Reordenar" + "reorder": "Reordenar", + "reading-profiles": "Perfis de Leitura", + "set-reading-profile": "Escolher Perfil de Leitura", + "set-reading-profile-tooltip": "Vincular um Perfil de Leitura a esta Biblioteca", + "clear-reading-profile": "Limpar Perfil de Leitura", + "clear-reading-profile-tooltip": "Limpar Perfil de Leitura para esta Biblioteca", + "cleared-profile": "Perfil de Leitura Limpo", + "rename": "Renomear", + "rename-tooltip": "Renomear o Filtro Inteligente", + "merge": "Juntar" }, "preferences": { "left-to-right": "Esquerda para Direita", @@ -2386,7 +2418,7 @@ "series-count": "{{num}} Séries", "item-count": "{{num}} Itens", "book-num": "Livro", - "issue-hash-num": "Número #", + "issue-hash-num": "Edição #", "issue-num": "Número", "chapter-num": "Capítulo", "volume-num": "Volume", @@ -2395,19 +2427,21 @@ "issue-num-shorthand": "#{{num}}", "volume-num-shorthand": "Vol {{num}}", "book-nums": "Livros", - "issue-nums": "Números", + "issue-nums": "Edições", "chapter-nums": "Capítulos", "volume-nums": "Volumes", "author-count": "{{num}} Autores", "chapter-count": "{{num}} Capítulos", - "no-data": "Sem Dados" + "no-data": "Sem Dados", + "issue-count": "{{num}} Edições" }, "confirm": { "alert": "Alerta", "confirm": "Confirmar", "cancel": "{{common.cancel}}", "info": "Informação", - "ok": "Ok" + "ok": "Ok", + "prompt": "Questão" }, "person-detail": { "all-roles": "Funções", @@ -2440,7 +2474,11 @@ "cover-image-description-extra": "Em alternativa pode descarregar uma capa a partir do CoversDB, se disponível.", "anilist-tooltip": "https://anilist.co/staff/{AniListId}/", "asin-tooltip": "https://www.amazon.com/stores/J.K.-Rowling/author/{ASIN}", - "download-coversdb": "Descarregar de CoversDB" + "download-coversdb": "Descarregar de CoversDB", + "aliases-tab": "Pseudónimos", + "aliases-label": "Editar pseudónimos", + "alias-overlap": "Este pseudónimo refere-se a outra pessoa ou é o nome desta pessoa, considere juntar as duas pessoas.", + "aliases-tooltip": "Quando uma série é etiquetada com o pseudónimo de uma pessoa, essa pessoa é associada à serie em vez de ser criada uma pessoa nova. Quando um pseudónimo for apagado, será necessário voltar a analisar a série para que essa alteração tenha efeito." }, "changelog-update-item": { "fixed": "Corrigido", @@ -2569,15 +2607,15 @@ "overrides-label": "Substituições", "person-roles-label": "Papéis", "enable-chapter-title-label": "Título", - "enable-chapter-title-tooltip": "Permitir que o Título do Capítulo/Número seja gravado", + "enable-chapter-title-tooltip": "Permitir que o Título do Capítulo/Edição seja gravado", "enable-chapter-summary-label": "{{manage-metadata-settings.summary-label}}", "enable-chapter-summary-tooltip": "{{manage-metadata-settings.summary-tooltip}}", "enable-chapter-release-date-label": "Data de Lançamento", - "enable-chapter-release-date-tooltip": "Permitir que a Data de Lançamento do Capítulo/Número seja gravado", + "enable-chapter-release-date-tooltip": "Permitir que a Data de Lançamento do Capítulo/Edição seja gravado", "enable-chapter-publisher-label": "Editora", - "enable-chapter-publisher-tooltip": "Permitir que a Editor do Capítulo/Número seja gravado", + "enable-chapter-publisher-tooltip": "Permitir que o Editor do Capítulo/Edição seja gravado", "enable-chapter-cover-label": "Capa do Capítulo", - "enable-chapter-cover-tooltip": "Permitir que a Capa do Capítulo/Número seja definida", + "enable-chapter-cover-tooltip": "Permitir que a Capa do Capítulo/Editor seja definida", "chapter-header": "Campos do Capítulo" }, "metadata-setting-field-pipe": { @@ -2589,7 +2627,12 @@ "localized-name": "{{edit-series-modal.localized-name-label}}", "publication-status": "{{edit-series-modal.publication-status-title}}", "covers": "Capas", - "start-date": "{{manage-metadata-settings.enable-start-date-label}}" + "start-date": "{{manage-metadata-settings.enable-start-date-label}}", + "chapter-release-date": "Data de Lançamento (Capítulo)", + "chapter-summary": "Sumário (Capítulo)", + "chapter-covers": "Capas (Capítulo)", + "chapter-publisher": "{{person-role-pipe.publisher}} (Capítulo)", + "chapter-title": "Título (Capítulo)" }, "role-localized-pipe": { "admin": "Administrador", @@ -2609,7 +2652,76 @@ "critical": "Crítico" }, "manage-reading-profiles": { - "reset": "{{common.reset}}" + "reset": "{{common.reset}}", + "description": "Nem todas as séries podem ser lidas da mesma maneira, defina perfis de leitura distintos por biblioteca ou séries para que o regresso às suas séries seja o mais simples possível.", + "extra-tip": "Atribua perfis de leitura a partir do menu de ações nas séries ou bibliotecas, ou em massa. Quando alterar definições num leitor, é criado um perfil escondido que memoriza as suas escolhas para essa série (não se aplica a pdfs). Este perfil é removido quando atribui o seu próprio perfil de leitura à série. Pode encontrar mais informação no", + "wiki-title": "wiki", + "profiles-title": "Os seus perfis de leitura", + "default-profile": "Por Defeito", + "add": "{{common.add}}", + "add-tooltip": "O seu novo perfil será gravado depois de lhe fazer qualquer alteração", + "make-default": "Definir por defeito", + "no-selected": "Nenhum perfil selecionado", + "confirm": "Tem a certeza que deseja eliminar o perfil de leitura {{name}}?", + "selection-tip": "Selecione um perfil da lista ou crie um novo à direita no topo", + "image-reader-settings-title": "Leitor de Imagens", + "reading-direction-label": "Direção de Leitura", + "reading-direction-tooltip": "Direção em que deve clicar para ir para a página seguinte. Direita para Esquerda significa que se clica no lado esquerdo do ecrã para ir para a página seguinte.", + "scaling-option-label": "Opções de Escala", + "scaling-option-tooltip": "Como a imagem é dimensionada no seu ecrã.", + "page-splitting-label": "Divisão de Páginas", + "page-splitting-tooltip": "Como dividir uma imagem que ocupa a largura toda (p.e., a imagem da esquerda e direita estão juntas)", + "reading-mode-label": "Modo de Leitura", + "reading-mode-tooltip": "Alterar o leitor para paginar verticalmente, horizontalmente ou para ter scroll infinito", + "layout-mode-label": "Modo de Layout", + "layout-mode-tooltip": "Mostrar apenas uma imagem no ecrã ou duas imagens lado a lado", + "background-color-label": "Cor de fundo", + "background-color-tooltip": "Cor de fundo do Leitor de Imagens", + "auto-close-menu-label": "Fechar Menu Automaticamente", + "auto-close-menu-tooltip": "Se o menu deve fechar automaticamente", + "show-screen-hints-label": "Mostrar Dicas no Ecrã", + "show-screen-hints-tooltip": "Mostrar dicas no ecrã que ajudam a compreender a área e direção de paginação", + "emulate-comic-book-label": "Emular livro de banda desenhada", + "emulate-comic-book-tooltip": "Aplica um efeito de sombra para emular a leitura a partir de um livro físico", + "swipe-to-paginate-label": "Deslizar para Paginar", + "swipe-to-paginate-tooltip": "Se o gesto de deslizar no ecrã causa que as páginas seguinte ou anterior sejam mostradas", + "allow-auto-webtoon-reader-label": "Modo Leitor Webtoon Automático", + "allow-auto-webtoon-reader-tooltip": "Muda para o modo Leitor Webtoon se as páginas indicam que é um webtoon. Podem acontecer falsos positivos.", + "width-override-label": "{{manga-reader.width-override-label}}", + "width-override-tooltip": "Substituir a largura das imagens no leitor", + "disable-width-override-label": "Desabilitar a substituição da largura", + "disable-width-override-tooltip": "Evitar que a substituição da largura tenha efeito quando o seu ecrã está configurado com o valor mínimo aceite", + "book-reader-settings-title": "Leitor de Livros", + "tap-to-paginate-label": "Tocar par Paginar", + "tap-to-paginate-tooltip": "Se as laterais do leitor de livros permitem que o toque mostre a página anterior/seguinte", + "immersive-mode-label": "Modo Imersivo", + "immersive-mode-tooltip": "Irá esconder o menu quando se clicar no documento e ligar o toque para paginar", + "reading-direction-book-label": "Direção de Leitura", + "reading-direction-book-tooltip": "Direção a clicar para ir para a página seguinte. Direita para Esquerda significa que se tem de clicar no lado esquerdo do ecrã para ir para a página seguinte.", + "font-family-label": "Família de Font", + "font-family-tooltip": "Família de fonts que vai ser carregada. Por defeito é carregada a font usada pelo livro", + "writing-style-label": "Estilo de Escrita", + "writing-style-tooltip": "Muda a direção do texto. A direção horizontal é da esquerda para direita, vertical é do topo para o fundo.", + "layout-mode-book-label": "Modo de Layout", + "layout-mode-book-tooltip": "Como o conteúdo deve ser disposto. O modo Scroll é como o livro define. O modo 1 ou 2 Colunas, ajusta-se à altura do dispositivo e tenta mostrar 1 ou 2 colunas de texto por página", + "color-theme-book-label": "Tema de Cores", + "color-theme-book-tooltip": "Que tema de cores deve ser aplicado ao conteúdo e menu do leitor de livros", + "font-size-book-label": "Tamanho da Font", + "font-size-book-tooltip": "Percentagem de dimensionamento que vai ser aplicado à font do livro", + "line-height-book-label": "Espaçamento entre Linhas", + "line-height-book-tooltip": "O espaçamento que existe entre linhas do livro", + "margin-book-label": "Margem", + "margin-book-tooltip": "Quando espaçamento existe de cada lado do ecrã. Esta opção será colocada a 0 em dispositivos móveis independentemente do valor que é aqui colocado.", + "pdf-reader-settings-title": "Leitor de PDF", + "pdf-scroll-mode-label": "Modo de Scroll", + "pdf-scroll-mode-tooltip": "Como é feito o scroll pelas páginas. Vertical/Horizontal e Tocar para Paginar (sem scroll)", + "pdf-spread-mode-label": "Modo de Layout", + "pdf-spread-mode-tooltip": "Como as páginas devem ser dispostas. Individual ou dupla (ímpar/par)", + "pdf-theme-label": "Tema", + "pdf-theme-tooltip": "O tema de cores do leitor", + "reading-profile-series-settings-title": "Séries", + "reading-profile-library-settings-title": "Biblioteca", + "delete": "{{common.delete}}" }, "reviews": { "user-reviews-local": "Criticas Locais", @@ -2647,5 +2759,59 @@ "genre-count": "{{num}} Etiquetas", "issue-count": "{{common.issue-count}}", "series-count": "{{common.series-count}}" + }, + "bulk-set-reading-profile-modal": { + "title": "Definir perfil de leitura", + "close": "{{common.close}}", + "filter-label": "{{common.filter}}", + "clear": "{{common.clear}}", + "no-data": "Ainda não foram criadas coleções", + "loading": "{{common.loading}}", + "create": "{{common.create}}", + "bound": "Vinculado" + }, + "merge-person-modal": { + "title": "{{personName}}", + "close": "{{common.close}}", + "save": "{{common.save}}", + "src": "Juntar Pessoa", + "merge-warning": "Se continuar, a pessoa selecionada será removida. O nome da pessoa selecionada será adicionada como pseudónimo e todas as suas funções serão transferidas.", + "alias-title": "Novos pseudónimos", + "known-for-title": "Conhecido por" + }, + "browse-title-pipe": { + "publication-status": "{{value}} trabalhos", + "age-rating": "Avaliado com {{value}}", + "user-rating": "{{value}} classificação em estrelas", + "tag": "Tem Etiqueta {{value}}", + "translator": "Traduzido por {{value}}", + "character": "Tem o personagem {{value}}", + "publisher": "Publicado por {{value}}", + "editor": "Editado por {{value}}", + "artist": "Desenhado por {{value}}", + "letterer": "Com letras de {{value}}", + "colorist": "Colorido por {{value}}", + "inker": "Arte-finalizado por {{value}}", + "penciller": "Desenhado por {{value}}", + "writer": "Escrito por {{value}}", + "genre": "Tem o Género {{value}}", + "library": "Dentro da biblioteca {{value}}", + "format": "Formato de {{value}}", + "release-year": "Lançado em {{value}}", + "imprint": "Impressão de {{value}}", + "team": "Equipa {{value}}", + "location": "Na localização {{value}}" + }, + "generic-filter-field-pipe": { + "person-role": "Função", + "person-name": "Nome", + "person-series-count": "Número de Séries", + "person-chapter-count": "Número de Capítulos" + }, + "breakpoint-pipe": { + "never": "Nunca", + "mobile": "Móvel", + "tablet": "Tablet", + "desktop": "Desktop" } } diff --git a/UI/Web/src/assets/langs/pt_BR.json b/UI/Web/src/assets/langs/pt_BR.json index 9df1580a6..f8427c85b 100644 --- a/UI/Web/src/assets/langs/pt_BR.json +++ b/UI/Web/src/assets/langs/pt_BR.json @@ -822,7 +822,7 @@ "library-name-unique": "O nome da biblioteca deve ser exclusivo", "last-scanned-label": "Último Escaneamento:", "type-label": "Tipo", - "type-tooltip": "O tipo de biblioteca determina como os nomes dos arquivos são analisados e se a IU mostra Capítulos (mangá) versus Números (quadrinhos). Verifique o wiki para obter mais detalhes sobre as diferenças entre os tipos de biblioteca.", + "type-tooltip": "O tipo de biblioteca determina como os nomes dos arquivos são analisados e se a IU mostra Capítulos (mangá) versus Edições (quadrinhos). Verifique o wiki para obter mais detalhes sobre as diferenças entre os tipos de biblioteca.", "kavitaplus-eligible-label": "Kavita+ Elegível", "kavitaplus-eligible-tooltip": "Kavita+ suporta as capacidades de metadados ou Scrobbling", "folder-description": "Adicionar pastas à sua biblioteca", @@ -1153,7 +1153,7 @@ "log-label": "Dias de Registros", "log-tooltip": "O número de registros a serem mantidos. O padrão é 30, o mínimo é 1 e o máximo é 30.", "logging-level-label": "Nível de Registro", - "logging-level-tooltip": "Use a depuração para ajudar a identificar problemas. A depuração pode consumir muito espaço em disco.", + "logging-level-tooltip": "Use a depuração para identificar problemas. A depuração pode consumir muito espaço em disco.", "cache-size-label": "Tamanho do Cache", "cache-size-tooltip": "A quantidade de memória permitida para cache de APIs pesadas. O padrão é 75 MB.", "on-deck-last-progress-label": "Última Leitura Na Estante (dias)", @@ -1545,7 +1545,7 @@ "import-description": "Para começar, importe um arquivo .cbl. O Kavita executará várias verificações antes de importar. Algumas etapas bloquearão o avanço devido a problemas com o arquivo.", "validate-description": "Todos os arquivos foram validados para verificar se há alguma operação a ser feita na lista. Quaisquer listas que falharam não passarão para a próxima etapa. Corrija os arquivos CBL e tente novamente.", "validate-warning": "Existem problemas com o CBL que impedirão uma importação. Corrija esses problemas e tente novamente.", - "validate-no-issue-description": "Nenhum problema encontrado com CBL, pressione próximo.", + "validate-no-issue-description": "Nenhum problema encontrado com CBL, pressione seguinte.", "dry-run-description": "Esta é uma simulação e mostra o que acontecerá se você pressionar Avançar e executar a importação. Todas as falhas não serão importadas.", "prev": "Anterior", "import": "Importar", @@ -1761,7 +1761,7 @@ "chapter-title": "Capítulo", "volume-num": "{{common.volume-num}}", "highest-count-tooltip": "Número mais alto encontrado em todos os ComicInfo da Série", - "max-issue-tooltip": "Campo Máx. de Número ou Volume de todos ComicInfo nas Séries", + "max-issue-tooltip": "Campo Máx. de Edições ou Volume de todos ComicInfo nas Séries", "force-refresh": "Forçar Atualização", "force-refresh-tooltip": "Forçar a atualização de metadados externos do Kavita+", "loose-leaf-volume": "Capítulos de Folhas Soltas", @@ -2193,7 +2193,7 @@ "confirm-library-delete": "Tem certeza de que deseja excluir a biblioteca {{name}}? Você não pode desfazer esta ação.", "confirm-library-type-change": "Alterar o tipo de biblioteca acionará uma nova verificação com regras de análise diferentes e poderá levar à recriação de séries e, portanto, você poderá perder progresso e marcadores. Você deve fazer backup antes de fazer isso. Tem certeza de que deseja continuar?", "confirm-download-size": "O {{entityType}} é {{size}}. Você tem certeza que quer continuar?", - "confirm-download-size-ios": "O iOS tem problemas para baixar arquivos maiores que 200 MB; esse download pode não ser concluído.", + "confirm-download-size-ios": "O iOS tem problemas para baixar arquivos maiores que 200 MB, esse download pode não ser concluído.", "list-doesnt-exist": "Esta lista não existe", "confirm-delete-smart-filter": "Tem certeza de que deseja excluir este Filtro Inteligente?", "smart-filter-deleted": "Filtro Inteligente Excluído", @@ -2418,8 +2418,8 @@ "series-count": "{{num}} Séries", "item-count": "{{num}} Itens", "book-num": "Livro", - "issue-hash-num": "Número #", - "issue-num": "Número", + "issue-hash-num": "Edição #", + "issue-num": "Edição", "chapter-num": "Capítulo", "volume-num": "Volume", "chapter-num-shorthand": "Cap. {{num}}", @@ -2608,15 +2608,15 @@ "overrides-label": "Substituir", "enable-chapter-title-label": "Título", "enable-chapter-publisher-label": "Editora", - "enable-chapter-publisher-tooltip": "Permitir que a Editora do Capítulo/Número seja salvo", + "enable-chapter-publisher-tooltip": "Permitir que a Editora do Capítulo/Edição seja salvo", "enable-chapter-cover-label": "Capa do Capítulo", - "enable-chapter-cover-tooltip": "Permitir que a Capa do Capítulo/Número seja definida", + "enable-chapter-cover-tooltip": "Permitir que a Capa do Capítulo/Edição seja definida", "chapter-header": "Campos de Capítulo", - "enable-chapter-title-tooltip": "Permitir que o Título do Capítulo/Número seja salvo", + "enable-chapter-title-tooltip": "Permitir que o Título do Capítulo/Edição seja salvo", "enable-chapter-summary-label": "{{manage-metadata-settings.summary-label}}", "enable-chapter-summary-tooltip": "{{manage-metadata-settings.summary-tooltip}}", "enable-chapter-release-date-label": "Data de Lançamento", - "enable-chapter-release-date-tooltip": "Permitir que a Data de Lançamento do Capítulo/Número seja salvo" + "enable-chapter-release-date-tooltip": "Permitir que a Data de Lançamento do Capítulo/Edição seja salvo" }, "metadata-setting-field-pipe": { "publication-status": "{{edit-series-modal.publication-status-title}}", diff --git a/UI/Web/src/assets/langs/zh_Hant.json b/UI/Web/src/assets/langs/zh_Hant.json index 935c32021..dcda7d6f1 100644 --- a/UI/Web/src/assets/langs/zh_Hant.json +++ b/UI/Web/src/assets/langs/zh_Hant.json @@ -14,7 +14,7 @@ "on-deck-title": "繼續閱讀", "recently-updated-title": "最近更新系列", "recently-added-title": "最近新增系列", - "more-in-genre-title": "{{genre}} 更多的內容分類" + "more-in-genre-title": "更多{{genre}}作品" }, "edit-user": { "edit": "{{common.edit}}", From 8deb96cf48e97fabd9e331d26e6db7d860bfb96f Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Thu, 3 Jul 2025 00:38:26 +0200 Subject: [PATCH 09/15] [skip ci] Weblate Changes (#3885) Co-authored-by: Gregory.Open --- UI/Web/src/assets/langs/fr.json | 38 ++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/UI/Web/src/assets/langs/fr.json b/UI/Web/src/assets/langs/fr.json index 9dbda1bbf..8aac96a4e 100644 --- a/UI/Web/src/assets/langs/fr.json +++ b/UI/Web/src/assets/langs/fr.json @@ -2226,7 +2226,12 @@ "bulk-delete-libraries": "Êtes vous sûr de vouloir supprimer {{count}} bibliothèque(s) ?", "webtoon-override": "Passage en mode Webtoon en raison d'images représentant un webtoon.", "match-success": "Séries avec la bonne correspondance", - "scrobble-gen-init": "Mise en attente d'une tâche pour générer des événements de scrobble à partir de l'historique de lecture et des évaluations, en les synchronisant avec les services connectés." + "scrobble-gen-init": "Mise en attente d'une tâche pour générer des événements de scrobble à partir de l'historique de lecture et des évaluations, en les synchronisant avec les services connectés.", + "series-added-want-to-read": "Série ajoutée à la liste \"A lire\"", + "confirm-delete-multiple-volumes": "Êtes-vous sûr de vouloir supprimer les {{count}} volumes ? Cela ne modifiera pas les fichiers sur le disque.", + "series-bound-to-reading-profile": "Série liée au Profil de lecture {{nom}}", + "library-bound-to-reading-profile": "Bibliothèque liée au Profil de lecture {{nom}}", + "external-match-rate-error": "Kavita a manqué de rythme en cherchant {{seriesName}}. Réessayez dans 5 minutes." }, "read-time-pipe": { "less-than-hour": "<1 Heure", @@ -2299,7 +2304,12 @@ "copy-settings": "Copier les paramètres depuis", "match-tooltip": "Associer les séries manuellement avec Kavita+", "match": "Correspondance", - "reorder": "Réorganiser" + "reorder": "Réorganiser", + "reading-profiles": "Profils de lecture", + "set-reading-profile": "Définir le profil de lecture", + "set-reading-profile-tooltip": "Lier un profil de lecture à cette bibliothèque", + "clear-reading-profile": "Effacer le Profil de lecture", + "clear-reading-profile-tooltip": "Efface le Profil de lecture pour cette bibliothèque" }, "preferences": { "left-to-right": "De gauche à droite", @@ -2602,7 +2612,12 @@ "summary": "{{filter-field-pipe.summary}}", "publication-status": "{{edit-series-modal.publication-status-title}}", "tags": "{{metadata-fields.tags-title}}", - "localized-name": "{{edit-series-modal.localized-name-label}}" + "localized-name": "{{edit-series-modal.localized-name-label}}", + "chapter-release-date": "Date de publication (Chapitre)", + "chapter-summary": "Résumé (Chapitre)", + "chapter-covers": "Couvertures (Chapitre)", + "chapter-publisher": "{{person-role-pipe.publisher}} (Chapitre)", + "chapter-title": "Titre (Chapitre)" }, "email-history": { "not-sent-tooltip": "Non envoyé", @@ -2691,7 +2706,7 @@ "publication-status": "{{value}} travaux", "age-rating": "Noté {{value}}", "user-rating": "{{value}} classement par étoiles", - "tag": "A l'étiquette {{value}}", + "tag": "Étiquette {{value}}", "translator": "Traduit par {{value}}", "character": "Possède le caractère {{value}}", "publisher": "Publié par {{value}}", @@ -2701,6 +2716,19 @@ "colorist": "Coloré par {{value}}", "inker": "Encré par {{value}}", "penciller": "Crayonné par {{value}}", - "writer": "Ecrit par {{value}}" + "writer": "Ecrit par {{value}}", + "genre": "Genre {{value}}", + "library": "Au sein de la bibliothèque {{value}}", + "format": "Format de {{value}}", + "release-year": "Publié en {{value}}", + "imprint": "Empreinte de {{value}}", + "team": "Équipe {{value}}", + "location": "Localisation {{value}}" + }, + "generic-filter-field-pipe": { + "person-role": "Rôle", + "person-name": "Nom", + "person-series-count": "Nombre de séries", + "person-chapter-count": "Nombre de chapitres" } } From 1389eb6320be3f797a75aede2a98298919cda142 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sat, 5 Jul 2025 21:30:06 +0200 Subject: [PATCH 10/15] [skip ci] Weblate Changes (#3893) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dark77 Co-authored-by: Gregory.Open Co-authored-by: Mateusz Co-authored-by: 안세훈 --- API/I18N/sk.json | 125 +++++++++++++++++++++++++++++++- UI/Web/src/assets/langs/fr.json | 95 ++++++++++++++++++++++-- UI/Web/src/assets/langs/ko.json | 18 +++-- UI/Web/src/assets/langs/pl.json | 18 ++--- UI/Web/src/assets/langs/sk.json | 4 +- 5 files changed, 236 insertions(+), 24 deletions(-) diff --git a/API/I18N/sk.json b/API/I18N/sk.json index a48add072..ef267ed02 100644 --- a/API/I18N/sk.json +++ b/API/I18N/sk.json @@ -1,5 +1,5 @@ { - "disabled-account": "Váš účet je zakázaný. Kontaktujte správcu servera.", + "disabled-account": "Váš účet je deaktivovaný. Kontaktujte správcu servera.", "register-user": "Niečo sa pokazilo pri registrácii užívateľa", "confirm-email": "Najprv musíte potvrdiť svoj e-mail", "locked-out": "Boli ste zamknutí z dôvodu veľkého počtu neúspešných pokusov o prihlásenie. Počkajte 10 minút.", @@ -88,5 +88,126 @@ "generic-device-create": "Pri vytváraní zariadenia sa vyskytla chyba", "series-doesnt-exist": "Séria neexistuje", "volume-doesnt-exist": "Zväzok neexistuje", - "library-name-exists": "Názov knižnice už existuje. Prosím, vyberte si pre daný server jedinečný názov." + "library-name-exists": "Názov knižnice už existuje. Prosím, vyberte si pre daný server jedinečný názov.", + "cache-file-find": "Nepodarilo sa nájsť obrázok vo vyrovnávacej pamäti. Znova načítajte a skúste to znova.", + "name-required": "Názov nemôže byť prázdny", + "valid-number": "Musí to byť platné číslo strany", + "duplicate-bookmark": "Duplicitný záznam záložky už existuje", + "reading-list-permission": "Nemáte povolenia na tento zoznam na čítanie alebo zoznam neexistuje", + "reading-list-position": "Nepodarilo sa aktualizovať pozíciu", + "reading-list-updated": "Aktualizované", + "reading-list-item-delete": "Položku(y) sa nepodarilo odstrániť", + "reading-list-deleted": "Zoznam na čítanie bol odstránený", + "generic-reading-list-delete": "Pri odstraňovaní zoznamu na čítanie sa vyskytol problém", + "generic-reading-list-update": "Pri aktualizácii zoznamu na čítanie sa vyskytol problém", + "generic-reading-list-create": "Pri vytváraní zoznamu na čítanie sa vyskytol problém", + "reading-list-doesnt-exist": "Zoznam na čítanie neexistuje", + "series-restricted": "Používateľ nemá prístup k tejto sérii", + "generic-scrobble-hold": "Pri pauznutí funkcie sa vyskytla chyba", + "libraries-restricted": "Používateľ nemá prístup k žiadnym knižniciam", + "no-series": "Nepodarilo sa získať sériu pre knižnicu", + "no-series-collection": "Nepodarilo sa získať sériu pre kolekciu", + "generic-series-delete": "Pri odstraňovaní série sa vyskytol problém", + "generic-series-update": "Pri aktualizácii série sa vyskytla chyba", + "series-updated": "Úspešne aktualizované", + "update-metadata-fail": "Nepodarilo sa aktualizovať metadáta", + "age-restriction-not-applicable": "Bez obmedzenia", + "generic-relationship": "Pri aktualizácii vzťahov sa vyskytol problém", + "job-already-running": "Úloha už beží", + "encode-as-warning": "Nedá sa konvertovať do formátu PNG. Pre obaly použite možnosť Obnoviť obaly. Záložky a favicony sa nedajú spätne zakódovať.", + "ip-address-invalid": "IP adresa „{0}“ je neplatná", + "bookmark-dir-permissions": "Adresár záložiek nemá správne povolenia pre použitie v aplikácii Kavita", + "total-backups": "Celkový počet záloh musí byť medzi 1 a 30", + "total-logs": "Celkový počet protokolov musí byť medzi 1 a 30", + "stats-permission-denied": "Nemáte oprávnenie zobraziť si štatistiky iného používateľa", + "url-not-valid": "URL nevracia platný obrázok alebo vyžaduje autorizáciu", + "url-required": "Na použitie musíte zadať URL adresu", + "generic-cover-series-save": "Obrázok obálky sa nepodarilo uložiť do série", + "generic-cover-collection-save": "Obrázok obálky sa nepodarilo uložiť do kolekcie", + "generic-cover-reading-list-save": "Obrázok obálky sa nepodarilo uložiť do zoznamu na čítanie", + "generic-cover-chapter-save": "Obrázok obálky sa nepodarilo uložiť do kapitoly", + "generic-cover-library-save": "Obrázok obálky sa nepodarilo uložiť do knižnice", + "generic-cover-person-save": "Obrázok obálky sa nepodarilo uložiť k tejto osobe", + "generic-cover-volume-save": "Obrázok obálky sa nepodarilo uložiť do zväzku", + "access-denied": "Nemáte prístup", + "reset-chapter-lock": "Nepodarilo sa resetovať zámok obalu pre kapitolu", + "generic-user-delete": "Používateľa sa nepodarilo odstrániť", + "generic-user-pref": "Pri ukladaní predvolieb sa vyskytol problém", + "opds-disabled": "OPDS nie je na tomto serveri povolený", + "on-deck": "Pokračovať v čítaní", + "browse-on-deck": "Prehliadať pokračovanie v čítaní", + "recently-added": "Nedávno pridané", + "want-to-read": "Chcem čítať", + "browse-want-to-read": "Prehliadať Chcem si prečítať", + "browse-recently-added": "Prehliadať nedávno pridané", + "reading-lists": "Zoznamy na čítanie", + "browse-reading-lists": "Prehliadať podľa zoznamov na čítanie", + "libraries": "Všetky knižnice", + "browse-libraries": "Prehliadať podľa knižníc", + "collections": "Všetky kolekcie", + "browse-collections": "Prehliadať podľa kolekcií", + "more-in-genre": "Viac v žánri {0}", + "browse-more-in-genre": "Prezrite si viac v {0}", + "recently-updated": "Nedávno aktualizované", + "browse-recently-updated": "Prehliadať nedávno aktualizované", + "smart-filters": "Inteligentné filtre", + "external-sources": "Externé zdroje", + "browse-external-sources": "Prehliadať externé zdroje", + "browse-smart-filters": "Prehliadať podľa inteligentných filtrov", + "reading-list-restricted": "Zoznam na čítanie neexistuje alebo k nemu nemáte prístup", + "query-required": "Musíte zadať parameter dopytu", + "search": "Hľadať", + "search-description": "Vyhľadávanie sérií, zbierok alebo zoznamov na čítanie", + "favicon-doesnt-exist": "Favicon neexistuje", + "smart-filter-doesnt-exist": "Inteligentný filter neexistuje", + "smart-filter-already-in-use": "Existuje existujúci stream s týmto inteligentným filtrom", + "dashboard-stream-doesnt-exist": "Stream dashboardu neexistuje", + "sidenav-stream-doesnt-exist": "SideNav Stream neexistuje", + "external-source-already-exists": "Externý zdroj už existuje", + "external-source-required": "Vyžaduje sa kľúč API a Host", + "external-source-doesnt-exist": "Externý zdroj neexistuje", + "external-source-already-in-use": "S týmto externým zdrojom existuje stream", + "sidenav-stream-only-delete-smart-filter": "Z bočného panela SideNav je možné odstrániť iba streamy inteligentných filtrov", + "dashboard-stream-only-delete-smart-filter": "Z ovládacieho panela je možné odstrániť iba streamy inteligentných filtrov", + "smart-filter-name-required": "Názov inteligentného filtra je povinný", + "smart-filter-system-name": "Nemôžete použiť názov streamu poskytnutého systémom", + "not-authenticated": "Používateľ nie je overený", + "unable-to-register-k+": "Licenciu sa nepodarilo zaregistrovať z dôvodu chyby. Kontaktujte podporu Kavita+", + "unable-to-reset-k+": "Licenciu Kavita+ sa nepodarilo resetovať z dôvodu chyby. Kontaktujte podporu Kavita+", + "anilist-cred-expired": "Prihlasovacie údaje AniList vypršali alebo chýbajú", + "scrobble-bad-payload": "Nesprávne údaje od poskytovateľa Scrobblovania", + "theme-doesnt-exist": "Súbor témy chýba alebo je neplatný", + "bad-copy-files-for-download": "Súbory sa nepodarilo skopírovať do dočasného adresára na stiahnutie archívu.", + "generic-create-temp-archive": "Pri vytváraní dočasného archívu sa vyskytla chyba", + "epub-malformed": "Súbor je nesprávne naformátovaný! Nedá sa prečítať.", + "epub-html-missing": "Zodpovedajúci súbor HTML pre túto stránku sa nenašiel", + "collection-tag-title-required": "Názov kolekcie nemôže byť prázdny", + "reading-list-title-required": "Názov zoznamu na čítanie nemôže byť prázdny", + "collection-tag-duplicate": "Kolekcia s týmto názvom už existuje", + "device-duplicate": "Zariadenie s týmto názvom už existuje", + "device-not-created": "Toto zariadenie ešte neexistuje. Najprv ho vytvorte", + "send-to-permission": "Nie je možné odoslať súbory iné ako EPUB alebo PDF na zariadenia, pretože nie sú podporované na Kindle", + "progress-must-exist": "Pokrok musí byť u používateľa k dispozícii", + "reading-list-name-exists": "Zoznam na prečítanie s týmto menom už existuje", + "user-no-access-library-from-series": "Používateľ nemá prístup do knižnice, do ktorej táto séria patrí", + "series-restricted-age-restriction": "Používateľ si nemôže pozrieť túto sériu z dôvodu vekového obmedzenia", + "kavitaplus-restricted": "Toto je obmedzené iba na Kavita+", + "aliases-have-overlap": "Jeden alebo viacero aliasov sa prekrýva s inými osobami, nie je možné ich aktualizovať", + "volume-num": "Zväzok {0}", + "book-num": "Kniha {0}", + "issue-num": "Problém {0}{1}", + "chapter-num": "Kapitola {0}", + "check-updates": "Skontrolovať aktualizácie", + "license-check": "Kontrola licencie", + "process-scrobbling-events": "Udalosti procesu scrobblovania", + "report-stats": "Štatistiky hlásení", + "check-scrobbling-tokens": "Skontrolujte Tokeny Scrobblingu", + "cleanup": "Čistenie", + "process-processed-scrobbling-events": "Spracovať udalosti scrobblovania", + "remove-from-want-to-read": "Upratanie listu Chcem si prečítať", + "scan-libraries": "Skenovanie knižníc", + "kavita+-data-refresh": "Obnovenie údajov Kavita+", + "backup": "Záloha", + "update-yearly-stats": "Aktualizovať ročné štatistiky", + "generated-reading-profile-name": "Vygenerované z {0}" } diff --git a/UI/Web/src/assets/langs/fr.json b/UI/Web/src/assets/langs/fr.json index 8aac96a4e..b664de804 100644 --- a/UI/Web/src/assets/langs/fr.json +++ b/UI/Web/src/assets/langs/fr.json @@ -660,7 +660,7 @@ "success": "Succès !" }, "confirm-reset-password": { - "title": "Réinitialisation du mot de passe", + "title": "Réinitialiser le mot de passe", "description": "Saisir un nouveau mot de passe", "password-label": "{{common.password}}", "required-field": "{{validation.required-field}}", @@ -733,7 +733,7 @@ "more-alt": "Plus", "time-left-alt": "Temps restant", "time-to-read-alt": "{{sort-field-pipe.time-to-read}}", - "scrobbling-tooltip": "{{settings.scrobbling}}: {{value}}", + "scrobbling-tooltip": "{{settings.scrobbling}} : {{value}}", "publication-status-title": "Publication", "publication-status-tooltip": "État de la publication", "on": "{{reader-settings.on}}", @@ -2171,7 +2171,7 @@ "load-prev-chapter": "Précédent(e) {{entity}} chargé(e)", "account-registration-complete": "Enregistrement du compte terminé", "account-migration-complete": "Migration des comptes terminée", - "password-reset": "Réinitialisation du mot de passe", + "password-reset": "Réinitialiser le mot de passe", "password-updated": "Le mot de passe a été mis à jour", "forced-scan-queued": "Une recherche forcée a été lancée pour {{name}}", "library-created": "Bibliothèque créée avec succès. Un scan a été lancé.", @@ -2231,7 +2231,7 @@ "confirm-delete-multiple-volumes": "Êtes-vous sûr de vouloir supprimer les {{count}} volumes ? Cela ne modifiera pas les fichiers sur le disque.", "series-bound-to-reading-profile": "Série liée au Profil de lecture {{nom}}", "library-bound-to-reading-profile": "Bibliothèque liée au Profil de lecture {{nom}}", - "external-match-rate-error": "Kavita a manqué de rythme en cherchant {{seriesName}}. Réessayez dans 5 minutes." + "external-match-rate-error": "Kavita a pris trop de temps en cherchant {{seriesName}}. Réessayez dans 5 minutes." }, "read-time-pipe": { "less-than-hour": "<1 Heure", @@ -2309,7 +2309,11 @@ "set-reading-profile": "Définir le profil de lecture", "set-reading-profile-tooltip": "Lier un profil de lecture à cette bibliothèque", "clear-reading-profile": "Effacer le Profil de lecture", - "clear-reading-profile-tooltip": "Efface le Profil de lecture pour cette bibliothèque" + "clear-reading-profile-tooltip": "Efface le Profil de lecture pour cette bibliothèque", + "cleared-profile": "Profil de lecture effacé", + "rename": "Renommer", + "rename-tooltip": "Renommer le filtre intelligent", + "merge": "Fusionner" }, "preferences": { "left-to-right": "De gauche à droite", @@ -2428,7 +2432,8 @@ "volume-nums": "Volumes", "author-count": "{{num}} Auteurs", "chapter-count": "{{num}} chapitres", - "no-data": "Aucune donnée" + "no-data": "Aucune donnée", + "issue-count": "{{num}} Numéros" }, "confirm": { "alert": "Alerte", @@ -2730,5 +2735,83 @@ "person-name": "Nom", "person-series-count": "Nombre de séries", "person-chapter-count": "Nombre de chapitres" + }, + "breakpoint-pipe": { + "never": "Jamais", + "mobile": "Mobile", + "tablet": "Tablette", + "desktop": "Bureau" + }, + "manage-reading-profiles": { + "description": "Toutes vos séries ne peuvent pas être lues de la même manière, établissez des profils de lecture distincts par bibliothèque ou par série pour vous permettre de vous replonger dans vos séries aussi facilement que possible.", + "extra-tip": "Attribuez des profils de lecture via le menu d'action sur les séries et les bibliothèques, ou en vrac. Lorsque vous modifiez les paramètres d'une liseuse, un profil caché est créé pour mémoriser vos choix pour cette série (pas pour les pdf). Ce profil est supprimé lorsque vous attribuez l'un de vos propres profils de lecture à la série. Pour plus d'informations, consultez la page", + "profiles-title": "Vos profils de lecture", + "default-profile": "Défaut", + "add-tooltip": "Votre nouveau profil sera sauvegardé après avoir été modifié", + "make-default": "Défini comme valeur par défaut", + "no-selected": "Aucun profil sélectionné", + "confirm": "Êtes-vous sûr de vouloir supprimer le profil de lecture {{name}} ?", + "selection-tip": "Sélectionnez un profil dans la liste ou créez-en un nouveau en haut à droite", + "image-reader-settings-title": "Lecteur d'images", + "reading-direction-label": "Sens de lecture", + "reading-direction-tooltip": "Sens dans lequel il faut cliquer pour passer à la page suivante. De droite à gauche signifie que vous cliquez sur le côté gauche de l'écran pour passer à la page suivante.", + "scaling-option-label": "Options de mise à l'échelle", + "scaling-option-tooltip": "Comment adapter l'image à votre écran.", + "page-splitting-label": "Fractionnement des pages", + "page-splitting-tooltip": "Comment diviser une image en pleine largeur (signifie que les images de gauche et de droite sont combinées)", + "reading-mode-label": "Mode lecture", + "reading-mode-tooltip": "Modifier le lecteur pour qu'il soit paginé verticalement, horizontalement ou qu'il ait un défilement infini", + "layout-mode-label": "Mode de présentation", + "layout-mode-tooltip": "Rendre une seule image à l'écran ou deux images côte à côte", + "background-color-label": "Couleur de fond", + "background-color-tooltip": "Couleur d'arrière-plan du lecteur d'images", + "auto-close-menu-label": "Menu de fermeture automatique", + "auto-close-menu-tooltip": "Le menu doit-il se fermer automatiquement", + "show-screen-hints-label": "Afficher les conseils à l'écran", + "show-screen-hints-tooltip": "Afficher une superposition pour aider à comprendre la zone et le sens de pagination", + "emulate-comic-book-label": "S'inspirer de la bande dessinée", + "emulate-comic-book-tooltip": "Applique un effet d'ombre pour imiter la lecture d'un livre", + "add": "{{common.add}}", + "wiki-title": "wiki", + "swipe-to-paginate-label": "Balayer pour paginer", + "swipe-to-paginate-tooltip": "Le balayage de l'écran doit-il déclencher l'affichage de la page suivante ou précédente", + "allow-auto-webtoon-reader-label": "Mode de lecture automatique des webtoons", + "allow-auto-webtoon-reader-tooltip": "Passez en mode \"Webtoon Reader\" si les pages ressemblent à un webtoon. Des faux positifs peuvent se produire.", + "width-override-label": "{{manga-reader.width-override-label}}", + "width-override-tooltip": "Remplacer la largeur des images dans le lecteur", + "disable-width-override-label": "Désactiver la surcharge de la largeur", + "reset": "{{common.reset}}", + "disable-width-override-tooltip": "Empêcher la surcharge de la largeur de prendre effet lorsque l'écran est plus petit ou égal au point de rupture configuré", + "book-reader-settings-title": "Lecteur de livres", + "tap-to-paginate-label": "Tapez pour paginer", + "tap-to-paginate-tooltip": "Les côtés de l'écran du lecteur de livres devraient-ils permettre d'appuyer dessus pour passer à la page précédente/suivante", + "immersive-mode-label": "Mode immersif", + "immersive-mode-tooltip": "Cela permet de masquer le menu derrière un clic sur le document de lecture et d'activer la fonction de pagination", + "reading-direction-book-label": "Sens de lecture", + "reading-direction-book-tooltip": "Sens dans lequel cliquer pour passer à la page suivante. De droite à gauche signifie que vous cliquez sur le côté gauche de l'écran pour passer à la page suivante.", + "font-family-label": "Famille de polices", + "font-family-tooltip": "Famille de polices à charger. Défaut chargera la police par défaut du livre", + "writing-style-label": "Style d'écriture", + "writing-style-tooltip": "Change la direction du texte. L'horizontale va de gauche à droite, la verticale va de haut en bas.", + "layout-mode-book-label": "Mode de présentation", + "layout-mode-book-tooltip": "Comment le contenu doit être présenté. Le défilement correspond à l'emballage du livre. 1 ou 2 colonnes s'adapte à la hauteur de l'appareil et contient 1 ou 2 colonnes de texte par page", + "color-theme-book-label": "Thème de couleur", + "color-theme-book-tooltip": "Quel thème de couleur appliquer au contenu et au menu du lecteur de livres", + "font-size-book-label": "Taille de la police", + "font-size-book-tooltip": "Pourcentage de l'échelle à appliquer à la police dans le livre", + "line-height-book-label": "Espacement des lignes", + "line-height-book-tooltip": "Quel est l'espacement entre les lignes du livre", + "margin-book-label": "Marge", + "margin-book-tooltip": "L'espacement de chaque côté de l'écran. Ce paramètre est remplacé par 0 sur les appareils mobiles, quel que soit ce paramètre.", + "pdf-reader-settings-title": "Lecteur PDF", + "pdf-scroll-mode-label": "Mode défilement", + "pdf-scroll-mode-tooltip": "Mode de défilement des pages. Vertical/Horizontal et Taper pour paginer (pas de défilement)", + "pdf-spread-mode-label": "Mode d'expansion", + "pdf-spread-mode-tooltip": "Comment les pages doivent être mises en page. Simple ou double (paire/impaire)", + "pdf-theme-label": "Thème", + "pdf-theme-tooltip": "Couleur du thème du lecteur", + "reading-profile-series-settings-title": "Série", + "reading-profile-library-settings-title": "Bibliothèque", + "delete": "{{common.delete}}" } } diff --git a/UI/Web/src/assets/langs/ko.json b/UI/Web/src/assets/langs/ko.json index 887a94e25..ee5a50f6d 100644 --- a/UI/Web/src/assets/langs/ko.json +++ b/UI/Web/src/assets/langs/ko.json @@ -50,7 +50,9 @@ "special": "{{entity-title.special}}", "scrobbling-disabled": "계정 설정에서 스크로블링이 비활성화되어 있습니다.", "generate-scrobble-events": "채우기 이벤트", - "token-expired": "AniList 토큰이 만료되었습니다! 계정 페이지에서 갱신하기 전까지는 Scrobbling 이벤트가 처리되지 않습니다." + "token-expired": "AniList 토큰이 만료되었습니다! 계정 페이지에서 갱신하기 전까지는 Scrobbling 이벤트가 처리되지 않습니다.", + "select-all-label": "모두 선택", + "delete-selected-label": "선택 취소" }, "scrobble-event-type-pipe": { "chapter-read": "읽기 진행률", @@ -858,7 +860,8 @@ "exclude-patterns-tooltip": "디렉터리를 스캔할 때 Kavita가 일치시킬 패턴 집합(Glob 구문)을 구성하고, 스캐너 결과에서 제외할 수 있도록 설정하세요.", "help": "{{common.help}}", "allow-metadata-matching-tooltip": "Kavita가 이 라이브러리 내의 시리즈에 대한 메타데이터를 다운로드해야 합니까? 이는 서버에 활성 Kavita+ 구독이 있는 경우에만 발생합니다.", - "allow-metadata-matching-label": "메타데이터 일치 허용" + "allow-metadata-matching-label": "메타데이터 일치 허용", + "enable-metadata-label": "메타데이터 활성화 (ComicInfo/Epub/PDF)" }, "file-type-group-pipe": { "archive": "아카이브", @@ -2210,7 +2213,8 @@ "webtoon-override": "웹툰을 나타내는 이미지가 있어 웹툰 모드로 전환합니다.", "match-success": "시리즈가 올바르게 일치함", "confirm-delete-multiple-volumes": "{{count}}개의 볼륨을 삭제하시겠습니까? 디스크의 파일은 수정되지 않습니다.", - "scrobble-gen-init": "과거 열람 이력 및 평가를 추적하는 이벤트를 생성하여 연결된 서비스와 동기화하는 기능을 작업 예정 목록에 추가하였습니다." + "scrobble-gen-init": "과거 열람 이력 및 평가를 추적하는 이벤트를 생성하여 연결된 서비스와 동기화하는 기능을 작업 예정 목록에 추가하였습니다.", + "series-added-want-to-read": "읽고 싶어요 목록에 추가된 시리즈" }, "read-time-pipe": { "less-than-hour": "<1시간", @@ -2285,7 +2289,10 @@ "match-tooltip": "Kavita+에서 시리즈를 수동으로 일치시키기", "reorder": "정렬 변경", "rename": "이름 변경", - "rename-tooltip": "스마트 필터 이름 변경" + "rename-tooltip": "스마트 필터 이름 변경", + "reading-profiles": "읽기 프로필", + "set-reading-profile": "읽기 프로필 설정", + "merge": "병합" }, "preferences": { "left-to-right": "왼쪽에서 오른쪽", @@ -2654,7 +2661,8 @@ "clear": "{{common.clear}}", "filter-label": "{{common.filter}}", "close": "{{common.close}}", - "create": "{{common.create}}" + "create": "{{common.create}}", + "title": "읽기 프로필 설정" }, "manage-reading-profiles": { "reset": "{{common.reset}}" diff --git a/UI/Web/src/assets/langs/pl.json b/UI/Web/src/assets/langs/pl.json index 0308255e5..3184ac616 100644 --- a/UI/Web/src/assets/langs/pl.json +++ b/UI/Web/src/assets/langs/pl.json @@ -413,7 +413,7 @@ }, "relationship-pipe": { "adaptation": "Adaptacja", - "alternative-setting": "Alternatywne miejsce akcji", + "alternative-setting": "Alternatywne uniwersum", "alternative-version": "Alternatywna wersja", "character": "Postać", "contains": "Zawiera", @@ -421,18 +421,18 @@ "other": "Inne", "prequel": "Prequel", "sequel": "Sequel", - "side-story": "Opowieść poboczna", - "spin-off": "Spin Off", - "parent": "Rodzic", - "edition": "Edycja", - "annual": "Coroczny" + "side-story": "Historia poboczna", + "spin-off": "Spin-off", + "parent": "Główna historia", + "edition": "Wersja wydania", + "annual": "Wydanie roczne" }, "publication-status-pipe": { "ongoing": "W trakcie", - "hiatus": "Hiatus", - "completed": "Zakończono", + "hiatus": "Wstrzymano", + "completed": "Zakończono (kompletne)", "cancelled": "Anulowano", - "ended": "Zakończono" + "ended": "Zakończono (niekompletne)" }, "person-role-pipe": { "artist": "Artysta", diff --git a/UI/Web/src/assets/langs/sk.json b/UI/Web/src/assets/langs/sk.json index 22f16924b..858c360b7 100644 --- a/UI/Web/src/assets/langs/sk.json +++ b/UI/Web/src/assets/langs/sk.json @@ -789,7 +789,7 @@ }, "side-nav": { "home": "Domov", - "want-to-read": "Chcem čítať", + "want-to-read": "Obľúbené", "collections": "Zbierky", "reading-lists": "Čítacie Zoznamy", "bookmarks": "Záložky", @@ -2076,7 +2076,7 @@ "series-added-to-collection": "Séria bola pridaná do zbierky {{collectionName}}", "no-series-collection-warning": "POZOR! Nie sú vybraté žiadne série, uložením sa zbierka odstráni. Ste si istý, že chcete pokračovať?", "collection-updated": "Zbierka bola aktualizovaná", - "reading-list-deleted": "Zoznam na čítanie bol odstránený", + "reading-list-deleted": "Zoznam na čítanie bo zmazaný", "reading-list-updated": "Zoznam na čítanie bol aktualizovaný", "confirm-delete-reading-list": "Naozaj chcete vymazať zoznam na čítanie? Toto nie je možné vrátiť späť.", "item-removed": "Položka bola odstránená", From eab3d7a20716c9978d68a656bfae5f369e7c5535 Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Sat, 5 Jul 2025 14:51:19 -0500 Subject: [PATCH 11/15] v0.8.7 - Comic Metadata Downloading, Reading Profiles, Browse by Genre and More (#3888) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- Kavita.Common/Kavita.Common.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index cdd72de1c..805c3b61d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -28,7 +28,7 @@ body: label: Kavita Version Number - If you don't see your version number listed, please update Kavita and see if your issue still persists. multiple: false options: - - 0.8.6.2 - Stable + - 0.8.7 - Stable - Nightly Testing Branch validations: required: true diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 2ac06907e..c2ba1669d 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -3,7 +3,7 @@ net9.0 kavitareader.com Kavita - 0.8.6.20 + 0.8.7.0 en true @@ -20,4 +20,4 @@ - \ No newline at end of file + From 9eadf956fbb58ed0742e4d336fbe9a9b7d895fe9 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 5 Jul 2025 19:53:05 +0000 Subject: [PATCH 12/15] Update OpenAPI documentation --- openapi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi.json b/openapi.json index fe9270370..e9a3620e9 100644 --- a/openapi.json +++ b/openapi.json @@ -2,12 +2,12 @@ "openapi": "3.0.4", "info": { "title": "Kavita", - "description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.6.19", + "description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.7.0", "license": { "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.8.6.19" + "version": "0.8.7.0" }, "servers": [ { From 08c52b42810b8f3460e95f3d3455f4ccbebcc85e Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Sat, 5 Jul 2025 17:18:11 -0500 Subject: [PATCH 13/15] No More Sort Prefixes (#3895) --- .../Helpers/BookSortTitlePrefixHelperTests.cs | 178 + API.Tests/Services/ScannerServiceTests.cs | 23 + .../TestCases/Series with Prefix - Book.json | 3 + API/Controllers/LibraryController.cs | 2 + API/DTOs/LibraryDto.cs | 4 + API/DTOs/UpdateLibraryDto.cs | 2 + ...153840_LibraryRemoveSortPrefix.Designer.cs | 3724 +++++++++++++++++ .../20250629153840_LibraryRemoveSortPrefix.cs | 29 + .../Migrations/DataContextModelSnapshot.cs | 3 + API/Entities/Library.cs | 4 + API/Helpers/BookSortTitlePrefixHelper.cs | 101 + API/Services/Tasks/Scanner/ProcessSeries.cs | 8 +- UI/Web/src/app/_models/library/library.ts | 1 + .../library-settings-modal.component.html | 10 + .../library-settings-modal.component.ts | 4 +- UI/Web/src/assets/langs/en.json | 2 + 16 files changed, 4095 insertions(+), 3 deletions(-) create mode 100644 API.Tests/Helpers/BookSortTitlePrefixHelperTests.cs create mode 100644 API.Tests/Services/Test Data/ScannerService/TestCases/Series with Prefix - Book.json create mode 100644 API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.Designer.cs create mode 100644 API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.cs create mode 100644 API/Helpers/BookSortTitlePrefixHelper.cs diff --git a/API.Tests/Helpers/BookSortTitlePrefixHelperTests.cs b/API.Tests/Helpers/BookSortTitlePrefixHelperTests.cs new file mode 100644 index 000000000..e1f585806 --- /dev/null +++ b/API.Tests/Helpers/BookSortTitlePrefixHelperTests.cs @@ -0,0 +1,178 @@ +using API.Helpers; +using Xunit; + +namespace API.Tests.Helpers; + +public class BookSortTitlePrefixHelperTests +{ + [Theory] + [InlineData("The Avengers", "Avengers")] + [InlineData("A Game of Thrones", "Game of Thrones")] + [InlineData("An American Tragedy", "American Tragedy")] + public void TestEnglishPrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("El Quijote", "Quijote")] + [InlineData("La Casa de Papel", "Casa de Papel")] + [InlineData("Los Miserables", "Miserables")] + [InlineData("Las Vegas", "Vegas")] + [InlineData("Un Mundo Feliz", "Mundo Feliz")] + [InlineData("Una Historia", "Historia")] + public void TestSpanishPrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("Le Petit Prince", "Petit Prince")] + [InlineData("La Belle et la Bête", "Belle et la Bête")] + [InlineData("Les Misérables", "Misérables")] + [InlineData("Un Amour de Swann", "Amour de Swann")] + [InlineData("Une Vie", "Vie")] + [InlineData("Des Souris et des Hommes", "Souris et des Hommes")] + public void TestFrenchPrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("Der Herr der Ringe", "Herr der Ringe")] + [InlineData("Die Verwandlung", "Verwandlung")] + [InlineData("Das Kapital", "Kapital")] + [InlineData("Ein Sommernachtstraum", "Sommernachtstraum")] + [InlineData("Eine Geschichte", "Geschichte")] + public void TestGermanPrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("Il Nome della Rosa", "Nome della Rosa")] + [InlineData("La Divina Commedia", "Divina Commedia")] + [InlineData("Lo Hobbit", "Hobbit")] + [InlineData("Gli Ultimi", "Ultimi")] + [InlineData("Le Città Invisibili", "Città Invisibili")] + [InlineData("Un Giorno", "Giorno")] + [InlineData("Una Notte", "Notte")] + public void TestItalianPrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("O Alquimista", "Alquimista")] + [InlineData("A Moreninha", "Moreninha")] + [InlineData("Os Lusíadas", "Lusíadas")] + [InlineData("As Meninas", "Meninas")] + [InlineData("Um Defeito de Cor", "Defeito de Cor")] + [InlineData("Uma História", "História")] + public void TestPortuguesePrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("", "")] // Empty string returns empty + [InlineData("Book", "Book")] // Single word, no change + [InlineData("Avengers", "Avengers")] // No prefix, no change + public void TestNoPrefixCases(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("The", "The")] // Just a prefix word alone + [InlineData("A", "A")] // Just single letter prefix alone + [InlineData("Le", "Le")] // French prefix alone + public void TestPrefixWordAlone(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("THE AVENGERS", "AVENGERS")] // All caps + [InlineData("the avengers", "avengers")] // All lowercase + [InlineData("The AVENGERS", "AVENGERS")] // Mixed case + [InlineData("tHe AvEnGeRs", "AvEnGeRs")] // Random case + public void TestCaseInsensitivity(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("Then Came You", "Then Came You")] // "The" + "n" = not a prefix + [InlineData("And Then There Were None", "And Then There Were None")] // "An" + "d" = not a prefix + [InlineData("Elsewhere", "Elsewhere")] // "El" + "sewhere" = not a prefix (no space) + [InlineData("Lesson Plans", "Lesson Plans")] // "Les" + "son" = not a prefix (no space) + [InlineData("Theory of Everything", "Theory of Everything")] // "The" + "ory" = not a prefix + public void TestFalsePositivePrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("The ", "The ")] // Prefix with only space after - returns original + [InlineData("La ", "La ")] // Same for other languages + [InlineData("El ", "El ")] // Same for Spanish + public void TestPrefixWithOnlySpaceAfter(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("The Multiple Spaces", " Multiple Spaces")] // Doesn't trim extra spaces from remainder + [InlineData("Le Petit Prince", " Petit Prince")] // Leading space preserved in remainder + public void TestSpaceHandling(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("The The Matrix", "The Matrix")] // Removes first "The", leaves second + [InlineData("A A Clockwork Orange", "A Clockwork Orange")] // Removes first "A", leaves second + [InlineData("El El Cid", "El Cid")] // Spanish version + public void TestRepeatedPrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("L'Étranger", "L'Étranger")] // French contraction - no space, no change + [InlineData("D'Artagnan", "D'Artagnan")] // Contraction - no space, no change + [InlineData("The-Matrix", "The-Matrix")] // Hyphen instead of space - no change + [InlineData("The.Avengers", "The.Avengers")] // Period instead of space - no change + public void TestNonSpaceSeparators(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("三国演义", "三国演义")] // Chinese - no processing due to CJK detection + [InlineData("한국어", "한국어")] // Korean - not in CJK range, would be processed normally + public void TestCjkLanguages(string inputString, string expected) + { + // NOTE: These don't do anything, I am waiting for user input on if these are needed + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("नमस्ते दुनिया", "नमस्ते दुनिया")] // Hindi - not CJK, processed normally + [InlineData("مرحبا بالعالم", "مرحبا بالعالم")] // Arabic - not CJK, processed normally + [InlineData("שלום עולם", "שלום עולם")] // Hebrew - not CJK, processed normally + public void TestNonLatinNonCjkScripts(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } + + [Theory] + [InlineData("в мире", "мире")] // Russian "в" (in) - should be removed + [InlineData("на столе", "столе")] // Russian "на" (on) - should be removed + [InlineData("с друзьями", "друзьями")] // Russian "с" (with) - should be removed + public void TestRussianPrefixes(string inputString, string expected) + { + Assert.Equal(expected, BookSortTitlePrefixHelper.GetSortTitle(inputString)); + } +} diff --git a/API.Tests/Services/ScannerServiceTests.cs b/API.Tests/Services/ScannerServiceTests.cs index acc0345b1..c337d2311 100644 --- a/API.Tests/Services/ScannerServiceTests.cs +++ b/API.Tests/Services/ScannerServiceTests.cs @@ -972,4 +972,27 @@ public class ScannerServiceTests : AbstractDbTest Assert.Contains(postLib.Series, x => x.Name == "Immoral Guild"); Assert.Contains(postLib.Series, x => x.Name == "Futoku No Guild"); } + + [Fact] + public async Task ScanLibrary_SortName_NoPrefix() + { + const string testcase = "Series with Prefix - Book.json"; + + var library = await _scannerHelper.GenerateScannerData(testcase); + + library.RemovePrefixForSortName = true; + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); + + var scanner = _scannerHelper.CreateServices(); + await scanner.ScanLibrary(library.Id); + + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + + Assert.NotNull(postLib); + Assert.Equal(1, postLib.Series.Count); + + Assert.Equal("The Avengers", postLib.Series.First().Name); + Assert.Equal("Avengers", postLib.Series.First().SortName); + } } diff --git a/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Prefix - Book.json b/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Prefix - Book.json new file mode 100644 index 000000000..fc2bee18c --- /dev/null +++ b/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Prefix - Book.json @@ -0,0 +1,3 @@ +[ + "The Avengers/The Avengers vol 1.pdf" +] \ No newline at end of file diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index c09011b77..8f9b18317 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -624,6 +624,8 @@ public class LibraryController : BaseApiController library.AllowScrobbling = dto.AllowScrobbling; library.AllowMetadataMatching = dto.AllowMetadataMatching; library.EnableMetadata = dto.EnableMetadata; + library.RemovePrefixForSortName = dto.RemovePrefixForSortName; + library.LibraryFileTypes = dto.FileGroupTypes .Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id}) .Distinct() diff --git a/API/DTOs/LibraryDto.cs b/API/DTOs/LibraryDto.cs index 7b38379c9..bd72ad2f0 100644 --- a/API/DTOs/LibraryDto.cs +++ b/API/DTOs/LibraryDto.cs @@ -70,4 +70,8 @@ public sealed record LibraryDto /// Allow Kavita to read metadata (ComicInfo.xml, Epub, PDF) /// public bool EnableMetadata { get; set; } = true; + /// + /// Should Kavita remove sort articles "The" for the sort name + /// + public bool RemovePrefixForSortName { get; set; } = false; } diff --git a/API/DTOs/UpdateLibraryDto.cs b/API/DTOs/UpdateLibraryDto.cs index 68d2417ec..d7f314208 100644 --- a/API/DTOs/UpdateLibraryDto.cs +++ b/API/DTOs/UpdateLibraryDto.cs @@ -30,6 +30,8 @@ public sealed record UpdateLibraryDto public bool AllowMetadataMatching { get; init; } [Required] public bool EnableMetadata { get; init; } + [Required] + public bool RemovePrefixForSortName { get; init; } /// /// What types of files to allow the scanner to pickup /// diff --git a/API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.Designer.cs b/API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.Designer.cs new file mode 100644 index 000000000..165663f3d --- /dev/null +++ b/API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.Designer.cs @@ -0,0 +1,3724 @@ +// +using System; +using System.Collections.Generic; +using API.Data; +using API.Entities.MetadataMatching; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250629153840_LibraryRemoveSortPrefix")] + partial class LibraryRemoveSortPrefix + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("AgeRestriction") + .HasColumnType("INTEGER"); + + b.Property("AgeRestrictionIncludeUnknowns") + .HasColumnType("INTEGER"); + + b.Property("AniListAccessToken") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("ConfirmationToken") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("HasRunScrobbleEventGeneration") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("LastActiveUtc") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MalAccessToken") + .HasColumnType("TEXT"); + + b.Property("MalUserName") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventGenerationRan") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Page") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserBookmark"); + }); + + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserChapterRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastSyncUtc") + .HasColumnType("TEXT"); + + b.Property("MissingSeriesFromSource") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("SourceUrl") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSourceCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserCollection"); + }); + + modelBuilder.Entity("API.Entities.AppUserDashboardStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("IsProvided") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("SmartFilterId") + .HasColumnType("INTEGER"); + + b.Property("StreamType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(4); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SmartFilterId"); + + b.HasIndex("Visible"); + + b.ToTable("AppUserDashboardStream"); + }); + + modelBuilder.Entity("API.Entities.AppUserExternalSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Host") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserExternalSource"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserOnDeckRemoval"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAutomaticWebtoonReaderDetection") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AniListScrobblingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); + + b.Property("BlurUnreadSummaries") + .HasColumnType("INTEGER"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderImmersiveMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLayoutMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("BookReaderWritingStyle") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + + b.Property("CollapseSeriesRelationships") + .HasColumnType("INTEGER"); + + b.Property("EmulateBook") + .HasColumnType("INTEGER"); + + b.Property("GlobalPageLayoutMode") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("LayoutMode") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("en"); + + b.Property("NoTransitions") + .HasColumnType("INTEGER"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("PdfScrollMode") + .HasColumnType("INTEGER"); + + b.Property("PdfSpreadMode") + .HasColumnType("INTEGER"); + + b.Property("PdfTheme") + .HasColumnType("INTEGER"); + + b.Property("PromptForDownloadSize") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("ShareReviews") + .HasColumnType("INTEGER"); + + b.Property("ShowScreenHints") + .HasColumnType("INTEGER"); + + b.Property("SwipeToPaginate") + .HasColumnType("INTEGER"); + + b.Property("ThemeId") + .HasColumnType("INTEGER"); + + b.Property("WantToReadSync") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.HasKey("Id"); + + b.HasIndex("AppUserId") + .IsUnique(); + + b.HasIndex("ThemeId"); + + b.ToTable("AppUserPreferences"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PagesRead") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserProgresses"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserReadingProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAutomaticWebtoonReaderDetection") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderImmersiveMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLayoutMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("BookReaderWritingStyle") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + + b.Property("DisableWidthOverride") + .HasColumnType("INTEGER"); + + b.Property("EmulateBook") + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("LayoutMode") + .HasColumnType("INTEGER"); + + b.Property("LibraryIds") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("PdfScrollMode") + .HasColumnType("INTEGER"); + + b.Property("PdfSpreadMode") + .HasColumnType("INTEGER"); + + b.Property("PdfTheme") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("SeriesIds") + .HasColumnType("TEXT"); + + b.Property("ShowScreenHints") + .HasColumnType("INTEGER"); + + b.Property("SwipeToPaginate") + .HasColumnType("INTEGER"); + + b.Property("WidthOverride") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserReadingProfiles"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserSideNavStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSourceId") + .HasColumnType("INTEGER"); + + b.Property("IsProvided") + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("SmartFilterId") + .HasColumnType("INTEGER"); + + b.Property("StreamType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(5); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SmartFilterId"); + + b.HasIndex("Visible"); + + b.ToTable("AppUserSideNavStream"); + }); + + modelBuilder.Entity("API.Entities.AppUserSmartFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Filter") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserSmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PageNumber") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserTableOfContent"); + }); + + modelBuilder.Entity("API.Entities.AppUserWantToRead", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserWantToRead"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("AlternateCount") + .HasColumnType("INTEGER"); + + b.Property("AlternateNumber") + .HasColumnType("TEXT"); + + b.Property("AlternateSeries") + .HasColumnType("TEXT"); + + b.Property("AverageExternalRating") + .HasColumnType("REAL"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("ISBN") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("ISBNLocked") + .HasColumnType("INTEGER"); + + b.Property("ImprintLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("IsSpecial") + .HasColumnType("INTEGER"); + + b.Property("KPlusOverrides") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("[]"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("LocationLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MaxNumber") + .HasColumnType("REAL"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinNumber") + .HasColumnType("REAL"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("Range") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("ReleaseDateLocked") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SeriesGroup") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("REAL"); + + b.Property("SortOrderLocked") + .HasColumnType("INTEGER"); + + b.Property("StoryArc") + .HasColumnType("TEXT"); + + b.Property("StoryArcNumber") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TeamLocked") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TitleName") + .HasColumnType("TEXT"); + + b.Property("TitleNameLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("VolumeId"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("API.Entities.CollectionTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Promoted") + .IsUnique(); + + b.ToTable("CollectionTag"); + }); + + modelBuilder.Entity("API.Entities.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastUsed") + .HasColumnType("TEXT"); + + b.Property("LastUsedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("API.Entities.EmailHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("DeliveryStatus") + .HasColumnType("TEXT"); + + b.Property("EmailTemplate") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("SendDate") + .HasColumnType("TEXT"); + + b.Property("Sent") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("Sent", "AppUserId", "EmailTemplate", "SendDate"); + + b.ToTable("EmailHistory"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("FolderPath"); + }); + + modelBuilder.Entity("API.Entities.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Genre"); + }); + + modelBuilder.Entity("API.Entities.History.ManualMigrationHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("RanAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ManualMigrationHistory"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowMetadataMatching") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AllowScrobbling") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EnableMetadata") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("FolderWatching") + .HasColumnType("INTEGER"); + + b.Property("IncludeInDashboard") + .HasColumnType("INTEGER"); + + b.Property("IncludeInRecommended") + .HasColumnType("INTEGER"); + + b.Property("IncludeInSearch") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("ManageCollections") + .HasColumnType("INTEGER"); + + b.Property("ManageReadingLists") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("RemovePrefixForSortName") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryExcludePattern", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Pattern") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryExcludePattern"); + }); + + modelBuilder.Entity("API.Entities.LibraryFileTypeGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FileTypeGroup") + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryFileTypeGroup"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("KoreaderHash") + .HasColumnType("TEXT"); + + b.Property("LastFileAnalysis") + .HasColumnType("TEXT"); + + b.Property("LastFileAnalysisUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.MediaError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MediaError"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Authority") + .HasColumnType("INTEGER"); + + b.Property("AverageScore") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ProviderUrl") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("ExternalRating"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRecommendation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("ExternalRecommendation"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Authority") + .HasColumnType("INTEGER"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("BodyJustText") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("INTEGER"); + + b.Property("RawBody") + .HasColumnType("TEXT"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("SiteUrl") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("TotalVotes") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("ExternalReview"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("AverageExternalRating") + .HasColumnType("INTEGER"); + + b.Property("CbrId") + .HasColumnType("INTEGER"); + + b.Property("GoogleBooksId") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("ValidUntilUtc") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.ToTable("ExternalSeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastChecked") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("SeriesBlacklist"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("ImprintLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("KPlusOverrides") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("[]"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("LocationLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxCount") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatusLocked") + .HasColumnType("INTEGER"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYear") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYearLocked") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TeamLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.HasIndex("Id", "SeriesId") + .IsUnique(); + + b.ToTable("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RelationKind") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("TargetSeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.HasIndex("TargetSeriesId"); + + b.ToTable("SeriesRelation"); + }); + + modelBuilder.Entity("API.Entities.MetadataFieldMapping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DestinationType") + .HasColumnType("INTEGER"); + + b.Property("DestinationValue") + .HasColumnType("TEXT"); + + b.Property("ExcludeFromSource") + .HasColumnType("INTEGER"); + + b.Property("MetadataSettingsId") + .HasColumnType("INTEGER"); + + b.Property("SourceType") + .HasColumnType("INTEGER"); + + b.Property("SourceValue") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MetadataSettingsId"); + + b.ToTable("MetadataFieldMapping"); + }); + + modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRatingMappings") + .HasColumnType("TEXT"); + + b.Property("Blacklist") + .HasColumnType("TEXT"); + + b.Property("EnableChapterCoverImage") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterPublisher") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterReleaseDate") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterSummary") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterTitle") + .HasColumnType("INTEGER"); + + b.Property("EnableCoverImage") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("EnableGenres") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalizedName") + .HasColumnType("INTEGER"); + + b.Property("EnablePeople") + .HasColumnType("INTEGER"); + + b.Property("EnablePublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("EnableRelationships") + .HasColumnType("INTEGER"); + + b.Property("EnableStartDate") + .HasColumnType("INTEGER"); + + b.Property("EnableSummary") + .HasColumnType("INTEGER"); + + b.Property("EnableTags") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("FirstLastPeopleNaming") + .HasColumnType("INTEGER"); + + b.Property("Overrides") + .HasColumnType("TEXT"); + + b.PrimitiveCollection("PersonRoles") + .HasColumnType("TEXT"); + + b.Property("Whitelist") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MetadataSettings"); + }); + + modelBuilder.Entity("API.Entities.Person.ChapterPeople", b => + { + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("KavitaPlusConnection") + .HasColumnType("INTEGER"); + + b.Property("OrderWeight") + .HasColumnType("INTEGER"); + + b.HasKey("ChapterId", "PersonId", "Role"); + + b.HasIndex("PersonId"); + + b.ToTable("ChapterPeople"); + }); + + modelBuilder.Entity("API.Entities.Person.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("Asin") + .HasColumnType("TEXT"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("HardcoverId") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.PersonAlias", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("NormalizedAlias") + .HasColumnType("TEXT"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PersonId"); + + b.ToTable("PersonAlias"); + }); + + modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b => + { + b.Property("SeriesMetadataId") + .HasColumnType("INTEGER"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("KavitaPlusConnection") + .HasColumnType("INTEGER"); + + b.Property("OrderWeight") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.HasKey("SeriesMetadataId", "PersonId", "Role"); + + b.HasIndex("PersonId"); + + b.ToTable("SeriesMetadataPeople"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EndingMonth") + .HasColumnType("INTEGER"); + + b.Property("EndingYear") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("StartingMonth") + .HasColumnType("INTEGER"); + + b.Property("StartingYear") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("ReadingList"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("ReadingListId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.HasIndex("ReadingListId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("VolumeId"); + + b.ToTable("ReadingListItem"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId1") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ScrobbleEventId1"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleError"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterNumber") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("ErrorDetails") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("IsErrored") + .HasColumnType("INTEGER"); + + b.Property("IsProcessed") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("ProcessDateUtc") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("ReviewBody") + .HasColumnType("TEXT"); + + b.Property("ReviewTitle") + .HasColumnType("TEXT"); + + b.Property("ScrobbleEventType") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeNumber") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("LibraryId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleEvent"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleHold"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("DontMatch") + .HasColumnType("INTEGER"); + + b.Property("FolderPath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("IsBlacklisted") + .HasColumnType("INTEGER"); + + b.Property("LastChapterAdded") + .HasColumnType("TEXT"); + + b.Property("LastChapterAddedUtc") + .HasColumnType("TEXT"); + + b.Property("LastFolderScanned") + .HasColumnType("TEXT"); + + b.Property("LastFolderScannedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("LocalizedName") + .HasColumnType("TEXT"); + + b.Property("LocalizedNameLocked") + .HasColumnType("INTEGER"); + + b.Property("LowestFolderPath") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedLocalizedName") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("SortNameLocked") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.ServerSetting", b => + { + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSetting"); + }); + + modelBuilder.Entity("API.Entities.ServerStatistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterCount") + .HasColumnType("INTEGER"); + + b.Property("FileCount") + .HasColumnType("INTEGER"); + + b.Property("GenreCount") + .HasColumnType("INTEGER"); + + b.Property("PersonCount") + .HasColumnType("INTEGER"); + + b.Property("SeriesCount") + .HasColumnType("INTEGER"); + + b.Property("TagCount") + .HasColumnType("INTEGER"); + + b.Property("UserCount") + .HasColumnType("INTEGER"); + + b.Property("VolumeCount") + .HasColumnType("INTEGER"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ServerStatistics"); + }); + + modelBuilder.Entity("API.Entities.SiteTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .HasColumnType("TEXT"); + + b.Property("CompatibleVersion") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("GitHubPath") + .HasColumnType("TEXT"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PreviewUrls") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ShaHash") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SiteTheme"); + }); + + modelBuilder.Entity("API.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Tag"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LookupName") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MaxNumber") + .HasColumnType("REAL"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinNumber") + .HasColumnType("REAL"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + modelBuilder.Entity("AppUserCollectionSeries", b => + { + b.Property("CollectionsId") + .HasColumnType("INTEGER"); + + b.Property("ItemsId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsId", "ItemsId"); + + b.HasIndex("ItemsId"); + + b.ToTable("AppUserCollectionSeries"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("ChapterGenre"); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("ChapterTag"); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.Property("CollectionTagsId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionTagsId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("CollectionTagSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => + { + b.Property("ExternalRatingsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalRatingsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalRatingExternalSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalRecommendationExternalSeriesMetadata", b => + { + b.Property("ExternalRecommendationsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalRecommendationsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalRecommendationExternalSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalReviewExternalSeriesMetadata", b => + { + b.Property("ExternalReviewsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalReviewsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalReviewExternalSeriesMetadata"); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("GenresId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("GenreSeriesMetadata"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("SeriesMetadatasId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("SeriesMetadataTag"); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Bookmarks") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ChapterRatings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Ratings") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserCollection", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Collections") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserDashboardStream", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("DashboardStreams") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUserSmartFilter", "SmartFilter") + .WithMany() + .HasForeignKey("SmartFilterId"); + + b.Navigation("AppUser"); + + b.Navigation("SmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserExternalSource", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ExternalSources") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithOne("UserPreferences") + .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.SiteTheme", "Theme") + .WithMany() + .HasForeignKey("ThemeId"); + + b.Navigation("AppUser"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Progresses") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", null) + .WithMany("UserProgress") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany("Progress") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Ratings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany("Ratings") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserReadingProfile", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ReadingProfiles") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.HasOne("API.Entities.AppRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUser", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("API.Entities.AppUserSideNavStream", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("SideNavStreams") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUserSmartFilter", "SmartFilter") + .WithMany() + .HasForeignKey("SmartFilterId"); + + b.Navigation("AppUser"); + + b.Navigation("SmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserSmartFilter", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("SmartFilters") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("TableOfContents") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserWantToRead", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("WantToRead") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.HasOne("API.Entities.Volume", "Volume") + .WithMany("Chapters") + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Device", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Devices") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.EmailHistory", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Folders") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryExcludePattern", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("LibraryExcludePatterns") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryFileTypeGroup", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("LibraryFileTypes") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Files") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalRatings") + .HasForeignKey("ChapterId"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalReviews") + .HasForeignKey("ChapterId"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("ExternalSeriesMetadata") + .HasForeignKey("API.Entities.Metadata.ExternalSeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesBlacklist", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("Metadata") + .HasForeignKey("API.Entities.Metadata.SeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Relations") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "TargetSeries") + .WithMany("RelationOf") + .HasForeignKey("TargetSeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + + b.Navigation("TargetSeries"); + }); + + modelBuilder.Entity("API.Entities.MetadataFieldMapping", b => + { + b.HasOne("API.Entities.MetadataMatching.MetadataSettings", "MetadataSettings") + .WithMany("FieldMappings") + .HasForeignKey("MetadataSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MetadataSettings"); + }); + + modelBuilder.Entity("API.Entities.Person.ChapterPeople", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("People") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("ChapterPeople") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.PersonAlias", b => + { + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("Aliases") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b => + { + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("SeriesMetadataPeople") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", "SeriesMetadata") + .WithMany("People") + .HasForeignKey("SeriesMetadataId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Person"); + + b.Navigation("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ReadingLists") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.ReadingList", "ReadingList") + .WithMany("Items") + .HasForeignKey("ReadingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Volume", "Volume") + .WithMany() + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("ReadingList"); + + b.Navigation("Series"); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.HasOne("API.Entities.Scrobble.ScrobbleEvent", "ScrobbleEvent") + .WithMany() + .HasForeignKey("ScrobbleEventId1"); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ScrobbleEvent"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", "Library") + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Library"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ScrobbleHolds") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Series") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Volumes") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("AppUserCollectionSeries", b => + { + b.HasOne("API.Entities.AppUserCollection", null) + .WithMany() + .HasForeignKey("CollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany() + .HasForeignKey("ItemsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("AppUsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", null) + .WithMany() + .HasForeignKey("LibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.HasOne("API.Entities.CollectionTag", null) + .WithMany() + .HasForeignKey("CollectionTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalRating", null) + .WithMany() + .HasForeignKey("ExternalRatingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalRecommendationExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalRecommendation", null) + .WithMany() + .HasForeignKey("ExternalRecommendationsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalReviewExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalReview", null) + .WithMany() + .HasForeignKey("ExternalReviewsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("Bookmarks"); + + b.Navigation("ChapterRatings"); + + b.Navigation("Collections"); + + b.Navigation("DashboardStreams"); + + b.Navigation("Devices"); + + b.Navigation("ExternalSources"); + + b.Navigation("Progresses"); + + b.Navigation("Ratings"); + + b.Navigation("ReadingLists"); + + b.Navigation("ReadingProfiles"); + + b.Navigation("ScrobbleHolds"); + + b.Navigation("SideNavStreams"); + + b.Navigation("SmartFilters"); + + b.Navigation("TableOfContents"); + + b.Navigation("UserPreferences"); + + b.Navigation("UserRoles"); + + b.Navigation("WantToRead"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Navigation("ExternalRatings"); + + b.Navigation("ExternalReviews"); + + b.Navigation("Files"); + + b.Navigation("People"); + + b.Navigation("Ratings"); + + b.Navigation("UserProgress"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + + b.Navigation("LibraryExcludePatterns"); + + b.Navigation("LibraryFileTypes"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Navigation("People"); + }); + + modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b => + { + b.Navigation("FieldMappings"); + }); + + modelBuilder.Entity("API.Entities.Person.Person", b => + { + b.Navigation("Aliases"); + + b.Navigation("ChapterPeople"); + + b.Navigation("SeriesMetadataPeople"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Navigation("ExternalSeriesMetadata"); + + b.Navigation("Metadata"); + + b.Navigation("Progress"); + + b.Navigation("Ratings"); + + b.Navigation("RelationOf"); + + b.Navigation("Relations"); + + b.Navigation("Volumes"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Navigation("Chapters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.cs b/API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.cs new file mode 100644 index 000000000..4800cf3fa --- /dev/null +++ b/API/Data/Migrations/20250629153840_LibraryRemoveSortPrefix.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Data.Migrations +{ + /// + public partial class LibraryRemoveSortPrefix : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RemovePrefixForSortName", + table: "Library", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RemovePrefixForSortName", + table: "Library"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index 106a86b4a..62d1fb1ef 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -1341,6 +1341,9 @@ namespace API.Data.Migrations b.Property("PrimaryColor") .HasColumnType("TEXT"); + b.Property("RemovePrefixForSortName") + .HasColumnType("INTEGER"); + b.Property("SecondaryColor") .HasColumnType("TEXT"); diff --git a/API/Entities/Library.cs b/API/Entities/Library.cs index 8dc386298..4a48fed99 100644 --- a/API/Entities/Library.cs +++ b/API/Entities/Library.cs @@ -52,6 +52,10 @@ public class Library : IEntityDate, IHasCoverImage /// Should Kavita read metadata files from the library /// public bool EnableMetadata { get; set; } = true; + /// + /// Should Kavita remove sort articles "The" for the sort name + /// + public bool RemovePrefixForSortName { get; set; } = false; public DateTime Created { get; set; } diff --git a/API/Helpers/BookSortTitlePrefixHelper.cs b/API/Helpers/BookSortTitlePrefixHelper.cs new file mode 100644 index 000000000..c92df5d65 --- /dev/null +++ b/API/Helpers/BookSortTitlePrefixHelper.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace API.Helpers; + +/// +/// Responsible for parsing book titles "The man on the street" and removing the prefix -> "man on the street". +/// +/// This code is performance sensitive +public static class BookSortTitlePrefixHelper +{ + private static readonly Dictionary PrefixLookup; + private static readonly Dictionary> PrefixesByFirstChar; + + static BookSortTitlePrefixHelper() + { + var prefixes = new[] + { + // English + "the", "a", "an", + // Spanish + "el", "la", "los", "las", "un", "una", "unos", "unas", + // French + "le", "la", "les", "un", "une", "des", + // German + "der", "die", "das", "den", "dem", "ein", "eine", "einen", "einer", + // Italian + "il", "lo", "la", "gli", "le", "un", "uno", "una", + // Portuguese + "o", "a", "os", "as", "um", "uma", "uns", "umas", + // Russian (transliterated common ones) + "в", "на", "с", "к", "от", "для", + }; + + // Build lookup structures + PrefixLookup = new Dictionary(prefixes.Length, StringComparer.OrdinalIgnoreCase); + PrefixesByFirstChar = new Dictionary>(); + + foreach (var prefix in prefixes) + { + PrefixLookup[prefix] = 1; + + var firstChar = char.ToLowerInvariant(prefix[0]); + if (!PrefixesByFirstChar.TryGetValue(firstChar, out var list)) + { + list = []; + PrefixesByFirstChar[firstChar] = list; + } + list.Add(prefix); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetSortTitle(ReadOnlySpan title) + { + if (title.IsEmpty) return title; + + // Fast detection of script type by first character + var firstChar = title[0]; + + // CJK Unicode ranges - no processing needed for most cases + if ((firstChar >= 0x4E00 && firstChar <= 0x9FFF) || // CJK Unified + (firstChar >= 0x3040 && firstChar <= 0x309F) || // Hiragana + (firstChar >= 0x30A0 && firstChar <= 0x30FF)) // Katakana + { + return title; + } + + var firstSpaceIndex = title.IndexOf(' '); + if (firstSpaceIndex <= 0) return title; + + var potentialPrefix = title.Slice(0, firstSpaceIndex); + + // Fast path: check if first character could match any prefix + firstChar = char.ToLowerInvariant(potentialPrefix[0]); + if (!PrefixesByFirstChar.ContainsKey(firstChar)) + return title; + + // Only do the expensive lookup if first character matches + if (PrefixLookup.ContainsKey(potentialPrefix.ToString())) + { + var remainder = title.Slice(firstSpaceIndex + 1); + return remainder.IsEmpty ? title : remainder; + } + + return title; + } + + /// + /// Removes the sort prefix + /// + /// + /// + public static string GetSortTitle(string title) + { + var result = GetSortTitle(title.AsSpan()); + + return result.ToString(); + } +} diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index cf3a9f3fb..307408adb 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -126,13 +126,17 @@ public class ProcessSeries : IProcessSeries series.Format = firstParsedInfo.Format; } + var removePrefix = library.RemovePrefixForSortName; + var sortName = removePrefix ? BookSortTitlePrefixHelper.GetSortTitle(series.Name) : series.Name; + if (string.IsNullOrEmpty(series.SortName)) { - series.SortName = series.Name; + series.SortName = sortName; } + if (!series.SortNameLocked) { - series.SortName = series.Name; + series.SortName = sortName; if (!string.IsNullOrEmpty(firstParsedInfo.SeriesSort)) { series.SortName = firstParsedInfo.SeriesSort; diff --git a/UI/Web/src/app/_models/library/library.ts b/UI/Web/src/app/_models/library/library.ts index 0e7d90ee2..bcbf9b447 100644 --- a/UI/Web/src/app/_models/library/library.ts +++ b/UI/Web/src/app/_models/library/library.ts @@ -32,6 +32,7 @@ export interface Library { allowScrobbling: boolean; allowMetadataMatching: boolean; enableMetadata: boolean; + removePrefixForSortName: boolean; collapseSeriesRelationships: boolean; libraryFileTypes: Array; excludePatterns: Array; diff --git a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.html b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.html index ff97fcbb0..e8a3bafeb 100644 --- a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.html +++ b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.html @@ -127,6 +127,16 @@ +
+ + +
+ +
+
+
+
+
diff --git a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts index d0fed5c81..9331376ef 100644 --- a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts +++ b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts @@ -115,6 +115,7 @@ export class LibrarySettingsModalComponent implements OnInit { allowMetadataMatching: new FormControl(true, { nonNullable: true, validators: [] }), collapseSeriesRelationships: new FormControl(false, { nonNullable: true, validators: [] }), enableMetadata: new FormControl(true, { nonNullable: true, validators: [] }), // required validator doesn't check value, just if true + removePrefixForSortName: new FormControl(false, { nonNullable: true, validators: [] }), }); selectedFolders: string[] = []; @@ -273,7 +274,8 @@ export class LibrarySettingsModalComponent implements OnInit { this.libraryForm.get('allowScrobbling')?.setValue(this.IsKavitaPlusEligible ? this.library.allowScrobbling : false); this.libraryForm.get('allowMetadataMatching')?.setValue(this.IsMetadataDownloadEligible ? this.library.allowMetadataMatching : false); this.libraryForm.get('excludePatterns')?.setValue(this.excludePatterns ? this.library.excludePatterns : false); - this.libraryForm.get('enableMetadata')?.setValue(this.library.enableMetadata, true); + this.libraryForm.get('enableMetadata')?.setValue(this.library.enableMetadata); + this.libraryForm.get('removePrefixForSortName')?.setValue(this.library.removePrefixForSortName); this.selectedFolders = this.library.folders; this.madeChanges = false; diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index c6b8c823f..33bde5e0e 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -1131,6 +1131,8 @@ "include-in-search-tooltip": "Should series and any derived information (genres, people, files) from the library be included in search results.", "enable-metadata-label": "Enable Metadata (ComicInfo/Epub/PDF)", "enable-metadata-tooltip": "Allow Kavita to read metadata files which override filename parsing.", + "remove-prefix-for-sortname-label": "Remove common prefixes for Sort Name", + "remove-prefix-for-sortname-tooltip": "Kavita will remove common prefixes like 'The', 'A', 'An' from titles for sort name. Does not override set metadata.", "force-scan": "Force Scan", "force-scan-tooltip": "This will force a scan on the library, treating like a fresh scan", "reset": "{{common.reset}}", From 76fd7ab4ce2b474fd1ed94281e9217f932975734 Mon Sep 17 00:00:00 2001 From: majora2007 Date: Sat, 5 Jul 2025 22:18:52 +0000 Subject: [PATCH 14/15] Bump versions by dotnet-bump-version. --- Kavita.Common/Kavita.Common.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index c2ba1669d..c7dd0ab94 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -3,7 +3,7 @@ net9.0 kavitareader.com Kavita - 0.8.7.0 + 0.8.7.1 en true @@ -20,4 +20,4 @@ - + \ No newline at end of file From ef2640b5fc2e7e2836cb30b8f8bbbf9025774d57 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 5 Jul 2025 22:20:01 +0000 Subject: [PATCH 15/15] Update OpenAPI documentation --- openapi.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openapi.json b/openapi.json index e9a3620e9..3e4b797cb 100644 --- a/openapi.json +++ b/openapi.json @@ -21371,6 +21371,10 @@ "type": "boolean", "description": "Should Kavita read metadata files from the library" }, + "removePrefixForSortName": { + "type": "boolean", + "description": "Should Kavita remove sort articles \"The\" for the sort name" + }, "created": { "type": "string", "format": "date-time" @@ -21533,6 +21537,10 @@ "enableMetadata": { "type": "boolean", "description": "Allow Kavita to read metadata (ComicInfo.xml, Epub, PDF)" + }, + "removePrefixForSortName": { + "type": "boolean", + "description": "Should Kavita remove sort articles \"The\" for the sort name" } }, "additionalProperties": false @@ -26438,6 +26446,7 @@ "manageCollections", "manageReadingLists", "name", + "removePrefixForSortName", "type" ], "type": "object", @@ -26492,6 +26501,9 @@ "enableMetadata": { "type": "boolean" }, + "removePrefixForSortName": { + "type": "boolean" + }, "fileGroupTypes": { "type": "array", "items": {