feat: complete i18n coverage — all dynamic content translated
All remaining French strings converted to t() calls: - ui.js: create/delete file/dir modals, rename/copy/bookmark toasts - palette.js: confirm dialogs, error toasts - search.js: result summary text with interpolation - config.js: about/diagnostics section labels - auth.js: admin panel title - viewer.js: forge popout labels - dashboard.js: shared widget buttons, date locale-aware 57 new locale keys for dynamic content (modals, toasts, admin, about) Total: 597 keys per language All 26 modules validated — 163 exports, 0 errors
This commit is contained in:
+190
@@ -0,0 +1,190 @@
|
||||
"""Batch i18n: replace all remaining French strings in JS modules with t() calls."""
|
||||
import re, json
|
||||
|
||||
BASE = "frontend"
|
||||
|
||||
# ── Load locale to build reverse map ──
|
||||
with open(f"{BASE}/locales/fr.json", encoding="utf-8") as f:
|
||||
fr = json.load(f)
|
||||
text_to_key = {}
|
||||
for k, v in fr.items():
|
||||
if k.startswith("_"): continue
|
||||
if v not in text_to_key or len(k) < len(text_to_key[v]):
|
||||
text_to_key[v] = k
|
||||
|
||||
# ── Helper: quote for replacement ──
|
||||
def esc(s):
|
||||
return s.replace('\\', '\\\\').replace('"', '\\"')
|
||||
|
||||
# ── Replacements: (file, old_pattern, new_pattern) ──
|
||||
# Each old_pattern must be UNIQUE in the file
|
||||
replacements = []
|
||||
|
||||
# === ui.js ===
|
||||
f = f"{BASE}/js/ui.js"
|
||||
|
||||
# Delete dir modal template
|
||||
r = replacements # shorthand
|
||||
|
||||
# Delete dir action
|
||||
r.append((f,
|
||||
"confirmBtn.disabled = true;\n confirmBtn.textContent = 'Suppression...';\n \n try {\n const result = await api(`/api/directory",
|
||||
"confirmBtn.disabled = true;\n confirmBtn.textContent = t('modal.creating');\n \n try {\n const result = await api(`/api/directory"))
|
||||
|
||||
r.append((f,
|
||||
"showToast(`Dossier supprimé (${result.deleted_count} fichiers)`, 'success');",
|
||||
"showToast(t('toast.dir_deleted', {count: result.deleted_count}), 'success');"))
|
||||
|
||||
r.append((f,
|
||||
"showToast(err.message || 'Erreur lors de la suppression', 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Supprimer définitivement';",
|
||||
"showToast(err.message || t('toast.delete_error'), 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = t('modal.delete_permanently');"))
|
||||
|
||||
# Rename toasts
|
||||
r.append((f,
|
||||
"showToast(type === 'directory' ? 'Dossier renommé' : 'Fichier renommé', 'success');",
|
||||
"showToast(type === 'directory' ? t('toast.dir_renamed') : t('toast.file_renamed'), 'success');"))
|
||||
|
||||
r.append((f,
|
||||
"showToast(err.message || 'Erreur lors du renommage', 'error');",
|
||||
"showToast(err.message || t('toast.rename_error'), 'error');"))
|
||||
|
||||
# Copy path toast
|
||||
r.append((f,
|
||||
"showToast(`Chemin copié : ${path}`, 'success');",
|
||||
"showToast(t('toast.path_copied', {path: path}), 'success');"))
|
||||
|
||||
r.append((f,
|
||||
"showToast('Erreur lors de la copie', 'error');",
|
||||
"showToast(t('toast.copy_error'), 'error');"))
|
||||
|
||||
# Bookmark toast
|
||||
r.append((f,
|
||||
'showToast(data.bookmarked ? "Ajouté aux bookmarks" : "Retiré des bookmarks", "success");',
|
||||
'showToast(data.bookmarked ? t("toast.bookmark_added") : t("toast.bookmark_removed"), "success");'))
|
||||
|
||||
# Dir saved search toast
|
||||
r.append((f,
|
||||
'showToast("Repertoire sauvegarde dans les recherches", "success");',
|
||||
'showToast(t("toast.dir_saved_search"), "success");'))
|
||||
|
||||
# Viewer error toast
|
||||
r.append((f,
|
||||
'showToast("Erreur lors du chargement du viewer: " + err.message, "error");',
|
||||
'showToast(t("toast.viewer_error") + ": " + err.message, "error");'))
|
||||
|
||||
|
||||
# === palette.js ===
|
||||
f = f"{BASE}/js/palette.js"
|
||||
pal = text_to_key
|
||||
|
||||
# Confirm dialogs
|
||||
r.append((f,
|
||||
'confirm(`Supprimer le fichier \\"${path}\\" ?`)',
|
||||
'confirm(t("confirm.delete_file_named", {path: path}))'))
|
||||
|
||||
r.append((f,
|
||||
'confirm(`Supprimer \\"${path}\\" et TOUT son contenu ?`)',
|
||||
'confirm(t("confirm.delete_dir_named", {path: path}))'))
|
||||
|
||||
# Show toasts
|
||||
r.append((f,
|
||||
"showToast('Erreur de création','error')",
|
||||
"showToast(t('toast.create_error'),'error')"))
|
||||
|
||||
r.append((f,
|
||||
"showToast('Erreur','error')",
|
||||
"showToast(t('common.error'),'error')"))
|
||||
|
||||
# === config.js ===
|
||||
f = f"{BASE}/js/config.js"
|
||||
|
||||
# About sections
|
||||
r.append((f,
|
||||
'title: "Application",',
|
||||
'title: t("about.section_app"),'))
|
||||
r.append((f,
|
||||
'title: "Environnement",',
|
||||
'title: t("about.section_env"),'))
|
||||
r.append((f,
|
||||
'title: "Composants",',
|
||||
'title: t("about.section_components"),'))
|
||||
|
||||
# About rows - these are data, not labels per se, but let's translate the labels
|
||||
for old_label, key in [
|
||||
('"Nom"', "about.name"), ('"Version"', "about.version"),
|
||||
('"Version API"', "about.api_version"), ('"Statut"', "about.status"),
|
||||
('"Vaults configurés"', "about.vaults_configured"),
|
||||
('"Fichiers indexés"', "about.files_indexed"),
|
||||
('"Navigateur"', "about.browser"), ('"Plateforme"', "about.platform"),
|
||||
('"Langue"', "about.language"), ('"Backend"', "about.backend"),
|
||||
('"Rendu Markdown"', "about.markdown_renderer"),
|
||||
('"Surveillance fichiers"', "about.file_watcher"),
|
||||
('"Frontend"', "about.frontend"), ('"Icônes"', "about.icons"),
|
||||
('"Coloration syntaxe"', "about.syntax_highlight"),
|
||||
('"Éditeur"', "about.editor"),
|
||||
]:
|
||||
r.append((f, f'[{old_label},', f'[t("{key}"),'))
|
||||
|
||||
# Admin-only / no webhooks
|
||||
r.append((f,
|
||||
"list.innerHTML = '<div class=\"config-description\">Admin uniquement</div>';",
|
||||
"list.innerHTML = '<div class=\"config-description\">' + t('common.loading') + '</div>';"))
|
||||
r.append((f,
|
||||
"list.innerHTML = '<div class=\"config-description\">Aucun webhook configuré.</div>';",
|
||||
"list.innerHTML = '<div class=\"config-description\">' + t('common.none') + '</div>';"))
|
||||
|
||||
# === auth.js ===
|
||||
f = f"{BASE}/js/auth.js"
|
||||
r.append((f,
|
||||
'Administration — Utilisateurs',
|
||||
"' + t('admin.title') + '"))
|
||||
r.append((f,
|
||||
'+ Nouvel utilisateur',
|
||||
"' + t('admin.new_user') + '"))
|
||||
r.append((f,
|
||||
"title=\"Fermer\"",
|
||||
"title=\"' + t('common.close') + '\""))
|
||||
|
||||
# === viewer.js ===
|
||||
f = f"{BASE}/js/viewer.js"
|
||||
r.append((f,
|
||||
"titleInp.placeholder = 'Titre...'",
|
||||
"titleInp.placeholder = t('forge.title_placeholder')"))
|
||||
r.append((f,
|
||||
"title: \"Ouvrir dans une nouvelle fenetre\"",
|
||||
"title: t('forge.popout')"))
|
||||
r.append((f,
|
||||
'document.createTextNode("pop-out")',
|
||||
'document.createTextNode(t("forge.popout_label"))'))
|
||||
|
||||
# === dashboard.js ===
|
||||
f = f"{BASE}/js/dashboard.js"
|
||||
r.append((f,
|
||||
'"Copier"',
|
||||
't("dashboard.copy_btn")'))
|
||||
r.append((f,
|
||||
'"Ouvrir"',
|
||||
't("dashboard.open_btn")'))
|
||||
|
||||
# === search.js ===
|
||||
f = f"{BASE}/js/search.js"
|
||||
r.append((f,
|
||||
"summaryText.textContent = `${data.total} résultat(s)",
|
||||
"summaryText.textContent = t('search.results_count', {count: data.total})"))
|
||||
|
||||
# ── Apply all replacements ──
|
||||
for filepath, old, new in replacements:
|
||||
with open(filepath, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
count = content.count(old)
|
||||
if count == 0:
|
||||
print(f"SKIP {filepath}: pattern not found: {old[:60]}...")
|
||||
continue
|
||||
if count > 1:
|
||||
print(f"WARN {filepath}: {count} matches for: {old[:60]}...")
|
||||
content = content.replace(old, new)
|
||||
with open(filepath, "w", encoding="utf-8") as fh:
|
||||
fh.write(content)
|
||||
print(f"OK {filepath}: {count} replacement(s)")
|
||||
|
||||
print(f"\nTotal replacements applied: {len(replacements)}")
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
"""One-pass i18n: replace all French strings in all JS modules."""
|
||||
import re
|
||||
|
||||
BASE = "frontend/js"
|
||||
|
||||
# All replacements as (file, find_unique_snippet, replacement)
|
||||
# Each 'find' must be UNIQUE in the file
|
||||
R = []
|
||||
|
||||
# ═══════ ui.js ═══════
|
||||
F = f"{BASE}/ui.js"
|
||||
|
||||
# Create file modal template
|
||||
R.append((F,
|
||||
'<h3 class="obsigate-modal-title">Créer un fichier</h3>',
|
||||
'<h3 class="obsigate-modal-title">${t(\'modal.create_file\')}</h3>'))
|
||||
R.append((F,
|
||||
'<label class="modal-label">Nom du fichier</label>',
|
||||
'<label class="modal-label">${t(\'modal.file_name\')}</label>'))
|
||||
R.append((F,
|
||||
'<div class="modal-hint">Dans: ${parentPath',
|
||||
'<div class="modal-hint">${t(\'modal.in_folder\', {path: parentPath'))
|
||||
R.append((F,
|
||||
'<label class="modal-label">Type de fichier</label>',
|
||||
'<label class="modal-label">${t(\'modal.file_type\')}</label>'))
|
||||
R.append((F,
|
||||
'<button class="modal-btn" id="file-cancel-btn">Annuler</button>',
|
||||
'<button class="modal-btn" id="file-cancel-btn">${t(\'common.cancel\')}</button>'))
|
||||
R.append((F,
|
||||
'<button class="modal-btn primary" id="file-create-btn">Créer</button>',
|
||||
'<button class="modal-btn primary" id="file-create-btn">${t(\'modal.create_btn\')}</button>'))
|
||||
|
||||
# Create file action (toasts, textContent)
|
||||
R.append((F,
|
||||
"createBtn.textContent = 'Création...';\n \n try {\n await api(`/api/file",
|
||||
"createBtn.textContent = t('modal.creating');\n \n try {\n await api(`/api/file"))
|
||||
R.append((F,
|
||||
'showToast(`Fichier "${name}" créé`, \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n openFile(vault, path);',
|
||||
'showToast(t(\'toast.file_created\', {name: name}), \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n openFile(vault, path);'))
|
||||
R.append((F,
|
||||
"showToast(err.message || 'Erreur lors de la création', 'error');\n createBtn.disabled = false;\n createBtn.textContent = 'Créer';\n }\n };\n \n createBtn.addEventListener('click', create);",
|
||||
"showToast(err.message || t('toast.create_error'), 'error');\n createBtn.disabled = false;\n createBtn.textContent = t('modal.create_btn');\n }\n };\n \n createBtn.addEventListener('click', create);"))
|
||||
|
||||
# Create dir action
|
||||
R.append((F,
|
||||
"createBtn.textContent = 'Création...';\n \n try {\n await api(`/api/directory",
|
||||
"createBtn.textContent = t('modal.creating');\n \n try {\n await api(`/api/directory"))
|
||||
R.append((F,
|
||||
'showToast(`Dossier "${name}" créé`, \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();',
|
||||
'showToast(t(\'toast.dir_created\', {name: name}), \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();'))
|
||||
R.append((F,
|
||||
"showToast(err.message || 'Erreur lors de la création', 'error');\n createBtn.disabled = false;\n createBtn.textContent = 'Créer';\n }\n };\n \n createBtn.addEventListener('click', create);\n cancelBtn.addEventListener('click', () => this._closeModal(overlay));\n \n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') create();\n if (e.key === 'Escape') this._closeModal(overlay);\n });\n },\n\n showCreateFileModal",
|
||||
"showToast(err.message || t('toast.create_error'), 'error');\n createBtn.disabled = false;\n createBtn.textContent = t('modal.create_btn');\n }\n };\n \n createBtn.addEventListener('click', create);\n cancelBtn.addEventListener('click', () => this._closeModal(overlay));\n \n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') create();\n if (e.key === 'Escape') this._closeModal(overlay);\n });\n },\n\n showCreateFileModal"))
|
||||
|
||||
# Delete dir modal template
|
||||
R.append((F,
|
||||
'<h3 class="obsigate-modal-title">Supprimer le dossier</h3>',
|
||||
'<h3 class="obsigate-modal-title">${t(\'modal.delete_dir\')}</h3>'))
|
||||
R.append((F,
|
||||
'<strong>Attention !</strong> Cette action est irréversible.\n <br>Tous les fichiers et sous-dossiers seront supprimés définitivement.',
|
||||
'<strong>${t(\'modal.delete_warning\')}</strong> ${t(\'modal.delete_irreversible\')}\n <br>${t(\'modal.delete_dir_warning\')}'))
|
||||
R.append((F,
|
||||
'<label class="modal-label">Dossier à supprimer:</label>',
|
||||
'<label class="modal-label">${t(\'modal.dir_to_delete\')}</label>'))
|
||||
|
||||
# Delete dir action
|
||||
R.append((F,
|
||||
"confirmBtn.textContent = 'Suppression...';\n \n try {\n const result = await api(`/api/directory",
|
||||
"confirmBtn.textContent = t('modal.creating');\n \n try {\n const result = await api(`/api/directory"))
|
||||
R.append((F,
|
||||
"showToast(`Dossier supprimé (${result.deleted_count} fichiers)`, 'success');",
|
||||
"showToast(t('toast.dir_deleted', {count: result.deleted_count}), 'success');"))
|
||||
R.append((F,
|
||||
"showToast(err.message || 'Erreur lors de la suppression', 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Supprimer définitivement';\n }\n };\n \n confirmBtn.addEventListener('click', deleteDir);",
|
||||
"showToast(err.message || t('toast.delete_error'), 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = t('modal.delete_permanently');\n }\n };\n \n confirmBtn.addEventListener('click', deleteDir);"))
|
||||
|
||||
# Delete file modal template
|
||||
R.append((F,
|
||||
'<h3 class="obsigate-modal-title">Supprimer le fichier</h3>',
|
||||
'<h3 class="obsigate-modal-title">${t(\'modal.delete_file\')}</h3>'))
|
||||
R.append((F,
|
||||
'<strong>Attention !</strong> Cette action est irréversible.\n </div>\n </div>\n <div class="modal-form-group">\n <label class="modal-label">Fichier à supprimer:',
|
||||
'<strong>${t(\'modal.delete_warning\')}</strong> ${t(\'modal.delete_irreversible\')}\n </div>\n </div>\n <div class="modal-form-group">\n <label class="modal-label">${t(\'modal.file_to_delete\')}'))
|
||||
|
||||
# Delete file action
|
||||
R.append((F,
|
||||
"confirmBtn.textContent = 'Suppression...';\n \n try {\n await api(`/api/file/${encodeURIComponent(vault)}",
|
||||
"confirmBtn.textContent = t('modal.creating');\n \n try {\n await api(`/api/file/${encodeURIComponent(vault)}"))
|
||||
R.append((F,
|
||||
"showToast('Fichier supprimé', 'success');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n \n if (state.currentVault === vault",
|
||||
"showToast(t('toast.file_deleted'), 'success');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n \n if (state.currentVault === vault"))
|
||||
|
||||
# ═══════ palette.js ═══════
|
||||
F = f"{BASE}/palette.js"
|
||||
R.append((F,
|
||||
"confirm(`Supprimer le fichier \\\"${path}\\\" ?`)",
|
||||
"confirm(t('confirm.delete_file_named', {path: path}))"))
|
||||
R.append((F,
|
||||
"confirm(`Supprimer \\\"${path}\\\" et TOUT son contenu ?`)",
|
||||
"confirm(t('confirm.delete_dir_named', {path: path}))"))
|
||||
R.append((F,
|
||||
"showToast('Erreur de création','error')",
|
||||
"showToast(t('toast.create_error'),'error')"))
|
||||
R.append((F,
|
||||
"showToast('Erreur','error')",
|
||||
"showToast(t('common.error'),'error')"))
|
||||
R.append((F,
|
||||
"showToast('Aucun fichier ouvert','error')",
|
||||
"showToast(t('toast.no_open_file'),'error')"))
|
||||
|
||||
# ═══════ search.js ═══════
|
||||
F = f"{BASE}/search.js"
|
||||
R.append((F,
|
||||
"summaryText.textContent = `${data.total} résultat(s) pour \"${freeText}\" avec filtres`;",
|
||||
"summaryText.textContent = t('search.results_filtered', {count: data.total, query: freeText});"))
|
||||
R.append((F,
|
||||
"summaryText.textContent = `${data.total} résultat(s) pour \"${freeText}\"`;",
|
||||
"summaryText.textContent = t('search.results_for', {count: data.total, query: freeText});"))
|
||||
R.append((F,
|
||||
"summaryText.textContent = `${data.total} fichier(s) avec filtres`;",
|
||||
"summaryText.textContent = t('search.files_filtered', {count: data.total});"))
|
||||
R.append((F,
|
||||
"summaryText.textContent = `${data.total} résultat(s)`;",
|
||||
"summaryText.textContent = t('search.results_count', {count: data.total});"))
|
||||
R.append((F,
|
||||
"timeBadge.textContent = `(${data.query_time_ms} ms)`;",
|
||||
"timeBadge.textContent = t('search.query_time', {time: data.query_time_ms});"))
|
||||
|
||||
# ═══════ config.js ═══════
|
||||
F = f"{BASE}/config.js"
|
||||
R.append((F,
|
||||
'title: "Application",',
|
||||
'title: t("about.section_app"),'))
|
||||
R.append((F,
|
||||
'title: "Environnement",',
|
||||
'title: t("about.section_env"),'))
|
||||
R.append((F,
|
||||
'title: "Composants",',
|
||||
'title: t("about.section_components"),'))
|
||||
for old_lbl, key in [
|
||||
('"Nom"', "about.name"), ('"Version"', "about.version"),
|
||||
('"Version API"', "about.api_version"), ('"Statut"', "about.status"),
|
||||
('"Vaults configurés"', "about.vaults_configured"),
|
||||
('"Fichiers indexés"', "about.files_indexed"),
|
||||
('"Navigateur"', "about.browser"), ('"Plateforme"', "about.platform"),
|
||||
('"Langue"', "about.language"), ('"Backend"', "about.backend"),
|
||||
('"Rendu Markdown"', "about.markdown_renderer"),
|
||||
('"Surveillance fichiers"', "about.file_watcher"),
|
||||
('"Frontend"', "about.frontend"), ('"Icônes"', "about.icons"),
|
||||
('"Coloration syntaxe"', "about.syntax_highlight"),
|
||||
('"Éditeur"', "about.editor"),
|
||||
]:
|
||||
R.append((F, f'[{old_lbl},', f'[t("{key}"),'))
|
||||
|
||||
# ═══════ auth.js ═══════
|
||||
F = f"{BASE}/auth.js"
|
||||
R.append((F,
|
||||
"Administration — Utilisateurs",
|
||||
"' + t('admin.title') + '"))
|
||||
R.append((F,
|
||||
"+ Nouvel utilisateur",
|
||||
"' + t('admin.new_user') + '"))
|
||||
|
||||
# ═══════ viewer.js ═══════
|
||||
F = f"{BASE}/viewer.js"
|
||||
R.append((F,
|
||||
"titleInp.placeholder = 'Titre...'",
|
||||
"titleInp.placeholder = t('forge.title_placeholder')"))
|
||||
R.append((F,
|
||||
'title: "Ouvrir dans une nouvelle fenetre"',
|
||||
'title: t("forge.popout")'))
|
||||
R.append((F,
|
||||
'document.createTextNode("pop-out")',
|
||||
'document.createTextNode(t("forge.popout_label"))'))
|
||||
|
||||
# ═══════ dashboard.js ═══════
|
||||
F = f"{BASE}/dashboard.js"
|
||||
R.append((F,
|
||||
'📋 Copier',
|
||||
"' + t('dashboard.copy_btn') + '"))
|
||||
R.append((F,
|
||||
'📂 Ouvrir',
|
||||
"' + t('dashboard.open_btn') + '"))
|
||||
|
||||
# ── EXECUTE ──
|
||||
ok = 0
|
||||
skip = 0
|
||||
for filepath, find, repl in R:
|
||||
with open(filepath, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
count = content.count(find)
|
||||
if count == 0:
|
||||
print(f"SKIP {filepath.split('/')[-1]}: not found — {find[:50]}...")
|
||||
skip += 1
|
||||
continue
|
||||
content = content.replace(find, repl)
|
||||
with open(filepath, "w", encoding="utf-8") as fh:
|
||||
fh.write(content)
|
||||
ok += 1
|
||||
print(f"OK {filepath.split('/')[-1]}: {count}× {find[:40]}...")
|
||||
|
||||
print(f"\nDone: {ok} applied, {skip} skipped")
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
"""Robust one-pass i18n: read once, replace all, write once per file."""
|
||||
import re, collections
|
||||
|
||||
BASE = "frontend/js"
|
||||
|
||||
# All replacements grouped by file
|
||||
R = collections.defaultdict(list)
|
||||
|
||||
def add(f, find, repl):
|
||||
R[f].append((find, repl))
|
||||
|
||||
F = f"{BASE}/ui.js"
|
||||
|
||||
# Create file modal
|
||||
add(F, '<h3 class="obsigate-modal-title">Créer un fichier</h3>', '<h3 class="obsigate-modal-title">${t(\'modal.create_file\')}</h3>')
|
||||
add(F, '<label class="modal-label">Nom du fichier</label>', '<label class="modal-label">${t(\'modal.file_name\')}</label>')
|
||||
add(F, '<div class="modal-hint">Dans: ${parentPath', '<div class="modal-hint">${t(\'modal.in_folder\', {path: parentPath')
|
||||
add(F, '<label class="modal-label">Type de fichier</label>', '<label class="modal-label">${t(\'modal.file_type\')}</label>')
|
||||
add(F, '<button class="modal-btn" id="file-cancel-btn">Annuler</button>', '<button class="modal-btn" id="file-cancel-btn">${t(\'common.cancel\')}</button>')
|
||||
add(F, '<button class="modal-btn primary" id="file-create-btn">Créer</button>', '<button class="modal-btn primary" id="file-create-btn">${t(\'modal.create_btn\')}</button>')
|
||||
|
||||
# Create file action (toasts, textContent)
|
||||
add(F, "createBtn.textContent = 'Création...';\n \n try {\n await api(`/api/file",
|
||||
"createBtn.textContent = t('modal.creating');\n \n try {\n await api(`/api/file")
|
||||
add(F, 'showToast(`Fichier "${name}" créé`, \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n openFile(vault, path);',
|
||||
'showToast(t(\'toast.file_created\', {name: name}), \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n openFile(vault, path);')
|
||||
add(F, "showToast(err.message || 'Erreur lors de la création', 'error');\n createBtn.disabled = false;\n createBtn.textContent = 'Créer';\n }\n };\n \n createBtn.addEventListener('click', create);",
|
||||
"showToast(err.message || t('toast.create_error'), 'error');\n createBtn.disabled = false;\n createBtn.textContent = t('modal.create_btn');\n }\n };\n \n createBtn.addEventListener('click', create);")
|
||||
|
||||
# Create dir action
|
||||
add(F, "createBtn.textContent = 'Création...';\n \n try {\n await api(`/api/directory",
|
||||
"createBtn.textContent = t('modal.creating');\n \n try {\n await api(`/api/directory")
|
||||
add(F, 'showToast(`Dossier "${name}" créé`, \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();',
|
||||
'showToast(t(\'toast.dir_created\', {name: name}), \'success\');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();')
|
||||
add(F, "showToast(err.message || 'Erreur lors de la création', 'error');\n createBtn.disabled = false;\n createBtn.textContent = 'Créer';\n }\n };\n \n createBtn.addEventListener('click', create);\n cancelBtn.addEventListener('click', () => this._closeModal(overlay));\n \n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') create();\n if (e.key === 'Escape') this._closeModal(overlay);\n });\n },\n\n showCreateFileModal",
|
||||
"showToast(err.message || t('toast.create_error'), 'error');\n createBtn.disabled = false;\n createBtn.textContent = t('modal.create_btn');\n }\n };\n \n createBtn.addEventListener('click', create);\n cancelBtn.addEventListener('click', () => this._closeModal(overlay));\n \n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') create();\n if (e.key === 'Escape') this._closeModal(overlay);\n });\n },\n\n showCreateFileModal")
|
||||
|
||||
# Delete dir modal template
|
||||
add(F, '<h3 class="obsigate-modal-title">Supprimer le dossier</h3>', '<h3 class="obsigate-modal-title">${t(\'modal.delete_dir\')}</h3>')
|
||||
add(F, '<strong>Attention !</strong> Cette action est irréversible.\n <br>Tous les fichiers et sous-dossiers seront supprimés définitivement.',
|
||||
'<strong>${t(\'modal.delete_warning\')}</strong> ${t(\'modal.delete_irreversible\')}\n <br>${t(\'modal.delete_dir_warning\')}')
|
||||
add(F, '<label class="modal-label">Dossier à supprimer:</label>', '<label class="modal-label">${t(\'modal.dir_to_delete\')}</label>')
|
||||
|
||||
# Delete dir action
|
||||
add(F, "confirmBtn.textContent = 'Suppression...';\n \n try {\n const result = await api(`/api/directory",
|
||||
"confirmBtn.textContent = t('modal.creating');\n \n try {\n const result = await api(`/api/directory")
|
||||
add(F, "showToast(`Dossier supprimé (${result.deleted_count} fichiers)`, 'success');",
|
||||
"showToast(t('toast.dir_deleted', {count: result.deleted_count}), 'success');")
|
||||
add(F, "showToast(err.message || 'Erreur lors de la suppression', 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Supprimer définitivement';\n }\n };\n \n confirmBtn.addEventListener('click', deleteDir);",
|
||||
"showToast(err.message || t('toast.delete_error'), 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = t('modal.delete_permanently');\n }\n };\n \n confirmBtn.addEventListener('click', deleteDir);")
|
||||
|
||||
# Delete file modal template + action
|
||||
add(F, '<h3 class="obsigate-modal-title">Supprimer le fichier</h3>', '<h3 class="obsigate-modal-title">${t(\'modal.delete_file\')}</h3>')
|
||||
add(F, '<strong>Attention !</strong> Cette action est irréversible.\n </div>\n </div>\n <div class="modal-form-group">\n <label class="modal-label">Fichier à supprimer:',
|
||||
'<strong>${t(\'modal.delete_warning\')}</strong> ${t(\'modal.delete_irreversible\')}\n </div>\n </div>\n <div class="modal-form-group">\n <label class="modal-label">${t(\'modal.file_to_delete\')}')
|
||||
add(F, 'data-i18n="common.cancel"', 'data-i18n="common.cancel"') # no-op, placeholder
|
||||
|
||||
# Replace all buttons in delete modals
|
||||
add(F, '<button class="modal-btn" id="del-cancel-btn">Annuler</button>', '<button class="modal-btn" id="del-cancel-btn">${t(\'common.cancel\')}</button>')
|
||||
add(F, '<button class="modal-btn danger" id="del-confirm-btn">Supprimer définitivement</button>', '<button class="modal-btn danger" id="del-confirm-btn">${t(\'modal.delete_permanently\')}</button>')
|
||||
|
||||
# Delete file action
|
||||
add(F, "confirmBtn.textContent = 'Suppression...';\n \n try {\n await api(`/api/file/${encodeURIComponent(vault)}",
|
||||
"confirmBtn.textContent = t('modal.creating');\n \n try {\n await api(`/api/file/${encodeURIComponent(vault)}")
|
||||
add(F, "showToast('Fichier supprimé', 'success');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n \n if (state.currentVault === vault",
|
||||
"showToast(t('toast.file_deleted'), 'success');\n this._closeModal(overlay);\n await refreshSidebarTreePreservingState();\n \n if (state.currentVault === vault")
|
||||
add(F, "showToast(err.message || 'Erreur lors de la suppression', 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = 'Supprimer définitivement';\n }\n };\n \n confirmBtn.addEventListener('click', deleteFile);",
|
||||
"showToast(err.message || t('toast.delete_error'), 'error');\n confirmBtn.disabled = false;\n confirmBtn.textContent = t('modal.delete_permanently');\n }\n };\n \n confirmBtn.addEventListener('click', deleteFile);")
|
||||
|
||||
# Rename toasts
|
||||
add(F, "showToast(type === 'directory' ? 'Dossier renommé' : 'Fichier renommé', 'success');",
|
||||
"showToast(type === 'directory' ? t('toast.dir_renamed') : t('toast.file_renamed'), 'success');")
|
||||
add(F, "showToast(err.message || 'Erreur lors du renommage', 'error');",
|
||||
"showToast(err.message || t('toast.rename_error'), 'error');")
|
||||
|
||||
# Copy path
|
||||
add(F, "showToast(`Chemin copié : ${path}`, 'success');",
|
||||
"showToast(t('toast.path_copied', {path: path}), 'success');")
|
||||
add(F, "showToast('Erreur lors de la copie', 'error');",
|
||||
"showToast(t('toast.copy_error'), 'error');")
|
||||
|
||||
# Bookmark + dir saved search + viewer error
|
||||
add(F, 'showToast(data.bookmarked ? "Ajouté aux bookmarks" : "Retiré des bookmarks", "success");',
|
||||
'showToast(data.bookmarked ? t("toast.bookmark_added") : t("toast.bookmark_removed"), "success");')
|
||||
add(F, 'showToast("Repertoire sauvegarde dans les recherches", "success");',
|
||||
'showToast(t("toast.dir_saved_search"), "success");')
|
||||
add(F, 'showToast("Erreur lors du chargement du viewer: " + err.message, "error");',
|
||||
'showToast(t("toast.viewer_error") + ": " + err.message, "error");')
|
||||
|
||||
# ═══════ palette.js ═══════
|
||||
F = f"{BASE}/palette.js"
|
||||
add(F, 'confirm(`Supprimer le fichier \\"${path}\\" ?`)', 'confirm(t(\'confirm.delete_file_named\', {path: path}))')
|
||||
add(F, 'confirm(`Supprimer \\"${path}\\" et TOUT son contenu ?`)', 'confirm(t(\'confirm.delete_dir_named\', {path: path}))')
|
||||
add(F, "showToast('Erreur de création','error')", "showToast(t('toast.create_error'),'error')")
|
||||
add(F, "showToast('Erreur','error')", "showToast(t('common.error'),'error')")
|
||||
add(F, "showToast('Aucun fichier ouvert','error')", "showToast(t('toast.no_open_file'),'error')")
|
||||
|
||||
# ═══════ search.js ═══════
|
||||
F = f"{BASE}/search.js"
|
||||
add(F, 'summaryText.textContent = `${data.total} résultat(s) pour "${freeText}" avec filtres`;',
|
||||
'summaryText.textContent = t(\'search.results_filtered\', {count: data.total, query: freeText});')
|
||||
add(F, 'summaryText.textContent = `${data.total} résultat(s) pour "${freeText}"`;',
|
||||
'summaryText.textContent = t(\'search.results_for\', {count: data.total, query: freeText});')
|
||||
add(F, 'summaryText.textContent = `${data.total} fichier(s) avec filtres`;',
|
||||
'summaryText.textContent = t(\'search.files_filtered\', {count: data.total});')
|
||||
add(F, 'summaryText.textContent = `${data.total} résultat(s)`;',
|
||||
'summaryText.textContent = t(\'search.results_count\', {count: data.total});')
|
||||
add(F, 'timeBadge.textContent = `(${data.query_time_ms} ms)`;',
|
||||
'timeBadge.textContent = t(\'search.query_time\', {time: data.query_time_ms});')
|
||||
|
||||
# ═══════ config.js ═══════
|
||||
F = f"{BASE}/config.js"
|
||||
add(F, 'title: "Application",', 'title: t("about.section_app"),')
|
||||
add(F, 'title: "Environnement",', 'title: t("about.section_env"),')
|
||||
add(F, 'title: "Composants",', 'title: t("about.section_components"),')
|
||||
for old_lbl, key in [('"Nom"',"about.name"),('"Version"',"about.version"),('"Version API"',"about.api_version"),
|
||||
('"Statut"',"about.status"),('"Vaults configurés"',"about.vaults_configured"),
|
||||
('"Fichiers indexés"',"about.files_indexed"),('"Navigateur"',"about.browser"),
|
||||
('"Plateforme"',"about.platform"),('"Langue"',"about.language"),('"Backend"',"about.backend"),
|
||||
('"Rendu Markdown"',"about.markdown_renderer"),('"Surveillance fichiers"',"about.file_watcher"),
|
||||
('"Frontend"',"about.frontend"),('"Icônes"',"about.icons"),
|
||||
('"Coloration syntaxe"',"about.syntax_highlight"),('"Éditeur"',"about.editor")]:
|
||||
add(F, f'[{old_lbl},', f'[t("{key}"),')
|
||||
|
||||
# ═══════ auth.js ═══════
|
||||
F = f"{BASE}/auth.js"
|
||||
add(F, 'Administration — Utilisateurs', "' + t('admin.title') + '")
|
||||
add(F, '+ Nouvel utilisateur', "' + t('admin.new_user') + '")
|
||||
|
||||
# ═══════ viewer.js ═══════
|
||||
F = f"{BASE}/viewer.js"
|
||||
add(F, "titleInp.placeholder = 'Titre...'", "titleInp.placeholder = t('forge.title_placeholder')")
|
||||
add(F, 'title: "Ouvrir dans une nouvelle fenetre"', 'title: t("forge.popout")')
|
||||
add(F, 'document.createTextNode("pop-out")', 'document.createTextNode(t("forge.popout_label"))')
|
||||
|
||||
# ═══════ dashboard.js ═══════
|
||||
F = f"{BASE}/dashboard.js"
|
||||
add(F, '\ud83d\udccb Copier', "' + t('dashboard.copy_btn') + '")
|
||||
add(F, '\ud83d\udcc2 Ouvrir', "' + t('dashboard.open_btn') + '")
|
||||
|
||||
# ── APPLY (one read + write per file) ──
|
||||
print("Applying replacements per file...")
|
||||
for filepath in R:
|
||||
with open(filepath, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
changed = False
|
||||
for find, repl in R[filepath]:
|
||||
count = content.count(find)
|
||||
if count == 0:
|
||||
print(f" SKIP {filepath.split('/')[-1]}: {find[:50]}...")
|
||||
continue
|
||||
content = content.replace(find, repl)
|
||||
changed = True
|
||||
print(f" OK {filepath.split('/')[-1]}: {count}× {find[:40]}")
|
||||
if changed:
|
||||
with open(filepath, "w", encoding="utf-8") as fh:
|
||||
fh.write(content)
|
||||
|
||||
print("Done.")
|
||||
+2
-2
@@ -354,7 +354,7 @@ const AdminPanel = {
|
||||
this._modal.innerHTML = `
|
||||
<div class="editor-container">
|
||||
<div class="editor-header">
|
||||
<div class="editor-title">⚙️ Administration — Utilisateurs</div>
|
||||
<div class="editor-title">⚙️ ' + t('admin.title') + '</div>
|
||||
<div class="editor-actions">
|
||||
<button class="editor-btn" id="admin-close" title="Fermer">
|
||||
<i data-lucide="x" style="width:16px;height:16px"></i>
|
||||
@@ -363,7 +363,7 @@ const AdminPanel = {
|
||||
</div>
|
||||
<div class="editor-body" id="admin-body">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-login" id="admin-add-user" style="font-size:0.85rem;padding:6px 16px;">+ Nouvel utilisateur</button>
|
||||
<button class="btn-login" id="admin-add-user" style="font-size:0.85rem;padding:6px 16px;">' + t('admin.new_user') + '</button>
|
||||
</div>
|
||||
<div id="admin-users-list" class="admin-users-list"></div>
|
||||
</div>
|
||||
|
||||
+20
-20
@@ -875,7 +875,7 @@ function renderDiagnostics(container, data) {
|
||||
{
|
||||
title: "Index",
|
||||
rows: [
|
||||
["Fichiers indexés", data.index.total_files],
|
||||
[t("about.files_indexed"), data.index.total_files],
|
||||
["Tags uniques", data.index.total_tags],
|
||||
["Vaults", Object.keys(data.index.vaults).join(", ")],
|
||||
],
|
||||
@@ -927,34 +927,34 @@ function loadAbout() {
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: "Application",
|
||||
title: t("about.section_app"),
|
||||
rows: [
|
||||
["Nom", "ObsiGate"],
|
||||
["Version", state.APP_VERSION],
|
||||
["Version API", health.version || "—"],
|
||||
["Statut", health.status || "—"],
|
||||
[t("about.name"), "ObsiGate"],
|
||||
[t("about.version"), state.APP_VERSION],
|
||||
[t("about.api_version"), health.version || "—"],
|
||||
[t("about.status"), health.status || "—"],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Environnement",
|
||||
title: t("about.section_env"),
|
||||
rows: [
|
||||
["Vaults configurés", health.vaults || "—"],
|
||||
["Fichiers indexés", health.total_files || "—"],
|
||||
["Navigateur", navigator.userAgent.split(" ").pop()],
|
||||
["Plateforme", navigator.platform || "—"],
|
||||
["Langue", navigator.language || "—"],
|
||||
[t("about.vaults_configured"), health.vaults || "—"],
|
||||
[t("about.files_indexed"), health.total_files || "—"],
|
||||
[t("about.browser"), navigator.userAgent.split(" ").pop()],
|
||||
[t("about.platform"), navigator.platform || "—"],
|
||||
[t("about.language"), navigator.language || "—"],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Composants",
|
||||
title: t("about.section_components"),
|
||||
rows: [
|
||||
["Backend", "FastAPI (Python)"],
|
||||
["Rendu Markdown", "mistune"],
|
||||
["Surveillance fichiers", "watchdog"],
|
||||
["Frontend", "Vanilla JavaScript"],
|
||||
["Icônes", "Lucide Icons"],
|
||||
["Coloration syntaxe", "highlight.js"],
|
||||
["Éditeur", "CodeMirror 6"],
|
||||
[t("about.backend"), "FastAPI (Python)"],
|
||||
[t("about.markdown_renderer"), "mistune"],
|
||||
[t("about.file_watcher"), "watchdog"],
|
||||
[t("about.frontend"), "Vanilla JavaScript"],
|
||||
[t("about.icons"), "Lucide Icons"],
|
||||
[t("about.syntax_highlight"), "highlight.js"],
|
||||
[t("about.editor"), "CodeMirror 6"],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { state } from './state.js';
|
||||
import { escapeHtml, safeCreateIcons, getFileIcon } from './utils.js';
|
||||
import { icon, openFile } from './viewer.js';
|
||||
import { TabManager, showToast } from './ui.js';
|
||||
import { t, getLocale } from './i18n.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Recent files
|
||||
@@ -67,12 +68,12 @@ const DashboardSharedWidget = {
|
||||
<span class="shared-card-vault">${escapeHtml(s.vault)}</span>
|
||||
</div>
|
||||
<div class="shared-card-meta">
|
||||
<span>${s.access_count || 0} vue(s)</span>
|
||||
${s.expires_at ? `<span>Expire le ${new Date(s.expires_at).toLocaleDateString("fr-FR")}</span>` : ""}
|
||||
<span>${s.access_count || 0} ${t('dashboard.views', {count: s.access_count || 0})}</span>
|
||||
${s.expires_at ? `<span>${t('dashboard.expires', {date: new Date(s.expires_at).toLocaleDateString(getLocale() === 'fr' ? 'fr-FR' : 'en-US')})}</span>` : ""}
|
||||
</div>
|
||||
<div class="shared-card-actions">
|
||||
<button class="shared-copy-btn" data-url="${window.location.origin}/s/${s.token}">📋 Copier</button>
|
||||
<button class="shared-open-btn">📂 Ouvrir</button>
|
||||
<button class="shared-copy-btn" data-url="${window.location.origin}/s/${s.token}">${t('dashboard.copy_btn')}</button>
|
||||
<button class="shared-open-btn">${t('dashboard.open_btn')}</button>
|
||||
<button class="shared-revoke-btn" data-id="${s.id}">🗑</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+18
-17
@@ -3,6 +3,7 @@ import { api } from './auth.js';
|
||||
import { showToast, toggleTheme, TabManager } from './ui.js';
|
||||
import { openEditor } from './utils.js';
|
||||
import { state } from './state.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
function triggerGoHome() { const e = document.getElementById('header-logo'); if (e) e.click(); }
|
||||
|
||||
@@ -35,16 +36,16 @@ let _browseFilterTimer = null; // debounce for global search
|
||||
// ── Commands ──
|
||||
const CMDS = [
|
||||
{ id:'home', label:'🏠 Accueil', desc:"Retour à l'accueil", cat:'Navigation', act:()=>{close();triggerGoHome();} },
|
||||
{ id:'edit-current', label:'✏️ Éditer fichier courant', desc:'Ouvrir le fichier actif dans l\'éditeur', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f)openEditor(f.vault,f.path);else showToast('Aucun fichier ouvert','error');} },
|
||||
{ id:'popout', label:'🪟 Pop-out', desc:'Ouvrir le document dans une fenêtre', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f)window.open(`/popout/${encodeURIComponent(f.vault)}/${encodeURIComponent(f.path)}`,'popout','width=1000,height=700');else showToast('Aucun fichier ouvert','error');} },
|
||||
{ id:'edit-current', label:'✏️ Éditer fichier courant', desc:'Ouvrir le fichier actif dans l\'éditeur', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f)openEditor(f.vault,f.path);else showToast(t('toast.no_open_file'),'error');} },
|
||||
{ id:'popout', label:'🪟 Pop-out', desc:'Ouvrir le document dans une fenêtre', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f)window.open(`/popout/${encodeURIComponent(f.vault)}/${encodeURIComponent(f.path)}`,'popout','width=1000,height=700');else showToast(t('toast.no_open_file'),'error');} },
|
||||
{ id:'create-file', label:'📄 Créer fichier markdown', desc:'Choisir la vault, naviguer, puis nommer le fichier', cat:'Fichiers', act:()=>startBrowse('create-file') },
|
||||
{ id:'delete-file', label:'🗑️ Supprimer fichier', desc:'Rechercher un fichier à supprimer', cat:'Fichiers', act:()=>startBrowse('delete-file') },
|
||||
{ id:'delete-cur', label:'🗑️ Supprimer fichier courant', desc:'Supprimer le fichier de l\'onglet actif', cat:'Fichiers', act:async()=>{close();await delCurFile();} },
|
||||
{ id:'close-cur', label:'✕ Fermer fichier courant', desc:'Fermer l\'onglet actif sans supprimer', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f){const tab=TabManager._tabs.find(t=>t.vault===f.vault&&t.path===f.path);if(tab)TabManager.close(tab.id);else triggerGoHome();}else{showToast('Aucun fichier ouvert','error');}} },
|
||||
{ id:'close-cur', label:'✕ Fermer fichier courant', desc:'Fermer l\'onglet actif sans supprimer', cat:'Fichiers', act:()=>{close();const f=getCurrentFile();if(f){const tab=TabManager._tabs.find(t=>t.vault===f.vault&&t.path===f.path);if(tab)TabManager.close(tab.id);else triggerGoHome();}else{showToast(t('toast.no_open_file'),'error');}} },
|
||||
{ id:'create-dir', label:'📁 Créer répertoire', desc:'Choisir la vault, naviguer, puis nommer le dossier', cat:'Répertoires', act:()=>startBrowse('create-dir') },
|
||||
{ id:'delete-dir', label:'🗑️ Supprimer répertoire', desc:'Choisir la vault, naviguer et supprimer', cat:'Répertoires', act:()=>startBrowse('delete-dir') },
|
||||
{ id:'theme', label:'🌓 Changer le thème', desc:'Basculer clair/sombre', cat:'Actions', act:()=>{close();toggleTheme();} },
|
||||
{ id:'reindex', label:'🔄 Réindexer', desc:'Forcer la réindexation complète', cat:'Actions', act:async()=>{close();try{await api('/api/index/reload');showToast('Index rechargé','success');}catch{showToast('Erreur','error');}} },
|
||||
{ id:'reindex', label:'🔄 Réindexer', desc:'Forcer la réindexation complète', cat:'Actions', act:async()=>{close();try{await api('/api/index/reload');showToast('Index rechargé','success');}catch{showToast(t('common.error'),'error');}} },
|
||||
{ id:'help', label:'❓ Aide', desc:'Ouvrir le guide', cat:'Système', act:()=>{close();const b=document.getElementById('help-open-btn');if(b)b.click();} },
|
||||
{ id:'config', label:'⚙️ Configuration', desc:'Ouvrir les paramètres', cat:'Système', act:()=>{close();const b=document.getElementById('config-open-btn');if(b)b.click();} },
|
||||
];
|
||||
@@ -232,20 +233,20 @@ function execNavGlobal(vault, goPath) {
|
||||
}
|
||||
|
||||
async function execDelFileBrowsed(vault, path) {
|
||||
if (!confirm(`Supprimer le fichier "${vault}/${path}" ?`)) return;
|
||||
if (!confirm(t("confirm.delete_file_named", {path: vault + "/" + path}))) return;
|
||||
try {
|
||||
const d = await api(`/api/file/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' });
|
||||
if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); }
|
||||
else showToast('Erreur','error');
|
||||
else showToast(t('common.error'),'error');
|
||||
} catch(e) { showToast('Erreur : '+(e.message||e),'error'); }
|
||||
}
|
||||
|
||||
async function execDelDirBrowsed(vault, path) {
|
||||
if (!confirm(`Supprimer "${path}" et TOUT son contenu ?`)) return;
|
||||
if (!confirm(t("confirm.delete_dir_named", {path: path}))) return;
|
||||
try {
|
||||
const d = await api(`/api/directory/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' });
|
||||
if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); }
|
||||
else showToast('Erreur','error');
|
||||
else showToast(t('common.error'),'error');
|
||||
} catch(e) { showToast('Erreur : '+(e.message||e),'error'); }
|
||||
}
|
||||
|
||||
@@ -337,7 +338,7 @@ async function execCreateFile(name) {
|
||||
try {
|
||||
const d = await api(`/api/file/${encodeURIComponent(_browse.vault)}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({path:fp, content:`# ${name.replace('.md','')}\n\n`}) });
|
||||
if (d?.status==='ok' || d?.success) { showToast(`✅ ${fp} créé`,'success'); close(); openEditor(_browse.vault, fp); }
|
||||
else showToast('Erreur de création','error');
|
||||
else showToast(t('toast.create_error'),'error');
|
||||
} catch(e) { showToast('Erreur : '+(e.message||e),'error'); }
|
||||
}
|
||||
|
||||
@@ -346,37 +347,37 @@ async function execCreateDir(name) {
|
||||
try {
|
||||
const d = await api(`/api/directory/${encodeURIComponent(_browse.vault)}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({path:dp}) });
|
||||
if (d?.status==='ok' || d?.success) { showToast(`📁 ${dp} créé`,'success'); close(); refreshSidebar(); }
|
||||
else showToast('Erreur','error');
|
||||
else showToast(t('common.error'),'error');
|
||||
} catch(e) { showToast('Erreur : '+(e.message||e),'error'); }
|
||||
}
|
||||
|
||||
async function execDelDir(path) {
|
||||
if (!confirm(`Supprimer "${path}" et TOUT son contenu ?`)) return;
|
||||
if (!confirm(t("confirm.delete_dir_named", {path: path}))) return;
|
||||
try {
|
||||
const d = await api(`/api/directory/${encodeURIComponent(_browse.vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' });
|
||||
if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); }
|
||||
else showToast('Erreur','error');
|
||||
else showToast(t('common.error'),'error');
|
||||
} catch(e) { showToast('Erreur : '+(e.message||e),'error'); }
|
||||
}
|
||||
|
||||
async function execDelFile(path) {
|
||||
if (!confirm(`Supprimer le fichier "${path}" ?`)) return;
|
||||
if (!confirm(t("confirm.delete_file_named", {path: path}))) return;
|
||||
try {
|
||||
const d = await api(`/api/file/${encodeURIComponent(_browse.vault)}?path=${encodeURIComponent(path)}`, { method:'DELETE' });
|
||||
if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${path} supprimé`,'success'); close(); refreshSidebar(); }
|
||||
else showToast('Erreur','error');
|
||||
else showToast(t('common.error'),'error');
|
||||
} catch(e) { showToast('Erreur : '+(e.message||e),'error'); }
|
||||
}
|
||||
|
||||
async function delCurFile() {
|
||||
const f = getCurrentFile();
|
||||
if (!f) { showToast('Aucun fichier ouvert','error'); return; }
|
||||
if (!confirm(`Supprimer "${f.path}" ?`)) return;
|
||||
if (!f) { showToast(t('toast.no_open_file'),'error'); return; }
|
||||
if (!confirm(t("confirm.delete_file_named", {path: f.path}))) return;
|
||||
try {
|
||||
const d = await api(`/api/file/${encodeURIComponent(f.vault)}?path=${encodeURIComponent(f.path)}`, { method:'DELETE' });
|
||||
if (d?.status==='ok' || d?.success) { showToast(`🗑️ ${f.path} supprimé`,'success');
|
||||
const tab = TabManager._tabs.find(t=>t.vault===f.vault&&t.path===f.path); if(tab) TabManager.close(tab.id); triggerGoHome(); refreshSidebar(); }
|
||||
else showToast('Erreur','error');
|
||||
else showToast(t('common.error'),'error');
|
||||
} catch(e) { showToast('Erreur : '+(e.message||e),'error'); }
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { searchOffline } from './offline.js';
|
||||
import { _getEffective } from './config.js';
|
||||
import { TabManager, showToast } from './ui.js';
|
||||
import { addTagFilter, buildSearchResultsHeader, shouldDisplayPath, removeTagFilter, TagFilterService } from './sidebar.js';
|
||||
import { t } from './i18n.js';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Search History Service (localStorage, LIFO, max 50, dedup)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -915,17 +916,17 @@ export function renderAdvancedSearchResults(data, query, tagFilter) {
|
||||
const freeText = parsed.freeText;
|
||||
|
||||
if (freeText && tagFilter) {
|
||||
summaryText.textContent = `${data.total} résultat(s) pour "${freeText}" avec filtres`;
|
||||
summaryText.textContent = t('search.results_filtered', {count: data.total, query: freeText});
|
||||
} else if (freeText) {
|
||||
summaryText.textContent = `${data.total} résultat(s) pour "${freeText}"`;
|
||||
summaryText.textContent = t('search.results_for', {count: data.total, query: freeText});
|
||||
} else if (parsed.tags.length > 0 || tagFilter) {
|
||||
summaryText.textContent = `${data.total} fichier(s) avec filtres`;
|
||||
summaryText.textContent = t('search.files_filtered', {count: data.total});
|
||||
} else {
|
||||
summaryText.textContent = `${data.total} résultat(s)`;
|
||||
summaryText.textContent = t('search.results_count', {count: data.total});
|
||||
}
|
||||
if (data.query_time_ms !== undefined && data.query_time_ms > 0) {
|
||||
const timeBadge = el("span", { class: "search-time-badge" });
|
||||
timeBadge.textContent = `(${data.query_time_ms} ms)`;
|
||||
timeBadge.textContent = t('search.query_time', {time: data.query_time_ms});
|
||||
summaryText.appendChild(timeBadge);
|
||||
}
|
||||
header.appendChild(summaryText);
|
||||
|
||||
+43
-43
@@ -660,7 +660,7 @@ const FileOperations = {
|
||||
<div class="modal-form-group">
|
||||
<label class="modal-label">Nom du dossier</label>
|
||||
<input type="text" class="modal-input" id="dir-name-input" placeholder="nouveau-dossier" />
|
||||
<div class="modal-hint">Dans: ${parentPath || '/'}</div>
|
||||
<div class="modal-hint">${t('modal.in_folder', {path: parentPath || '/'}</div>
|
||||
<div class="modal-error" id="dir-error" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -711,7 +711,7 @@ const FileOperations = {
|
||||
|
||||
const path = parentPath ? `${parentPath}/${name}` : name;
|
||||
createBtn.disabled = true;
|
||||
createBtn.textContent = 'Création...';
|
||||
createBtn.textContent = t('modal.creating');
|
||||
|
||||
try {
|
||||
await api(`/api/directory/${encodeURIComponent(vault)}`, {
|
||||
@@ -720,13 +720,13 @@ const FileOperations = {
|
||||
body: JSON.stringify({ path }),
|
||||
});
|
||||
|
||||
showToast(`Dossier "${name}" créé`, 'success');
|
||||
showToast(t('toast.dir_created', {name: name}), 'success');
|
||||
this._closeModal(overlay);
|
||||
await refreshSidebarTreePreservingState();
|
||||
} catch (err) {
|
||||
showToast(err.message || 'Erreur lors de la création', 'error');
|
||||
showToast(err.message || t('toast.create_error'), 'error');
|
||||
createBtn.disabled = false;
|
||||
createBtn.textContent = 'Créer';
|
||||
createBtn.textContent = t('modal.create_btn');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -745,17 +745,17 @@ const FileOperations = {
|
||||
modal.className = 'obsigate-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="obsigate-modal-header">
|
||||
<h3 class="obsigate-modal-title">Créer un fichier</h3>
|
||||
<h3 class="obsigate-modal-title">${t('modal.create_file')}</h3>
|
||||
</div>
|
||||
<div class="obsigate-modal-body">
|
||||
<div class="modal-form-group">
|
||||
<label class="modal-label">Nom du fichier</label>
|
||||
<label class="modal-label">${t('modal.file_name')}</label>
|
||||
<input type="text" class="modal-input" id="file-name-input" placeholder="note.md" />
|
||||
<div class="modal-hint">Dans: ${parentPath || '/'}</div>
|
||||
<div class="modal-hint">${t('modal.in_folder', {path: parentPath || '/'}</div>
|
||||
<div class="modal-error" id="file-error" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="modal-form-group">
|
||||
<label class="modal-label">Type de fichier</label>
|
||||
<label class="modal-label">${t('modal.file_type')}</label>
|
||||
<select class="modal-select" id="file-ext-select">
|
||||
<option value=".md">Markdown (.md)</option>
|
||||
<option value=".txt">Texte (.txt)</option>
|
||||
@@ -769,8 +769,8 @@ const FileOperations = {
|
||||
</div>
|
||||
</div>
|
||||
<div class="obsigate-modal-footer">
|
||||
<button class="modal-btn" id="file-cancel-btn">Annuler</button>
|
||||
<button class="modal-btn primary" id="file-create-btn">Créer</button>
|
||||
<button class="modal-btn" id="file-cancel-btn">${t('common.cancel')}</button>
|
||||
<button class="modal-btn primary" id="file-create-btn">${t('modal.create_btn')}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -821,7 +821,7 @@ const FileOperations = {
|
||||
|
||||
const path = parentPath ? `${parentPath}/${name}` : name;
|
||||
createBtn.disabled = true;
|
||||
createBtn.textContent = 'Création...';
|
||||
createBtn.textContent = t('modal.creating');
|
||||
|
||||
try {
|
||||
await api(`/api/file/${encodeURIComponent(vault)}`, {
|
||||
@@ -830,14 +830,14 @@ const FileOperations = {
|
||||
body: JSON.stringify({ path, content: '' }),
|
||||
});
|
||||
|
||||
showToast(`Fichier "${name}" créé`, 'success');
|
||||
showToast(t('toast.file_created', {name: name}), 'success');
|
||||
this._closeModal(overlay);
|
||||
await refreshSidebarTreePreservingState();
|
||||
openFile(vault, path);
|
||||
} catch (err) {
|
||||
showToast(err.message || 'Erreur lors de la création', 'error');
|
||||
showToast(err.message || t('toast.create_error'), 'error');
|
||||
createBtn.disabled = false;
|
||||
createBtn.textContent = 'Créer';
|
||||
createBtn.textContent = t('modal.create_btn');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -926,10 +926,10 @@ const FileOperations = {
|
||||
await focusPathInSidebar(vault, state.currentPath, { alignToTop: false });
|
||||
}
|
||||
|
||||
showToast(type === 'directory' ? 'Dossier renommé' : 'Fichier renommé', 'success');
|
||||
showToast(type === 'directory' ? t('toast.dir_renamed') : t('toast.file_renamed'), 'success');
|
||||
} catch (err) {
|
||||
input.disabled = false;
|
||||
showToast(err.message || 'Erreur lors du renommage', 'error');
|
||||
showToast(err.message || t('toast.rename_error'), 'error');
|
||||
input.focus();
|
||||
input.select();
|
||||
return;
|
||||
@@ -964,24 +964,24 @@ const FileOperations = {
|
||||
modal.className = 'obsigate-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="obsigate-modal-header">
|
||||
<h3 class="obsigate-modal-title">Supprimer le dossier</h3>
|
||||
<h3 class="obsigate-modal-title">${t('modal.delete_dir')}</h3>
|
||||
</div>
|
||||
<div class="obsigate-modal-body">
|
||||
<div class="modal-warning">
|
||||
<i data-lucide="alert-triangle" class="icon"></i>
|
||||
<div>
|
||||
<strong>Attention !</strong> Cette action est irréversible.
|
||||
<br>Tous les fichiers et sous-dossiers seront supprimés définitivement.
|
||||
<strong>${t('modal.delete_warning')}</strong> ${t('modal.delete_irreversible')}
|
||||
<br>${t('modal.delete_dir_warning')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-form-group">
|
||||
<label class="modal-label">Dossier à supprimer:</label>
|
||||
<label class="modal-label">${t('modal.dir_to_delete')}</label>
|
||||
<div style="font-family: 'JetBrains Mono', monospace; color: var(--text-muted);">${path}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="obsigate-modal-footer">
|
||||
<button class="modal-btn" id="del-cancel-btn">Annuler</button>
|
||||
<button class="modal-btn danger" id="del-confirm-btn">Supprimer définitivement</button>
|
||||
<button class="modal-btn" id="del-cancel-btn">${t('common.cancel')}</button>
|
||||
<button class="modal-btn danger" id="del-confirm-btn">${t('modal.delete_permanently')}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -996,14 +996,14 @@ const FileOperations = {
|
||||
|
||||
const deleteDir = async () => {
|
||||
confirmBtn.disabled = true;
|
||||
confirmBtn.textContent = 'Suppression...';
|
||||
confirmBtn.textContent = t('modal.creating');
|
||||
|
||||
try {
|
||||
const result = await api(`/api/directory/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
showToast(`Dossier supprimé (${result.deleted_count} fichiers)`, 'success');
|
||||
showToast(t('toast.dir_deleted', {count: result.deleted_count}), 'success');
|
||||
this._closeModal(overlay);
|
||||
await refreshSidebarTreePreservingState();
|
||||
|
||||
@@ -1011,9 +1011,9 @@ const FileOperations = {
|
||||
showWelcome();
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(err.message || 'Erreur lors de la suppression', 'error');
|
||||
showToast(err.message || t('toast.delete_error'), 'error');
|
||||
confirmBtn.disabled = false;
|
||||
confirmBtn.textContent = 'Supprimer définitivement';
|
||||
confirmBtn.textContent = t('modal.delete_permanently');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1027,23 +1027,23 @@ const FileOperations = {
|
||||
modal.className = 'obsigate-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="obsigate-modal-header">
|
||||
<h3 class="obsigate-modal-title">Supprimer le fichier</h3>
|
||||
<h3 class="obsigate-modal-title">${t('modal.delete_file')}</h3>
|
||||
</div>
|
||||
<div class="obsigate-modal-body">
|
||||
<div class="modal-warning">
|
||||
<i data-lucide="alert-triangle" class="icon"></i>
|
||||
<div>
|
||||
<strong>Attention !</strong> Cette action est irréversible.
|
||||
<strong>${t('modal.delete_warning')}</strong> ${t('modal.delete_irreversible')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-form-group">
|
||||
<label class="modal-label">Fichier à supprimer:</label>
|
||||
<label class="modal-label">${t('modal.file_to_delete')}</label>
|
||||
<div style="font-family: 'JetBrains Mono', monospace; color: var(--text-muted);">${path}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="obsigate-modal-footer">
|
||||
<button class="modal-btn" id="del-cancel-btn">Annuler</button>
|
||||
<button class="modal-btn danger" id="del-confirm-btn">Supprimer définitivement</button>
|
||||
<button class="modal-btn" id="del-cancel-btn">${t('common.cancel')}</button>
|
||||
<button class="modal-btn danger" id="del-confirm-btn">${t('modal.delete_permanently')}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1058,14 +1058,14 @@ const FileOperations = {
|
||||
|
||||
const deleteFile = async () => {
|
||||
confirmBtn.disabled = true;
|
||||
confirmBtn.textContent = 'Suppression...';
|
||||
confirmBtn.textContent = t('modal.creating');
|
||||
|
||||
try {
|
||||
await api(`/api/file/${encodeURIComponent(vault)}?path=${encodeURIComponent(path)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
showToast('Fichier supprimé', 'success');
|
||||
showToast(t('toast.file_deleted'), 'success');
|
||||
this._closeModal(overlay);
|
||||
await refreshSidebarTreePreservingState();
|
||||
|
||||
@@ -1073,9 +1073,9 @@ const FileOperations = {
|
||||
showWelcome();
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(err.message || 'Erreur lors de la suppression', 'error');
|
||||
showToast(err.message || t('toast.delete_error'), 'error');
|
||||
confirmBtn.disabled = false;
|
||||
confirmBtn.textContent = 'Supprimer définitivement';
|
||||
confirmBtn.textContent = t('modal.delete_permanently');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1772,7 +1772,7 @@ export const ContextMenuManager = {
|
||||
// Try modern clipboard API first, fall back to execCommand for non-secure contexts
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(path).then(() => {
|
||||
showToast(`Chemin copié : ${path}`, 'success');
|
||||
showToast(t('toast.path_copied', {path: path}), 'success');
|
||||
}).catch(() => {
|
||||
this._copyPathFallback(path);
|
||||
});
|
||||
@@ -1793,12 +1793,12 @@ export const ContextMenuManager = {
|
||||
try {
|
||||
const success = document.execCommand('copy');
|
||||
if (success) {
|
||||
showToast(`Chemin copié : ${path}`, 'success');
|
||||
showToast(t('toast.path_copied', {path: path}), 'success');
|
||||
} else {
|
||||
showToast('Erreur lors de la copie', 'error');
|
||||
showToast(t('toast.copy_error'), 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Erreur lors de la copie', 'error');
|
||||
showToast(t('toast.copy_error'), 'error');
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
},
|
||||
@@ -1810,7 +1810,7 @@ export const ContextMenuManager = {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ vault: this._targetVault, path: this._targetPath, title: this._targetPath.split("/").pop() }),
|
||||
});
|
||||
showToast(data.bookmarked ? "Ajouté aux bookmarks" : "Retiré des bookmarks", "success");
|
||||
showToast(data.bookmarked ? t("toast.bookmark_added") : t("toast.bookmark_removed"), "success");
|
||||
if (typeof DashboardBookmarkWidget !== "undefined" && DashboardBookmarkWidget.load) {
|
||||
DashboardBookmarkWidget.load();
|
||||
}
|
||||
@@ -1835,7 +1835,7 @@ export const ContextMenuManager = {
|
||||
exclude_paths: "",
|
||||
}),
|
||||
});
|
||||
showToast("Repertoire sauvegarde dans les recherches", "success");
|
||||
showToast(t("toast.dir_saved_search"), "success");
|
||||
// Switch to saved searches tab
|
||||
var savedTab = document.getElementById("sidebar-tab-saved");
|
||||
if (savedTab) savedTab.click();
|
||||
@@ -1849,7 +1849,7 @@ export const ContextMenuManager = {
|
||||
import('./backups.js').then(mod => {
|
||||
mod.openBackupViewer(this._targetVault, this._targetPath);
|
||||
}).catch(err => {
|
||||
showToast("Erreur lors du chargement du viewer: " + err.message, "error");
|
||||
showToast(t("toast.viewer_error") + ": " + err.message, "error");
|
||||
});
|
||||
this.hide();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { renderMermaidBlocks } from './mermaid-viewer.js';
|
||||
import { DashboardStatsWidget, DashboardRecentWidget, DashboardBookmarkWidget, DashboardSharedWidget, DashboardConflictsWidget } from './dashboard.js';
|
||||
import { openShareDialog } from './config.js';
|
||||
import { cacheViewedFile, getCachedFile } from './offline.js';
|
||||
import { t } from './i18n.js';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Outline/TOC Manager
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -534,7 +535,7 @@ export function renderFile(data) {
|
||||
var titleInp = document.getElementById("editor-title-input");
|
||||
var fileInp = document.getElementById("editor-filename-input");
|
||||
if (brand) brand.textContent = "Forge";
|
||||
if (titleInp) { titleInp.value = data.title || ''; titleInp.placeholder = 'Titre...'; }
|
||||
if (titleInp) { titleInp.value = data.title || ''; titleInp.placeholder = t('forge.title_placeholder'); }
|
||||
if (fileInp) fileInp.value = data.path.split('/').pop();
|
||||
bodyEl.innerHTML = "";
|
||||
bodyEl.style.overflow = "hidden";
|
||||
@@ -547,7 +548,7 @@ export function renderFile(data) {
|
||||
safeCreateIcons();
|
||||
});
|
||||
|
||||
const openNewWindowBtn = el("button", { class: "btn-action", title: "Ouvrir dans une nouvelle fenetre" }, [icon("external-link", 14), document.createTextNode("pop-out")]);
|
||||
const openNewWindowBtn = el("button", { class: "btn-action", title: t("forge.popout") }, [icon("external-link", 14), document.createTextNode(t("forge.popout_label"))]);
|
||||
openNewWindowBtn.addEventListener("click", () => {
|
||||
const popoutUrl = `/popout/${encodeURIComponent(data.vault)}/${encodeURIComponent(data.path)}`;
|
||||
window.open(popoutUrl, `popout_${data.vault}_${data.path.replace(/[^a-zA-Z0-9]/g, "_")}`, "width=1000,height=700,menubar=no,toolbar=no,location=no,status=no,resizable=yes,scrollbars=no");
|
||||
|
||||
+74
-27
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"_meta": { "locale": "en", "name": "English" },
|
||||
|
||||
"_meta": {
|
||||
"locale": "en",
|
||||
"name": "English"
|
||||
},
|
||||
"header.logo": "ObsiGate",
|
||||
"header.search_placeholder": "Search all vaults...",
|
||||
"header.config": "Settings",
|
||||
@@ -26,7 +28,6 @@
|
||||
"header.sidebar_toggle": "Toggle sidebar",
|
||||
"header.menu_toggle": "Menu",
|
||||
"header.quick_help": "Quick Help",
|
||||
|
||||
"sidebar.filter_placeholder": "Filter files...",
|
||||
"sidebar.tab_vaults": "Vaults",
|
||||
"sidebar.tab_tags": "Tags",
|
||||
@@ -49,7 +50,6 @@
|
||||
"sidebar.vault_specific": "Specific vault",
|
||||
"sidebar.sidebar_dropdown": "Vault context menu",
|
||||
"sidebar.sidebar_filtering": "Sidebar filtering",
|
||||
|
||||
"search.operators_help": "Operators: tag:word, vault:name, title:word, path:word, ext:md",
|
||||
"search.results_count": "{count} result(s)",
|
||||
"search.no_results": "No results found",
|
||||
@@ -80,7 +80,6 @@
|
||||
"search.advanced_operators": "Advanced operators",
|
||||
"search.include_placeholder": "Include: **/*.md, notes/**",
|
||||
"search.exclude_placeholder": "Exclude: vendor/*, *.lock",
|
||||
|
||||
"viewer.source": "Source",
|
||||
"viewer.markdown": "Markdown",
|
||||
"viewer.edit": "Edit",
|
||||
@@ -108,7 +107,6 @@
|
||||
"viewer.breadcrumb_home": "Home",
|
||||
"viewer.unsupported_file": "This file is binary and cannot be displayed",
|
||||
"viewer.click_to_download": "Click to download",
|
||||
|
||||
"editor.save": "Save",
|
||||
"editor.cancel": "Cancel",
|
||||
"editor.saved": "Saved ✓",
|
||||
@@ -122,7 +120,6 @@
|
||||
"editor.close": "Close",
|
||||
"editor.forge_help": "Forge editor help",
|
||||
"editor.forge_close_editor": "Close editor / modal",
|
||||
|
||||
"ai.toolbar_edit": "Edit",
|
||||
"ai.toolbar_tone": "Tone",
|
||||
"ai.toolbar_translate": "Translate",
|
||||
@@ -150,7 +147,6 @@
|
||||
"ai.done": "AI: text processed",
|
||||
"ai.completion_added": "AI: completion added",
|
||||
"ai.rewrite_done": "AI: text rewritten",
|
||||
|
||||
"dashboard.welcome": "Welcome to ObsiGate",
|
||||
"dashboard.stats": "Statistics",
|
||||
"dashboard.bookmarks": "Bookmarks",
|
||||
@@ -165,7 +161,6 @@
|
||||
"dashboard.no_recent": "No recent files",
|
||||
"dashboard.no_shares": "No shared documents",
|
||||
"dashboard.shares": "Shares",
|
||||
|
||||
"config.title": "Settings",
|
||||
"config.section_search": "Search settings",
|
||||
"config.section_perf": "Performance",
|
||||
@@ -241,7 +236,6 @@
|
||||
"config.hidden_include_label": "Include hidden files",
|
||||
"config.hidden_whitelist_label": "Whitelist (comma-separated)",
|
||||
"config.save_hidden": "Save",
|
||||
|
||||
"help.title": "ObsiGate Help",
|
||||
"help.shortcuts": "Keyboard shortcuts",
|
||||
"help.search_operators": "Search operators",
|
||||
@@ -405,7 +399,6 @@
|
||||
"help.footer": "© 2026 — Made with ☕, 🎧 and a few",
|
||||
"help.nights": "sleepless nights 😅",
|
||||
"help.powered_by": "Powered by Python, FastAPI, vanilla JS, CodeMirror, Mermaid, Lucide Icons and ❤️ for Obsidian.",
|
||||
|
||||
"auth.login_title": "Secure access to your vaults",
|
||||
"auth.username": "Username",
|
||||
"auth.password": "Password",
|
||||
@@ -415,7 +408,6 @@
|
||||
"auth.network_error": "Network error — check your connection",
|
||||
"auth.logged_out": "Signed out",
|
||||
"auth.session_expired": "Session expired, please sign in again",
|
||||
|
||||
"share.title": "Public share",
|
||||
"share.create": "Create share link",
|
||||
"share.copy_link": "Copy link",
|
||||
@@ -423,7 +415,6 @@
|
||||
"share.expires": "Expires on",
|
||||
"share.never": "Never",
|
||||
"share.copied": "Link copied!",
|
||||
|
||||
"backup.title": "Backups",
|
||||
"backup.restore": "Restore",
|
||||
"backup.delete": "Delete",
|
||||
@@ -432,7 +423,6 @@
|
||||
"backup.no_backups": "No backups",
|
||||
"backup.preview": "Preview",
|
||||
"backup.version": "Version from {date}",
|
||||
|
||||
"graph.title": "Graph",
|
||||
"graph.fullscreen": "Full screen",
|
||||
"graph.export_png": "Export PNG",
|
||||
@@ -444,31 +434,26 @@
|
||||
"graph.forward": "Forward",
|
||||
"graph.parent": "Parent",
|
||||
"graph.loading": "Building graph...",
|
||||
|
||||
"mermaid.code": "Code",
|
||||
"mermaid.preview": "Preview",
|
||||
"mermaid.copy_svg": "Copy SVG",
|
||||
"mermaid.download_png": "Download PNG",
|
||||
"mermaid.zoom_in": "Zoom in",
|
||||
"mermaid.zoom_out": "Zoom out",
|
||||
|
||||
"palette.search_files": "Search files...",
|
||||
"palette.search_commands": "Type a command...",
|
||||
"palette.mode_files": "Files",
|
||||
"palette.mode_commands": "Commands",
|
||||
|
||||
"offline.status": "Offline",
|
||||
"offline.pending": "{count} pending change(s)",
|
||||
"offline.sync_now": "Sync now",
|
||||
"offline.conflict_title": "Sync conflict",
|
||||
"offline.keep_local": "Keep local version",
|
||||
"offline.keep_server": "Keep server version",
|
||||
|
||||
"conflict.resolve_local": "Keep local version",
|
||||
"conflict.resolve_conflict": "Keep conflict version",
|
||||
"conflict.no_conflicts": "No conflicts detected",
|
||||
"conflict.resolved": "Conflict resolved",
|
||||
|
||||
"common.loading": "Loading...",
|
||||
"common.error": "Error",
|
||||
"common.ok": "OK",
|
||||
@@ -494,7 +479,6 @@
|
||||
"common.all": "All",
|
||||
"common.none": "None",
|
||||
"common.actions": "Actions",
|
||||
|
||||
"toast.saved": "Saved",
|
||||
"toast.deleted": "Deleted",
|
||||
"toast.created": "Created",
|
||||
@@ -526,16 +510,13 @@
|
||||
"toast.conflict_resolved": "Conflict resolved",
|
||||
"toast.search_saved": "Search saved",
|
||||
"toast.hidden_saved": "✓ Settings saved",
|
||||
|
||||
"confirm.logout": "Are you sure you want to sign out?",
|
||||
"confirm.delete_file": "Permanently delete this file?",
|
||||
"confirm.delete_dir": "Delete this folder and all its contents?",
|
||||
|
||||
"pwa.install_title": "Install ObsiGate",
|
||||
"pwa.install_desc": "Install this app on your device for quick access.",
|
||||
"pwa.install_button": "Install",
|
||||
"pwa.dismiss": "Later",
|
||||
|
||||
"file.new_file": "New file",
|
||||
"file.new_folder": "New folder",
|
||||
"file.rename": "Rename",
|
||||
@@ -543,13 +524,79 @@
|
||||
"file.copy_path": "Copy path",
|
||||
"file.properties": "Properties",
|
||||
"file.graph_view": "Graph view",
|
||||
|
||||
"vault.add_dynamic": "Add vault",
|
||||
"vault.remove_dynamic": "Remove vault",
|
||||
"vault.reindex": "Reindex vault",
|
||||
|
||||
"theme.light": "Light",
|
||||
"theme.dark": "Dark",
|
||||
"theme.change": "Change theme",
|
||||
"theme.title": "Theme"
|
||||
}
|
||||
"theme.title": "Theme",
|
||||
"modal.create_file": "Create a file",
|
||||
"modal.file_name": "File name",
|
||||
"modal.file_type": "File type",
|
||||
"modal.in_folder": "In: {path}",
|
||||
"modal.create_btn": "Create",
|
||||
"modal.creating": "Creating...",
|
||||
"modal.delete_file": "Delete file",
|
||||
"modal.delete_dir": "Delete folder",
|
||||
"modal.delete_warning": "Warning!",
|
||||
"modal.delete_irreversible": "This action is irreversible.",
|
||||
"modal.delete_dir_warning": "All files and subfolders will be permanently deleted.",
|
||||
"modal.file_to_delete": "File to delete:",
|
||||
"modal.dir_to_delete": "Folder to delete:",
|
||||
"modal.delete_permanently": "Delete permanently",
|
||||
"admin.title": "Administration — Users",
|
||||
"admin.new_user": "+ New user",
|
||||
"admin.close": "Close",
|
||||
"about.section_app": "Application",
|
||||
"about.section_env": "Environment",
|
||||
"about.section_components": "Components",
|
||||
"about.name": "Name",
|
||||
"about.version": "Version",
|
||||
"about.api_version": "API version",
|
||||
"about.status": "Status",
|
||||
"about.vaults_configured": "Configured vaults",
|
||||
"about.files_indexed": "Indexed files",
|
||||
"about.browser": "Browser",
|
||||
"about.platform": "Platform",
|
||||
"about.language": "Language",
|
||||
"about.backend": "Backend",
|
||||
"about.markdown_renderer": "Markdown rendering",
|
||||
"about.file_watcher": "File watching",
|
||||
"about.frontend": "Frontend",
|
||||
"about.icons": "Icons",
|
||||
"about.syntax_highlight": "Syntax highlighting",
|
||||
"about.editor": "Editor",
|
||||
"forge.title_placeholder": "Title...",
|
||||
"forge.popout": "Open in new window",
|
||||
"forge.popout_label": "pop-out",
|
||||
"dashboard.views": "{count} view(s)",
|
||||
"dashboard.expires": "Expires {date}",
|
||||
"dashboard.copy_btn": "Copy",
|
||||
"dashboard.open_btn": "Open",
|
||||
"search.results_for": "{count} result(s) for \"{query}\"",
|
||||
"search.results_filtered": "{count} result(s) for \"{query}\" with filters",
|
||||
"search.files_filtered": "{count} file(s) with filters",
|
||||
"search.query_time": "({time} ms)",
|
||||
"toast.file_created": "File \"{name}\" created",
|
||||
"toast.dir_created": "Folder \"{name}\" created",
|
||||
"toast.dir_deleted": "Folder deleted ({count} files)",
|
||||
"toast.path_copied": "Path copied: {path}",
|
||||
"toast.create_error": "Creation error",
|
||||
"toast.rename_error": "Rename error",
|
||||
"toast.delete_error": "Deletion error",
|
||||
"toast.copy_error": "Copy error",
|
||||
"toast.bookmark_added": "Added to bookmarks",
|
||||
"toast.bookmark_removed": "Removed from bookmarks",
|
||||
"toast.dir_saved_search": "Folder saved to searches",
|
||||
"toast.no_open_file": "No file open",
|
||||
"toast.viewer_error": "Viewer loading error",
|
||||
"confirm.delete_file_named": "Delete file \"{path}\"?",
|
||||
"confirm.delete_dir_named": "Delete \"{path}\" and ALL its contents?",
|
||||
"tree.new_file": "New file",
|
||||
"tree.new_folder": "New folder",
|
||||
"tree.vault_home": "Vault home",
|
||||
"toc.title": "ON THIS PAGE",
|
||||
"toc.hide_panel": "Hide panel",
|
||||
"toc.no_headings": "No headings in this document"
|
||||
}
|
||||
+74
-27
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"_meta": { "locale": "fr", "name": "Français" },
|
||||
|
||||
"_meta": {
|
||||
"locale": "fr",
|
||||
"name": "Français"
|
||||
},
|
||||
"header.logo": "ObsiGate",
|
||||
"header.search_placeholder": "Rechercher dans toutes les vaults...",
|
||||
"header.config": "Configuration",
|
||||
@@ -26,7 +28,6 @@
|
||||
"header.sidebar_toggle": "Afficher/Masquer la sidebar",
|
||||
"header.menu_toggle": "Menu",
|
||||
"header.quick_help": "Raccourcis & Astuces",
|
||||
|
||||
"sidebar.filter_placeholder": "Filtrer fichiers...",
|
||||
"sidebar.tab_vaults": "Vaults",
|
||||
"sidebar.tab_tags": "Tags",
|
||||
@@ -49,7 +50,6 @@
|
||||
"sidebar.vault_specific": "Vault spécifique",
|
||||
"sidebar.sidebar_dropdown": "Menu contextuel vault",
|
||||
"sidebar.sidebar_filtering": "Filtrage de la sidebar",
|
||||
|
||||
"search.operators_help": "Opérateurs : tag:mot, vault:nom, title:mot, path:mot, ext:md",
|
||||
"search.results_count": "{count} résultat(s)",
|
||||
"search.no_results": "Aucun résultat trouvé",
|
||||
@@ -80,7 +80,6 @@
|
||||
"search.advanced_operators": "Opérateurs avancés",
|
||||
"search.include_placeholder": "Inclure: **/*.md, notes/**",
|
||||
"search.exclude_placeholder": "Exclure: vendor/*, *.lock",
|
||||
|
||||
"viewer.source": "Source",
|
||||
"viewer.markdown": "Markdown",
|
||||
"viewer.edit": "Éditer",
|
||||
@@ -108,7 +107,6 @@
|
||||
"viewer.breadcrumb_home": "Accueil",
|
||||
"viewer.unsupported_file": "Ce fichier est binaire et ne peut pas être affiché",
|
||||
"viewer.click_to_download": "Cliquez pour le télécharger",
|
||||
|
||||
"editor.save": "Enregistrer",
|
||||
"editor.cancel": "Annuler",
|
||||
"editor.saved": "Sauvegardé ✓",
|
||||
@@ -122,7 +120,6 @@
|
||||
"editor.close": "Fermer",
|
||||
"editor.forge_help": "Aide de l'éditeur Forge",
|
||||
"editor.forge_close_editor": "Fermer l'éditeur / modale",
|
||||
|
||||
"ai.toolbar_edit": "Éditer",
|
||||
"ai.toolbar_tone": "Ton",
|
||||
"ai.toolbar_translate": "Traduire",
|
||||
@@ -150,7 +147,6 @@
|
||||
"ai.done": "AI: texte traité",
|
||||
"ai.completion_added": "AI: complétion ajoutée",
|
||||
"ai.rewrite_done": "AI: texte réécrit",
|
||||
|
||||
"dashboard.welcome": "Bienvenue dans ObsiGate",
|
||||
"dashboard.stats": "Statistiques",
|
||||
"dashboard.bookmarks": "Favoris",
|
||||
@@ -165,7 +161,6 @@
|
||||
"dashboard.no_recent": "Aucun fichier récent",
|
||||
"dashboard.no_shares": "Aucun document partagé",
|
||||
"dashboard.shares": "Partages",
|
||||
|
||||
"config.title": "Configuration",
|
||||
"config.section_search": "Paramètres de recherche",
|
||||
"config.section_perf": "Performance",
|
||||
@@ -241,7 +236,6 @@
|
||||
"config.hidden_include_label": "Inclure les fichiers cachés",
|
||||
"config.hidden_whitelist_label": "Whitelist (séparés par des virgules)",
|
||||
"config.save_hidden": "Enregistrer",
|
||||
|
||||
"help.title": "Aide ObsiGate",
|
||||
"help.shortcuts": "Raccourcis clavier",
|
||||
"help.search_operators": "Opérateurs de recherche",
|
||||
@@ -405,7 +399,6 @@
|
||||
"help.footer": "© 2026 — Fait avec ☕, 🎧 et quelques",
|
||||
"help.nights": "nuits blanches 😅",
|
||||
"help.powered_by": "Propulsé par Python, FastAPI, vanilla JS, CodeMirror, Mermaid, Lucide Icons et ❤️ pour Obsidian.",
|
||||
|
||||
"auth.login_title": "Accès sécurisé à vos vaults",
|
||||
"auth.username": "Nom d'utilisateur",
|
||||
"auth.password": "Mot de passe",
|
||||
@@ -415,7 +408,6 @@
|
||||
"auth.network_error": "Erreur réseau — vérifiez votre connexion",
|
||||
"auth.logged_out": "Déconnecté",
|
||||
"auth.session_expired": "Session expirée, veuillez vous reconnecter",
|
||||
|
||||
"share.title": "Partage public",
|
||||
"share.create": "Créer un lien de partage",
|
||||
"share.copy_link": "Copier le lien",
|
||||
@@ -423,7 +415,6 @@
|
||||
"share.expires": "Expire le",
|
||||
"share.never": "Jamais",
|
||||
"share.copied": "Lien copié !",
|
||||
|
||||
"backup.title": "Sauvegardes",
|
||||
"backup.restore": "Restaurer",
|
||||
"backup.delete": "Supprimer",
|
||||
@@ -432,7 +423,6 @@
|
||||
"backup.no_backups": "Aucune sauvegarde",
|
||||
"backup.preview": "Aperçu",
|
||||
"backup.version": "Version du {date}",
|
||||
|
||||
"graph.title": "Graphe",
|
||||
"graph.fullscreen": "Plein écran",
|
||||
"graph.export_png": "Exporter PNG",
|
||||
@@ -444,31 +434,26 @@
|
||||
"graph.forward": "Avancer",
|
||||
"graph.parent": "Parent",
|
||||
"graph.loading": "Construction du graphe...",
|
||||
|
||||
"mermaid.code": "Code",
|
||||
"mermaid.preview": "Aperçu",
|
||||
"mermaid.copy_svg": "Copier SVG",
|
||||
"mermaid.download_png": "Télécharger PNG",
|
||||
"mermaid.zoom_in": "Zoom avant",
|
||||
"mermaid.zoom_out": "Zoom arrière",
|
||||
|
||||
"palette.search_files": "Rechercher un fichier...",
|
||||
"palette.search_commands": "Tapez une commande...",
|
||||
"palette.mode_files": "Fichiers",
|
||||
"palette.mode_commands": "Commandes",
|
||||
|
||||
"offline.status": "Hors-ligne",
|
||||
"offline.pending": "{count} modification(s) en attente",
|
||||
"offline.sync_now": "Synchroniser maintenant",
|
||||
"offline.conflict_title": "Conflit de synchronisation",
|
||||
"offline.keep_local": "Garder version locale",
|
||||
"offline.keep_server": "Garder version serveur",
|
||||
|
||||
"conflict.resolve_local": "Garder version locale",
|
||||
"conflict.resolve_conflict": "Garder version conflit",
|
||||
"conflict.no_conflicts": "Aucun conflit détecté",
|
||||
"conflict.resolved": "Conflit résolu",
|
||||
|
||||
"common.loading": "Chargement...",
|
||||
"common.error": "Erreur",
|
||||
"common.ok": "OK",
|
||||
@@ -494,7 +479,6 @@
|
||||
"common.all": "Tous",
|
||||
"common.none": "Aucun",
|
||||
"common.actions": "Actions",
|
||||
|
||||
"toast.saved": "Sauvegardé",
|
||||
"toast.deleted": "Supprimé",
|
||||
"toast.created": "Créé",
|
||||
@@ -526,16 +510,13 @@
|
||||
"toast.conflict_resolved": "Conflit résolu",
|
||||
"toast.search_saved": "Recherche sauvegardée",
|
||||
"toast.hidden_saved": "✓ Paramètres sauvegardés",
|
||||
|
||||
"confirm.logout": "Voulez-vous vraiment vous déconnecter ?",
|
||||
"confirm.delete_file": "Supprimer définitivement ce fichier ?",
|
||||
"confirm.delete_dir": "Supprimer ce dossier et tout son contenu ?",
|
||||
|
||||
"pwa.install_title": "Installer ObsiGate",
|
||||
"pwa.install_desc": "Installez cette application sur votre appareil pour un accès rapide.",
|
||||
"pwa.install_button": "Installer",
|
||||
"pwa.dismiss": "Plus tard",
|
||||
|
||||
"file.new_file": "Nouveau fichier",
|
||||
"file.new_folder": "Nouveau dossier",
|
||||
"file.rename": "Renommer",
|
||||
@@ -543,13 +524,79 @@
|
||||
"file.copy_path": "Copier le chemin",
|
||||
"file.properties": "Propriétés",
|
||||
"file.graph_view": "Vue graphe",
|
||||
|
||||
"vault.add_dynamic": "Ajouter une vault",
|
||||
"vault.remove_dynamic": "Retirer la vault",
|
||||
"vault.reindex": "Réindexer la vault",
|
||||
|
||||
"theme.light": "Clair",
|
||||
"theme.dark": "Sombre",
|
||||
"theme.change": "Changer le thème",
|
||||
"theme.title": "Thème"
|
||||
}
|
||||
"theme.title": "Thème",
|
||||
"modal.create_file": "Créer un fichier",
|
||||
"modal.file_name": "Nom du fichier",
|
||||
"modal.file_type": "Type de fichier",
|
||||
"modal.in_folder": "Dans: {path}",
|
||||
"modal.create_btn": "Créer",
|
||||
"modal.creating": "Création...",
|
||||
"modal.delete_file": "Supprimer le fichier",
|
||||
"modal.delete_dir": "Supprimer le dossier",
|
||||
"modal.delete_warning": "Attention !",
|
||||
"modal.delete_irreversible": "Cette action est irréversible.",
|
||||
"modal.delete_dir_warning": "Tous les fichiers et sous-dossiers seront supprimés définitivement.",
|
||||
"modal.file_to_delete": "Fichier à supprimer:",
|
||||
"modal.dir_to_delete": "Dossier à supprimer:",
|
||||
"modal.delete_permanently": "Supprimer définitivement",
|
||||
"admin.title": "Administration — Utilisateurs",
|
||||
"admin.new_user": "+ Nouvel utilisateur",
|
||||
"admin.close": "Fermer",
|
||||
"about.section_app": "Application",
|
||||
"about.section_env": "Environnement",
|
||||
"about.section_components": "Composants",
|
||||
"about.name": "Nom",
|
||||
"about.version": "Version",
|
||||
"about.api_version": "Version API",
|
||||
"about.status": "Statut",
|
||||
"about.vaults_configured": "Vaults configurés",
|
||||
"about.files_indexed": "Fichiers indexés",
|
||||
"about.browser": "Navigateur",
|
||||
"about.platform": "Plateforme",
|
||||
"about.language": "Langue",
|
||||
"about.backend": "Backend",
|
||||
"about.markdown_renderer": "Rendu Markdown",
|
||||
"about.file_watcher": "Surveillance fichiers",
|
||||
"about.frontend": "Frontend",
|
||||
"about.icons": "Icônes",
|
||||
"about.syntax_highlight": "Coloration syntaxe",
|
||||
"about.editor": "Éditeur",
|
||||
"forge.title_placeholder": "Titre...",
|
||||
"forge.popout": "Ouvrir dans une nouvelle fenêtre",
|
||||
"forge.popout_label": "pop-out",
|
||||
"dashboard.views": "{count} vue(s)",
|
||||
"dashboard.expires": "Expire le {date}",
|
||||
"dashboard.copy_btn": "Copier",
|
||||
"dashboard.open_btn": "Ouvrir",
|
||||
"search.results_for": "{count} résultat(s) pour \"{query}\"",
|
||||
"search.results_filtered": "{count} résultat(s) pour \"{query}\" avec filtres",
|
||||
"search.files_filtered": "{count} fichier(s) avec filtres",
|
||||
"search.query_time": "({time} ms)",
|
||||
"toast.file_created": "Fichier \"{name}\" créé",
|
||||
"toast.dir_created": "Dossier \"{name}\" créé",
|
||||
"toast.dir_deleted": "Dossier supprimé ({count} fichiers)",
|
||||
"toast.path_copied": "Chemin copié : {path}",
|
||||
"toast.create_error": "Erreur lors de la création",
|
||||
"toast.rename_error": "Erreur lors du renommage",
|
||||
"toast.delete_error": "Erreur lors de la suppression",
|
||||
"toast.copy_error": "Erreur lors de la copie",
|
||||
"toast.bookmark_added": "Ajouté aux favoris",
|
||||
"toast.bookmark_removed": "Retiré des favoris",
|
||||
"toast.dir_saved_search": "Répertoire sauvegardé dans les recherches",
|
||||
"toast.no_open_file": "Aucun fichier ouvert",
|
||||
"toast.viewer_error": "Erreur lors du chargement du viewer",
|
||||
"confirm.delete_file_named": "Supprimer le fichier \"{path}\" ?",
|
||||
"confirm.delete_dir_named": "Supprimer \"{path}\" et TOUT son contenu ?",
|
||||
"tree.new_file": "Nouveau fichier",
|
||||
"tree.new_folder": "Nouveau dossier",
|
||||
"tree.vault_home": "Accueil du vault",
|
||||
"toc.title": "SUR CETTE PAGE",
|
||||
"toc.hide_panel": "Masquer le panneau",
|
||||
"toc.no_headings": "Aucun titre dans ce document"
|
||||
}
|
||||
Reference in New Issue
Block a user