Feature/post media translations (#42)

* chore: updated todo with translation ideas

* feat: first take at the implementation of translations

* fix: small addition for the translation feature

* feat: support language switching in the editor and preview

* feat: better handling of long bodies by not running them through a json envelope

* fix: unknown macros have better fallback

* feat: api for python to get translations

* fix: strip dumb prefix of content in translation

* feat: extend meta diff for translations

* feat: hook up translations to rebuild-from-disk

* feat: generation of the website prefers project language, falling back to canonical language

* fix: crashes during rendering

* feat: translation validation report

* fix: made the translation validation actually work

* chore: reorganization of menu

* fix: some topics cleanup

* chore: updated doc

* feat: translations for media

* feat: more aligned in UI/UX

* feat: edit translations possible

* chore: added full multi-language todo

* chore: updated todo for clarity

* feat: implementation of full multi-linguality

* fix: page creation creates pages

* fix: flags on every page

* fix: better prompt

* feat: made MCP server aware of language content

* feat: python tools for translations

* fix: better fill-in-translations

* fix: better prompt for translation. maybe.

* fix: losing posts from search due to translation process

* fix: translation validation handles in-db content and fill-in of missing translations fixed to flush

* fix: faster scanning for infilling of missing translations

* chore: updated agent instructions

* feat: calendar and tag cloud respect current language now

* fix: retries going up

* fix: got metadata-diff and rebuild into sync

* fix: extended meta-diff for timestamps

* fix: made website validation look at translated content, too

* fix: multi-lingual search

* chore: refactor Editor.tsx into two separate editors

* feat: do language detection when no explicit language given

---------

Co-authored-by: hugo <hugoms@me.com>
This commit is contained in:
Georg Bauer
2026-03-09 14:43:18 +01:00
committed by GitHub
parent f1c9038803
commit b855d61524
116 changed files with 19954 additions and 2094 deletions

View File

@@ -54,6 +54,33 @@
"siteValidation.error.validate": "La validación del sitio falló",
"siteValidation.error.apply": "La aplicación de la validación falló",
"siteValidation.toast.applySuccess": "Validación aplicada: {rendered} renderizadas, {deleted} eliminadas",
"menu.item.validateTranslations": "Validar traducciones",
"translationValidation.tabTitle": "Validación de traducciones",
"translationValidation.title": "Validar traducciones",
"translationValidation.summary": "Filas de BD revisadas: {dbRows} · Archivos revisados: {files} · Filas de BD inválidas: {invalidDb} · Archivos inválidos: {invalidFiles}",
"translationValidation.loading": "Validando traducciones...",
"translationValidation.empty": "Ejecuta Blog -> Validar traducciones para inspeccionar la integridad de las traducciones.",
"translationValidation.databaseTitle": "Filas de traducción inválidas en la base de datos",
"translationValidation.filesystemTitle": "Archivos de traducción inválidos en disco",
"translationValidation.noneDatabase": "No se encontraron filas de traducción inválidas.",
"translationValidation.noneFilesystem": "No se encontraron archivos de traducción inválidos.",
"translationValidation.error.validate": "La validación de traducciones falló",
"translationValidation.issue.sameLanguage": "El idioma de la traducción coincide con el idioma canónico de la entrada",
"translationValidation.issue.missingSource": "La traducción apunta a una entrada de origen inexistente",
"translationValidation.issue.doNotTranslate": "La entrada está marcada como no-traducir pero tiene traducciones",
"translationValidation.issue.contentInDatabase": "Traducción publicada con contenido en la BD en lugar del sistema de archivos",
"translationValidation.field.translationFor": "Entrada de origen",
"translationValidation.field.translationId": "Fila de traducción",
"translationValidation.field.title": "Título",
"translationValidation.field.languages": "Idiomas",
"translationValidation.field.filePath": "Archivo",
"translationValidation.languagesWithCanonical": "{canonical} = {translation}",
"translationValidation.revalidate": "Revalidar",
"translationValidation.revalidating": "Revalidando…",
"translationValidation.fix": "Corregir problemas",
"translationValidation.fixing": "Corrigiendo…",
"translationValidation.toast.fixSuccess": "{dbRows} filas de BD y {files} archivos eliminados, {flushed} traducciones escritas a disco",
"translationValidation.error.fix": "Error al corregir traducciones inválidas",
"menuEditor.tabTitle": "Menú del blog",
"menuEditor.title": "Editor del menú del blog",
"menuEditor.description": "Gestiona la estructura central de navegación del blog y guárdala en meta/menu.opml.",
@@ -412,7 +439,7 @@
"metadataDiff.fieldFilter.toggle": "Filtrar por {field}",
"metadataDiff.sync.failed": "falló",
"metadataDiff.sync.dbToFile.title": "Actualizar archivos con valores de la base de datos",
"metadataDiff.sync.dbToFile.short": "BD\u2192A",
"metadataDiff.sync.dbToFile.short": "BDA",
"metadataDiff.sync.dbToFile.success": "Se sincronizaron {success} entradas a archivos{falló}",
"metadataDiff.sync.dbToFile.error": "No se pudo sincronizar a archivos",
"metadataDiff.sync.fileToDb.title": "Actualizar base de datos con valores de archivos",
@@ -433,7 +460,7 @@
"metadataDiff.orphanFiles.badge": "Archivo huérfano",
"metadataDiff.orphanFiles.slug": "Slug",
"metadataDiff.orphanFiles.path": "Ruta",
"metadataDiff.orphanFiles.importButton": "D \u2192 BD",
"metadataDiff.orphanFiles.importButton": "D BD",
"metadataDiff.orphanFiles.importTitle": "Importar todos los archivos huérfanos a la base de datos",
"metadataDiff.orphanFiles.importing": "Importando…",
"metadataDiff.orphanFiles.importSuccess": "{success} archivos huérfanos importados{failed}",
@@ -461,6 +488,7 @@
"sidebar.published": "Publicadas",
"sidebar.archived": "Archivadas",
"sidebar.untitled": "Sin título",
"sidebar.languagesAvailable": "{count} idiomas disponibles",
"sidebar.noMatchingPosts": "No hay entradas coincidentes",
"sidebar.createFirstPost": "Crea tu primera entrada",
"sidebar.loadMore": "Cargar más ({loaded} de {total})",
@@ -525,6 +553,8 @@
"settings.project.publicUrlPlaceholder": "https://example.com",
"settings.project.mainLanguageLabel": "Idioma principal",
"settings.project.mainLanguageDescription": "Idioma principal del contenido del blog. Los títulos, textos alternativos y pies generados por IA usarán este idioma.",
"settings.project.blogLanguagesLabel": "Idiomas del blog",
"settings.project.blogLanguagesDescription": "Idiomas en los que se genera el blog. El idioma principal siempre está incluido. Los idiomas adicionales generan subárboles traducidos.",
"settings.project.defaultAuthorLabel": "Autor predeterminado",
"settings.project.defaultAuthorDescription": "Nombre de autor predeterminado para nuevas entradas y medios. Se puede reemplazar por elemento.",
"settings.project.defaultAuthorPlaceholder": "Nombre del autor",
@@ -578,6 +608,27 @@
"editor.previewFrameTitle": "Vista previa de la entrada",
"editor.previewLoading": "Cargando vista previa...",
"editor.metadata.toggle": "Metadatos",
"editor.translations.title": "Traducciones",
"editor.translations.currentLanguage": "Idioma actual: {language}",
"editor.translations.none": "Todavía no hay traducciones.",
"editor.translations.selectTarget": "Selecciona el idioma de destino",
"editor.translations.translateButton": "Traducir a...",
"editor.translations.translateTitle": "Crear o actualizar una traducción con IA",
"editor.translations.translating": "Traduciendo...",
"editor.translations.refresh": "Actualizar",
"editor.translations.refreshTitle": "Regenerar esta traducción con IA",
"editor.translations.publish": "Publicar",
"editor.translations.publishTitle": "Publicar esta traducción en su archivo Markdown",
"editor.translations.publishing": "Publicando...",
"editor.translations.missing": "Faltan: {languages}",
"editor.translations.complete": "Todos los idiomas de traducción compatibles están disponibles.",
"editor.translations.translateSuccess": "Traducción actualizada para {language}",
"editor.translations.translateFailed": "La traducción falló",
"editor.translations.publishSuccess": "Traducción publicada para {language}",
"editor.translations.publishFailed": "No se pudo publicar la traducción",
"editor.translations.status.draft": "Borrador",
"editor.translations.status.published": "Publicada",
"editor.translations.status.archived": "Archivada",
"editor.excerpt.toggle": "Extracto",
"editor.footer.created": "Creado",
"editor.footer.updated": "Actualizado",
@@ -927,6 +978,12 @@
"editor.media.quickActions.button": "✨ Analizar con IA",
"editor.media.quickActions.aiTitle": "Título sugerido por IA",
"editor.media.quickActions.aiDescription": "Genera automáticamente título, texto alternativo y pie de foto.",
"editor.media.quickActions.detectLanguageTitle": "Detectar idioma",
"editor.media.quickActions.detectLanguageDescription": "Detectar el idioma de los metadatos con IA",
"editor.media.quickActions.translateTitle": "Traducir a…",
"editor.media.quickActions.translateDescription": "Crear o actualizar una traducción con IA",
"editor.media.translations.currentLanguage": "Idioma actual: {language}",
"editor.media.translations.selectTarget": "Seleccionar idioma de destino",
"editor.post.quickActions.title": "Acciones rápidas",
"editor.post.quickActions.analyzing": "⏳ Analizando…",
"editor.post.quickActions.button": "⚡ Acciones rápidas",
@@ -949,6 +1006,24 @@
"editor.media.field.caption": "Pie de foto",
"editor.media.field.tags": "Etiquetas",
"editor.media.field.author": "Autor",
"editor.media.field.language": "Idioma",
"editor.media.field.languageNone": "No definido",
"editor.media.translations.title": "Traducciones",
"editor.media.translations.none": "Aún no hay traducciones.",
"editor.media.translations.translateButton": "Traducir a…",
"editor.media.translations.translating": "Traduciendo…",
"editor.media.translations.translateSuccess": "Traducción actualizada para {language}",
"editor.media.translations.translateFailed": "Error en la traducción",
"editor.media.translations.refresh": "Actualizar",
"editor.media.translations.refreshTitle": "Regenerar esta traducción con IA",
"editor.media.translations.deleteTitle": "Eliminar esta traducción",
"editor.media.translations.deleted": "Traducción eliminada para {language}",
"editor.media.translations.deleteFailed": "Error al eliminar la traducción",
"editor.media.translations.editTitle": "Editar traducción — {language}",
"editor.media.translations.saved": "Traducción guardada para {language}",
"editor.media.translations.saveFailed": "Error al guardar la traducción",
"editor.media.toast.languageDetected": "Idioma detectado: {language}",
"editor.media.error.detectLanguage": "Error al detectar el idioma",
"editor.media.placeholder.title": "Introduce un título",
"editor.media.placeholder.altText": "Describe la imagen para accesibilidad",
"editor.media.placeholder.caption": "Añadir pie de foto",
@@ -1097,9 +1172,7 @@
"importAnalysis.usedIn": "Usado en: {items}{more}",
"importAnalysis.moreSuffix": ", +{count} más",
"importAnalysis.noParameters": "(sin parámetros)",
"sidebar.nav.mcp": "Servidor MCP",
"settings.mcp.title": "Servidor MCP",
"settings.mcp.description": "Configure el servidor Model Context Protocol que permite a los agentes de programación IA interactuar con su blog.",
"settings.mcp.statusLabel": "Estado del servidor",
@@ -1132,5 +1205,9 @@
"duplicatesView.checkAll": "Seleccionar todo",
"duplicatesView.uncheckAll": "Deseleccionar todo",
"duplicatesView.dismissChecked": "Descartar seleccionados ({count})",
"duplicatesView.notEnabled": "La similitud semántica no está activada. Actívela en Configuración → Tecnología."
"duplicatesView.notEnabled": "La similitud semántica no está activada. Actívela en Configuración → Tecnología.",
"editor.doNotTranslateLabel": "No traducir",
"blog.fillMissing.nothingToDo": "Todas las traducciones están al día.",
"blog.fillMissing.started": "Tarea de traducción iniciada. Consulte el panel de tareas para ver el progreso.",
"blog.fillMissing.error": "Error al rellenar las traducciones faltantes."
}