feat(webapp): complete i18n implementation and admin document input enhancement

- feat(admin): accept URLs, file paths, and IDs in document creation form
  - Modified AdminDashboard to use findOrCreateDocument service
  - Now matches user UI functionality for flexible document references

- feat(i18n): replace all hardcoded French texts with translation keys
  - Added 50+ new translation keys across admin and user interfaces
  - Updated 7 Vue components: AdminDashboard, AdminDocumentDetail,
    DocumentForm, SignButton, SignatureList, SignaturesPage, EmbedPage
  - Synchronized all new keys to 5 languages (fr, en, es, de, it)

- All templates now use vue-i18n with proper parameterized translations
- Zero hardcoded texts remaining in HTML templates
This commit is contained in:
Benjamin
2025-11-06 10:46:43 +01:00
parent cff602c812
commit d28283f6ed
12 changed files with 935 additions and 386 deletions

View File

@@ -64,7 +64,7 @@ const handleSubmit = async () => {
<Input
v-model="documentUrl"
type="text"
placeholder="URL, PATH ou RÉFÉRENCE du document à lire (optionnel)"
:placeholder="$t('admin.documentForm.placeholder')"
class="flex-1 h-11"
:disabled="isSubmitting"
@keyup.enter="handleSubmit"
@@ -75,8 +75,8 @@ const handleSubmit = async () => {
class="group whitespace-nowrap"
:disabled="isSubmitting"
>
<span v-if="isSubmitting">Chargement...</span>
<span v-else>Commencer</span>
<span v-if="isSubmitting">{{ $t('admin.documentForm.submitting') }}</span>
<span v-else>{{ $t('admin.documentForm.submit') }}</span>
<ArrowRight v-if="!isSubmitting" :size="16" class="ml-2 transition-transform group-hover:translate-x-1" />
</Button>
</div>

View File

@@ -44,7 +44,7 @@
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
/>
</svg>
{{ loading ? 'Confirmation en cours...' : 'Confirmer la lecture' }}
{{ loading ? $t('signButton.signing') : $t('signButton.confirmAction') }}
</button>
<div v-else class="signed-status">
@@ -63,10 +63,10 @@
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span class="font-semibold">Lecture confirmée</span>
<span class="font-semibold">{{ $t('signButton.confirmed') }}</span>
</div>
<p v-if="signedAt" class="mt-2 text-sm text-muted-foreground text-center">
Le {{ formatDate(signedAt) }}
{{ $t('signButton.on') }} {{ formatDate(signedAt) }}
</p>
</div>
@@ -78,6 +78,7 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useSignatureStore } from '@/stores/signatures'
import { useAuthStore } from '@/stores/auth'
@@ -100,6 +101,7 @@ const emit = defineEmits<{
error: [error: string]
}>()
const { t } = useI18n()
const authStore = useAuthStore()
const signatureStore = useSignatureStore()
const loading = ref(false)
@@ -146,7 +148,7 @@ const buttonClasses = computed(() => {
async function handleSign() {
if (!props.docId) {
error.value = 'Document ID manquant'
error.value = t('signButton.error.missingDocId')
return
}
@@ -164,7 +166,7 @@ async function handleSign() {
try {
await authStore.startOAuthLogin(window.location.pathname + window.location.search)
} catch (err: any) {
error.value = 'Impossible de démarrer l\'authentification'
error.value = t('signButton.error.authFailed')
emit('error', error.value)
}
return

View File

@@ -39,7 +39,7 @@
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
<p class="mt-2 text-muted-foreground">{{ emptyMessage || 'Aucune confirmation trouvée' }}</p>
<p class="mt-2 text-muted-foreground">{{ emptyMessage || $t('signatureList.empty') }}</p>
</div>
<div v-else class="space-y-4">
@@ -75,7 +75,7 @@
d="M5 13l4 4L19 7"
/>
</svg>
Confirmé
{{ $t('signatureList.confirmed') }}
</span>
<span
v-else
@@ -95,28 +95,28 @@
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
Document supprimé{{ signature.docDeletedAt ? ` le ${formatDate(signature.docDeletedAt)}` : '' }}
{{ $t('signatureList.documentDeleted') }}{{ signature.docDeletedAt ? ` ${formatDate(signature.docDeletedAt)}` : '' }}
</span>
</div>
<div class="mt-2 space-y-1 text-sm text-muted-foreground">
<p v-if="signature.docTitle">
<span class="font-medium">ID:</span> {{ signature.docId }}
<span class="font-medium">{{ $t('signatureList.fields.id') }}</span> {{ signature.docId }}
</p>
<p v-if="signature.docUrl">
<span class="font-medium">Document:</span>
<span class="font-medium">{{ $t('signatureList.fields.document') }}</span>
<a :href="signature.docUrl" target="_blank" rel="noopener noreferrer" class="text-primary hover:text-primary/80 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded">
{{ signature.docUrl }}
</a>
</p>
<p v-if="showUserInfo">
<span class="font-medium">Lecteur:</span> {{ signature.userName || signature.userEmail }}
<span class="font-medium">{{ $t('signatureList.fields.reader') }}</span> {{ signature.userName || signature.userEmail }}
</p>
<p>
<span class="font-medium">Date:</span> {{ formatDate(signature.signedAt) }}
<span class="font-medium">{{ $t('signatureList.fields.date') }}</span> {{ formatDate(signature.signedAt) }}
</p>
<p v-if="signature.serviceInfo" class="flex items-center">
<span class="font-medium mr-2">Origine:</span>
<span class="font-medium mr-2">{{ $t('signatureList.fields.source') }}</span>
<span class="inline-flex items-center space-x-1">
<span v-html="signature.serviceInfo.icon"></span>
<span>{{ signature.serviceInfo.name }}</span>
@@ -127,20 +127,20 @@
<div v-if="showDetails" class="mt-3 pt-3 border-t border-border">
<details class="text-xs text-muted-foreground">
<summary class="cursor-pointer hover:text-foreground font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded">
Détails de vérification
{{ $t('signatureList.verificationDetails') }}
</summary>
<div class="mt-2 space-y-1 font-mono bg-muted p-2 rounded border border-border">
<p><span class="font-semibold">ID:</span> {{ signature.id }}</p>
<p><span class="font-semibold">Nonce:</span> {{ signature.nonce }}</p>
<p><span class="font-semibold">{{ $t('signatureList.fields.id') }}</span> {{ signature.id }}</p>
<p><span class="font-semibold">{{ $t('signatureList.fields.nonce') }}</span> {{ signature.nonce }}</p>
<p class="break-all">
<span class="font-semibold">Hash:</span> {{ signature.payloadHash }}
<span class="font-semibold">{{ $t('signatureList.fields.hash') }}</span> {{ signature.payloadHash }}
</p>
<p class="break-all">
<span class="font-semibold">Confirmation:</span>
<span class="font-semibold">{{ $t('signatureList.confirmation') }}</span>
{{ signature.signature.substring(0, 64) }}...
</p>
<p v-if="signature.prevHash" class="break-all">
<span class="font-semibold">Hash précédent:</span> {{ signature.prevHash }}
<span class="font-semibold">{{ $t('signatureList.previousHash') }}</span> {{ signature.prevHash }}
</p>
</div>
</details>
@@ -152,7 +152,7 @@
@click="$emit('view-details', signature)"
class="text-primary hover:text-primary/80 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded px-2 py-1"
>
Voir détails
{{ $t('signatureList.viewDetails') }}
</button>
</div>
</div>

View File

@@ -28,13 +28,13 @@
"connectedAs": "Angemeldet als"
},
"choice": {
"title": "Bei Ackify anmelden",
"title": "Anmeldung bei Ackify",
"subtitle": "Wählen Sie Ihre bevorzugte Authentifizierungsmethode",
"privacy": "Ihre Authentifizierung ist sicher und verschlüsselt"
},
"oauth": {
"title": "Anmeldung mit OAuth",
"description": "Verwenden Sie Ihr bestehendes Konto",
"description": "Verwenden Sie Ihr Unternehmenskonto",
"button": "Mit OAuth fortfahren",
"error": "OAuth-Anmeldung fehlgeschlagen"
},
@@ -42,11 +42,11 @@
"title": "Anmeldung per E-Mail",
"description": "Wir senden Ihnen einen magischen Link",
"email_label": "E-Mail-Adresse",
"email_placeholder": "sie{'@'}beispiel.de",
"email_placeholder": "sie{'@'}beispiel.com",
"button": "Magischen Link senden",
"sent": {
"title": "Überprüfen Sie Ihre E-Mail",
"message": "Wir haben Ihnen einen magischen Link geschickt. Klicken Sie darauf, um sich anzumelden.",
"title": "Überprüfen Sie Ihre E-Mails",
"message": "Wir haben Ihnen einen magischen Link gesendet. Klicken Sie darauf, um sich anzumelden.",
"expire": "Der Link läuft in 15 Minuten ab."
},
"error_invalid_email": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
@@ -55,10 +55,10 @@
},
"sign": {
"title": "Lesebestätigung",
"subtitle": "Bestätigen Sie Ihre Lesung mit einer kryptographischen Ed25519-Bestätigung",
"subtitle": "Bestätigen Sie Ihre Lektüre mit einer kryptografischen Ed25519-Bestätigung",
"loading": {
"title": "Dokument wird geladen...",
"description": "Bitte warten Sie, während wir das Dokument zur Signierung vorbereiten."
"description": "Bitte warten Sie, während wir das Dokument zur Signatur vorbereiten."
},
"noDocument": {
"title": "Kein Dokument angegeben",
@@ -66,8 +66,8 @@
"examples": "Beispiele:"
},
"success": {
"title": "Lesung erfolgreich bestätigt!",
"description": "Ihre Bestätigung wurde kryptographisch und sicher gespeichert."
"title": "Lektüre erfolgreich bestätigt!",
"description": "Ihre Bestätigung wurde kryptografisch und sicher aufgezeichnet."
},
"error": {
"title": "Ein Fehler ist aufgetreten",
@@ -80,64 +80,69 @@
"id": "ID"
},
"info": {
"description": "Durch die Bestätigung der Lesung dieses Dokuments bestätigen Sie, dass Sie dessen Inhalt gelesen haben und akzeptieren, es kryptographisch und unwiderruflich zu validieren.",
"recorded": "Ihre Bestätigung wird mit den folgenden Informationen gespeichert:",
"description": "Indem Sie die Lektüre dieses Dokuments bestätigen, bestätigen Sie, dass Sie dessen Inhalt zur Kenntnis genommen haben und akzeptieren, es auf kryptografische und unwiderrufliche Weise zu validieren.",
"recorded": "Ihre Bestätigung wird mit den folgenden Informationen aufgezeichnet:",
"email": "Ihre E-Mail-Adresse",
"timestamp": "Präziser Zeitstempel der Bestätigung",
"signature": "Kryptographische Ed25519-Bestätigung",
"signature": "Kryptografische Ed25519-Bestätigung",
"hash": "SHA-256-Hash des Inhalts"
},
"confirmations": {
"title": "Bestehende Bestätigungen",
"title": "Vorhandene Bestätigungen",
"count": "{count} Bestätigung | {count} Bestätigungen",
"recorded": "gespeichert"
"recorded": "aufgezeichnet | aufgezeichnet"
},
"empty": {
"title": "Noch keine Bestätigungen",
"description": "Seien Sie der Erste, der die Lesung dieses Dokuments bestätigt"
"title": "Noch keine Bestätigung",
"description": "Seien Sie der Erste, der die Lektüre dieses Dokuments bestätigt"
},
"howItWorks": {
"title": "Wie funktioniert es?",
"subtitle": "Ackify ermöglicht es Ihnen, kryptographisch zu beweisen, dass Sie ein Dokument gelesen haben",
"subtitle": "Ackify ermöglicht es Ihnen, kryptografisch zu beweisen, dass Sie ein Dokument gelesen haben",
"step1": {
"title": "1. Greifen Sie auf das Dokument zu",
"title": "1. Auf das Dokument zugreifen",
"description": "Fügen Sie {code} zur Adresse dieser Seite hinzu"
},
"step2": {
"title": "2. Authentifizieren Sie sich",
"title": "2. Authentifizieren",
"description": "Melden Sie sich über OAuth2 an, um Ihre Identität zu bestätigen"
},
"step3": {
"title": "3. Bestätigen Sie die Lesung",
"description": "Ihre Bestätigung wird mit einer Ed25519-Signatur gespeichert"
"title": "3. Lektüre bestätigen",
"description": "Ihre Bestätigung wird mit einer Ed25519-Signatur aufgezeichnet"
},
"features": {
"crypto": {
"title": "Kryptographische Sicherheit",
"description": "Unwiderrufliche Ed25519-Signaturen garantieren Authentizität"
"title": "Kryptografische Sicherheit",
"description": "Unwiderrufliche Ed25519-Signaturen garantieren die Authentizität"
},
"instant": {
"title": "Sofort",
"description": "Bestätigung mit zwei Klicks und sofortiger kryptographischer Verifizierung"
"description": "Bestätigung mit zwei Klicks und sofortiger kryptografischer Überprüfung"
},
"timestamp": {
"title": "Präziser Zeitstempel",
"description": "Jede Bestätigung wird zeitgestempelt und verkettet, um Integrität zu gewährleisten"
"description": "Jede Bestätigung wird mit Zeitstempel versehen und verkettet, um Integrität zu gewährleisten"
}
}
}
},
"signButton": {
"confirm": "Meine Lesung bestätigen",
"confirm": "Meine Lektüre bestätigen",
"confirmAction": "Lektüre bestätigen",
"alreadySigned": "Bereits bestätigt",
"confirmed": "Lektüre bestätigt",
"mustLogin": "Anmelden zum Bestätigen",
"signing": "Bestätigung läuft...",
"verified": "Verifiziert",
"on": "Am",
"error": {
"title": "Bestätigung fehlgeschlagen",
"notAuthenticated": "Sie müssen angemeldet sein, um ein Dokument zu bestätigen.",
"alreadySigned": "Sie haben dieses Dokument bereits bestätigt.",
"generic": "Bei der Bestätigung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut."
"generic": "Bei der Bestätigung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"missingDocId": "Dokument-ID fehlt",
"authFailed": "Authentifizierung kann nicht gestartet werden"
}
},
"signatureList": {
@@ -145,42 +150,60 @@
"email": "E-Mail",
"date": "Datum",
"signature": "Signatur",
"confirmation": "Bestätigung:",
"hash": "Hash",
"nonce": "Nonce",
"verificationStatus": "Verifizierungsstatus",
"verificationStatus": "Überprüfungsstatus",
"verified": "Verifiziert",
"confirmed": "Bestätigt",
"notVerified": "Nicht verifiziert",
"showDetails": "Details anzeigen",
"hideDetails": "Details verbergen",
"hideDetails": "Details ausblenden",
"viewDetails": "Details ansehen",
"verificationDetails": "Überprüfungsdetails",
"copy": "Kopieren",
"copied": "Kopiert!",
"previousHash": "Vorheriger Hash"
"previousHash": "Vorheriger Hash:",
"empty": "Keine Bestätigung gefunden",
"documentDeleted": "Dokument gelöscht",
"fields": {
"id": "ID:",
"document": "Dokument:",
"reader": "Leser:",
"date": "Datum:",
"source": "Herkunft:",
"nonce": "Nonce:",
"hash": "Hash:"
}
},
"signatures": {
"title": "Meine Lesebestätigungen",
"subtitle": "Liste aller Dokumente, deren Lesung Sie kryptographisch bestätigt haben",
"subtitle": "Liste aller Dokumente, deren Lektüre Sie kryptografisch bestätigt haben",
"loading": "Ihre Bestätigungen werden geladen...",
"empty": {
"title": "Noch keine Bestätigungen",
"description": "Sie haben noch keine Dokumente bestätigt. Beginnen Sie mit der Bestätigung eines Dokuments, um es hier anzuzeigen."
"title": "Noch keine Bestätigung",
"description": "Sie haben noch kein Dokument bestätigt. Beginnen Sie mit der Bestätigung eines Dokuments, um es hier zu sehen.",
"alternative": "Sie haben noch keine Dokumentlektüre bestätigt"
},
"count": "{count} Bestätigung | {count} Bestätigungen",
"results": "{count} Ergebnis | {count} Ergebnisse",
"document": "Dokument",
"signedAt": "Bestätigt am",
"viewDetails": "Details anzeigen",
"viewDetails": "Details ansehen",
"deletedDocuments": "Gelöschte Dokumente",
"stats": {
"total": "Gesamt",
"totalConfirmations": "Gesamtbestätigungen",
"unique": "Eindeutige",
"uniqueDocuments": "Eindeutige Dokumente",
"unique": "Einzigartig",
"uniqueDocuments": "Einzigartige Dokumente",
"last": "Letzte",
"lastConfirmation": "Letzte Bestätigung"
"lastConfirmation": "Letzte Bestätigung",
"notAvailable": "N/V"
},
"allConfirmations": "Alle meine Bestätigungen",
"about": {
"title": "Über Bestätigungen",
"description": "Jede Bestätigung wird kryptographisch mit Ed25519 aufgezeichnet und verkettet, um Integrität zu gewährleisten. Bestätigungen sind unwiderruflich und präzise zeitgestempelt."
"description": "Jede Bestätigung wird kryptografisch mit Ed25519 aufgezeichnet und verkettet, um die Integrität zu gewährleisten. Bestätigungen sind unwiderruflich und präzise mit Zeitstempel versehen."
},
"search": "Suchen...",
"error": {
@@ -189,7 +212,7 @@
}
},
"admin": {
"title": "Verwaltung",
"title": "Administration",
"subtitle": "Dokumente und erwartete Leser verwalten",
"loading": "Daten werden geladen...",
"dashboard": {
@@ -197,6 +220,7 @@
"totalDocuments": "Gesamtdokumente",
"totalSignatures": "Gesamtbestätigungen",
"recentActivity": "Letzte Aktivität",
"backToDashboard": "Zurück zum Dashboard",
"stats": {
"documents": "Dokumente",
"readers": "Leser",
@@ -204,19 +228,20 @@
"expected": "Erwartet",
"signed": "Signiert",
"pending": "Ausstehend",
"completion": "Fertigstellung"
"completion": "Vollständigkeit"
}
},
"documents": {
"title": "Alle Dokumente",
"new": "Neues Dokument erstellen",
"newDescription": "Eine Dokumentreferenz vorbereiten, um Lesebestätigungen zu verfolgen",
"newDescription": "Dokumentreferenz vorbereiten, um Lesebestätigungen zu verfolgen",
"search": "Suchen...",
"searchPlaceholder": "Nach ID, Titel oder URL suchen...",
"id": "Dokument-ID",
"idLabel": "Dokument-ID",
"idHelper": "Nur Buchstaben, Zahlen, Bindestriche und Unterstriche",
"idPlaceholder": "z.B.: sicherheitsrichtlinie-2025",
"idLabel": "Dokument",
"idHelper": "Akzeptiert URLs, Dateipfade oder einfache Kennungen",
"idHelperShort": "Akzeptiert URLs, Pfade oder Kennungen",
"idPlaceholder": "URL, PFAD oder ID des Dokuments",
"signatures": "Bestätigungen",
"created": "Erstellt",
"createdOn": "Erstellt am",
@@ -228,11 +253,75 @@
"manage": "Verwalten",
"edit": "Bearbeiten",
"delete": "Löschen",
"empty": "Keine Dokumente gefunden"
"empty": "Kein Dokument gefunden",
"noResults": "Keine Ergebnisse",
"noDocuments": "Keine Dokumente",
"tryAnotherSearch": "Versuchen Sie eine andere Suche",
"willAppear": "Dokumente werden hier angezeigt, sobald sie erstellt wurden",
"totalCount": "{count} Dokument insgesamt | {count} Dokumente insgesamt",
"pagination": {
"page": "Seite {current}/{total}",
"pageOf": "Seite {current} von {total}"
}
},
"webhooks": {
"title": "Webhooks",
"subtitle": "Benachrichtigungen an Drittanwendungen konfigurieren",
"manage": "Webhooks verwalten",
"new": "Neuer Webhook",
"edit": "Bearbeiten",
"delete": "Löschen",
"enable": "Aktivieren",
"disable": "Deaktivieren",
"status": {
"enabled": "Aktiv",
"disabled": "Inaktiv"
},
"confirmDelete": "Diesen Webhook löschen?",
"empty": "Kein Webhook",
"listTitle": "Liste der Webhooks",
"listSubtitle": "Ein Webhook kann mehrere Ereignisse abhören",
"columns": {
"title": "Name",
"url": "URL",
"events": "Ereignisse",
"status": "Status",
"actions": "Aktionen"
},
"form": {
"title": "Webhook-Einstellungen",
"subtitle": "Geben Sie URL, Geheimnis und Ereignisse an",
"nameLabel": "Webhook-Name",
"namePlaceholder": "CRM-Webhook",
"urlLabel": "Ziel-URL",
"secretLabel": "HMAC-Geheimnis",
"secretPlaceholder": "Geben Sie ein Geheimnis ein (erforderlich)",
"secretKeep": "Leer lassen zum Beibehalten",
"eventsLabel": "Zu abhörende Ereignisse",
"descriptionLabel": "Beschreibung (optional)",
"descriptionPlaceholder": "Interne Notiz…",
"validation": "Bitte füllen Sie Name, URL, Geheimnis und mindestens ein Ereignis aus."
},
"editTitle": "Webhook bearbeiten",
"events": {
"documentCreated": "Dokument erstellt",
"signatureCreated": "Signatur erstellt",
"documentCompleted": "Dokument abgeschlossen",
"reminderSent": "Erinnerung gesendet",
"reminderFailed": "Erinnerung fehlgeschlagen"
},
"eventsMap": {
"document.created": "Dokument erstellt",
"signature.created": "Signatur erstellt",
"document.completed": "Dokument abgeschlossen",
"reminder.sent": "Erinnerung gesendet",
"reminder.failed": "Erinnerung fehlgeschlagen"
}
},
"documentDetail": {
"title": "Dokumentdetails",
"metadata": "Dokumentmetadaten und Prüfsumme",
"title": "Dokument",
"metadata": "📄 Dokumentinformationen",
"metadataDescription": "Dokumentmetadaten und Prüfsumme",
"checksum": "Prüfsumme",
"algorithm": "Algorithmus",
"titleLabel": "Titel",
@@ -245,69 +334,110 @@
"descriptionLabel": "Beschreibung",
"descriptionPlaceholder": "Dokumentbeschreibung...",
"signatures": "Bestätigungen",
"back": "Zurück zur Liste",
"back": "Zurück",
"expectedSigners": "Erwartete Unterzeichner",
"addExpectedSigner": "Erwarteten Unterzeichner hinzufügen",
"addSigners": "Erwartete Leser hinzufügen",
"addButton": "Hinzufügen",
"adding": "Wird hinzugefügt...",
"emailsLabel": "E-Mails (eine pro Zeile)",
"emailsPlaceholder": "Maria Schmidt <maria.schmidt{'@'}example.com>\njohn.mueller{'@'}example.com\nSophie Weber <sophie{'@'}example.com>",
"emailsPlaceholder": "Marie Dupont <marie.dupont{'@'}example.com>\njean.martin{'@'}example.com\nSophie Bernard <sophie{'@'}example.com>",
"emailsHelper": "Akzeptierte Formate: \"Vorname Nachname <email{'@'}example.com>\" oder \"email{'@'}example.com\"",
"emailLabel": "E-Mail *",
"emailPlaceholder": "email{'@'}example.com",
"nameLabel": "Name",
"namePlaceholder": "Vollständiger Name",
"reader": "Leser",
"readers": "✓ Erwartete Leser",
"user": "Benutzer",
"status": "Status",
"statusConfirmed": "✓ Bestätigt",
"statusPending": "⏳ Ausstehend",
"confirmedOn": "Bestätigt am",
"noExpectedSigners": "Keine erwarteten Leser",
"noSignatures": "Keine Bestätigungen",
"reminders": "E-Mail-Erinnerungen",
"reminders": "📧 E-Mail-Erinnerungen",
"remindersDescription": "Erinnerungen an Leser senden, die auf Bestätigung warten",
"remindersSent": "Gesendete Erinnerungen",
"toRemind": "Zu erinnern",
"lastReminder": "Letzte Erinnerung",
"sendReminder": "Erinnerung senden",
"sending": "Wird gesendet...",
"sendReminders": "Erinnerungen senden",
"sendToAll": "An alle wartenden Leser senden ({count})",
"sendToSelected": "Nur an Ausgewählte senden ({count})",
"allContacted": "✓ Alle erwarteten Leser wurden kontaktiert oder haben bestätigt",
"unexpectedSignatures": "⚠ Zusätzliche Lesebestätigungen",
"unexpectedDescription": "Benutzer, die bestätigt haben, aber nicht in der Liste der erwarteten Leser stehen",
"createdBy": "Erstellt von {by} am {date}",
"saving": "Wird gespeichert...",
"deleting": "Wird gelöscht...",
"copiedToClipboard": "In die Zwischenablage kopiert",
"metadataSaved": "Metadaten erfolgreich gespeichert",
"signersAdded": "{count} Leser erfolgreich hinzugefügt",
"signerRemoved": "{email} erfolgreich entfernt",
"remindersSentSuccess": "{count} Erinnerung(en) erfolgreich gesendet",
"remindersSentPartial": "{sent} Erinnerung(en) gesendet, {failed} fehlgeschlagen",
"remindersSentGeneric": "Erinnerungen erfolgreich gesendet",
"confirmSendReminders": "Erinnerungen an {count} Leser senden, die auf Bestätigung warten?",
"confirmSendRemindersSelected": "Erinnerungen an {count} ausgewählte(n) Leser senden?",
"confirmSendRemindersTitle": "📧 Erinnerungen senden",
"removeSignerTitle": "⚠️ Erwarteten Leser entfernen",
"removeSignerMessage": "{email} aus der Liste der erwarteten Leser entfernen?",
"metadataWarning": {
"title": "⚠️ Warnung: Signaturungültigkeit",
"description": "Sie sind dabei, kritische Dokumentinformationen (URL, Prüfsumme, Algorithmus oder Beschreibung) zu ändern.",
"warning": "Diese Änderung führt zur Ungültigkeit aller bestehenden Signaturen, da sie kryptographisch mit dem aktuellen Dokumentinhalt verknüpft sind.",
"title": "⚠️ Achtung: Ungültigmachung der Signaturen",
"description": "Sie sind dabei, kritische Informationen des Dokuments zu ändern (URL, Prüfsumme, Algorithmus oder Beschreibung).",
"warning": "Diese Änderung führt zur Ungültigmachung aller vorhandenen Signaturen, da sie kryptografisch mit dem aktuellen Inhalt des Dokuments verbunden sind.",
"currentSignatures": "Aktuelle Signaturen, die ungültig werden:",
"confirm": "Ich verstehe, fortfahren",
"cancel": "Abbrechen"
},
"dangerZone": "Gefahrenbereich",
"dangerZone": "⚠️ Gefahrenzone",
"dangerZoneDescription": "Irreversible Aktionen für dieses Dokument",
"deleteDocument": "Dieses Dokument löschen",
"deleteDocumentDescription": "Diese Aktion löscht dauerhaft das Dokument, seine Metadaten, die erwarteten Leser und alle zugehörigen Bestätigungen.\nDiese Aktion ist irreversibel.",
"deleteWarning": "Diese Aktion ist irreversibel!",
"deleteWillRemove": "Diese Aktion wird dauerhaft entfernen:",
"deleteWillRemove": "Das Löschen dieses Dokuments führt zum endgültigen Verlust von:",
"deleteItem1": "Alle Dokumentmetadaten",
"deleteItem2": "Die Liste der erwarteten Leser",
"deleteItem3": "Alle kryptographischen Bestätigungen",
"deleteItem4": "Den Erinnerungsverlauf"
"deleteItem3": "Alle kryptografischen Bestätigungen",
"deleteItem4": "Der Erinnerungsverlauf",
"deleteConfirmTitle": "⚠️ Löschen bestätigen",
"deleteConfirmButton": "Endgültig löschen",
"documentId": "Dokument-ID:"
},
"documentForm": {
"title": "Dokumentreferenz",
"label": "Referenz (URL, Pfad oder ID)",
"placeholder": "https://example.com/doc.pdf oder /pfad/zum/doc",
"submit": "Bestätigen",
"placeholder": "URL, PFAD oder REFERENZ des zu lesenden Dokuments (optional)",
"submit": "Beginnen",
"submitting": "Wird geladen...",
"creating": "Wird erstellt..."
}
},
"embed": {
"loading": "Signaturinformationen werden geladen...",
"title": "Signatur für",
"document": "Dokument:",
"signedBy": "Signiert von",
"on": "am",
"verified": "Verifiziert",
"viewAll": "Alle Bestätigungen anzeigen",
"error": "Signaturinformationen können nicht geladen werden"
"viewAll": "Alle Bestätigungen ansehen",
"error": "Signaturinformationen können nicht geladen werden",
"sign": "Signieren",
"signDocument": "Dieses Dokument signieren",
"noSignatures": "Keine Signatur für dieses Dokument",
"confirmationsCount": "{count} Bestätigung(en)",
"poweredBy": "Powered by Ackify",
"missingDocId": "Dokument-ID fehlt"
},
"notFound": {
"title": "Seite nicht gefunden",
"description": "Die Seite, die Sie suchen, existiert nicht oder wurde verschoben.",
"description": "Die von Ihnen gesuchte Seite existiert nicht oder wurde verschoben.",
"home": "Zurück zur Startseite"
},
"footer": {
"description": "Open-Source-Lösung für kryptographische Dokumentenlesebestätigung mit unwiderruflichen Ed25519-Signaturen.",
"description": "Open-Source-Lösung für kryptografische Dokumentlesebestätigung mit unwiderruflichen Ed25519-Signaturen.",
"navigation": {
"title": "Navigation"
},
@@ -320,12 +450,12 @@
"legal": {
"title": "Rechtliches",
"terms": "Nutzungsbedingungen",
"privacy": "Datenschutzerklärung",
"privacy": "Datenschutzrichtlinie",
"contact": "Kontakt"
},
"copyright": "Alle Rechte vorbehalten.",
"license": "Lizenziert unter AGPL-3.0-or-later",
"madeWith": "Erstellt mit",
"license": "Lizenz AGPL-3.0-or-later",
"madeWith": "Gemacht mit",
"by": "von",
"links": {
"privacy": "Datenschutz",
@@ -340,7 +470,7 @@
"warning": "Warnung"
},
"common": {
"loading": "Wird geladen...",
"loading": "Laden...",
"save": "Speichern",
"cancel": "Abbrechen",
"delete": "Löschen",

View File

@@ -7,8 +7,8 @@
"myConfirmations": "My confirmations",
"admin": "Admin",
"administration": "Administration",
"login": "Log in",
"logout": "Log out",
"login": "Sign in",
"logout": "Sign out",
"mobileMenu": "Mobile menu",
"mainNavigation": "Main navigation"
},
@@ -34,19 +34,19 @@
},
"oauth": {
"title": "Sign in with OAuth",
"description": "Use your entreprise account",
"description": "Use your company account",
"button": "Continue with OAuth",
"error": "OAuth login failed"
"error": "OAuth sign in failed"
},
"magiclink": {
"title": "Sign in with Email",
"title": "Sign in by Email",
"description": "We'll send you a magic link",
"email_label": "Email address",
"email_placeholder": "you{'@'}example.com",
"button": "Send Magic Link",
"button": "Send magic link",
"sent": {
"title": "Check your email",
"message": "We sent you a magic link. Click on it to sign in.",
"message": "We've sent you a magic link. Click on it to sign in.",
"expire": "The link expires in 15 minutes."
},
"error_invalid_email": "Please enter a valid email address",
@@ -67,13 +67,13 @@
},
"success": {
"title": "Reading confirmed successfully!",
"description": "Your confirmation has been recorded cryptographically and securely."
"description": "Your confirmation has been cryptographically and securely recorded."
},
"error": {
"title": "An error occurred",
"authRequired": "You must be logged in to create a document.",
"authRequired": "You must be signed in to create a document.",
"loadFailed": "Failed to load document",
"loginButton": "Log in"
"loginButton": "Sign in"
},
"document": {
"title": "Document to confirm",
@@ -83,7 +83,7 @@
"description": "By confirming the reading of this document, you certify that you have read its content and agree to validate it cryptographically and non-repudiably.",
"recorded": "Your confirmation will be recorded with the following information:",
"email": "Your email address",
"timestamp": "Precise timestamp of the confirmation",
"timestamp": "Precise confirmation timestamp",
"signature": "Ed25519 cryptographic confirmation",
"hash": "SHA-256 hash of the content"
},
@@ -98,14 +98,14 @@
},
"howItWorks": {
"title": "How does it work?",
"subtitle": "Ackify allows you to cryptographically prove that you have read a document",
"subtitle": "Ackify allows you to cryptographically prove that you've read a document",
"step1": {
"title": "1. Access the document",
"description": "Add {code} to this page's address"
},
"step2": {
"title": "2. Authenticate",
"description": "Log in via OAuth2 to confirm your identity"
"description": "Sign in via OAuth2 to confirm your identity"
},
"step3": {
"title": "3. Confirm reading",
@@ -121,7 +121,7 @@
"description": "Two-click confirmation with immediate cryptographic verification"
},
"timestamp": {
"title": "Precise timestamp",
"title": "Precise timestamping",
"description": "Each confirmation is timestamped and chained to ensure integrity"
}
}
@@ -129,15 +129,20 @@
},
"signButton": {
"confirm": "Confirm my reading",
"confirmAction": "Confirm reading",
"alreadySigned": "Already confirmed",
"mustLogin": "Log in to confirm",
"confirmed": "Reading confirmed",
"mustLogin": "Sign in to confirm",
"signing": "Confirming...",
"verified": "Verified",
"on": "On",
"error": {
"title": "Confirmation failed",
"notAuthenticated": "You must be logged in to confirm a document.",
"notAuthenticated": "You must be signed in to confirm a document.",
"alreadySigned": "You have already confirmed this document.",
"generic": "An error occurred during confirmation. Please try again."
"generic": "An error occurred during confirmation. Please try again.",
"missingDocId": "Document ID missing",
"authFailed": "Unable to start authentication"
}
},
"signatureList": {
@@ -145,16 +150,31 @@
"email": "Email",
"date": "Date",
"signature": "Signature",
"confirmation": "Confirmation:",
"hash": "Hash",
"nonce": "Nonce",
"verificationStatus": "Verification status",
"verified": "Verified",
"confirmed": "Confirmed",
"notVerified": "Not verified",
"showDetails": "Show details",
"hideDetails": "Hide details",
"viewDetails": "View details",
"verificationDetails": "Verification details",
"copy": "Copy",
"copied": "Copied!",
"previousHash": "Previous hash"
"previousHash": "Previous hash:",
"empty": "No confirmations found",
"documentDeleted": "Document deleted",
"fields": {
"id": "ID:",
"document": "Document:",
"reader": "Reader:",
"date": "Date:",
"source": "Source:",
"nonce": "Nonce:",
"hash": "Hash:"
}
},
"signatures": {
"title": "My reading confirmations",
@@ -162,20 +182,23 @@
"loading": "Loading your confirmations...",
"empty": {
"title": "No confirmations yet",
"description": "You haven't confirmed any documents yet. Start by confirming a document to see it appear here."
"description": "You haven't confirmed any documents yet. Start by confirming a document to see it appear here.",
"alternative": "You haven't confirmed reading any documents yet"
},
"count": "{count} confirmation | {count} confirmations",
"results": "{count} result | {count} results",
"document": "Document",
"signedAt": "Confirmed on",
"viewDetails": "View details",
"deletedDocuments": "Deleted documents",
"stats": {
"total": "Total",
"totalConfirmations": "Total confirmations",
"unique": "Unique",
"uniqueDocuments": "Unique documents",
"last": "Last",
"lastConfirmation": "Last confirmation"
"lastConfirmation": "Last confirmation",
"notAvailable": "N/A"
},
"allConfirmations": "All my confirmations",
"about": {
@@ -197,6 +220,7 @@
"totalDocuments": "Total documents",
"totalSignatures": "Total confirmations",
"recentActivity": "Recent activity",
"backToDashboard": "Back to dashboard",
"stats": {
"documents": "Documents",
"readers": "Readers",
@@ -212,11 +236,12 @@
"new": "Create new document",
"newDescription": "Prepare a document reference to track reading confirmations",
"search": "Search...",
"searchPlaceholder": "Search by ID, title, or URL...",
"searchPlaceholder": "Search by ID, title or URL...",
"id": "Document ID",
"idLabel": "Document ID",
"idHelper": "Letters, numbers, dashes and underscores only",
"idPlaceholder": "e.g: security-policy-2025",
"idLabel": "Document",
"idHelper": "Accepts URLs, file paths or simple identifiers",
"idHelperShort": "Accepts URLs, paths or identifiers",
"idPlaceholder": "URL, PATH or document ID",
"signatures": "Confirmations",
"created": "Created",
"createdOn": "Created on",
@@ -228,22 +253,34 @@
"manage": "Manage",
"edit": "Edit",
"delete": "Delete",
"empty": "No documents found"
"empty": "No documents found",
"noResults": "No results",
"noDocuments": "No documents",
"tryAnotherSearch": "Try another search",
"willAppear": "Documents will appear here once created",
"totalCount": "{count} document in total | {count} documents in total",
"pagination": {
"page": "Page {current}/{total}",
"pageOf": "Page {current} of {total}"
}
},
"webhooks": {
"title": "Webhooks",
"subtitle": "Notify third-party apps of events",
"subtitle": "Configure notifications to third-party applications",
"manage": "Manage webhooks",
"new": "New webhook",
"edit": "Edit",
"delete": "Delete",
"enable": "Enable",
"disable": "Disable",
"status": { "enabled": "Enabled", "disabled": "Disabled" },
"status": {
"enabled": "Active",
"disabled": "Inactive"
},
"confirmDelete": "Delete this webhook?",
"empty": "No webhooks",
"listTitle": "Webhooks list",
"listSubtitle": "A webhook can subscribe to several events",
"listSubtitle": "A webhook can listen to multiple events",
"columns": {
"title": "Name",
"url": "URL",
@@ -253,17 +290,17 @@
},
"form": {
"title": "Webhook settings",
"subtitle": "Provide URL, secret and events",
"subtitle": "Fill in the URL, secret and events",
"nameLabel": "Webhook name",
"namePlaceholder": "CRM webhook",
"urlLabel": "Target URL",
"secretLabel": "HMAC secret",
"namePlaceholder": "CRM Webhook",
"urlLabel": "Destination URL",
"secretLabel": "HMAC Secret",
"secretPlaceholder": "Enter a secret (required)",
"secretKeep": "Leave empty to keep current",
"eventsLabel": "Events to subscribe",
"secretKeep": "Leave empty to keep",
"eventsLabel": "Events to listen to",
"descriptionLabel": "Description (optional)",
"descriptionPlaceholder": "Internal note…",
"validation": "Please fill name, URL, secret and at least one event."
"validation": "Please complete the name, URL, secret and at least one event."
},
"editTitle": "Edit webhook",
"events": {
@@ -282,8 +319,9 @@
}
},
"documentDetail": {
"title": "Document details",
"metadata": "Document metadata and checksum",
"title": "Document",
"metadata": "📄 Document information",
"metadataDescription": "Document metadata and checksum",
"checksum": "Checksum",
"algorithm": "Algorithm",
"titleLabel": "Title",
@@ -296,61 +334,102 @@
"descriptionLabel": "Description",
"descriptionPlaceholder": "Document description...",
"signatures": "Confirmations",
"back": "Back to list",
"back": "Back",
"expectedSigners": "Expected signers",
"addExpectedSigner": "Add expected signer",
"addSigners": "Add expected readers",
"addButton": "Add",
"adding": "Adding...",
"emailsLabel": "Emails (one per line)",
"emailsPlaceholder": "Mary Smith <mary.smith{'@'}example.com>\njohn.doe{'@'}example.com\nSophie Johnson <sophie{'@'}example.com>",
"emailsPlaceholder": "Jane Doe <jane.doe{'@'}example.com>\njohn.smith{'@'}example.com\nSarah Johnson <sarah{'@'}example.com>",
"emailsHelper": "Accepted formats: \"First Last <email{'@'}example.com>\" or \"email{'@'}example.com\"",
"emailLabel": "Email *",
"emailPlaceholder": "email{'@'}example.com",
"nameLabel": "Name",
"namePlaceholder": "Full name",
"reader": "Reader",
"readers": "✓ Expected readers",
"user": "User",
"status": "Status",
"statusConfirmed": "✓ Confirmed",
"statusPending": "⏳ Pending",
"confirmedOn": "Confirmed on",
"noExpectedSigners": "No expected readers",
"noSignatures": "No confirmations",
"reminders": "Email reminders",
"reminders": "📧 Email reminders",
"remindersDescription": "Send reminders to readers awaiting confirmation",
"remindersSent": "Reminders sent",
"toRemind": "To remind",
"lastReminder": "Last reminder",
"sendReminder": "Send reminder",
"sending": "Sending...",
"sendReminders": "Send reminders",
"sendToAll": "Send to all pending readers ({count})",
"sendToSelected": "Send to selected only ({count})",
"allContacted": "✓ All expected readers have been contacted or confirmed",
"unexpectedSignatures": "⚠ Additional reading confirmations",
"unexpectedDescription": "Users who confirmed but are not on the expected readers list",
"createdBy": "Created by {by} on {date}",
"saving": "Saving...",
"deleting": "Deleting...",
"copiedToClipboard": "Copied to clipboard",
"metadataSaved": "Metadata saved successfully",
"signersAdded": "{count} reader(s) added successfully",
"signerRemoved": "{email} removed successfully",
"remindersSentSuccess": "{count} reminder(s) sent successfully",
"remindersSentPartial": "{sent} reminder(s) sent, {failed} failed",
"remindersSentGeneric": "Reminders sent successfully",
"confirmSendReminders": "Send reminders to {count} reader(s) awaiting confirmation?",
"confirmSendRemindersSelected": "Send reminders to {count} selected reader(s)?",
"confirmSendRemindersTitle": "📧 Send reminders",
"removeSignerTitle": "⚠️ Remove expected reader",
"removeSignerMessage": "Remove {email} from the expected readers list?",
"metadataWarning": {
"title": "⚠️ Warning: Signature invalidation",
"description": "You are about to modify critical document information (URL, checksum, algorithm, or description).",
"description": "You are about to modify critical document information (URL, checksum, algorithm or description).",
"warning": "This modification will invalidate all existing signatures, as they are cryptographically linked to the current document content.",
"currentSignatures": "Current signatures that will be invalidated:",
"confirm": "I understand, continue",
"cancel": "Cancel"
},
"dangerZone": "Danger zone",
"dangerZone": "⚠️ Danger zone",
"dangerZoneDescription": "Irreversible actions on this document",
"deleteDocument": "Delete this document",
"deleteDocumentDescription": "This action will permanently delete the document, its metadata, expected readers and all associated confirmations.\nThis action is irreversible.",
"deleteWarning": "This action is irreversible!",
"deleteWillRemove": "This action will permanently remove:",
"deleteWillRemove": "Deleting this document will result in the permanent loss of:",
"deleteItem1": "All document metadata",
"deleteItem2": "The list of expected readers",
"deleteItem3": "All cryptographic confirmations",
"deleteItem4": "The reminder history"
"deleteItem4": "The reminder history",
"deleteConfirmTitle": "⚠️ Confirm deletion",
"deleteConfirmButton": "Delete permanently",
"documentId": "Document ID:"
},
"documentForm": {
"title": "Document reference",
"label": "Reference (URL, path or ID)",
"placeholder": "https://example.com/doc.pdf or /path/to/doc",
"submit": "Confirm",
"placeholder": "URL, PATH or REFERENCE of the document to read (optional)",
"submit": "Start",
"submitting": "Loading...",
"creating": "Creating..."
}
},
"embed": {
"loading": "Loading signature information...",
"title": "Signature for",
"document": "Document:",
"signedBy": "Signed by",
"on": "on",
"verified": "Verified",
"viewAll": "View all confirmations",
"error": "Unable to load signature information"
"error": "Unable to load signature information",
"sign": "Sign",
"signDocument": "Sign this document",
"noSignatures": "No signatures for this document",
"confirmationsCount": "{count} confirmation(s)",
"poweredBy": "Powered by Ackify",
"missingDocId": "Document ID missing"
},
"notFound": {
"title": "Page not found",
@@ -358,7 +437,7 @@
"home": "Back to home"
},
"footer": {
"description": "Open-source cryptographic document reading confirmation solution with non-repudiable Ed25519 signatures.",
"description": "Open-source solution for cryptographic confirmation of document reading with non-repudiable Ed25519 signatures.",
"navigation": {
"title": "Navigation"
},
@@ -370,12 +449,12 @@
},
"legal": {
"title": "Legal",
"terms": "Terms of Service",
"privacy": "Privacy Policy",
"terms": "Terms of use",
"privacy": "Privacy policy",
"contact": "Contact"
},
"copyright": "All rights reserved.",
"license": "Licensed under AGPL-3.0-or-later",
"license": "AGPL-3.0-or-later License",
"madeWith": "Made with",
"by": "by",
"links": {

View File

@@ -30,26 +30,26 @@
"choice": {
"title": "Iniciar sesión en Ackify",
"subtitle": "Elija su método de autenticación preferido",
"privacy": "Su autenticación es segura y cifrada"
"privacy": "Su autenticación está asegurada y cifrada"
},
"oauth": {
"title": "Iniciar sesión con OAuth",
"description": "Use su cuenta existente",
"description": "Use su cuenta empresarial",
"button": "Continuar con OAuth",
"error": "Error de inicio de sesión OAuth"
"error": "Error en el inicio de sesión OAuth"
},
"magiclink": {
"title": "Iniciar sesión por correo electrónico",
"title": "Iniciar sesión por Email",
"description": "Le enviaremos un enlace mágico",
"email_label": "Dirección de correo electrónico",
"email_label": "Dirección de email",
"email_placeholder": "usted{'@'}ejemplo.com",
"button": "Enviar enlace mágico",
"sent": {
"title": "Revise su correo electrónico",
"title": "Consulte sus emails",
"message": "Le hemos enviado un enlace mágico. Haga clic en él para iniciar sesión.",
"expire": "El enlace caduca en 15 minutos."
"expire": "El enlace expira en 15 minutos."
},
"error_invalid_email": "Por favor ingrese una dirección de correo electrónico válida",
"error_invalid_email": "Por favor, ingrese una dirección de email válida",
"error_send": "Error al enviar el enlace mágico"
}
},
@@ -57,21 +57,21 @@
"title": "Confirmación de Lectura",
"subtitle": "Certifique su lectura con una confirmación criptográfica Ed25519",
"loading": {
"title": "Cargando documento...",
"description": "Por favor espere mientras preparamos el documento para la firma."
"title": "Cargando el documento...",
"description": "Por favor, espere mientras preparamos el documento para la firma."
},
"noDocument": {
"title": "Ningún documento especificado",
"description": "Para firmar un documento, agregue el parámetro {code} a la URL",
"description": "Para firmar un documento, añada el parámetro {code} a la URL",
"examples": "Ejemplos:"
},
"success": {
"title": "¡Lectura confirmada con éxito!",
"description": "Su confirmación ha sido registrada de manera criptográfica y segura."
"description": "Su confirmación ha sido registrada de forma criptográfica y segura."
},
"error": {
"title": "Ha ocurrido un error",
"authRequired": "Debe iniciar sesión para crear un documento.",
"authRequired": "Debe estar conectado para crear un documento.",
"loadFailed": "Error al cargar el documento",
"loginButton": "Iniciar sesión"
},
@@ -80,9 +80,9 @@
"id": "ID"
},
"info": {
"description": "Al confirmar la lectura de este documento, usted certifica haber leído su contenido y acepta validarlo de manera criptográfica y no repudiable.",
"description": "Al confirmar la lectura de este documento, usted certifica haber tomado conocimiento de su contenido y acepta validarlo de manera criptográfica e irrefutable.",
"recorded": "Su confirmación será registrada con la siguiente información:",
"email": "Su dirección de correo electrónico",
"email": "Su dirección de email",
"timestamp": "Marca de tiempo precisa de la confirmación",
"signature": "Confirmación criptográfica Ed25519",
"hash": "Hash SHA-256 del contenido"
@@ -98,14 +98,14 @@
},
"howItWorks": {
"title": "¿Cómo funciona?",
"subtitle": "Ackify le permite demostrar criptográficamente que ha leído un documento",
"subtitle": "Ackify le permite probar criptográficamente que ha leído un documento",
"step1": {
"title": "1. Acceda al documento",
"description": "Agregue {code} a la dirección de esta página"
"description": "Añada {code} a la dirección de esta página"
},
"step2": {
"title": "2. Autentíquese",
"description": "Inicie sesión a través de OAuth2 para confirmar su identidad"
"description": "Inicie sesión vía OAuth2 para confirmar su identidad"
},
"step3": {
"title": "3. Confirme la lectura",
@@ -114,7 +114,7 @@
"features": {
"crypto": {
"title": "Seguridad criptográfica",
"description": "Firmas Ed25519 no repudiables que garantizan la autenticidad"
"description": "Firmas Ed25519 irrefutables que garantizan la autenticidad"
},
"instant": {
"title": "Instantáneo",
@@ -122,70 +122,93 @@
},
"timestamp": {
"title": "Marca de tiempo precisa",
"description": "Cada confirmación está marcada temporalmente y encadenada para garantizar la integridad"
"description": "Cada confirmación tiene marca de tiempo y está encadenada para garantizar la integridad"
}
}
}
},
"signButton": {
"confirm": "Confirmar mi lectura",
"confirmAction": "Confirmar la lectura",
"alreadySigned": "Ya confirmado",
"confirmed": "Lectura confirmada",
"mustLogin": "Iniciar sesión para confirmar",
"signing": "Confirmando...",
"signing": "Confirmación en curso...",
"verified": "Verificado",
"on": "El",
"error": {
"title": "Error en la confirmación",
"notAuthenticated": "Debe iniciar sesión para confirmar un documento.",
"notAuthenticated": "Debe estar conectado para confirmar un documento.",
"alreadySigned": "Ya ha confirmado este documento.",
"generic": "Ha ocurrido un error durante la confirmación. Por favor, inténtelo de nuevo."
"generic": "Ha ocurrido un error durante la confirmación. Por favor, inténtelo de nuevo.",
"missingDocId": "ID de documento faltante",
"authFailed": "Imposible iniciar la autenticación"
}
},
"signatureList": {
"loading": "Cargando confirmaciones...",
"email": "Correo electrónico",
"loading": "Cargando las confirmaciones...",
"email": "Email",
"date": "Fecha",
"signature": "Firma",
"confirmation": "Confirmación:",
"hash": "Hash",
"nonce": "Nonce",
"verificationStatus": "Estado de verificación",
"verified": "Verificado",
"confirmed": "Confirmado",
"notVerified": "No verificado",
"showDetails": "Mostrar detalles",
"hideDetails": "Ocultar detalles",
"viewDetails": "Ver detalles",
"verificationDetails": "Detalles de verificación",
"copy": "Copiar",
"copied": "¡Copiado!",
"previousHash": "Hash anterior"
"previousHash": "Hash anterior:",
"empty": "Ninguna confirmación encontrada",
"documentDeleted": "Documento eliminado",
"fields": {
"id": "ID:",
"document": "Documento:",
"reader": "Lector:",
"date": "Fecha:",
"source": "Origen:",
"nonce": "Nonce:",
"hash": "Hash:"
}
},
"signatures": {
"title": "Mis confirmaciones de lectura",
"subtitle": "Lista de todos los documentos cuya lectura ha confirmado criptográficamente",
"subtitle": "Lista de todos los documentos cuya lectura has confirmado criptográficamente",
"loading": "Cargando sus confirmaciones...",
"empty": {
"title": "Ninguna confirmación por el momento",
"description": "Aún no ha confirmado ningún documento. Comience confirmando un documento para verlo aparecer aquí."
"description": "Aún no ha confirmado ningún documento. Comience confirmando un documento para verlo aparecer aquí.",
"alternative": "Aún no ha confirmado la lectura de documentos"
},
"count": "{count} confirmación | {count} confirmaciones",
"results": "{count} resultado | {count} resultados",
"document": "Documento",
"signedAt": "Confirmado el",
"viewDetails": "Ver detalles",
"deletedDocuments": "Documentos eliminados",
"stats": {
"total": "Total",
"totalConfirmations": "Total confirmaciones",
"unique": "Únicos",
"uniqueDocuments": "Documentos únicos",
"last": "Último",
"lastConfirmation": "Última confirmación"
"lastConfirmation": "Última confirmación",
"notAvailable": "N/A"
},
"allConfirmations": "Todas mis confirmaciones",
"about": {
"title": "Acerca de las confirmaciones",
"description": "Cada confirmación se registra criptográficamente con Ed25519 y se encadena para garantizar la integridad. Las confirmaciones son no repudiables y están marcadas temporalmente con precisión."
"description": "Cada confirmación se registra criptográficamente con Ed25519 y se encadena para garantizar la integridad. Las confirmaciones son irrefutables y tienen una marca de tiempo precisa."
},
"search": "Buscar...",
"error": {
"title": "Error al cargar las confirmaciones",
"description": "No se pueden cargar sus confirmaciones. Por favor, inténtelo de nuevo."
"description": "Imposible cargar sus confirmaciones. Por favor, inténtelo de nuevo."
}
},
"admin": {
@@ -197,6 +220,7 @@
"totalDocuments": "Documentos totales",
"totalSignatures": "Confirmaciones totales",
"recentActivity": "Actividad reciente",
"backToDashboard": "Volver al panel de control",
"stats": {
"documents": "Documentos",
"readers": "Lectores",
@@ -204,19 +228,20 @@
"expected": "Esperados",
"signed": "Firmados",
"pending": "Pendientes",
"completion": "Finalización"
"completion": "Completitud"
}
},
"documents": {
"title": "Todos los documentos",
"new": "Crear nuevo documento",
"newDescription": "Preparar la referencia de un documento para rastrear las confirmaciones de lectura",
"newDescription": "Preparar una referencia de documento para seguir las confirmaciones de lectura",
"search": "Buscar...",
"searchPlaceholder": "Buscar por ID, título o URL...",
"id": "ID del documento",
"idLabel": "ID del documento",
"idHelper": "Solo letras, números, guiones y guiones bajos",
"idPlaceholder": "ej: politica-seguridad-2025",
"idLabel": "Documento",
"idHelper": "Acepta URLs, rutas de archivos o identificadores simples",
"idHelperShort": "Acepta URLs, rutas o identificadores",
"idPlaceholder": "URL, PATH o ID del documento",
"signatures": "Confirmaciones",
"created": "Creado",
"createdOn": "Creado el",
@@ -228,15 +253,79 @@
"manage": "Gestionar",
"edit": "Editar",
"delete": "Eliminar",
"empty": "No se encontraron documentos"
"empty": "Ningún documento encontrado",
"noResults": "Ningún resultado",
"noDocuments": "Ningún documento",
"tryAnotherSearch": "Pruebe otra búsqueda",
"willAppear": "Los documentos aparecerán aquí una vez creados",
"totalCount": "{count} documento en total | {count} documentos en total",
"pagination": {
"page": "Página {current}/{total}",
"pageOf": "Página {current} de {total}"
}
},
"webhooks": {
"title": "Webhooks",
"subtitle": "Configurar notificaciones hacia aplicaciones de terceros",
"manage": "Gestionar webhooks",
"new": "Nuevo webhook",
"edit": "Editar",
"delete": "Eliminar",
"enable": "Activar",
"disable": "Desactivar",
"status": {
"enabled": "Activo",
"disabled": "Inactivo"
},
"confirmDelete": "¿Eliminar este webhook?",
"empty": "Ningún webhook",
"listTitle": "Lista de webhooks",
"listSubtitle": "Un webhook puede escuchar varios eventos",
"columns": {
"title": "Nombre",
"url": "URL",
"events": "Eventos",
"status": "Estado",
"actions": "Acciones"
},
"form": {
"title": "Parámetros del webhook",
"subtitle": "Complete la URL, el secreto y los eventos",
"nameLabel": "Nombre del webhook",
"namePlaceholder": "Webhook CRM",
"urlLabel": "URL de destino",
"secretLabel": "Secreto HMAC",
"secretPlaceholder": "Ingrese un secreto (requerido)",
"secretKeep": "Dejar vacío para conservar",
"eventsLabel": "Eventos a escuchar",
"descriptionLabel": "Descripción (opcional)",
"descriptionPlaceholder": "Nota interna…",
"validation": "Por favor, complete el nombre, la URL, el secreto y al menos un evento."
},
"editTitle": "Editar el webhook",
"events": {
"documentCreated": "Documento creado",
"signatureCreated": "Firma creada",
"documentCompleted": "Documento completado",
"reminderSent": "Recordatorio enviado",
"reminderFailed": "Recordatorio fallido"
},
"eventsMap": {
"document.created": "Documento creado",
"signature.created": "Firma creada",
"document.completed": "Documento completado",
"reminder.sent": "Recordatorio enviado",
"reminder.failed": "Recordatorio fallido"
}
},
"documentDetail": {
"title": "Detalles del documento",
"metadata": "Metadatos y checksum del documento",
"title": "Documento",
"metadata": "📄 Información del documento",
"metadataDescription": "Metadatos y checksum del documento",
"checksum": "Checksum",
"algorithm": "Algoritmo",
"titleLabel": "Título",
"titlePlaceholder": "Política de Seguridad 2025",
"titlePlaceholder": "Política de seguridad 2025",
"urlLabel": "URL",
"urlPlaceholder": "https://example.com/doc.pdf",
"checksumLabel": "Checksum",
@@ -245,61 +334,102 @@
"descriptionLabel": "Descripción",
"descriptionPlaceholder": "Descripción del documento...",
"signatures": "Confirmaciones",
"back": "Volver a la lista",
"back": "Volver",
"expectedSigners": "Firmantes esperados",
"addExpectedSigner": "Agregar firmante esperado",
"addSigners": "Agregar lectores esperados",
"emailsLabel": "Correos electrónicos (uno por línea)",
"emailsPlaceholder": "María García <maria.garcia{'@'}example.com>\njuan.martinez{'@'}example.com\nSofía Rodríguez <sofia{'@'}example.com>",
"emailLabel": "Correo electrónico *",
"addExpectedSigner": "Añadir firmante esperado",
"addSigners": "Añadir lectores esperados",
"addButton": "Añadir",
"adding": "Añadiendo...",
"emailsLabel": "Emails (uno por línea)",
"emailsPlaceholder": "María López <maria.lopez{'@'}example.com>\njuan.martin{'@'}example.com>\nSofía García <sofia{'@'}example.com>",
"emailsHelper": "Formatos aceptados: \"Nombre Apellido <email{'@'}example.com>\" o \"email{'@'}example.com\"",
"emailLabel": "Email *",
"emailPlaceholder": "email{'@'}example.com",
"nameLabel": "Nombre",
"namePlaceholder": "Nombre completo",
"reader": "Lector",
"readers": "✓ Lectores esperados",
"user": "Usuario",
"status": "Estado",
"statusConfirmed": "✓ Confirmado",
"statusPending": "⏳ En espera",
"confirmedOn": "Confirmado el",
"noExpectedSigners": "Ningún lector esperado",
"noSignatures": "Ninguna confirmación",
"reminders": "Recordatorios por correo electrónico",
"reminders": "📧 Recordatorios por email",
"remindersDescription": "Enviar recordatorios a los lectores en espera de confirmación",
"remindersSent": "Recordatorios enviados",
"toRemind": "Por recordar",
"toRemind": "Para recordar",
"lastReminder": "Último recordatorio",
"sendReminder": "Enviar recordatorio",
"sending": "Enviando...",
"sendReminders": "Enviar recordatorios",
"sendToAll": "Enviar a todos los lectores en espera ({count})",
"sendToSelected": "Enviar solo a los seleccionados ({count})",
"allContacted": "✓ Todos los lectores esperados han sido contactados o han confirmado",
"unexpectedSignatures": "⚠ Confirmaciones de lectura complementarias",
"unexpectedDescription": "Usuarios que han confirmado pero no están presentes en la lista de lectores esperados",
"createdBy": "Creado por {by} el {date}",
"saving": "Guardando...",
"deleting": "Eliminando...",
"copiedToClipboard": "Copiado al portapapeles",
"metadataSaved": "Metadatos guardados con éxito",
"signersAdded": "{count} lector(es) añadido(s) con éxito",
"signerRemoved": "{email} eliminado con éxito",
"remindersSentSuccess": "{count} recordatorio(s) enviado(s) con éxito",
"remindersSentPartial": "{sent} recordatorio(s) enviado(s), {failed} fallo(s)",
"remindersSentGeneric": "Recordatorios enviados con éxito",
"confirmSendReminders": "¿Enviar recordatorios a {count} lector(es) en espera de confirmación?",
"confirmSendRemindersSelected": "¿Enviar recordatorios a {count} lector(es) seleccionado(s)?",
"confirmSendRemindersTitle": "📧 Enviar recordatorios",
"removeSignerTitle": "⚠️ Eliminar el lector esperado",
"removeSignerMessage": "¿Eliminar {email} de la lista de lectores esperados?",
"metadataWarning": {
"title": "⚠️ Atención: Invalidación de firmas",
"description": "Está a punto de modificar información crítica del documento (URL, checksum, algoritmo o descripción).",
"warning": "Esta modificación resultará en la invalidación de todas las firmas existentes, ya que están vinculadas criptográficamente al contenido actual del documento.",
"warning": "Esta modificación provocará la invalidación de todas las firmas existentes, ya que están vinculadas criptográficamente al contenido actual del documento.",
"currentSignatures": "Firmas actuales que serán invalidadas:",
"confirm": "Entiendo, continuar",
"cancel": "Cancelar"
},
"dangerZone": "Zona de peligro",
"dangerZoneDescription": "Acciones irreversibles en este documento",
"dangerZone": "⚠️ Zona de peligro",
"dangerZoneDescription": "Acciones irreversibles sobre este documento",
"deleteDocument": "Eliminar este documento",
"deleteDocumentDescription": "Esta acción eliminará definitivamente el documento, sus metadatos, los lectores esperados y todas las confirmaciones asociadas.\nEsta acción es irreversible.",
"deleteWarning": "¡Esta acción es irreversible!",
"deleteWillRemove": "Esta acción eliminará permanentemente:",
"deleteWillRemove": "La eliminación de este documento provocará la pérdida definitiva de:",
"deleteItem1": "Todos los metadatos del documento",
"deleteItem2": "La lista de lectores esperados",
"deleteItem3": "Todas las confirmaciones criptográficas",
"deleteItem4": "El historial de recordatorios"
"deleteItem4": "El historial de recordatorios",
"deleteConfirmTitle": "⚠️ Confirmar la eliminación",
"deleteConfirmButton": "Eliminar definitivamente",
"documentId": "ID del documento:"
},
"documentForm": {
"title": "Referencia del documento",
"label": "Referencia (URL, ruta o ID)",
"placeholder": "https://example.com/doc.pdf o /ruta/al/doc",
"submit": "Confirmar",
"creating": "Creando..."
"placeholder": "URL, PATH o REFERENCIA del documento a leer (opcional)",
"submit": "Comenzar",
"submitting": "Cargando...",
"creating": "Creación en curso..."
}
},
"embed": {
"loading": "Cargando información de firma...",
"loading": "Cargando información de la firma...",
"title": "Firma para",
"document": "Documento:",
"signedBy": "Firmado por",
"on": "el",
"verified": "Verificado",
"viewAll": "Ver todas las confirmaciones",
"error": "No se puede cargar la información de firma"
"error": "Imposible cargar la información de la firma",
"sign": "Firmar",
"signDocument": "Firmar este documento",
"noSignatures": "Ninguna firma para este documento",
"confirmationsCount": "{count} confirmación(es)",
"poweredBy": "Desarrollado por Ackify",
"missingDocId": "ID de documento faltante"
},
"notFound": {
"title": "Página no encontrada",
@@ -307,7 +437,7 @@
"home": "Volver al inicio"
},
"footer": {
"description": "Solución de código abierto de confirmación criptográfica de lectura de documentos con firmas Ed25519 no repudiables.",
"description": "Solución de código abierto de confirmación criptográfica de lectura de documentos con firmas Ed25519 irrefutables.",
"navigation": {
"title": "Navegación"
},
@@ -319,7 +449,7 @@
},
"legal": {
"title": "Legal",
"terms": "Términos de uso",
"terms": "Condiciones de uso",
"privacy": "Política de privacidad",
"contact": "Contacto"
},
@@ -329,7 +459,7 @@
"by": "por",
"links": {
"privacy": "Privacidad",
"terms": "Términos",
"terms": "Condiciones",
"contact": "Contacto"
}
},

View File

@@ -129,15 +129,20 @@
},
"signButton": {
"confirm": "Confirmer ma lecture",
"confirmAction": "Confirmer la lecture",
"alreadySigned": "Déjà confirmé",
"confirmed": "Lecture confirmée",
"mustLogin": "Se connecter pour confirmer",
"signing": "Confirmation en cours...",
"verified": "Vérifié",
"on": "Le",
"error": {
"title": "Échec de la confirmation",
"notAuthenticated": "Vous devez être connecté pour confirmer un document.",
"alreadySigned": "Vous avez déjà confirmé ce document.",
"generic": "Une erreur est survenue lors de la confirmation. Veuillez réessayer."
"generic": "Une erreur est survenue lors de la confirmation. Veuillez réessayer.",
"missingDocId": "Document ID manquant",
"authFailed": "Impossible de démarrer l'authentification"
}
},
"signatureList": {
@@ -145,16 +150,31 @@
"email": "Email",
"date": "Date",
"signature": "Signature",
"confirmation": "Confirmation:",
"hash": "Hash",
"nonce": "Nonce",
"verificationStatus": "Statut de vérification",
"verified": "Vérifié",
"confirmed": "Confirmé",
"notVerified": "Non vérifié",
"showDetails": "Afficher les détails",
"hideDetails": "Masquer les détails",
"viewDetails": "Voir détails",
"verificationDetails": "Détails de vérification",
"copy": "Copier",
"copied": "Copié !",
"previousHash": "Hash précédent"
"previousHash": "Hash précédent:",
"empty": "Aucune confirmation trouvée",
"documentDeleted": "Document supprimé",
"fields": {
"id": "ID:",
"document": "Document:",
"reader": "Lecteur:",
"date": "Date:",
"source": "Origine:",
"nonce": "Nonce:",
"hash": "Hash:"
}
},
"signatures": {
"title": "Mes confirmations de lecture",
@@ -162,20 +182,23 @@
"loading": "Chargement de vos confirmations...",
"empty": {
"title": "Aucune confirmation pour le moment",
"description": "Vous n'avez pas encore confirmé de document. Commencez par confirmer un document pour le voir apparaître ici."
"description": "Vous n'avez pas encore confirmé de document. Commencez par confirmer un document pour le voir apparaître ici.",
"alternative": "Vous n'avez pas encore confirmé la lecture de documents"
},
"count": "{count} confirmation | {count} confirmations",
"results": "{count} résultat | {count} résultats",
"document": "Document",
"signedAt": "Confirmé le",
"viewDetails": "Voir les détails",
"deletedDocuments": "Documents supprimés",
"stats": {
"total": "Total",
"totalConfirmations": "Total confirmations",
"unique": "Uniques",
"uniqueDocuments": "Documents uniques",
"last": "Dernier",
"lastConfirmation": "Dernière confirmation"
"lastConfirmation": "Dernière confirmation",
"notAvailable": "N/A"
},
"allConfirmations": "Toutes mes confirmations",
"about": {
@@ -197,6 +220,7 @@
"totalDocuments": "Documents totaux",
"totalSignatures": "Confirmations totales",
"recentActivity": "Activité récente",
"backToDashboard": "Retour au tableau de bord",
"stats": {
"documents": "Documents",
"readers": "Lecteurs",
@@ -214,9 +238,10 @@
"search": "Rechercher...",
"searchPlaceholder": "Rechercher par ID, titre ou URL...",
"id": "ID du document",
"idLabel": "ID du document",
"idHelper": "Lettres, chiffres, tirets et underscores uniquement",
"idPlaceholder": "ex: politique-securite-2025",
"idLabel": "Document",
"idHelper": "Accepte les URLs, les chemins de fichiers ou les identifiants simples",
"idHelperShort": "Accepte les URLs, chemins ou identifiants",
"idPlaceholder": "URL, PATH ou ID du document",
"signatures": "Confirmations",
"created": "Créé",
"createdOn": "Créé le",
@@ -228,7 +253,16 @@
"manage": "Gérer",
"edit": "Modifier",
"delete": "Supprimer",
"empty": "Aucun document trouvé"
"empty": "Aucun document trouvé",
"noResults": "Aucun résultat",
"noDocuments": "Aucun document",
"tryAnotherSearch": "Essayez une autre recherche",
"willAppear": "Les documents apparaîtront ici une fois créés",
"totalCount": "{count} document au total | {count} documents au total",
"pagination": {
"page": "Page {current}/{total}",
"pageOf": "Page {current} sur {total}"
}
},
"webhooks": {
"title": "Webhooks",
@@ -282,8 +316,9 @@
}
},
"documentDetail": {
"title": "Détails du document",
"metadata": "Métadonnées et checksum du document",
"title": "Document",
"metadata": "📄 Informations sur le document",
"metadataDescription": "Métadonnées et checksum du document",
"checksum": "Checksum",
"algorithm": "Algorithme",
"titleLabel": "Titre",
@@ -296,27 +331,56 @@
"descriptionLabel": "Description",
"descriptionPlaceholder": "Description du document...",
"signatures": "Confirmations",
"back": "Retour à la liste",
"back": "Retour",
"expectedSigners": "Signataires attendus",
"addExpectedSigner": "Ajouter un signataire attendu",
"addSigners": "Ajouter des lecteurs attendus",
"addButton": "Ajouter",
"adding": "Ajout...",
"emailsLabel": "Emails (un par ligne)",
"emailsPlaceholder": "Marie Dupont <marie.dupont{'@'}example.com>\njean.martin{'@'}example.com\nSophie Bernard <sophie{'@'}example.com>",
"emailsHelper": "Formats acceptés : \"Nom Prénom <email{'@'}example.com>\" ou \"email{'@'}example.com\"",
"emailLabel": "Email *",
"emailPlaceholder": "email{'@'}example.com",
"nameLabel": "Nom",
"namePlaceholder": "Nom complet",
"reader": "Lecteur",
"readers": "✓ Lecteurs attendus",
"user": "Utilisateur",
"status": "Statut",
"statusConfirmed": "✓ Confirmé",
"statusPending": "⏳ En attente",
"confirmedOn": "Confirmé le",
"noExpectedSigners": "Aucun lecteur attendu",
"noSignatures": "Aucune confirmation",
"reminders": "Relances email",
"reminders": "📧 Relances par email",
"remindersDescription": "Envoyer des rappels aux lecteurs en attente de confirmation",
"remindersSent": "Relances envoyées",
"toRemind": "À relancer",
"lastReminder": "Dernière relance",
"sendReminder": "Envoyer une relance",
"sending": "Envoi...",
"sendReminders": "Envoyer les relances",
"sendToAll": "Envoyer à tous les lecteurs en attente ({count})",
"sendToSelected": "Envoyer uniquement aux sélectionnés ({count})",
"allContacted": "✓ Tous les lecteurs attendus ont été contactés ou ont confirmé",
"unexpectedSignatures": "⚠ Confirmations de lecture complémentaires",
"unexpectedDescription": "Utilisateurs ayant confirmé mais non présents dans la liste des lecteurs attendus",
"createdBy": "Créé par {by} le {date}",
"saving": "Enregistrement...",
"deleting": "Suppression...",
"copiedToClipboard": "Copié dans le presse-papiers",
"metadataSaved": "Métadonnées enregistrées avec succès",
"signersAdded": "{count} lecteur(s) ajouté(s) avec succès",
"signerRemoved": "{email} retiré avec succès",
"remindersSentSuccess": "{count} relance(s) envoyée(s) avec succès",
"remindersSentPartial": "{sent} relance(s) envoyée(s), {failed} échec(s)",
"remindersSentGeneric": "Relances envoyées avec succès",
"confirmSendReminders": "Envoyer des relances à {count} lecteur(s) en attente de confirmation ?",
"confirmSendRemindersSelected": "Envoyer des relances à {count} lecteur(s) sélectionné(s) ?",
"confirmSendRemindersTitle": "📧 Envoyer des relances",
"removeSignerTitle": "⚠️ Retirer le lecteur attendu",
"removeSignerMessage": "Retirer {email} de la liste des lecteurs attendus ?",
"metadataWarning": {
"title": "⚠️ Attention : Invalidation des signatures",
"description": "Vous êtes sur le point de modifier des informations critiques du document (URL, checksum, algorithme ou description).",
@@ -325,32 +389,44 @@
"confirm": "Je comprends, continuer",
"cancel": "Annuler"
},
"dangerZone": "Zone de danger",
"dangerZone": "⚠️ Zone de danger",
"dangerZoneDescription": "Actions irréversibles sur ce document",
"deleteDocument": "Supprimer ce document",
"deleteDocumentDescription": "Cette action supprimera définitivement le document, ses métadonnées, les lecteurs attendus et toutes les confirmations associées.\nCette action est irréversible.",
"deleteWarning": "Cette action est irréversible !",
"deleteWillRemove": "Cette action supprimera définitivement :",
"deleteWillRemove": "La suppression de ce document entraînera la perte définitive de :",
"deleteItem1": "Toutes les métadonnées du document",
"deleteItem2": "La liste des lecteurs attendus",
"deleteItem3": "Toutes les confirmations cryptographiques",
"deleteItem4": "L'historique des relances"
"deleteItem4": "L'historique des relances",
"deleteConfirmTitle": "⚠️ Confirmer la suppression",
"deleteConfirmButton": "Supprimer définitivement",
"documentId": "Document ID:"
},
"documentForm": {
"title": "Référence du document",
"label": "Référence (URL, chemin ou ID)",
"placeholder": "https://example.com/doc.pdf ou /chemin/vers/doc",
"submit": "Confirmer",
"placeholder": "URL, PATH ou RÉFÉRENCE du document à lire (optionnel)",
"submit": "Commencer",
"submitting": "Chargement...",
"creating": "Création en cours..."
}
},
"embed": {
"loading": "Chargement des informations de signature...",
"title": "Signature pour",
"document": "Document:",
"signedBy": "Signé par",
"on": "le",
"verified": "Vérifié",
"viewAll": "Voir toutes les confirmations",
"error": "Impossible de charger les informations de signature"
"error": "Impossible de charger les informations de signature",
"sign": "Signer",
"signDocument": "Signer ce document",
"noSignatures": "Aucune signature pour ce document",
"confirmationsCount": "{count} confirmation(s)",
"poweredBy": "Powered by Ackify",
"missingDocId": "ID de document manquant"
},
"notFound": {
"title": "Page non trouvée",

View File

@@ -28,37 +28,37 @@
"connectedAs": "Connesso come"
},
"choice": {
"title": "Accedi ad Ackify",
"title": "Accesso ad Ackify",
"subtitle": "Scegli il tuo metodo di autenticazione preferito",
"privacy": "La tua autenticazione è sicura e crittografata"
},
"oauth": {
"title": "Accedi con OAuth",
"description": "Usa il tuo account esistente",
"title": "Accesso con OAuth",
"description": "Usa il tuo account aziendale",
"button": "Continua con OAuth",
"error": "Accesso OAuth fallito"
},
"magiclink": {
"title": "Accedi tramite email",
"title": "Accesso tramite Email",
"description": "Ti invieremo un link magico",
"email_label": "Indirizzo email",
"email_placeholder": "tu{'@'}esempio.com",
"button": "Invia link magico",
"sent": {
"title": "Controlla la tua email",
"message": "Ti abbiamo inviato un link magico. Clicca su di esso per accedere.",
"message": "Ti abbiamo inviato un link magico. Cliccaci sopra per accedere.",
"expire": "Il link scade tra 15 minuti."
},
"error_invalid_email": "Inserisci un indirizzo email valido",
"error_send": "Errore durante l'invio del link magico"
"error_send": "Invio del link magico fallito"
}
},
"sign": {
"title": "Conferma di Lettura",
"subtitle": "Certifica la tua lettura con una conferma crittografica Ed25519",
"loading": {
"title": "Caricamento documento...",
"description": "Attendere mentre prepariamo il documento per la firma."
"title": "Caricamento del documento...",
"description": "Attendi mentre prepariamo il documento per la firma."
},
"noDocument": {
"title": "Nessun documento specificato",
@@ -98,7 +98,7 @@
},
"howItWorks": {
"title": "Come funziona?",
"subtitle": "Ackify ti consente di provare crittograficamente di aver letto un documento",
"subtitle": "Ackify ti permette di provare crittograficamente di aver letto un documento",
"step1": {
"title": "1. Accedi al documento",
"description": "Aggiungi {code} all'indirizzo di questa pagina"
@@ -109,7 +109,7 @@
},
"step3": {
"title": "3. Conferma la lettura",
"description": "La tua conferma viene registrata con una firma Ed25519"
"description": "La tua conferma è registrata con una firma Ed25519"
},
"features": {
"crypto": {
@@ -122,39 +122,59 @@
},
"timestamp": {
"title": "Timestamp preciso",
"description": "Ogni conferma è timestampata e concatenata per garantire l'integrità"
"description": "Ogni conferma è datata e concatenata per garantire l'integrità"
}
}
}
},
"signButton": {
"confirm": "Conferma la mia lettura",
"confirmAction": "Conferma la lettura",
"alreadySigned": "Già confermato",
"confirmed": "Lettura confermata",
"mustLogin": "Accedi per confermare",
"signing": "Conferma in corso...",
"verified": "Verificato",
"on": "Il",
"error": {
"title": "Conferma fallita",
"notAuthenticated": "Devi essere connesso per confermare un documento.",
"alreadySigned": "Hai già confermato questo documento.",
"generic": "Si è verificato un errore durante la conferma. Riprova."
"generic": "Si è verificato un errore durante la conferma. Riprova.",
"missingDocId": "ID documento mancante",
"authFailed": "Impossibile avviare l'autenticazione"
}
},
"signatureList": {
"loading": "Caricamento conferme...",
"loading": "Caricamento delle conferme...",
"email": "Email",
"date": "Data",
"signature": "Firma",
"confirmation": "Conferma:",
"hash": "Hash",
"nonce": "Nonce",
"verificationStatus": "Stato di verifica",
"verified": "Verificato",
"confirmed": "Confermato",
"notVerified": "Non verificato",
"showDetails": "Mostra dettagli",
"hideDetails": "Nascondi dettagli",
"viewDetails": "Vedi dettagli",
"verificationDetails": "Dettagli di verifica",
"copy": "Copia",
"copied": "Copiato!",
"previousHash": "Hash precedente"
"previousHash": "Hash precedente:",
"empty": "Nessuna conferma trovata",
"documentDeleted": "Documento eliminato",
"fields": {
"id": "ID:",
"document": "Documento:",
"reader": "Lettore:",
"date": "Data:",
"source": "Origine:",
"nonce": "Nonce:",
"hash": "Hash:"
}
},
"signatures": {
"title": "Le mie conferme di lettura",
@@ -162,61 +182,66 @@
"loading": "Caricamento delle tue conferme...",
"empty": {
"title": "Nessuna conferma per il momento",
"description": "Non hai ancora confermato alcun documento. Inizia confermando un documento per vederlo apparire qui."
"description": "Non hai ancora confermato nessun documento. Inizia confermando un documento per vederlo apparire qui.",
"alternative": "Non hai ancora confermato la lettura di documenti"
},
"count": "{count} conferma | {count} conferme",
"results": "{count} risultato | {count} risultati",
"document": "Documento",
"signedAt": "Confermato il",
"viewDetails": "Vedi dettagli",
"deletedDocuments": "Documenti eliminati",
"stats": {
"total": "Totale",
"totalConfirmations": "Conferme totali",
"unique": "Unici",
"uniqueDocuments": "Documenti unici",
"last": "Ultimo",
"lastConfirmation": "Ultima conferma"
"lastConfirmation": "Ultima conferma",
"notAvailable": "N/D"
},
"allConfirmations": "Tutte le mie conferme",
"about": {
"title": "Informazioni sulle conferme",
"description": "Ogni conferma è registrata crittograficamente con Ed25519 e concatenata per garantire l'integrità. Le conferme sono non ripudiabili e timestampate con precisione."
"description": "Ogni conferma viene registrata crittograficamente con Ed25519 e concatenata per garantire l'integrità. Le conferme sono irrevocabili e timestampate in modo preciso."
},
"search": "Cerca...",
"error": {
"title": "Errore nel caricamento delle conferme",
"title": "Errore di caricamento delle conferme",
"description": "Impossibile caricare le tue conferme. Riprova."
}
},
"admin": {
"title": "Amministrazione",
"subtitle": "Gestisci documenti e lettori attesi",
"subtitle": "Gestire documenti e lettori previsti",
"loading": "Caricamento dati...",
"dashboard": {
"title": "Dashboard",
"title": "Pannello di controllo",
"totalDocuments": "Documenti totali",
"totalSignatures": "Conferme totali",
"recentActivity": "Attività recente",
"backToDashboard": "Torna al pannello di controllo",
"stats": {
"documents": "Documenti",
"readers": "Lettori",
"active": "Attivi",
"expected": "Attesi",
"expected": "Previsti",
"signed": "Firmati",
"pending": "In sospeso",
"pending": "In attesa",
"completion": "Completamento"
}
},
"documents": {
"title": "Tutti i documenti",
"new": "Crea nuovo documento",
"newDescription": "Prepara il riferimento di un documento per tracciare le conferme di lettura",
"newDescription": "Preparare un riferimento documento per tracciare le conferme di lettura",
"search": "Cerca...",
"searchPlaceholder": "Cerca per ID, titolo o URL...",
"id": "ID del documento",
"idLabel": "ID del documento",
"idHelper": "Solo lettere, numeri, trattini e underscore",
"idPlaceholder": "es: politica-sicurezza-2025",
"id": "ID documento",
"idLabel": "Documento",
"idHelper": "Accetta URL, percorsi di file o identificatori semplici",
"idHelperShort": "Accetta URL, percorsi o identificatori",
"idPlaceholder": "URL, PATH o ID del documento",
"signatures": "Conferme",
"created": "Creato",
"createdOn": "Creato il",
@@ -224,82 +249,187 @@
"url": "URL",
"document": "Documento",
"actions": "Azioni",
"view": "Visualizza",
"view": "Vedi",
"manage": "Gestisci",
"edit": "Modifica",
"delete": "Elimina",
"empty": "Nessun documento trovato"
"empty": "Nessun documento trovato",
"noResults": "Nessun risultato",
"noDocuments": "Nessun documento",
"tryAnotherSearch": "Prova un'altra ricerca",
"willAppear": "I documenti appariranno qui una volta creati",
"totalCount": "{count} documento totale | {count} documenti totali",
"pagination": {
"page": "Pagina {current}/{total}",
"pageOf": "Pagina {current} di {total}"
}
},
"webhooks": {
"title": "Webhook",
"subtitle": "Configurare le notifiche verso applicazioni terze",
"manage": "Gestisci webhook",
"new": "Nuovo webhook",
"edit": "Modifica",
"delete": "Elimina",
"enable": "Attiva",
"disable": "Disattiva",
"status": {
"enabled": "Attivo",
"disabled": "Inattivo"
},
"confirmDelete": "Eliminare questo webhook?",
"empty": "Nessun webhook",
"listTitle": "Elenco webhook",
"listSubtitle": "Un webhook può ascoltare più eventi",
"columns": {
"title": "Nome",
"url": "URL",
"events": "Eventi",
"status": "Stato",
"actions": "Azioni"
},
"form": {
"title": "Parametri del webhook",
"subtitle": "Inserisci l'URL, il segreto e gli eventi",
"nameLabel": "Nome del webhook",
"namePlaceholder": "Webhook CRM",
"urlLabel": "URL di destinazione",
"secretLabel": "Segreto HMAC",
"secretPlaceholder": "Inserisci un segreto (richiesto)",
"secretKeep": "Lascia vuoto per mantenere",
"eventsLabel": "Eventi da ascoltare",
"descriptionLabel": "Descrizione (opzionale)",
"descriptionPlaceholder": "Nota interna…",
"validation": "Completa il nome, l'URL, il segreto e almeno un evento."
},
"editTitle": "Modifica il webhook",
"events": {
"documentCreated": "Documento creato",
"signatureCreated": "Firma creata",
"documentCompleted": "Documento completato",
"reminderSent": "Promemoria inviato",
"reminderFailed": "Promemoria fallito"
},
"eventsMap": {
"document.created": "Documento creato",
"signature.created": "Firma creata",
"document.completed": "Documento completato",
"reminder.sent": "Promemoria inviato",
"reminder.failed": "Promemoria fallito"
}
},
"documentDetail": {
"title": "Dettagli del documento",
"metadata": "Metadati e checksum del documento",
"title": "Documento",
"metadata": "📄 Informazioni sul documento",
"metadataDescription": "Metadati e checksum del documento",
"checksum": "Checksum",
"algorithm": "Algoritmo",
"titleLabel": "Titolo",
"titlePlaceholder": "Politica di Sicurezza 2025",
"titlePlaceholder": "Politica di sicurezza 2025",
"urlLabel": "URL",
"urlPlaceholder": "https://example.com/doc.pdf",
"urlPlaceholder": "https://esempio.com/doc.pdf",
"checksumLabel": "Checksum",
"checksumPlaceholder": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"algorithmLabel": "Algoritmo",
"descriptionLabel": "Descrizione",
"descriptionPlaceholder": "Descrizione del documento...",
"signatures": "Conferme",
"back": "Torna all'elenco",
"expectedSigners": "Firmatari attesi",
"addExpectedSigner": "Aggiungi firmatario atteso",
"addSigners": "Aggiungi lettori attesi",
"back": "Indietro",
"expectedSigners": "Firmatari previsti",
"addExpectedSigner": "Aggiungi firmatario previsto",
"addSigners": "Aggiungi lettori previsti",
"addButton": "Aggiungi",
"adding": "Aggiunta...",
"emailsLabel": "Email (una per riga)",
"emailsPlaceholder": "Maria Rossi <maria.rossi{'@'}example.com>\ngiovanni.bianchi{'@'}example.com\nSofia Verdi <sofia{'@'}example.com>",
"emailsPlaceholder": "Maria Rossi <maria.rossi{'@'}esempio.com>\ngiovanni.bianchi{'@'}esempio.com>\nSofia Verdi <sofia{'@'}esempio.com>",
"emailsHelper": "Formati accettati: \"Nome Cognome <email{'@'}esempio.com>\" o \"email{'@'}esempio.com\"",
"emailLabel": "Email *",
"emailPlaceholder": "email{'@'}example.com",
"emailPlaceholder": "email{'@'}esempio.com",
"nameLabel": "Nome",
"namePlaceholder": "Nome completo",
"reader": "Lettore",
"readers": "✓ Lettori previsti",
"user": "Utente",
"status": "Stato",
"statusConfirmed": "✓ Confermato",
"statusPending": "⏳ In attesa",
"confirmedOn": "Confermato il",
"noExpectedSigners": "Nessun lettore atteso",
"noExpectedSigners": "Nessun lettore previsto",
"noSignatures": "Nessuna conferma",
"reminders": "Promemoria email",
"reminders": "📧 Solleciti via email",
"remindersDescription": "Inviare promemoria ai lettori in attesa di conferma",
"remindersSent": "Promemoria inviati",
"toRemind": "Da ricordare",
"lastReminder": "Ultimo promemoria",
"sendReminder": "Invia promemoria",
"sending": "Invio...",
"sendReminders": "Invia solleciti",
"sendToAll": "Invia a tutti i lettori in attesa ({count})",
"sendToSelected": "Invia solo ai selezionati ({count})",
"allContacted": "✓ Tutti i lettori previsti sono stati contattati o hanno confermato",
"unexpectedSignatures": "⚠ Conferme di lettura aggiuntive",
"unexpectedDescription": "Utenti che hanno confermato ma non presenti nell'elenco dei lettori previsti",
"createdBy": "Creato da {by} il {date}",
"saving": "Salvataggio...",
"deleting": "Eliminazione...",
"copiedToClipboard": "Copiato negli appunti",
"metadataSaved": "Metadati salvati con successo",
"signersAdded": "{count} lettore/i aggiunto/i con successo",
"signerRemoved": "{email} rimosso con successo",
"remindersSentSuccess": "{count} sollecito/i inviato/i con successo",
"remindersSentPartial": "{sent} sollecito/i inviato/i, {failed} fallito/i",
"remindersSentGeneric": "Solleciti inviati con successo",
"confirmSendReminders": "Inviare solleciti a {count} lettore/i in attesa di conferma?",
"confirmSendRemindersSelected": "Inviare solleciti a {count} lettore/i selezionato/i?",
"confirmSendRemindersTitle": "📧 Invia solleciti",
"removeSignerTitle": "⚠️ Rimuovi lettore previsto",
"removeSignerMessage": "Rimuovere {email} dall'elenco dei lettori previsti?",
"metadataWarning": {
"title": "⚠️ Attenzione: Invalidazione delle firme",
"description": "Stai per modificare informazioni critiche del documento (URL, checksum, algoritmo o descrizione).",
"warning": "Questa modifica comporterà l'invalidazione di tutte le firme esistenti, poiché sono legate crittograficamente al contenuto attuale del documento.",
"warning": "Questa modifica comporterà l'invalidazione di tutte le firme esistenti, poiché sono collegate crittograficamente al contenuto attuale del documento.",
"currentSignatures": "Firme attuali che saranno invalidate:",
"confirm": "Capisco, continua",
"cancel": "Annulla"
},
"dangerZone": "Zona pericolosa",
"dangerZone": "⚠️ Zona di pericolo",
"dangerZoneDescription": "Azioni irreversibili su questo documento",
"deleteDocument": "Elimina questo documento",
"deleteDocumentDescription": "Questa azione eliminerà definitivamente il documento, i suoi metadati, i lettori previsti e tutte le conferme associate.\nQuesta azione è irreversibile.",
"deleteWarning": "Questa azione è irreversibile!",
"deleteWillRemove": "Questa azione rimuoverà definitivamente:",
"deleteWillRemove": "L'eliminazione di questo documento comporterà la perdita definitiva di:",
"deleteItem1": "Tutti i metadati del documento",
"deleteItem2": "L'elenco dei lettori attesi",
"deleteItem2": "L'elenco dei lettori previsti",
"deleteItem3": "Tutte le conferme crittografiche",
"deleteItem4": "La cronologia dei promemoria"
"deleteItem4": "La cronologia dei promemoria",
"deleteConfirmTitle": "⚠️ Conferma eliminazione",
"deleteConfirmButton": "Elimina definitivamente",
"documentId": "ID Documento:"
},
"documentForm": {
"title": "Riferimento del documento",
"label": "Riferimento (URL, percorso o ID)",
"placeholder": "https://example.com/doc.pdf o /percorso/al/doc",
"submit": "Conferma",
"placeholder": "URL, PATH o RIFERIMENTO del documento da leggere (opzionale)",
"submit": "Inizia",
"submitting": "Caricamento...",
"creating": "Creazione in corso..."
}
},
"embed": {
"loading": "Caricamento informazioni firma...",
"loading": "Caricamento delle informazioni di firma...",
"title": "Firma per",
"document": "Documento:",
"signedBy": "Firmato da",
"on": "il",
"verified": "Verificato",
"viewAll": "Vedi tutte le conferme",
"error": "Impossibile caricare le informazioni della firma"
"error": "Impossibile caricare le informazioni di firma",
"sign": "Firma",
"signDocument": "Firma questo documento",
"noSignatures": "Nessuna firma per questo documento",
"confirmationsCount": "{count} conferma/e",
"poweredBy": "Powered by Ackify",
"missingDocId": "ID documento mancante"
},
"notFound": {
"title": "Pagina non trovata",
@@ -319,9 +449,9 @@
},
"legal": {
"title": "Legale",
"terms": "Termini di utilizzo",
"privacy": "Politica sulla privacy",
"contact": "Contatto"
"terms": "Condizioni d'uso",
"privacy": "Informativa sulla privacy",
"contact": "Contatti"
},
"copyright": "Tutti i diritti riservati.",
"license": "Licenza AGPL-3.0-or-later",
@@ -329,8 +459,8 @@
"by": "da",
"links": {
"privacy": "Privacy",
"terms": "Termini",
"contact": "Contatto"
"terms": "Condizioni",
"contact": "Contatti"
}
},
"toast": {
@@ -355,7 +485,7 @@
"actions": "Azioni",
"details": "Dettagli",
"back": "Indietro",
"next": "Avanti",
"next": "Successivo",
"previous": "Precedente",
"skipToContent": "Vai al contenuto principale"
}

View File

@@ -17,7 +17,7 @@
<div v-if="documentData.signatures.length > 0">
<div class="mb-6">
<h2 class="text-xl font-bold text-foreground mb-2">
Document: {{ documentData.title }}
{{ t('embed.document') }} {{ documentData.title }}
</h2>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-4 text-sm text-muted-foreground">
@@ -25,7 +25,7 @@
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
{{ documentData.signatures.length }} confirmation(s)
{{ t('embed.confirmationsCount', { count: documentData.signatures.length }) }}
</span>
<span v-if="documentData.metadata?.title">{{ documentData.metadata.title }}</span>
</div>
@@ -38,7 +38,7 @@
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
</svg>
Signer
{{ t('embed.sign') }}
</a>
</div>
</div>
@@ -66,7 +66,7 @@
<svg class="w-16 h-16 mx-auto mb-4 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<p class="text-sm text-muted-foreground mb-4">Aucune signature pour ce document</p>
<p class="text-sm text-muted-foreground mb-4">{{ t('embed.noSignatures') }}</p>
<a
:href="signUrl"
target="_blank"
@@ -75,7 +75,7 @@
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
</svg>
Signer ce document
{{ t('embed.signDocument') }}
</a>
</div>
@@ -86,7 +86,7 @@
target="_blank"
class="text-xs text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background rounded"
>
Powered by Ackify
{{ t('embed.poweredBy') }}
</a>
</div>
</div>
@@ -96,12 +96,14 @@
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { usePageTitle } from '@/composables/usePageTitle'
import { documentService } from '@/services/documents'
import http, { extractError } from '@/services/http'
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
usePageTitle('embed.title')
// State
@@ -130,7 +132,7 @@ function formatDateCompact(dateString: string): string {
async function loadDocument() {
if (!docRef.value) {
error.value = 'ID de document manquant'
error.value = t('embed.missingDocId')
loading.value = false
return
}

View File

@@ -106,7 +106,7 @@ onMounted(() => {
</div>
<div class="flex flex-col items-center justify-center gap-1 px-3 py-3 rounded-lg bg-blue-500/10 text-blue-600 dark:text-blue-400">
<Clock :size="18" />
<span class="text-sm font-bold">{{ lastSignatureDate || 'N/A' }}</span>
<span class="text-sm font-bold">{{ lastSignatureDate || t('signatures.stats.notAvailable') }}</span>
<span class="text-xs whitespace-nowrap">{{ t('signatures.stats.last') }}</span>
</div>
</div>
@@ -155,7 +155,7 @@ onMounted(() => {
<div class="flex-1">
<p class="text-sm font-medium text-muted-foreground">{{ t('signatures.stats.lastConfirmation') }}</p>
<p class="text-lg font-semibold text-foreground">
{{ lastSignatureDate || 'N/A' }}
{{ lastSignatureDate || t('signatures.stats.notAvailable') }}
</p>
</div>
</div>
@@ -212,7 +212,7 @@ onMounted(() => {
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p class="mt-2 text-muted-foreground">Vous n'avez pas encore confirmé la lecture de documents</p>
<p class="mt-2 text-muted-foreground">{{ t('signatures.empty.alternative') }}</p>
</div>
<div v-else class="space-y-4">
@@ -231,7 +231,7 @@ onMounted(() => {
<div v-if="activeSignatures.length > 0 && deletedSignatures.length > 0" class="py-4">
<hr class="border-border" />
<p class="text-center text-sm text-muted-foreground mt-4 mb-2">
Documents supprimés
{{ t('signatures.deletedDocuments') }}
</p>
</div>

View File

@@ -5,6 +5,7 @@ import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { usePageTitle } from '@/composables/usePageTitle'
import { listDocuments, type Document } from '@/services/admin'
import { documentService } from '@/services/documents'
import { extractError } from '@/services/http'
import { FileText, Users, CheckCircle, ExternalLink, Settings, Loader2, Plus, Search, Webhook } from 'lucide-vue-next'
import Card from '@/components/ui/Card.vue'
@@ -102,8 +103,12 @@ async function createDocument() {
try {
creating.value = true
error.value = ''
// Navigate to document detail page (will be created next)
await router.push({ name: 'admin-document', params: { docId: newDocId.value.trim() } })
// Use findOrCreateDocument to handle URL, path, or ID
const response = await documentService.findOrCreateDocument(newDocId.value.trim())
// Navigate to document detail page with the returned docId
await router.push({ name: 'admin-document', params: { docId: response.docId } })
} catch (err) {
error.value = extractError(err)
console.error('Failed to create document:', err)
@@ -175,7 +180,6 @@ onMounted(() => {
id="newDocId"
type="text"
required
pattern="[a-zA-Z0-9\-_]+"
:placeholder="t('admin.documents.idPlaceholder')"
class="w-full"
/>
@@ -203,7 +207,6 @@ onMounted(() => {
id="newDocIdMobile"
type="text"
required
pattern="[a-zA-Z0-9\-_]+"
:placeholder="t('admin.documents.idPlaceholder')"
class="flex-1"
/>
@@ -213,7 +216,7 @@ onMounted(() => {
</Button>
</div>
<p class="text-xs text-muted-foreground">
{{ t('admin.documents.idHelper') }}
{{ t('admin.documents.idHelperShort') }}
</p>
</div>
</form>
@@ -365,7 +368,7 @@ onMounted(() => {
>
<Button variant="ghost" size="sm">
<Settings :size="16" class="mr-1" />
Gérer
{{ t('admin.documents.manage') }}
</Button>
</router-link>
</TableCell>
@@ -417,7 +420,7 @@ onMounted(() => {
>
<Button variant="outline" size="sm" class="w-full">
<Settings :size="16" class="mr-2" />
Gérer
{{ t('admin.documents.manage') }}
</Button>
</router-link>
</div>
@@ -431,10 +434,10 @@ onMounted(() => {
<FileText :size="28" class="text-muted-foreground" />
</div>
<h3 class="mb-2 text-lg font-semibold text-foreground">
{{ searchQuery ? 'Aucun résultat' : 'Aucun document' }}
{{ searchQuery ? t('admin.documents.noResults') : t('admin.documents.noDocuments') }}
</h3>
<p class="text-sm text-muted-foreground">
{{ searchQuery ? 'Essayez une autre recherche' : 'Les documents apparaîtront ici une fois créés' }}
{{ searchQuery ? t('admin.documents.tryAnotherSearch') : t('admin.documents.willAppear') }}
</p>
</div>
@@ -448,10 +451,10 @@ onMounted(() => {
:disabled="currentPage === 1"
@click="prevPage"
>
Précédent
{{ t('common.previous') }}
</Button>
<span class="text-sm text-muted-foreground">
Page {{ currentPage }}/{{ totalPages }}
{{ t('admin.documents.pagination.page', { current: currentPage, total: totalPages }) }}
</span>
<Button
variant="outline"
@@ -459,14 +462,14 @@ onMounted(() => {
:disabled="currentPage >= totalPages"
@click="nextPage"
>
Suivant
{{ t('common.next') }}
</Button>
</div>
<!-- Desktop Pagination -->
<div class="hidden md:flex items-center justify-between w-full">
<div class="text-sm text-muted-foreground">
{{ totalDocuments }} document{{ totalDocuments > 1 ? 's' : '' }} au total
{{ t('admin.documents.totalCount', totalDocuments) }}
</div>
<div class="flex items-center gap-2">
<Button
@@ -475,10 +478,10 @@ onMounted(() => {
:disabled="currentPage === 1"
@click="prevPage"
>
Précédent
{{ t('common.previous') }}
</Button>
<span class="text-sm text-muted-foreground">
Page {{ currentPage }} sur {{ totalPages }}
{{ t('admin.documents.pagination.pageOf', { current: currentPage, total: totalPages }) }}
</span>
<Button
variant="outline"
@@ -486,7 +489,7 @@ onMounted(() => {
:disabled="currentPage >= totalPages"
@click="nextPage"
>
Suivant
{{ t('common.next') }}
</Button>
</div>
</div>

View File

@@ -177,7 +177,7 @@ async function saveMetadata() {
success.value = ''
showMetadataWarningModal.value = false
await updateDocumentMetadata(docId.value, metadataForm.value)
success.value = 'Métadonnées enregistrées avec succès'
success.value = t('admin.documentDetail.metadataSaved')
await loadDocumentStatus()
setTimeout(() => (success.value = ''), 3000)
} catch (err) {
@@ -216,7 +216,7 @@ async function addSigners() {
showAddSignersModal.value = false
signersEmails.value = ''
success.value = `${addedCount} lecteur(s) ajouté(s) avec succès`
success.value = t('admin.documentDetail.signersAdded', { count: addedCount })
await loadDocumentStatus()
setTimeout(() => (success.value = ''), 3000)
} catch (err) {
@@ -240,7 +240,7 @@ async function removeSigner() {
error.value = ''
success.value = ''
await removeExpectedSigner(docId.value, email)
success.value = `${email} retiré avec succès`
success.value = t('admin.documentDetail.signerRemoved', { email })
showRemoveSignerModal.value = false
signerToRemove.value = ''
await loadDocumentStatus()
@@ -259,8 +259,8 @@ function cancelRemoveSigner() {
function confirmSendReminders() {
remindersMessage.value =
sendMode.value === 'all'
? `Envoyer des relances à ${reminderStats.value?.pendingCount || 0} lecteur(s) en attente de confirmation ?`
: `Envoyer des relances à ${selectedEmails.value.length} lecteur(s) sélectionné(s) ?`
? t('admin.documentDetail.confirmSendReminders', { count: reminderStats.value?.pendingCount || 0 })
: t('admin.documentDetail.confirmSendRemindersSelected', { count: selectedEmails.value.length })
showSendRemindersModal.value = true
}
@@ -288,12 +288,12 @@ async function sendRemindersAction() {
if (response.data.result) {
const result = response.data.result
if (result.failed > 0) {
success.value = `${result.successfullySent} relance(s) envoyée(s), ${result.failed} échec(s)`
success.value = t('admin.documentDetail.remindersSentPartial', { sent: result.successfullySent, failed: result.failed })
} else {
success.value = `${result.successfullySent} relance(s) envoyée(s) avec succès`
success.value = t('admin.documentDetail.remindersSentSuccess', { count: result.successfullySent })
}
} else {
success.value = 'Relances envoyées avec succès'
success.value = t('admin.documentDetail.remindersSentGeneric')
}
await loadDocumentStatus()
@@ -312,7 +312,7 @@ function cancelSendReminders() {
function copyToClipboard(text: string) {
navigator.clipboard.writeText(text)
success.value = 'Copié dans le presse-papiers'
success.value = t('admin.documentDetail.copiedToClipboard')
setTimeout(() => (success.value = ''), 2000)
}
@@ -370,16 +370,16 @@ onMounted(() => {
<!-- Header -->
<div class="mb-8">
<div class="flex items-center space-x-3 mb-2">
<Button variant="ghost" size="icon" @click="router.push('/admin')" aria-label="Retour">
<Button variant="ghost" size="icon" @click="router.push('/admin')" :aria-label="t('admin.documentDetail.back')">
<ArrowLeft :size="20" />
</Button>
<h1 class="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
Document {{ docId }}
{{ t('admin.documentDetail.title') }} {{ docId }}
</h1>
</div>
<div class="flex items-center gap-3 ml-14">
<p class="text-sm text-muted-foreground font-mono">{{ shareLink }}</p>
<Button @click="copyToClipboard(shareLink)" variant="ghost" size="icon" aria-label="Copier le lien">
<Button @click="copyToClipboard(shareLink)" variant="ghost" size="icon" :aria-label="t('signatureList.copy')">
<Copy :size="16" />
</Button>
</div>
@@ -397,7 +397,7 @@ onMounted(() => {
<!-- Loading -->
<div v-if="loading" class="flex flex-col items-center justify-center py-24">
<Loader2 :size="48" class="animate-spin text-primary" />
<p class="mt-4 text-muted-foreground">Chargement...</p>
<p class="mt-4 text-muted-foreground">{{ t('common.loading') }}</p>
</div>
<!-- Content -->
@@ -411,7 +411,7 @@ onMounted(() => {
<Users :size="24" class="text-blue-600" />
</div>
<div>
<p class="text-sm font-medium text-muted-foreground">Attendus</p>
<p class="text-sm font-medium text-muted-foreground">{{ t('admin.dashboard.stats.expected') }}</p>
<p class="text-2xl font-bold text-foreground">{{ stats.expectedCount }}</p>
</div>
</div>
@@ -425,7 +425,7 @@ onMounted(() => {
<CheckCircle :size="24" class="text-green-600" />
</div>
<div>
<p class="text-sm font-medium text-muted-foreground">Confirmés</p>
<p class="text-sm font-medium text-muted-foreground">{{ t('admin.dashboard.stats.signed') }}</p>
<p class="text-2xl font-bold text-foreground">{{ stats.signedCount }}</p>
</div>
</div>
@@ -439,7 +439,7 @@ onMounted(() => {
<Clock :size="24" class="text-orange-600" />
</div>
<div>
<p class="text-sm font-medium text-muted-foreground">En attente</p>
<p class="text-sm font-medium text-muted-foreground">{{ t('admin.dashboard.stats.pending') }}</p>
<p class="text-2xl font-bold text-foreground">{{ stats.pendingCount }}</p>
</div>
</div>
@@ -453,7 +453,7 @@ onMounted(() => {
<Shield :size="24" class="text-purple-600" />
</div>
<div>
<p class="text-sm font-medium text-muted-foreground">Complétion</p>
<p class="text-sm font-medium text-muted-foreground">{{ t('admin.dashboard.stats.completion') }}</p>
<p class="text-2xl font-bold text-foreground">{{ Math.round(stats.completionRate) }}%</p>
</div>
</div>
@@ -465,8 +465,8 @@ onMounted(() => {
<Card class="clay-card">
<CardHeader>
<div>
<CardTitle>📄 Informations sur le document</CardTitle>
<CardDescription>Métadonnées et checksum du document</CardDescription>
<CardTitle>{{ t('admin.documentDetail.metadata') }}</CardTitle>
<CardDescription>{{ t('admin.documentDetail.metadataDescription') }}</CardDescription>
</div>
</CardHeader>
<CardContent>
@@ -474,23 +474,23 @@ onMounted(() => {
<!-- Titre et URL côte à côte -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium mb-2">Titre</label>
<Input v-model="metadataForm.title" placeholder="Politique de sécurité 2025" />
<label class="block text-sm font-medium mb-2">{{ t('admin.documentDetail.titleLabel') }}</label>
<Input v-model="metadataForm.title" :placeholder="t('admin.documentDetail.titlePlaceholder')" />
</div>
<div>
<label class="block text-sm font-medium mb-2">URL</label>
<Input v-model="metadataForm.url" type="url" placeholder="https://example.com/doc.pdf" />
<label class="block text-sm font-medium mb-2">{{ t('admin.documentDetail.urlLabel') }}</label>
<Input v-model="metadataForm.url" type="url" :placeholder="t('admin.documentDetail.urlPlaceholder')" />
</div>
</div>
<!-- Checksum et Algorithme côte à côte -->
<div class="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-4">
<div>
<label class="block text-sm font-medium mb-2">Checksum</label>
<Input v-model="metadataForm.checksum" placeholder="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" class="font-mono text-sm" />
<label class="block text-sm font-medium mb-2">{{ t('admin.documentDetail.checksumLabel') }}</label>
<Input v-model="metadataForm.checksum" :placeholder="t('admin.documentDetail.checksumPlaceholder')" class="font-mono text-sm" />
</div>
<div class="md:min-w-[140px]">
<label class="block text-sm font-medium mb-2">Algorithme</label>
<label class="block text-sm font-medium mb-2">{{ t('admin.documentDetail.algorithmLabel') }}</label>
<select v-model="metadataForm.checksumAlgorithm" class="flex h-10 w-full rounded-md clay-input px-3 py-2 text-sm">
<option value="SHA-256">SHA-256</option>
<option value="SHA-512">SHA-512</option>
@@ -500,15 +500,15 @@ onMounted(() => {
</div>
<div>
<label class="block text-sm font-medium mb-2">Description</label>
<Textarea v-model="metadataForm.description" :rows="4" placeholder="Description du document..." />
<label class="block text-sm font-medium mb-2">{{ t('admin.documentDetail.descriptionLabel') }}</label>
<Textarea v-model="metadataForm.description" :rows="4" :placeholder="t('admin.documentDetail.descriptionPlaceholder')" />
</div>
<div v-if="documentMetadata" class="text-xs text-muted-foreground pt-2 border-t">
Créé par {{ documentMetadata.createdBy }} le {{ formatDate(documentMetadata.createdAt) }}
{{ t('admin.documentDetail.createdBy', { by: documentMetadata.createdBy, date: formatDate(documentMetadata.createdAt) }) }}
</div>
<div class="flex justify-end">
<Button type="submit" :disabled="savingMetadata">
{{ savingMetadata ? 'Enregistrement...' : 'Enregistrer' }}
{{ savingMetadata ? t('admin.documentDetail.saving') : t('common.save') }}
</Button>
</div>
</form>
@@ -520,12 +520,12 @@ onMounted(() => {
<CardHeader>
<div class="flex items-center justify-between">
<div>
<CardTitle> Lecteurs attendus</CardTitle>
<CardDescription v-if="stats">{{ stats.signedCount }} / {{ stats.expectedCount }} confirmés</CardDescription>
<CardTitle>{{ t('admin.documentDetail.readers') }}</CardTitle>
<CardDescription v-if="stats">{{ stats.signedCount }} / {{ stats.expectedCount }} {{ t('admin.dashboard.stats.signed').toLowerCase() }}</CardDescription>
</div>
<Button @click="showAddSignersModal = true" size="sm">
<Plus :size="16" class="mr-2" />
Ajouter
{{ t('admin.documentDetail.addButton') }}
</Button>
</div>
</CardHeader>
@@ -539,10 +539,10 @@ onMounted(() => {
<input type="checkbox" class="rounded"
@change="(e: any) => selectedEmails = e.target.checked ? expectedSigners.filter(s => !s.hasSigned).map(s => s.email) : []" />
</TableHead>
<TableHead>Lecteur</TableHead>
<TableHead>Statut</TableHead>
<TableHead>Confirmé le</TableHead>
<TableHead>Actions</TableHead>
<TableHead>{{ t('admin.documentDetail.reader') }}</TableHead>
<TableHead>{{ t('admin.documentDetail.status') }}</TableHead>
<TableHead>{{ t('admin.documentDetail.confirmedOn') }}</TableHead>
<TableHead>{{ t('common.actions') }}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -560,7 +560,7 @@ onMounted(() => {
</TableCell>
<TableCell>
<Badge :variant="signer.hasSigned ? 'default' : 'secondary'">
{{ signer.hasSigned ? '✓ Confirmé' : '⏳ En attente' }}
{{ signer.hasSigned ? t('admin.documentDetail.statusConfirmed') : t('admin.documentDetail.statusPending') }}
</Badge>
</TableCell>
<TableCell>
@@ -577,24 +577,24 @@ onMounted(() => {
</div>
<div v-else class="text-center py-8 text-muted-foreground">
<Users :size="48" class="mx-auto mb-4 opacity-50" />
<p>Aucun lecteur attendu</p>
<p>{{ t('admin.documentDetail.noExpectedSigners') }}</p>
</div>
<!-- Confirmations complémentaires (toujours visible si présents) -->
<div v-if="unexpectedSignatures.length > 0" class="mt-8 pt-8 border-t border-border">
<h3 class="text-lg font-semibold mb-4 flex items-center">
<span class="mr-2"></span>
Confirmations de lecture complémentaires
{{ t('admin.documentDetail.unexpectedSignatures') }}
<Badge variant="secondary" class="ml-2">{{ unexpectedSignatures.length }}</Badge>
</h3>
<p class="text-sm text-muted-foreground mb-4">
Utilisateurs ayant confirmé mais non présents dans la liste des lecteurs attendus
{{ t('admin.documentDetail.unexpectedDescription') }}
</p>
<Table>
<TableHeader>
<TableRow>
<TableHead>Utilisateur</TableHead>
<TableHead>Confirmé le</TableHead>
<TableHead>{{ t('admin.documentDetail.user') }}</TableHead>
<TableHead>{{ t('admin.documentDetail.confirmedOn') }}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -616,22 +616,22 @@ onMounted(() => {
<!-- Email Reminders -->
<Card v-if="reminderStats && stats && stats.expectedCount > 0" class="clay-card">
<CardHeader>
<CardTitle>📧 Relances par email</CardTitle>
<CardDescription>Envoyer des rappels aux lecteurs en attente de confirmation</CardDescription>
<CardTitle>{{ t('admin.documentDetail.reminders') }}</CardTitle>
<CardDescription>{{ t('admin.documentDetail.remindersDescription') }}</CardDescription>
</CardHeader>
<CardContent class="space-y-6">
<!-- Stats -->
<div class="grid gap-4 sm:grid-cols-3">
<div class="bg-muted rounded-lg p-4">
<p class="text-sm text-muted-foreground">Relances envoyées</p>
<p class="text-sm text-muted-foreground">{{ t('admin.documentDetail.remindersSent') }}</p>
<p class="text-2xl font-bold">{{ reminderStats.totalSent }}</p>
</div>
<div class="bg-muted rounded-lg p-4">
<p class="text-sm text-muted-foreground">À relancer</p>
<p class="text-sm text-muted-foreground">{{ t('admin.documentDetail.toRemind') }}</p>
<p class="text-2xl font-bold">{{ reminderStats.pendingCount }}</p>
</div>
<div v-if="reminderStats.lastSentAt" class="bg-muted rounded-lg p-4">
<p class="text-sm text-muted-foreground">Dernière relance</p>
<p class="text-sm text-muted-foreground">{{ t('admin.documentDetail.lastReminder') }}</p>
<p class="text-sm font-bold">{{ formatDate(reminderStats.lastSentAt) }}</p>
</div>
</div>
@@ -641,20 +641,20 @@ onMounted(() => {
<div class="space-y-2">
<label class="flex items-center space-x-2">
<input type="radio" v-model="sendMode" value="all" class="rounded-full" />
<span>Envoyer à tous les lecteurs en attente ({{ reminderStats.pendingCount }})</span>
<span>{{ t('admin.documentDetail.sendToAll', { count: reminderStats.pendingCount }) }}</span>
</label>
<label class="flex items-center space-x-2">
<input type="radio" v-model="sendMode" value="selected" class="rounded-full" />
<span>Envoyer uniquement aux sélectionnés ({{ selectedEmails.length }})</span>
<span>{{ t('admin.documentDetail.sendToSelected', { count: selectedEmails.length }) }}</span>
</label>
</div>
<Button @click="confirmSendReminders" :disabled="sendingReminders || (sendMode === 'selected' && selectedEmails.length === 0)">
<Mail :size="16" class="mr-2" />
{{ sendingReminders ? 'Envoi...' : 'Envoyer les relances' }}
{{ sendingReminders ? t('admin.documentDetail.sending') : t('admin.documentDetail.sendReminders') }}
</Button>
</div>
<div v-else class="text-center py-4 text-muted-foreground">
Tous les lecteurs attendus ont été contactés ou ont confirmé
{{ t('admin.documentDetail.allContacted') }}
</div>
</CardContent>
</Card>
@@ -662,16 +662,15 @@ onMounted(() => {
<!-- Danger Zone -->
<Card class="clay-card border-destructive/50">
<CardHeader>
<CardTitle class="text-destructive"> Zone de danger</CardTitle>
<CardDescription>Actions irréversibles sur ce document</CardDescription>
<CardTitle class="text-destructive">{{ t('admin.documentDetail.dangerZone') }}</CardTitle>
<CardDescription>{{ t('admin.documentDetail.dangerZoneDescription') }}</CardDescription>
</CardHeader>
<CardContent>
<div class="flex items-center justify-between p-4 bg-destructive/5 rounded-lg">
<div class="flex-1">
<h3 class="font-semibold text-foreground mb-1">Supprimer ce document</h3>
<h3 class="font-semibold text-foreground mb-1">{{ t('admin.documentDetail.deleteDocument') }}</h3>
<p class="text-sm text-muted-foreground">
Cette action supprimera définitivement le document, ses métadonnées, les lecteurs attendus et toutes les confirmations associées.<br>
Cette action est irréversible.
{{ t('admin.documentDetail.deleteDocumentDescription') }}
</p>
</div>
<Button
@@ -680,7 +679,7 @@ onMounted(() => {
class="ml-4"
>
<Trash2 :size="16" class="mr-2" />
Supprimer
{{ t('common.delete') }}
</Button>
</div>
</CardContent>
@@ -696,7 +695,7 @@ onMounted(() => {
<Card class="max-w-2xl w-full">
<CardHeader>
<div class="flex items-center justify-between">
<CardTitle>Ajouter des lecteurs attendus</CardTitle>
<CardTitle>{{ t('admin.documentDetail.addSigners') }}</CardTitle>
<Button variant="ghost" size="icon" @click="showAddSignersModal = false">
<X :size="20" />
</Button>
@@ -705,17 +704,17 @@ onMounted(() => {
<CardContent>
<form @submit.prevent="addSigners" class="space-y-4">
<div>
<label class="block text-sm font-medium mb-2">Emails (un par ligne)</label>
<label class="block text-sm font-medium mb-2">{{ t('admin.documentDetail.emailsLabel') }}</label>
<Textarea v-model="signersEmails" :rows="8"
placeholder="Marie Dupont <marie.dupont@example.com>&#10;jean.martin@example.com&#10;Sophie Bernard <sophie@example.com>" />
:placeholder="t('admin.documentDetail.emailsPlaceholder')" />
<p class="text-xs text-muted-foreground mt-2">
Formats acceptés : "Nom Prénom &lt;email@example.com&gt;" ou "email@example.com"
{{ t('admin.documentDetail.emailsHelper') }}
</p>
</div>
<div class="flex justify-end space-x-3">
<Button type="button" variant="outline" @click="showAddSignersModal = false">Annuler</Button>
<Button type="button" variant="outline" @click="showAddSignersModal = false">{{ t('common.cancel') }}</Button>
<Button type="submit" :disabled="addingSigners || !signersEmails.trim()">
{{ addingSigners ? 'Ajout...' : 'Ajouter' }}
{{ addingSigners ? t('admin.documentDetail.adding') : t('admin.documentDetail.addButton') }}
</Button>
</div>
</form>
@@ -728,7 +727,7 @@ onMounted(() => {
<Card class="max-w-md w-full border-destructive">
<CardHeader>
<div class="flex items-center justify-between">
<CardTitle class="text-destructive"> Confirmer la suppression</CardTitle>
<CardTitle class="text-destructive">{{ t('admin.documentDetail.deleteConfirmTitle') }}</CardTitle>
<Button variant="ghost" size="icon" @click="showDeleteConfirmModal = false">
<X :size="20" />
</Button>
@@ -738,28 +737,26 @@ onMounted(() => {
<div class="space-y-4">
<Alert variant="destructive" class="border-destructive">
<AlertDescription>
<p class="font-semibold mb-2">Cette action est irréversible !</p>
<p class="font-semibold mb-2">{{ t('admin.documentDetail.deleteWarning') }}</p>
<p class="text-sm">
La suppression de ce document entraînera la perte définitive de :
{{ t('admin.documentDetail.deleteWillRemove') }}
</p>
<ul class="text-sm list-disc list-inside mt-2 space-y-1">
<li>Toutes les métadonnées du document</li>
<li>La liste des lecteurs attendus</li>
<li>Toutes les confirmations cryptographiques</li>
<li>L'historique des relances</li>
<li>{{ t('admin.documentDetail.deleteItem1') }}</li>
<li>{{ t('admin.documentDetail.deleteItem2') }}</li>
<li>{{ t('admin.documentDetail.deleteItem3') }}</li>
<li>{{ t('admin.documentDetail.deleteItem4') }}</li>
</ul>
</AlertDescription>
</Alert>
<div class="bg-muted p-3 rounded-lg">
<p class="text-sm font-mono text-muted-foreground">
Document ID: <span class="text-foreground font-semibold">{{ docId }}</span>
</p>
<p class="text-sm font-mono text-muted-foreground" v-html="t('admin.documentDetail.documentId') + ' ' + docId"></p>
</div>
<div class="flex justify-end space-x-3 pt-4">
<Button type="button" variant="outline" @click="showDeleteConfirmModal = false">
Annuler
{{ t('common.cancel') }}
</Button>
<Button
@click="handleDeleteDocument"
@@ -768,7 +765,7 @@ onMounted(() => {
>
<Trash2 v-if="!deletingDocument" :size="16" class="mr-2" />
<Loader2 v-else :size="16" class="mr-2 animate-spin" />
{{ deletingDocument ? 'Suppression...' : 'Supprimer définitivement' }}
{{ deletingDocument ? t('admin.documentDetail.deleting') : t('admin.documentDetail.deleteConfirmButton') }}
</Button>
</div>
</div>
@@ -838,10 +835,10 @@ onMounted(() => {
<!-- Remove Signer Confirmation Dialog -->
<ConfirmDialog
v-if="showRemoveSignerModal"
title="⚠️ Retirer le lecteur attendu"
:message="`Retirer ${signerToRemove} de la liste des lecteurs attendus ?`"
confirm-text="Retirer"
cancel-text="Annuler"
:title="t('admin.documentDetail.removeSignerTitle')"
:message="t('admin.documentDetail.removeSignerMessage', { email: signerToRemove })"
:confirm-text="t('common.delete')"
:cancel-text="t('common.cancel')"
variant="warning"
@confirm="removeSigner"
@cancel="cancelRemoveSigner"
@@ -850,10 +847,10 @@ onMounted(() => {
<!-- Send Reminders Confirmation Dialog -->
<ConfirmDialog
v-if="showSendRemindersModal"
title="📧 Envoyer des relances"
:title="t('admin.documentDetail.confirmSendRemindersTitle')"
:message="remindersMessage"
confirm-text="Envoyer"
cancel-text="Annuler"
:confirm-text="t('common.confirm')"
:cancel-text="t('common.cancel')"
variant="default"
:loading="sendingReminders"
@confirm="sendRemindersAction"