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