Files
Momento/memento-note/scripts/sync_locales_from_en.py
Antigravity 1fcea6ed7d
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 7s
feat: brainstorm sessions, PDF document Q&A, embedding fixes, and UI improvements
- Add brainstorm feature with collaborative canvas, AI idea generation, live cursors, playback, and export
- Add PDF upload/extraction/ingestion pipeline with pgvector document search (RAG)
- Add document Q&A overlay with streaming chat and PDF preview
- Add note attachments UI with status polling, grid layout, and auto-scroll
- Add task extraction AI tool and agent executor improvements
- Fix NoteEmbedding missing updatedAt column, re-index 66 notes with 1536-dim embeddings
- Fix brainstorm 'Create Note' button: add success toast and redirect to created note
- Fix memory echo notification infinite polling
- Fix chat route to always include document_search tool
- Add brainstorm i18n keys across all 14 locales
- Add socket server for real-time brainstorm collaboration
- Add hierarchical notebook selector and organize notebook dialog improvements
- Add sidebar brainstorm section with session management
- Update prisma schema with brainstorm tables, attachments, and document chunks
2026-05-14 17:43:21 +00:00

164 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
Synchronise locales/*.json avec locales/en.json (référence des clés).
- Garde les chaînes déjà présentes et non vides dans la locale cible.
- Traduit les clés manquantes (Google via deep-translator).
Usage : depuis memento-note/ avec le venv :
.venv-i18n/bin/python scripts/sync_locales_from_en.py
"""
from __future__ import annotations
import json
import sys
import time
from pathlib import Path
from deep_translator import GoogleTranslator
ROOT = Path(__file__).resolve().parents[1]
LOCALES = ROOT / "locales"
# Codes Google Translate pour deep-translator (source=en)
LANG_TARGETS = {
"fr": "fr",
"es": "es",
"de": "de",
"fa": "fa",
"it": "it",
"pt": "pt",
"ru": "ru",
"zh": "zh-CN",
"ja": "ja",
"ko": "ko",
"ar": "ar",
"hi": "hi",
"nl": "nl",
"pl": "pl",
}
def flatten_leaves(obj: dict, prefix: str = "") -> dict[str, str]:
out: dict[str, str] = {}
for k, v in obj.items():
path = f"{prefix}.{k}" if prefix else k
if isinstance(v, dict):
out.update(flatten_leaves(v, path))
elif isinstance(v, str):
out[path] = v
else:
raise TypeError(f"Valeur non supportée à {path}: {type(v)}")
return out
def unflatten_leaves(flat: dict[str, str]) -> dict:
root: dict = {}
for path, val in flat.items():
parts = path.split(".")
cur = root
for p in parts[:-1]:
cur = cur.setdefault(p, {})
cur[parts[-1]] = val
return root
def translate_unique(texts: list[str], target_code: str, batch_size: int = 35) -> dict[str, str]:
translator = GoogleTranslator(source="en", target=target_code)
mapping: dict[str, str] = {}
for i in range(0, len(texts), batch_size):
batch = texts[i : i + batch_size]
try:
outs = translator.translate_batch(batch)
except Exception as e:
print(f" batch erreur ({e}), traduction unitaire…", flush=True)
outs = []
for t in batch:
try:
outs.append(translator.translate(t))
except Exception:
outs.append(t)
time.sleep(0.15)
if len(outs) != len(batch):
outs = batch # fallback
for src, dst in zip(batch, outs):
mapping[src] = dst if isinstance(dst, str) else src
time.sleep(0.6)
print(f"{min(i + batch_size, len(texts))}/{len(texts)}", flush=True)
return mapping
def merge_locale(en_flat: dict[str, str], loc_flat: dict[str, str], target_code: str) -> dict[str, str]:
text_to_keys: dict[str, list[str]] = {}
result: dict[str, str] = {}
for key, en_val in en_flat.items():
loc_val = loc_flat.get(key)
if isinstance(loc_val, str) and loc_val.strip():
result[key] = loc_val
else:
text_to_keys.setdefault(en_val, []).append(key)
if not text_to_keys:
return result
unique = list(text_to_keys.keys())
print(f" {len(unique)} textes uniques à traduire ({sum(len(v) for v in text_to_keys.values())} clés)", flush=True)
trans_map = translate_unique(unique, target_code)
for src, keys in text_to_keys.items():
tr = trans_map.get(src, src)
for k in keys:
result[k] = tr
return result
def main() -> int:
en_path = LOCALES / "en.json"
if not en_path.exists():
print("locales/en.json introuvable", file=sys.stderr)
return 1
en_obj = json.loads(en_path.read_text(encoding="utf-8"))
en_flat = flatten_leaves(en_obj)
print(f"Référence en.json : {len(en_flat)} clés feuilles", flush=True)
skip = {"en"}
for code, google_target in LANG_TARGETS.items():
if code in skip:
continue
path = LOCALES / f"{code}.json"
if not path.exists():
print(f"Absence de {path.name}, ignoré", flush=True)
continue
loc_obj = json.loads(path.read_text(encoding="utf-8"))
loc_flat = flatten_leaves(loc_obj)
before_missing = sum(1 for k in en_flat if k not in loc_flat or not str(loc_flat.get(k, "")).strip())
if before_missing == 0:
print(f"\n=== {code}.json — déjà complet ({len(en_flat)} clés), rien à faire", flush=True)
continue
print(f"\n=== {code}.json ({google_target}) — manquantes avant: {before_missing}", flush=True)
merged_flat = merge_locale(en_flat, loc_flat, google_target)
# Couverture complète des clés en
out_flat = dict(en_flat)
out_flat.update(merged_flat)
missing_after = sum(1 for k in en_flat if k not in out_flat or not str(out_flat[k]).strip())
if missing_after:
print(f"ERREUR: encore {missing_after} clés vides pour {code}", file=sys.stderr)
return 1
tree = unflatten_leaves(out_flat)
path.write_text(json.dumps(tree, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f" Écrit {path.name}", flush=True)
print("\nTerminé.", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())