feat: AniList reading status integration

- Add full AniList integration: OAuth connect, series linking, push/pull sync
- Push: PLANNING/CURRENT/COMPLETED based on books read vs total_volumes (never auto-complete from owned books alone)
- Pull: update local reading progress from AniList list (per-user)
- Detailed sync/pull reports with per-series status and progress
- Local user selector in settings to scope sync to a specific user
- Rename "AniList" tab/buttons to generic "État de lecture" / "Reading status"
- Make Bédéthèque and AniList badges clickable links on series detail page
- Fix ON CONFLICT error on series link (provider column in PK)
- Migration 0054: fix series_metadata missing columns (authors, publishers, locked_fields, total_volumes, status)
- Align button heights on series detail page; move MarkSeriesReadButton to action row

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 17:08:11 +01:00
parent 2a7881ac6e
commit e94a4a0b13
29 changed files with 2352 additions and 40 deletions

View File

@@ -195,6 +195,23 @@ const en: Record<TranslationKey, string> = {
"libraryActions.metadataRefreshSchedule": "Auto-refresh",
"libraryActions.metadataRefreshDesc": "Periodically re-fetch metadata for existing series",
"libraryActions.saving": "Saving...",
"libraryActions.sectionReadingStatus": "Reading Status",
"libraryActions.readingStatusProvider": "Reading Status Provider",
"libraryActions.readingStatusProviderDesc": "Syncs reading states (read / reading / planned) with an external service",
// Reading status modal
"readingStatus.button": "Reading status",
"readingStatus.linkTo": "Link to {{provider}}",
"readingStatus.search": "Search",
"readingStatus.searching": "Searching…",
"readingStatus.searchPlaceholder": "Series title…",
"readingStatus.noResults": "No results.",
"readingStatus.link": "Link",
"readingStatus.unlink": "Unlink",
"readingStatus.changeLink": "Change",
"readingStatus.status.linked": "linked",
"readingStatus.status.synced": "synced",
"readingStatus.status.error": "error",
// Library sub-page header
"libraryHeader.libraries": "Libraries",
@@ -602,6 +619,59 @@ const en: Record<TranslationKey, string> = {
"settings.telegramHelpChat": "Send a message to your bot, then open <code>https://api.telegram.org/bot&lt;TOKEN&gt;/getUpdates</code> in your browser. The <b>chat id</b> is in <code>message.chat.id</code>.",
"settings.telegramHelpGroup": "For a group: add the bot to the group, send a message, then check the same URL. Group IDs are negative (e.g. <code>-123456789</code>).",
// Settings - AniList
"settings.anilist": "Reading status",
"settings.anilistTitle": "AniList Sync",
"settings.anilistDesc": "Sync your reading progress with AniList. Get a personal access token at anilist.co/settings/developer.",
"settings.anilistToken": "Personal Access Token",
"settings.anilistTokenPlaceholder": "AniList token...",
"settings.anilistUserId": "AniList User ID",
"settings.anilistUserIdPlaceholder": "Numeric (e.g. 123456)",
"settings.anilistConnected": "Connected as",
"settings.anilistNotConnected": "Not connected",
"settings.anilistTestConnection": "Test connection",
"settings.anilistLibraries": "Libraries",
"settings.anilistLibrariesDesc": "Enable AniList sync per library",
"settings.anilistEnabled": "AniList sync enabled",
"settings.anilistLocalUserTitle": "Local user",
"settings.anilistLocalUserDesc": "Select the local user whose reading progress is synced with this AniList account",
"settings.anilistLocalUserNone": "— Select a user —",
"settings.anilistSyncTitle": "Sync",
"settings.anilistSyncDesc": "Push local reading progress to AniList. Rules: none read → PLANNING · at least 1 read → CURRENT (progress = volumes read) · all published volumes read (total_volumes known) → COMPLETED.",
"settings.anilistSyncButton": "Sync to AniList",
"settings.anilistPullButton": "Pull from AniList",
"settings.anilistPullDesc": "Import your AniList reading list and update local reading progress. Rules: COMPLETED/CURRENT/REPEATING → books marked read up to the progress volume · PLANNING/PAUSED/DROPPED → unread.",
"settings.anilistSyncing": "Syncing...",
"settings.anilistPulling": "Pulling...",
"settings.anilistSynced": "{{count}} series synced",
"settings.anilistUpdated": "{{count}} series updated",
"settings.anilistSkipped": "{{count}} skipped",
"settings.anilistErrors": "{{count}} error(s)",
"settings.anilistLinks": "AniList Links",
"settings.anilistLinksDesc": "Series linked to AniList",
"settings.anilistNoLinks": "No series linked to AniList",
"settings.anilistUnlink": "Unlink",
"settings.anilistSyncStatus": "synced",
"settings.anilistLinkedStatus": "linked",
"settings.anilistErrorStatus": "error",
"settings.anilistUnlinkedTitle": "{{count}} unlinked series",
"settings.anilistUnlinkedDesc": "These series belong to AniList-enabled libraries but have no AniList link yet. Search each one to link it.",
"settings.anilistSearchButton": "Search",
"settings.anilistSearchNoResults": "No AniList results.",
"settings.anilistLinkButton": "Link",
"settings.anilistRedirectUrlLabel": "Redirect URL to configure in your AniList app:",
"settings.anilistRedirectUrlHint": "Paste this URL in the « Redirect URL » field of your application at anilist.co/settings/developer.",
"settings.anilistTokenPresent": "Token present — not verified",
"settings.anilistPreviewButton": "Preview",
"settings.anilistPreviewing": "Loading...",
"settings.anilistPreviewTitle": "{{count}} series to sync",
"settings.anilistPreviewEmpty": "No series to sync (link series to AniList first).",
"settings.anilistClientId": "AniList Client ID",
"settings.anilistClientIdPlaceholder": "E.g. 37777",
"settings.anilistConnectButton": "Connect with AniList",
"settings.anilistConnectDesc": "Use OAuth to connect automatically. Find your Client ID in your AniList apps (anilist.co/settings/developer).",
"settings.anilistManualToken": "Manual token (advanced)",
// Settings - Language
"settings.language": "Language",
"settings.languageDesc": "Choose the interface language",