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
191 lines
6.5 KiB
Python
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)}")
|