diff --git a/frontend/js/ai.js b/frontend/js/ai.js index 73ef02c..928ab58 100644 --- a/frontend/js/ai.js +++ b/frontend/js/ai.js @@ -220,7 +220,7 @@ export async function createAIToolbar(container, getEditorView) { if (!aiConfigured) { const hint = document.createElement('div'); hint.style.cssText = 'padding:6px 12px;font-size:0.7rem;color:var(--text-muted);border-bottom:1px solid var(--border-color)'; - hint.textContent = '⚠️ AI non configuré — ajouter DEEPSEEK_API_KEY, OPENROUTER_API_KEY ou GEMINI_API_KEY dans .env'; + hint.textContent = t('ai.not_configured'); container.appendChild(hint); return; } @@ -256,7 +256,7 @@ export async function createAIToolbar(container, getEditorView) { padding: '3px 8px', borderRadius: '4px', }); - aiBtn.title = 'AI Inline Completion (Ctrl+J) — Complète le texte automatiquement'; + aiBtn.title = t('ai.inline_completion_title'); aiBtn.addEventListener('click', async () => { const v = ev(); if (!v) return; @@ -268,7 +268,7 @@ export async function createAIToolbar(container, getEditorView) { try { const result = await aiAction('inline-complete', text); replaceSelection(v, result, 'append'); - showToast('AI: complétion ajoutée', 'success'); + showToast(t('ai.completion_added'), 'success'); } catch (e) { showToast('AI: ' + (String(e.message || e).includes('401') ? 'clé API invalide' : e.message), 'error'); } finally { @@ -312,8 +312,8 @@ export async function createAIToolbar(container, getEditorView) { // ── Custom Rewrite ── const rewriteBtn = document.createElement('button'); rewriteBtn.className = 'ai-toolbar-btn'; - rewriteBtn.innerHTML = '💬 Réécrire'; - rewriteBtn.title = 'Réécrit le texte selon vos instructions'; + rewriteBtn.innerHTML = t('ai.rewrite'); + rewriteBtn.title = t('ai.rewrite_title'); Object.assign(rewriteBtn.style, { fontSize: '0.7rem', background: 'transparent', @@ -332,7 +332,7 @@ export async function createAIToolbar(container, getEditorView) { try { const result = await aiAction('rewrite', text, { instruction }); replaceSelection(v, result); - showToast('AI: texte réécrit', 'success'); + showToast(t('ai.text_rewritten'), 'success'); } catch (e) { showToast('AI: ' + (String(e.message || e).includes('401') ? 'clé API invalide' : e.message), 'error'); } @@ -364,10 +364,10 @@ export async function createAIToolbar(container, getEditorView) { if (!v) return; const text = getSelection(v); if (!text.trim()) { - showToast('AI: sélectionnez du texte à traiter', 'warning'); + showToast(t('ai.select_text'), 'warning'); return; } - showToast('⏳ AI: traitement en cours...', 'info'); + showToast(t('ai.processing'), 'info'); // Show processing indicator in the editor header _showProcessingIndicator(true); @@ -377,13 +377,13 @@ export async function createAIToolbar(container, getEditorView) { try { const result = await aiAction(endpoint, text, extra); replaceSelection(v, result, mode); - showToast('AI: texte traité ✓', 'success'); + showToast(t('ai.text_processed'), 'success'); } catch (e) { const msg = String(e.message || e); if (msg.includes('401')) { - showToast('AI: clé API invalide. Vérifiez DEEPSEEK_API_KEY dans .env', 'error'); + showToast(t('ai.invalid_api_key'), 'error'); } else if (msg.includes('402') || msg.includes('429')) { - showToast('AI: quota dépassé ou paiement requis', 'error'); + showToast(t('ai.quota_exceeded'), 'error'); } else { showToast('AI: ' + msg, 'error'); } diff --git a/frontend/js/app.js b/frontend/js/app.js index 61e2741..404116e 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -84,7 +84,7 @@ async function init() { } } catch (err) { console.error("Failed to initialize ObsiGate:", err); - UI.showToast("Erreur lors de l'initialisation", "error"); + UI.showToast(t("error.initialization"), "error"); } } diff --git a/frontend/js/auth.js b/frontend/js/auth.js index 065606b..b0f04da 100644 --- a/frontend/js/auth.js +++ b/frontend/js/auth.js @@ -195,7 +195,7 @@ const AuthManager = { userMenu.innerHTML = ""; return; } - userMenu.innerHTML = '' + (user.display_name || user.username) + "" + ''; + userMenu.innerHTML = '' + (user.display_name || user.username) + "" + ''; safeCreateIcons(); const logoutBtn = document.getElementById("logout-btn"); @@ -535,14 +535,14 @@ const AdminPanel = { async _deleteUser(username) { const currentUser = AuthManager.getUser(); if (currentUser && currentUser.username === username) { - showToast("Impossible de supprimer son propre compte", "error"); + showToast(t("auth.cannot_delete_self"), "error"); return; } if (!confirm("Supprimer l'utilisateur \"" + username + '" ?')) return; try { await api("/api/auth/admin/users/" + username, { method: "DELETE" }); this._loadUsers(); - showToast("Utilisateur supprimé", "success"); + showToast(t("auth.user_deleted"), "success"); } catch (err) { showToast(err.message, "error"); } diff --git a/frontend/js/backup-manager.js b/frontend/js/backup-manager.js index d9c8e26..ba7bc00 100644 --- a/frontend/js/backup-manager.js +++ b/frontend/js/backup-manager.js @@ -235,7 +235,7 @@ async function previewBackup(fpath) { async function restoreBackup(vault, file) { // Find the backup timestamp from the currently selected backup var item = document.querySelector('.bm-backup-item.active'); - if (!item) { showToast('Selectionnez un backup a restaurer', 'error'); return; } + if (!item) { showToast(t('backup.select_to_restore'), 'error'); return; } var backupPath = item.dataset.path; // Extract timestamp from the filename @@ -248,7 +248,7 @@ async function restoreBackup(vault, file) { break; } } - if (!ts) { showToast('Impossible de trouver le timestamp du backup', 'error'); return; } + if (!ts) { showToast(t('backup.timestamp_not_found'), 'error'); return; } if (!confirm('Restaurer "' + file + '" a la version du backup selectionne ?\nLa version actuelle sera sauvegardee.')) return; @@ -259,7 +259,7 @@ async function restoreBackup(vault, file) { body: JSON.stringify({ version: ts }), }); if (resp && resp.success) { - showToast('Fichier restaure avec succes', 'success'); + showToast(t('backup.file_restored'), 'success'); load(); } } catch (err) { diff --git a/frontend/js/backups.js b/frontend/js/backups.js index ff21204..fb3fdd2 100644 --- a/frontend/js/backups.js +++ b/frontend/js/backups.js @@ -337,14 +337,14 @@ async function restoreBackup() { ); if (resp && resp.success) { - showToast('Fichier restaure avec succes', 'success'); + showToast(t('backup.file_restored'), 'success'); await refreshSidebarTreePreservingState(); close(); if (typeof TabManager !== 'undefined' && TabManager.reloadCurrent) { TabManager.reloadCurrent(); } } else { - showToast('Echec de la restauration', 'error'); + showToast(t('backup.restore_failed'), 'error'); } } catch (err) { showToast('Erreur: ' + ((err && err.detail) || (err && err.message) || 'Unknown'), 'error'); diff --git a/frontend/js/config.js b/frontend/js/config.js index 2332ca7..ddaa0e7 100644 --- a/frontend/js/config.js +++ b/frontend/js/config.js @@ -801,7 +801,7 @@ async function saveBackendConfig() { } } catch (err) { console.error("Failed to save backend config:", err); - showToast("Erreur de sauvegarde", "error"); + showToast(t("config.save_error"), "error"); } } @@ -809,7 +809,7 @@ async function forceReindex() { const btn = document.getElementById("cfg-reindex"); if (btn) { btn.disabled = true; - btn.textContent = "Réindexation..."; + btn.textContent = t('config.reindexing'); } try { await api("/api/index/reload"); @@ -818,11 +818,11 @@ async function forceReindex() { await Promise.all([loadVaults(), loadTags()]); } catch (err) { console.error("Reindex error:", err); - showToast("Erreur de réindexation", "error"); + showToast(t("config.reindex_error"), "error"); } finally { if (btn) { btn.disabled = false; - btn.textContent = "Forcer réindexation"; + btn.textContent = t('config.force_reindex'); } } } @@ -1067,7 +1067,7 @@ async function saveHiddenFilesSettings() { // Reload vault settings to update the cache await loadVaultSettings(); - showToast("✓ Paramètres sauvegardés", "success"); + showToast(t("config.settings_saved"), "success"); // Refresh the UI to apply the filter await Promise.all([refreshSidebarTreePreservingState(), refreshTagsForContext()]); @@ -1113,7 +1113,7 @@ document.addEventListener("click", function(e) { if (e.target.id === "webhook-add-btn") { const name = document.getElementById("webhook-name-input").value.trim(); const url = document.getElementById("webhook-url-input").value.trim(); - if (!url) { showToast("URL requise", "error"); return; } + if (!url) { showToast(t("config.url_required"), "error"); return; } api("/api/webhooks", { method: "POST", body: JSON.stringify({ name: name || "Webhook", url, events: ["file_created","file_deleted","file_modified","file_renamed"] }) }).then(() => { loadWebhooksUI(); document.getElementById("webhook-name-input").value = ""; document.getElementById("webhook-url-input").value = ""; }).catch(err => showToast(err.message, "error")); } }); @@ -1186,13 +1186,13 @@ export async function openShareDialog(vault, path) { document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } - showToast("Lien copié !", "success"); + showToast(t("config.link_copied"), "success"); div.remove(); }); div.querySelector(".share-revoke-btn").addEventListener("click", async () => { try { await api(`/api/share/${existingShare.id}`, { method: "DELETE" }); - showToast("Partage révoqué", "success"); + showToast(t("config.share_revoked"), "success"); existingShare = null; renderContent(); } catch (err) { showToast("Erreur: " + err.message, "error"); } @@ -1228,7 +1228,7 @@ export async function openShareDialog(vault, path) { }); existingShare = share; renderContent(); - showToast("Lien créé !", "success"); + showToast(t("config.link_created"), "success"); } catch (err) { showToast("Erreur: " + err.message, "error"); } }); } @@ -1365,10 +1365,10 @@ async function saveAIKeys() { const input = document.getElementById(id); if (input && input.value.trim()) keys[name] = input.value.trim(); } - if (!Object.keys(keys).length) { showToast("Aucune cle a sauvegarder", "warning"); return; } + if (!Object.keys(keys).length) { showToast(t("config.no_keys"), "warning"); return; } try { await api("/api/config/ai-keys", { method: "POST", body: JSON.stringify(keys) }); - showToast("Cles API sauvegardees", "success"); + showToast(t("config.api_keys_saved"), "success"); // Clear inputs Object.keys(map).forEach(id => { const el = document.getElementById(id); if (el) el.value = ""; }); loadAIKeys(); @@ -1461,8 +1461,8 @@ function initThemePicker() { // Mode toggle var toggle = document.createElement('div'); toggle.className = 'theme-mode-toggle'; - var dBtn = elBtn('Sombre', 'dark', mode); - var lBtn = elBtn('Clair', 'light', mode); + var dBtn = elBtn(t('theme.dark'), 'dark', mode); + var lBtn = elBtn(t('theme.light'), 'light', mode); toggle.appendChild(dBtn); toggle.appendChild(lBtn); grid.appendChild(toggle); diff --git a/frontend/js/dashboard.js b/frontend/js/dashboard.js index b0932ec..7c598ae 100644 --- a/frontend/js/dashboard.js +++ b/frontend/js/dashboard.js @@ -83,7 +83,7 @@ const DashboardSharedWidget = { e.stopPropagation(); const url = b.dataset.url; try { await navigator.clipboard.writeText(url); } catch { const ta = document.createElement("textarea"); ta.value=url; ta.style.position="fixed"; ta.style.left="-9999px"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } - showToast("Lien copié !", "success"); + showToast(t("config.link_copied"), "success"); })); grid.querySelectorAll(".shared-open-btn").forEach(b => b.addEventListener("click", (e) => { e.stopPropagation(); @@ -93,7 +93,7 @@ const DashboardSharedWidget = { grid.querySelectorAll(".shared-revoke-btn").forEach(b => b.addEventListener("click", async (e) => { e.stopPropagation(); await api(`/api/share/${b.dataset.id}`, { method: "DELETE" }); - showToast("Partage révoqué", "success"); + showToast(t("config.share_revoked"), "success"); this.load(); })); grid.querySelectorAll(".shared-card").forEach(card => card.addEventListener("click", () => { @@ -147,7 +147,7 @@ const DashboardConflictsWidget = { async _resolve(d, action) { try { await api("/api/conflicts/resolve", { method: "POST", body: JSON.stringify({ vault: d.vault, conflict_path: d.conflict, original_path: d.original, action }) }); - showToast("Conflit résolu", "success"); + showToast(t("dashboard.conflict_resolved"), "success"); this.load(); } catch (err) { showToast("Erreur: " + err.message, "error"); } } @@ -204,7 +204,7 @@ const DashboardRecentWidget = { }); } catch (err) { console.error("Failed to toggle bookmark:", err); - showToast("Erreur lors de l'épinglage", "error"); + showToast(t("dashboard.pin_error"), "error"); } }, diff --git a/frontend/js/dragdrop.js b/frontend/js/dragdrop.js index 8aeff60..2c56e27 100644 --- a/frontend/js/dragdrop.js +++ b/frontend/js/dragdrop.js @@ -235,7 +235,7 @@ async function onDrop(e) { // Don't move if source and target are the same vault if (vault !== targetVault) { - showToast('Cross-vault moves are not supported', 'error'); + showToast(t('dragdrop.cross_vault_unsupported'), 'error'); onDragEnd(); return; } @@ -262,7 +262,7 @@ async function onDrop(e) { // Refresh the tree preserving expanded state await refreshSidebarTreePreservingState(); } else { - showToast('Move failed', 'error'); + showToast(t('dragdrop.move_failed'), 'error'); } } catch (err) { console.error('Drag & drop move failed:', err); diff --git a/frontend/js/offline.js b/frontend/js/offline.js index a3c53db..d893423 100644 --- a/frontend/js/offline.js +++ b/frontend/js/offline.js @@ -21,9 +21,9 @@ export async function initOffline() { updateOfflineIndicator(); if (online) { syncPendingChanges(); - showToast('Connexion retablie — synchronisation en cours...', 'info'); + showToast(t('offline.reconnected'), 'info'); } else { - showToast('Mode hors-ligne — vos modifications seront synchronisees au retour', 'warning'); + showToast(t('offline.mode_offline'), 'warning'); } }); @@ -182,7 +182,7 @@ export async function syncPendingChanges() { updateOfflineIndicator(); if (remaining === 0) { - showToast('Toutes les modifications ont ete synchronisees', 'success'); + showToast(t('offline.all_synced'), 'success'); } else { showToast(remaining + ' modification(s) en attente de resolution', 'warning'); } diff --git a/frontend/js/search.js b/frontend/js/search.js index 3a49c1e..4012cde 100644 --- a/frontend/js/search.js +++ b/frontend/js/search.js @@ -984,7 +984,7 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { exclude_paths: exclEl?.value || "", }), }); - showToast("Recherche sauvegardée", "success"); + showToast(t("search.saved"), "success"); loadSavedSearches(); } catch (err) { showToast("Erreur: " + err.message, "error"); } }); @@ -1128,7 +1128,7 @@ export function renderAdvancedSearchResults(data, query, tagFilter) { if (data.total > state.ADVANCED_SEARCH_LIMIT) { const paginationDiv = el("div", { class: "search-pagination" }); const prevBtn = el("button", { class: "search-pagination__btn", type: "button" }); - prevBtn.textContent = "← Précédent"; + prevBtn.textContent = t('search.previous'); prevBtn.disabled = state.advancedSearchOffset === 0; prevBtn.addEventListener("click", () => { state.advancedSearchOffset = Math.max(0, state.advancedSearchOffset - state.ADVANCED_SEARCH_LIMIT); diff --git a/frontend/js/sidebar.js b/frontend/js/sidebar.js index 845112f..fd18911 100644 --- a/frontend/js/sidebar.js +++ b/frontend/js/sidebar.js @@ -223,7 +223,7 @@ async function loadVaults() { container.innerHTML = ""; // Prepare dropdown options - const dropdownOptions = [{ value: "all", text: "Tous les vaults" }, ...vaults.map((v) => ({ value: v.name, text: v.name }))]; + const dropdownOptions = [{ value: "all", text: t('help.all_vaults') }, ...vaults.map((v) => ({ value: v.name, text: v.name }))]; // Populate custom dropdowns populateCustomDropdown("vault-filter-dropdown", dropdownOptions, "all"); diff --git a/frontend/js/sidebar_raw.js b/frontend/js/sidebar_raw.js index 78c708a..e454eda 100644 --- a/frontend/js/sidebar_raw.js +++ b/frontend/js/sidebar_raw.js @@ -201,7 +201,7 @@ async function loadVaults() { container.innerHTML = ""; // Prepare dropdown options - const dropdownOptions = [{ value: "all", text: "Tous les vaults" }, ...vaults.map((v) => ({ value: v.name, text: v.name }))]; + const dropdownOptions = [{ value: "all", text: t('help.all_vaults') }, ...vaults.map((v) => ({ value: v.name, text: v.name }))]; // Populate custom dropdowns populateCustomDropdown("vault-filter-dropdown", dropdownOptions, "all"); diff --git a/frontend/js/sync.js b/frontend/js/sync.js index 745707e..d745e2f 100644 --- a/frontend/js/sync.js +++ b/frontend/js/sync.js @@ -187,7 +187,7 @@ export const IndexUpdateManager = (() => { async function _onIndexReloaded(data) { connectionState = "syncing"; _updateBadge(); - showToast("Index complet rechargé", "info"); + showToast(t("sync.index_reloaded"), "info"); try { await Promise.all([loadVaults(), loadTags()]); } catch (err) { @@ -562,7 +562,7 @@ window.addEventListener("beforeinstallprompt", (e) => { // Log when app is installed window.addEventListener("appinstalled", () => { console.log("[PWA] ObsiGate has been installed"); - showToast("ObsiGate installé avec succès !", "success"); + showToast(t("sync.install_success"), "success"); }); // ── Dashboard tab switching (runs on page load and after rebuild) ── diff --git a/frontend/js/ui.js b/frontend/js/ui.js index d215e92..9babb75 100644 --- a/frontend/js/ui.js +++ b/frontend/js/ui.js @@ -153,7 +153,7 @@ export function applyTheme(theme) { if (icon) { icon.setAttribute("data-lucide", theme === "dark" ? "moon" : "sun"); } - themeLabel.textContent = theme === "dark" ? "Sombre" : "Clair"; + themeLabel.textContent = theme === "dark" ? t('theme.dark') : t('theme.light'); safeCreateIcons(); } @@ -860,7 +860,7 @@ const FileOperations = { // Find the text span (.tree-item-text) — tree items use spans, not direct text nodes const textSpan = item.querySelector('.tree-item-text'); if (!textSpan) { - showToast('Impossible de renommer cet élément', 'error'); + showToast(t('ui.cannot_rename'), 'error'); return; } @@ -1586,7 +1586,7 @@ async function init() { } } catch (err) { console.error("Failed to initialize ObsiGate:", err); - showToast("Erreur lors de l'initialisation", "error"); + showToast(t("error.initialization"), "error"); } } diff --git a/frontend/js/viewer.js b/frontend/js/viewer.js index 73daa2a..6e88c2a 100644 --- a/frontend/js/viewer.js +++ b/frontend/js/viewer.js @@ -360,7 +360,7 @@ export async function openFile(vaultName, filePath) { offline: true }; renderFile(fakeData); - showToast('Fichier charge depuis le cache hors-ligne', 'info'); + showToast(t('viewer.loaded_from_cache'), 'info'); } else { area.innerHTML = '

Impossible de charger le fichier — vous etes hors-ligne et ce fichier n\'est pas en cache.

'; } @@ -488,11 +488,11 @@ export function renderFile(data) { document.execCommand("copy"); document.body.removeChild(ta); } - copyBtn.lastChild.textContent = "Copié !"; - setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500); + copyBtn.lastChild.textContent = t('viewer.copied'); + setTimeout(() => (copyBtn.lastChild.textContent = t('viewer.copy')), 1500); } catch (err) { console.error("Copy error:", err); - showToast("Erreur lors de la copie", "error"); + showToast(t("viewer.copy_error"), "error"); } }); @@ -567,7 +567,7 @@ export function renderFile(data) { const shares = await api("/api/shares"); if (shares.some(s => s.vault === data.vault && s.path === data.path)) { shareBtn.classList.add("shared"); - shareBtn.title = "Document partagé — cliquer pour gérer"; + shareBtn.title = t('viewer.shared_doc_title'); } } catch (e) { /* ignore */ } })(); @@ -1331,7 +1331,7 @@ const IndexUpdateManager = (() => { async function _onIndexReloaded(data) { connectionState = "syncing"; _updateBadge(); - showToast("Index complet rechargé", "info"); + showToast(t("sync.index_reloaded"), "info"); try { await Promise.all([loadVaults(), loadTags()]); } catch (err) { diff --git a/frontend/locales/en.json b/frontend/locales/en.json index e350b22..ee0fe03 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -984,5 +984,56 @@ "help.graph_nodes": "Nodes", "help.graph_info": "Info panel", "help.graph_export": "Export PNG", - "help.graph_context": "Context menu" -} \ No newline at end of file + "help.graph_context": "Context menu", + "ai.completion_added": "AI: completion added", + "ai.invalid_api_key": "AI: invalid API key. Check DEEPSEEK_API_KEY in .env", + "ai.processing": "⏳ AI: processing...", + "ai.quota_exceeded": "AI: quota exceeded or payment required", + "ai.select_text": "AI: select text to process", + "ai.text_processed": "AI: text processed ✓", + "ai.text_rewritten": "AI: text rewritten", + "auth.cannot_delete_self": "Cannot delete your own account", + "auth.user_deleted": "User deleted", + "backup.file_restored": "File restored successfully", + "backup.restore_failed": "Restore failed", + "backup.select_to_restore": "Select a backup to restore", + "backup.timestamp_not_found": "Cannot find backup timestamp", + "button.cancel": "Cancel", + "config.api_keys_saved": "API keys saved", + "config.link_copied": "Link copied!", + "config.link_created": "Link created!", + "config.no_keys": "No keys to save", + "config.reindex_error": "Reindex error", + "config.save_error": "Save error", + "config.settings_saved": "✓ Settings saved", + "config.share_revoked": "Share revoked", + "config.url_required": "URL required", + "dashboard.conflict_resolved": "Conflict resolved", + "dashboard.pin_error": "Pin error", + "dragdrop.cross_vault_unsupported": "Cross-vault moves are not supported", + "dragdrop.move_failed": "Move failed", + "error.initialization": "Initialization error", + "offline.all_synced": "All changes synced", + "offline.mode_offline": "Offline mode — changes will sync when back online", + "offline.reconnected": "Connection restored — synchronizing...", + "search.saved": "Search saved", + "sync.index_reloaded": "Full index reloaded", + "sync.install_success": "ObsiGate installed successfully!", + "toast.dir_renamed": "Directory renamed", + "toast.file_deleted": "File deleted", + "toast.file_renamed": "File renamed", + "ui.cannot_rename": "Cannot rename this item", + "ui.element_not_found": "Element not found in tree", + "viewer.copy_error": "Copy error", + "viewer.loaded_from_cache": "File loaded from offline cache", + "ai.not_configured": "⚠️ AI not configured — add DEEPSEEK_API_KEY, OPENROUTER_API_KEY or GEMINI_API_KEY in .env", + "ai.inline_completion_title": "AI Inline Completion (Ctrl+J) — Auto-complete text", + "ai.rewrite": "💬 Rewrite", + "ai.rewrite_title": "Rewrite text according to your instructions", + "auth.logout_title": "Sign out", + "config.reindexing": "Reindexing...", + "config.force_reindex": "Force reindex", + "search.previous": "← Previous", + "viewer.copied": "Copied!", + "viewer.shared_doc_title": "Shared document — click to manage" +} diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 85c32ab..e6dfe4d 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -984,5 +984,56 @@ "help.graph_nodes": "Nœuds", "help.graph_info": "Panneau d'info", "help.graph_export": "Export PNG", - "help.graph_context": "Menu contextuel" -} \ No newline at end of file + "help.graph_context": "Menu contextuel", + "ai.completion_added": "AI: complétion ajoutée", + "ai.invalid_api_key": "AI: clé API invalide. Vérifiez DEEPSEEK_API_KEY dans .env", + "ai.processing": "⏳ AI: traitement en cours...", + "ai.quota_exceeded": "AI: quota dépassé ou paiement requis", + "ai.select_text": "AI: sélectionnez du texte à traiter", + "ai.text_processed": "AI: texte traité ✓", + "ai.text_rewritten": "AI: texte réécrit", + "auth.cannot_delete_self": "Impossible de supprimer son propre compte", + "auth.user_deleted": "Utilisateur supprimé", + "backup.file_restored": "Fichier restauré avec succès", + "backup.restore_failed": "Échec de la restauration", + "backup.select_to_restore": "Sélectionnez un backup à restaurer", + "backup.timestamp_not_found": "Impossible de trouver le timestamp du backup", + "button.cancel": "Annuler", + "config.api_keys_saved": "Clés API sauvegardées", + "config.link_copied": "Lien copié !", + "config.link_created": "Lien créé !", + "config.no_keys": "Aucune clé à sauvegarder", + "config.reindex_error": "Erreur de réindexation", + "config.save_error": "Erreur de sauvegarde", + "config.settings_saved": "✓ Paramètres sauvegardés", + "config.share_revoked": "Partage révoqué", + "config.url_required": "URL requise", + "dashboard.conflict_resolved": "Conflit résolu", + "dashboard.pin_error": "Erreur lors de l'épinglage", + "dragdrop.cross_vault_unsupported": "Cross-vault moves are not supported", + "dragdrop.move_failed": "Move failed", + "error.initialization": "Erreur lors de l'initialisation", + "offline.all_synced": "Toutes les modifications ont été synchronisées", + "offline.mode_offline": "Mode hors-ligne — vos modifications seront synchronisées au retour", + "offline.reconnected": "Connexion rétablie — synchronisation en cours...", + "search.saved": "Recherche sauvegardée", + "sync.index_reloaded": "Index complet rechargé", + "sync.install_success": "ObsiGate installé avec succès !", + "toast.dir_renamed": "Dossier renommé", + "toast.file_deleted": "Fichier supprimé", + "toast.file_renamed": "Fichier renommé", + "ui.cannot_rename": "Impossible de renommer cet élément", + "ui.element_not_found": "Élément introuvable dans l'arborescence", + "viewer.copy_error": "Erreur lors de la copie", + "viewer.loaded_from_cache": "Fichier chargé depuis le cache hors-ligne", + "ai.not_configured": "⚠️ AI non configuré — ajouter DEEPSEEK_API_KEY, OPENROUTER_API_KEY ou GEMINI_API_KEY dans .env", + "ai.inline_completion_title": "AI Inline Completion (Ctrl+J) — Complète le texte automatiquement", + "ai.rewrite": "💬 Réécrire", + "ai.rewrite_title": "Réécrit le texte selon vos instructions", + "auth.logout_title": "Déconnexion", + "config.reindexing": "Réindexation...", + "config.force_reindex": "Forcer réindexation", + "search.previous": "← Précédent", + "viewer.copied": "Copié !", + "viewer.shared_doc_title": "Document partagé — cliquer pour gérer" +}