Files
ObsiGate/_batch_i18n.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

191 lines
6.5 KiB
Python

"""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)}")