From 928ff8d42bbf989c4cf4b7630d883b764c51cdd2 Mon Sep 17 00:00:00 2001 From: Axelle Abbadie Date: Wed, 20 May 2026 00:07:39 +0200 Subject: [PATCH] feat: i18n ru/es, export HTML noScribe word-level, fix auto-convert + export paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - i18n: add Russian (ru) and Spanish (es) locales (~240 keys each) - feat: markdownToNoScribeHtml — reconstruct pseudonymized noScribe HTML with word-level ts_ anchors via proportional index mapping on words.json; add export-as-html command, panel button, notice.htmlExported - fix: autoConvert now skips files created in exports/mappings folders (prevented re-import of freshly written VTT/SRT/HTML exports) - fix: pseudonymizeActiveFile respects exportMirrorClasses — intermediate .pseudonymized.md is now written into the correct class subfolder - fix: extract getClassSub() helper shared by resolveExportPath and pseudonymizeActiveFile - fix: "Nouvelle classe" button in corpus panel now opens CorpusModal instead of window.prompt (blocked in Electron) - refactor: remove next-to-source export destination option Co-Authored-By: Claude Sonnet 4.6 --- main.js | 948 +++++++++++++++++++++++++++-- src/i18n/index.ts | 6 +- src/i18n/locales/en.json | 5 +- src/i18n/locales/es.json | 378 ++++++++++++ src/i18n/locales/fr.json | 5 +- src/i18n/locales/ru.json | 378 ++++++++++++ src/main.ts | 117 ++-- src/parsers/TranscriptConverter.ts | 128 ++++ src/settings.ts | 2 +- src/ui/CorpusModal.ts | 5 +- src/ui/PseudonymizationView.ts | 25 +- 11 files changed, 1906 insertions(+), 91 deletions(-) create mode 100644 src/i18n/locales/es.json create mode 100644 src/i18n/locales/ru.json diff --git a/main.js b/main.js index 0123f10..c5843a6 100644 --- a/main.js +++ b/main.js @@ -236,7 +236,6 @@ var init_en = __esm({ "panel.corpus.deleteClassConfirm": 'Delete class "{0}"? Files inside will be moved to root.', "panel.corpus.exportSettings": "Final export destination", "panel.corpus.exportDest.vault": "In the vault", - "panel.corpus.exportDest.nextToSource": "Next to source file", "panel.corpus.exportDest.external": "External folder", "panel.corpus.exportFolder": "Export folder (vault)", "panel.corpus.exportExternalPath": "Absolute path", @@ -255,8 +254,12 @@ var init_en = __esm({ "panel.exports.exportSrt.desc": "Rebuilds the pseudonymized SRT file from the Markdown.", "panel.exports.exportCha": "Re-export as CHAT (.cha)", "panel.exports.exportCha.desc": "Rebuilds the pseudonymized CHAT file from the Markdown.", + "panel.exports.exportHtml": "Re-export as HTML", + "panel.exports.exportHtml.desc": "Rebuilds the pseudonymized noScribe HTML file from the Markdown and word timestamps (.words.json).", + "notice.htmlExported": "HTML exported: {0}", "command.exportAsSrt": "Export as SRT", "command.exportAsCha": "Export as CHAT", + "command.exportAsHtml": "Export as HTML", "panel.ner.scanBtn": "Identify candidates", "panel.ner.hint": "Settings for the automatic named entity detection scanner.", "panel.ner.threshold": "Confidence threshold", @@ -401,6 +404,371 @@ var init_en = __esm({ } }); +// src/i18n/locales/es.json +var es_default; +var init_es = __esm({ + "src/i18n/locales/es.json"() { + es_default = { + "corpus.modal.title": "Organizar corpus", + "corpus.modal.hint": "Cada clase crea carpetas espejo para las transcripciones, las tablas de correspondencia y los exportes.", + "corpus.modal.noClasses": "No hay clases definidas. A\xF1ada una para organizar sus documentos por carpeta.", + "corpus.modal.addClass": "A\xF1adir una clase", + "corpus.modal.classNamePlaceholder": "Nombre de la clase (p. ej. Sesi\xF3n_A, Informante_1\u2026)", + "corpus.modal.classNameEmpty": "El nombre de la clase no puede estar vac\xEDo.", + "corpus.modal.classExists": "Ya existe una clase con este nombre.", + "corpus.modal.deleteConfirm": "\xBFEliminar la clase \xAB{0}\xBB? Las carpetas ser\xE1n retiradas del vault (los archivos dentro ir\xE1n a la papelera).", + "corpus.modal.files": "{0} archivo(s)", + "corpus.modal.close": "Cerrar", + "corpus.select.title": "\xBFEn qu\xE9 clase a\xF1adir?", + "corpus.select.hint": "Elija la clase (carpeta) para esta transcripci\xF3n.", + "corpus.select.none": "Sin clase (ra\xEDz)", + "corpus.select.confirm": "A\xF1adir", + "quickModal.title": "Pseudonimizar", + "quickModal.source": "Expresi\xF3n seleccionada", + "quickModal.sourceDesc": "T\xE9rmino a reemplazar \u2014 no modificable", + "quickModal.replaceBy": "Reemplazar por", + "quickModal.replacementPlaceholder": "Pseud\xF3nimo o categor\xEDa anal\xEDtica", + "quickModal.scope": "\xC1mbito del reemplazo", + "quickModal.scopeFile": "Todas las ocurrencias en este archivo", + "quickModal.scopeOccurrence": "Solo esta ocurrencia", + "quickModal.submit": "Pseudonimizar", + "notice.appliedOccurrence": "\u2713 \xAB{0}\xBB \u2192 \xAB{1}\xBB (esta ocurrencia)", + "notice.appliedFile": "\u2713 \xAB{0}\xBB \u2192 \xAB{1}\xBB ({2} ocurrencia{3})", + "onboarding.dict.importOk": "{0} diccionario importado", + "onboarding.dict.importOkMany": "{0} diccionarios importados", + "onboarding.dict.importErr": "{0} archivo rechazado", + "onboarding.dict.importErrMany": "{0} archivos rechazados", + "onboarding.dict.importError": "Error al importar {0} \u2014 verifique el formato .dict.json", + "notice.noActiveFile": "No hay archivo activo.", + "notice.formatUnsupported": "Formato no compatible: .{0}", + "notice.noRules": "No hay reglas para este archivo.\nCree reglas con Ctrl+P \u2192 \xABCrear una regla\xBB.", + "notice.noOccurrences": "No se encontraron ocurrencias para las reglas activas.", + "notice.noNerEntities": "El NER no detect\xF3 ninguna entidad.", + "notice.nerEntitiesFound": "\u2713 {0} {1} detectada(s) \u2014 resaltada(s) en azul.\nClic derecho para crear una regla.", + "notice.nerEntitiesFound.entity": "entidad", + "notice.nerEntitiesFound.entities": "entidades", + "notice.nerNotEnabled": "La detecci\xF3n NER de transformers.js no est\xE1 activada.\nAct\xEDvela en Configuraci\xF3n \u2192 Pseudonymizer Tool.", + "notice.noDictDetection": "No hay diccionario de detecci\xF3n cargado.\nInstale uno desde el panel Diccionarios.", + "notice.noDictEntities": "No se encontraron entidades en los diccionarios de detecci\xF3n.", + "notice.noDictReplacements": "No hay reemplazos disponibles para las entidades encontradas.", + "notice.ruleDeleted": "\u2713 Regla eliminada \u2014 \xAB{0}\xBB restaurado en el archivo activo", + "notice.ruleCreated": "\u2713 Regla creada: \xAB{0}\xBB \u2192 \xAB{1}\xBB", + "notice.rulesCreated": "\u2713 {0} {1} creada(s)", + "notice.rulesCreated.rule": "regla", + "notice.rulesCreated.rules": "reglas", + "notice.occurrencesPseudonymized": "\u2713 {0} {1} pseudonimizada(s) en \xAB{2}\xBB", + "notice.occurrencesPseudonymized.occurrence": "ocurrencia", + "notice.occurrencesPseudonymized.occurrences": "ocurrencias", + "notice.converted": "\u2713 {0} \u2192 {1}", + "notice.conversionError": "Error de conversi\xF3n de {0}: {1}", + "notice.fileExists": "El archivo ya existe en el vault: {0}", + "notice.conversionSkipped": "\u26A0 {0}.md ya existe \u2014 conversi\xF3n omitida para {1}", + "notice.exportDone": "\u2713 {0} regla(s) aplicada(s)\n\u2192 {1}", + "notice.mappingExported": "\u2713 Tabla de correspondencia exportada \u2192 {0}", + "notice.noMapping": "No se encontr\xF3 tabla de correspondencia para {0}", + "notice.invalidFormat": "Formato no v\xE1lido: {0}", + "notice.dictImported": "\u2713 {0} {1} importado(s)", + "notice.dictImported.one": "diccionario", + "notice.dictImported.many": "diccionarios", + "notice.dictRejected": "{0} {1} rechazado(s)", + "notice.dictRejected.one": "archivo", + "notice.dictRejected.many": "archivos", + "notice.nerModelLoaded": "\u2713 Modelo NER cargado", + "notice.nerModelLoading": "Cargando modelo NER (primer uso \u2014 ~66 MB)\u2026", + "notice.ruleNotFound": "Regla no encontrada en las tablas de correspondencia.", + "notice.noCheckedDicts": "Ning\xFAn diccionario marcado.", + "notice.audioImported": "Audio importado: {0}", + "occurrencesCtx.hint": "{0} ocurrencia{1} \u2014 seleccione las que desea reemplazar.", + "occurrencesCtx.hint.s": "s", + "occurrencesCtx.hint.empty": "", + "occurrencesCtx.validateAll": "Validar todas", + "occurrencesCtx.ignoreAll": "Ignorar todas", + "occurrencesCtx.cancel": "Cancelar", + "occurrencesCtx.confirm": "Confirmar selecci\xF3n", + "occurrencesCtx.btn.validate": "Validar", + "occurrencesCtx.btn.ignore": "Ignorar", + "occurrencesCtx.btn.fp": "Falso positivo", + "occurrencesCtx.status.ignored": "Conservado tal cual", + "occurrencesCtx.status.fp": "Falso positivo \u2014 excluido", + "notice.notNoScribeFormat": "Este archivo no es una transcripci\xF3n de noScribe (se espera pseudobs-format: vtt o html).", + "notice.wordsJsonMissing": "Archivo de marcas de tiempo no encontrado: {0}.words.json", + "notice.vttMismatch": "Aviso: el n\xFAmero de cues del Markdown y del words.json difiere \u2014 el VTT exportado puede estar incompleto.", + "notice.vttExported": "VTT exportado: {0}", + "command.organizeCorpus": "Organizar corpus", + "command.addTranscription": "A\xF1adir una transcripci\xF3n", + "command.pseudonymizeFile": "Pseudonimizar el archivo actual", + "command.createRule": "Crear una regla de reemplazo", + "command.scanFile": "Escanear el archivo actual", + "command.scanNer": "Escanear el archivo con detecci\xF3n NER", + "command.scanDictionaries": "Escanear el archivo con diccionarios", + "command.pseudonymizeSelection": "Pseudonimizar la selecci\xF3n", + "command.exportAsVtt": "Exportar como VTT", + "command.exportAsSrt": "Exportar como SRT", + "command.exportAsCha": "Exportar como CHAT", + "command.exportAsHtml": "Exportar como HTML", + "command.openPanel": "Pseudonimizaci\xF3n: abrir panel", + "contextMenu.declareException": "Declarar \xAB{0}\xBB como excepci\xF3n aqu\xED", + "contextMenu.cancelPseudonymization": "Cancelar pseudonimizaci\xF3n de \xAB{0}\xBB", + "contextMenu.editRule": "Editar regla para \xAB{0}\xBB", + "contextMenu.pseudonymize": "Pseudonimizar \xAB{0}\xBB", + "contextMenu.redact": "Redactar \xAB{0}\xBB", + "contextMenu.coulmont": "Pseudonimizar con el prof. Baptiste Coulmont", + "contextMenu.createRule": "Crear una regla de reemplazo\u2026", + "redaction.checkbox": "Redactar (\u{1F02B})", + "redaction.checkboxDesc": "Reemplazar con s\xEDmbolos \u{1F02B} (uno por s\xEDlaba) \u2014 para contenido identificador no esencial", + "ruleModal.delete": "Eliminar regla", + "ruleModal.scopeWarnTitle": "\xC1mbito longitudinal", + "ruleModal.scopeOkTitle": "\xC1mbito de archivo", + "ruleModal.scopeOk": "Buena pr\xE1ctica. Este pseud\xF3nimo solo se aplicar\xE1 a esta transcripci\xF3n \u2014 cada archivo permanece independiente.", + "ruleModal.title": "Crear una regla de reemplazo", + "ruleModal.source": "Fuente", + "ruleModal.sourceDesc": "T\xE9rmino original a reemplazar", + "ruleModal.replacement": "Reemplazo", + "ruleModal.replacementDesc": "Pseud\xF3nimo o categor\xEDa anal\xEDtica", + "ruleModal.replacementDescClass": "El \xEDndice exacto se calcular\xE1 al crearlo seg\xFAn las reglas existentes en el \xE1mbito.", + "ruleModal.category": "Categor\xEDa", + "ruleModal.scope": "\xC1mbito", + "ruleModal.scopeFile": "Solo este archivo", + "ruleModal.scopeFolder": "Esta carpeta", + "ruleModal.scopeVault": "Todo el vault", + "ruleModal.priority": "Prioridad", + "ruleModal.priorityDesc": "Entero libre, como un z-index CSS \u2014 por defecto 0, mayor = aplicado primero", + "ruleModal.submit": "Crear regla", + "ruleModal.errorMissing": "La fuente y el reemplazo son obligatorios.", + "ruleModal.errorNoFile": "No hay archivo activo.", + "ruleModal.coulomontLabel": "Sugerencias de nombres equivalentes \u2014 elija:", + "ruleModal.dictLabel": "Diccionario: \xAB{0}\xBB \u2192 clase {1}", + "ruleModal.dictUseClass": "Usar \xAB{0}\xBB (\xE1mbito: {1})", + "category.first_name": "Nombre", + "category.last_name": "Apellido", + "category.full_name": "Nombre completo", + "category.place": "Lugar", + "category.institution": "Instituci\xF3n", + "category.date": "Fecha", + "category.age": "Edad", + "category.profession": "Profesi\xF3n", + "category.custom": "Otro", + "scope.file": "Archivo", + "scope.folder": "Carpeta", + "scope.vault": "Vault", + "status.validated": "Activo", + "status.ignored": "Ignorado", + "status.partial": "Parcial", + "status.suggested": "Sugerido", + "status.conflict": "Conflicto", + "status.disabled": "Inactivo", + "status.needs_review": "Revisi\xF3n", + "panel.tab.mappings": "Correspondencias", + "panel.tab.dictionaries": "Diccionarios", + "panel.tab.exports": "Exportes", + "panel.tab.ner": "NER", + "panel.tab.corpus": "Corpus", + "panel.mappings.filterActive": "Solo archivo activo", + "panel.mappings.warnBroadName": "\u26A0 Ampliar el \xE1mbito de un nombre o apellido m\xE1s all\xE1 del archivo solo debe hacerse como elecci\xF3n anal\xEDtica deliberada \u2014 por ejemplo, para seguir de forma coherente al mismo participante en un estudio longitudinal. Incluso cuando el mismo individuo aparece en varios archivos, la pseudonimizaci\xF3n a nivel de archivo suele ser preferible: mantiene cada transcripci\xF3n independiente y reduce el riesgo de identificaci\xF3n cruzada.", + "panel.mappings.addRule": "A\xF1adir una regla", + "panel.mappings.scanFile": "Escanear archivo", + "panel.mappings.scanning": "Escaneando\u2026", + "panel.mappings.noFile": "No hay archivo activo.", + "panel.mappings.noRules": "No hay reglas para {0}.", + "panel.mappings.noRulesHint": "No hay reglas activas para este archivo. Cree reglas desde el men\xFA contextual o el comando \xABCrear una regla\xBB.", + "panel.mappings.col.source": "Fuente", + "panel.mappings.col.replacement": "Reemplazo", + "panel.mappings.col.category": "Categor\xEDa", + "panel.mappings.col.scope": "\xC1mbito", + "panel.mappings.col.priority": "P.", + "panel.mappings.col.status": "Estado", + "panel.mappings.exceptions": "Excepciones", + "panel.mappings.exceptions.hint": "Ocurrencias expl\xEDcitamente ignoradas para esta regla.", + "panel.mappings.exceptions.delete": "Eliminar esta excepci\xF3n", + "panel.dict.noneInstalled": "No hay diccionarios instalados. Instale uno desde el asistente (Configuraci\xF3n \u2192 Reconfigurar) o importe un archivo local.", + "panel.dict.checkbox": "Incluir en el escaneo agrupado", + "panel.dict.scanAll": "Escanear diccionarios marcados", + "panel.dict.importLocal": "Importar un archivo local (.dict.json)", + "panel.dict.scanWith": "Escanear el archivo activo con \xAB{0}\xBB", + "panel.dict.remove": "Eliminar este diccionario", + "panel.corpus.noFolder": "Carpeta de transcripciones no encontrada. Config\xFArela en los ajustes.", + "panel.corpus.empty": "No se encontraron transcripciones en esta carpeta.", + "panel.corpus.rules": "{0} regla(s)", + "panel.corpus.hasPseudo": "Versi\xF3n pseudonimizada creada", + "panel.corpus.noPseudo": "A\xFAn no pseudonimizado", + "panel.corpus.hasFinal": "Exporte final {0} presente", + "panel.corpus.noFinal": "Sin exporte final", + "panel.corpus.addClass": "Nueva clase", + "panel.corpus.noClass": "Sin clase", + "panel.corpus.moveTo": "Mover a\u2026", + "panel.corpus.moveToRoot": "Sin clase (ra\xEDz)", + "panel.corpus.deleteClass": "Eliminar clase", + "panel.corpus.deleteClassConfirm": "\xBFEliminar la clase \xAB{0}\xBB? Los archivos dentro se mover\xE1n a la ra\xEDz.", + "panel.corpus.exportSettings": "Destino del exporte final", + "panel.corpus.exportDest.vault": "En el vault", + "panel.corpus.exportDest.external": "Carpeta externa", + "panel.corpus.exportFolder": "Carpeta de exporte (vault)", + "panel.corpus.exportExternalPath": "Ruta absoluta", + "panel.corpus.exportExternalPathPlaceholder": "/Volumes/Archives/Exports", + "panel.corpus.exportMirrorClasses": "Replicar estructura de clases", + "panel.filenameWarning.msg": "El nombre de archivo \xAB{0}\xBB contiene un t\xE9rmino pseudonimizable.", + "panel.filenameWarning.edit": "Renombrar\u2026", + "panel.filenameWarning.editTitle": "Introduzca un nombre manualmente", + "panel.filenameWarning.editPrompt": "Nuevo nombre para \xAB{0}\xBB", + "panel.filenameWarning.wandTitle": "Renombrar a \xAB{0}\xBB", + "panel.exports.pseudonymize": "Crear versi\xF3n pseudonimizada", + "panel.exports.exportMapping": "Exportar tabla de correspondencia", + "panel.exports.exportVtt": "Reexportar como VTT", + "panel.exports.exportVtt.desc": "Reconstruye un archivo WebVTT pseudonimizado desde el Markdown y las marcas de tiempo (.words.json).", + "panel.exports.exportSrt": "Reexportar como SRT", + "panel.exports.exportSrt.desc": "Reconstruye el archivo SRT pseudonimizado desde el Markdown.", + "panel.exports.exportCha": "Reexportar como CHAT (.cha)", + "panel.exports.exportCha.desc": "Reconstruye el archivo CHAT pseudonimizado desde el Markdown.", + "panel.exports.exportHtml": "Reexportar como HTML", + "panel.exports.exportHtml.desc": "Reconstruye el archivo HTML noScribe pseudonimizado desde el Markdown y las marcas de tiempo (.words.json).", + "notice.htmlExported": "HTML exportado: {0}", + "panel.ner.scanBtn": "Identificar candidatos", + "panel.ner.hint": "Ajustes del esc\xE1ner de detecci\xF3n autom\xE1tica de entidades nombradas.", + "panel.ner.threshold": "Umbral de confianza", + "panel.ner.thresholdDesc": "Las entidades con una puntuaci\xF3n inferior a este valor se ignoran. Mayor = menos falsos positivos pero menos detecciones.", + "panel.ner.functionWords": "Palabras funcionales excluidas", + "panel.ner.functionWordsDesc": "Una palabra por l\xEDnea. Estos tokens nunca se conservar\xE1n como entidades nombradas, aunque el modelo los detecte.", + "panel.ner.save": "Guardar", + "panel.ner.saved": "Guardado", + "panel.ner.reset": "Restablecer valores predeterminados", + "dictScanModal.title": "Revisi\xF3n del escaneo \u2014 diccionarios", + "dictScanModal.summary": "{0} {1} detectada(s) \xB7 {2}", + "dictScanModal.summary.entity": "entidad", + "dictScanModal.summary.entities": "entidades", + "dictScanModal.hint": "Desmarque los falsos positivos. El prefijo es editable; el \xEDndice se calcula autom\xE1ticamente.", + "dictScanModal.apply": "Crear {0} {1}", + "dictScanModal.apply.rule": "regla", + "dictScanModal.apply.rules": "reglas", + "dictScanModal.noRules": "No hay reglas que aplicar", + "dictScanModal.cancel": "Cancelar", + "mappingScanModal.title": "Revisi\xF3n del escaneo \u2014 correspondencias", + "mappingScanModal.summary": "{0} {1} con ocurrencias en \xAB{2}\xBB", + "mappingScanModal.summary.rule": "regla activa", + "mappingScanModal.summary.rules": "reglas activas", + "mappingScanModal.hint": "Desmarque las reglas que no desea aplicar. Los reemplazos se escribir\xE1n en el archivo fuente.", + "mappingScanModal.col.source": "", + "mappingScanModal.col.replacement": "Reemplazo", + "mappingScanModal.col.occurrences": "Ocur.", + "mappingScanModal.apply": "Pseudonimizar ({0} {1}, {2} {3})", + "mappingScanModal.apply.rule": "regla", + "mappingScanModal.apply.rules": "reglas", + "mappingScanModal.apply.occurrence": "ocurrencia", + "mappingScanModal.apply.occurrences": "ocurrencias", + "mappingScanModal.noRules": "No hay reglas que aplicar", + "mappingScanModal.cancel": "Cancelar", + "mappingScanModal.saveExceptions": "Guardar excepciones", + "mappingScanModal.exceptionsSaved": "Excepciones guardadas en la tabla de correspondencia.", + "mappingScanModal.countTooltip": "Ver y seleccionar candidatos", + "onboarding.step.storage": "Almacenamiento", + "onboarding.step.language": "Idioma", + "onboarding.step.welcome": "Bienvenida", + "onboarding.step.ner": "Detecci\xF3n NER", + "onboarding.step.dictionaries": "Diccionarios", + "onboarding.step.summary": "Resumen", + "onboarding.welcome.title": "Pseudonymizer Tool", + "onboarding.welcome.desc": "Este plugin le ayuda a corregir y pseudonimizar transcripciones de entrevistas y corpus interaccionales (convenciones Jefferson, ICOR, formatos .srt, .cha).", + "onboarding.welcome.steps": "El asistente le guiar\xE1 en unos pocos pasos r\xE1pidos:", + "onboarding.welcome.step1": "Elija el idioma de la interfaz.", + "onboarding.welcome.step2": "Elija un motor de detecci\xF3n autom\xE1tica de entidades nombradas (opcional).", + "onboarding.welcome.step3": "Instale diccionarios de candidatos de reemplazo si los necesita.", + "onboarding.welcome.hint": "Puede reconfigurar estas opciones en cualquier momento desde los ajustes del plugin.", + "onboarding.storage.title": "Almacenamiento", + "onboarding.storage.vaultPerCorpus": "Un vault por corpus", + "onboarding.storage.vaultPerCorpusDesc": "Recomendamos usar un vault de Obsidian dedicado por corpus. Esto garantiza que los archivos de correspondencia, los diccionarios y los exportes permanezcan junto a las transcripciones, facilitando compartir o archivar un corpus.", + "onboarding.storage.transcriptionsFolder": "Transcripciones", + "onboarding.storage.mappingFolder": "Tablas de correspondencia", + "onboarding.storage.dictionariesFolder": "Diccionarios", + "onboarding.storage.exportsFolder": "Exportes", + "onboarding.storage.hint": "Estas rutas son relativas a la ra\xEDz del vault. Puede modificarlas m\xE1s tarde en Configuraci\xF3n.", + "onboarding.language.title": "Idioma de la interfaz", + "onboarding.language.hint": "El idioma de la interfaz no afecta al an\xE1lisis de las transcripciones. Las convenciones (Jefferson, ICOR) son independientes del idioma.", + "onboarding.ner.desc": "Un modelo NER (reconocimiento de entidades nombradas) puede detectar autom\xE1ticamente nombres propios, apellidos, lugares e instituciones en las transcripciones \u2014 sin una lista exhaustiva, ejecut\xE1ndose localmente en Obsidian.", + "onboarding.ner.tfjsTitle": "transformers.js \u2014 modelo ONNX integrado", + "onboarding.ner.tfjsBadge": "Python no requerido", + "onboarding.ner.tfjsDesc": "Modelo NER multiling\xFCe ejecutado localmente en el vault. Los archivos WASM (~19 MB) se descargan una vez. El modelo (~66 MB) se descarga en el primer escaneo.", + "onboarding.ner.wasmReady": "Archivos WASM ya instalados", + "onboarding.ner.installBtn": "Instalar y usar transformers.js", + "onboarding.ner.selectedBtn": "Seleccionado", + "onboarding.ner.noneActive": "Desactivado (solo reglas manuales)", + "onboarding.ner.noneSkip": "Omitir \u2014 trabajar\xE9 manualmente", + "onboarding.dict.title": "Diccionarios de candidatos de reemplazo", + "onboarding.dict.desc": "Los diccionarios proporcionan candidatos de reemplazo (ciudades, nombres\u2026) y alimentan la detecci\xF3n autom\xE1tica. Est\xE1n alojados en un repositorio dedicado y se descargan al vault \u2014 ning\xFAn dato de transcripci\xF3n sale de Obsidian.", + "onboarding.dict.catalogueLoading": "Cargando cat\xE1logo\u2026", + "onboarding.dict.catalogueError": "No se puede acceder al cat\xE1logo en l\xEDnea. Compruebe su conexi\xF3n o importe un archivo local a continuaci\xF3n.", + "onboarding.dict.recommended": "Recomendado", + "onboarding.dict.col.dict": "Diccionario", + "onboarding.dict.col.lang": "Idioma", + "onboarding.dict.col.roles": "Roles", + "onboarding.dict.col.size": "Tama\xF1o", + "onboarding.dict.role.detection": "detecci\xF3n", + "onboarding.dict.role.replacement": "reemplazo", + "onboarding.dict.role.classes": "clases", + "onboarding.dict.install": "Instalar", + "onboarding.dict.reinstall": "Reinstalar", + "onboarding.dict.installed": "Instalado", + "onboarding.dict.manualHint": "\xBFYa tiene un archivo .dict.json? Imp\xF3rtelo manualmente:", + "onboarding.dict.importBtn": "Importar un archivo local (.dict.json)", + "onboarding.dict.listTitle": "Diccionarios instalados en este vault ({0}):", + "onboarding.dict.remove": "Eliminar este diccionario", + "onboarding.summary.ner.none": "Desactivado \u2014 solo reglas manuales", + "onboarding.summary.ner.tfjs": "transformers.js \u2014 modelo ONNX (descarga en el primer escaneo)", + "onboarding.nav.cancel": "Cancelar", + "onboarding.nav.back": "Atr\xE1s", + "onboarding.nav.skip": "Omitir este paso", + "onboarding.nav.next": "Siguiente", + "onboarding.nav.start": "Comenzar", + "onboarding.nav.finish": "Empezar a trabajar", + "onboarding.summary.title": "Configuraci\xF3n completa", + "onboarding.summary.intro": "Esto es lo que se ha configurado:", + "onboarding.summary.ner": "Detecci\xF3n NER", + "onboarding.summary.dicts": "Diccionarios", + "onboarding.summary.dicts.count": "{0} archivo(s) en el vault", + "onboarding.summary.hint": "Estos ajustes pueden cambiarse en cualquier momento desde la configuraci\xF3n del plugin: Reconfigurar.", + "settings.heading.general": "General", + "settings.heading.textDetection": "Detecci\xF3n de texto", + "settings.heading.pseudonymization": "Pseudonimizaci\xF3n", + "settings.heading.ner": "Detecci\xF3n autom\xE1tica (NER)", + "settings.heading.storage": "Almacenamiento", + "settings.heading.security": "Seguridad", + "settings.language": "Idioma de la interfaz", + "settings.languageDesc": "No afecta al an\xE1lisis de transcripciones. Las convenciones (Jefferson, ICOR) son independientes del idioma.", + "settings.vaultPerCorpus": "Un vault por corpus", + "settings.vaultPerCorpusDesc": "Recomendamos usar un vault de Obsidian dedicado por corpus. Esto garantiza que los archivos de correspondencia, los diccionarios y los exportes permanezcan junto a las transcripciones.", + "settings.transcriptionsFolder": "Transcripciones importadas", + "settings.transcriptionsFolderDesc": "Carpeta de destino para las transcripciones importadas.", + "settings.mappingFolder": "Tablas de correspondencia", + "settings.mappingFolderDesc": "Ruta relativa en el vault.", + "settings.dictionariesFolder": "Diccionarios", + "settings.dictionariesFolderDesc": "Ruta relativa en el vault.", + "settings.exportsFolder": "Exportes", + "settings.accentSensitive": "Sensible a los acentos", + "settings.preserveCase": "Preservar may\xFAsculas", + "settings.preserveCaseDesc": "Adaptar las may\xFAsculas del reemplazo para que coincidan con la fuente (p. ej. JUAN \u2192 PEDRO, juan \u2192 pedro, Juan \u2192 Pedro).", + "settings.preserveAnalyticNotation": "Preservar notaci\xF3n anal\xEDtica", + "settings.preserveAnalyticNotationDesc": "No reemplazar nunca los s\xEDmbolos de convenci\xF3n anal\xEDtica de tipo Jefferson o ICOR.", + "settings.useMarkerInExport": "A\xF1adir marcador alrededor de los pseud\xF3nimos en el exporte", + "settings.useMarkerInExportDesc": "Permite identificar visualmente los t\xE9rminos pseudonimizados en el archivo exportado.", + "settings.markerOpen": "Marcador de apertura", + "settings.markerOpenDesc": "Ejemplo: {{ \u27E6 [ \xAB", + "settings.markerClose": "Marcador de cierre", + "settings.markerCloseDesc": "Ejemplo: }} \u27E7 ] \xBB", + "settings.nerBackend": "Motor de detecci\xF3n", + "settings.nerBackendDesc": "Backend usado para la detecci\xF3n autom\xE1tica de entidades nombradas identificadoras.", + "settings.nerBackend.none": "Desactivado \u2014 solo reglas manuales", + "settings.nerBackend.tfjs": "transformers.js (modelo ONNX integrado)", + "settings.reconfigure": "Asistente de configuraci\xF3n", + "settings.reconfigureDesc2": "Relanzar el asistente para reconfigurar la detecci\xF3n NER y los diccionarios.", + "settings.reconfigureBtn2": "Reconfigurar\u2026", + "settings.warnIfSyncedFolder": "Avisar si la carpeta est\xE1 sincronizada", + "settings.warnIfSyncedFolderDesc": "Alertar si las tablas de correspondencia est\xE1n en una carpeta Git, iCloud o Synology Drive.", + "settings.caseSensitive": "Sensible a may\xFAsculas", + "settings.caseSensitiveDesc": "Si est\xE1 activado, \xABJuan\xBB y \xABjuan\xBB se tratan como distintos.", + "settings.wholeWordOnly": "Solo palabras completas", + "settings.wholeWordOnlyDesc": "Si est\xE1 activado, solo se reemplazan las ocurrencias de palabras completas." + }; + } +}); + // src/i18n/locales/fr.json var fr_default; var init_fr = __esm({ @@ -598,7 +966,6 @@ var init_fr = __esm({ "panel.corpus.deleteClassConfirm": "Supprimer la classe \xAB {0} \xBB ? Les fichiers \xE0 l'int\xE9rieur seront d\xE9plac\xE9s \xE0 la racine.", "panel.corpus.exportSettings": "Destination des exports finaux", "panel.corpus.exportDest.vault": "Dans le vault", - "panel.corpus.exportDest.nextToSource": "\xC0 c\xF4t\xE9 du fichier source", "panel.corpus.exportDest.external": "Dossier externe", "panel.corpus.exportFolder": "Dossier d'export (vault)", "panel.corpus.exportExternalPath": "Chemin absolu", @@ -617,8 +984,12 @@ var init_fr = __esm({ "panel.exports.exportSrt.desc": "Reconstruit le fichier SRT pseudonymis\xE9 \xE0 partir du Markdown.", "panel.exports.exportCha": "Re-exporter en CHAT (.cha)", "panel.exports.exportCha.desc": "Reconstruit le fichier CHAT pseudonymis\xE9 \xE0 partir du Markdown.", + "panel.exports.exportHtml": "Re-exporter en HTML", + "panel.exports.exportHtml.desc": "Reconstruit le fichier HTML noScribe pseudonymis\xE9 \xE0 partir du Markdown et des timestamps (.words.json).", + "notice.htmlExported": "HTML export\xE9 : {0}", "command.exportAsSrt": "Exporter en SRT", "command.exportAsCha": "Exporter en CHAT", + "command.exportAsHtml": "Exporter en HTML", "panel.ner.scanBtn": "Identifier des candidats", "panel.ner.hint": "Param\xE8tres du scanner de d\xE9tection automatique des entit\xE9s nomm\xE9es.", "panel.ner.threshold": "Seuil de confiance", @@ -763,6 +1134,371 @@ var init_fr = __esm({ } }); +// src/i18n/locales/ru.json +var ru_default; +var init_ru = __esm({ + "src/i18n/locales/ru.json"() { + ru_default = { + "corpus.modal.title": "\u041E\u0440\u0433\u0430\u043D\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u043A\u043E\u0440\u043F\u0443\u0441", + "corpus.modal.hint": "\u041A\u0430\u0436\u0434\u044B\u0439 \u043A\u043B\u0430\u0441\u0441 \u0441\u043E\u0437\u0434\u0430\u0451\u0442 \u0437\u0435\u0440\u043A\u0430\u043B\u044C\u043D\u044B\u0435 \u043F\u0430\u043F\u043A\u0438 \u0434\u043B\u044F \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439, \u0442\u0430\u0431\u043B\u0438\u0446 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F \u0438 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u043E\u0432.", + "corpus.modal.noClasses": "\u041A\u043B\u0430\u0441\u0441\u044B \u043D\u0435 \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0435\u043D\u044B. \u0414\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u043E\u0434\u0438\u043D, \u0447\u0442\u043E\u0431\u044B \u043E\u0440\u0433\u0430\u043D\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043F\u043E \u043F\u0430\u043F\u043A\u0430\u043C.", + "corpus.modal.addClass": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043A\u043B\u0430\u0441\u0441", + "corpus.modal.classNamePlaceholder": "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u043A\u043B\u0430\u0441\u0441\u0430 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u0421\u0435\u0441\u0441\u0438\u044F_\u0410, \u0418\u043D\u0444\u043E\u0440\u043C\u0430\u043D\u0442_1\u2026)", + "corpus.modal.classNameEmpty": "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u043A\u043B\u0430\u0441\u0441\u0430 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C.", + "corpus.modal.classExists": "\u041A\u043B\u0430\u0441\u0441 \u0441 \u0442\u0430\u043A\u0438\u043C \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435\u043C \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442.", + "corpus.modal.deleteConfirm": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043A\u043B\u0430\u0441\u0441 \xAB{0}\xBB? \u041F\u0430\u043F\u043A\u0438 \u0431\u0443\u0434\u0443\u0442 \u0443\u0434\u0430\u043B\u0435\u043D\u044B \u0438\u0437 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430 (\u0444\u0430\u0439\u043B\u044B \u0432\u043D\u0443\u0442\u0440\u0438 \u0431\u0443\u0434\u0443\u0442 \u043F\u0435\u0440\u0435\u043C\u0435\u0449\u0435\u043D\u044B \u0432 \u043A\u043E\u0440\u0437\u0438\u043D\u0443).", + "corpus.modal.files": "{0} \u0444\u0430\u0439\u043B(\u043E\u0432)", + "corpus.modal.close": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C", + "corpus.select.title": "\u0412 \u043A\u0430\u043A\u043E\u0439 \u043A\u043B\u0430\u0441\u0441 \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C?", + "corpus.select.hint": "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043A\u043B\u0430\u0441\u0441 (\u043F\u0430\u043F\u043A\u0443) \u0434\u043B\u044F \u044D\u0442\u043E\u0439 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0438.", + "corpus.select.none": "\u0411\u0435\u0437 \u043A\u043B\u0430\u0441\u0441\u0430 (\u043A\u043E\u0440\u0435\u043D\u044C)", + "corpus.select.confirm": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C", + "quickModal.title": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C", + "quickModal.source": "\u0412\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0435 \u0432\u044B\u0440\u0430\u0436\u0435\u043D\u0438\u0435", + "quickModal.sourceDesc": "\u0417\u0430\u043C\u0435\u043D\u044F\u0435\u043C\u044B\u0439 \u0442\u0435\u0440\u043C\u0438\u043D \u2014 \u043D\u0435\u043B\u044C\u0437\u044F \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C", + "quickModal.replaceBy": "\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u044C \u043D\u0430", + "quickModal.replacementPlaceholder": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C \u0438\u043B\u0438 \u0430\u043D\u0430\u043B\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F", + "quickModal.scope": "\u041E\u0431\u043B\u0430\u0441\u0442\u044C \u0437\u0430\u043C\u0435\u043D\u044B", + "quickModal.scopeFile": "\u0412\u0441\u0435 \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u044F \u0432 \u0444\u0430\u0439\u043B\u0435", + "quickModal.scopeOccurrence": "\u0422\u043E\u043B\u044C\u043A\u043E \u044D\u0442\u043E \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0435", + "quickModal.submit": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C", + "notice.appliedOccurrence": "\u2713 \xAB{0}\xBB \u2192 \xAB{1}\xBB (\u044D\u0442\u043E \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0435)", + "notice.appliedFile": "\u2713 \xAB{0}\xBB \u2192 \xAB{1}\xBB ({2} \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0439)", + "onboarding.dict.importOk": "{0} \u0441\u043B\u043E\u0432\u0430\u0440\u044C \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D", + "onboarding.dict.importOkMany": "{0} \u0441\u043B\u043E\u0432\u0430\u0440\u0435\u0439 \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E", + "onboarding.dict.importErr": "{0} \u0444\u0430\u0439\u043B \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D", + "onboarding.dict.importErrMany": "{0} \u0444\u0430\u0439\u043B\u043E\u0432 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E", + "onboarding.dict.importError": "\u041E\u0448\u0438\u0431\u043A\u0430 \u0438\u043C\u043F\u043E\u0440\u0442\u0430 {0} \u2014 \u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0444\u043E\u0440\u043C\u0430\u0442 .dict.json", + "notice.noActiveFile": "\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430.", + "notice.formatUnsupported": "\u041D\u0435\u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442: .{0}", + "notice.noRules": "\u041D\u0435\u0442 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430.\n\u0421\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0447\u0435\u0440\u0435\u0437 Ctrl+P \u2192 \xAB\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E\xBB.", + "notice.noOccurrences": "\u0412\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0439 \u0434\u043B\u044F \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u043F\u0440\u0430\u0432\u0438\u043B \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.", + "notice.noNerEntities": "NER \u043D\u0435 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0438\u043B \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439.", + "notice.nerEntitiesFound": "\u2713 \u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u043E {0} {1} \u2014 \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u043E \u0441\u0438\u043D\u0438\u043C.\n\u041F\u0440\u0430\u0432\u043E\u0439 \u043A\u043D\u043E\u043F\u043A\u043E\u0439 \u043C\u044B\u0448\u0438 \u2014 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E.", + "notice.nerEntitiesFound.entity": "\u0441\u0443\u0449\u043D\u043E\u0441\u0442\u044C", + "notice.nerEntitiesFound.entities": "\u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439", + "notice.nerNotEnabled": "\u0414\u0435\u0442\u0435\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 transformers.js \u043D\u0435 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E.\n\u0412\u043A\u043B\u044E\u0447\u0438\u0442\u0435 \u0432 \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u2192 Pseudonymizer Tool.", + "notice.noDictDetection": "\u0421\u043B\u043E\u0432\u0430\u0440\u044C \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D.\n\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0441\u043B\u043E\u0432\u0430\u0440\u044C \u0432 \u043F\u0430\u043D\u0435\u043B\u0438 \xAB\u0421\u043B\u043E\u0432\u0430\u0440\u0438\xBB.", + "notice.noDictEntities": "\u0421\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439 \u0432 \u0441\u043B\u043E\u0432\u0430\u0440\u044F\u0445 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.", + "notice.noDictReplacements": "\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0445 \u0437\u0430\u043C\u0435\u043D \u0434\u043B\u044F \u043D\u0430\u0439\u0434\u0435\u043D\u043D\u044B\u0445 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439.", + "notice.ruleDeleted": "\u2713 \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0443\u0434\u0430\u043B\u0435\u043D\u043E \u2014 \xAB{0}\xBB \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u0432 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u043C \u0444\u0430\u0439\u043B\u0435", + "notice.ruleCreated": "\u2713 \u041F\u0440\u0430\u0432\u0438\u043B\u043E \u0441\u043E\u0437\u0434\u0430\u043D\u043E: \xAB{0}\xBB \u2192 \xAB{1}\xBB", + "notice.rulesCreated": "\u2713 {0} {1} \u0441\u043E\u0437\u0434\u0430\u043D\u043E", + "notice.rulesCreated.rule": "\u043F\u0440\u0430\u0432\u0438\u043B\u043E", + "notice.rulesCreated.rules": "\u043F\u0440\u0430\u0432\u0438\u043B", + "notice.occurrencesPseudonymized": "\u2713 {0} {1} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E \u0432 \xAB{2}\xBB", + "notice.occurrencesPseudonymized.occurrence": "\u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0435", + "notice.occurrencesPseudonymized.occurrences": "\u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0439", + "notice.converted": "\u2713 {0} \u2192 {1}", + "notice.conversionError": "\u041E\u0448\u0438\u0431\u043A\u0430 \u043A\u043E\u043D\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 {0}: {1}", + "notice.fileExists": "\u0424\u0430\u0439\u043B \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: {0}", + "notice.conversionSkipped": "\u26A0 {0}.md \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u2014 \u043A\u043E\u043D\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u044F \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u0430 \u0434\u043B\u044F {1}", + "notice.exportDone": "\u2713 {0} \u043F\u0440\u0430\u0432\u0438\u043B(\u043E) \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E\n\u2192 {1}", + "notice.mappingExported": "\u2713 \u0422\u0430\u0431\u043B\u0438\u0446\u0430 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0430 \u2192 {0}", + "notice.noMapping": "\u0422\u0430\u0431\u043B\u0438\u0446\u0430 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F \u0434\u043B\u044F {0} \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430", + "notice.invalidFormat": "\u041D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442: {0}", + "notice.dictImported": "\u2713 {0} {1} \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E", + "notice.dictImported.one": "\u0441\u043B\u043E\u0432\u0430\u0440\u044C", + "notice.dictImported.many": "\u0441\u043B\u043E\u0432\u0430\u0440\u0435\u0439", + "notice.dictRejected": "{0} {1} \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E", + "notice.dictRejected.one": "\u0444\u0430\u0439\u043B", + "notice.dictRejected.many": "\u0444\u0430\u0439\u043B\u043E\u0432", + "notice.nerModelLoaded": "\u2713 \u041C\u043E\u0434\u0435\u043B\u044C NER \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u0430", + "notice.nerModelLoading": "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043C\u043E\u0434\u0435\u043B\u0438 NER (\u043F\u0435\u0440\u0432\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A \u2014 ~66 \u041C\u0411)\u2026", + "notice.ruleNotFound": "\u041F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0432 \u0442\u0430\u0431\u043B\u0438\u0446\u0430\u0445 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F.", + "notice.noCheckedDicts": "\u041D\u0438 \u043E\u0434\u0438\u043D \u0441\u043B\u043E\u0432\u0430\u0440\u044C \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D.", + "notice.audioImported": "\u0410\u0443\u0434\u0438\u043E \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E: {0}", + "occurrencesCtx.hint": "{0} \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438{1} \u2014 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043D\u0443\u0436\u043D\u044B\u0435 \u0434\u043B\u044F \u0437\u0430\u043C\u0435\u043D\u044B.", + "occurrencesCtx.hint.s": "\u0439", + "occurrencesCtx.hint.empty": "\u0435", + "occurrencesCtx.validateAll": "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435", + "occurrencesCtx.ignoreAll": "\u041F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435", + "occurrencesCtx.cancel": "\u041E\u0442\u043C\u0435\u043D\u0430", + "occurrencesCtx.confirm": "\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0432\u044B\u0431\u043E\u0440", + "occurrencesCtx.btn.validate": "\u0412\u044B\u0431\u0440\u0430\u0442\u044C", + "occurrencesCtx.btn.ignore": "\u041F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C", + "occurrencesCtx.btn.fp": "\u041B\u043E\u0436\u043D\u043E\u0435 \u0441\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u043D\u0438\u0435", + "occurrencesCtx.status.ignored": "\u041E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E \u043A\u0430\u043A \u0435\u0441\u0442\u044C", + "occurrencesCtx.status.fp": "\u041B\u043E\u0436\u043D\u043E\u0435 \u0441\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u043D\u0438\u0435 \u2014 \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u043E", + "notice.notNoScribeFormat": "\u042D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0435\u0439 noScribe (\u043E\u0436\u0438\u0434\u0430\u0435\u0442\u0441\u044F pseudobs-format: vtt \u0438\u043B\u0438 html).", + "notice.wordsJsonMissing": "\u0424\u0430\u0439\u043B \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445 \u043C\u0435\u0442\u043E\u043A \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D: {0}.words.json", + "notice.vttMismatch": "\u041F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435: \u043A\u043E\u043B\u0438\u0447\u0435\u0441\u0442\u0432\u043E \u0440\u0435\u043F\u043B\u0438\u043A \u0432 Markdown \u0438 words.json \u0440\u0430\u0437\u043B\u0438\u0447\u0430\u0435\u0442\u0441\u044F \u2014 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 VTT \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043D\u0435\u043F\u043E\u043B\u043D\u044B\u043C.", + "notice.vttExported": "VTT \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D: {0}", + "command.organizeCorpus": "\u041E\u0440\u0433\u0430\u043D\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u043A\u043E\u0440\u043F\u0443\u0441", + "command.addTranscription": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u044E", + "command.pseudonymizeFile": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0444\u0430\u0439\u043B", + "command.createRule": "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u0437\u0430\u043C\u0435\u043D\u044B", + "command.scanFile": "\u0421\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0444\u0430\u0439\u043B", + "command.scanNer": "\u0421\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0430\u0439\u043B \u0441 NER-\u0434\u0435\u0442\u0435\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435\u043C", + "command.scanDictionaries": "\u0421\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0430\u0439\u043B \u043F\u043E \u0441\u043B\u043E\u0432\u0430\u0440\u044F\u043C", + "command.pseudonymizeSelection": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u0438\u0435", + "command.exportAsVtt": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A VTT", + "command.exportAsSrt": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A SRT", + "command.exportAsCha": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A CHAT", + "command.exportAsHtml": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A HTML", + "command.openPanel": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0430\u0446\u0438\u044F: \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u043F\u0430\u043D\u0435\u043B\u044C", + "contextMenu.declareException": "\u041E\u0431\u044A\u044F\u0432\u0438\u0442\u044C \xAB{0}\xBB \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435\u043C \u0437\u0434\u0435\u0441\u044C", + "contextMenu.cancelPseudonymization": "\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0430\u0446\u0438\u044E \xAB{0}\xBB", + "contextMenu.editRule": "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u0434\u043B\u044F \xAB{0}\xBB", + "contextMenu.pseudonymize": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \xAB{0}\xBB", + "contextMenu.redact": "\u0421\u043A\u0440\u044B\u0442\u044C \xAB{0}\xBB", + "contextMenu.coulmont": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441 \u043F\u0440\u043E\u0444. \u0411\u0430\u0442\u0438\u0441\u0442\u043E\u043C \u041A\u0443\u043B\u044C\u043C\u043E\u043D\u043E\u043C", + "contextMenu.createRule": "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u0437\u0430\u043C\u0435\u043D\u044B\u2026", + "redaction.checkbox": "\u0421\u043A\u0440\u044B\u0442\u044C (\u{1F02B})", + "redaction.checkboxDesc": "\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u044C \u0441\u0438\u043C\u0432\u043E\u043B\u0430\u043C\u0438 \u{1F02B} (\u043F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u043D\u0430 \u0441\u043B\u043E\u0433) \u2014 \u0434\u043B\u044F \u043D\u0435\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0433\u043E \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u044E\u0449\u0435\u0433\u043E \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u044F", + "ruleModal.delete": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E", + "ruleModal.scopeWarnTitle": "\u041B\u043E\u043D\u0433\u0438\u0442\u044E\u0434\u043D\u0430\u044F \u043E\u0431\u043B\u0430\u0441\u0442\u044C", + "ruleModal.scopeOkTitle": "\u041E\u0431\u043B\u0430\u0441\u0442\u044C \u0444\u0430\u0439\u043B\u0430", + "ruleModal.scopeOk": "\u0425\u043E\u0440\u043E\u0448\u0430\u044F \u043F\u0440\u0430\u043A\u0442\u0438\u043A\u0430. \u042D\u0442\u043E\u0442 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C \u0431\u0443\u0434\u0435\u0442 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u044C\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043A \u044D\u0442\u043E\u0439 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0438 \u2014 \u043A\u0430\u0436\u0434\u044B\u0439 \u0444\u0430\u0439\u043B \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u044B\u043C.", + "ruleModal.title": "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u0437\u0430\u043C\u0435\u043D\u044B", + "ruleModal.source": "\u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A", + "ruleModal.sourceDesc": "\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0442\u0435\u0440\u043C\u0438\u043D \u0434\u043B\u044F \u0437\u0430\u043C\u0435\u043D\u044B", + "ruleModal.replacement": "\u0417\u0430\u043C\u0435\u043D\u0430", + "ruleModal.replacementDesc": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C \u0438\u043B\u0438 \u0430\u043D\u0430\u043B\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0430\u044F \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F", + "ruleModal.replacementDescClass": "\u0422\u043E\u0447\u043D\u044B\u0439 \u0438\u043D\u0434\u0435\u043A\u0441 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u0430\u043D \u043F\u0440\u0438 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0438 \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0445 \u043F\u0440\u0430\u0432\u0438\u043B \u0432 \u043E\u0431\u043B\u0430\u0441\u0442\u0438.", + "ruleModal.category": "\u041A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F", + "ruleModal.scope": "\u041E\u0431\u043B\u0430\u0441\u0442\u044C", + "ruleModal.scopeFile": "\u0422\u043E\u043B\u044C\u043A\u043E \u044D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B", + "ruleModal.scopeFolder": "\u042D\u0442\u0430 \u043F\u0430\u043F\u043A\u0430", + "ruleModal.scopeVault": "\u0412\u0441\u0451 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", + "ruleModal.priority": "\u041F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442", + "ruleModal.priorityDesc": "\u0426\u0435\u043B\u043E\u0435 \u0447\u0438\u0441\u043B\u043E, \u043A\u0430\u043A CSS z-index \u2014 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E 0, \u0447\u0435\u043C \u0432\u044B\u0448\u0435 \u2014 \u0442\u0435\u043C \u0440\u0430\u043D\u044C\u0448\u0435 \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F", + "ruleModal.submit": "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E", + "ruleModal.errorMissing": "\u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A \u0438 \u0437\u0430\u043C\u0435\u043D\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u044B.", + "ruleModal.errorNoFile": "\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430.", + "ruleModal.coulomontLabel": "\u041F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u044D\u043A\u0432\u0438\u0432\u0430\u043B\u0435\u043D\u0442\u043D\u044B\u0445 \u0438\u043C\u0451\u043D \u2014 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435:", + "ruleModal.dictLabel": "\u0421\u043B\u043E\u0432\u0430\u0440\u044C: \xAB{0}\xBB \u2192 \u043A\u043B\u0430\u0441\u0441 {1}", + "ruleModal.dictUseClass": "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \xAB{0}\xBB (\u043E\u0431\u043B\u0430\u0441\u0442\u044C: {1})", + "category.first_name": "\u0418\u043C\u044F", + "category.last_name": "\u0424\u0430\u043C\u0438\u043B\u0438\u044F", + "category.full_name": "\u041F\u043E\u043B\u043D\u043E\u0435 \u0438\u043C\u044F", + "category.place": "\u041C\u0435\u0441\u0442\u043E", + "category.institution": "\u0423\u0447\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u0435", + "category.date": "\u0414\u0430\u0442\u0430", + "category.age": "\u0412\u043E\u0437\u0440\u0430\u0441\u0442", + "category.profession": "\u041F\u0440\u043E\u0444\u0435\u0441\u0441\u0438\u044F", + "category.custom": "\u0414\u0440\u0443\u0433\u043E\u0435", + "scope.file": "\u0424\u0430\u0439\u043B", + "scope.folder": "\u041F\u0430\u043F\u043A\u0430", + "scope.vault": "\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", + "status.validated": "\u0410\u043A\u0442\u0438\u0432\u043D\u043E", + "status.ignored": "\u0418\u0433\u043D\u043E\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044F", + "status.partial": "\u0427\u0430\u0441\u0442\u0438\u0447\u043D\u043E", + "status.suggested": "\u041F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u043E", + "status.conflict": "\u041A\u043E\u043D\u0444\u043B\u0438\u043A\u0442", + "status.disabled": "\u041D\u0435\u0430\u043A\u0442\u0438\u0432\u043D\u043E", + "status.needs_review": "\u041D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0435", + "panel.tab.mappings": "\u0421\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F", + "panel.tab.dictionaries": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438", + "panel.tab.exports": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u044B", + "panel.tab.ner": "NER", + "panel.tab.corpus": "\u041A\u043E\u0440\u043F\u0443\u0441", + "panel.mappings.filterActive": "\u0422\u043E\u043B\u044C\u043A\u043E \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0444\u0430\u0439\u043B", + "panel.mappings.warnBroadName": "\u26A0 \u0420\u0430\u0441\u043F\u0440\u043E\u0441\u0442\u0440\u0430\u043D\u0435\u043D\u0438\u0435 \u0438\u043C\u0435\u043D\u0438 \u0438\u043B\u0438 \u0444\u0430\u043C\u0438\u043B\u0438\u0438 \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u0444\u0430\u0439\u043B\u0430 \u0441\u043B\u0435\u0434\u0443\u0435\u0442 \u0434\u0435\u043B\u0430\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u0430\u043A \u043E\u0431\u0434\u0443\u043C\u0430\u043D\u043D\u044B\u0439 \u0430\u043D\u0430\u043B\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u0432\u044B\u0431\u043E\u0440 \u2014 \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u0434\u043B\u044F \u043F\u043E\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u043D\u043E\u0433\u043E \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u043D\u0438\u044F \u043E\u0434\u043D\u043E\u0433\u043E \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 \u0432 \u043B\u043E\u043D\u0433\u0438\u0442\u044E\u0434\u043D\u043E\u043C \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u043D\u0438\u0438. \u0414\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043E\u0434\u0438\u043D \u0438 \u0442\u043E\u0442 \u0436\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u0432 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u0438\u0445 \u0444\u0430\u0439\u043B\u0430\u0445, \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0430\u0446\u0438\u044F \u043D\u0430 \u0443\u0440\u043E\u0432\u043D\u0435 \u0444\u0430\u0439\u043B\u0430 \u043F\u0440\u0435\u0434\u043F\u043E\u0447\u0442\u0438\u0442\u0435\u043B\u044C\u043D\u0435\u0435: \u043E\u043D\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u0442 \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u044C \u043A\u0430\u0436\u0434\u043E\u0439 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0438 \u0438 \u0441\u043D\u0438\u0436\u0430\u0435\u0442 \u0440\u0438\u0441\u043A \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 \u043F\u0435\u0440\u0435\u043A\u0440\u0451\u0441\u0442\u043D\u044B\u0439 \u0430\u043D\u0430\u043B\u0438\u0437.", + "panel.mappings.addRule": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E", + "panel.mappings.scanFile": "\u0421\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0430\u0439\u043B", + "panel.mappings.scanning": "\u0421\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435\u2026", + "panel.mappings.noFile": "\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430.", + "panel.mappings.noRules": "\u041D\u0435\u0442 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F {0}.", + "panel.mappings.noRulesHint": "\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430. \u0421\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0447\u0435\u0440\u0435\u0437 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u043E\u0435 \u043C\u0435\u043D\u044E \u0438\u043B\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \xAB\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0440\u0430\u0432\u0438\u043B\u043E\xBB.", + "panel.mappings.col.source": "\u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A", + "panel.mappings.col.replacement": "\u0417\u0430\u043C\u0435\u043D\u0430", + "panel.mappings.col.category": "\u041A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F", + "panel.mappings.col.scope": "\u041E\u0431\u043B\u0430\u0441\u0442\u044C", + "panel.mappings.col.priority": "\u041F.", + "panel.mappings.col.status": "\u0421\u0442\u0430\u0442\u0443\u0441", + "panel.mappings.exceptions": "\u0418\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F", + "panel.mappings.exceptions.hint": "\u0412\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u044F, \u044F\u0432\u043D\u043E \u043F\u0440\u043E\u0438\u0433\u043D\u043E\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0430.", + "panel.mappings.exceptions.delete": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u0442\u043E \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435", + "panel.dict.noneInstalled": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B. \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0447\u0435\u0440\u0435\u0437 \u043C\u0430\u0441\u0442\u0435\u0440 (\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u2192 \u041F\u0435\u0440\u0435\u043D\u0430\u0441\u0442\u0440\u043E\u0438\u0442\u044C) \u0438\u043B\u0438 \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0444\u0430\u0439\u043B.", + "panel.dict.checkbox": "\u0412\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0432 \u0433\u0440\u0443\u043F\u043F\u043E\u0432\u043E\u0435 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435", + "panel.dict.scanAll": "\u0421\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043E\u0442\u043C\u0435\u0447\u0435\u043D\u043D\u044B\u0435 \u0441\u043B\u043E\u0432\u0430\u0440\u0438", + "panel.dict.importLocal": "\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0444\u0430\u0439\u043B (.dict.json)", + "panel.dict.scanWith": "\u0421\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439 \u0444\u0430\u0439\u043B \u043F\u043E \xAB{0}\xBB", + "panel.dict.remove": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u0441\u043B\u043E\u0432\u0430\u0440\u044C", + "panel.corpus.noFolder": "\u041F\u0430\u043F\u043A\u0430 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430. \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u0442\u0435 \u0435\u0451 \u0432 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u0445.", + "panel.corpus.empty": "\u0412 \u044D\u0442\u043E\u0439 \u043F\u0430\u043F\u043A\u0435 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.", + "panel.corpus.rules": "{0} \u043F\u0440\u0430\u0432\u0438\u043B(\u043E)", + "panel.corpus.hasPseudo": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u0441\u043E\u0437\u0434\u0430\u043D\u0430", + "panel.corpus.noPseudo": "\u0415\u0449\u0451 \u043D\u0435 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043E", + "panel.corpus.hasFinal": "\u0424\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0439 \u044D\u043A\u0441\u043F\u043E\u0440\u0442 {0} \u043F\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442", + "panel.corpus.noFinal": "\u041D\u0435\u0442 \u0444\u0438\u043D\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0430", + "panel.corpus.addClass": "\u041D\u043E\u0432\u044B\u0439 \u043A\u043B\u0430\u0441\u0441", + "panel.corpus.noClass": "\u0411\u0435\u0437 \u043A\u043B\u0430\u0441\u0441\u0430", + "panel.corpus.moveTo": "\u041F\u0435\u0440\u0435\u043C\u0435\u0441\u0442\u0438\u0442\u044C \u0432\u2026", + "panel.corpus.moveToRoot": "\u0411\u0435\u0437 \u043A\u043B\u0430\u0441\u0441\u0430 (\u043A\u043E\u0440\u0435\u043D\u044C)", + "panel.corpus.deleteClass": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043A\u043B\u0430\u0441\u0441", + "panel.corpus.deleteClassConfirm": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u043A\u043B\u0430\u0441\u0441 \xAB{0}\xBB? \u0424\u0430\u0439\u043B\u044B \u0432\u043D\u0443\u0442\u0440\u0438 \u0431\u0443\u0434\u0443\u0442 \u043F\u0435\u0440\u0435\u043C\u0435\u0449\u0435\u043D\u044B \u0432 \u043A\u043E\u0440\u0435\u043D\u044C.", + "panel.corpus.exportSettings": "\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0444\u0438\u043D\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0430", + "panel.corpus.exportDest.vault": "\u0412 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", + "panel.corpus.exportDest.external": "\u0412\u043D\u0435\u0448\u043D\u044F\u044F \u043F\u0430\u043F\u043A\u0430", + "panel.corpus.exportFolder": "\u041F\u0430\u043F\u043A\u0430 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0430 (\u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435)", + "panel.corpus.exportExternalPath": "\u0410\u0431\u0441\u043E\u043B\u044E\u0442\u043D\u044B\u0439 \u043F\u0443\u0442\u044C", + "panel.corpus.exportExternalPathPlaceholder": "/Volumes/Archives/Exports", + "panel.corpus.exportMirrorClasses": "\u0417\u0435\u0440\u043A\u0430\u043B\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043A\u043B\u0430\u0441\u0441\u043E\u0432", + "panel.filenameWarning.msg": "\u0418\u043C\u044F \u0444\u0430\u0439\u043B\u0430 \xAB{0}\xBB \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u0443\u0435\u043C\u044B\u0439 \u0442\u0435\u0440\u043C\u0438\u043D.", + "panel.filenameWarning.edit": "\u041F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C\u2026", + "panel.filenameWarning.editTitle": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043C\u044F \u0432\u0440\u0443\u0447\u043D\u0443\u044E", + "panel.filenameWarning.editPrompt": "\u041D\u043E\u0432\u043E\u0435 \u0438\u043C\u044F \u0434\u043B\u044F \xAB{0}\xBB", + "panel.filenameWarning.wandTitle": "\u041F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C \u0432 \xAB{0}\xBB", + "panel.exports.pseudonymize": "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E", + "panel.exports.exportMapping": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0442\u0430\u0431\u043B\u0438\u0446\u0443 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F", + "panel.exports.exportVtt": "\u041F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A VTT", + "panel.exports.exportVtt.desc": "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 WebVTT \u0438\u0437 Markdown \u0438 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445 \u043C\u0435\u0442\u043E\u043A (.words.json).", + "panel.exports.exportSrt": "\u041F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A SRT", + "panel.exports.exportSrt.desc": "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 SRT \u0438\u0437 Markdown.", + "panel.exports.exportCha": "\u041F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A CHAT (.cha)", + "panel.exports.exportCha.desc": "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 CHAT \u0438\u0437 Markdown.", + "panel.exports.exportHtml": "\u041F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043A\u0430\u043A HTML", + "panel.exports.exportHtml.desc": "\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0435\u0442 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0439 HTML noScribe \u0438\u0437 Markdown \u0438 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445 \u043C\u0435\u0442\u043E\u043A (.words.json).", + "notice.htmlExported": "HTML \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D: {0}", + "panel.ner.scanBtn": "\u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u043E\u0432", + "panel.ner.hint": "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u043E\u0433\u043E \u0441\u043A\u0430\u043D\u0435\u0440\u0430 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u044F \u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439.", + "panel.ner.threshold": "\u041F\u043E\u0440\u043E\u0433 \u0443\u0432\u0435\u0440\u0435\u043D\u043D\u043E\u0441\u0442\u0438", + "panel.ner.thresholdDesc": "\u0421\u0443\u0449\u043D\u043E\u0441\u0442\u0438 \u0441 \u043E\u0446\u0435\u043D\u043A\u043E\u0439 \u043D\u0438\u0436\u0435 \u044D\u0442\u043E\u0433\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0438\u0433\u043D\u043E\u0440\u0438\u0440\u0443\u044E\u0442\u0441\u044F. \u0412\u044B\u0448\u0435 = \u043C\u0435\u043D\u044C\u0448\u0435 \u043B\u043E\u0436\u043D\u044B\u0445 \u0441\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u043D\u0438\u0439, \u043D\u043E \u043C\u0435\u043D\u044C\u0448\u0435 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u0439.", + "panel.ner.functionWords": "\u0418\u0441\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u0435 \u0441\u043B\u0443\u0436\u0435\u0431\u043D\u044B\u0435 \u0441\u043B\u043E\u0432\u0430", + "panel.ner.functionWordsDesc": "\u041F\u043E \u043E\u0434\u043D\u043E\u043C\u0443 \u0441\u043B\u043E\u0432\u0443 \u043D\u0430 \u0441\u0442\u0440\u043E\u043A\u0443. \u042D\u0442\u0438 \u0442\u043E\u043A\u0435\u043D\u044B \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0431\u0443\u0434\u0443\u0442 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B \u043A\u0430\u043A \u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0438, \u0434\u0430\u0436\u0435 \u0435\u0441\u043B\u0438 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u044B \u043C\u043E\u0434\u0435\u043B\u044C\u044E.", + "panel.ner.save": "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C", + "panel.ner.saved": "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E", + "panel.ner.reset": "\u0421\u0431\u0440\u043E\u0441\u0438\u0442\u044C \u0434\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0439 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E", + "dictScanModal.title": "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u043F\u043E \u0441\u043B\u043E\u0432\u0430\u0440\u044E", + "dictScanModal.summary": "{0} {1} \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u043E \xB7 {2}", + "dictScanModal.summary.entity": "\u0441\u0443\u0449\u043D\u043E\u0441\u0442\u044C", + "dictScanModal.summary.entities": "\u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439", + "dictScanModal.hint": "\u0421\u043D\u0438\u043C\u0438\u0442\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0441 \u043B\u043E\u0436\u043D\u044B\u0445 \u0441\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u043D\u0438\u0439. \u041F\u0440\u0435\u0444\u0438\u043A\u0441 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F; \u0438\u043D\u0434\u0435\u043A\u0441 \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438.", + "dictScanModal.apply": "\u0421\u043E\u0437\u0434\u0430\u0442\u044C {0} {1}", + "dictScanModal.apply.rule": "\u043F\u0440\u0430\u0432\u0438\u043B\u043E", + "dictScanModal.apply.rules": "\u043F\u0440\u0430\u0432\u0438\u043B", + "dictScanModal.noRules": "\u041D\u0435\u0442 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u0438\u044F", + "dictScanModal.cancel": "\u041E\u0442\u043C\u0435\u043D\u0430", + "mappingScanModal.title": "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0439", + "mappingScanModal.summary": "{0} {1} \u0441 \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u044F\u043C\u0438 \u0432 \xAB{2}\xBB", + "mappingScanModal.summary.rule": "\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E", + "mappingScanModal.summary.rules": "\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u043F\u0440\u0430\u0432\u0438\u043B", + "mappingScanModal.hint": "\u0421\u043D\u0438\u043C\u0438\u0442\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0438 \u0441 \u043F\u0440\u0430\u0432\u0438\u043B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043D\u0435 \u043D\u0443\u0436\u043D\u043E \u043F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u044C. \u0417\u0430\u043C\u0435\u043D\u044B \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u044B \u0432 \u0438\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u0444\u0430\u0439\u043B.", + "mappingScanModal.col.source": "", + "mappingScanModal.col.replacement": "\u0417\u0430\u043C\u0435\u043D\u0430", + "mappingScanModal.col.occurrences": "\u0412\u0445\u043E\u0436\u0434.", + "mappingScanModal.apply": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C ({0} {1}, {2} {3})", + "mappingScanModal.apply.rule": "\u043F\u0440\u0430\u0432\u0438\u043B\u043E", + "mappingScanModal.apply.rules": "\u043F\u0440\u0430\u0432\u0438\u043B", + "mappingScanModal.apply.occurrence": "\u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0435", + "mappingScanModal.apply.occurrences": "\u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0439", + "mappingScanModal.noRules": "\u041D\u0435\u0442 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u0438\u044F", + "mappingScanModal.cancel": "\u041E\u0442\u043C\u0435\u043D\u0430", + "mappingScanModal.saveExceptions": "\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0438\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F", + "mappingScanModal.exceptionsSaved": "\u0418\u0441\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u044B \u0432 \u0442\u0430\u0431\u043B\u0438\u0446\u0443 \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F.", + "mappingScanModal.countTooltip": "\u041F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0438 \u0432\u044B\u0431\u043E\u0440 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u043E\u0432", + "onboarding.step.storage": "\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", + "onboarding.step.language": "\u042F\u0437\u044B\u043A", + "onboarding.step.welcome": "\u0414\u043E\u0431\u0440\u043E \u043F\u043E\u0436\u0430\u043B\u043E\u0432\u0430\u0442\u044C", + "onboarding.step.ner": "\u0414\u0435\u0442\u0435\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 NER", + "onboarding.step.dictionaries": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438", + "onboarding.step.summary": "\u0418\u0442\u043E\u0433\u0438", + "onboarding.welcome.title": "Pseudonymizer Tool", + "onboarding.welcome.desc": "\u042D\u0442\u043E\u0442 \u043F\u043B\u0430\u0433\u0438\u043D \u043F\u043E\u043C\u043E\u0433\u0430\u0435\u0442 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0438 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0438 \u0438\u043D\u0442\u0435\u0440\u0432\u044C\u044E \u0438 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0446\u0438\u043E\u043D\u0430\u043B\u044C\u043D\u044B\u0445 \u043A\u043E\u0440\u043F\u0443\u0441\u043E\u0432 (\u043A\u043E\u043D\u0432\u0435\u043D\u0446\u0438\u0438 Jefferson, ICOR, \u0444\u043E\u0440\u043C\u0430\u0442\u044B .srt, .cha).", + "onboarding.welcome.steps": "\u041C\u0430\u0441\u0442\u0435\u0440 \u043F\u0440\u043E\u0432\u0435\u0434\u0451\u0442 \u0432\u0430\u0441 \u0447\u0435\u0440\u0435\u0437 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0431\u044B\u0441\u0442\u0440\u044B\u0445 \u0448\u0430\u0433\u043E\u0432:", + "onboarding.welcome.step1": "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u044F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430.", + "onboarding.welcome.step2": "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u0432\u0438\u0436\u043E\u043A \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u043E\u0433\u043E \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u044F \u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439 (\u043D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E).", + "onboarding.welcome.step3": "\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0441\u043B\u043E\u0432\u0430\u0440\u0438 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u043E\u0432 \u0434\u043B\u044F \u0437\u0430\u043C\u0435\u043D\u044B, \u0435\u0441\u043B\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E.", + "onboarding.welcome.hint": "\u042D\u0442\u0438 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u043C\u043E\u0436\u043D\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0432 \u043B\u044E\u0431\u043E\u0435 \u0432\u0440\u0435\u043C\u044F \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043F\u043B\u0430\u0433\u0438\u043D\u0430.", + "onboarding.storage.title": "\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", + "onboarding.storage.vaultPerCorpus": "\u041E\u0434\u043D\u043E \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0430 \u043A\u043E\u0440\u043F\u0443\u0441", + "onboarding.storage.vaultPerCorpusDesc": "\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u043C \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0435 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 Obsidian \u0434\u043B\u044F \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u043A\u043E\u0440\u043F\u0443\u0441\u0430. \u042D\u0442\u043E \u043F\u043E\u0437\u0432\u043E\u043B\u044F\u0435\u0442 \u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0442\u0430\u0431\u043B\u0438\u0446\u044B \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F, \u0441\u043B\u043E\u0432\u0430\u0440\u0438 \u0438 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u044B \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u044F\u043C\u0438, \u0443\u043F\u0440\u043E\u0449\u0430\u044F \u043E\u0431\u043C\u0435\u043D \u0438 \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043A\u043E\u0440\u043F\u0443\u0441\u0430.", + "onboarding.storage.transcriptionsFolder": "\u041F\u0430\u043F\u043A\u0430 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439", + "onboarding.storage.mappingFolder": "\u0422\u0430\u0431\u043B\u0438\u0446\u044B \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F", + "onboarding.storage.dictionariesFolder": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438", + "onboarding.storage.exportsFolder": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u044B", + "onboarding.storage.hint": "\u042D\u0442\u0438 \u043F\u0443\u0442\u0438 \u0443\u043A\u0430\u0437\u0430\u043D\u044B \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043A\u043E\u0440\u043D\u044F \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0430. \u0418\u0445 \u043C\u043E\u0436\u043D\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u043F\u043E\u0437\u0436\u0435 \u0432 \u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445.", + "onboarding.language.title": "\u042F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430", + "onboarding.language.hint": "\u042F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043D\u0435 \u0432\u043B\u0438\u044F\u0435\u0442 \u043D\u0430 \u0430\u043D\u0430\u043B\u0438\u0437 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439. \u041A\u043E\u043D\u0432\u0435\u043D\u0446\u0438\u0438 (Jefferson, ICOR) \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u044F\u0442 \u043E\u0442 \u044F\u0437\u044B\u043A\u0430.", + "onboarding.ner.desc": "\u041C\u043E\u0434\u0435\u043B\u044C NER (\u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439) \u043C\u043E\u0436\u0435\u0442 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u0438 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0442\u044C \u0438\u043C\u0435\u043D\u0430, \u0444\u0430\u043C\u0438\u043B\u0438\u0438, \u043C\u0435\u0441\u0442\u0430 \u0438 \u0443\u0447\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u044F \u0432 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u044F\u0445 \u2014 \u0431\u0435\u0437 \u0438\u0441\u0447\u0435\u0440\u043F\u044B\u0432\u0430\u044E\u0449\u0435\u0433\u043E \u0441\u043F\u0438\u0441\u043A\u0430, \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E \u0432 Obsidian.", + "onboarding.ner.tfjsTitle": "transformers.js \u2014 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C ONNX", + "onboarding.ner.tfjsBadge": "Python \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F", + "onboarding.ner.tfjsDesc": "\u041C\u043D\u043E\u0433\u043E\u044F\u0437\u044B\u0447\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C NER, \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0430\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435. WASM-\u0444\u0430\u0439\u043B\u044B (~19 \u041C\u0411) \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044E\u0442\u0441\u044F \u043E\u0434\u0438\u043D \u0440\u0430\u0437. \u041C\u043E\u0434\u0435\u043B\u044C (~66 \u041C\u0411) \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u043F\u0435\u0440\u0432\u043E\u043C \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0438.", + "onboarding.ner.wasmReady": "WASM-\u0444\u0430\u0439\u043B\u044B \u0443\u0436\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B", + "onboarding.ner.installBtn": "\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C transformers.js", + "onboarding.ner.selectedBtn": "\u0412\u044B\u0431\u0440\u0430\u043D\u043E", + "onboarding.ner.noneActive": "\u041E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u043E (\u0442\u043E\u043B\u044C\u043A\u043E \u0440\u0443\u0447\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430)", + "onboarding.ner.noneSkip": "\u041F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u2014 \u0431\u0443\u0434\u0443 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E", + "onboarding.dict.title": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u043E\u0432 \u0434\u043B\u044F \u0437\u0430\u043C\u0435\u043D\u044B", + "onboarding.dict.desc": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442 \u043A\u0430\u043D\u0434\u0438\u0434\u0430\u0442\u043E\u0432 \u0434\u043B\u044F \u0437\u0430\u043C\u0435\u043D\u044B (\u0433\u043E\u0440\u043E\u0434\u0430, \u0438\u043C\u0435\u043D\u0430\u2026) \u0438 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044E\u0442 \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u043E\u0435 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u0435. \u041E\u043D\u0438 \u0440\u0430\u0437\u043C\u0435\u0449\u0435\u043D\u044B \u0432 \u0441\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044E\u0442\u0441\u044F \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u2014 \u0434\u0430\u043D\u043D\u044B\u0435 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439 \u043D\u0435 \u043F\u043E\u043A\u0438\u0434\u0430\u044E\u0442 Obsidian.", + "onboarding.dict.catalogueLoading": "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0430\u2026", + "onboarding.dict.catalogueError": "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C\u0441\u044F \u043A \u043E\u043D\u043B\u0430\u0439\u043D-\u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0443. \u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0438\u043B\u0438 \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0444\u0430\u0439\u043B \u043D\u0438\u0436\u0435.", + "onboarding.dict.recommended": "\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u0442\u0441\u044F", + "onboarding.dict.col.dict": "\u0421\u043B\u043E\u0432\u0430\u0440\u044C", + "onboarding.dict.col.lang": "\u042F\u0437\u044B\u043A", + "onboarding.dict.col.roles": "\u0420\u043E\u043B\u0438", + "onboarding.dict.col.size": "\u0420\u0430\u0437\u043C\u0435\u0440", + "onboarding.dict.role.detection": "\u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u0435", + "onboarding.dict.role.replacement": "\u0437\u0430\u043C\u0435\u043D\u0430", + "onboarding.dict.role.classes": "\u043A\u043B\u0430\u0441\u0441\u044B", + "onboarding.dict.install": "\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C", + "onboarding.dict.reinstall": "\u041F\u0435\u0440\u0435\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C", + "onboarding.dict.installed": "\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E", + "onboarding.dict.manualHint": "\u0423\u0436\u0435 \u0435\u0441\u0442\u044C \u0444\u0430\u0439\u043B .dict.json? \u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u0432\u0440\u0443\u0447\u043D\u0443\u044E:", + "onboarding.dict.importBtn": "\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0444\u0430\u0439\u043B (.dict.json)", + "onboarding.dict.listTitle": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438, \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0432 \u044D\u0442\u043E\u043C \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 ({0}):", + "onboarding.dict.remove": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u0441\u043B\u043E\u0432\u0430\u0440\u044C", + "onboarding.summary.ner.none": "\u041E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0440\u0443\u0447\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430", + "onboarding.summary.ner.tfjs": "transformers.js \u2014 \u043C\u043E\u0434\u0435\u043B\u044C ONNX (\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043F\u0440\u0438 \u043F\u0435\u0440\u0432\u043E\u043C \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0438)", + "onboarding.nav.cancel": "\u041E\u0442\u043C\u0435\u043D\u0430", + "onboarding.nav.back": "\u041D\u0430\u0437\u0430\u0434", + "onboarding.nav.skip": "\u041F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u044D\u0442\u043E\u0442 \u0448\u0430\u0433", + "onboarding.nav.next": "\u0414\u0430\u043B\u0435\u0435", + "onboarding.nav.start": "\u041D\u0430\u0447\u0430\u0442\u044C", + "onboarding.nav.finish": "\u041F\u0440\u0438\u0441\u0442\u0443\u043F\u0438\u0442\u044C \u043A \u0440\u0430\u0431\u043E\u0442\u0435", + "onboarding.summary.title": "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430", + "onboarding.summary.intro": "\u0412\u043E\u0442 \u0447\u0442\u043E \u0431\u044B\u043B\u043E \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u043E:", + "onboarding.summary.ner": "\u0414\u0435\u0442\u0435\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 NER", + "onboarding.summary.dicts": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438", + "onboarding.summary.dicts.count": "{0} \u0444\u0430\u0439\u043B(\u043E\u0432) \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", + "onboarding.summary.hint": "\u042D\u0442\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043C\u043E\u0436\u043D\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0432 \u043B\u044E\u0431\u043E\u0435 \u0432\u0440\u0435\u043C\u044F \u0432 \u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u0430\u0445 \u043F\u043B\u0430\u0433\u0438\u043D\u0430: \u041F\u0435\u0440\u0435\u043D\u0430\u0441\u0442\u0440\u043E\u0438\u0442\u044C.", + "settings.heading.general": "\u041E\u0431\u0449\u0438\u0435", + "settings.heading.textDetection": "\u041E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u0442\u0435\u043A\u0441\u0442\u0430", + "settings.heading.pseudonymization": "\u041F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0430\u0446\u0438\u044F", + "settings.heading.ner": "\u0410\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u043E\u0435 \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u0435 (NER)", + "settings.heading.storage": "\u0425\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435", + "settings.heading.security": "\u0411\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u044C", + "settings.language": "\u042F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430", + "settings.languageDesc": "\u041D\u0435 \u0432\u043B\u0438\u044F\u0435\u0442 \u043D\u0430 \u0430\u043D\u0430\u043B\u0438\u0437 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439. \u041A\u043E\u043D\u0432\u0435\u043D\u0446\u0438\u0438 (Jefferson, ICOR) \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u044F\u0442 \u043E\u0442 \u044F\u0437\u044B\u043A\u0430.", + "settings.vaultPerCorpus": "\u041E\u0434\u043D\u043E \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0430 \u043A\u043E\u0440\u043F\u0443\u0441", + "settings.vaultPerCorpusDesc": "\u0420\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0435\u043C \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u0435 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 Obsidian \u0434\u043B\u044F \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u043A\u043E\u0440\u043F\u0443\u0441\u0430. \u042D\u0442\u043E \u0433\u0430\u0440\u0430\u043D\u0442\u0438\u0440\u0443\u0435\u0442, \u0447\u0442\u043E \u0442\u0430\u0431\u043B\u0438\u0446\u044B \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F, \u0441\u043B\u043E\u0432\u0430\u0440\u0438 \u0438 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u044B \u0445\u0440\u0430\u043D\u044F\u0442\u0441\u044F \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u044F\u043C\u0438.", + "settings.transcriptionsFolder": "\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0438", + "settings.transcriptionsFolderDesc": "\u041F\u0430\u043F\u043A\u0430 \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0442\u0440\u0430\u043D\u0441\u043A\u0440\u0438\u043F\u0446\u0438\u0439.", + "settings.mappingFolder": "\u0422\u0430\u0431\u043B\u0438\u0446\u044B \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F", + "settings.mappingFolderDesc": "\u041E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u043F\u0443\u0442\u044C \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435.", + "settings.dictionariesFolder": "\u0421\u043B\u043E\u0432\u0430\u0440\u0438", + "settings.dictionariesFolderDesc": "\u041E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u043F\u0443\u0442\u044C \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435.", + "settings.exportsFolder": "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u044B", + "settings.accentSensitive": "\u0423\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u0430\u043A\u0446\u0435\u043D\u0442\u044B", + "settings.preserveCase": "\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u0440\u0435\u0433\u0438\u0441\u0442\u0440", + "settings.preserveCaseDesc": "\u0410\u0434\u0430\u043F\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0440\u0435\u0433\u0438\u0441\u0442\u0440 \u0437\u0430\u043C\u0435\u043D\u044B \u043F\u043E\u0434 \u0438\u0441\u0445\u043E\u0434\u043D\u044B\u0439 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u0418\u0412\u0410\u041D \u2192 \u041F\u0401\u0422\u0420, \u0438\u0432\u0430\u043D \u2192 \u043F\u0451\u0442\u0440, \u0418\u0432\u0430\u043D \u2192 \u041F\u0451\u0442\u0440).", + "settings.preserveAnalyticNotation": "\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0442\u044C \u0430\u043D\u0430\u043B\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0443\u044E \u043D\u043E\u0442\u0430\u0446\u0438\u044E", + "settings.preserveAnalyticNotationDesc": "\u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0437\u0430\u043C\u0435\u043D\u044F\u0442\u044C \u0441\u0438\u043C\u0432\u043E\u043B\u044B \u0430\u043D\u0430\u043B\u0438\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0445 \u043A\u043E\u043D\u0432\u0435\u043D\u0446\u0438\u0439 Jefferson \u0438\u043B\u0438 ICOR.", + "settings.useMarkerInExport": "\u0414\u043E\u0431\u0430\u0432\u043B\u044F\u0442\u044C \u043C\u0430\u0440\u043A\u0435\u0440 \u0432\u043E\u043A\u0440\u0443\u0433 \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u043E\u0432 \u0432 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0435", + "settings.useMarkerInExportDesc": "\u041F\u043E\u0437\u0432\u043E\u043B\u044F\u0435\u0442 \u0432\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u043E \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0442\u0435\u0440\u043C\u0438\u043D\u044B \u0432 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u043C \u0444\u0430\u0439\u043B\u0435.", + "settings.markerOpen": "\u041E\u0442\u043A\u0440\u044B\u0432\u0430\u044E\u0449\u0438\u0439 \u043C\u0430\u0440\u043A\u0435\u0440", + "settings.markerOpenDesc": "\u041F\u0440\u0438\u043C\u0435\u0440: {{ \u27E6 [ \xAB", + "settings.markerClose": "\u0417\u0430\u043A\u0440\u044B\u0432\u0430\u044E\u0449\u0438\u0439 \u043C\u0430\u0440\u043A\u0435\u0440", + "settings.markerCloseDesc": "\u041F\u0440\u0438\u043C\u0435\u0440: }} \u27E7 ] \xBB", + "settings.nerBackend": "\u0414\u0432\u0438\u0436\u043E\u043A \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u044F", + "settings.nerBackendDesc": "\u0411\u044D\u043A\u0435\u043D\u0434 \u0434\u043B\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u0435\u0441\u043A\u043E\u0433\u043E \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0435\u043D\u0438\u044F \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u044E\u0449\u0438\u0445 \u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u0441\u0443\u0449\u043D\u043E\u0441\u0442\u0435\u0439.", + "settings.nerBackend.none": "\u041E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u043E \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0440\u0443\u0447\u043D\u044B\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u0430", + "settings.nerBackend.tfjs": "transformers.js (\u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C ONNX)", + "settings.reconfigure": "\u041C\u0430\u0441\u0442\u0435\u0440 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438", + "settings.reconfigureDesc2": "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u043C\u0430\u0441\u0442\u0435\u0440 \u0434\u043B\u044F \u043F\u0435\u0440\u0435\u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 NER-\u0434\u0435\u0442\u0435\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0438 \u0441\u043B\u043E\u0432\u0430\u0440\u0435\u0439.", + "settings.reconfigureBtn2": "\u041F\u0435\u0440\u0435\u043D\u0430\u0441\u0442\u0440\u043E\u0438\u0442\u044C\u2026", + "settings.warnIfSyncedFolder": "\u041F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0430\u0442\u044C \u043E \u0441\u0438\u043D\u0445\u0440\u043E\u043D\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u0435", + "settings.warnIfSyncedFolderDesc": "\u041F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0430\u0442\u044C, \u0435\u0441\u043B\u0438 \u0442\u0430\u0431\u043B\u0438\u0446\u044B \u0441\u043E\u043E\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u044F \u043D\u0430\u0445\u043E\u0434\u044F\u0442\u0441\u044F \u0432 \u043F\u0430\u043F\u043A\u0435 Git, iCloud \u0438\u043B\u0438 Synology Drive.", + "settings.caseSensitive": "\u0423\u0447\u0438\u0442\u044B\u0432\u0430\u0442\u044C \u0440\u0435\u0433\u0438\u0441\u0442\u0440", + "settings.caseSensitiveDesc": "\u0415\u0441\u043B\u0438 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E, \xAB\u0418\u0432\u0430\u043D\xBB \u0438 \xAB\u0438\u0432\u0430\u043D\xBB \u0441\u0447\u0438\u0442\u0430\u044E\u0442\u0441\u044F \u0440\u0430\u0437\u043D\u044B\u043C\u0438.", + "settings.wholeWordOnly": "\u0422\u043E\u043B\u044C\u043A\u043E \u0446\u0435\u043B\u044B\u0435 \u0441\u043B\u043E\u0432\u0430", + "settings.wholeWordOnlyDesc": "\u0415\u0441\u043B\u0438 \u0432\u043A\u043B\u044E\u0447\u0435\u043D\u043E, \u0437\u0430\u043C\u0435\u043D\u044F\u044E\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u044F \u043F\u043E\u043B\u043D\u044B\u0445 \u0441\u043B\u043E\u0432." + }; + } +}); + // src/i18n/index.ts function setLocale(lang) { _locale = LOCALES[lang] ? lang : FALLBACK; @@ -780,13 +1516,17 @@ var init_i18n = __esm({ "src/i18n/index.ts"() { "use strict"; init_en(); + init_es(); init_fr(); - LOCALES = { en: en_default, fr: fr_default }; + init_ru(); + LOCALES = { en: en_default, es: es_default, fr: fr_default, ru: ru_default }; FALLBACK = "en"; _locale = FALLBACK; AVAILABLE_LANGUAGES = { en: "English", - fr: "Fran\xE7ais" + es: "Espa\xF1ol", + fr: "Fran\xE7ais", + ru: "\u0420\u0443\u0441\u0441\u043A\u0438\u0439" }; } }); @@ -33476,9 +34216,10 @@ function getCorpusClasses(app, transcriptionsFolder) { return folder.children.filter((c) => c instanceof import_obsidian7.TFolder).map((c) => c.name).sort(); } var CorpusModal = class extends import_obsidian7.Modal { - constructor(app, plugin) { + constructor(app, plugin, onCloseCb) { super(app); this.plugin = plugin; + this.onCloseCb = onCloseCb; } onOpen() { this.render(); @@ -33571,6 +34312,7 @@ var CorpusModal = class extends import_obsidian7.Modal { } onClose() { this.contentEl.empty(); + this.onCloseCb?.(); } }; var ClassSelectModal = class extends import_obsidian7.Modal { @@ -34160,7 +34902,6 @@ var PseudonymizationView = class extends import_obsidian10.ItemView { const typeRow = exportSection.createDiv("pseudobs-corpus-export-type-row"); for (const [val, labelKey] of [ ["vault", "panel.corpus.exportDest.vault"], - ["next-to-source", "panel.corpus.exportDest.nextToSource"], ["external", "panel.corpus.exportDest.external"] ]) { const lbl = typeRow.createEl("label", { cls: "pseudobs-corpus-export-type-label" }); @@ -34204,13 +34945,8 @@ var PseudonymizationView = class extends import_obsidian10.ItemView { text: `+ ${t("panel.corpus.addClass")}`, cls: "pseudobs-corpus-add-class-btn" }); - addClassBtn.addEventListener("click", async () => { - const name2 = this.promptText(t("corpus.modal.classNamePlaceholder")); - if (!name2) - return; - await this.plugin.ensureFolder(`${transcRoot}/${name2}`); - await this.plugin.ensureFolder(`${s.mappingFolder}/${name2}`); - void this.renderTab("corpus"); + addClassBtn.addEventListener("click", () => { + new CorpusModal(this.app, this.plugin, () => void this.renderTab("corpus")).open(); }); const classes = getCorpusClasses(this.app, transcRoot); const allClasses = [null, ...classes]; @@ -34602,6 +35338,13 @@ var PseudonymizationView = class extends import_obsidian10.ItemView { void this.plugin.exportCurrentFileAsVtt(); }) ); + if (format === "html") { + new import_obsidian10.Setting(el).setName(t("panel.exports.exportHtml")).setDesc(t("panel.exports.exportHtml.desc")).addButton( + (btn) => btn.setButtonText(t("command.exportAsHtml")).onClick(() => { + void this.plugin.exportCurrentFileAsHtml(); + }) + ); + } } else if (format === "srt") { new import_obsidian10.Setting(el).setName(t("panel.exports.exportSrt")).setDesc(t("panel.exports.exportSrt.desc")).addButton( (btn) => btn.setButtonText(t("command.exportAsSrt")).setCta().onClick(() => { @@ -36036,6 +36779,86 @@ function vttToMarkdown(doc, sourceName, audioFilename) { function noScribeHtmlToMarkdown(doc, sourceName, audioFilename) { return vttDocToMarkdown(doc, sourceName, "html", audioFilename); } +function tsToMs(ts) { + const [h, m, s] = ts.split(":"); + return Math.round((parseInt(h, 10) * 3600 + parseInt(m, 10) * 60 + parseFloat(s)) * 1e3); +} +function escapeHtml(text) { + return text.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} +function sanitizeSpeakerId(speaker) { + return speaker.replace(/\W/g, ""); +} +function markdownToNoScribeHtml(mdContent, wordData, audioSource) { + const bodyMatch = /^---\n[\s\S]*?\n---\n+([\s\S]*)$/.exec(mdContent); + const body = bodyMatch ? bodyMatch[1] : mdContent; + const cueLines = body.split("\n").filter((l) => MD_CUE_RE.test(l.trim())); + const mismatch = cueLines.length !== wordData.length; + const count = Math.min(cueLines.length, wordData.length); + const P_STYLE = " margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"; + const paragraphs = []; + for (let i2 = 0; i2 < count; i2++) { + const m = MD_CUE_RE.exec(cueLines[i2].trim()); + const speaker = m[1]?.trim() || wordData[i2].speaker || ""; + const text = m[2]?.trim() ?? ""; + if (!text) + continue; + const cueData = wordData[i2]; + const cueStartMs = tsToMs(cueData.startTime); + const cueEndMs = tsToMs(cueData.endTime); + const speakerId = speaker ? sanitizeSpeakerId(speaker) : ""; + const cueAnchor = `ts_${cueStartMs}_${cueEndMs}_${speakerId}`; + const displayTs = `[${cueData.startTime.slice(0, 8)}]`; + const origWords = cueData.words.filter((w) => w.time !== ""); + const pseudoTokens = text.split(/\s+/).filter(Boolean); + let contentHtml; + if (origWords.length === 0 || pseudoTokens.length === 0) { + const prefix = speaker ? `${escapeHtml(speaker)} : ` : ""; + contentHtml = `${prefix}${escapeHtml(text)}`; + } else { + const wordStartMs = origWords.map((w) => tsToMs(w.time)); + const wordEndMs = origWords.map( + (_, j) => j + 1 < origWords.length ? wordStartMs[j + 1] : cueEndMs + ); + const mapToOrig = (j) => Math.min(Math.floor(j * origWords.length / pseudoTokens.length), origWords.length - 1); + const groups = []; + for (let j = 0; j < pseudoTokens.length; j++) { + const origIdx = mapToOrig(j); + if (groups.length === 0 || groups[groups.length - 1].origIdx !== origIdx) { + groups.push({ origIdx, tokens: [] }); + } + groups[groups.length - 1].tokens.push(pseudoTokens[j]); + } + const parts = []; + for (let g = 0; g < groups.length; g++) { + const { origIdx, tokens } = groups[g]; + const anchorName = `ts_${wordStartMs[origIdx]}_${wordEndMs[origIdx]}_${speakerId}`; + let groupText = tokens.join(" "); + if (g < groups.length - 1) + groupText += " "; + const content = g === 0 && speaker ? `${escapeHtml(speaker)} : ${escapeHtml(groupText)}` : escapeHtml(groupText); + parts.push(`${content}`); + } + contentHtml = parts.join(""); + } + paragraphs.push( + `

${displayTs}` + contentHtml + `

` + ); + } + const audioMeta = audioSource ? ` +` : ""; + const html = [ + '', + ` +${audioMeta}`, + ...paragraphs, + "", + "" + ].join("\n"); + return { html, mismatch }; +} function lineGroup(line) { if (line.type === "meta" || line.type === "dependent") return "structural"; @@ -36317,6 +37140,11 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { name: t("command.exportAsCha"), callback: () => void this.exportCurrentFileAsFormat("cha") }); + this.addCommand({ + id: "export-as-html", + name: t("command.exportAsHtml"), + callback: () => void this.exportCurrentFileAsHtml() + }); this.addCommand({ id: "pseudonymize-selection", name: t("command.pseudonymizeSelection"), @@ -36482,32 +37310,32 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { * Calcule le chemin de l'export final selon les paramètres de destination. * Retourne { vaultPath } si dans le vault, { externalPath } si hors vault. */ + /** Retourne le sous-dossier de classe (vide si pas de mirroring ou pas de classe). */ + getClassSub(file) { + const s = this.settings; + if (!s.exportMirrorClasses) + return ""; + const transcRoot = s.transcriptionsFolder; + const fileFolder = file.parent?.path ?? ""; + const rawBase = file.basename.replace(/\.pseudonymized$/, ""); + if (fileFolder.startsWith(transcRoot)) { + return fileFolder.slice(transcRoot.length).replace(/^\//, ""); + } + const mappingFile = this.findInMappings(`${rawBase}.mapping.json`); + if (mappingFile) { + const mFolder = mappingFile.parent?.path ?? ""; + if (mFolder.startsWith(s.mappingFolder)) { + return mFolder.slice(s.mappingFolder.length).replace(/^\//, ""); + } + } + return ""; + } resolveExportPath(file, ext) { const s = this.settings; const rawBase = file.basename.replace(/\.pseudonymized$/, ""); const outputName = `${rawBase}.pseudonymized.${ext}`; - const fileFolder = file.parent?.path ?? ""; - let classSub = ""; - if (s.exportMirrorClasses) { - const transcRoot = s.transcriptionsFolder; - const fileFolder2 = file.parent?.path ?? ""; - if (fileFolder2.startsWith(transcRoot)) { - classSub = fileFolder2.slice(transcRoot.length).replace(/^\//, ""); - } else { - const mappingFile = this.findInMappings(`${rawBase}.mapping.json`); - if (mappingFile) { - const mFolder = mappingFile.parent?.path ?? ""; - const mRoot = s.mappingFolder; - if (mFolder.startsWith(mRoot)) { - classSub = mFolder.slice(mRoot.length).replace(/^\//, ""); - } - } - } - } + const classSub = this.getClassSub(file); const join2 = (base) => classSub ? `${base}/${classSub}/${outputName}` : `${base}/${outputName}`; - if (s.exportDestinationType === "next-to-source") { - return { vaultPath: `${fileFolder}/${outputName}`, externalPath: null }; - } if (s.exportDestinationType === "external" && s.exportExternalPath) { return { vaultPath: null, externalPath: join2(s.exportExternalPath) }; } @@ -36890,6 +37718,11 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { } // --- Conversion automatique --- async autoConvert(file) { + const s = this.settings; + const fileFolder = file.parent?.path ?? ""; + const outputFolders = [s.exportsFolder, s.mappingFolder, s.exportFinalFolder].filter(Boolean); + if (outputFolders.some((f) => fileFolder === f || fileFolder.startsWith(f + "/"))) + return; try { const raw = await this.app.vault.read(file); const ext = file.extension.toLowerCase(); @@ -36932,8 +37765,8 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { } await this.app.vault.create(mdPath, mdContent); const transcRoot = this.settings.transcriptionsFolder; - const fileFolder = file.parent?.path ?? ""; - const relSubFolder = fileFolder.startsWith(transcRoot) ? fileFolder.slice(transcRoot.length).replace(/^\//, "") : ""; + const fileFolder2 = file.parent?.path ?? ""; + const relSubFolder = fileFolder2.startsWith(transcRoot) ? fileFolder2.slice(transcRoot.length).replace(/^\//, "") : ""; const mappingDir = relSubFolder ? `${this.settings.mappingFolder}/${relSubFolder}` : this.settings.mappingFolder; await this.ensureFolder(mappingDir); const mappingPath = `${mappingDir}/${basename}.mapping.json`; @@ -37106,8 +37939,10 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { } else { pseudonymized = engine.pseudonymize(content, rules, marker); } - await this.ensureFolder(this.settings.exportsFolder); - const outputPath = `${this.settings.exportsFolder}/${file.basename}.pseudonymized.${ext}`; + const classSub = this.getClassSub(file); + const exportBase = classSub ? `${this.settings.exportsFolder}/${classSub}` : this.settings.exportsFolder; + await this.ensureFolder(exportBase); + const outputPath = `${exportBase}/${file.basename}.pseudonymized.${ext}`; const existing = this.app.vault.getAbstractFileByPath(outputPath); if (existing instanceof import_obsidian14.TFile) { await this.app.vault.modify(existing, pseudonymized); @@ -37276,6 +38111,37 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { } await this.writeExport(file, "vtt", vtt); } + /** + * Re-exporte le Markdown noScribe actif au format HTML noScribe pseudonymisé. + * Lit le .words.json correspondant pour les timestamps précis. + */ + async exportCurrentFileAsHtml() { + const file = this.getActiveOrLastFile(); + if (!file || file.extension !== "md") { + new import_obsidian14.Notice(t("notice.noActiveFile")); + return; + } + const content = await this.app.vault.read(file); + const formatMatch = /^pseudobs-format:\s*(\w+)/m.exec(content); + if (formatMatch?.[1] !== "html") { + new import_obsidian14.Notice(t("notice.notNoScribeFormat")); + return; + } + const rawBasename = file.basename.replace(/\.pseudonymized$/, ""); + const wordsJson = await this.findWordsJson(rawBasename); + if (!wordsJson) { + new import_obsidian14.Notice(t("notice.wordsJsonMissing", rawBasename)); + return; + } + const audioMatch = /^pseudobs-audio:\s*"([^"]+)"/m.exec(content); + const audioSource = audioMatch?.[1]; + const wordData = JSON.parse(wordsJson); + const { html, mismatch } = markdownToNoScribeHtml(content, wordData, audioSource); + if (mismatch) { + new import_obsidian14.Notice(t("notice.vttMismatch")); + } + await this.writeExport(file, "html", html, "notice.htmlExported"); + } /** * Re-exporte le Markdown pseudonymisé courant au format d'origine (srt ou cha). */ @@ -37301,7 +38167,7 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { * Écrit le contenu d'un export final selon les paramètres de destination. * Gère vault, next-to-source et external. */ - async writeExport(file, ext, content) { + async writeExport(file, ext, content, noticeKey = "notice.vttExported") { const dest = this.resolveExportPath(file, ext); if (dest.externalPath) { try { @@ -37309,7 +38175,7 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { const nodePath = require("path"); await nodeFs.promises.mkdir(nodePath.dirname(dest.externalPath), { recursive: true }); await nodeFs.promises.writeFile(dest.externalPath, content, "utf-8"); - new import_obsidian14.Notice(t("notice.vttExported", dest.externalPath)); + new import_obsidian14.Notice(t(noticeKey, dest.externalPath)); } catch (e) { new import_obsidian14.Notice(`Export error: ${e.message}`); } @@ -37325,7 +38191,7 @@ var PseudObsPlugin = class extends import_obsidian14.Plugin { } else { await this.app.vault.create(outputPath, content); } - new import_obsidian14.Notice(t("notice.vttExported", outputPath)); + new import_obsidian14.Notice(t(noticeKey, outputPath)); } /** Cherche .words.json dans le dossier mappings et ses sous-dossiers. */ async findWordsJson(basename) { diff --git a/src/i18n/index.ts b/src/i18n/index.ts index b804e2f..d9c3cb0 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,9 +1,11 @@ import en from './locales/en.json'; +import es from './locales/es.json'; import fr from './locales/fr.json'; +import ru from './locales/ru.json'; type Strings = Record; -const LOCALES: Record = { en, fr }; +const LOCALES: Record = { en, es, fr, ru }; const FALLBACK = 'en'; let _locale = FALLBACK; @@ -36,5 +38,7 @@ export function t(key: string, ...args: (string | number)[]): string { /** Langues disponibles avec leur nom natif. */ export const AVAILABLE_LANGUAGES: Record = { en: 'English', + es: 'Español', fr: 'Français', + ru: 'Русский', }; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index b6dcba3..bd6f914 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -201,7 +201,6 @@ "panel.corpus.deleteClassConfirm": "Delete class \"{0}\"? Files inside will be moved to root.", "panel.corpus.exportSettings": "Final export destination", "panel.corpus.exportDest.vault": "In the vault", - "panel.corpus.exportDest.nextToSource": "Next to source file", "panel.corpus.exportDest.external": "External folder", "panel.corpus.exportFolder": "Export folder (vault)", "panel.corpus.exportExternalPath": "Absolute path", @@ -220,8 +219,12 @@ "panel.exports.exportSrt.desc": "Rebuilds the pseudonymized SRT file from the Markdown.", "panel.exports.exportCha": "Re-export as CHAT (.cha)", "panel.exports.exportCha.desc": "Rebuilds the pseudonymized CHAT file from the Markdown.", + "panel.exports.exportHtml": "Re-export as HTML", + "panel.exports.exportHtml.desc": "Rebuilds the pseudonymized noScribe HTML file from the Markdown and word timestamps (.words.json).", + "notice.htmlExported": "HTML exported: {0}", "command.exportAsSrt": "Export as SRT", "command.exportAsCha": "Export as CHAT", + "command.exportAsHtml": "Export as HTML", "panel.ner.scanBtn": "Identify candidates", "panel.ner.hint": "Settings for the automatic named entity detection scanner.", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json new file mode 100644 index 0000000..e5deea6 --- /dev/null +++ b/src/i18n/locales/es.json @@ -0,0 +1,378 @@ +{ + "corpus.modal.title": "Organizar corpus", + "corpus.modal.hint": "Cada clase crea carpetas espejo para las transcripciones, las tablas de correspondencia y los exportes.", + "corpus.modal.noClasses": "No hay clases definidas. Añada una para organizar sus documentos por carpeta.", + "corpus.modal.addClass": "Añadir una clase", + "corpus.modal.classNamePlaceholder": "Nombre de la clase (p. ej. Sesión_A, Informante_1…)", + "corpus.modal.classNameEmpty": "El nombre de la clase no puede estar vacío.", + "corpus.modal.classExists": "Ya existe una clase con este nombre.", + "corpus.modal.deleteConfirm": "¿Eliminar la clase «{0}»? Las carpetas serán retiradas del vault (los archivos dentro irán a la papelera).", + "corpus.modal.files": "{0} archivo(s)", + "corpus.modal.close": "Cerrar", + "corpus.select.title": "¿En qué clase añadir?", + "corpus.select.hint": "Elija la clase (carpeta) para esta transcripción.", + "corpus.select.none": "Sin clase (raíz)", + "corpus.select.confirm": "Añadir", + "quickModal.title": "Pseudonimizar", + "quickModal.source": "Expresión seleccionada", + "quickModal.sourceDesc": "Término a reemplazar — no modificable", + "quickModal.replaceBy": "Reemplazar por", + "quickModal.replacementPlaceholder": "Pseudónimo o categoría analítica", + "quickModal.scope": "Ámbito del reemplazo", + "quickModal.scopeFile": "Todas las ocurrencias en este archivo", + "quickModal.scopeOccurrence": "Solo esta ocurrencia", + "quickModal.submit": "Pseudonimizar", + "notice.appliedOccurrence": "✓ «{0}» → «{1}» (esta ocurrencia)", + "notice.appliedFile": "✓ «{0}» → «{1}» ({2} ocurrencia{3})", + "onboarding.dict.importOk": "{0} diccionario importado", + "onboarding.dict.importOkMany": "{0} diccionarios importados", + "onboarding.dict.importErr": "{0} archivo rechazado", + "onboarding.dict.importErrMany": "{0} archivos rechazados", + "onboarding.dict.importError": "Error al importar {0} — verifique el formato .dict.json", + "notice.noActiveFile": "No hay archivo activo.", + "notice.formatUnsupported": "Formato no compatible: .{0}", + "notice.noRules": "No hay reglas para este archivo.\nCree reglas con Ctrl+P → «Crear una regla».", + "notice.noOccurrences": "No se encontraron ocurrencias para las reglas activas.", + "notice.noNerEntities": "El NER no detectó ninguna entidad.", + "notice.nerEntitiesFound": "✓ {0} {1} detectada(s) — resaltada(s) en azul.\nClic derecho para crear una regla.", + "notice.nerEntitiesFound.entity": "entidad", + "notice.nerEntitiesFound.entities": "entidades", + "notice.nerNotEnabled": "La detección NER de transformers.js no está activada.\nActívela en Configuración → Pseudonymizer Tool.", + "notice.noDictDetection": "No hay diccionario de detección cargado.\nInstale uno desde el panel Diccionarios.", + "notice.noDictEntities": "No se encontraron entidades en los diccionarios de detección.", + "notice.noDictReplacements": "No hay reemplazos disponibles para las entidades encontradas.", + "notice.ruleDeleted": "✓ Regla eliminada — «{0}» restaurado en el archivo activo", + "notice.ruleCreated": "✓ Regla creada: «{0}» → «{1}»", + "notice.rulesCreated": "✓ {0} {1} creada(s)", + "notice.rulesCreated.rule": "regla", + "notice.rulesCreated.rules": "reglas", + "notice.occurrencesPseudonymized": "✓ {0} {1} pseudonimizada(s) en «{2}»", + "notice.occurrencesPseudonymized.occurrence": "ocurrencia", + "notice.occurrencesPseudonymized.occurrences": "ocurrencias", + "notice.converted": "✓ {0} → {1}", + "notice.conversionError": "Error de conversión de {0}: {1}", + "notice.fileExists": "El archivo ya existe en el vault: {0}", + "notice.conversionSkipped": "⚠ {0}.md ya existe — conversión omitida para {1}", + "notice.exportDone": "✓ {0} regla(s) aplicada(s)\n→ {1}", + "notice.mappingExported": "✓ Tabla de correspondencia exportada → {0}", + "notice.noMapping": "No se encontró tabla de correspondencia para {0}", + "notice.invalidFormat": "Formato no válido: {0}", + "notice.dictImported": "✓ {0} {1} importado(s)", + "notice.dictImported.one": "diccionario", + "notice.dictImported.many": "diccionarios", + "notice.dictRejected": "{0} {1} rechazado(s)", + "notice.dictRejected.one": "archivo", + "notice.dictRejected.many": "archivos", + "notice.nerModelLoaded": "✓ Modelo NER cargado", + "notice.nerModelLoading": "Cargando modelo NER (primer uso — ~66 MB)…", + "notice.ruleNotFound": "Regla no encontrada en las tablas de correspondencia.", + "notice.noCheckedDicts": "Ningún diccionario marcado.", + "notice.audioImported": "Audio importado: {0}", + "occurrencesCtx.hint": "{0} ocurrencia{1} — seleccione las que desea reemplazar.", + "occurrencesCtx.hint.s": "s", + "occurrencesCtx.hint.empty": "", + "occurrencesCtx.validateAll": "Validar todas", + "occurrencesCtx.ignoreAll": "Ignorar todas", + "occurrencesCtx.cancel": "Cancelar", + "occurrencesCtx.confirm": "Confirmar selección", + "occurrencesCtx.btn.validate": "Validar", + "occurrencesCtx.btn.ignore": "Ignorar", + "occurrencesCtx.btn.fp": "Falso positivo", + "occurrencesCtx.status.ignored": "Conservado tal cual", + "occurrencesCtx.status.fp": "Falso positivo — excluido", + "notice.notNoScribeFormat": "Este archivo no es una transcripción de noScribe (se espera pseudobs-format: vtt o html).", + "notice.wordsJsonMissing": "Archivo de marcas de tiempo no encontrado: {0}.words.json", + "notice.vttMismatch": "Aviso: el número de cues del Markdown y del words.json difiere — el VTT exportado puede estar incompleto.", + "notice.vttExported": "VTT exportado: {0}", + + "command.organizeCorpus": "Organizar corpus", + "command.addTranscription": "Añadir una transcripción", + "command.pseudonymizeFile": "Pseudonimizar el archivo actual", + "command.createRule": "Crear una regla de reemplazo", + "command.scanFile": "Escanear el archivo actual", + "command.scanNer": "Escanear el archivo con detección NER", + "command.scanDictionaries": "Escanear el archivo con diccionarios", + "command.pseudonymizeSelection": "Pseudonimizar la selección", + "command.exportAsVtt": "Exportar como VTT", + "command.exportAsSrt": "Exportar como SRT", + "command.exportAsCha": "Exportar como CHAT", + "command.exportAsHtml": "Exportar como HTML", + "command.openPanel": "Pseudonimización: abrir panel", + + "contextMenu.declareException": "Declarar «{0}» como excepción aquí", + "contextMenu.cancelPseudonymization": "Cancelar pseudonimización de «{0}»", + "contextMenu.editRule": "Editar regla para «{0}»", + "contextMenu.pseudonymize": "Pseudonimizar «{0}»", + "contextMenu.redact": "Redactar «{0}»", + "contextMenu.coulmont": "Pseudonimizar con el prof. Baptiste Coulmont", + "contextMenu.createRule": "Crear una regla de reemplazo…", + "redaction.checkbox": "Redactar (🀫)", + "redaction.checkboxDesc": "Reemplazar con símbolos 🀫 (uno por sílaba) — para contenido identificador no esencial", + + "ruleModal.delete": "Eliminar regla", + "ruleModal.scopeWarnTitle": "Ámbito longitudinal", + "ruleModal.scopeOkTitle": "Ámbito de archivo", + "ruleModal.scopeOk": "Buena práctica. Este pseudónimo solo se aplicará a esta transcripción — cada archivo permanece independiente.", + "ruleModal.title": "Crear una regla de reemplazo", + "ruleModal.source": "Fuente", + "ruleModal.sourceDesc": "Término original a reemplazar", + "ruleModal.replacement": "Reemplazo", + "ruleModal.replacementDesc": "Pseudónimo o categoría analítica", + "ruleModal.replacementDescClass": "El índice exacto se calculará al crearlo según las reglas existentes en el ámbito.", + "ruleModal.category": "Categoría", + "ruleModal.scope": "Ámbito", + "ruleModal.scopeFile": "Solo este archivo", + "ruleModal.scopeFolder": "Esta carpeta", + "ruleModal.scopeVault": "Todo el vault", + "ruleModal.priority": "Prioridad", + "ruleModal.priorityDesc": "Entero libre, como un z-index CSS — por defecto 0, mayor = aplicado primero", + "ruleModal.submit": "Crear regla", + "ruleModal.errorMissing": "La fuente y el reemplazo son obligatorios.", + "ruleModal.errorNoFile": "No hay archivo activo.", + "ruleModal.coulomontLabel": "Sugerencias de nombres equivalentes — elija:", + "ruleModal.dictLabel": "Diccionario: «{0}» → clase {1}", + "ruleModal.dictUseClass": "Usar «{0}» (ámbito: {1})", + + "category.first_name": "Nombre", + "category.last_name": "Apellido", + "category.full_name": "Nombre completo", + "category.place": "Lugar", + "category.institution": "Institución", + "category.date": "Fecha", + "category.age": "Edad", + "category.profession": "Profesión", + "category.custom": "Otro", + + "scope.file": "Archivo", + "scope.folder": "Carpeta", + "scope.vault": "Vault", + + "status.validated": "Activo", + "status.ignored": "Ignorado", + "status.partial": "Parcial", + "status.suggested": "Sugerido", + "status.conflict": "Conflicto", + "status.disabled": "Inactivo", + "status.needs_review": "Revisión", + + "panel.tab.mappings": "Correspondencias", + "panel.tab.dictionaries": "Diccionarios", + "panel.tab.exports": "Exportes", + "panel.tab.ner": "NER", + "panel.tab.corpus": "Corpus", + + "panel.mappings.filterActive": "Solo archivo activo", + "panel.mappings.warnBroadName": "⚠ Ampliar el ámbito de un nombre o apellido más allá del archivo solo debe hacerse como elección analítica deliberada — por ejemplo, para seguir de forma coherente al mismo participante en un estudio longitudinal. Incluso cuando el mismo individuo aparece en varios archivos, la pseudonimización a nivel de archivo suele ser preferible: mantiene cada transcripción independiente y reduce el riesgo de identificación cruzada.", + "panel.mappings.addRule": "Añadir una regla", + "panel.mappings.scanFile": "Escanear archivo", + "panel.mappings.scanning": "Escaneando…", + "panel.mappings.noFile": "No hay archivo activo.", + "panel.mappings.noRules": "No hay reglas para {0}.", + "panel.mappings.noRulesHint": "No hay reglas activas para este archivo. Cree reglas desde el menú contextual o el comando «Crear una regla».", + "panel.mappings.col.source": "Fuente", + "panel.mappings.col.replacement": "Reemplazo", + "panel.mappings.col.category": "Categoría", + "panel.mappings.col.scope": "Ámbito", + "panel.mappings.col.priority": "P.", + "panel.mappings.col.status": "Estado", + "panel.mappings.exceptions": "Excepciones", + "panel.mappings.exceptions.hint": "Ocurrencias explícitamente ignoradas para esta regla.", + "panel.mappings.exceptions.delete": "Eliminar esta excepción", + + "panel.dict.noneInstalled": "No hay diccionarios instalados. Instale uno desde el asistente (Configuración → Reconfigurar) o importe un archivo local.", + "panel.dict.checkbox": "Incluir en el escaneo agrupado", + "panel.dict.scanAll": "Escanear diccionarios marcados", + "panel.dict.importLocal": "Importar un archivo local (.dict.json)", + "panel.dict.scanWith": "Escanear el archivo activo con «{0}»", + "panel.dict.remove": "Eliminar este diccionario", + + "panel.corpus.noFolder": "Carpeta de transcripciones no encontrada. Configúrela en los ajustes.", + "panel.corpus.empty": "No se encontraron transcripciones en esta carpeta.", + "panel.corpus.rules": "{0} regla(s)", + "panel.corpus.hasPseudo": "Versión pseudonimizada creada", + "panel.corpus.noPseudo": "Aún no pseudonimizado", + "panel.corpus.hasFinal": "Exporte final {0} presente", + "panel.corpus.noFinal": "Sin exporte final", + "panel.corpus.addClass": "Nueva clase", + "panel.corpus.noClass": "Sin clase", + "panel.corpus.moveTo": "Mover a…", + "panel.corpus.moveToRoot": "Sin clase (raíz)", + "panel.corpus.deleteClass": "Eliminar clase", + "panel.corpus.deleteClassConfirm": "¿Eliminar la clase «{0}»? Los archivos dentro se moverán a la raíz.", + "panel.corpus.exportSettings": "Destino del exporte final", + "panel.corpus.exportDest.vault": "En el vault", + "panel.corpus.exportDest.external": "Carpeta externa", + "panel.corpus.exportFolder": "Carpeta de exporte (vault)", + "panel.corpus.exportExternalPath": "Ruta absoluta", + "panel.corpus.exportExternalPathPlaceholder": "/Volumes/Archives/Exports", + "panel.corpus.exportMirrorClasses": "Replicar estructura de clases", + + "panel.filenameWarning.msg": "El nombre de archivo «{0}» contiene un término pseudonimizable.", + "panel.filenameWarning.edit": "Renombrar…", + "panel.filenameWarning.editTitle": "Introduzca un nombre manualmente", + "panel.filenameWarning.editPrompt": "Nuevo nombre para «{0}»", + "panel.filenameWarning.wandTitle": "Renombrar a «{0}»", + + "panel.exports.pseudonymize": "Crear versión pseudonimizada", + "panel.exports.exportMapping": "Exportar tabla de correspondencia", + "panel.exports.exportVtt": "Reexportar como VTT", + "panel.exports.exportVtt.desc": "Reconstruye un archivo WebVTT pseudonimizado desde el Markdown y las marcas de tiempo (.words.json).", + "panel.exports.exportSrt": "Reexportar como SRT", + "panel.exports.exportSrt.desc": "Reconstruye el archivo SRT pseudonimizado desde el Markdown.", + "panel.exports.exportCha": "Reexportar como CHAT (.cha)", + "panel.exports.exportCha.desc": "Reconstruye el archivo CHAT pseudonimizado desde el Markdown.", + "panel.exports.exportHtml": "Reexportar como HTML", + "panel.exports.exportHtml.desc": "Reconstruye el archivo HTML noScribe pseudonimizado desde el Markdown y las marcas de tiempo (.words.json).", + "notice.htmlExported": "HTML exportado: {0}", + + "panel.ner.scanBtn": "Identificar candidatos", + "panel.ner.hint": "Ajustes del escáner de detección automática de entidades nombradas.", + "panel.ner.threshold": "Umbral de confianza", + "panel.ner.thresholdDesc": "Las entidades con una puntuación inferior a este valor se ignoran. Mayor = menos falsos positivos pero menos detecciones.", + "panel.ner.functionWords": "Palabras funcionales excluidas", + "panel.ner.functionWordsDesc": "Una palabra por línea. Estos tokens nunca se conservarán como entidades nombradas, aunque el modelo los detecte.", + "panel.ner.save": "Guardar", + "panel.ner.saved": "Guardado", + "panel.ner.reset": "Restablecer valores predeterminados", + + "dictScanModal.title": "Revisión del escaneo — diccionarios", + "dictScanModal.summary": "{0} {1} detectada(s) · {2}", + "dictScanModal.summary.entity": "entidad", + "dictScanModal.summary.entities": "entidades", + "dictScanModal.hint": "Desmarque los falsos positivos. El prefijo es editable; el índice se calcula automáticamente.", + "dictScanModal.apply": "Crear {0} {1}", + "dictScanModal.apply.rule": "regla", + "dictScanModal.apply.rules": "reglas", + "dictScanModal.noRules": "No hay reglas que aplicar", + "dictScanModal.cancel": "Cancelar", + + "mappingScanModal.title": "Revisión del escaneo — correspondencias", + "mappingScanModal.summary": "{0} {1} con ocurrencias en «{2}»", + "mappingScanModal.summary.rule": "regla activa", + "mappingScanModal.summary.rules": "reglas activas", + "mappingScanModal.hint": "Desmarque las reglas que no desea aplicar. Los reemplazos se escribirán en el archivo fuente.", + "mappingScanModal.col.source": "", + "mappingScanModal.col.replacement": "Reemplazo", + "mappingScanModal.col.occurrences": "Ocur.", + "mappingScanModal.apply": "Pseudonimizar ({0} {1}, {2} {3})", + "mappingScanModal.apply.rule": "regla", + "mappingScanModal.apply.rules": "reglas", + "mappingScanModal.apply.occurrence": "ocurrencia", + "mappingScanModal.apply.occurrences": "ocurrencias", + "mappingScanModal.noRules": "No hay reglas que aplicar", + "mappingScanModal.cancel": "Cancelar", + "mappingScanModal.saveExceptions": "Guardar excepciones", + "mappingScanModal.exceptionsSaved": "Excepciones guardadas en la tabla de correspondencia.", + "mappingScanModal.countTooltip": "Ver y seleccionar candidatos", + + "onboarding.step.storage": "Almacenamiento", + "onboarding.step.language": "Idioma", + "onboarding.step.welcome": "Bienvenida", + "onboarding.step.ner": "Detección NER", + "onboarding.step.dictionaries": "Diccionarios", + "onboarding.step.summary": "Resumen", + "onboarding.welcome.title": "Pseudonymizer Tool", + "onboarding.welcome.desc": "Este plugin le ayuda a corregir y pseudonimizar transcripciones de entrevistas y corpus interaccionales (convenciones Jefferson, ICOR, formatos .srt, .cha).", + "onboarding.welcome.steps": "El asistente le guiará en unos pocos pasos rápidos:", + "onboarding.welcome.step1": "Elija el idioma de la interfaz.", + "onboarding.welcome.step2": "Elija un motor de detección automática de entidades nombradas (opcional).", + "onboarding.welcome.step3": "Instale diccionarios de candidatos de reemplazo si los necesita.", + "onboarding.welcome.hint": "Puede reconfigurar estas opciones en cualquier momento desde los ajustes del plugin.", + "onboarding.storage.title": "Almacenamiento", + "onboarding.storage.vaultPerCorpus": "Un vault por corpus", + "onboarding.storage.vaultPerCorpusDesc": "Recomendamos usar un vault de Obsidian dedicado por corpus. Esto garantiza que los archivos de correspondencia, los diccionarios y los exportes permanezcan junto a las transcripciones, facilitando compartir o archivar un corpus.", + "onboarding.storage.transcriptionsFolder": "Transcripciones", + "onboarding.storage.mappingFolder": "Tablas de correspondencia", + "onboarding.storage.dictionariesFolder": "Diccionarios", + "onboarding.storage.exportsFolder": "Exportes", + "onboarding.storage.hint": "Estas rutas son relativas a la raíz del vault. Puede modificarlas más tarde en Configuración.", + "onboarding.language.title": "Idioma de la interfaz", + "onboarding.language.hint": "El idioma de la interfaz no afecta al análisis de las transcripciones. Las convenciones (Jefferson, ICOR) son independientes del idioma.", + "onboarding.ner.desc": "Un modelo NER (reconocimiento de entidades nombradas) puede detectar automáticamente nombres propios, apellidos, lugares e instituciones en las transcripciones — sin una lista exhaustiva, ejecutándose localmente en Obsidian.", + "onboarding.ner.tfjsTitle": "transformers.js — modelo ONNX integrado", + "onboarding.ner.tfjsBadge": "Python no requerido", + "onboarding.ner.tfjsDesc": "Modelo NER multilingüe ejecutado localmente en el vault. Los archivos WASM (~19 MB) se descargan una vez. El modelo (~66 MB) se descarga en el primer escaneo.", + "onboarding.ner.wasmReady": "Archivos WASM ya instalados", + "onboarding.ner.installBtn": "Instalar y usar transformers.js", + "onboarding.ner.selectedBtn": "Seleccionado", + "onboarding.ner.noneActive": "Desactivado (solo reglas manuales)", + "onboarding.ner.noneSkip": "Omitir — trabajaré manualmente", + + "onboarding.dict.title": "Diccionarios de candidatos de reemplazo", + "onboarding.dict.desc": "Los diccionarios proporcionan candidatos de reemplazo (ciudades, nombres…) y alimentan la detección automática. Están alojados en un repositorio dedicado y se descargan al vault — ningún dato de transcripción sale de Obsidian.", + "onboarding.dict.catalogueLoading": "Cargando catálogo…", + "onboarding.dict.catalogueError": "No se puede acceder al catálogo en línea. Compruebe su conexión o importe un archivo local a continuación.", + "onboarding.dict.recommended": "Recomendado", + "onboarding.dict.col.dict": "Diccionario", + "onboarding.dict.col.lang": "Idioma", + "onboarding.dict.col.roles": "Roles", + "onboarding.dict.col.size": "Tamaño", + "onboarding.dict.role.detection": "detección", + "onboarding.dict.role.replacement": "reemplazo", + "onboarding.dict.role.classes": "clases", + "onboarding.dict.install": "Instalar", + "onboarding.dict.reinstall": "Reinstalar", + "onboarding.dict.installed": "Instalado", + "onboarding.dict.manualHint": "¿Ya tiene un archivo .dict.json? Impórtelo manualmente:", + "onboarding.dict.importBtn": "Importar un archivo local (.dict.json)", + "onboarding.dict.listTitle": "Diccionarios instalados en este vault ({0}):", + "onboarding.dict.remove": "Eliminar este diccionario", + + "onboarding.summary.ner.none": "Desactivado — solo reglas manuales", + "onboarding.summary.ner.tfjs": "transformers.js — modelo ONNX (descarga en el primer escaneo)", + + "onboarding.nav.cancel": "Cancelar", + "onboarding.nav.back": "Atrás", + "onboarding.nav.skip": "Omitir este paso", + "onboarding.nav.next": "Siguiente", + "onboarding.nav.start": "Comenzar", + "onboarding.nav.finish": "Empezar a trabajar", + "onboarding.summary.title": "Configuración completa", + "onboarding.summary.intro": "Esto es lo que se ha configurado:", + "onboarding.summary.ner": "Detección NER", + "onboarding.summary.dicts": "Diccionarios", + "onboarding.summary.dicts.count": "{0} archivo(s) en el vault", + "onboarding.summary.hint": "Estos ajustes pueden cambiarse en cualquier momento desde la configuración del plugin: Reconfigurar.", + + "settings.heading.general": "General", + "settings.heading.textDetection": "Detección de texto", + "settings.heading.pseudonymization": "Pseudonimización", + "settings.heading.ner": "Detección automática (NER)", + "settings.heading.storage": "Almacenamiento", + "settings.heading.security": "Seguridad", + "settings.language": "Idioma de la interfaz", + "settings.languageDesc": "No afecta al análisis de transcripciones. Las convenciones (Jefferson, ICOR) son independientes del idioma.", + "settings.vaultPerCorpus": "Un vault por corpus", + "settings.vaultPerCorpusDesc": "Recomendamos usar un vault de Obsidian dedicado por corpus. Esto garantiza que los archivos de correspondencia, los diccionarios y los exportes permanezcan junto a las transcripciones.", + "settings.transcriptionsFolder": "Transcripciones importadas", + "settings.transcriptionsFolderDesc": "Carpeta de destino para las transcripciones importadas.", + "settings.mappingFolder": "Tablas de correspondencia", + "settings.mappingFolderDesc": "Ruta relativa en el vault.", + "settings.dictionariesFolder": "Diccionarios", + "settings.dictionariesFolderDesc": "Ruta relativa en el vault.", + "settings.exportsFolder": "Exportes", + "settings.accentSensitive": "Sensible a los acentos", + "settings.preserveCase": "Preservar mayúsculas", + "settings.preserveCaseDesc": "Adaptar las mayúsculas del reemplazo para que coincidan con la fuente (p. ej. JUAN → PEDRO, juan → pedro, Juan → Pedro).", + "settings.preserveAnalyticNotation": "Preservar notación analítica", + "settings.preserveAnalyticNotationDesc": "No reemplazar nunca los símbolos de convención analítica de tipo Jefferson o ICOR.", + "settings.useMarkerInExport": "Añadir marcador alrededor de los pseudónimos en el exporte", + "settings.useMarkerInExportDesc": "Permite identificar visualmente los términos pseudonimizados en el archivo exportado.", + "settings.markerOpen": "Marcador de apertura", + "settings.markerOpenDesc": "Ejemplo: {{ ⟦ [ «", + "settings.markerClose": "Marcador de cierre", + "settings.markerCloseDesc": "Ejemplo: }} ⟧ ] »", + "settings.nerBackend": "Motor de detección", + "settings.nerBackendDesc": "Backend usado para la detección automática de entidades nombradas identificadoras.", + "settings.nerBackend.none": "Desactivado — solo reglas manuales", + "settings.nerBackend.tfjs": "transformers.js (modelo ONNX integrado)", + "settings.reconfigure": "Asistente de configuración", + "settings.reconfigureDesc2": "Relanzar el asistente para reconfigurar la detección NER y los diccionarios.", + "settings.reconfigureBtn2": "Reconfigurar…", + "settings.warnIfSyncedFolder": "Avisar si la carpeta está sincronizada", + "settings.warnIfSyncedFolderDesc": "Alertar si las tablas de correspondencia están en una carpeta Git, iCloud o Synology Drive.", + "settings.caseSensitive": "Sensible a mayúsculas", + "settings.caseSensitiveDesc": "Si está activado, «Juan» y «juan» se tratan como distintos.", + "settings.wholeWordOnly": "Solo palabras completas", + "settings.wholeWordOnlyDesc": "Si está activado, solo se reemplazan las ocurrencias de palabras completas." +} diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index e76c360..d5a32b3 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -201,7 +201,6 @@ "panel.corpus.deleteClassConfirm": "Supprimer la classe « {0} » ? Les fichiers à l'intérieur seront déplacés à la racine.", "panel.corpus.exportSettings": "Destination des exports finaux", "panel.corpus.exportDest.vault": "Dans le vault", - "panel.corpus.exportDest.nextToSource": "À côté du fichier source", "panel.corpus.exportDest.external": "Dossier externe", "panel.corpus.exportFolder": "Dossier d'export (vault)", "panel.corpus.exportExternalPath": "Chemin absolu", @@ -220,8 +219,12 @@ "panel.exports.exportSrt.desc": "Reconstruit le fichier SRT pseudonymisé à partir du Markdown.", "panel.exports.exportCha": "Re-exporter en CHAT (.cha)", "panel.exports.exportCha.desc": "Reconstruit le fichier CHAT pseudonymisé à partir du Markdown.", + "panel.exports.exportHtml": "Re-exporter en HTML", + "panel.exports.exportHtml.desc": "Reconstruit le fichier HTML noScribe pseudonymisé à partir du Markdown et des timestamps (.words.json).", + "notice.htmlExported": "HTML exporté : {0}", "command.exportAsSrt": "Exporter en SRT", "command.exportAsCha": "Exporter en CHAT", + "command.exportAsHtml": "Exporter en HTML", "panel.ner.scanBtn": "Identifier des candidats", "panel.ner.hint": "Paramètres du scanner de détection automatique des entités nommées.", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json new file mode 100644 index 0000000..6379fb7 --- /dev/null +++ b/src/i18n/locales/ru.json @@ -0,0 +1,378 @@ +{ + "corpus.modal.title": "Организовать корпус", + "corpus.modal.hint": "Каждый класс создаёт зеркальные папки для транскрипций, таблиц соответствия и экспортов.", + "corpus.modal.noClasses": "Классы не определены. Добавьте один, чтобы организовать документы по папкам.", + "corpus.modal.addClass": "Добавить класс", + "corpus.modal.classNamePlaceholder": "Название класса (например, Сессия_А, Информант_1…)", + "corpus.modal.classNameEmpty": "Название класса не может быть пустым.", + "corpus.modal.classExists": "Класс с таким названием уже существует.", + "corpus.modal.deleteConfirm": "Удалить класс «{0}»? Папки будут удалены из хранилища (файлы внутри будут перемещены в корзину).", + "corpus.modal.files": "{0} файл(ов)", + "corpus.modal.close": "Закрыть", + "corpus.select.title": "В какой класс добавить?", + "corpus.select.hint": "Выберите класс (папку) для этой транскрипции.", + "corpus.select.none": "Без класса (корень)", + "corpus.select.confirm": "Добавить", + "quickModal.title": "Псевдонимизировать", + "quickModal.source": "Выбранное выражение", + "quickModal.sourceDesc": "Заменяемый термин — нельзя изменить", + "quickModal.replaceBy": "Заменить на", + "quickModal.replacementPlaceholder": "Псевдоним или аналитическая категория", + "quickModal.scope": "Область замены", + "quickModal.scopeFile": "Все вхождения в файле", + "quickModal.scopeOccurrence": "Только это вхождение", + "quickModal.submit": "Псевдонимизировать", + "notice.appliedOccurrence": "✓ «{0}» → «{1}» (это вхождение)", + "notice.appliedFile": "✓ «{0}» → «{1}» ({2} вхождений)", + "onboarding.dict.importOk": "{0} словарь импортирован", + "onboarding.dict.importOkMany": "{0} словарей импортировано", + "onboarding.dict.importErr": "{0} файл отклонён", + "onboarding.dict.importErrMany": "{0} файлов отклонено", + "onboarding.dict.importError": "Ошибка импорта {0} — проверьте формат .dict.json", + "notice.noActiveFile": "Нет активного файла.", + "notice.formatUnsupported": "Неподдерживаемый формат: .{0}", + "notice.noRules": "Нет правил для этого файла.\nСоздайте правила через Ctrl+P → «Создать правило».", + "notice.noOccurrences": "Вхождений для активных правил не найдено.", + "notice.noNerEntities": "NER не обнаружил сущностей.", + "notice.nerEntitiesFound": "✓ Обнаружено {0} {1} — выделено синим.\nПравой кнопкой мыши — создать правило.", + "notice.nerEntitiesFound.entity": "сущность", + "notice.nerEntitiesFound.entities": "сущностей", + "notice.nerNotEnabled": "Детектирование через transformers.js не включено.\nВключите в Настройках → Pseudonymizer Tool.", + "notice.noDictDetection": "Словарь обнаружения не загружен.\nУстановите словарь в панели «Словари».", + "notice.noDictEntities": "Сущностей в словарях обнаружения не найдено.", + "notice.noDictReplacements": "Нет доступных замен для найденных сущностей.", + "notice.ruleDeleted": "✓ Правило удалено — «{0}» восстановлено в активном файле", + "notice.ruleCreated": "✓ Правило создано: «{0}» → «{1}»", + "notice.rulesCreated": "✓ {0} {1} создано", + "notice.rulesCreated.rule": "правило", + "notice.rulesCreated.rules": "правил", + "notice.occurrencesPseudonymized": "✓ {0} {1} псевдонимизировано в «{2}»", + "notice.occurrencesPseudonymized.occurrence": "вхождение", + "notice.occurrencesPseudonymized.occurrences": "вхождений", + "notice.converted": "✓ {0} → {1}", + "notice.conversionError": "Ошибка конвертации {0}: {1}", + "notice.fileExists": "Файл уже существует в хранилище: {0}", + "notice.conversionSkipped": "⚠ {0}.md уже существует — конвертация пропущена для {1}", + "notice.exportDone": "✓ {0} правил(о) применено\n→ {1}", + "notice.mappingExported": "✓ Таблица соответствия экспортирована → {0}", + "notice.noMapping": "Таблица соответствия для {0} не найдена", + "notice.invalidFormat": "Неверный формат: {0}", + "notice.dictImported": "✓ {0} {1} импортировано", + "notice.dictImported.one": "словарь", + "notice.dictImported.many": "словарей", + "notice.dictRejected": "{0} {1} отклонено", + "notice.dictRejected.one": "файл", + "notice.dictRejected.many": "файлов", + "notice.nerModelLoaded": "✓ Модель NER загружена", + "notice.nerModelLoading": "Загрузка модели NER (первый запуск — ~66 МБ)…", + "notice.ruleNotFound": "Правило не найдено в таблицах соответствия.", + "notice.noCheckedDicts": "Ни один словарь не выбран.", + "notice.audioImported": "Аудио импортировано: {0}", + "occurrencesCtx.hint": "{0} вхождени{1} — выберите нужные для замены.", + "occurrencesCtx.hint.s": "й", + "occurrencesCtx.hint.empty": "е", + "occurrencesCtx.validateAll": "Выбрать все", + "occurrencesCtx.ignoreAll": "Пропустить все", + "occurrencesCtx.cancel": "Отмена", + "occurrencesCtx.confirm": "Подтвердить выбор", + "occurrencesCtx.btn.validate": "Выбрать", + "occurrencesCtx.btn.ignore": "Пропустить", + "occurrencesCtx.btn.fp": "Ложное срабатывание", + "occurrencesCtx.status.ignored": "Оставлено как есть", + "occurrencesCtx.status.fp": "Ложное срабатывание — исключено", + "notice.notNoScribeFormat": "Этот файл не является транскрипцией noScribe (ожидается pseudobs-format: vtt или html).", + "notice.wordsJsonMissing": "Файл временных меток не найден: {0}.words.json", + "notice.vttMismatch": "Предупреждение: количество реплик в Markdown и words.json различается — экспортированный VTT может быть неполным.", + "notice.vttExported": "VTT экспортирован: {0}", + + "command.organizeCorpus": "Организовать корпус", + "command.addTranscription": "Добавить транскрипцию", + "command.pseudonymizeFile": "Псевдонимизировать текущий файл", + "command.createRule": "Создать правило замены", + "command.scanFile": "Сканировать текущий файл", + "command.scanNer": "Сканировать файл с NER-детектированием", + "command.scanDictionaries": "Сканировать файл по словарям", + "command.pseudonymizeSelection": "Псевдонимизировать выделение", + "command.exportAsVtt": "Экспортировать как VTT", + "command.exportAsSrt": "Экспортировать как SRT", + "command.exportAsCha": "Экспортировать как CHAT", + "command.exportAsHtml": "Экспортировать как HTML", + "command.openPanel": "Псевдонимизация: открыть панель", + + "contextMenu.declareException": "Объявить «{0}» исключением здесь", + "contextMenu.cancelPseudonymization": "Отменить псевдонимизацию «{0}»", + "contextMenu.editRule": "Изменить правило для «{0}»", + "contextMenu.pseudonymize": "Псевдонимизировать «{0}»", + "contextMenu.redact": "Скрыть «{0}»", + "contextMenu.coulmont": "Псевдонимизировать с проф. Батистом Кульмоном", + "contextMenu.createRule": "Создать правило замены…", + "redaction.checkbox": "Скрыть (🀫)", + "redaction.checkboxDesc": "Заменить символами 🀫 (по одному на слог) — для несущественного идентифицирующего содержания", + + "ruleModal.delete": "Удалить правило", + "ruleModal.scopeWarnTitle": "Лонгитюдная область", + "ruleModal.scopeOkTitle": "Область файла", + "ruleModal.scopeOk": "Хорошая практика. Этот псевдоним будет применяться только к этой транскрипции — каждый файл остаётся независимым.", + "ruleModal.title": "Создать правило замены", + "ruleModal.source": "Источник", + "ruleModal.sourceDesc": "Исходный термин для замены", + "ruleModal.replacement": "Замена", + "ruleModal.replacementDesc": "Псевдоним или аналитическая категория", + "ruleModal.replacementDescClass": "Точный индекс будет рассчитан при создании на основе существующих правил в области.", + "ruleModal.category": "Категория", + "ruleModal.scope": "Область", + "ruleModal.scopeFile": "Только этот файл", + "ruleModal.scopeFolder": "Эта папка", + "ruleModal.scopeVault": "Всё хранилище", + "ruleModal.priority": "Приоритет", + "ruleModal.priorityDesc": "Целое число, как CSS z-index — по умолчанию 0, чем выше — тем раньше применяется", + "ruleModal.submit": "Создать правило", + "ruleModal.errorMissing": "Источник и замена обязательны.", + "ruleModal.errorNoFile": "Нет активного файла.", + "ruleModal.coulomontLabel": "Предложения эквивалентных имён — выберите:", + "ruleModal.dictLabel": "Словарь: «{0}» → класс {1}", + "ruleModal.dictUseClass": "Использовать «{0}» (область: {1})", + + "category.first_name": "Имя", + "category.last_name": "Фамилия", + "category.full_name": "Полное имя", + "category.place": "Место", + "category.institution": "Учреждение", + "category.date": "Дата", + "category.age": "Возраст", + "category.profession": "Профессия", + "category.custom": "Другое", + + "scope.file": "Файл", + "scope.folder": "Папка", + "scope.vault": "Хранилище", + + "status.validated": "Активно", + "status.ignored": "Игнорируется", + "status.partial": "Частично", + "status.suggested": "Предложено", + "status.conflict": "Конфликт", + "status.disabled": "Неактивно", + "status.needs_review": "На проверке", + + "panel.tab.mappings": "Соответствия", + "panel.tab.dictionaries": "Словари", + "panel.tab.exports": "Экспорты", + "panel.tab.ner": "NER", + "panel.tab.corpus": "Корпус", + + "panel.mappings.filterActive": "Только активный файл", + "panel.mappings.warnBroadName": "⚠ Распространение имени или фамилии за пределы файла следует делать только как обдуманный аналитический выбор — например, для последовательного отслеживания одного участника в лонгитюдном исследовании. Даже если один и тот же человек фигурирует в нескольких файлах, псевдонимизация на уровне файла предпочтительнее: она сохраняет независимость каждой транскрипции и снижает риск идентификации через перекрёстный анализ.", + "panel.mappings.addRule": "Добавить правило", + "panel.mappings.scanFile": "Сканировать файл", + "panel.mappings.scanning": "Сканирование…", + "panel.mappings.noFile": "Нет активного файла.", + "panel.mappings.noRules": "Нет правил для {0}.", + "panel.mappings.noRulesHint": "Нет активных правил для этого файла. Создайте правила через контекстное меню или команду «Создать правило».", + "panel.mappings.col.source": "Источник", + "panel.mappings.col.replacement": "Замена", + "panel.mappings.col.category": "Категория", + "panel.mappings.col.scope": "Область", + "panel.mappings.col.priority": "П.", + "panel.mappings.col.status": "Статус", + "panel.mappings.exceptions": "Исключения", + "panel.mappings.exceptions.hint": "Вхождения, явно проигнорированные для этого правила.", + "panel.mappings.exceptions.delete": "Удалить это исключение", + + "panel.dict.noneInstalled": "Словари не установлены. Установите через мастер (Настройки → Перенастроить) или импортируйте локальный файл.", + "panel.dict.checkbox": "Включить в групповое сканирование", + "panel.dict.scanAll": "Сканировать отмеченные словари", + "panel.dict.importLocal": "Импортировать локальный файл (.dict.json)", + "panel.dict.scanWith": "Сканировать активный файл по «{0}»", + "panel.dict.remove": "Удалить этот словарь", + + "panel.corpus.noFolder": "Папка транскрипций не найдена. Настройте её в параметрах.", + "panel.corpus.empty": "В этой папке транскрипций не найдено.", + "panel.corpus.rules": "{0} правил(о)", + "panel.corpus.hasPseudo": "Псевдонимизированная версия создана", + "panel.corpus.noPseudo": "Ещё не псевдонимизировано", + "panel.corpus.hasFinal": "Финальный экспорт {0} присутствует", + "panel.corpus.noFinal": "Нет финального экспорта", + "panel.corpus.addClass": "Новый класс", + "panel.corpus.noClass": "Без класса", + "panel.corpus.moveTo": "Переместить в…", + "panel.corpus.moveToRoot": "Без класса (корень)", + "panel.corpus.deleteClass": "Удалить класс", + "panel.corpus.deleteClassConfirm": "Удалить класс «{0}»? Файлы внутри будут перемещены в корень.", + "panel.corpus.exportSettings": "Назначение финального экспорта", + "panel.corpus.exportDest.vault": "В хранилище", + "panel.corpus.exportDest.external": "Внешняя папка", + "panel.corpus.exportFolder": "Папка экспорта (хранилище)", + "panel.corpus.exportExternalPath": "Абсолютный путь", + "panel.corpus.exportExternalPathPlaceholder": "/Volumes/Archives/Exports", + "panel.corpus.exportMirrorClasses": "Зеркалировать структуру классов", + + "panel.filenameWarning.msg": "Имя файла «{0}» содержит псевдонимизируемый термин.", + "panel.filenameWarning.edit": "Переименовать…", + "panel.filenameWarning.editTitle": "Введите имя вручную", + "panel.filenameWarning.editPrompt": "Новое имя для «{0}»", + "panel.filenameWarning.wandTitle": "Переименовать в «{0}»", + + "panel.exports.pseudonymize": "Создать псевдонимизированную версию", + "panel.exports.exportMapping": "Экспортировать таблицу соответствия", + "panel.exports.exportVtt": "Повторно экспортировать как VTT", + "panel.exports.exportVtt.desc": "Восстанавливает псевдонимизированный WebVTT из Markdown и временных меток (.words.json).", + "panel.exports.exportSrt": "Повторно экспортировать как SRT", + "panel.exports.exportSrt.desc": "Восстанавливает псевдонимизированный SRT из Markdown.", + "panel.exports.exportCha": "Повторно экспортировать как CHAT (.cha)", + "panel.exports.exportCha.desc": "Восстанавливает псевдонимизированный CHAT из Markdown.", + "panel.exports.exportHtml": "Повторно экспортировать как HTML", + "panel.exports.exportHtml.desc": "Восстанавливает псевдонимизированный HTML noScribe из Markdown и временных меток (.words.json).", + "notice.htmlExported": "HTML экспортирован: {0}", + + "panel.ner.scanBtn": "Определить кандидатов", + "panel.ner.hint": "Настройки автоматического сканера распознавания именованных сущностей.", + "panel.ner.threshold": "Порог уверенности", + "panel.ner.thresholdDesc": "Сущности с оценкой ниже этого значения игнорируются. Выше = меньше ложных срабатываний, но меньше обнаружений.", + "panel.ner.functionWords": "Исключённые служебные слова", + "panel.ner.functionWordsDesc": "По одному слову на строку. Эти токены никогда не будут сохранены как именованные сущности, даже если обнаружены моделью.", + "panel.ner.save": "Сохранить", + "panel.ner.saved": "Сохранено", + "panel.ner.reset": "Сбросить до значений по умолчанию", + + "dictScanModal.title": "Проверка сканирования по словарю", + "dictScanModal.summary": "{0} {1} обнаружено · {2}", + "dictScanModal.summary.entity": "сущность", + "dictScanModal.summary.entities": "сущностей", + "dictScanModal.hint": "Снимите галочки с ложных срабатываний. Префикс редактируется; индекс рассчитывается автоматически.", + "dictScanModal.apply": "Создать {0} {1}", + "dictScanModal.apply.rule": "правило", + "dictScanModal.apply.rules": "правил", + "dictScanModal.noRules": "Нет правил для применения", + "dictScanModal.cancel": "Отмена", + + "mappingScanModal.title": "Проверка сканирования соответствий", + "mappingScanModal.summary": "{0} {1} с вхождениями в «{2}»", + "mappingScanModal.summary.rule": "активное правило", + "mappingScanModal.summary.rules": "активных правил", + "mappingScanModal.hint": "Снимите галочки с правил, которые не нужно применять. Замены будут записаны в исходный файл.", + "mappingScanModal.col.source": "", + "mappingScanModal.col.replacement": "Замена", + "mappingScanModal.col.occurrences": "Вхожд.", + "mappingScanModal.apply": "Псевдонимизировать ({0} {1}, {2} {3})", + "mappingScanModal.apply.rule": "правило", + "mappingScanModal.apply.rules": "правил", + "mappingScanModal.apply.occurrence": "вхождение", + "mappingScanModal.apply.occurrences": "вхождений", + "mappingScanModal.noRules": "Нет правил для применения", + "mappingScanModal.cancel": "Отмена", + "mappingScanModal.saveExceptions": "Сохранить исключения", + "mappingScanModal.exceptionsSaved": "Исключения сохранены в таблицу соответствия.", + "mappingScanModal.countTooltip": "Просмотр и выбор кандидатов", + + "onboarding.step.storage": "Хранилище", + "onboarding.step.language": "Язык", + "onboarding.step.welcome": "Добро пожаловать", + "onboarding.step.ner": "Детектирование NER", + "onboarding.step.dictionaries": "Словари", + "onboarding.step.summary": "Итоги", + "onboarding.welcome.title": "Pseudonymizer Tool", + "onboarding.welcome.desc": "Этот плагин помогает корректировать и псевдонимизировать транскрипции интервью и интеракциональных корпусов (конвенции Jefferson, ICOR, форматы .srt, .cha).", + "onboarding.welcome.steps": "Мастер проведёт вас через несколько быстрых шагов:", + "onboarding.welcome.step1": "Выберите язык интерфейса.", + "onboarding.welcome.step2": "Выберите движок автоматического распознавания именованных сущностей (необязательно).", + "onboarding.welcome.step3": "Установите словари кандидатов для замены, если необходимо.", + "onboarding.welcome.hint": "Эти параметры можно изменить в любое время в настройках плагина.", + "onboarding.storage.title": "Хранилище", + "onboarding.storage.vaultPerCorpus": "Одно хранилище на корпус", + "onboarding.storage.vaultPerCorpusDesc": "Рекомендуем использовать отдельное хранилище Obsidian для каждого корпуса. Это позволяет хранить таблицы соответствия, словари и экспорты вместе с транскрипциями, упрощая обмен и архивирование корпуса.", + "onboarding.storage.transcriptionsFolder": "Папка транскрипций", + "onboarding.storage.mappingFolder": "Таблицы соответствия", + "onboarding.storage.dictionariesFolder": "Словари", + "onboarding.storage.exportsFolder": "Экспорты", + "onboarding.storage.hint": "Эти пути указаны относительно корня хранилища. Их можно изменить позже в Настройках.", + "onboarding.language.title": "Язык интерфейса", + "onboarding.language.hint": "Язык интерфейса не влияет на анализ транскрипций. Конвенции (Jefferson, ICOR) не зависят от языка.", + "onboarding.ner.desc": "Модель NER (распознавание именованных сущностей) может автоматически обнаруживать имена, фамилии, места и учреждения в транскрипциях — без исчерпывающего списка, локально в Obsidian.", + "onboarding.ner.tfjsTitle": "transformers.js — встроенная модель ONNX", + "onboarding.ner.tfjsBadge": "Python не требуется", + "onboarding.ner.tfjsDesc": "Многоязычная модель NER, работающая локально в хранилище. WASM-файлы (~19 МБ) загружаются один раз. Модель (~66 МБ) загружается при первом сканировании.", + "onboarding.ner.wasmReady": "WASM-файлы уже установлены", + "onboarding.ner.installBtn": "Установить и использовать transformers.js", + "onboarding.ner.selectedBtn": "Выбрано", + "onboarding.ner.noneActive": "Отключено (только ручные правила)", + "onboarding.ner.noneSkip": "Пропустить — буду работать вручную", + + "onboarding.dict.title": "Словари кандидатов для замены", + "onboarding.dict.desc": "Словари предоставляют кандидатов для замены (города, имена…) и поддерживают автоматическое обнаружение. Они размещены в специальном репозитории и загружаются в хранилище — данные транскрипций не покидают Obsidian.", + "onboarding.dict.catalogueLoading": "Загрузка каталога…", + "onboarding.dict.catalogueError": "Не удалось подключиться к онлайн-каталогу. Проверьте соединение или импортируйте локальный файл ниже.", + "onboarding.dict.recommended": "Рекомендуется", + "onboarding.dict.col.dict": "Словарь", + "onboarding.dict.col.lang": "Язык", + "onboarding.dict.col.roles": "Роли", + "onboarding.dict.col.size": "Размер", + "onboarding.dict.role.detection": "обнаружение", + "onboarding.dict.role.replacement": "замена", + "onboarding.dict.role.classes": "классы", + "onboarding.dict.install": "Установить", + "onboarding.dict.reinstall": "Переустановить", + "onboarding.dict.installed": "Установлено", + "onboarding.dict.manualHint": "Уже есть файл .dict.json? Импортируйте вручную:", + "onboarding.dict.importBtn": "Импортировать локальный файл (.dict.json)", + "onboarding.dict.listTitle": "Словари, установленные в этом хранилище ({0}):", + "onboarding.dict.remove": "Удалить этот словарь", + + "onboarding.summary.ner.none": "Отключено — только ручные правила", + "onboarding.summary.ner.tfjs": "transformers.js — модель ONNX (загрузка при первом сканировании)", + + "onboarding.nav.cancel": "Отмена", + "onboarding.nav.back": "Назад", + "onboarding.nav.skip": "Пропустить этот шаг", + "onboarding.nav.next": "Далее", + "onboarding.nav.start": "Начать", + "onboarding.nav.finish": "Приступить к работе", + "onboarding.summary.title": "Настройка завершена", + "onboarding.summary.intro": "Вот что было настроено:", + "onboarding.summary.ner": "Детектирование NER", + "onboarding.summary.dicts": "Словари", + "onboarding.summary.dicts.count": "{0} файл(ов) в хранилище", + "onboarding.summary.hint": "Эти настройки можно изменить в любое время в параметрах плагина: Перенастроить.", + + "settings.heading.general": "Общие", + "settings.heading.textDetection": "Обнаружение текста", + "settings.heading.pseudonymization": "Псевдонимизация", + "settings.heading.ner": "Автоматическое обнаружение (NER)", + "settings.heading.storage": "Хранилище", + "settings.heading.security": "Безопасность", + "settings.language": "Язык интерфейса", + "settings.languageDesc": "Не влияет на анализ транскрипций. Конвенции (Jefferson, ICOR) не зависят от языка.", + "settings.vaultPerCorpus": "Одно хранилище на корпус", + "settings.vaultPerCorpusDesc": "Рекомендуем использовать отдельное хранилище Obsidian для каждого корпуса. Это гарантирует, что таблицы соответствия, словари и экспорты хранятся вместе с транскрипциями.", + "settings.transcriptionsFolder": "Импортированные транскрипции", + "settings.transcriptionsFolderDesc": "Папка назначения для импортированных транскрипций.", + "settings.mappingFolder": "Таблицы соответствия", + "settings.mappingFolderDesc": "Относительный путь в хранилище.", + "settings.dictionariesFolder": "Словари", + "settings.dictionariesFolderDesc": "Относительный путь в хранилище.", + "settings.exportsFolder": "Экспорты", + "settings.accentSensitive": "Учитывать акценты", + "settings.preserveCase": "Сохранять регистр", + "settings.preserveCaseDesc": "Адаптировать регистр замены под исходный (например, ИВАН → ПЁТР, иван → пётр, Иван → Пётр).", + "settings.preserveAnalyticNotation": "Сохранять аналитическую нотацию", + "settings.preserveAnalyticNotationDesc": "Никогда не заменять символы аналитических конвенций Jefferson или ICOR.", + "settings.useMarkerInExport": "Добавлять маркер вокруг псевдонимов в экспорте", + "settings.useMarkerInExportDesc": "Позволяет визуально идентифицировать псевдонимизированные термины в экспортированном файле.", + "settings.markerOpen": "Открывающий маркер", + "settings.markerOpenDesc": "Пример: {{ ⟦ [ «", + "settings.markerClose": "Закрывающий маркер", + "settings.markerCloseDesc": "Пример: }} ⟧ ] »", + "settings.nerBackend": "Движок обнаружения", + "settings.nerBackendDesc": "Бэкенд для автоматического обнаружения идентифицирующих именованных сущностей.", + "settings.nerBackend.none": "Отключено — только ручные правила", + "settings.nerBackend.tfjs": "transformers.js (встроенная модель ONNX)", + "settings.reconfigure": "Мастер настройки", + "settings.reconfigureDesc2": "Перезапустить мастер для перенастройки NER-детектирования и словарей.", + "settings.reconfigureBtn2": "Перенастроить…", + "settings.warnIfSyncedFolder": "Предупреждать о синхронизированной папке", + "settings.warnIfSyncedFolderDesc": "Предупреждать, если таблицы соответствия находятся в папке Git, iCloud или Synology Drive.", + "settings.caseSensitive": "Учитывать регистр", + "settings.caseSensitiveDesc": "Если включено, «Иван» и «иван» считаются разными.", + "settings.wholeWordOnly": "Только целые слова", + "settings.wholeWordOnlyDesc": "Если включено, заменяются только вхождения полных слов." +} diff --git a/src/main.ts b/src/main.ts index 066d7c3..0cabcc6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,7 +22,7 @@ import { ChatParser } from './parsers/ChatParser'; import { VttParser } from './parsers/VttParser'; import { NoScribeHtmlParser } from './parsers/NoScribeHtmlParser'; import { NoScribeVttParser } from './parsers/NoScribeVttParser'; -import { srtToMarkdown, chatToMarkdown, vttToMarkdown, noScribeHtmlToMarkdown, extractWordData, markdownToVtt, markdownToSrt, markdownToCha, type VttCueData } from './parsers/TranscriptConverter'; +import { srtToMarkdown, chatToMarkdown, vttToMarkdown, noScribeHtmlToMarkdown, extractWordData, markdownToVtt, markdownToSrt, markdownToCha, markdownToNoScribeHtml, type VttCueData } from './parsers/TranscriptConverter'; import { MappingStore } from './mappings/MappingStore'; import { ScopeResolver } from './mappings/ScopeResolver'; import { PseudonymizationEngine } from './pseudonymizer/PseudonymizationEngine'; @@ -183,6 +183,12 @@ export default class PseudObsPlugin extends Plugin { callback: () => void this.exportCurrentFileAsFormat('cha'), }); + this.addCommand({ + id: 'export-as-html', + name: t('command.exportAsHtml'), + callback: () => void this.exportCurrentFileAsHtml(), + }); + this.addCommand({ id: 'pseudonymize-selection', name: t('command.pseudonymizeSelection'), @@ -389,6 +395,26 @@ export default class PseudObsPlugin extends Plugin { * Calcule le chemin de l'export final selon les paramètres de destination. * Retourne { vaultPath } si dans le vault, { externalPath } si hors vault. */ + /** Retourne le sous-dossier de classe (vide si pas de mirroring ou pas de classe). */ + private getClassSub(file: TFile): string { + const s = this.settings; + if (!s.exportMirrorClasses) return ''; + const transcRoot = s.transcriptionsFolder; + const fileFolder = file.parent?.path ?? ''; + const rawBase = file.basename.replace(/\.pseudonymized$/, ''); + if (fileFolder.startsWith(transcRoot)) { + return fileFolder.slice(transcRoot.length).replace(/^\//, ''); + } + const mappingFile = this.findInMappings(`${rawBase}.mapping.json`); + if (mappingFile) { + const mFolder = mappingFile.parent?.path ?? ''; + if (mFolder.startsWith(s.mappingFolder)) { + return mFolder.slice(s.mappingFolder.length).replace(/^\//, ''); + } + } + return ''; + } + resolveExportPath( file: TFile, ext: string, @@ -396,41 +422,13 @@ export default class PseudObsPlugin extends Plugin { const s = this.settings; const rawBase = file.basename.replace(/\.pseudonymized$/, ''); const outputName = `${rawBase}.pseudonymized.${ext}`; - const fileFolder = file.parent?.path ?? ''; - - // Sous-dossier de classe pour le mirroring. - // Si le fichier est dans transcriptionsFolder, on le lit directement. - // Sinon (fichier pseudonymisé dans exports/), on remonte via l'emplacement - // du .mapping.json qui reflète fidèlement la structure de classes. - let classSub = ''; - if (s.exportMirrorClasses) { - const transcRoot = s.transcriptionsFolder; - const fileFolder = file.parent?.path ?? ''; - if (fileFolder.startsWith(transcRoot)) { - classSub = fileFolder.slice(transcRoot.length).replace(/^\//, ''); - } else { - // Retrouver la classe depuis l'emplacement du mapping - const mappingFile = this.findInMappings(`${rawBase}.mapping.json`); - if (mappingFile) { - const mFolder = mappingFile.parent?.path ?? ''; - const mRoot = s.mappingFolder; - if (mFolder.startsWith(mRoot)) { - classSub = mFolder.slice(mRoot.length).replace(/^\//, ''); - } - } - } - } - + const classSub = this.getClassSub(file); const join = (base: string) => classSub ? `${base}/${classSub}/${outputName}` : `${base}/${outputName}`; - if (s.exportDestinationType === 'next-to-source') { - return { vaultPath: `${fileFolder}/${outputName}`, externalPath: null }; - } if (s.exportDestinationType === 'external' && s.exportExternalPath) { return { vaultPath: null, externalPath: join(s.exportExternalPath) }; } - // 'vault' (défaut) return { vaultPath: join(s.exportFinalFolder || s.exportsFolder), externalPath: null }; } @@ -879,6 +877,12 @@ export default class PseudObsPlugin extends Plugin { // --- Conversion automatique --- private async autoConvert(file: TFile): Promise { + // Ne pas convertir les fichiers produits par le plugin lui-même + const s = this.settings; + const fileFolder = file.parent?.path ?? ''; + const outputFolders = [s.exportsFolder, s.mappingFolder, s.exportFinalFolder].filter(Boolean); + if (outputFolders.some((f) => fileFolder === f || fileFolder.startsWith(f + '/'))) return; + try { const raw = await this.app.vault.read(file); const ext = file.extension.toLowerCase(); @@ -1141,8 +1145,12 @@ export default class PseudObsPlugin extends Plugin { pseudonymized = engine.pseudonymize(content, rules, marker); } - await this.ensureFolder(this.settings.exportsFolder); - const outputPath = `${this.settings.exportsFolder}/${file.basename}.pseudonymized.${ext}`; + const classSub = this.getClassSub(file); + const exportBase = classSub + ? `${this.settings.exportsFolder}/${classSub}` + : this.settings.exportsFolder; + await this.ensureFolder(exportBase); + const outputPath = `${exportBase}/${file.basename}.pseudonymized.${ext}`; const existing = this.app.vault.getAbstractFileByPath(outputPath); if (existing instanceof TFile) { await this.app.vault.modify(existing, pseudonymized); @@ -1345,6 +1353,45 @@ export default class PseudObsPlugin extends Plugin { await this.writeExport(file, 'vtt', vtt); } + /** + * Re-exporte le Markdown noScribe actif au format HTML noScribe pseudonymisé. + * Lit le .words.json correspondant pour les timestamps précis. + */ + async exportCurrentFileAsHtml(): Promise { + const file = this.getActiveOrLastFile(); + if (!file || file.extension !== 'md') { + new Notice(t('notice.noActiveFile')); + return; + } + + const content = await this.app.vault.read(file); + + const formatMatch = /^pseudobs-format:\s*(\w+)/m.exec(content); + if (formatMatch?.[1] !== 'html') { + new Notice(t('notice.notNoScribeFormat')); + return; + } + + const rawBasename = file.basename.replace(/\.pseudonymized$/, ''); + const wordsJson = await this.findWordsJson(rawBasename); + if (!wordsJson) { + new Notice(t('notice.wordsJsonMissing', rawBasename)); + return; + } + + const audioMatch = /^pseudobs-audio:\s*"([^"]+)"/m.exec(content); + const audioSource = audioMatch?.[1]; + + const wordData = JSON.parse(wordsJson) as VttCueData[]; + const { html, mismatch } = markdownToNoScribeHtml(content, wordData, audioSource); + + if (mismatch) { + new Notice(t('notice.vttMismatch')); + } + + await this.writeExport(file, 'html', html, 'notice.htmlExported'); + } + /** * Re-exporte le Markdown pseudonymisé courant au format d'origine (srt ou cha). */ @@ -1374,7 +1421,7 @@ export default class PseudObsPlugin extends Plugin { * Écrit le contenu d'un export final selon les paramètres de destination. * Gère vault, next-to-source et external. */ - private async writeExport(file: TFile, ext: string, content: string): Promise { + private async writeExport(file: TFile, ext: string, content: string, noticeKey = 'notice.vttExported'): Promise { const dest = this.resolveExportPath(file, ext); if (dest.externalPath) { @@ -1386,7 +1433,7 @@ export default class PseudObsPlugin extends Plugin { const nodePath = require('path') as typeof import('path'); await nodeFs.promises.mkdir(nodePath.dirname(dest.externalPath), { recursive: true }); await nodeFs.promises.writeFile(dest.externalPath, content, 'utf-8'); - new Notice(t('notice.vttExported', dest.externalPath)); + new Notice(t(noticeKey, dest.externalPath)); } catch (e) { new Notice(`Export error: ${(e as Error).message}`); } @@ -1404,7 +1451,7 @@ export default class PseudObsPlugin extends Plugin { } else { await this.app.vault.create(outputPath, content); } - new Notice(t('notice.vttExported', outputPath)); + new Notice(t(noticeKey, outputPath)); } /** Cherche .words.json dans le dossier mappings et ses sous-dossiers. */ diff --git a/src/parsers/TranscriptConverter.ts b/src/parsers/TranscriptConverter.ts index 538abda..527e3a9 100644 --- a/src/parsers/TranscriptConverter.ts +++ b/src/parsers/TranscriptConverter.ts @@ -286,6 +286,134 @@ export function noScribeHtmlToMarkdown(doc: VttDocument, sourceName: string, aud return vttDocToMarkdown(doc, sourceName, 'html', audioFilename); } +// ---- Re-export HTML noScribe ------------------------------------------------ + +/** Convertit HH:MM:SS.mmm en millisecondes. */ +function tsToMs(ts: string): number { + const [h, m, s] = ts.split(':'); + return Math.round((parseInt(h, 10) * 3600 + parseInt(m, 10) * 60 + parseFloat(s)) * 1000); +} + +function escapeHtml(text: string): string { + return text.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} + +/** Conserve uniquement les caractères word ASCII pour les noms d'ancre ts_. */ +function sanitizeSpeakerId(speaker: string): string { + return speaker.replace(/\W/g, ''); +} + +/** + * Reconstruit un fichier HTML noScribe pseudonymisé depuis : + * - `mdContent` : le Markdown noScribe (pseudobs-format: html) + * - `wordData` : le contenu de .words.json (timestamps précis) + * - `audioSource` : chemin vers le fichier audio (optionnel, pour la meta tag) + * + * Chaque réplique devient un

avec deux ancres ts_ : le timestamp d'affichage + * et le texte. Les timestamps word-level ne sont pas reconstitués (le texte + * pseudonymisé ne s'aligne plus mot à mot sur l'original). + */ +export function markdownToNoScribeHtml( + mdContent: string, + wordData: VttCueData[], + audioSource?: string, +): { html: string; mismatch: boolean } { + const bodyMatch = /^---\n[\s\S]*?\n---\n+([\s\S]*)$/.exec(mdContent); + const body = bodyMatch ? bodyMatch[1] : mdContent; + + const cueLines = body.split('\n').filter((l) => MD_CUE_RE.test(l.trim())); + const mismatch = cueLines.length !== wordData.length; + const count = Math.min(cueLines.length, wordData.length); + + const P_STYLE = ' margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;'; + const paragraphs: string[] = []; + + for (let i = 0; i < count; i++) { + const m = MD_CUE_RE.exec(cueLines[i].trim())!; + const speaker = m[1]?.trim() || wordData[i].speaker || ''; + const text = m[2]?.trim() ?? ''; + if (!text) continue; + + const cueData = wordData[i]; + const cueStartMs = tsToMs(cueData.startTime); + const cueEndMs = tsToMs(cueData.endTime); + const speakerId = speaker ? sanitizeSpeakerId(speaker) : ''; + const cueAnchor = `ts_${cueStartMs}_${cueEndMs}_${speakerId}`; + const displayTs = `[${cueData.startTime.slice(0, 8)}]`; + + // Ancres word-level : mapper les tokens pseudonymisés sur les timestamps d'origine + // par index proportionnel (floor). Un pseudonyme hérite du timestamp du nom original. + const origWords = cueData.words.filter(w => w.time !== ''); + const pseudoTokens = text.split(/\s+/).filter(Boolean); + + let contentHtml: string; + + if (origWords.length === 0 || pseudoTokens.length === 0) { + // Pas de timestamps word-level : ancre unique pour toute la réplique + const prefix = speaker ? `${escapeHtml(speaker)} : ` : ''; + contentHtml = `${prefix}${escapeHtml(text)}`; + } else { + const wordStartMs = origWords.map(w => tsToMs(w.time)); + const wordEndMs = origWords.map((_, j) => + j + 1 < origWords.length ? wordStartMs[j + 1] : cueEndMs + ); + + // Mapping floor : token pseudo j → orig word floor(j * M / N) + const mapToOrig = (j: number): number => + Math.min(Math.floor(j * origWords.length / pseudoTokens.length), origWords.length - 1); + + // Grouper les tokens consécutifs qui partagent le même index orig + interface Group { origIdx: number; tokens: string[] } + const groups: Group[] = []; + for (let j = 0; j < pseudoTokens.length; j++) { + const origIdx = mapToOrig(j); + if (groups.length === 0 || groups[groups.length - 1].origIdx !== origIdx) { + groups.push({ origIdx, tokens: [] }); + } + groups[groups.length - 1].tokens.push(pseudoTokens[j]); + } + + const parts: string[] = []; + for (let g = 0; g < groups.length; g++) { + const { origIdx, tokens } = groups[g]; + const anchorName = `ts_${wordStartMs[origIdx]}_${wordEndMs[origIdx]}_${speakerId}`; + let groupText = tokens.join(' '); + // Espace séparateur entre ancres (sauf la dernière) + if (g < groups.length - 1) groupText += ' '; + // Préfixe locuteur sur le premier groupe + const content = (g === 0 && speaker) + ? `${escapeHtml(speaker)} : ${escapeHtml(groupText)}` + : escapeHtml(groupText); + parts.push(`${content}`); + } + contentHtml = parts.join(''); + } + + paragraphs.push( + `

` + + `${displayTs}` + + contentHtml + + `

` + ); + } + + const audioMeta = audioSource + ? `\n` + : ''; + + const html = [ + '', + `\n${audioMeta}`, + ...paragraphs, + '', + '', + ].join('\n'); + + return { html, mismatch }; +} + function lineGroup(line: ChatLine): 'structural' | 'turn' | null { if (line.type === 'meta' || line.type === 'dependent') return 'structural'; if (line.type === 'turn' || line.type === 'continuation') return 'turn'; diff --git a/src/settings.ts b/src/settings.ts index ced3b12..c407c73 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -5,7 +5,7 @@ import { OnboardingModal } from './ui/OnboardingModal'; import { t, setLocale, AVAILABLE_LANGUAGES } from './i18n'; export type NerBackend = 'none' | 'spacy' | 'transformers-js'; -export type ExportDestinationType = 'vault' | 'next-to-source' | 'external'; +export type ExportDestinationType = 'vault' | 'external'; export interface PseudObsSettings { transcriptionsFolder: string; diff --git a/src/ui/CorpusModal.ts b/src/ui/CorpusModal.ts index 2bf3aa1..98cb74b 100644 --- a/src/ui/CorpusModal.ts +++ b/src/ui/CorpusModal.ts @@ -21,10 +21,12 @@ export function getCorpusClasses(app: App, transcriptionsFolder: string): string */ export class CorpusModal extends Modal { private plugin: PseudObsPlugin; + private onCloseCb?: () => void; - constructor(app: App, plugin: PseudObsPlugin) { + constructor(app: App, plugin: PseudObsPlugin, onCloseCb?: () => void) { super(app); this.plugin = plugin; + this.onCloseCb = onCloseCb; } onOpen(): void { @@ -145,6 +147,7 @@ export class CorpusModal extends Modal { onClose(): void { this.contentEl.empty(); + this.onCloseCb?.(); } } diff --git a/src/ui/PseudonymizationView.ts b/src/ui/PseudonymizationView.ts index 6d8bffe..e1a12ec 100644 --- a/src/ui/PseudonymizationView.ts +++ b/src/ui/PseudonymizationView.ts @@ -3,7 +3,7 @@ import { t } from '../i18n'; import type { DictionaryFile } from '../types'; import type PseudObsPlugin from '../main'; import { scanOccurrences } from '../scanner/OccurrenceScanner'; -import { getCorpusClasses } from './CorpusModal'; +import { getCorpusClasses, CorpusModal } from './CorpusModal'; import { FolderSuggest } from './FolderSuggest'; import { EditRuleModal } from './EditRuleModal'; import { RuleModal } from './RuleModal'; @@ -211,9 +211,8 @@ export class PseudonymizationView extends ItemView { // Sélecteur de type const typeRow = exportSection.createDiv('pseudobs-corpus-export-type-row'); for (const [val, labelKey] of [ - ['vault', 'panel.corpus.exportDest.vault'], - ['next-to-source', 'panel.corpus.exportDest.nextToSource'], - ['external', 'panel.corpus.exportDest.external'], + ['vault', 'panel.corpus.exportDest.vault'], + ['external', 'panel.corpus.exportDest.external'], ] as [string, string][]) { const lbl = typeRow.createEl('label', { cls: 'pseudobs-corpus-export-type-label' }); const radio = lbl.createEl('input'); @@ -270,12 +269,8 @@ export class PseudonymizationView extends ItemView { text: `+ ${t('panel.corpus.addClass')}`, cls: 'pseudobs-corpus-add-class-btn', }); - addClassBtn.addEventListener('click', async () => { - const name = this.promptText(t('corpus.modal.classNamePlaceholder')); - if (!name) return; - await this.plugin.ensureFolder(`${transcRoot}/${name}`); - await this.plugin.ensureFolder(`${s.mappingFolder}/${name}`); - void this.renderTab('corpus'); + addClassBtn.addEventListener('click', () => { + new CorpusModal(this.app, this.plugin, () => void this.renderTab('corpus')).open(); }); // ---- Liste des fichiers par classe ---------------------------- @@ -723,6 +718,16 @@ export class PseudonymizationView extends ItemView { void this.plugin.exportCurrentFileAsVtt(); }) ); + if (format === 'html') { + new Setting(el) + .setName(t('panel.exports.exportHtml')) + .setDesc(t('panel.exports.exportHtml.desc')) + .addButton((btn) => + btn.setButtonText(t('command.exportAsHtml')).onClick(() => { + void this.plugin.exportCurrentFileAsHtml(); + }) + ); + } } else if (format === 'srt') { new Setting(el) .setName(t('panel.exports.exportSrt'))