1 {$_(`dateConflict.${typeKey}.keepDesc`, { values: i18nValues })}
2 {$_(`dateConflict.${typeKey}.clearDesc`, { values: i18nValues })}
diff --git a/frontend/src/lib/i18n/locales/de.json b/frontend/src/lib/i18n/locales/de.json index 989f8fc8..06e59c0c 100644 --- a/frontend/src/lib/i18n/locales/de.json +++ b/frontend/src/lib/i18n/locales/de.json @@ -74,7 +74,9 @@ "worstRated": "Am schlechtesten bewertet", "showMore": "Mehr anzeigen", "totalBooksAndAuthors": "Bücher insgesamt", - "booksFromAuthors": "von {authors} {authors, plural, one {Autor} other {Autoren}}" + "booksFromAuthors": "von {authors} {authors, plural, one {Autor} other {Autoren}}", + "totalReads": "Lesungen gesamt", + "rereadBooks": "Erneut gelesene Bücher" }, "dashboard": { "title": "Lese-Dashboard", @@ -213,7 +215,17 @@ "progressPromptSkip": "Überspringen", "authorsPlaceholder": "Autor eingeben und Enter drücken", "authorsHint": "Enter fügt einen Autor hinzu. Mit Rücktaste entfernt man den letzten.", - "authorsLabel": "Autor(en)" + "authorsLabel": "Autor(en)", + "readAgain": "Erneut lesen", + "startAgain": "Erneut beginnen", + "readingHistory": "Leseverlauf", + "addPastReading": "Frühere Lektüre hinzufügen", + "sessionStarted": "Begonnen", + "sessionFinished": "Beendet", + "sessionReread": "Erneut gelesen", + "sessionDeleteConfirm": "Diese Lesesitzung und ihre Fortschrittseinträge löschen?", + "sessionDelete": "Sitzung löschen", + "readCount": "{count, plural, one {1 Mal} other {# Mal}} gelesen" }, "addModal": { "manual": "Manuell", @@ -345,7 +357,8 @@ "clearAndStart": "Beendigungsdatum löschen & heute starten", "keepDesc": "Behält das Beendigungsdatum ({finishedDate}) und setzt kein Startdatum.", "clearDesc": "Entfernt das Beendigungsdatum und setzt heute ({newStartDate}) als Startdatum." - } + }, + "startNewRead": "Neue Lektüre beginnen (behält frühere)" }, "acquisition": { "owned": "Im Besitz", @@ -703,7 +716,8 @@ "subtitle": "Eine chronologische Ansicht der Bücher, die du gelesen hast", "viewInLibrary": "Alle in der Bibliothek anzeigen", "noReadBooks": "Noch keine gelesenen Bücher in deiner Bibliothek.", - "goToLibrary": "Zur Bibliothek" + "goToLibrary": "Zur Bibliothek", + "reread": "Erneut gelesen" }, "data": { "title": "Datenverwaltung", @@ -719,7 +733,8 @@ "books": "Bücher", "progress": "Lesefortschritt", "tags": "Tags", - "covers": "Cover-Dateien" + "covers": "Cover-Dateien", + "sessions": "Lesesitzungen" }, "button": "Daten exportieren", "exporting": "Exportiere...", diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index ad6c5c2c..0937ef6f 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -74,7 +74,9 @@ "noRating": "No rating", "topRated": "Top Rated", "worstRated": "Worst Rated", - "showMore": "Show more" + "showMore": "Show more", + "totalReads": "Total reads", + "rereadBooks": "Reread books" }, "dashboard": { "title": "Reading Dashboard", @@ -213,7 +215,17 @@ "progressPromptSkip": "Skip", "authorsPlaceholder": "Type an author and press Enter", "authorsLabel": "Author(s)", - "authorsHint": "Press Enter to add authors. Backspace removes the last author." + "authorsHint": "Press Enter to add authors. Backspace removes the last author.", + "readAgain": "Read again", + "startAgain": "Start again", + "readingHistory": "Reading history", + "addPastReading": "Add a past reading", + "sessionStarted": "Started", + "sessionFinished": "Finished", + "sessionReread": "Reread", + "sessionDeleteConfirm": "Delete this reading session and its progress entries?", + "sessionDelete": "Delete session", + "readCount": "Read {count, plural, one {1 time} other {# times}}" }, "addModal": { "manual": "Manual", @@ -345,7 +357,8 @@ "clearAndStart": "Clear finish date & start today", "keepDesc": "Keeps the finish date ({finishedDate}) and does not set a start date.", "clearDesc": "Removes the finish date and sets today ({newStartDate}) as the start date." - } + }, + "startNewRead": "Start a new read (keeps your earlier reads)" }, "acquisition": { "owned": "Owned", @@ -703,7 +716,8 @@ "subtitle": "A chronological view of books you've finished reading", "viewInLibrary": "View all in library", "noReadBooks": "No finished books in your library yet.", - "goToLibrary": "Go to library" + "goToLibrary": "Go to library", + "reread": "Reread" }, "data": { "title": "Data Management", @@ -719,7 +733,8 @@ "books": "Books", "progress": "Reading progress", "tags": "Tags", - "covers": "Cover files" + "covers": "Cover files", + "sessions": "Reading sessions" }, "button": "Export data", "exporting": "Exporting...", diff --git a/frontend/src/lib/i18n/locales/es.json b/frontend/src/lib/i18n/locales/es.json index 990607b5..15114de8 100644 --- a/frontend/src/lib/i18n/locales/es.json +++ b/frontend/src/lib/i18n/locales/es.json @@ -74,7 +74,9 @@ "worstRated": "Peor valorados", "showMore": "Mostrar más", "totalBooksAndAuthors": "Libros en total", - "booksFromAuthors": "de {authors} {authors, plural, one {autor} other {autores}}" + "booksFromAuthors": "de {authors} {authors, plural, one {autor} other {autores}}", + "totalReads": "Lecturas totales", + "rereadBooks": "Libros releídos" }, "dashboard": { "title": "Panel de lectura", @@ -213,7 +215,17 @@ "progressPromptSkip": "Saltar", "authorsPlaceholder": "Escribe un autor y pulsa Enter", "authorsHint": "Pulsa Enter para añadir autores. Retroceso elimina el último.", - "authorsLabel": "Autor(es)" + "authorsLabel": "Autor(es)", + "readAgain": "Volver a leer", + "startAgain": "Empezar de nuevo", + "readingHistory": "Historial de lectura", + "addPastReading": "Añadir lectura pasada", + "sessionStarted": "Iniciado", + "sessionFinished": "Terminado", + "sessionReread": "Releído", + "sessionDeleteConfirm": "¿Eliminar esta sesión de lectura y sus entradas de progreso?", + "sessionDelete": "Eliminar sesión", + "readCount": "Leído {count, plural, one {1 vez} other {# veces}}" }, "addModal": { "manual": "Manual", @@ -345,7 +357,8 @@ "clearAndStart": "Borrar fecha de finalización y empezar hoy", "keepDesc": "Conserva la fecha de finalización ({finishedDate}) y no establece una fecha de inicio.", "clearDesc": "Elimina la fecha de finalización y establece hoy ({newStartDate}) como fecha de inicio." - } + }, + "startNewRead": "Empezar una nueva lectura (conserva las anteriores)" }, "acquisition": { "owned": "En propiedad", @@ -703,7 +716,8 @@ "subtitle": "Una vista cronológica de los libros que has terminado", "viewInLibrary": "Ver todo en la biblioteca", "noReadBooks": "Aún no hay libros terminados en tu biblioteca.", - "goToLibrary": "Ir a la biblioteca" + "goToLibrary": "Ir a la biblioteca", + "reread": "Releído" }, "data": { "title": "Gestión de datos", @@ -719,7 +733,8 @@ "books": "Libros", "progress": "Progreso de lectura", "tags": "Etiquetas", - "covers": "Archivos de portada" + "covers": "Archivos de portada", + "sessions": "Sesiones de lectura" }, "button": "Exportar datos", "exporting": "Exportando...", diff --git a/frontend/src/lib/i18n/locales/fr.json b/frontend/src/lib/i18n/locales/fr.json index e198a71c..47036cf8 100644 --- a/frontend/src/lib/i18n/locales/fr.json +++ b/frontend/src/lib/i18n/locales/fr.json @@ -74,7 +74,9 @@ "worstRated": "Les moins bien notés", "showMore": "Afficher plus", "totalBooksAndAuthors": "Livres au total", - "booksFromAuthors": "de {authors} {authors, plural, one {auteur} other {auteurs}}" + "booksFromAuthors": "de {authors} {authors, plural, one {auteur} other {auteurs}}", + "totalReads": "Lectures totales", + "rereadBooks": "Livres relus" }, "dashboard": { "title": "Tableau de bord de lecture", @@ -213,7 +215,17 @@ "progressPromptSkip": "Passer", "authorsPlaceholder": "Saisis un auteur et appuie sur Entrée", "authorsHint": "Entrée pour ajouter des auteurs. Retour arrière supprime le dernier.", - "authorsLabel": "Auteur(s)" + "authorsLabel": "Auteur(s)", + "readAgain": "Relire", + "startAgain": "Recommencer", + "readingHistory": "Historique de lecture", + "addPastReading": "Ajouter une lecture passée", + "sessionStarted": "Commencé", + "sessionFinished": "Terminé", + "sessionReread": "Relu", + "sessionDeleteConfirm": "Supprimer cette session de lecture et ses entrées de progression ?", + "sessionDelete": "Supprimer la session", + "readCount": "Lu {count, plural, one {1 fois} other {# fois}}" }, "addModal": { "manual": "Manuel", @@ -345,7 +357,8 @@ "clearAndStart": "Effacer la date de fin et commencer aujourd'hui", "keepDesc": "Conserve la date de fin ({finishedDate}) et ne définit pas de date de début.", "clearDesc": "Supprime la date de fin et définit aujourd'hui ({newStartDate}) comme date de début." - } + }, + "startNewRead": "Commencer une nouvelle lecture (conserve les précédentes)" }, "acquisition": { "owned": "Possédé", @@ -703,7 +716,8 @@ "subtitle": "Une vue chronologique des livres que tu as terminés", "viewInLibrary": "Voir tout dans la bibliothèque", "noReadBooks": "Aucun livre terminé dans ta bibliothèque.", - "goToLibrary": "Aller à la bibliothèque" + "goToLibrary": "Aller à la bibliothèque", + "reread": "Relu" }, "data": { "title": "Gestion des données", @@ -719,7 +733,8 @@ "books": "Livres", "progress": "Progression de lecture", "tags": "Étiquettes", - "covers": "Fichiers de couverture" + "covers": "Fichiers de couverture", + "sessions": "Sessions de lecture" }, "button": "Exporter les données", "exporting": "Exportation...", diff --git a/frontend/src/lib/i18n/locales/zh.json b/frontend/src/lib/i18n/locales/zh.json index 95793fd2..90b8ee6e 100644 --- a/frontend/src/lib/i18n/locales/zh.json +++ b/frontend/src/lib/i18n/locales/zh.json @@ -74,7 +74,9 @@ "worstRated": "评分最低", "showMore": "显示更多", "totalBooksAndAuthors": "书籍总数", - "booksFromAuthors": "来自 {authors} 位不同的作者" + "booksFromAuthors": "来自 {authors} 位不同的作者", + "totalReads": "总阅读次数", + "rereadBooks": "重读书籍" }, "dashboard": { "title": "阅读仪表盘", @@ -213,7 +215,17 @@ "progressPromptSkip": "跳过", "authorsPlaceholder": "输入作者并按回车", "authorsHint": "按回车添加作者。按退格键删除上一个。", - "authorsLabel": "作者" + "authorsLabel": "作者", + "readAgain": "再次阅读", + "startAgain": "重新开始", + "readingHistory": "阅读历史", + "addPastReading": "添加过去的阅读", + "sessionStarted": "开始", + "sessionFinished": "结束", + "sessionReread": "重读", + "sessionDeleteConfirm": "删除此阅读会话及其进度记录?", + "sessionDelete": "删除会话", + "readCount": "阅读 {count} 次" }, "addModal": { "manual": "手动添加", @@ -345,7 +357,8 @@ "clearAndStart": "清除完成日期并从今天开始", "keepDesc": "保留完成日期({finishedDate}),不设置开始日期。", "clearDesc": "删除完成日期并将今天({newStartDate})设为开始日期。" - } + }, + "startNewRead": "开始新的阅读(保留之前的阅读)" }, "acquisition": { "owned": "已拥有", @@ -703,7 +716,8 @@ "subtitle": "你已完成阅读的图书的时间线视图", "viewInLibrary": "在书库中查看全部", "noReadBooks": "你的书库中还没有已读完的图书。", - "goToLibrary": "前往书库" + "goToLibrary": "前往书库", + "reread": "重读" }, "data": { "title": "数据管理", @@ -719,7 +733,8 @@ "books": "图书", "progress": "阅读进度", "tags": "标签", - "covers": "封面文件" + "covers": "封面文件", + "sessions": "阅读会话" }, "button": "导出数据", "exporting": "导出中...", diff --git a/frontend/src/lib/publicProfile/sections.test.ts b/frontend/src/lib/publicProfile/sections.test.ts index aff33cf6..52a7108f 100644 --- a/frontend/src/lib/publicProfile/sections.test.ts +++ b/frontend/src/lib/publicProfile/sections.test.ts @@ -44,7 +44,8 @@ const ALL_STATISTICS: PublicProfileStatisticsKey[] = [ 'books_without_rating', 'average_rating', 'top_rated_books', - 'worst_rated_books' + 'worst_rated_books', + 'total_reads' ]; function lookup(obj: unknown, path: string): unknown { diff --git a/frontend/src/lib/publicProfile/sections.ts b/frontend/src/lib/publicProfile/sections.ts index 57dee000..80573507 100644 --- a/frontend/src/lib/publicProfile/sections.ts +++ b/frontend/src/lib/publicProfile/sections.ts @@ -196,6 +196,12 @@ export const PUBLIC_PROFILE_STATISTICS: PublicProfileStatisticsDef[] = [ i18nKey: 'statistics.worstRated', group: 'ratings', defaultOn: false + }, + { + key: 'total_reads', + i18nKey: 'statistics.totalReads', + group: 'summary', + defaultOn: false } ]; diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 7cb25d39..5fd459f5 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -24,6 +24,8 @@ export interface Book { date_added: string; // ISO datetime date_started: string | null; date_finished: string | null; + read_count?: number; + session_count?: number; } export interface BookListResponse { @@ -212,6 +214,8 @@ export interface StatisticsResponse { average_rating: number | null; top_rated_books: TopRatedBook[]; worst_rated_books: TopRatedBook[]; + total_reads?: number; + reread_books?: number; } export type StatisticsRange = 'alltime' | 'this_year' | 'last_year' | '3years' | 'custom'; @@ -348,6 +352,7 @@ export interface ReadingProgressEntry { id: number; book_id: number; page: number; + session_id?: number | null; created_at: string; updated_at: string; } @@ -357,6 +362,52 @@ export interface BookProgress { current_page: number; } +export interface ReadingSession { + id: number; + book_id: number; + status: ReadingStatus; + date_started: string | null; + date_finished: string | null; + is_current: boolean; + read_number: number | null; + is_reread: boolean; + progress_count: number; + last_page: number | null; + max_page: number | null; + created_at: string; + updated_at: string; +} + +export interface ReadingSessionInput { + status: ReadingStatus; + date_started?: string | null; + date_finished?: string | null; +} + +export interface SessionMutationResponse { + session: ReadingSession; + book: Book; +} + +export interface TimelineSessionEntry { + session_id: number; + book_id: number; + title: string; + author: string | null; + authors: string[]; + cover_url: string | null; + rating: number | null; + page_count: number | null; + date_finished: string | null; + read_number: number | null; + is_reread: boolean; +} + +export interface TimelineSessionListResponse { + entries: TimelineSessionEntry[]; + total: number; +} + /** Search sources the backend can report progress for. */ export type ImportSource = | 'open_library' @@ -378,7 +429,7 @@ export type SearchStage = | { stage: 'complete'; results: BookImportCandidate[] } | { stage: 'error'; message: string }; -export type DataExportDataset = 'books' | 'progress' | 'tags' | 'covers'; +export type DataExportDataset = 'books' | 'progress' | 'tags' | 'covers' | 'sessions'; export type DataExportFormat = 'csv' | 'json'; export interface DataImportParseResponse { @@ -511,7 +562,8 @@ export type PublicProfileStatisticsKey = | 'books_without_rating' | 'average_rating' | 'top_rated_books' - | 'worst_rated_books'; + | 'worst_rated_books' + | 'total_reads'; export interface PublicProfileVisibilityConfig { sections: PublicProfileSectionKey[]; diff --git a/frontend/src/routes/library/+page.svelte b/frontend/src/routes/library/+page.svelte index d49b6809..e63859fb 100644 --- a/frontend/src/routes/library/+page.svelte +++ b/frontend/src/routes/library/+page.svelte @@ -513,6 +513,7 @@ onEdit={openEditFromDetail} onDelete={handleDelete} onProgress={handleProgressChange} + onSave={handleSave} />{item.book.title}
- {#if item.book.author} -{formatAuthors(item.book.authors, item.book.author)}
+{item.entry.title}
+ {#if item.entry.author} +{formatAuthors(item.entry.authors, item.entry.author)}
+ {/if} + {#if item.entry.rating} +{stars(item.entry.rating)}
{/if} - {#if item.book.rating} -{stars(item.book.rating)}
+ {#if item.entry.is_reread} + {$_('timeline.reread')} {/if}