diff --git a/_batch_i18n.py b/_batch_i18n.py
new file mode 100644
index 0000000..a123258
--- /dev/null
+++ b/_batch_i18n.py
@@ -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 = '
Admin uniquement
';",
+ "list.innerHTML = '' + t('common.loading') + '
';"))
+r.append((f,
+ "list.innerHTML = 'Aucun webhook configuré.
';",
+ "list.innerHTML = '' + t('common.none') + '
';"))
+
+# === 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)}")
diff --git a/_i18n_final.py b/_i18n_final.py
new file mode 100644
index 0000000..23ad980
--- /dev/null
+++ b/_i18n_final.py
@@ -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,
+ 'Créer un fichier ',
+ '${t(\'modal.create_file\')} '))
+R.append((F,
+ 'Nom du fichier ',
+ '${t(\'modal.file_name\')} '))
+R.append((F,
+ 'Dans: ${parentPath',
+ '
${t(\'modal.in_folder\', {path: parentPath'))
+R.append((F,
+ 'Type de fichier ',
+ '${t(\'modal.file_type\')} '))
+R.append((F,
+ 'Annuler ',
+ '${t(\'common.cancel\')} '))
+R.append((F,
+ 'Créer ',
+ '${t(\'modal.create_btn\')} '))
+
+# 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,
+ '
Supprimer le dossier ',
+ '${t(\'modal.delete_dir\')} '))
+R.append((F,
+ 'Attention ! Cette action est irréversible.\n Tous les fichiers et sous-dossiers seront supprimés définitivement.',
+ '${t(\'modal.delete_warning\')} ${t(\'modal.delete_irreversible\')}\n ${t(\'modal.delete_dir_warning\')}'))
+R.append((F,
+ 'Dossier à supprimer: ',
+ '${t(\'modal.dir_to_delete\')} '))
+
+# 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,
+ 'Supprimer le fichier ',
+ '${t(\'modal.delete_file\')} '))
+R.append((F,
+ 'Attention ! Cette action est irréversible.\n \n
\n \n Fichier à supprimer:',
+ '${t(\'modal.delete_warning\')} ${t(\'modal.delete_irreversible\')}\n
\n \n \n
@@ -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 = `
- Type de fichier
+ ${t('modal.file_type')}
Markdown (.md)
Texte (.txt)
@@ -769,8 +769,8 @@ const FileOperations = {
`;
@@ -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 = `
- Attention ! Cette action est irréversible.
- Tous les fichiers et sous-dossiers seront supprimés définitivement.
+ ${t('modal.delete_warning')} ${t('modal.delete_irreversible')}
+ ${t('modal.delete_dir_warning')}
`;
@@ -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 = `
- Attention ! Cette action est irréversible.
+ ${t('modal.delete_warning')} ${t('modal.delete_irreversible')}
`;
@@ -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();
}
diff --git a/frontend/js/viewer.js b/frontend/js/viewer.js
index cb7620e..3546884 100644
--- a/frontend/js/viewer.js
+++ b/frontend/js/viewer.js
@@ -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");
diff --git a/frontend/locales/en.json b/frontend/locales/en.json
index 318b432..b7bb6f9 100644
--- a/frontend/locales/en.json
+++ b/frontend/locales/en.json
@@ -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"
+}
\ No newline at end of file
diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json
index 0e27541..56fb9b2 100644
--- a/frontend/locales/fr.json
+++ b/frontend/locales/fr.json
@@ -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"
+}
\ No newline at end of file