-
+
- Se souvenir de moi (30 jours)
+ Se souvenir de moi (30 jours)
@@ -290,6 +290,7 @@
@@ -2021,29 +2022,29 @@
class="config-section help-section"
id="cfg-profile"
>
-
👤 Profil utilisateur
-
- Preferences personnelles pour personnaliser
- l'experience ObsiGate.
+
👤 Profil utilisateur
+
+ Préférences personnelles pour personnaliser
+ l'expérience ObsiGate.
diff --git a/frontend/js/app.js b/frontend/js/app.js
index 3e8dfb1..61e2741 100644
--- a/frontend/js/app.js
+++ b/frontend/js/app.js
@@ -4,6 +4,7 @@
import * as UI from './ui.js';
import * as Utils from './utils.js';
+import { initI18n } from './i18n.js';
// Wire up AI toolbar toast (avoids circular import in utils.js)
window._obsigateShowToast = UI.showToast;
@@ -23,6 +24,7 @@ import { initMermaid, setupFocusMode } from './mermaid-viewer.js';
// =========================================================================
async function init() {
UI.initTheme();
+ await initI18n(); // Must run before any rendering that uses t()
UI.initHeaderMenu();
UI.initCustomDropdowns();
diff --git a/frontend/js/auth.js b/frontend/js/auth.js
index 5597a70..737a810 100644
--- a/frontend/js/auth.js
+++ b/frontend/js/auth.js
@@ -2,6 +2,7 @@
import { state } from './state.js';
import { safeCreateIcons } from './utils.js';
import { showToast, closeHeaderMenu } from './ui.js';
+import { t } from './i18n.js';
import { showWelcome } from './viewer.js';
// ---------------------------------------------------------------------------
@@ -33,7 +34,7 @@ async function api(path, opts) {
res = await fetch(path, mergedOpts);
} catch (err) {
if (err.name === "AbortError") throw err; // let callers handle abort
- showToast("Erreur réseau — vérifiez votre connexion", "error");
+ showToast(t("auth.network_error"), "error");
throw err;
}
if (res.status === 401 && AuthManager._authEnabled) {
diff --git a/frontend/js/config.js b/frontend/js/config.js
index 5c34f05..c0e1e6a 100644
--- a/frontend/js/config.js
+++ b/frontend/js/config.js
@@ -5,6 +5,7 @@ import { el, icon, openFile } from './viewer.js';
import { syncVaultSelectors, setSelectedVaultContext, refreshSidebarForContext, loadVaults, loadVaultSettings, loadTags, TagFilterService, refreshSidebarTreePreservingState } from './sidebar.js';
import { escapeHtml, safeCreateIcons } from './utils.js';
import { showToast, closeHeaderMenu, closeMobileSidebar } from './ui.js';
+import { t, setLocale, getLocale } from './i18n.js';
let _recentTimestampTimer = null;
let _recentFilesCache = [];
@@ -769,7 +770,7 @@ function saveFrontendConfig() {
search_timeout_ms: _getFieldNum("cfg-timeout", 30000),
};
localStorage.setItem(_FRONTEND_CONFIG_KEY, JSON.stringify(cfg));
- showToast("Paramètres client sauvegardés", "success");
+ showToast(t("toast.config_client_saved"), "success");
}
async function saveBackendConfig() {
@@ -793,7 +794,7 @@ async function saveBackendConfig() {
body: JSON.stringify(body),
});
if (res.ok) {
- showToast("Configuration backend sauvegardée", "success");
+ showToast(t("toast.config_saved"), "success");
} else {
const errorData = await res.json().catch(() => ({}));
showToast(errorData.detail || "Erreur de sauvegarde", "error");
@@ -812,7 +813,7 @@ async function forceReindex() {
}
try {
await api("/api/index/reload");
- showToast("Réindexation terminée", "success");
+ showToast(t("toast.reindex_done"), "success");
loadDiagnostics();
await Promise.all([loadVaults(), loadTags()]);
} catch (err) {
@@ -853,7 +854,7 @@ async function resetConfigDefaults() {
console.error("Reset config error:", err);
}
loadConfigFields();
- showToast("Configuration réinitialisée", "success");
+ showToast(t("toast.config_reset"), "success");
}
async function loadDiagnostics() {
@@ -1549,15 +1550,16 @@ function initProfile() {
// Load saved profile
try {
- var lang = localStorage.getItem('obsigate-lang') || 'fr';
+ var lang = getLocale();
var name = localStorage.getItem('obsigate-name') || '';
if (langSel) langSel.value = lang;
if (nameInp) nameInp.value = name;
} catch(e) {}
- saveBtn.addEventListener('click', function() {
+ saveBtn.addEventListener('click', async function() {
try {
- localStorage.setItem('obsigate-lang', langSel.value);
+ var newLang = langSel.value;
+ await setLocale(newLang); // Live-reload all translations
if (nameInp) localStorage.setItem('obsigate-name', nameInp.value.trim());
if (savedEl) { savedEl.style.display = 'inline'; setTimeout(function() { savedEl.style.display = 'none'; }, 2000); }
} catch(e) {}
@@ -1566,7 +1568,7 @@ function initProfile() {
// Logout button
if (logoutBtn) {
logoutBtn.addEventListener('click', function() {
- if (confirm('Voulez-vous vraiment vous deconnecter ?')) {
+ if (confirm(t('confirm.logout'))) {
fetch('/api/auth/logout', { method: 'POST' })
.then(function() { window.location.href = '/'; })
.catch(function() { window.location.href = '/'; });
diff --git a/frontend/js/i18n.js b/frontend/js/i18n.js
new file mode 100644
index 0000000..664f253
--- /dev/null
+++ b/frontend/js/i18n.js
@@ -0,0 +1,146 @@
+/* ObsiGate — i18n (internationalisation).
+ *
+ * Architecture:
+ * Flat key→string JSON locale files in /static/locales/{fr,en,...}.json
+ * Static DOM: data-i18n="key" → textContent
+ * data-i18n-placeholder="key" → placeholder
+ * data-i18n-attr:title="key" → title attribute
+ * data-i18n-html="key" → innerHTML (use sparingly)
+ * Dynamic JS: import { t } from './i18n.js'; t('key', {param: 'val'})
+ * Live reload: setLocale('en') updates every data-i18n element instantly.
+ *
+ * Key naming: section.what — lowercase, dot-separated, descriptive.
+ * e.g. header.search_placeholder button.save error.network
+ *
+ * Usage:
+ * import { t, setLocale, getLocale, initI18n } from './i18n.js';
+ * await initI18n(); // early, before any rendering
+ * el.textContent = t('button.cancel'); // dynamic string
+ * setLocale('en'); // user switches language
+ */
+
+// ── State ──────────────────────────────────────────────────────────────────
+let _currentLocale = 'fr';
+let _strings = {}; // { key: translated_string }
+let _fallbackStrings = {}; // FR fallback, loaded once
+let _loaded = false;
+
+// ── Public helpers ─────────────────────────────────────────────────────────
+
+/** Return the currently active locale code ('fr', 'en', ...). */
+export function getLocale() {
+ return _currentLocale;
+}
+
+/** Translate a key. If params provided, replace {name} placeholders.
+ * Falls back: key → French → key itself. Never throws. */
+export function t(key, params) {
+ let s = _strings[key];
+ if (s === undefined) {
+ s = _fallbackStrings[key];
+ if (s === undefined) {
+ console.warn('obsigate i18n: missing key', key);
+ s = key; // last resort — show the key so the dev notices
+ }
+ }
+ if (params) {
+ for (const [k, v] of Object.entries(params)) {
+ s = s.replace(new RegExp('\\{' + k + '\\}', 'g'), String(v));
+ }
+ }
+ return s;
+}
+
+/** Change locale and re-translate the entire DOM.
+ * Persists to localStorage. */
+export async function setLocale(locale) {
+ locale = locale || 'fr';
+ if (!_loaded) {
+ // Too early — store intent and apply after init
+ try { localStorage.setItem('obsigate-lang', locale); } catch (e) { /* */ }
+ return;
+ }
+ await _loadLocaleStrings(locale);
+ _applyDOM();
+ document.documentElement.lang = locale;
+ try { localStorage.setItem('obsigate-lang', locale); } catch (e) { /* */ }
+}
+
+/** Initialise the i18n system. Call once, early, before any rendering.
+ * Reads saved preference from localStorage, falls back to navigator.language,
+ * then to 'fr'. */
+export async function initI18n() {
+ // 1. Determine locale: saved > navigator.language > 'fr'
+ let locale = null;
+ try { locale = localStorage.getItem('obsigate-lang'); } catch (e) { /* */ }
+ if (!locale) {
+ const nav = (navigator.language || '').toLowerCase();
+ locale = nav.startsWith('fr') ? 'fr'
+ : nav.startsWith('en') ? 'en'
+ : 'fr';
+ }
+
+ // 2. Load fallback (FR) — always needed
+ _fallbackStrings = await _fetchLocale('fr');
+
+ // 3. Load selected locale
+ await _loadLocaleStrings(locale);
+
+ // 4. Apply to DOM
+ _applyDOM();
+ document.documentElement.lang = locale;
+ _loaded = true;
+}
+
+// ── Internal ───────────────────────────────────────────────────────────────
+
+async function _fetchLocale(locale) {
+ try {
+ const resp = await fetch('/static/locales/' + locale + '.json');
+ if (!resp.ok) throw new Error('HTTP ' + resp.status);
+ return await resp.json();
+ } catch (err) {
+ console.warn('obsigate i18n: failed to load locale ' + locale, err);
+ return {};
+ }
+}
+
+async function _loadLocaleStrings(locale) {
+ _currentLocale = locale;
+ if (locale === 'fr') {
+ _strings = _fallbackStrings;
+ } else {
+ _strings = await _fetchLocale(locale);
+ }
+}
+
+/** Walk the DOM and update every element with a data-i18n* attribute. */
+function _applyDOM() {
+ // textContent
+ document.querySelectorAll('[data-i18n]').forEach(function (el) {
+ const key = el.getAttribute('data-i18n');
+ el.textContent = t(key);
+ });
+
+ // placeholder
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(function (el) {
+ const key = el.getAttribute('data-i18n-placeholder');
+ el.setAttribute('placeholder', t(key));
+ });
+
+ // innerHTML (use sparingly — XSS risk if keys ever hold user content)
+ document.querySelectorAll('[data-i18n-html]').forEach(function (el) {
+ const key = el.getAttribute('data-i18n-html');
+ el.innerHTML = t(key);
+ });
+
+ // data-i18n-attr:TITLE → sets any attribute
+ document.querySelectorAll('[data-i18n-attr]').forEach(function (el) {
+ const raw = el.getAttribute('data-i18n-attr');
+ const colon = raw.indexOf(':');
+ if (colon === -1) return;
+ const attr = raw.substring(0, colon);
+ const key = raw.substring(colon + 1);
+ el.setAttribute(attr, t(key));
+ });
+}
diff --git a/frontend/locales/en.json b/frontend/locales/en.json
new file mode 100644
index 0000000..6329d05
--- /dev/null
+++ b/frontend/locales/en.json
@@ -0,0 +1,271 @@
+{
+ "_meta": { "locale": "en", "name": "English" },
+
+ "header.logo": "ObsiGate",
+ "header.search_placeholder": "Search all vaults...",
+ "header.config": "Settings",
+ "header.help": "Help",
+ "header.profile": "Profile",
+ "header.logout": "Sign out",
+ "header.refresh": "Refresh",
+ "header.install_app": "Install app",
+ "header.admin_panel": "Administration",
+
+ "sidebar.filter_placeholder": "Filter files...",
+ "sidebar.tab_vaults": "Vaults",
+ "sidebar.tab_tags": "Tags",
+ "sidebar.tab_recent": "Recent",
+ "sidebar.tab_saved": "Searches",
+ "sidebar.loading": "Loading...",
+ "sidebar.no_results": "No results",
+ "sidebar.vault_context_all": "All vaults",
+
+ "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",
+ "search.loading": "Searching...",
+ "search.error": "Search error",
+ "search.save_search": "Save search",
+ "search.sort_relevance": "Relevance",
+ "search.sort_date": "Date",
+ "search.sort_title": "Title",
+ "search.facet_tags": "Tags",
+ "search.facet_vaults": "Vaults",
+ "search.filters": "Filters",
+ "search.case_sensitive": "Case sensitive",
+ "search.whole_word": "Whole word",
+ "search.regex": "Regular expression",
+
+ "viewer.source": "Source",
+ "viewer.markdown": "Markdown",
+ "viewer.edit": "Edit",
+ "viewer.raw": "Raw",
+ "viewer.copy": "Copy",
+ "viewer.popout": "Open in popup",
+ "viewer.download": "Download",
+ "viewer.share": "Share",
+ "viewer.backlinks": "Backlinks",
+ "viewer.toc": "Table of contents",
+ "viewer.outline": "Outline",
+ "viewer.loading": "Loading file...",
+ "viewer.error": "Loading error",
+ "viewer.binary_unsupported": "Unsupported binary file",
+ "viewer.binary_download": "Download file",
+ "viewer.pdf_pages": "{count} pages",
+
+ "editor.save": "Save",
+ "editor.cancel": "Cancel",
+ "editor.saved": "Saved âś“",
+ "editor.unsaved": "Unsaved changes",
+ "editor.find": "Find in file...",
+ "editor.replace": "Replace",
+ "editor.find_regex": "Regex",
+ "editor.find_case": "Case sensitive",
+ "editor.find_whole": "Whole word",
+ "editor.no_results": "No results",
+ "editor.close": "Close",
+
+ "ai.toolbar_edit": "Edit",
+ "ai.toolbar_tone": "Tone",
+ "ai.toolbar_translate": "Translate",
+ "ai.toolbar_generate": "Generate",
+ "ai.toolbar_rewrite": "Rewrite",
+ "ai.toolbar_toolbox": "Toolbox",
+ "ai.improve": "Improve",
+ "ai.fix_spelling": "Fix spelling",
+ "ai.shorter": "Shorter",
+ "ai.longer": "Longer",
+ "ai.simplify": "Simplify",
+ "ai.professional": "Professional tone",
+ "ai.casual": "Casual tone",
+ "ai.translate_to": "Translate to {lang}",
+ "ai.explain": "Explain",
+ "ai.summarize": "Summarize",
+ "ai.continue": "Continue",
+ "ai.to_list": "Convert to list",
+ "ai.to_table": "Convert to table",
+ "ai.frontmatter": "Generate frontmatter",
+ "ai.to_canvas": "Convert to Canvas",
+ "ai.custom_rewrite": "Custom rewrite",
+ "ai.no_api_key": "No API key configured",
+ "ai.select_text": "Select text to process",
+ "ai.done": "AI: text processed",
+
+ "dashboard.welcome": "Welcome to ObsiGate",
+ "dashboard.stats": "Statistics",
+ "dashboard.bookmarks": "Bookmarks",
+ "dashboard.conflicts": "Conflicts",
+ "dashboard.recent": "Recent",
+ "dashboard.total_files": "Total files",
+ "dashboard.total_tags": "Unique tags",
+ "dashboard.total_size": "Total size",
+ "dashboard.vaults_count": "Vaults",
+ "dashboard.no_bookmarks": "No bookmarks",
+ "dashboard.no_conflicts": "No conflicts",
+ "dashboard.no_recent": "No recent files",
+
+ "config.title": "Settings",
+ "config.profile": "User profile",
+ "config.profile_desc": "Personal preferences to customize your ObsiGate experience.",
+ "config.lang_label": "Preferred language",
+ "config.lang_hint": "Used by autocomplete and ghost text.",
+ "config.display_name": "Display name",
+ "config.save": "Save",
+ "config.logout": "Sign out",
+ "config.saved": "âś“ Saved",
+ "config.about": "About",
+ "config.about_loading": "Loading...",
+ "config.about_error": "Loading error",
+ "config.ai_keys": "AI API keys",
+ "config.ai_keys_desc": "Configure API keys for the AI editor.",
+ "config.hidden_files": "Hidden files",
+ "config.hidden_files_desc": "Manage display of hidden files and folders.",
+ "config.webhooks": "Webhooks",
+ "config.webhooks_desc": "HTTP notifications to external services on file changes.",
+ "config.shares": "Public shares",
+ "config.shares_desc": "Manage public share links for your documents.",
+ "config.backups": "Backups",
+ "config.backups_desc": "Manage automatic file backups.",
+ "config.diagnostics": "Diagnostics",
+ "config.diagnostics_desc": "Technical information about the server state.",
+ "config.reindex": "Reindex",
+ "config.reindex_desc": "Rebuild the search index for all vaults.",
+ "config.client_config": "Client configuration",
+ "config.server_config": "Server configuration",
+
+ "help.title": "ObsiGate Help",
+ "help.shortcuts": "Keyboard shortcuts",
+ "help.search_operators": "Search operators",
+ "help.markdown": "Markdown syntax",
+ "help.about": "About",
+
+ "auth.login_title": "Secure access to your vaults",
+ "auth.username": "Username",
+ "auth.password": "Password",
+ "auth.login_button": "Sign in",
+ "auth.remember_me": "Remember me",
+ "auth.login_error": "Invalid credentials",
+ "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",
+ "share.revoke": "Revoke",
+ "share.expires": "Expires on",
+ "share.never": "Never",
+ "share.copied": "Link copied!",
+
+ "backup.title": "Backups",
+ "backup.restore": "Restore",
+ "backup.delete": "Delete",
+ "backup.diff": "Compare",
+ "backup.purge": "Purge",
+ "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",
+ "graph.depth": "Depth",
+ "graph.filter_tag": "Filter by tag",
+ "graph.filter_type": "Filter by type",
+ "graph.focus_mode": "Focus mode",
+ "graph.back": "Back",
+ "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...",
+
+ "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",
+ "common.cancel": "Cancel",
+ "common.close": "Close",
+ "common.save": "Save",
+ "common.delete": "Delete",
+ "common.confirm": "Confirm",
+ "common.yes": "Yes",
+ "common.no": "No",
+ "common.search": "Search",
+ "common.create": "Create",
+ "common.edit": "Edit",
+ "common.rename": "Rename",
+ "common.move": "Move",
+ "common.copy": "Copy",
+ "common.copied": "Copied!",
+ "common.download": "Download",
+ "common.refresh": "Refresh",
+ "common.back": "Back",
+ "common.next": "Next",
+ "common.previous": "Previous",
+ "common.all": "All",
+ "common.none": "None",
+ "common.actions": "Actions",
+
+ "toast.saved": "Saved",
+ "toast.deleted": "Deleted",
+ "toast.created": "Created",
+ "toast.renamed": "Renamed",
+ "toast.moved": "Moved",
+ "toast.copied": "Copied!",
+ "toast.error_rename": "Cannot rename this item",
+ "toast.error_delete": "Delete error",
+ "toast.error_load": "Loading error",
+ "toast.error_network": "Network error",
+ "toast.reindex_started": "Reindexing...",
+ "toast.reindex_done": "Reindexing complete",
+ "toast.index_reloaded": "Full index reloaded",
+ "toast.installed": "ObsiGate installed successfully!",
+ "toast.not_found_tree": "Item not found in tree",
+ "toast.config_saved": "Configuration saved",
+ "toast.config_client_saved": "Client settings saved",
+ "toast.config_reset": "Configuration reset",
+
+ "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",
+ "file.delete": "Delete",
+ "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"
+}
diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json
new file mode 100644
index 0000000..2a9663f
--- /dev/null
+++ b/frontend/locales/fr.json
@@ -0,0 +1,271 @@
+{
+ "_meta": { "locale": "fr", "name": "Français" },
+
+ "header.logo": "ObsiGate",
+ "header.search_placeholder": "Rechercher dans toutes les vaults...",
+ "header.config": "Configuration",
+ "header.help": "Aide",
+ "header.profile": "Profil",
+ "header.logout": "Déconnexion",
+ "header.refresh": "Rafraîchir",
+ "header.install_app": "Installer l'application",
+ "header.admin_panel": "Administration",
+
+ "sidebar.filter_placeholder": "Filtrer fichiers...",
+ "sidebar.tab_vaults": "Vaults",
+ "sidebar.tab_tags": "Tags",
+ "sidebar.tab_recent": "Récents",
+ "sidebar.tab_saved": "Recherches",
+ "sidebar.loading": "Chargement...",
+ "sidebar.no_results": "Aucun résultat",
+ "sidebar.vault_context_all": "Toutes les vaults",
+
+ "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é",
+ "search.loading": "Recherche en cours...",
+ "search.error": "Erreur de recherche",
+ "search.save_search": "Sauvegarder la recherche",
+ "search.sort_relevance": "Pertinence",
+ "search.sort_date": "Date",
+ "search.sort_title": "Titre",
+ "search.facet_tags": "Tags",
+ "search.facet_vaults": "Vaults",
+ "search.filters": "Filtres",
+ "search.case_sensitive": "Respecter la casse",
+ "search.whole_word": "Mot entier",
+ "search.regex": "Expression régulière",
+
+ "viewer.source": "Source",
+ "viewer.markdown": "Markdown",
+ "viewer.edit": "Éditer",
+ "viewer.raw": "Brut",
+ "viewer.copy": "Copier",
+ "viewer.popout": "Ouvrir dans une fenĂŞtre",
+ "viewer.download": "Télécharger",
+ "viewer.share": "Partager",
+ "viewer.backlinks": "Backlinks",
+ "viewer.toc": "Table des matières",
+ "viewer.outline": "Plan",
+ "viewer.loading": "Chargement du fichier...",
+ "viewer.error": "Erreur de chargement",
+ "viewer.binary_unsupported": "Fichier binaire non supporté",
+ "viewer.binary_download": "Télécharger le fichier",
+ "viewer.pdf_pages": "{count} pages",
+
+ "editor.save": "Enregistrer",
+ "editor.cancel": "Annuler",
+ "editor.saved": "Sauvegardé ✓",
+ "editor.unsaved": "Modifications non sauvegardées",
+ "editor.find": "Rechercher dans le fichier...",
+ "editor.replace": "Remplacer",
+ "editor.find_regex": "Regex",
+ "editor.find_case": "Respecter la casse",
+ "editor.find_whole": "Mot entier",
+ "editor.no_results": "Aucun résultat",
+ "editor.close": "Fermer",
+
+ "ai.toolbar_edit": "Éditer",
+ "ai.toolbar_tone": "Ton",
+ "ai.toolbar_translate": "Traduire",
+ "ai.toolbar_generate": "Générer",
+ "ai.toolbar_rewrite": "Réécrire",
+ "ai.toolbar_toolbox": "Toolbox",
+ "ai.improve": "Améliorer",
+ "ai.fix_spelling": "Corriger l'orthographe",
+ "ai.shorter": "Raccourcir",
+ "ai.longer": "Allonger",
+ "ai.simplify": "Simplifier",
+ "ai.professional": "Ton professionnel",
+ "ai.casual": "Ton décontracté",
+ "ai.translate_to": "Traduire en {lang}",
+ "ai.explain": "Expliquer",
+ "ai.summarize": "Résumer",
+ "ai.continue": "Continuer",
+ "ai.to_list": "Convertir en liste",
+ "ai.to_table": "Convertir en tableau",
+ "ai.frontmatter": "Générer frontmatter",
+ "ai.to_canvas": "Convertir en Canvas",
+ "ai.custom_rewrite": "Réécriture personnalisée",
+ "ai.no_api_key": "Aucune clé API configurée",
+ "ai.select_text": "Sélectionnez du texte à traiter",
+ "ai.done": "AI: texte traité",
+
+ "dashboard.welcome": "Bienvenue dans ObsiGate",
+ "dashboard.stats": "Statistiques",
+ "dashboard.bookmarks": "Favoris",
+ "dashboard.conflicts": "Conflits",
+ "dashboard.recent": "Récents",
+ "dashboard.total_files": "Fichiers totaux",
+ "dashboard.total_tags": "Tags uniques",
+ "dashboard.total_size": "Taille totale",
+ "dashboard.vaults_count": "Vaults",
+ "dashboard.no_bookmarks": "Aucun favori",
+ "dashboard.no_conflicts": "Aucun conflit",
+ "dashboard.no_recent": "Aucun fichier récent",
+
+ "config.title": "Configuration",
+ "config.profile": "Profil utilisateur",
+ "config.profile_desc": "Préférences personnelles pour personnaliser l'expérience ObsiGate.",
+ "config.lang_label": "Langue préférée",
+ "config.lang_hint": "Utilisée par l'autocomplétion et le ghost text.",
+ "config.display_name": "Nom d'affichage",
+ "config.save": "Enregistrer",
+ "config.logout": "Déconnexion",
+ "config.saved": "✓ Sauvegardé",
+ "config.about": "Ă€ propos",
+ "config.about_loading": "Chargement...",
+ "config.about_error": "Erreur de chargement",
+ "config.ai_keys": "Clés API IA",
+ "config.ai_keys_desc": "Configurez les clés API pour l'éditeur IA.",
+ "config.hidden_files": "Fichiers cachés",
+ "config.hidden_files_desc": "Gérez l'affichage des fichiers et dossiers cachés.",
+ "config.webhooks": "Webhooks",
+ "config.webhooks_desc": "Notifications HTTP vers des services externes lors des changements de fichiers.",
+ "config.shares": "Partages publics",
+ "config.shares_desc": "Gérez les liens de partage public de vos documents.",
+ "config.backups": "Sauvegardes",
+ "config.backups_desc": "Gérez les sauvegardes automatiques de vos fichiers.",
+ "config.diagnostics": "Diagnostics",
+ "config.diagnostics_desc": "Informations techniques sur l'état du serveur.",
+ "config.reindex": "Réindexer",
+ "config.reindex_desc": "Reconstruire l'index de recherche pour toutes les vaults.",
+ "config.client_config": "Configuration client",
+ "config.server_config": "Configuration serveur",
+
+ "help.title": "Aide ObsiGate",
+ "help.shortcuts": "Raccourcis clavier",
+ "help.search_operators": "Opérateurs de recherche",
+ "help.markdown": "Syntaxe Markdown",
+ "help.about": "Ă€ propos",
+
+ "auth.login_title": "Connexion",
+ "auth.username": "Nom d'utilisateur",
+ "auth.password": "Mot de passe",
+ "auth.login_button": "Se connecter",
+ "auth.remember_me": "Se souvenir de moi",
+ "auth.login_error": "Identifiants invalides",
+ "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",
+ "share.revoke": "Révoquer",
+ "share.expires": "Expire le",
+ "share.never": "Jamais",
+ "share.copied": "Lien copié !",
+
+ "backup.title": "Sauvegardes",
+ "backup.restore": "Restaurer",
+ "backup.delete": "Supprimer",
+ "backup.diff": "Comparer",
+ "backup.purge": "Purger",
+ "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",
+ "graph.depth": "Profondeur",
+ "graph.filter_tag": "Filtrer par tag",
+ "graph.filter_type": "Filtrer par type",
+ "graph.focus_mode": "Mode focus",
+ "graph.back": "Retour",
+ "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...",
+
+ "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",
+ "common.cancel": "Annuler",
+ "common.close": "Fermer",
+ "common.save": "Enregistrer",
+ "common.delete": "Supprimer",
+ "common.confirm": "Confirmer",
+ "common.yes": "Oui",
+ "common.no": "Non",
+ "common.search": "Rechercher",
+ "common.create": "Créer",
+ "common.edit": "Éditer",
+ "common.rename": "Renommer",
+ "common.move": "Déplacer",
+ "common.copy": "Copier",
+ "common.copied": "Copié !",
+ "common.download": "Télécharger",
+ "common.refresh": "Rafraîchir",
+ "common.back": "Retour",
+ "common.next": "Suivant",
+ "common.previous": "Précédent",
+ "common.all": "Tous",
+ "common.none": "Aucun",
+ "common.actions": "Actions",
+
+ "toast.saved": "Sauvegardé",
+ "toast.deleted": "Supprimé",
+ "toast.created": "Créé",
+ "toast.renamed": "Renommé",
+ "toast.moved": "Déplacé",
+ "toast.copied": "Copié !",
+ "toast.error_rename": "Impossible de renommer cet élément",
+ "toast.error_delete": "Erreur de suppression",
+ "toast.error_load": "Erreur de chargement",
+ "toast.error_network": "Erreur réseau",
+ "toast.reindex_started": "Réindexation en cours...",
+ "toast.reindex_done": "Réindexation terminée",
+ "toast.index_reloaded": "Index complet rechargé",
+ "toast.installed": "ObsiGate installé avec succès !",
+ "toast.not_found_tree": "Élément introuvable dans l'arborescence",
+ "toast.config_saved": "Configuration sauvegardée",
+ "toast.config_client_saved": "Paramètres client sauvegardés",
+ "toast.config_reset": "Configuration réinitialisée",
+
+ "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",
+ "file.delete": "Supprimer",
+ "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"
+}