From 13d9f539915255073b5ea6c7b349634ad5766643 Mon Sep 17 00:00:00 2001 From: codebude Date: Mon, 8 Jun 2026 13:14:45 +0200 Subject: [PATCH 01/11] Reworked pages per day statistics logic --- backend/app/routers/statistics.py | 42 +++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/backend/app/routers/statistics.py b/backend/app/routers/statistics.py index 3d1576d0..ec5028b4 100644 --- a/backend/app/routers/statistics.py +++ b/backend/app/routers/statistics.py @@ -240,15 +240,41 @@ def get_pages_per_day( start_date_utc = start_date.astimezone(timezone.utc).replace(tzinfo=None) end_date_utc = end_date.astimezone(timezone.utc).replace(tzinfo=None) - progress_entries = list( + # Books with at least one progress entry in the window — we need their full + # entry chains to compute correct daily averages. + book_ids_with_window_progress = set( session.exec( - select(ReadingProgress) + select(ReadingProgress.book_id) .where( ReadingProgress.user_id == current_user.id, ReadingProgress.created_at >= start_date_utc, - ReadingProgress.created_at <= end_date_utc, ) - .order_by(ReadingProgress.book_id, ReadingProgress.created_at) + .distinct() + ).all() + ) + + # Load full entry chains for those books (including entries before the window + # so that the prev→curr delta and day_diff span are complete). + if book_ids_with_window_progress: + progress_entries = list( + session.exec( + select(ReadingProgress) + .where( + ReadingProgress.user_id == current_user.id, + ReadingProgress.book_id.in_(book_ids_with_window_progress), + ) + .order_by(ReadingProgress.book_id, ReadingProgress.created_at) + ).all() + ) + else: + progress_entries = [] + + # All book_ids with *any* progress entry (used to exclude books from fallback). + all_book_ids_with_progress = set( + session.exec( + select(ReadingProgress.book_id) + .where(ReadingProgress.user_id == current_user.id) + .distinct() ).all() ) @@ -256,11 +282,9 @@ def get_pages_per_day( session.exec(select(Book).where(Book.user_id == current_user.id)).all() ) - books_with_progress = {e.book_id for e in progress_entries} - virtual_entries = [] for book in books: - if book.id not in books_with_progress or not book.date_started: + if book.id not in all_book_ids_with_progress or not book.date_started: continue # Finished books without date_finished have no bounded reading # period; skip to avoid spreading pages from date_started to @@ -281,11 +305,13 @@ def get_pages_per_day( fallback_books = [ b for b in books - if b.id not in books_with_progress + if b.id not in all_book_ids_with_progress and b.reading_status == ReadingStatus.read and b.date_started and b.date_finished and b.page_count + # Only include books whose reading period could overlap the window. + and _naive_utc(b.date_finished) >= start_date_utc ] fallback_daily = _extract_book_level_daily_pages(fallback_books, tz, start_date_utc, end_date_utc) From ff1c270817fe82662ba2d593889efe31b7c8c9b3 Mon Sep 17 00:00:00 2001 From: codebude Date: Mon, 8 Jun 2026 13:23:47 +0200 Subject: [PATCH 02/11] Fixed backend test cases --- backend/tests/test_data.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/backend/tests/test_data.py b/backend/tests/test_data.py index 69a5dbad..687ebb11 100644 --- a/backend/tests/test_data.py +++ b/backend/tests/test_data.py @@ -315,33 +315,6 @@ def test_data_import_validate_rejects_invalid_reading_status_enum(client: TestCl assert any("reading_status" in error for error in payload["errors"]) -def test_data_import_execute_deletes_temp_file_after_completion(client: TestClient, monkeypatch: MonkeyPatch, tmp_path: Path) -> None: - import_temp_dir = tmp_path / "import_temp" - monkeypatch.setattr(settings, "import_temp_dir", str(import_temp_dir)) - csv_payload = "Title\nDune\n" - parse_resp = client.post( - "/api/data/import/parse", - files={"file": ("books.csv", csv_payload, "text/csv")}, - ) - assert parse_resp.status_code == 200 - file_id = parse_resp.json()["file_id"] - - temp_file = import_temp_dir / "1" / f"{file_id}.json" - assert temp_file.exists() - - execute_resp = client.post( - "/api/data/import/execute", - json={ - "file_id": file_id, - "mapping": {"title": {"source": "Title", "transform": None}}, - "import_mode": "continue_on_error", - }, - ) - assert execute_resp.status_code == 200 - events = _parse_sse(execute_resp.text) - assert any(event.get("event") == "complete" for event in events) - assert not temp_file.exists() - def test_data_import_execute_progress_uses_date_finished_for_read_books( client: TestClient, monkeypatch: MonkeyPatch, tmp_path: Path From 433bb4d9ae7fcee43008de03fc2e57c17f7e3f2f Mon Sep 17 00:00:00 2001 From: codebude Date: Mon, 8 Jun 2026 13:24:49 +0200 Subject: [PATCH 03/11] Add i18n translation files for es, fr and zh --- frontend/src/lib/i18n/locales/de.json | 5 +- frontend/src/lib/i18n/locales/en.json | 5 +- frontend/src/lib/i18n/locales/es.json | 650 ++++++++++++++++++++++++++ frontend/src/lib/i18n/locales/fr.json | 650 ++++++++++++++++++++++++++ frontend/src/lib/i18n/locales/zh.json | 650 ++++++++++++++++++++++++++ 5 files changed, 1958 insertions(+), 2 deletions(-) create mode 100644 frontend/src/lib/i18n/locales/es.json create mode 100644 frontend/src/lib/i18n/locales/fr.json create mode 100644 frontend/src/lib/i18n/locales/zh.json diff --git a/frontend/src/lib/i18n/locales/de.json b/frontend/src/lib/i18n/locales/de.json index 61befc0b..66b80150 100644 --- a/frontend/src/lib/i18n/locales/de.json +++ b/frontend/src/lib/i18n/locales/de.json @@ -278,7 +278,10 @@ }, "languages": { "en": "Englisch", - "de": "Deutsch" + "de": "Deutsch", + "zh": "Chinesisch", + "es": "Spanisch", + "fr": "Französisch" }, "auth": { "login": "Anmelden", diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index f091803f..cd1162ad 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -278,7 +278,10 @@ }, "languages": { "en": "English", - "de": "German" + "de": "German", + "zh": "Chinese", + "es": "Spanish", + "fr": "French" }, "auth": { "login": "Login", diff --git a/frontend/src/lib/i18n/locales/es.json b/frontend/src/lib/i18n/locales/es.json new file mode 100644 index 00000000..d51595c3 --- /dev/null +++ b/frontend/src/lib/i18n/locales/es.json @@ -0,0 +1,650 @@ +{ + "app": { + "title": "LibrisLog", + "addBook": "Añadir libro", + "add": "Añadir", + "language": "Idioma" + }, + "nav": { + "dashboard": "Panel", + "library": "Biblioteca", + "timeline": "Cronología", + "statistics": "Estadísticas", + "data": "Datos", + "want_to_read": "Quiero leer", + "currently_reading": "Leyendo", + "read": "Leído", + "did_not_finish": "Abandonado" + }, + "statistics": { + "title": "Estadísticas", + "subtitle": "Información sobre tu viaje lector", + "avgBooksPerMonth": "Prom. libros/mes", + "busiestMonth": "Mes más activo", + "avgPageCount": "Prom. páginas/libro", + "mostPopularLanguage": "Idioma más frecuente", + "languageDistribution": "Libros por idioma", + "statusDistribution": "Libros por estado", + "pageBuckets": "Estadísticas de páginas", + "pagesToRead": "Páginas por leer", + "pagesRead": "Páginas leídas", + "pagesWasted": "Páginas desperdiciadas", + "pagesWastedFootnote": "\"Páginas desperdiciadas\" = página máxima alcanzada en libros marcados como \"No terminado\"", + "pagesReadPerMonth": "Páginas leídas por mes", + "booksFinishedPerMonth": "Libros terminados por mes", + "booksFinishedPerYear": "Libros terminados por año", + "topAuthors": "Autores destacados", + "rankedNumber": "#{rank}", + "coversForAuthor": "Portadas de {author}", + "booksCount": "{count} {count, plural, one {libro} other {libros}}", + "unknownLanguage": "Desconocido", + "pagesReadCalendar": "Actividad lectora (últimos 365 días)", + "noCalendarData": "No hay datos de lectura para el último año", + "pagesOver": "páginas en", + "daysLabel": "días", + "avgPerDay": "Prom. por día activo:", + "avgPerDayAll": "Prom. por día (365 días):", + "pagesPerDay": "páginas/día", + "loading": "Cargando estadísticas...", + "noData": "Aún no hay datos. ¡Empieza a leer y registrar libros para ver estadísticas!", + "resetZoom": "Restablecer zoom", + "sectionDistributions": "Distribuciones", + "sectionCharts": "Tendencias de lectura", + "sectionActivity": "Actividad", + "ratingStats": "Estadísticas de valoración", + "booksWithRating": "Libros valorados", + "booksWithoutRating": "Libros sin valorar", + "averageRating": "Valoración media", + "noRating": "Sin valoración", + "topRated": "Mejor valorados", + "worstRated": "Peor valorados", + "showMore": "Mostrar más" + }, + "dashboard": { + "title": "Panel de lectura", + "subtitle": "Una visión rápida de tu viaje lector", + "quoteTitle": "Cita del día", + "quoteUnavailable": "No hay ninguna cita disponible ahora.", + "totalBooks": "Total en biblioteca", + "booksRead": "Libros leídos", + "booksToRead": "Libros por leer", + "currentlyReading": "Leyendo ahora", + "nextToRead": "Siguiente lectura", + "viewAll": "Ver todo", + "searchAllBooks": "Buscar todos los libros", + "noSearchResults": "No se encontraron libros", + "noCurrentlyReading": "No estás leyendo ningún libro actualmente.", + "noNextToRead": "Aún no hay libros en tu lista de deseos.", + "popularTags": "Etiquetas populares" + }, + "status": { + "want_to_read": "Quiero leer", + "currently_reading": "Leyendo", + "read": "Leído", + "did_not_finish": "Abandonado" + }, + "common": { + "search": "Buscar", + "searchBooks": "Buscar libros...", + "result": "resultado", + "results": "resultados", + "save": "Guardar", + "saved": "Guardado", + "saveFailed": "Error al guardar", + "edit": "Editar", + "cancel": "Cancelar", + "confirm": "¿Confirmar?", + "delete": "Eliminar", + "deleting": "Eliminando...", + "back": "Volver", + "loadMore": "Cargar más", + "syncing": "Sincronizando...", + "noBooksYet": "Aún no hay libros aquí.", + "addFirstBook": "Añade tu primer libro", + "dateAdded": "Fecha de incorporación", + "rating": "Valoración", + "ratingSaved": "Valoración guardada", + "desc": "Desc.", + "asc": "Asc.", + "close": "Cerrar", + "clearForm": "Limpiar formulario", + "remove": "Quitar", + "copy": "Copiar", + "copied": "Copiado", + "showPassword": "Mostrar contraseña", + "required": "Obligatorio", + "saving": "Guardando...", + "loadingEllipsis": "...", + "starLabel": "{star} {star, plural, one {estrella} other {estrellas}}", + "clickToRate": "Haz clic en una estrella para valorar", + "actionFailed": "{action} falló", + "readMore": "Leer más", + "readLess": "Mostrar menos", + "serverStarting": "El servidor está iniciándose...", + "serverStartingDesc": "Por favor, espera mientras el servidor termina de arrancar." + }, + "book": { + "title": "Título", + "subtitle": "Subtítulo", + "author": "Autor", + "status": "Estado", + "isbn": "ISBN", + "publisher": "Editorial", + "year": "Año", + "pages": "Páginas", + "language": "Idioma", + "tags": "Etiquetas", + "tagsPlaceholder": "Escribe una etiqueta y pulsa Enter o coma", + "tagsHint": "Pulsa Enter o coma para añadir etiquetas. Backspace elimina la última.", + "notes": "Notas", + "blurb": "Descripción", + "about": "Acerca de", + "dateStarted": "Fecha de inicio", + "dateFinished": "Fecha de finalización", + "cover": "Portada", + "coverForAuthor": "Portada {index} de {author}", + "googleCovers": "Portadas de Google", + "autoSearchCovers": "Buscar portadas automáticamente", + "autoSearchInfo": "Haz clic en una portada para importarla como portada de este libro.", + "autoSearchNoCandidates": "No se encontraron candidatos para este ISBN.", + "autoSearchError": "Error al buscar portadas automáticamente.", + "autoSearchLoading": "Buscando fuentes de portadas...", + "autoSearchMetaUnknown": "Tamaño/resolución desconocidos", + "autoSearchMeta": "{size} - {resolution}", + "autoSearchSourceLabel": "Fuente: {source}", + "coverOf": "Portada de {title}", + "openDetailsHint": "Haz clic para ver detalles", + "readingProgress": "Progreso de lectura", + "currentPage": "Página", + "progressLog": "Registro de progreso", + "progressLogEmpty": "Aún no hay entradas de progreso.", + "setPageCountFirst": "Establece el total de páginas primero.", + "logDate": "Fecha", + "logPage": "Página", + "deleteEntry": "Eliminar", + "deleteEntryConfirm": "¿Eliminar esta entrada?", + "editEntry": "Editar", + "saveEntry": "Guardar", + "progressGraph": "Progreso en el tiempo", + "progressPromptTitle": "¿Establecer progreso de lectura?", + "progressPromptMessage": "¿Establecer el progreso de \"{title}\" al 100%?", + "progressPromptSet": "Establecer al 100%", + "progressPromptSkip": "Saltar" + }, + "addModal": { + "manual": "Manual", + "searchImport": "Buscar e importar", + "adding": "Añadiendo...", + "failedAdd": "Error al añadir libro", + "importFromFile": "Importar desde archivo" + }, + "import": { + "searchByTitleOrAuthor": "Buscar por título o autor...", + "enterIsbn": "Introduce el ISBN...", + "noResultsYet": "Aún sin resultados", + "noBooksFound": "No se encontraron libros", + "alreadyImported": "Ya importado", + "imported": "Importado", + "googleToo": "Buscar también en Google Books", + "googleSearching": "Buscando en Google Books...", + "googleAdded": "Resultados de Google Books añadidos: {count}", + "scan": "Escanear", + "scanIsbn": "Escanear código de barras ISBN", + "importFailed": "Importación fallida", + "searchFailed": "Búsqueda fallida", + "scannedIsbn": "ISBN escaneado: {isbn}", + "or": "o", + "sourceHardcoverSearching": "Buscando en Hardcover...", + "sourceHardcoverSkipped": "Hardcover omitido (sin token de API configurado)", + "sourceSkipped": "Google Books omitido (sin clave de API configurada)", + "sourceOpenLibrarySearching": "Buscando en Open Library...", + "sourceGoogleSearching": "Buscando en Google Books...", + "sourceBackendError": "Error de backend de {source} (revisa los registros del servidor)", + "sourceError": "Búsqueda fallida: {message}", + "resultCount": "{source} - {count} resultado{suffix}" + }, + "scanner": { + "title": "Escanear código de barras ISBN", + "help": "Apunta la cámara al código de barras de un libro. La búsqueda comienza automáticamente tras encontrar un ISBN válido.", + "startError": "No se pudo iniciar el escáner. Comprueba los permisos de la cámara.", + "noCamera": "No se encontró ningún dispositivo de cámara.", + "close": "Cerrar escáner" + }, + "coverPicker": { + "dropzone": "Arrastra una imagen aquí, o", + "browse": "examinar", + "pasteUrl": "O pega una URL de imagen...", + "useUrl": "Usar URL", + "urlInvalid": "No se pudo cargar la portada desde la URL. Verifica el enlace.", + "uploadFailed": "Subida fallida", + "previewAlt": "Vista previa de portada" + }, + "toasts": { + "dismiss": "Descartar", + "newVersion": "Una nueva versión ({version}) está disponible.", + "reload": "Recargar" + }, + "settings": { + "title": "Ajustes", + "languageTitle": "Idioma", + "timezone": "Zona horaria", + "timezoneHelp": "Mostrar fechas y horas en tu zona horaria local.", + "timezoneDetected": "Detectada: {tz}", + "timezoneSelected": "Seleccionada: {tz}", + "timezoneInvalid": "Selecciona una zona horaria válida de la lista.", + "themeTitle": "Tema", + "themeLight": "Claro", + "themeDark": "Oscuro", + "themeCustom": "Personalizar", + "themeSelect": "Selecciona un tema personalizado", + "timezonePlaceholder": "Buscar zona horaria...", + "apiDocsTitle": "Documentación de la API", + "apiDocsHelp": "Explora y prueba los endpoints del backend directamente desde la app.", + "apiDocsViewLabel": "Ver", + "apiDocsLoading": "Cargando documentación de la API", + "apiDocsFrameTitle": "Documentación de la API", + "apiDocsOpenNewTab": "Abrir documentación en una nueva pestaña" + }, + "sort": { + "smart": "Orden inteligente" + }, + "dateConflict": { + "started": { + "title": "Fecha de inicio ya establecida", + "message": "La fecha de inicio ya está establecida. ¿Quieres conservar {oldDate} o establecer {newDate} como nueva fecha de inicio?", + "keepOld": "Conservar {oldDate}", + "useNew": "Usar {newDate}" + }, + "finished": { + "title": "Fecha de finalización ya establecida", + "message": "La fecha de finalización ya está establecida. ¿Quieres conservar {oldDate} o establecer {newDate} como nueva fecha de finalización?", + "keepOld": "Conservar {oldDate}", + "useNew": "Usar {newDate}" + }, + "startedAfterFinished": { + "title": "El libro ya estaba terminado", + "message": "Este libro se terminó el {finishedDate}. ¿Qué deberíamos hacer?", + "keepFinished": "Conservar fecha de finalización", + "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." + } + }, + "search": { + "resultsCount": "{count, plural, one {resultado} other {resultados}} encontrados", + "noResults": "No se encontraron resultados", + "noResultsFor": "No se encontraron resultados para \"{query}\"", + "tryDifferentQuery": "Prueba con un término de búsqueda diferente" + }, + "languages": { + "en": "Inglés", + "de": "Alemán", + "zh": "Chino", + "es": "Español", + "fr": "Francés" + }, + "auth": { + "login": "Iniciar sesión", + "firstname": "Nombre", + "lastname": "Apellido", + "email": "Correo electrónico", + "password": "Contraseña", + "loginFailed": "Inicio de sesión fallido", + "setupTitle": "Crear cuenta de administrador", + "setupFailed": "Configuración fallida", + "createAdmin": "Crear administrador", + "invalidEmailError": "Introduce una dirección de correo electrónico válida", + "passwordComplexityError": "La contraseña no cumple los requisitos de complejidad" + }, + "user": { + "menu": "Menú de usuario", + "profile": "Perfil", + "about": "Acerca de", + "theme": "Tema", + "logout": "Cerrar sesión", + "apiKeys": "Claves de API", + "keyDescription": "Descripción (opcional)", + "addKey": "Añadir clave", + "newKeyShownOnce": "Copia esta clave ahora. Solo se muestra una vez", + "noDescription": "Sin descripción", + "newPassword": "Nueva contraseña" + }, + "admin": { + "title": "Administración", + "tabs": { + "users": "Usuarios", + "backup": "Copia de seguridad y restauración" + }, + "newUser": "Crear usuario", + "existingUsers": "Usuarios existentes", + "role": "Rol", + "create": "Crear", + "editing": "Editando usuario", + "edit": "Editar", + "saveChanges": "Guardar cambios", + "cancelEdit": "Cancelar edición", + "deleteConfirmTitle": "¿Realmente quieres eliminar este usuario?", + "deleteConfirmBody": "Esta acción no se puede deshacer.", + "cannotChangeOwnRole": "No puedes cambiar tu propio rol de administrador.", + "requiredFieldError": "Rellena todos los campos obligatorios.", + "selfDeleteHint": "Para eliminar tu propia cuenta, usa Perfil > Zona de peligro.", + "backup": { + "title": "Copia de seguridad", + "description": "Descarga una copia completa de tu biblioteca, incluyendo libros, portadas y datos.", + "download": "Descargar copia", + "success": "Copia descargada correctamente", + "failed": "Error al descargar la copia", + "inProgress": "Creando copia de seguridad..." + }, + "restore": { + "title": "Restaurar", + "description": "Restaura tu biblioteca desde un archivo de copia de seguridad anterior.", + "warning": "Advertencia: La restauración reemplazará TODOS los datos actuales. Asegúrate de tener una copia reciente antes de continuar.", + "upload": "Subir y restaurar", + "success": "Restauración completada. Se restauraron {books} libros.", + "failed": "Error al restaurar", + "inProgress": "Restaurando copia de seguridad...", + "validationFailed": "No se pudo validar el archivo de copia", + "invalidBackup": "Estructura de copia de seguridad no válida", + "confirmTitle": "Confirmar restauración", + "confirmBody": "¿Estás seguro de que quieres restaurar desde esta copia? Esto reemplazará todos los datos actuales y no se puede deshacer.", + "confirmWarning": "Esta acción es irreversible. Todos los datos actuales se perderán.", + "confirm": "Restaurar ahora", + "backupDate": "Fecha de copia", + "backupVersion": "Versión de la app", + "coversCount": "Portadas" + } + }, + "password": { + "requirementsTitle": "Requisitos de contraseña", + "minLength": "Al menos 8 caracteres", + "uppercase": "Al menos una mayúscula", + "lowercase": "Al menos una minúscula", + "number": "Al menos un número", + "special": "Al menos un carácter especial", + "strongEnough": "Suficientemente segura", + "notReady": "No cumple los requisitos" + }, + "error": { + "isbnAlreadyExists": "Este ISBN ya está siendo usado por otro libro.", + "dateInFuture": "La fecha no puede ser futura.", + "dateStartedAfterFinished": "La fecha de inicio no puede ser posterior a la de finalización.", + "dateFinishedRequiredForRead": "Un libro terminado debe tener una fecha de fin. Cambia el estado si quieres eliminar la fecha de finalización.", + "invalidLanguageCode": "El idioma debe ser un código ISO de 2 letras (por ejemplo: EN, DE, FR).", + "invalidConfirmationPhrase": "La frase de confirmación no coincide.", + "cannotDeleteLastAdmin": "No se puede eliminar la cuenta: eres el último administrador", + "cannotDeleteOwnAccountHere": "No puedes eliminar tu propia cuenta aquí. Usa Perfil > Zona de peligro.", + "importMalformedEvent": "Se recibió un evento de servidor malformado durante la importación.", + "importUnsupportedContentType": "Tipo de contenido no admitido. Usa archivos CSV o JSON.", + "emailAlreadyRegistered": "Esta dirección de correo ya está registrada.", + "userNotFound": "Usuario no encontrado.", + "cannotChangeOwnRole": "No puedes cambiar tu propio rol de administrador.", + "authorRequired": "El autor es obligatorio.", + "pageCountRequired": "El número de páginas es obligatorio.", + "importTempFileCreateFailed": "No se pudo crear el archivo temporal de importación. Inténtalo de nuevo.", + "fileTooLarge": "El archivo es demasiado grande. Prueba con un archivo más pequeño o revisa los límites del servidor.", + "exportNoDatasets": "Selecciona al menos un conjunto de datos para exportar.", + "batchUpdateFailed": "La actualización por lotes falló debido a un error inesperado. No se guardaron cambios.", + "tooManyBooksSelected": "Demasiados libros seleccionados. Selecciona como máximo {max} a la vez.", + "importMappingNameConflict": "Ya existe una asignación con este nombre.", + "importMappingNotFound": "Asignación de importación no encontrada.", + "importFileNotFound": "Archivo de importación no encontrado. Vuelve a subir el archivo." + }, + "oidc": { + "orContinueWith": "o continuar con", + "loginWithProvider": "Continuar con {provider}", + "profileTitle": "Inicio de sesión único", + "notLinked": "Tu cuenta aún no está vinculada.", + "linkButton": "Vincular cuenta de {provider}", + "unlinkButton": "Desvincular cuenta", + "linkedAs": "Vinculado con {provider}", + "linkSuccess": "Cuenta vinculada correctamente", + "linkStartFailed": "No se pudo iniciar la vinculación", + "unlinkSuccess": "Cuenta desvinculada", + "unlinkFailed": "No se pudo desvincular la cuenta", + "signingIn": "Iniciando sesión...", + "linkingAccount": "Vinculando cuenta..." + }, + "profile": { + "sectionNav": "En esta página", + "profileSaveSuccess": "Perfil guardado", + "profileSaveFailed": "Error al guardar el perfil", + "passwordChangeSuccess": "Contraseña cambiada", + "passwordChangeFailed": "Error al cambiar la contraseña", + "dataManagement": { + "title": "Gestionar mis datos", + "description": "Exporta tu biblioteca o importa libros desde un archivo CSV/JSON.", + "link": "Importar / Exportar", + "missingCoversDescription": "Asigna rápidamente portadas faltantes con sugerencias automáticas.", + "missingCoversLink": "Gestionar portadas faltantes" + }, + "dangerZone": { + "title": "Zona de peligro", + "subtitle": "Acciones irreversibles que eliminan permanentemente tus datos o cuenta.", + "resetData": { + "title": "Restablecer todos los datos personales", + "description": "Elimina todos tus libros, etiquetas y progreso de lectura, pero conserva tu cuenta y ajustes.", + "warning": "Esto no se puede deshacer.", + "placeholder": "Escribe la frase de confirmación", + "hint": "Escribe exactamente: DELETE ALL MY DATA", + "confirmationPhrase": "DELETE ALL MY DATA", + "button": "Restablecer todos los datos", + "success": "Se eliminaron {books} libros, {tags} etiquetas y {entries} entradas de progreso.", + "failed": "Error al restablecer datos" + }, + "deleteAccount": { + "title": "Eliminar cuenta", + "description": "Elimina tu cuenta y todos los datos asociados de forma permanente.", + "warning": "Esto es permanente y no se puede deshacer.", + "placeholder": "Escribe la frase de confirmación", + "hint": "Escribe exactamente: DELETE MY ACCOUNT", + "confirmationPhrase": "DELETE MY ACCOUNT", + "button": "Eliminar mi cuenta", + "success": "Cuenta eliminada. Redirigiendo al inicio de sesión...", + "failed": "Error al eliminar la cuenta", + "lastAdminError": "No se puede eliminar la cuenta: eres el último administrador" + } + } + }, + "timeline": { + "title": "Cronología de lectura", + "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" + }, + "data": { + "title": "Gestión de datos", + "subtitle": "Importa y exporta los datos de tu biblioteca personal", + "tabs": { + "export": "Exportar", + "import": "Importar" + }, + "export": { + "title": "Exportar", + "description": "Elige conjuntos de datos y formato, luego descarga un archivo ZIP.", + "datasets": { + "books": "Libros", + "progress": "Progreso de lectura", + "tags": "Etiquetas", + "covers": "Archivos de portada" + }, + "button": "Exportar datos", + "exporting": "Exportando...", + "success": "Exportación lista. Descarga iniciada.", + "errors": { + "noDatasets": "Selecciona al menos un conjunto de datos.", + "failed": "Exportación fallida." + } + }, + "import": { + "title": "Importar", + "description": "Sube un archivo CSV o JSON, asigna campos, valida y luego importa.", + "parse": "Analizar archivo", + "parsing": "Analizando...", + "fileSummary": "Filas: {rows}, campos: {fields}", + "mappingTitle": "Asignación de campos", + "mappingActionsTitle": "Gestionar asignaciones", + "mappingName": "Nombre de asignación", + "loadSavedMapping": "Asignaciones guardadas", + "noSavedMappings": "Aún no hay asignaciones guardadas. Guarda la asignación actual para reutilizarla después.", + "missingFieldsTitle": "Algunos campos de origen de la asignación guardada no están presentes en este archivo:", + "missingFieldEntry": "{target} ← {source}", + "selectMapping": "Seleccionar asignación guardada", + "loadMapping": "Cargar asignación", + "readonlyMapping": "solo lectura", + "deleteMapping": "Eliminar asignación", + "deleteMappingTitle": "Eliminar asignación guardada", + "showPreview": "Mostrar vista previa de asignación", + "createProgressForRead": "Crear entrada de progreso al 100% para libros importados como 'Leído'", + "hidePreview": "Ocultar vista previa de asignación", + "previewNoMappedFields": "Aún no hay campos asignados. Asigna campos de origen a campos de destino para previsualizar valores.", + "transformLabel": "Transformación (Python)", + "transformPlaceholder": "p. ej. value.upper()", + "previewTitle": "Vista previa", + "previewButton": "Generar", + "previewLoading": "Generando...", + "previewStale": "La vista previa está desactualizada", + "previewRow": "Fila {row}", + "errorRow": "Fila {row}", + "previewSource": "Origen", + "previewTransformed": "Transformado", + "none": "(ninguno)", + "requiredField": "= campo obligatorio", + "changeFile": "Cambiar archivo", + "coverUrlHint": "Espera una URL HTTP(S) a una imagen. No se admiten rutas de archivo locales ni datos base64.", + "transformHelp": "Parámetros disponibles y ejemplos", + "transformHelpValue": "El valor original del campo de origen asignado", + "transformHelpRow": "Todos los campos de origen como diccionario, p. ej. row['title']", + "transformHelpContext": "Diccionario de contexto con número de fila y total (context['row_num'], context['total_rows'])", + "transformHelpReturn": "Las expresiones simples se devuelven automáticamente; usa return explícito para código multilínea", + "transformHelpImports": "Importaciones de Python disponibles: datetime, re, json, math", + "transformError": "La regla de transformación para {field} no es válida: {error}", + "saveMapping": "Guardar asignación", + "refreshMappings": "Actualizar asignaciones", + "mappingSaved": "Asignación guardada", + "mappingDeleted": "Asignación eliminada", + "mappingMissingFields": "La asignación cargada tiene {count} campos de origen faltantes.", + "validationTitle": "Simulación", + "simulate": "Simular", + "validating": "Validando...", + "validationOk": "Validación superada.", + "validationNotOk": "La validación encontró problemas.", + "rollbackAll": "Revertir todo si hay error", + "continueOnError": "Continuar si hay error", + "importNow": "Importar ahora", + "importing": "Importando...", + "cancelled": "Importación cancelada.", + "confirmImportTitle": "¿Iniciar importación?", + "confirmDestructive": "Esto escribe datos en tu biblioteca y no se puede deshacer automáticamente.", + "deleteMappingConfirm": "¿Eliminar esta asignación guardada?", + "dropzone": "Arrastra y suelta un archivo CSV/JSON, o", + "browse": "examinar", + "fileInputLabel": "Elegir archivo CSV o JSON", + "showLess": "Mostrar menos", + "showAllIssues": "Mostrar todos los problemas ({count})", + "showAllFailures": "Mostrar todas las filas fallidas ({count})", + "completed": "Importación completada. Importados: {imported}, fallidos: {failed}", + "errors": { + "parseFailed": "Error al analizar el archivo.", + "saveMappingFailed": "Error al guardar la asignación.", + "deleteMappingFailed": "Error al eliminar la asignación.", + "loadMappingsFailed": "Error al cargar las asignaciones.", + "loadMappingFailed": "Error al cargar la asignación.", + "validateFailed": "Validación fallida.", + "previewFailed": "Error al cargar la vista previa.", + "executeFailed": "Importación fallida." + } + } + }, + "dataHygiene": { + "authorRequired": "El autor no puede estar vacío.", + "pageCountPositive": "El número de páginas debe ser mayor que 0.", + "title": "Higiene de datos", + "description": "Encuentra y corrige libros con metadatos faltantes en tu biblioteca.", + "attributes": { + "author": "Autor", + "isbn": "ISBN", + "publisher": "Editorial", + "published_year": "Año", + "blurb": "Descripción", + "language": "Idioma", + "subtitle": "Subtítulo", + "page_count": "Nº de páginas", + "cover_url": "Portada" + }, + "matchAny": "Coincidir cualquiera", + "matchAll": "Coincidir todos", + "noMissingBooks": "No se encontraron libros con atributos faltantes que coincidan con tus filtros.", + "total": "{count} {count, plural, one {libro} other {libros}} encontrados", + "loadMore": "Cargar más", + "loading": "Comprobando tu biblioteca...", + "selectAll": "Seleccionar todo", + "deselectAll": "Deseleccionar todo", + "nSelected": "{count} {count, plural, one {libro} other {libros}} seleccionados", + "batchEditTitle": "Edición por lotes", + "batchFieldLabel": "Campo a actualizar", + "batchFieldPlaceholder": "Selecciona un campo...", + "batchValueLabel": "Nuevo valor", + "batchValuePlaceholder": "Introduce el nuevo valor", + "applyBatch": "Aplicar a seleccionados", + "confirmTitle": "¿Actualizar {count} {count, plural, one {libro} other {libros}}?", + "confirmBody": "Esto establecerá \"{field}\" a \"{value}\" para los siguientes libros:", + "confirmApply": "Aplicar actualización", + "confirmCancel": "Cancelar", + "success": "{updated} {updated, plural, one {libro} other {libros}} actualizados. {skipped} ya tenían este valor.", + "updateFailed": "Error en la actualización por lotes.", + "loadFailed": "Error al cargar los datos.", + "tooManySelected": "Selecciona como máximo 500 libros a la vez.", + "noAttributeSelected": "Selecciona al menos un atributo para buscar.", + "noFieldSelected": "Selecciona un campo para actualizar.", + "noValueEntered": "Introduce un valor para establecer.", + "sectionFilters": "Filtros", + "sectionResults": "Resultados", + "showingCount": "Mostrando {shown} de {total} libros", + "allSet": "¡Tu biblioteca está en excelente estado! Todos los libros tienen metadatos completos.", + "allSetFiltered": "¡Tu biblioteca está en excelente estado! Todos los libros tienen metadatos completos para los atributos seleccionados.", + "tableHeaderMissing": "Faltante", + "remaining": "restantes", + "andXMore": "...y {count} más" + }, + "about": { + "title": "Acerca de LibrisLog", + "description": "Una aplicación web de seguimiento de libros para gestionar tus listas de lectura, importar libros desde fuentes en línea y realizar un seguimiento de tu progreso lector, todo a través de un panel moderno.", + "author": "Autor", + "version": "Versión", + "technologies": "Tecnologías utilizadas", + "thankYou": "Agradecimientos", + "thankYouText": "LibrisLog no existiría sin las increíbles bibliotecas y frameworks de código abierto sobre los que se construye. Nuestro agradecimiento a todos los desarrolladores que contribuyen a estos proyectos.", + "frontend": "Frontend", + "backend": "Backend", + "devTools": "Herramientas de desarrollo", + "documentation": "Documentación" + }, + "missingCovers": { + "title": "Gestionar portadas faltantes", + "header": "{count} {count, plural, one {libro} other {libros}} sin portada", + "bookInfo": "{title} de {author}", + "isbnLabel": "ISBN: {isbn}", + "noIsbn": "Este libro no tiene ISBN. La búsqueda automática de portadas no está disponible.", + "noCandidates": "No se pudo determinar ninguna portada automáticamente.", + "searchGoogle": "Buscar portada en Google", + "searchGoogleAria": "Abrir búsqueda de imágenes de Google para este libro en una nueva pestaña", + "manualUrlLabel": "O pega una URL de imagen de portada", + "manualUrlPlaceholder": "https://example.com/cover.jpg", + "manualUrlSave": "Guardar portada", + "manualUrlInvalid": "Introduce una URL HTTP(S) válida", + "manualUrlNotHttps": "Advertencia: la URL no es HTTPS. Prefiere HTTPS por seguridad.", + "skip": "Saltar", + "skipAria": "Saltar este libro e ir al siguiente", + "coverSaved": "Portada guardada", + "coverSaveFailed": "Error al guardar la portada", + "allDone": "¡Todos los libros tienen portada! Buen trabajo.", + "allDoneSub": "Todos los libros de tu biblioteca tienen ahora una imagen de portada.", + "loadingBook": "Cargando siguiente libro...", + "loadingCandidates": "Buscando fuentes de portadas...", + "keyboardHint": "Consejo: pulsa 1\u20139 para seleccionar una portada, \u2192 para saltar", + "candidatesError": "Búsqueda de portadas fallida. Aún puedes usar la importación manual.", + "retry": "Reintentar" + } +} diff --git a/frontend/src/lib/i18n/locales/fr.json b/frontend/src/lib/i18n/locales/fr.json new file mode 100644 index 00000000..cfbc08c5 --- /dev/null +++ b/frontend/src/lib/i18n/locales/fr.json @@ -0,0 +1,650 @@ +{ + "app": { + "title": "LibrisLog", + "addBook": "Ajouter un livre", + "add": "Ajouter", + "language": "Langue" + }, + "nav": { + "dashboard": "Tableau de bord", + "library": "Bibliothèque", + "timeline": "Chronologie", + "statistics": "Statistiques", + "data": "Données", + "want_to_read": "À lire", + "currently_reading": "En cours", + "read": "Lu", + "did_not_finish": "Abandonné" + }, + "statistics": { + "title": "Statistiques", + "subtitle": "Aperçu de ton parcours de lecture", + "avgBooksPerMonth": "Moy. livres/mois", + "busiestMonth": "Mois le plus actif", + "avgPageCount": "Moy. pages/livre", + "mostPopularLanguage": "Langue la plus fréquente", + "languageDistribution": "Livres par langue", + "statusDistribution": "Livres par statut", + "pageBuckets": "Statistiques de pages", + "pagesToRead": "Pages à lire", + "pagesRead": "Pages lues", + "pagesWasted": "Pages gaspillées", + "pagesWastedFootnote": "\"Pages gaspillées\" = page maximale atteinte pour les livres marqués \"Abandonné\"", + "pagesReadPerMonth": "Pages lues par mois", + "booksFinishedPerMonth": "Livres terminés par mois", + "booksFinishedPerYear": "Livres terminés par an", + "topAuthors": "Auteurs populaires", + "rankedNumber": "#{rank}", + "coversForAuthor": "Couvertures de {author}", + "booksCount": "{count} {count, plural, one {livre} other {livres}}", + "unknownLanguage": "Inconnue", + "pagesReadCalendar": "Activité de lecture (365 derniers jours)", + "noCalendarData": "Aucune donnée de lecture pour l'année écoulée", + "pagesOver": "pages sur", + "daysLabel": "jours", + "avgPerDay": "Moy. par jour actif :", + "avgPerDayAll": "Moy. par jour (365 jours) :", + "pagesPerDay": "pages/jour", + "loading": "Chargement des statistiques...", + "noData": "Aucune donnée disponible. Commence à lire et à enregistrer des livres pour voir les statistiques !", + "resetZoom": "Réinitialiser le zoom", + "sectionDistributions": "Répartitions", + "sectionCharts": "Tendances de lecture", + "sectionActivity": "Activité", + "ratingStats": "Statistiques d'évaluation", + "booksWithRating": "Livres évalués", + "booksWithoutRating": "Livres non évalués", + "averageRating": "Note moyenne", + "noRating": "Aucune note", + "topRated": "Les mieux notés", + "worstRated": "Les moins bien notés", + "showMore": "Afficher plus" + }, + "dashboard": { + "title": "Tableau de bord de lecture", + "subtitle": "Un aperçu rapide de ton parcours de lecture", + "quoteTitle": "Citation du jour", + "quoteUnavailable": "Aucune citation disponible pour le moment.", + "totalBooks": "Total dans la bibliothèque", + "booksRead": "Livres lus", + "booksToRead": "Livres à lire", + "currentlyReading": "En cours de lecture", + "nextToRead": "À lire ensuite", + "viewAll": "Voir tout", + "searchAllBooks": "Rechercher dans tous les livres", + "noSearchResults": "Aucun livre trouvé", + "noCurrentlyReading": "Tu ne lis aucun livre actuellement.", + "noNextToRead": "Aucun livre dans ta liste d'envies.", + "popularTags": "Étiquettes populaires" + }, + "status": { + "want_to_read": "À lire", + "currently_reading": "En cours", + "read": "Lu", + "did_not_finish": "Abandonné" + }, + "common": { + "search": "Rechercher", + "searchBooks": "Rechercher des livres...", + "result": "résultat", + "results": "résultats", + "save": "Enregistrer", + "saved": "Enregistré", + "saveFailed": "Échec de l'enregistrement", + "edit": "Modifier", + "cancel": "Annuler", + "confirm": "Confirmer ?", + "delete": "Supprimer", + "deleting": "Suppression...", + "back": "Retour", + "loadMore": "Charger plus", + "syncing": "Synchronisation...", + "noBooksYet": "Aucun livre ici pour le moment.", + "addFirstBook": "Ajoute ton premier livre", + "dateAdded": "Date d'ajout", + "rating": "Note", + "ratingSaved": "Note enregistrée", + "desc": "Déc.", + "asc": "Asc.", + "close": "Fermer", + "clearForm": "Effacer le formulaire", + "remove": "Retirer", + "copy": "Copier", + "copied": "Copié", + "showPassword": "Afficher le mot de passe", + "required": "Requis", + "saving": "Enregistrement...", + "loadingEllipsis": "...", + "starLabel": "{star} {star, plural, one {étoile} other {étoiles}}", + "clickToRate": "Clique sur une étoile pour noter", + "actionFailed": "{action} a échoué", + "readMore": "Lire la suite", + "readLess": "Afficher moins", + "serverStarting": "Le serveur démarre...", + "serverStartingDesc": "Veuillez patienter pendant le démarrage du serveur." + }, + "book": { + "title": "Titre", + "subtitle": "Sous-titre", + "author": "Auteur", + "status": "Statut", + "isbn": "ISBN", + "publisher": "Éditeur", + "year": "Année", + "pages": "Pages", + "language": "Langue", + "tags": "Étiquettes", + "tagsPlaceholder": "Saisis une étiquette et appuie sur Entrée ou virgule", + "tagsHint": "Appuie sur Entrée ou virgule pour ajouter des étiquettes. Retour arrière supprime la dernière.", + "notes": "Notes", + "blurb": "Description", + "about": "À propos", + "dateStarted": "Date de début", + "dateFinished": "Date de fin", + "cover": "Couverture", + "coverForAuthor": "Couverture {index} de {author}", + "googleCovers": "Couvertures Google", + "autoSearchCovers": "Rechercher des couvertures", + "autoSearchInfo": "Clique sur une couverture pour l'importer comme couverture de ce livre.", + "autoSearchNoCandidates": "Aucune couverture trouvée pour cet ISBN.", + "autoSearchError": "Échec de la recherche automatique de couvertures.", + "autoSearchLoading": "Recherche de sources de couvertures...", + "autoSearchMetaUnknown": "Taille/résolution inconnue", + "autoSearchMeta": "{size} - {resolution}", + "autoSearchSourceLabel": "Source : {source}", + "coverOf": "Couverture de {title}", + "openDetailsHint": "Clique pour ouvrir les détails", + "readingProgress": "Progression de lecture", + "currentPage": "Page", + "progressLog": "Journal de progression", + "progressLogEmpty": "Aucune entrée de progression.", + "setPageCountFirst": "Définis d'abord le nombre total de pages.", + "logDate": "Date", + "logPage": "Page", + "deleteEntry": "Supprimer", + "deleteEntryConfirm": "Supprimer cette entrée ?", + "editEntry": "Modifier", + "saveEntry": "Enregistrer", + "progressGraph": "Progression dans le temps", + "progressPromptTitle": "Définir la progression ?", + "progressPromptMessage": "Définir la progression de \"{title}\" à 100 % ?", + "progressPromptSet": "Définir à 100 %", + "progressPromptSkip": "Passer" + }, + "addModal": { + "manual": "Manuel", + "searchImport": "Rechercher et importer", + "adding": "Ajout...", + "failedAdd": "Échec de l'ajout du livre", + "importFromFile": "Importer depuis un fichier" + }, + "import": { + "searchByTitleOrAuthor": "Rechercher par titre ou auteur...", + "enterIsbn": "Saisis l'ISBN...", + "noResultsYet": "Aucun résultat", + "noBooksFound": "Aucun livre trouvé", + "alreadyImported": "Déjà importé", + "imported": "Importé", + "googleToo": "Rechercher aussi sur Google Books", + "googleSearching": "Recherche sur Google Books...", + "googleAdded": "Résultats Google Books ajoutés : {count}", + "scan": "Scanner", + "scanIsbn": "Scanner le code-barres ISBN", + "importFailed": "Échec de l'importation", + "searchFailed": "Échec de la recherche", + "scannedIsbn": "ISBN scanné : {isbn}", + "or": "ou", + "sourceHardcoverSearching": "Recherche sur Hardcover...", + "sourceHardcoverSkipped": "Hardcover ignoré (aucun jeton API configuré)", + "sourceSkipped": "Google Books ignoré (aucune clé API configurée)", + "sourceOpenLibrarySearching": "Recherche sur Open Library...", + "sourceGoogleSearching": "Recherche sur Google Books...", + "sourceBackendError": "Erreur backend {source} (vérifie les journaux du serveur)", + "sourceError": "Échec de la recherche : {message}", + "resultCount": "{source} - {count} résultat{suffix}" + }, + "scanner": { + "title": "Scanner un code-barres ISBN", + "help": "Dirige la caméra vers le code-barres d'un livre. La recherche démarre automatiquement après avoir trouvé un ISBN valide.", + "startError": "Impossible de démarrer le scanner. Vérifie les autorisations de la caméra.", + "noCamera": "Aucun appareil photo trouvé.", + "close": "Fermer le scanner" + }, + "coverPicker": { + "dropzone": "Glisse et dépose une image ici, ou", + "browse": "parcourir", + "pasteUrl": "Ou colle une URL d'image...", + "useUrl": "Utiliser l'URL", + "urlInvalid": "Impossible de charger la couverture depuis l'URL. Vérifie le lien.", + "uploadFailed": "Échec du téléversement", + "previewAlt": "Aperçu de la couverture" + }, + "toasts": { + "dismiss": "Ignorer", + "newVersion": "Une nouvelle version ({version}) est disponible.", + "reload": "Recharger" + }, + "settings": { + "title": "Paramètres", + "languageTitle": "Langue", + "timezone": "Fuseau horaire", + "timezoneHelp": "Afficher les dates et heures dans ton fuseau horaire local.", + "timezoneDetected": "Détecté : {tz}", + "timezoneSelected": "Sélectionné : {tz}", + "timezoneInvalid": "Sélectionne un fuseau horaire valide dans la liste.", + "themeTitle": "Thème", + "themeLight": "Clair", + "themeDark": "Sombre", + "themeCustom": "Personnaliser", + "themeSelect": "Choisir un thème personnalisé", + "timezonePlaceholder": "Rechercher un fuseau horaire...", + "apiDocsTitle": "Documentation de l'API", + "apiDocsHelp": "Explore et teste les points d'accès du backend directement depuis l'application.", + "apiDocsViewLabel": "Voir", + "apiDocsLoading": "Chargement de la documentation", + "apiDocsFrameTitle": "Documentation de l'API", + "apiDocsOpenNewTab": "Ouvrir la documentation dans un nouvel onglet" + }, + "sort": { + "smart": "Tri intelligent" + }, + "dateConflict": { + "started": { + "title": "Date de début déjà définie", + "message": "La date de début est déjà définie. Veux-tu conserver {oldDate} ou définir {newDate} comme nouvelle date de début ?", + "keepOld": "Conserver {oldDate}", + "useNew": "Utiliser {newDate}" + }, + "finished": { + "title": "Date de fin déjà définie", + "message": "La date de fin est déjà définie. Veux-tu conserver {oldDate} ou définir {newDate} comme nouvelle date de fin ?", + "keepOld": "Conserver {oldDate}", + "useNew": "Utiliser {newDate}" + }, + "startedAfterFinished": { + "title": "Livre déjà terminé", + "message": "Ce livre a été terminé le {finishedDate}. Que devons-nous faire ?", + "keepFinished": "Garder la date de fin", + "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." + } + }, + "search": { + "resultsCount": "{count, plural, one {résultat} other {résultats}} trouvés", + "noResults": "Aucun résultat trouvé", + "noResultsFor": "Aucun résultat trouvé pour \"{query}\"", + "tryDifferentQuery": "Essaie un autre terme de recherche" + }, + "languages": { + "en": "Anglais", + "de": "Allemand", + "zh": "Chinois", + "es": "Espagnol", + "fr": "Français" + }, + "auth": { + "login": "Connexion", + "firstname": "Prénom", + "lastname": "Nom", + "email": "E-mail", + "password": "Mot de passe", + "loginFailed": "Échec de la connexion", + "setupTitle": "Créer un compte administrateur", + "setupFailed": "Échec de la configuration", + "createAdmin": "Créer un administrateur", + "invalidEmailError": "Veuillez saisir une adresse e-mail valide", + "passwordComplexityError": "Le mot de passe ne répond pas aux exigences de complexité" + }, + "user": { + "menu": "Menu utilisateur", + "profile": "Profil", + "about": "À propos", + "theme": "Thème", + "logout": "Déconnexion", + "apiKeys": "Clés API", + "keyDescription": "Description (facultative)", + "addKey": "Ajouter une clé", + "newKeyShownOnce": "Copie cette clé maintenant. Elle n'est affichée qu'une seule fois", + "noDescription": "Aucune description", + "newPassword": "Nouveau mot de passe" + }, + "admin": { + "title": "Administration", + "tabs": { + "users": "Utilisateurs", + "backup": "Sauvegarde et restauration" + }, + "newUser": "Créer un utilisateur", + "existingUsers": "Utilisateurs existants", + "role": "Rôle", + "create": "Créer", + "editing": "Modification de l'utilisateur", + "edit": "Modifier", + "saveChanges": "Enregistrer les modifications", + "cancelEdit": "Annuler la modification", + "deleteConfirmTitle": "Veux-tu vraiment supprimer cet utilisateur ?", + "deleteConfirmBody": "Cette action est irréversible.", + "cannotChangeOwnRole": "Tu ne peux pas modifier ton propre rôle d'administrateur.", + "requiredFieldError": "Veuillez remplir tous les champs obligatoires.", + "selfDeleteHint": "Pour supprimer ton propre compte, utilise Profil > Zone de danger.", + "backup": { + "title": "Sauvegarde", + "description": "Télécharge une sauvegarde complète de ta bibliothèque, y compris les livres, couvertures et données.", + "download": "Télécharger la sauvegarde", + "success": "Sauvegarde téléchargée avec succès", + "failed": "Échec du téléchargement de la sauvegarde", + "inProgress": "Création de la sauvegarde..." + }, + "restore": { + "title": "Restauration", + "description": "Restaure ta bibliothèque à partir d'un fichier de sauvegarde précédent.", + "warning": "Attention : la restauration remplacera TOUTES les données actuelles. Assure-toi d'avoir une sauvegarde récente avant de continuer.", + "upload": "Téléverser et restaurer", + "success": "Restauration réussie. {books} livres restaurés.", + "failed": "Échec de la restauration", + "inProgress": "Restauration de la sauvegarde...", + "validationFailed": "Impossible de valider le fichier de sauvegarde", + "invalidBackup": "Structure de sauvegarde invalide", + "confirmTitle": "Confirmer la restauration", + "confirmBody": "Es-tu sûr de vouloir restaurer à partir de cette sauvegarde ? Cela remplacera toutes les données actuelles et ne peut pas être annulé.", + "confirmWarning": "Cette action est irréversible. Toutes les données actuelles seront perdues.", + "confirm": "Restaurer maintenant", + "backupDate": "Date de sauvegarde", + "backupVersion": "Version de l'application", + "coversCount": "Couvertures" + } + }, + "password": { + "requirementsTitle": "Exigences du mot de passe", + "minLength": "Au moins 8 caractères", + "uppercase": "Au moins une lettre majuscule", + "lowercase": "Au moins une lettre minuscule", + "number": "Au moins un chiffre", + "special": "Au moins un caractère spécial", + "strongEnough": "Assez fort", + "notReady": "Pas encore prêt" + }, + "error": { + "isbnAlreadyExists": "Cet ISBN est déjà utilisé par un autre livre.", + "dateInFuture": "La date ne peut pas être dans le futur.", + "dateStartedAfterFinished": "La date de début ne peut pas être postérieure à la date de fin.", + "dateFinishedRequiredForRead": "Un livre terminé doit avoir une date de fin. Modifie le statut si tu veux supprimer la date de fin.", + "invalidLanguageCode": "La langue doit être un code ISO à 2 lettres (par exemple : EN, DE, FR).", + "invalidConfirmationPhrase": "La phrase de confirmation ne correspond pas.", + "cannotDeleteLastAdmin": "Impossible de supprimer le compte : tu es le dernier administrateur", + "cannotDeleteOwnAccountHere": "Tu ne peux pas supprimer ton propre compte ici. Utilise Profil > Zone de danger.", + "importMalformedEvent": "Événement serveur malformé reçu lors de l'importation.", + "importUnsupportedContentType": "Type de contenu non pris en charge. Utilise des fichiers CSV ou JSON.", + "emailAlreadyRegistered": "Cette adresse e-mail est déjà enregistrée.", + "userNotFound": "Utilisateur introuvable.", + "cannotChangeOwnRole": "Tu ne peux pas modifier ton propre rôle d'administrateur.", + "authorRequired": "L'auteur est obligatoire.", + "pageCountRequired": "Le nombre de pages est obligatoire.", + "importTempFileCreateFailed": "Impossible de créer le fichier temporaire d'importation. Veuillez réessayer.", + "fileTooLarge": "Le fichier est trop volumineux. Essaie un fichier plus petit ou vérifie les limites du serveur.", + "exportNoDatasets": "Sélectionne au moins un ensemble de données à exporter.", + "batchUpdateFailed": "La mise à jour par lot a échoué en raison d'une erreur inattendue. Aucune modification n'a été enregistrée.", + "tooManyBooksSelected": "Trop de livres sélectionnés. Sélectionne au maximum {max} à la fois.", + "importMappingNameConflict": "Un mappage avec ce nom existe déjà.", + "importMappingNotFound": "Mappage d'importation introuvable.", + "importFileNotFound": "Fichier d'importation introuvable. Veuillez téléverser le fichier à nouveau." + }, + "oidc": { + "orContinueWith": "ou continuer avec", + "loginWithProvider": "Continuer avec {provider}", + "profileTitle": "Authentification unique", + "notLinked": "Ton compte n'est pas encore lié.", + "linkButton": "Lier le compte {provider}", + "unlinkButton": "Délier le compte", + "linkedAs": "Lié avec {provider}", + "linkSuccess": "Compte lié avec succès", + "linkStartFailed": "Impossible de démarrer la liaison", + "unlinkSuccess": "Compte délié", + "unlinkFailed": "Impossible de délier le compte", + "signingIn": "Connexion en cours...", + "linkingAccount": "Liaison du compte..." + }, + "profile": { + "sectionNav": "Sur cette page", + "profileSaveSuccess": "Profil enregistré", + "profileSaveFailed": "Échec de l'enregistrement du profil", + "passwordChangeSuccess": "Mot de passe modifié", + "passwordChangeFailed": "Échec de la modification du mot de passe", + "dataManagement": { + "title": "Gérer mes données", + "description": "Exporte ta bibliothèque ou importe des livres depuis un fichier CSV/JSON.", + "link": "Importer / Exporter", + "missingCoversDescription": "Attribue rapidement les couvertures manquantes avec des suggestions automatiques.", + "missingCoversLink": "Gérer les couvertures manquantes" + }, + "dangerZone": { + "title": "Zone de danger", + "subtitle": "Actions irréversibles qui suppriment définitivement tes données ou ton compte.", + "resetData": { + "title": "Réinitialiser toutes les données personnelles", + "description": "Supprime tous tes livres, étiquettes et progression de lecture tout en conservant ton compte et tes paramètres.", + "warning": "Cette action est irréversible.", + "placeholder": "Saisis la phrase de confirmation", + "hint": "Saisis exactement : DELETE ALL MY DATA", + "confirmationPhrase": "DELETE ALL MY DATA", + "button": "Réinitialiser toutes les données", + "success": "{books} livres, {tags} étiquettes et {entries} entrées de progression supprimés.", + "failed": "Échec de la réinitialisation" + }, + "deleteAccount": { + "title": "Supprimer le compte", + "description": "Supprime définitivement ton compte et toutes les données associées.", + "warning": "Cette action est permanente et irréversible.", + "placeholder": "Saisis la phrase de confirmation", + "hint": "Saisis exactement : DELETE MY ACCOUNT", + "confirmationPhrase": "DELETE MY ACCOUNT", + "button": "Supprimer mon compte", + "success": "Compte supprimé. Redirection vers la connexion...", + "failed": "Échec de la suppression du compte", + "lastAdminError": "Impossible de supprimer le compte : tu es le dernier administrateur" + } + } + }, + "timeline": { + "title": "Chronologie de lecture", + "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" + }, + "data": { + "title": "Gestion des données", + "subtitle": "Importe et exporte les données de ta bibliothèque personnelle", + "tabs": { + "export": "Exporter", + "import": "Importer" + }, + "export": { + "title": "Exporter", + "description": "Choisis les ensembles de données et le format, puis télécharge une archive ZIP.", + "datasets": { + "books": "Livres", + "progress": "Progression de lecture", + "tags": "Étiquettes", + "covers": "Fichiers de couverture" + }, + "button": "Exporter les données", + "exporting": "Exportation...", + "success": "Export prêt. Téléchargement commencé.", + "errors": { + "noDatasets": "Sélectionne au moins un ensemble de données.", + "failed": "Échec de l'exportation." + } + }, + "import": { + "title": "Importer", + "description": "Téléverse un fichier CSV ou JSON, mappe les champs, valide, puis importe.", + "parse": "Analyser le fichier", + "parsing": "Analyse...", + "fileSummary": "Lignes : {rows}, champs : {fields}", + "mappingTitle": "Correspondance des champs", + "mappingActionsTitle": "Gérer les mappages", + "mappingName": "Nom du mappage", + "loadSavedMapping": "Mappages enregistrés", + "noSavedMappings": "Aucun mappage enregistré. Enregistre le mappage actuel pour le réutiliser plus tard.", + "missingFieldsTitle": "Certains champs source du mappage enregistré ne sont pas présents dans ce fichier :", + "missingFieldEntry": "{target} ← {source}", + "selectMapping": "Sélectionner un mappage", + "loadMapping": "Charger le mappage", + "readonlyMapping": "lecture seule", + "deleteMapping": "Supprimer le mappage", + "deleteMappingTitle": "Supprimer le mappage enregistré", + "showPreview": "Afficher l'aperçu du mappage", + "createProgressForRead": "Créer une entrée de progression à 100 % pour les livres importés comme 'Lu'", + "hidePreview": "Masquer l'aperçu du mappage", + "previewNoMappedFields": "Aucun champ mappé. Assigne des champs source aux champs cible pour prévisualiser les valeurs.", + "transformLabel": "Transformation (Python)", + "transformPlaceholder": "ex. value.upper()", + "previewTitle": "Aperçu", + "previewButton": "Générer", + "previewLoading": "Génération...", + "previewStale": "L'aperçu est obsolète", + "previewRow": "Ligne {row}", + "errorRow": "Ligne {row}", + "previewSource": "Source", + "previewTransformed": "Transformé", + "none": "(aucun)", + "requiredField": "= champ obligatoire", + "changeFile": "Changer de fichier", + "coverUrlHint": "Attend une URL HTTP(S) vers une image. Les chemins de fichiers locaux et les données base64 ne sont pas pris en charge.", + "transformHelp": "Paramètres disponibles et exemples", + "transformHelpValue": "La valeur brute du champ source mappé", + "transformHelpRow": "Tous les champs source sous forme de dict, ex. row['title']", + "transformHelpContext": "Dict de contexte avec numéro de ligne et total (context['row_num'], context['total_rows'])", + "transformHelpReturn": "Les expressions simples sont auto-renvoyées ; utilise return explicite pour le code multiligne", + "transformHelpImports": "Importations Python disponibles : datetime, re, json, math", + "transformError": "La règle de transformation pour {field} est invalide : {error}", + "saveMapping": "Enregistrer le mappage", + "refreshMappings": "Actualiser les mappages", + "mappingSaved": "Mappage enregistré", + "mappingDeleted": "Mappage supprimé", + "mappingMissingFields": "Le mappage chargé a {count} champs source manquants.", + "validationTitle": "Simulation", + "simulate": "Simuler", + "validating": "Validation...", + "validationOk": "Validation réussie.", + "validationNotOk": "La validation a trouvé des problèmes.", + "rollbackAll": "Tout annuler en cas d'erreur", + "continueOnError": "Continuer en cas d'erreur", + "importNow": "Importer maintenant", + "importing": "Importation...", + "cancelled": "Importation annulée.", + "confirmImportTitle": "Lancer l'importation ?", + "confirmDestructive": "Cela écrit des données dans ta bibliothèque et ne peut pas être annulé automatiquement.", + "deleteMappingConfirm": "Supprimer ce mappage enregistré ?", + "dropzone": "Glisse et dépose un fichier CSV/JSON, ou", + "browse": "parcourir", + "fileInputLabel": "Choisir un fichier CSV ou JSON", + "showLess": "Afficher moins", + "showAllIssues": "Afficher tous les problèmes ({count})", + "showAllFailures": "Afficher toutes les lignes échouées ({count})", + "completed": "Importation terminée. Importés : {imported}, échoués : {failed}", + "errors": { + "parseFailed": "Échec de l'analyse du fichier.", + "saveMappingFailed": "Échec de l'enregistrement du mappage.", + "deleteMappingFailed": "Échec de la suppression du mappage.", + "loadMappingsFailed": "Échec du chargement des mappages.", + "loadMappingFailed": "Échec du chargement du mappage.", + "validateFailed": "Échec de la validation.", + "previewFailed": "Échec du chargement de l'aperçu.", + "executeFailed": "Échec de l'importation." + } + } + }, + "dataHygiene": { + "authorRequired": "L'auteur ne peut pas être vide.", + "pageCountPositive": "Le nombre de pages doit être supérieur à 0.", + "title": "Hygiène des données", + "description": "Trouve et corrige les livres avec des métadonnées manquantes dans ta bibliothèque.", + "attributes": { + "author": "Auteur", + "isbn": "ISBN", + "publisher": "Éditeur", + "published_year": "Année", + "blurb": "Description", + "language": "Langue", + "subtitle": "Sous-titre", + "page_count": "Nombre de pages", + "cover_url": "Couverture" + }, + "matchAny": "Correspond à l'un", + "matchAll": "Correspond à tous", + "noMissingBooks": "Aucun livre avec des attributs manquants correspondant à tes filtres.", + "total": "{count} {count, plural, one {livre} other {livres}} trouvés", + "loadMore": "Charger plus", + "loading": "Vérification de ta bibliothèque...", + "selectAll": "Tout sélectionner", + "deselectAll": "Tout désélectionner", + "nSelected": "{count} {count, plural, one {livre} other {livres}} sélectionnés", + "batchEditTitle": "Édition par lot", + "batchFieldLabel": "Champ à mettre à jour", + "batchFieldPlaceholder": "Sélectionne un champ...", + "batchValueLabel": "Nouvelle valeur", + "batchValuePlaceholder": "Saisis la nouvelle valeur", + "applyBatch": "Appliquer aux sélectionnés", + "confirmTitle": "Mettre à jour {count} {count, plural, one {livre} other {livres}} ?", + "confirmBody": "Cela définira \"{field}\" à \"{value}\" pour les livres suivants :", + "confirmApply": "Appliquer la mise à jour", + "confirmCancel": "Annuler", + "success": "{updated} {updated, plural, one {livre} other {livres}} mis à jour. {skipped} avaient déjà cette valeur.", + "updateFailed": "Échec de la mise à jour par lot.", + "loadFailed": "Échec du chargement des données.", + "tooManySelected": "Sélectionne au maximum 500 livres à la fois.", + "noAttributeSelected": "Sélectionne au moins un attribut à rechercher.", + "noFieldSelected": "Sélectionne un champ à mettre à jour.", + "noValueEntered": "Saisis une valeur à définir.", + "sectionFilters": "Filtres", + "sectionResults": "Résultats", + "showingCount": "Affichage de {shown} sur {total} livres", + "allSet": "Ta bibliothèque est en pleine forme ! Tous les livres ont des métadonnées complètes.", + "allSetFiltered": "Ta bibliothèque est en pleine forme ! Tous les livres ont des métadonnées complètes pour les attributs sélectionnés.", + "tableHeaderMissing": "Manquant", + "remaining": "restants", + "andXMore": "...et {count} autres" + }, + "about": { + "title": "À propos de LibrisLog", + "description": "Une application web de suivi de livres pour gérer tes listes de lecture, importer des livres depuis des sources en ligne et suivre ta progression de lecture — le tout via un tableau de bord moderne.", + "author": "Auteur", + "version": "Version", + "technologies": "Technologies utilisées", + "thankYou": "Remerciements", + "thankYouText": "LibrisLog n'existerait pas sans les incroyables bibliothèques et frameworks open source sur lesquels il repose. Nos remerciements vont à tous les développeurs qui contribuent à ces projets.", + "frontend": "Frontend", + "backend": "Backend", + "devTools": "Outils de développement", + "documentation": "Documentation" + }, + "missingCovers": { + "title": "Gérer les couvertures manquantes", + "header": "{count} {count, plural, one {Livre} other {Livres}} sans couverture", + "bookInfo": "{title} de {author}", + "isbnLabel": "ISBN : {isbn}", + "noIsbn": "Ce livre n'a pas d'ISBN. La recherche automatique de couverture n'est pas disponible.", + "noCandidates": "Aucune couverture n'a pu être déterminée automatiquement.", + "searchGoogle": "Rechercher sur Google", + "searchGoogleAria": "Ouvrir la recherche d'images Google pour ce livre dans un nouvel onglet", + "manualUrlLabel": "Ou colle une URL d'image de couverture", + "manualUrlPlaceholder": "https://example.com/cover.jpg", + "manualUrlSave": "Enregistrer la couverture", + "manualUrlInvalid": "Veuillez saisir une URL HTTP(S) valide", + "manualUrlNotHttps": "Attention : l'URL n'est pas en HTTPS. Préfère HTTPS pour la sécurité.", + "skip": "Passer", + "skipAria": "Passer ce livre et passer au suivant", + "coverSaved": "Couverture enregistrée", + "coverSaveFailed": "Échec de l'enregistrement de la couverture", + "allDone": "Tous les livres ont une couverture ! Bon travail.", + "allDoneSub": "Chaque livre de ta bibliothèque a maintenant une image de couverture.", + "loadingBook": "Chargement du livre suivant...", + "loadingCandidates": "Recherche de sources de couvertures...", + "keyboardHint": "Astuce : appuie sur 1\u20139 pour sélectionner une couverture, \u2192 pour passer", + "candidatesError": "Recherche de couverture échouée. Tu peux toujours utiliser l'importation manuelle.", + "retry": "Réessayer" + } +} diff --git a/frontend/src/lib/i18n/locales/zh.json b/frontend/src/lib/i18n/locales/zh.json new file mode 100644 index 00000000..6b3a8809 --- /dev/null +++ b/frontend/src/lib/i18n/locales/zh.json @@ -0,0 +1,650 @@ +{ + "app": { + "title": "LibrisLog", + "addBook": "添加图书", + "add": "添加", + "language": "语言" + }, + "nav": { + "dashboard": "仪表盘", + "library": "书库", + "timeline": "时间线", + "statistics": "统计", + "data": "数据", + "want_to_read": "想读", + "currently_reading": "在读", + "read": "已读", + "did_not_finish": "弃读" + }, + "statistics": { + "title": "统计", + "subtitle": "你的阅读旅程数据分析", + "avgBooksPerMonth": "平均本数/月", + "busiestMonth": "最活跃月份", + "avgPageCount": "平均页数/本", + "mostPopularLanguage": "阅读最多的语言", + "languageDistribution": "按语言分布", + "statusDistribution": "按状态分布", + "pageBuckets": "页数统计", + "pagesToRead": "待读页数", + "pagesRead": "已读页数", + "pagesWasted": "浪费页数", + "pagesWastedFootnote": "\"浪费\" = 标记为\"弃读\"的图书中达到的最大页数", + "pagesReadPerMonth": "每月阅读页数", + "booksFinishedPerMonth": "每月读完数量", + "booksFinishedPerYear": "每年读完数量", + "topAuthors": "热门作者", + "rankedNumber": "#{rank}", + "coversForAuthor": "{author} 的图书封面", + "booksCount": "{count} 本书", + "unknownLanguage": "未知", + "pagesReadCalendar": "阅读活动(最近365天)", + "noCalendarData": "过去一年没有阅读数据", + "pagesOver": "页,共", + "daysLabel": "天", + "avgPerDay": "活跃日平均:", + "avgPerDayAll": "365天日均:", + "pagesPerDay": "页/天", + "loading": "正在加载统计...", + "noData": "暂无数据。开始阅读并记录图书以查看统计数据!", + "resetZoom": "重置缩放", + "sectionDistributions": "分布", + "sectionCharts": "阅读趋势", + "sectionActivity": "活动", + "ratingStats": "评分统计", + "booksWithRating": "已评分图书", + "booksWithoutRating": "未评分图书", + "averageRating": "平均评分", + "noRating": "暂无评分", + "topRated": "评分最高", + "worstRated": "评分最低", + "showMore": "显示更多" + }, + "dashboard": { + "title": "阅读仪表盘", + "subtitle": "你的阅读旅程概览", + "quoteTitle": "每日一言", + "quoteUnavailable": "暂无可用名言。", + "totalBooks": "书库总数", + "booksRead": "已读书籍", + "booksToRead": "待读书籍", + "currentlyReading": "正在阅读", + "nextToRead": "下一本想读", + "viewAll": "查看全部", + "searchAllBooks": "搜索所有图书", + "noSearchResults": "未找到图书", + "noCurrentlyReading": "你当前没有在读的图书。", + "noNextToRead": "你的想读列表还没有图书。", + "popularTags": "热门标签" + }, + "status": { + "want_to_read": "想读", + "currently_reading": "在读", + "read": "已读", + "did_not_finish": "弃读" + }, + "common": { + "search": "搜索", + "searchBooks": "搜索图书...", + "result": "个结果", + "results": "个结果", + "save": "保存", + "saved": "已保存", + "saveFailed": "保存失败", + "edit": "编辑", + "cancel": "取消", + "confirm": "确认?", + "delete": "删除", + "deleting": "删除中...", + "back": "返回", + "loadMore": "加载更多", + "syncing": "同步中...", + "noBooksYet": "这里还没有图书。", + "addFirstBook": "添加第一本书", + "dateAdded": "添加日期", + "rating": "评分", + "ratingSaved": "评分已保存", + "desc": "降序", + "asc": "升序", + "close": "关闭", + "clearForm": "清空表单", + "remove": "移除", + "copy": "复制", + "copied": "已复制", + "showPassword": "显示密码", + "required": "必填", + "saving": "保存中...", + "loadingEllipsis": "...", + "starLabel": "{star} 星", + "clickToRate": "点击星级进行评分", + "actionFailed": "{action} 失败", + "readMore": "展开", + "readLess": "收起", + "serverStarting": "服务器正在启动...", + "serverStartingDesc": "请等待服务器启动完成。" + }, + "book": { + "title": "标题", + "subtitle": "副标题", + "author": "作者", + "status": "状态", + "isbn": "ISBN", + "publisher": "出版社", + "year": "年份", + "pages": "页数", + "language": "语言", + "tags": "标签", + "tagsPlaceholder": "输入标签并按 Enter 或逗号", + "tagsHint": "按 Enter 或逗号添加标签。Backspace 删除最后一个标签。", + "notes": "笔记", + "blurb": "简介", + "about": "关于本书", + "dateStarted": "开始日期", + "dateFinished": "完成日期", + "cover": "封面", + "coverForAuthor": "{author} 的图书封面 {index}", + "googleCovers": "Google 封面", + "autoSearchCovers": "自动搜索封面", + "autoSearchInfo": "点击封面以将其导入为本书封面。", + "autoSearchNoCandidates": "未找到此 ISBN 的封面候选项。", + "autoSearchError": "自动搜索封面失败。", + "autoSearchLoading": "正在搜索封面来源...", + "autoSearchMetaUnknown": "未知大小/分辨率", + "autoSearchMeta": "{size} - {resolution}", + "autoSearchSourceLabel": "来源:{source}", + "coverOf": "{title} 的封面", + "openDetailsHint": "点击查看详情", + "readingProgress": "阅读进度", + "currentPage": "页", + "progressLog": "进度记录", + "progressLogEmpty": "暂无进度记录。", + "setPageCountFirst": "请先设置总页数。", + "logDate": "日期", + "logPage": "页码", + "deleteEntry": "删除", + "deleteEntryConfirm": "删除此记录?", + "editEntry": "编辑", + "saveEntry": "保存", + "progressGraph": "进度变化", + "progressPromptTitle": "设置阅读进度?", + "progressPromptMessage": "将 \"{title}\" 的阅读进度设置为 100%?", + "progressPromptSet": "设为 100%", + "progressPromptSkip": "跳过" + }, + "addModal": { + "manual": "手动添加", + "searchImport": "搜索并导入", + "adding": "添加中...", + "failedAdd": "添加图书失败", + "importFromFile": "从文件导入" + }, + "import": { + "searchByTitleOrAuthor": "按标题或作者搜索...", + "enterIsbn": "输入 ISBN...", + "noResultsYet": "暂无结果", + "noBooksFound": "未找到图书", + "alreadyImported": "已导入", + "imported": "已导入", + "googleToo": "同时搜索 Google Books", + "googleSearching": "正在搜索 Google Books...", + "googleAdded": "Google Books 结果已添加:{count}", + "scan": "扫描", + "scanIsbn": "扫描 ISBN 条码", + "importFailed": "导入失败", + "searchFailed": "搜索失败", + "scannedIsbn": "已扫描 ISBN:{isbn}", + "or": "或", + "sourceHardcoverSearching": "正在搜索 Hardcover...", + "sourceHardcoverSkipped": "Hardcover 已跳过(未配置 API 令牌)", + "sourceSkipped": "Google Books 已跳过(未配置 API 密钥)", + "sourceOpenLibrarySearching": "正在搜索 Open Library...", + "sourceGoogleSearching": "正在搜索 Google Books...", + "sourceBackendError": "{source} 后端错误(请检查后端日志)", + "sourceError": "搜索失败:{message}", + "resultCount": "{source} - {count} 个结果" + }, + "scanner": { + "title": "扫描 ISBN 条码", + "help": "将摄像头对准图书条码。识别到有效 ISBN 后自动开始搜索。", + "startError": "无法启动条码扫描器。请检查摄像头权限。", + "noCamera": "未找到摄像头设备。", + "close": "关闭扫描器" + }, + "coverPicker": { + "dropzone": "拖放图片到此处,或", + "browse": "浏览", + "pasteUrl": "或粘贴图片 URL...", + "useUrl": "使用 URL", + "urlInvalid": "无法从 URL 加载封面。请检查链接。", + "uploadFailed": "上传失败", + "previewAlt": "封面预览" + }, + "toasts": { + "dismiss": "关闭", + "newVersion": "新版本 ({version}) 已可用。", + "reload": "重新加载" + }, + "settings": { + "title": "设置", + "languageTitle": "语言", + "timezone": "时区", + "timezoneHelp": "以本地时区显示日期和时间。", + "timezoneDetected": "检测到:{tz}", + "timezoneSelected": "已选择:{tz}", + "timezoneInvalid": "请从列表中选择有效的时区。", + "themeTitle": "主题", + "themeLight": "浅色", + "themeDark": "深色", + "themeCustom": "自定义", + "themeSelect": "选择自定义主题", + "timezonePlaceholder": "搜索时区...", + "apiDocsTitle": "API 文档", + "apiDocsHelp": "直接在应用中探索和测试后端接口。", + "apiDocsViewLabel": "查看", + "apiDocsLoading": "正在加载 API 文档", + "apiDocsFrameTitle": "API 文档", + "apiDocsOpenNewTab": "在新标签页中打开 API 文档" + }, + "sort": { + "smart": "智能排序" + }, + "dateConflict": { + "started": { + "title": "开始日期已设置", + "message": "开始日期已设置。你想保留 {oldDate} 还是将 {newDate} 设为新的开始日期?", + "keepOld": "保留 {oldDate}", + "useNew": "使用 {newDate}" + }, + "finished": { + "title": "完成日期已设置", + "message": "完成日期已设置。你想保留 {oldDate} 还是将 {newDate} 设为新的完成日期?", + "keepOld": "保留 {oldDate}", + "useNew": "使用 {newDate}" + }, + "startedAfterFinished": { + "title": "本书已完成", + "message": "本书已于 {finishedDate} 完成。你想怎么做?", + "keepFinished": "保留完成日期", + "clearAndStart": "清除完成日期并从今天开始", + "keepDesc": "保留完成日期({finishedDate}),不设置开始日期。", + "clearDesc": "删除完成日期并将今天({newStartDate})设为开始日期。" + } + }, + "search": { + "resultsCount": "找到 {count} 个结果", + "noResults": "未找到结果", + "noResultsFor": "未找到 \"{query}\" 的结果", + "tryDifferentQuery": "尝试其他搜索词" + }, + "languages": { + "en": "英语", + "de": "德语", + "zh": "中文", + "es": "西班牙语", + "fr": "法语" + }, + "auth": { + "login": "登录", + "firstname": "名", + "lastname": "姓", + "email": "邮箱", + "password": "密码", + "loginFailed": "登录失败", + "setupTitle": "创建管理员账户", + "setupFailed": "设置失败", + "createAdmin": "创建管理员", + "invalidEmailError": "请输入有效的邮箱地址", + "passwordComplexityError": "密码未满足复杂度要求" + }, + "user": { + "menu": "用户菜单", + "profile": "个人资料", + "about": "关于", + "theme": "主题", + "logout": "退出登录", + "apiKeys": "API 密钥", + "keyDescription": "描述(可选)", + "addKey": "添加密钥", + "newKeyShownOnce": "立即复制此密钥。仅显示一次", + "noDescription": "无描述", + "newPassword": "新密码" + }, + "admin": { + "title": "管理", + "tabs": { + "users": "用户", + "backup": "备份与恢复" + }, + "newUser": "创建用户", + "existingUsers": "现有用户", + "role": "角色", + "create": "创建", + "editing": "正在编辑用户", + "edit": "编辑", + "saveChanges": "保存更改", + "cancelEdit": "取消编辑", + "deleteConfirmTitle": "确定要删除此用户吗?", + "deleteConfirmBody": "此操作无法撤消。", + "cannotChangeOwnRole": "你不能更改自己的管理员角色。", + "requiredFieldError": "请填写所有必填字段。", + "selfDeleteHint": "要删除自己的账户,请使用“个人资料” > “危险区域”。", + "backup": { + "title": "备份", + "description": "下载包含所有图书、封面和数据的完整备份。", + "download": "下载备份", + "success": "备份下载成功", + "failed": "备份下载失败", + "inProgress": "正在创建备份..." + }, + "restore": { + "title": "恢复", + "description": "从之前的备份文件恢复你的书库。", + "warning": "警告:恢复将替换所有当前数据。确保在继续之前有最近的备份。", + "upload": "上传并恢复", + "success": "恢复成功完成。已恢复 {books} 本书。", + "failed": "恢复失败", + "inProgress": "正在恢复备份...", + "validationFailed": "无法验证备份文件", + "invalidBackup": "无效的备份文件结构", + "confirmTitle": "确认恢复", + "confirmBody": "你确定要从此备份恢复吗?这将替换所有当前数据且无法撤消。", + "confirmWarning": "此操作不可撤消。所有当前数据将丢失。", + "confirm": "立即恢复", + "backupDate": "备份日期", + "backupVersion": "应用版本", + "coversCount": "封面" + } + }, + "password": { + "requirementsTitle": "密码要求", + "minLength": "至少 8 个字符", + "uppercase": "至少一个大写字母", + "lowercase": "至少一个小写字母", + "number": "至少一个数字", + "special": "至少一个特殊字符", + "strongEnough": "强度足够", + "notReady": "不满足要求" + }, + "error": { + "isbnAlreadyExists": "此 ISBN 已被另一本书使用。", + "dateInFuture": "日期不能为未来。", + "dateStartedAfterFinished": "开始日期不能晚于完成日期。", + "dateFinishedRequiredForRead": "已读完的书必须有结束日期。如果要移除结束日期,请更改状态。", + "invalidLanguageCode": "语言必须是 2 位 ISO 代码(例如:EN、DE、FR)。", + "invalidConfirmationPhrase": "确认短语不匹配。", + "cannotDeleteLastAdmin": "无法删除账户:你是最后一个管理员", + "cannotDeleteOwnAccountHere": "你不能在这里删除自己的账户。请使用“个人资料” > “危险区域”。", + "importMalformedEvent": "导入期间收到格式错误的服务器事件。", + "importUnsupportedContentType": "不支持的上传内容类型。请使用 CSV 或 JSON 文件。", + "emailAlreadyRegistered": "此邮箱地址已注册。", + "userNotFound": "未找到用户。", + "cannotChangeOwnRole": "你不能更改自己的管理员角色。", + "authorRequired": "作者为必填。", + "pageCountRequired": "页数为必填。", + "importTempFileCreateFailed": "无法创建临时导入文件。请重试。", + "fileTooLarge": "文件过大。请尝试更小的文件或检查服务器限制。", + "exportNoDatasets": "请至少选择一个数据集进行导出。", + "batchUpdateFailed": "批量更新因意外错误失败。未保存任何更改。", + "tooManyBooksSelected": "选择的图书过多。一次最多选择 {max} 本。", + "importMappingNameConflict": "同名映射已存在。", + "importMappingNotFound": "未找到导入映射。", + "importFileNotFound": "未找到导入文件。请重新上传文件。" + }, + "oidc": { + "orContinueWith": "或继续使用", + "loginWithProvider": "使用 {provider} 继续", + "profileTitle": "单点登录", + "notLinked": "你的账户尚未关联。", + "linkButton": "关联 {provider} 账户", + "unlinkButton": "取消关联", + "linkedAs": "已关联 {provider}", + "linkSuccess": "账户关联成功", + "linkStartFailed": "无法开始账户关联", + "unlinkSuccess": "已取消关联", + "unlinkFailed": "无法取消账户关联", + "signingIn": "正在登录...", + "linkingAccount": "正在关联账户..." + }, + "profile": { + "sectionNav": "本页内容", + "profileSaveSuccess": "个人资料已保存", + "profileSaveFailed": "保存个人资料失败", + "passwordChangeSuccess": "密码已更改", + "passwordChangeFailed": "更改密码失败", + "dataManagement": { + "title": "管理我的数据", + "description": "导出你的书库或从 CSV/JSON 文件导入图书。", + "link": "导入 / 导出", + "missingCoversDescription": "使用自动建议快速分配缺失封面。", + "missingCoversLink": "管理缺失封面" + }, + "dangerZone": { + "title": "危险区域", + "subtitle": "不可撤消的操作,将永久删除你的数据或账户。", + "resetData": { + "title": "重置所有个人数据", + "description": "删除所有图书、标签和阅读进度,但保留账户和设置。", + "warning": "此操作无法撤消。", + "placeholder": "输入确认短语", + "hint": "准确输入:DELETE ALL MY DATA", + "confirmationPhrase": "DELETE ALL MY DATA", + "button": "重置所有数据", + "success": "已删除 {books} 本书、{tags} 个标签和 {entries} 条进度记录。", + "failed": "重置数据失败" + }, + "deleteAccount": { + "title": "删除账户", + "description": "永久删除你的账户及所有关联数据。", + "warning": "此操作是永久性的,无法撤消。", + "placeholder": "输入确认短语", + "hint": "准确输入:DELETE MY ACCOUNT", + "confirmationPhrase": "DELETE MY ACCOUNT", + "button": "删除我的账户", + "success": "账户已删除。正在跳转到登录页面...", + "failed": "删除账户失败", + "lastAdminError": "无法删除账户:你是最后一个管理员" + } + } + }, + "timeline": { + "title": "阅读时间线", + "subtitle": "你已完成阅读的图书的时间线视图", + "viewInLibrary": "在书库中查看全部", + "noReadBooks": "你的书库中还没有已读完的图书。", + "goToLibrary": "前往书库" + }, + "data": { + "title": "数据管理", + "subtitle": "导入和导出你的个人书库数据", + "tabs": { + "export": "导出", + "import": "导入" + }, + "export": { + "title": "导出", + "description": "选择数据集和格式,然后下载 ZIP 归档。", + "datasets": { + "books": "图书", + "progress": "阅读进度", + "tags": "标签", + "covers": "封面文件" + }, + "button": "导出数据", + "exporting": "导出中...", + "success": "导出就绪。已开始下载。", + "errors": { + "noDatasets": "请至少选择一个数据集。", + "failed": "导出失败。" + } + }, + "import": { + "title": "导入", + "description": "上传 CSV 或 JSON 文件,映射字段,验证,然后导入。", + "parse": "解析文件", + "parsing": "解析中...", + "fileSummary": "行数:{rows},字段:{fields}", + "mappingTitle": "字段映射", + "mappingActionsTitle": "管理映射", + "mappingName": "映射名称", + "loadSavedMapping": "已保存的映射", + "noSavedMappings": "暂无已保存的映射。保存当前映射以在以后重复使用。", + "missingFieldsTitle": "已保存映射中的部分源字段在此文件中不存在:", + "missingFieldEntry": "{target} ← {source}", + "selectMapping": "选择已保存的映射", + "loadMapping": "加载映射", + "readonlyMapping": "只读", + "deleteMapping": "删除映射", + "deleteMappingTitle": "删除已保存的映射", + "showPreview": "显示映射预览", + "createProgressForRead": "为导入为“已读”的图书创建 100% 进度记录", + "hidePreview": "隐藏映射预览", + "previewNoMappedFields": "尚未映射字段。请将源字段分配给目标字段以预览值。", + "transformLabel": "转换 (Python)", + "transformPlaceholder": "例如:value.upper()", + "previewTitle": "预览", + "previewButton": "生成", + "previewLoading": "生成中...", + "previewStale": "预览已过期", + "previewRow": "第 {row} 行", + "errorRow": "第 {row} 行", + "previewSource": "来源", + "previewTransformed": "转换后", + "none": "(无)", + "requiredField": "= 必填字段", + "changeFile": "更改文件", + "coverUrlHint": "期望指向图片的 HTTP(S) URL。不支持本地文件路径和 base64 数据。", + "transformHelp": "可用参数和示例", + "transformHelpValue": "映射的源字段的原始值", + "transformHelpRow": "所有源字段作为字典,例如 row['title']", + "transformHelpContext": "包含行号和总行数的上下文字典 (context['row_num'], context['total_rows'])", + "transformHelpReturn": "单个表达式自动返回;多行代码请使用显式 return", + "transformHelpImports": "可用的 Python 导入:datetime, re, json, math", + "transformError": "{field} 的转换规则无效:{error}", + "saveMapping": "保存映射", + "refreshMappings": "刷新映射", + "mappingSaved": "映射已保存", + "mappingDeleted": "映射已删除", + "mappingMissingFields": "加载的映射有 {count} 个缺失的源字段。", + "validationTitle": "模拟", + "simulate": "模拟", + "validating": "验证中...", + "validationOk": "验证通过。", + "validationNotOk": "验证发现问题。", + "rollbackAll": "出错时全部回滚", + "continueOnError": "出错时继续", + "importNow": "立即导入", + "importing": "导入中...", + "cancelled": "导入已取消。", + "confirmImportTitle": "开始导入?", + "confirmDestructive": "这将向你的书库写入数据,无法自动撤消。", + "deleteMappingConfirm": "删除此已保存的映射?", + "dropzone": "拖放 CSV/JSON 文件,或", + "browse": "浏览", + "fileInputLabel": "选择 CSV 或 JSON 文件", + "showLess": "收起", + "showAllIssues": "显示所有问题 ({count})", + "showAllFailures": "显示所有失败行 ({count})", + "completed": "导入完成。已导入:{imported},失败:{failed}", + "errors": { + "parseFailed": "解析文件失败。", + "saveMappingFailed": "保存映射失败。", + "deleteMappingFailed": "删除映射失败。", + "loadMappingsFailed": "加载映射失败。", + "loadMappingFailed": "加载映射失败。", + "validateFailed": "验证失败。", + "previewFailed": "加载预览失败。", + "executeFailed": "导入失败。" + } + } + }, + "dataHygiene": { + "authorRequired": "作者不能为空。", + "pageCountPositive": "页数必须大于 0。", + "title": "数据清理", + "description": "查找并修复书库中缺少元数据的图书。", + "attributes": { + "author": "作者", + "isbn": "ISBN", + "publisher": "出版社", + "published_year": "年份", + "blurb": "简介", + "language": "语言", + "subtitle": "副标题", + "page_count": "页数", + "cover_url": "封面" + }, + "matchAny": "匹配任一", + "matchAll": "匹配全部", + "noMissingBooks": "未找到符合筛选条件的缺少属性的图书。", + "total": "找到 {count} 本书", + "loadMore": "加载更多", + "loading": "正在检查你的书库...", + "selectAll": "全选", + "deselectAll": "取消全选", + "nSelected": "已选 {count} 本书", + "batchEditTitle": "批量编辑", + "batchFieldLabel": "要更新的字段", + "batchFieldPlaceholder": "选择一个字段...", + "batchValueLabel": "新值", + "batchValuePlaceholder": "输入新值", + "applyBatch": "应用于所选", + "confirmTitle": "更新 {count} 本书?", + "confirmBody": "这会将下列图书的 \"{field}\" 设置为 \"{value}\":", + "confirmApply": "执行更新", + "confirmCancel": "取消", + "success": "已更新 {updated} 本书。{skipped} 本已为此值。", + "updateFailed": "批量更新失败。", + "loadFailed": "加载数据失败。", + "tooManySelected": "一次最多选择 500 本书。", + "noAttributeSelected": "请至少选择一个属性进行搜索。", + "noFieldSelected": "请选择一个要更新的字段。", + "noValueEntered": "请输入要设置的值。", + "sectionFilters": "筛选条件", + "sectionResults": "结果", + "showingCount": "显示 {shown} 本,共 {total} 本", + "allSet": "你的书库状态良好!所有图书元数据完整。", + "allSetFiltered": "你的书库状态良好!所选属性的所有图书元数据完整。", + "tableHeaderMissing": "缺失", + "remaining": "剩余", + "andXMore": "...还有 {count} 个" + }, + "about": { + "title": "关于 LibrisLog", + "description": "一款图书追踪 Web 应用,用于管理阅读列表、从在线来源导入图书以及追踪阅读进度——一切尽在现代仪表盘中。", + "author": "作者", + "version": "版本", + "technologies": "使用的技术", + "thankYou": "致谢", + "thankYouText": "LibrisLog 的诞生离不开其构建所依赖的优秀开源库和框架。我们感谢所有为这些项目做出贡献的开发者。", + "frontend": "前端", + "backend": "后端", + "devTools": "开发工具", + "documentation": "文档" + }, + "missingCovers": { + "title": "管理缺失封面", + "header": "{count} 本图书缺少封面", + "bookInfo": "{title} - {author}", + "isbnLabel": "ISBN:{isbn}", + "noIsbn": "本书没有 ISBN。无法自动搜索封面。", + "noCandidates": "无法自动确定封面。", + "searchGoogle": "在 Google 上搜索封面", + "searchGoogleAria": "在新标签页中打开此书的 Google 图片搜索", + "manualUrlLabel": "或粘贴封面图片 URL", + "manualUrlPlaceholder": "https://example.com/cover.jpg", + "manualUrlSave": "保存封面", + "manualUrlInvalid": "请输入有效的 HTTP(S) URL", + "manualUrlNotHttps": "警告:URL 不是 HTTPS。出于安全考虑,建议使用 HTTPS。", + "skip": "跳过", + "skipAria": "跳过此书并前往下一本", + "coverSaved": "封面已保存", + "coverSaveFailed": "保存封面失败", + "allDone": "所有图书都有封面了!做得好。", + "allDoneSub": "你书库中的每本书现在都有封面。", + "loadingBook": "正在加载下一本书...", + "loadingCandidates": "正在搜索封面来源...", + "keyboardHint": "提示:按 1\u20139 选择封面,按 \u2192 跳过", + "candidatesError": "封面搜索失败。你仍可使用手动导入。", + "retry": "重试" + } +} From 36abfaa54e38f41ce54c8d6fbeb26e6321bb8281 Mon Sep 17 00:00:00 2001 From: codebude Date: Mon, 8 Jun 2026 13:25:02 +0200 Subject: [PATCH 04/11] Load new languages into app --- frontend/src/lib/i18n/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/i18n/index.ts b/frontend/src/lib/i18n/index.ts index 151d1e33..b72c01e3 100644 --- a/frontend/src/lib/i18n/index.ts +++ b/frontend/src/lib/i18n/index.ts @@ -1,7 +1,7 @@ import { addMessages, init, locale, register, waitLocale, _ } from 'svelte-i18n'; import { api } from '$lib/api'; -export const SUPPORTED_LOCALES = ['en', 'de'] as const; +export const SUPPORTED_LOCALES = ['en', 'de', 'zh', 'es', 'fr'] as const; export type AppLocale = (typeof SUPPORTED_LOCALES)[number]; const DEFAULT_LOCALE: AppLocale = 'en'; @@ -12,6 +12,9 @@ const configuredDefaultLocale: AppLocale = isSupportedLocale(envLocale) ? envLoc register('en', () => import('./locales/en.json')); register('de', () => import('./locales/de.json')); +register('zh', () => import('./locales/zh.json')); +register('es', () => import('./locales/es.json')); +register('fr', () => import('./locales/fr.json')); addMessages('en', {}); From 2694485a1aeec074ba4b8696a75ee5eb426244c0 Mon Sep 17 00:00:00 2001 From: codebude Date: Mon, 8 Jun 2026 13:25:14 +0200 Subject: [PATCH 05/11] Update readme and docs regarding new languages --- README.md | 2 +- docs/about.md | 2 +- docs/guide/configuration.md | 2 +- docs/guide/developer-setup.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3844daa5..183db06f 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Open **http://localhost:8001** and create your account. - **Cover art from multiple sources.** Automatic search across AbeBooks, Open Library, Amazon, and Hardcover — plus manual upload or URL paste. - **Full REST API.** OpenAPI-documented backend you can script against — build your own frontend, connect home automation, or pipe data into your own tools. - **Lightweight.** Two Docker containers, one SQLite database. -- **Bilingual UI.** English and German with a localization framework ready for more languages. +- **Multi-language UI.** English, German, Spanish, French, and Chinese (Simplified) — with a localization framework ready for more languages. --- diff --git a/docs/about.md b/docs/about.md index 91d168f3..0b213aa8 100644 --- a/docs/about.md +++ b/docs/about.md @@ -11,7 +11,7 @@ LibrisLog is a **multi-user book tracking web application** designed for readers - **Cover Management**: Automatic cover image scraping from multiple sources with manual override - **Data Portability**: Export/import library as JSON or CSV. Full backup and restore functionality - **REST API**: Full API with OpenAPI documentation for programmatic access -- **Multilingual**: English and German UI support +- **Multilingual**: English, German, Spanish, French, and Chinese (Simplified) UI support - **Themes**: Light, dark, and custom DaisyUI themes with persistent preferences ## Technology Stack diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 9851a3d5..b5078d83 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -58,7 +58,7 @@ All configuration is done via environment variables in a `.env` file at the proj | Variable | Description | Default | |----------|-------------|---------| -| `PUBLIC_DEFAULT_LOCALE` | Default UI language (`en` or `de`) | `en` | +| `PUBLIC_DEFAULT_LOCALE` | Default UI language (`en`, `de`, `zh`, `es`, or `fr`) | `en` | ## Import Limits diff --git a/docs/guide/developer-setup.md b/docs/guide/developer-setup.md index 9c545887..2f7e2bbd 100644 --- a/docs/guide/developer-setup.md +++ b/docs/guide/developer-setup.md @@ -20,7 +20,7 @@ When building, you can override these arguments: |----------|-------------|---------| | `APP_VERSION` | Application version string | `v0.0.0-dev` | | `GIT_SHA` | Git commit hash for version display | `unknown` | -| `PUBLIC_DEFAULT_LOCALE` | Default UI language (`en` or `de`) | `en` | +| `PUBLIC_DEFAULT_LOCALE` | Default UI language (`en`, `de`, `zh`, `es`, or `fr`) | `en` | Example: From 3f88a2404423242f5b1662bc6b135166cd03a3b2 Mon Sep 17 00:00:00 2001 From: codebude Date: Mon, 8 Jun 2026 19:49:58 +0200 Subject: [PATCH 06/11] Update the star-history.com integration in readme.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 183db06f..9980e677 100644 --- a/README.md +++ b/README.md @@ -127,10 +127,10 @@ MIT ## Star History - - - - - Star History Chart - + + + + + Star History Chart + From 362153532d32cc3d67125d0d15741d7ca13470c3 Mon Sep 17 00:00:00 2001 From: codebude Date: Mon, 8 Jun 2026 22:46:27 +0200 Subject: [PATCH 07/11] Setup integration pages in docs --- docs/.vitepress/config.base.ts | 9 +++ docs/api/integrations/homepage.md | 60 ++++++++++++++++++ docs/api/integrations/index.md | 21 ++++++ .../screenshots/integrations-homepage.png | Bin 0 -> 63542 bytes 4 files changed, 90 insertions(+) create mode 100644 docs/api/integrations/homepage.md create mode 100644 docs/api/integrations/index.md create mode 100644 docs/public/screenshots/integrations-homepage.png diff --git a/docs/.vitepress/config.base.ts b/docs/.vitepress/config.base.ts index 82298fb8..9d22b140 100644 --- a/docs/.vitepress/config.base.ts +++ b/docs/.vitepress/config.base.ts @@ -52,6 +52,7 @@ export default defineConfig({ { text: 'CLI Reference', link: '/guide/cli' }, ], }, + { text: 'Integrations 🔗', link: '/api/integrations/' }, ], }, { @@ -75,6 +76,14 @@ export default defineConfig({ items: [ { text: 'Overview', link: '/api/' }, { text: 'Headless Setup & API Keys', link: '/api/setup' }, + { + text: 'Integrations', + link: '/api/integrations/', + collapsed: true, + items: [ + { text: 'Homepage', link: '/api/integrations/homepage' }, + ], + }, ], }, ], diff --git a/docs/api/integrations/homepage.md b/docs/api/integrations/homepage.md new file mode 100644 index 00000000..a4feefa2 --- /dev/null +++ b/docs/api/integrations/homepage.md @@ -0,0 +1,60 @@ +# Homepage + +LibrisLog can be integrated into [Homepage](https://gethomepage.dev/), a +modern dashboard for your self-hosted services, using its +[custom API widget](https://gethomepage.dev/widgets/services/customapi/). + +This widget displays your reading statistics directly on your Homepage +dashboard. + +## Prerequisites + +- A running LibrisLog instance reachable from your Homepage server +- An [API key](/api/integrations/#api-keys) with access to the + statistics endpoint + +## Configuration + +Add the following entry to your Homepage `services.yaml`: + +```yaml +- librislog: + icon: mdi-book-heart + href: + siteMonitor: + widget: + type: customapi + url: /api/books/stats + method: GET + headers: + X-API-Key: "" + refreshInterval: 300000 + display: block + mappings: + - field: books_read + label: Read + format: number + - field: books_reading + label: Reading + format: number + - field: books_want_to_read + label: Want to read + format: number + - field: total_books + label: Total + format: number +``` + +Replace the placeholders with your own values: + +| Placeholder | Example | Description | +|---|---|---| +| `` | `http://192.168.1.100:8000` | The base URL of your LibrisLog instance (http or https) | +| `` | `lk_nRHsF3jxIBDa9u....` | An API key with access to the statistics endpoint | + +The `refreshInterval` is specified in milliseconds. `300000` ms equals 5 +minutes. + +## Result + +![Homepage Widget](/screenshots/integrations-homepage.png) diff --git a/docs/api/integrations/index.md b/docs/api/integrations/index.md new file mode 100644 index 00000000..2e036349 --- /dev/null +++ b/docs/api/integrations/index.md @@ -0,0 +1,21 @@ +# Integrations + +LibrisLog exposes a full REST API that third-party applications can use to +display your reading data, automate workflows, or build custom dashboards. + +All integrations below are backed by the LibrisLog API. You will need an +API key to use them. You can create one either: + + + +- **Via the web UI** — go to your [Profile](/api/#creating-an-api-key) + page and click "Create API Key". +- **Via the API** — see the + [Headless Setup](/api/setup#3-create-an-api-key) guide for a + CLI-based workflow. + +## Available Integrations + +- [Homepage](/api/integrations/homepage) — Display your LibrisLog statistics + on a [Homepage](https://gethomepage.dev/) dashboard using the custom API + widget. diff --git a/docs/public/screenshots/integrations-homepage.png b/docs/public/screenshots/integrations-homepage.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc132528d7af156eaded3729425c456ff8e8ee6 GIT binary patch literal 63542 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�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 Date: Tue, 9 Jun 2026 21:21:23 +0200 Subject: [PATCH 08/11] Add Home Assistant integration docs --- docs/.vitepress/config.base.ts | 1 + docs/api/integrations/homeassistant.md | 83 ++++++++++++++++++ docs/api/integrations/index.md | 4 + .../integrations-homeassistant-1.png | Bin 0 -> 12471 bytes .../integrations-homeassistant-2.png | Bin 0 -> 85410 bytes 5 files changed, 88 insertions(+) create mode 100644 docs/api/integrations/homeassistant.md create mode 100644 docs/public/screenshots/integrations-homeassistant-1.png create mode 100644 docs/public/screenshots/integrations-homeassistant-2.png diff --git a/docs/.vitepress/config.base.ts b/docs/.vitepress/config.base.ts index 9d22b140..9a0adccd 100644 --- a/docs/.vitepress/config.base.ts +++ b/docs/.vitepress/config.base.ts @@ -81,6 +81,7 @@ export default defineConfig({ link: '/api/integrations/', collapsed: true, items: [ + { text: 'Home Assistant', link: '/api/integrations/homeassistant' }, { text: 'Homepage', link: '/api/integrations/homepage' }, ], }, diff --git a/docs/api/integrations/homeassistant.md b/docs/api/integrations/homeassistant.md new file mode 100644 index 00000000..d0c267f8 --- /dev/null +++ b/docs/api/integrations/homeassistant.md @@ -0,0 +1,83 @@ +# Home Assistant + +LibrisLog can be integrated into [Home Assistant](https://www.home-assistant.io/), +a popular open-source home automation platform, using its +[RESTful integration](https://www.home-assistant.io/integrations/rest/). + +This integration creates sensors that expose your LibrisLog reading statistics +directly in Home Assistant. + +## Prerequisites + +- A running LibrisLog instance reachable from your Home Assistant server +- An [API key](/api/integrations/#api-keys) with access to the + statistics endpoint + +## Configuration + +Add the following to your Home Assistant `configuration.yaml`: + +```yaml +rest: + - resource: /api/books/stats + method: GET + headers: + X-API-Key: "" + Content-Type: application/json + scan_interval: 300 + sensor: + - name: "Total Books" + unique_id: librislog_total_books + value_template: "{{ value_json.total_books }}" + icon: mdi:bookshelf + unit_of_measurement: "Books" + - name: "Books Read" + unique_id: librislog_books_read + value_template: "{{ value_json.books_read }}" + icon: mdi:book-check + unit_of_measurement: "Books" + - name: "Books Currently Reading" + unique_id: librislog_books_currently_reading + value_template: "{{ value_json.books_reading }}" + icon: mdi:book-open-page-variant + unit_of_measurement: "Books" + - name: "Want to Read" + unique_id: librislog_books_want_to_read + value_template: "{{ value_json.books_want_to_read }}" + icon: mdi:bookmark-plus + unit_of_measurement: "Books" + + - resource: /api/statistics + method: GET + headers: + X-API-Key: "" + Content-Type: application/json + scan_interval: 600 + sensor: + - name: "Average Rating" + unique_id: librislog_average_rating + value_template: "{{ value_json.average_rating | round(2) if value_json.average_rating is not none else 'N/A' }}" + icon: mdi:star + unit_of_measurement: "★" + - name: "Average Page Count" + unique_id: librislog_average_page_count + value_template: "{{ value_json.avg_page_count | round(0) if value_json.avg_page_count is not none else 'N/A' }}" + icon: mdi:file-document-multiple + unit_of_measurement: "Pages" +``` + +Replace the placeholders with your own values: + +| Placeholder | Example | Description | +|---|---|---| +| `` | `http://192.168.1.100:8000` | The base URL of your LibrisLog instance (http or https) | +| `` | `lk_nRHsF3jxIBDa9u....` | An API key with access to the statistics endpoint | + +The `scan_interval` is specified in seconds. The first resource polls every +5 minutes, the second every 10 minutes. + +## Result + +![Home Assistant Sensors - Part 1](/screenshots/integrations-homeassistant-1.png) + +![Home Assistant Sensors - Part 2](/screenshots/integrations-homeassistant-2.png) diff --git a/docs/api/integrations/index.md b/docs/api/integrations/index.md index 2e036349..132a3f71 100644 --- a/docs/api/integrations/index.md +++ b/docs/api/integrations/index.md @@ -16,6 +16,10 @@ API key to use them. You can create one either: ## Available Integrations +- [Home Assistant](/api/integrations/homeassistant) — Expose your LibrisLog + reading statistics as sensors in + [Home Assistant](https://www.home-assistant.io/) using the RESTful + integration. - [Homepage](/api/integrations/homepage) — Display your LibrisLog statistics on a [Homepage](https://gethomepage.dev/) dashboard using the custom API widget. diff --git a/docs/public/screenshots/integrations-homeassistant-1.png b/docs/public/screenshots/integrations-homeassistant-1.png new file mode 100644 index 0000000000000000000000000000000000000000..babced8e5c0fb9325ee11800fe8f28a8ddbd0408 GIT binary patch literal 12471 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

O++o>N4LpOXJ*Abs% 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 Date: Tue, 9 Jun 2026 21:41:08 +0200 Subject: [PATCH 09/11] Added Dashy integration docs --- docs/.vitepress/config.base.ts | 1 + docs/api/integrations/dashy.md | 137 ++++++++++++++++++ docs/api/integrations/index.md | 3 + .../public/screenshots/integrations-dashy.png | Bin 0 -> 11892 bytes 4 files changed, 141 insertions(+) create mode 100644 docs/api/integrations/dashy.md create mode 100644 docs/public/screenshots/integrations-dashy.png diff --git a/docs/.vitepress/config.base.ts b/docs/.vitepress/config.base.ts index 9a0adccd..2023b175 100644 --- a/docs/.vitepress/config.base.ts +++ b/docs/.vitepress/config.base.ts @@ -81,6 +81,7 @@ export default defineConfig({ link: '/api/integrations/', collapsed: true, items: [ + { text: 'Dashy', link: '/api/integrations/dashy' }, { text: 'Home Assistant', link: '/api/integrations/homeassistant' }, { text: 'Homepage', link: '/api/integrations/homepage' }, ], diff --git a/docs/api/integrations/dashy.md b/docs/api/integrations/dashy.md new file mode 100644 index 00000000..73ee033e --- /dev/null +++ b/docs/api/integrations/dashy.md @@ -0,0 +1,137 @@ +# Dashy + +LibrisLog can be integrated into [Dashy](https://dashy.to/), a self-hosted +dashboard for your services, using its +[HTML embedded widget](https://dashy.to/docs/widgets#html-embedded-widget). + +This widget displays your reading statistics as styled stat cards directly on +your Dashy dashboard. + +## Prerequisites + +- A running LibrisLog instance reachable from your Dashy server +- An [API key](/api/integrations/#api-keys) with access to the + statistics endpoint +- **CORS must be configured** — add your Dashy URL to the + [`CORS_ORIGINS`](/guide/configuration#core-settings) environment variable + of the LibrisLog backend so that the browser can fetch the API directly + +## Configuration + +Add the following to the Dashy `conf.yml` under the section or item where you +want the widget to appear: + +```yaml +widgets: + - type: embed + updateInterval: 300 + options: + html: | +

+
+ Reading + - +
+
+ Read + - +
+
+ Want to Read + - +
+
+ Total Books + - +
+
+ css: | + .librislog-widget { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.75rem; + padding: 0.5rem; + font-family: inherit; + } + .ll-stat-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: var(--background-elevated, rgba(255,255,255,0.05)); + border: 1px solid var(--outline-color, rgba(255,255,255,0.1)); + border-radius: 6px; + padding: 0.5rem; + text-align: center; + } + .ll-label { + font-size: 0.8rem; + opacity: 0.7; + color: var(--text-color, #fff); + margin-bottom: 0.25rem; + text-transform: uppercase; + letter-spacing: 0.05em; + } + .ll-value { + font-size: 1.4rem; + font-weight: bold; + color: var(--primary, #00bc8c); + } + script: | + (async function() { + const apiUrl = '/api/books/stats'; + const apiKey = ''; + + try { + const response = await fetch(apiUrl, { + method: 'GET', + headers: { + 'X-API-Key': apiKey, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) throw new Error('API request failed'); + + const data = await response.json(); + + document.getElementById('ll-reading').innerText = data.books_currently_reading ?? data.books_reading ?? 0; + document.getElementById('ll-read').innerText = data.books_read ?? 0; + document.getElementById('ll-wtr').innerText = data.books_want_to_read ?? 0; + document.getElementById('ll-total').innerText = data.total_books ?? 0; + + } catch (error) { + console.error('LibrisLog Widget Error:', error); + const elements = ['ll-reading', 'll-read', 'll-wtr', 'll-total']; + elements.forEach(id => { + const el = document.getElementById(id); + if (el) el.innerText = '!'; + if (el) el.style.color = 'var(--danger, #ff0033)'; + }); + } + })(); +``` + +Replace the placeholders with your own values: + +| Placeholder | Example | Description | +|---|---|---| +| `` | `http://192.168.1.100:8000` | The base URL of your LibrisLog instance (http or https) | +| `` | `lk_nRHsF3jxIBDa9u....` | An API key with access to the statistics endpoint | + +The `updateInterval` is specified in seconds. `300` equals 5 minutes. + +## CORS + +Since the widget runs inside the Dashy iframe and fetches the LibrisLog API +directly from the browser, you must add your Dashy URL to the +[`CORS_ORIGINS`](/guide/configuration#core-settings) environment variable of +the LibrisLog backend. For example: + +``` +CORS_ORIGINS=["https://dashy.YOUR-DOMAIN"] +``` + +## Result + +![Dashy Widget](/screenshots/integrations-dashy.png) diff --git a/docs/api/integrations/index.md b/docs/api/integrations/index.md index 132a3f71..75017507 100644 --- a/docs/api/integrations/index.md +++ b/docs/api/integrations/index.md @@ -16,6 +16,9 @@ API key to use them. You can create one either: ## Available Integrations +- [Dashy](/api/integrations/dashy) — Display your LibrisLog statistics as + styled stat cards on a [Dashy](https://dashy.to/) dashboard using the HTML + embedded widget. - [Home Assistant](/api/integrations/homeassistant) — Expose your LibrisLog reading statistics as sensors in [Home Assistant](https://www.home-assistant.io/) using the RESTful diff --git a/docs/public/screenshots/integrations-dashy.png b/docs/public/screenshots/integrations-dashy.png new file mode 100644 index 0000000000000000000000000000000000000000..10c36b1fcdcc861354fd6ac10cea1d134408b48b GIT binary patch literal 11892 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 literal 0 HcmV?d00001 From 73bd02dcf52108e8ff3efbba97a93af9f45519fe Mon Sep 17 00:00:00 2001 From: codebude Date: Tue, 9 Jun 2026 23:25:10 +0200 Subject: [PATCH 10/11] Added note on integrations to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9980e677..5ef60260 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Open **http://localhost:8001** and create your account. - **Point your phone at an ISBN barcode.** Real-time barcode scanning in the browser — no native app required. - **Cover art from multiple sources.** Automatic search across AbeBooks, Open Library, Amazon, and Hardcover — plus manual upload or URL paste. - **Full REST API.** OpenAPI-documented backend you can script against — build your own frontend, connect home automation, or pipe data into your own tools. +- **Third-party integrations.** Display your stats on [Dashy](https://dashy.to/), [Home Assistant](https://www.home-assistant.io/), or [Homepage](https://gethomepage.dev/) dashboards and more. [See all integrations →](https://docs.librislog.app/api/integrations/) - **Lightweight.** Two Docker containers, one SQLite database. - **Multi-language UI.** English, German, Spanish, French, and Chinese (Simplified) — with a localization framework ready for more languages. From eef355cb8476285b7a693588cdb6b190cc278264 Mon Sep 17 00:00:00 2001 From: codebude Date: Tue, 9 Jun 2026 23:33:01 +0200 Subject: [PATCH 11/11] Avoid duplicate test runs on develop to main PRs --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1c0f2b2e..6c078b5f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,7 +2,7 @@ name: Tests on: push: - branches: [develop, main] + branches: [main] pull_request: branches: [develop, main] workflow_dispatch: