Files
ObsiGate/_i18n_final2.py
T
bruno 0403be4516
CI / test (push) Has been cancelled
CI / security (push) Has been cancelled
CI / build (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / lint (push) Has been cancelled
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
2026-06-18 14:18:25 -04:00

160 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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.")