mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-08 21:02:12 +02:00
233 lines
9 KiB
Python
233 lines
9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
"""Generate TrustGraph pre-built language packs using the local Ollama translate model.
|
|
|
|
This script translates a curated set of UI/CLI strings (keys) from English into
|
|
all supported languages and writes JSON packs under:
|
|
trustgraph-base/trustgraph/i18n/packs/
|
|
|
|
Design goal: no runtime translation. Packs are generated ahead of time and
|
|
loaded via `trustgraph.i18n`.
|
|
|
|
Requires:
|
|
- Ollama running locally (default: http://localhost:11434)
|
|
- Model available (default: translategemma:12b)
|
|
|
|
Usage:
|
|
python generate_i18n_packs.py
|
|
|
|
You can override:
|
|
OLLAMA_URL, OLLAMA_MODEL
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Dict, List, Tuple
|
|
|
|
# Reuse the hardened translation engine we already use for docs.
|
|
import translate_docs
|
|
|
|
|
|
DEFAULT_OUTPUT_DIR = Path("trustgraph-base/trustgraph/i18n/packs")
|
|
|
|
# 10 languages (including English)
|
|
LANGS: Dict[str, Tuple[str, str]] = {
|
|
"en": ("English", "en"),
|
|
"es": ("Spanish", "es"),
|
|
"sw": ("Swahili", "sw"),
|
|
"pt": ("Portuguese", "pt"),
|
|
"tr": ("Turkish", "tr"),
|
|
"hi": ("Hindi", "hi"),
|
|
"he": ("Hebrew", "he"),
|
|
"ar": ("Arabic", "ar"),
|
|
"zh-cn": ("Chinese (simplified)", "zh-cn"),
|
|
"ru": ("Russian", "ru"),
|
|
}
|
|
|
|
|
|
# Curated strings currently used by tg-verify-system-status.
|
|
# Keep keys stable.
|
|
EN_STRINGS: Dict[str, str] = {
|
|
"cli.verify_system_status.title": "TrustGraph System Status Verification",
|
|
"cli.verify_system_status.phase_1": "Phase 1: Infrastructure",
|
|
"cli.verify_system_status.phase_2": "Phase 2: Core Services",
|
|
"cli.verify_system_status.phase_3": "Phase 3: Data Services",
|
|
"cli.verify_system_status.phase_4": "Phase 4: User Interface",
|
|
"cli.verify_system_status.summary": "Summary",
|
|
"cli.verify_system_status.checking": "Checking {name}...",
|
|
"cli.verify_system_status.checking_attempt": "Checking {name}... (attempt {attempt})",
|
|
"cli.verify_system_status.failed_timeout": "{name}: Failed (timeout after {attempt} attempts)",
|
|
"cli.verify_system_status.pulsar_not_responding": "Pulsar is not responding - other checks may fail",
|
|
"cli.verify_system_status.checks_passed": "Checks passed: {passed}/{total}",
|
|
"cli.verify_system_status.checks_failed": "Checks failed: {failed}/{total}",
|
|
"cli.verify_system_status.total_time": "Total time: {elapsed}",
|
|
"cli.verify_system_status.system_healthy": "System is healthy!",
|
|
"cli.verify_system_status.system_failing": "System has {failed} failing check(s)",
|
|
|
|
"cli.verify_system_status.check_name.pulsar": "Pulsar",
|
|
"cli.verify_system_status.check_name.api_gateway": "API Gateway",
|
|
"cli.verify_system_status.check_name.processors": "Processors",
|
|
"cli.verify_system_status.check_name.flow_blueprints": "Flow Blueprints",
|
|
"cli.verify_system_status.check_name.flows": "Flows",
|
|
"cli.verify_system_status.check_name.prompts": "Prompts",
|
|
"cli.verify_system_status.check_name.library": "Library",
|
|
"cli.verify_system_status.check_name.workbench_ui": "Workbench UI",
|
|
|
|
"cli.verify_system_status.pulsar.healthy": "Pulsar healthy ({clusters} cluster(s))",
|
|
"cli.verify_system_status.pulsar.status": "Pulsar returned status {status_code}",
|
|
"cli.verify_system_status.pulsar.timeout": "Pulsar connection timeout",
|
|
"cli.verify_system_status.pulsar.cannot_connect": "Cannot connect to Pulsar",
|
|
"cli.verify_system_status.pulsar.error": "Pulsar error: {error}",
|
|
|
|
"cli.verify_system_status.api_gateway.responding": "API Gateway is responding",
|
|
"cli.verify_system_status.api_gateway.status": "API Gateway returned status {status_code}",
|
|
"cli.verify_system_status.api_gateway.timeout": "API Gateway connection timeout",
|
|
"cli.verify_system_status.api_gateway.cannot_connect": "Cannot connect to API Gateway",
|
|
"cli.verify_system_status.api_gateway.error": "API Gateway error: {error}",
|
|
|
|
"cli.verify_system_status.processors.found": "Found {count} processors (≥ {min})",
|
|
"cli.verify_system_status.processors.only": "Only {count} processors running (need {min})",
|
|
"cli.verify_system_status.processors.metrics_status": "Metrics returned status {status_code}",
|
|
"cli.verify_system_status.processors.error": "Processor check error: {error}",
|
|
|
|
"cli.verify_system_status.flow_blueprints.found": "Found {count} flow blueprint(s)",
|
|
"cli.verify_system_status.flow_blueprints.none": "No flow blueprints found",
|
|
"cli.verify_system_status.flow_blueprints.error": "Flow blueprints check error: {error}",
|
|
|
|
"cli.verify_system_status.flows.responding": "Flow manager responding ({count} flow(s))",
|
|
"cli.verify_system_status.flows.error": "Flow manager check error: {error}",
|
|
|
|
"cli.verify_system_status.prompts.found": "Found {count} prompt(s)",
|
|
"cli.verify_system_status.prompts.none": "No prompts found",
|
|
"cli.verify_system_status.prompts.error": "Prompts check error: {error}",
|
|
|
|
"cli.verify_system_status.library.responding": "Library responding ({count} document(s))",
|
|
"cli.verify_system_status.library.error": "Library check error: {error}",
|
|
|
|
"cli.verify_system_status.ui.responding": "Workbench UI is responding",
|
|
"cli.verify_system_status.ui.status": "UI returned status {status_code}",
|
|
"cli.verify_system_status.ui.timeout": "UI connection timeout",
|
|
"cli.verify_system_status.ui.cannot_connect": "Cannot connect to UI",
|
|
"cli.verify_system_status.ui.error": "UI error: {error}",
|
|
}
|
|
|
|
|
|
_FMT_RE = re.compile(r"\{[a-zA-Z0-9_]+\}")
|
|
|
|
|
|
def _protect_format_placeholders(text: str) -> Tuple[str, Dict[str, str]]:
|
|
"""Replace `{name}` placeholders with CODE-like placeholders that the doc translator preserves."""
|
|
|
|
mapping: Dict[str, str] = {}
|
|
|
|
def repl(m: re.Match[str]) -> str:
|
|
idx = len(mapping)
|
|
token = f"⟦CODE_{idx}⟧"
|
|
mapping[token] = m.group(0)
|
|
return token
|
|
|
|
protected = _FMT_RE.sub(repl, text)
|
|
return protected, mapping
|
|
|
|
|
|
def _restore_format_placeholders(text: str, mapping: Dict[str, str]) -> str:
|
|
out = text
|
|
for token, original in mapping.items():
|
|
out = out.replace(token, original)
|
|
return out
|
|
|
|
|
|
def _validate_placeholders(restored: str, originals: List[str]) -> bool:
|
|
return all(ph in restored for ph in originals)
|
|
|
|
|
|
def translate_pack(client: translate_docs.OllamaClient, *, lang_name: str, lang_code: str) -> Dict[str, str]:
|
|
keys = list(EN_STRINGS.keys())
|
|
|
|
protected_lines: List[str] = []
|
|
placeholder_maps: List[Dict[str, str]] = []
|
|
placeholder_originals: List[List[str]] = []
|
|
|
|
for k in keys:
|
|
text = EN_STRINGS[k]
|
|
protected, mapping = _protect_format_placeholders(text)
|
|
protected_lines.append(protected + "\n")
|
|
placeholder_maps.append(mapping)
|
|
placeholder_originals.append(list(mapping.values()))
|
|
|
|
translated = translate_docs.translate_lines_resilient(
|
|
client,
|
|
lines=protected_lines,
|
|
target_language_name=lang_name,
|
|
target_language_code=lang_code,
|
|
retries=2,
|
|
)
|
|
|
|
if translated is None:
|
|
raise RuntimeError(f"Translation failed for {lang_code}")
|
|
|
|
out_lines = translated.splitlines()
|
|
if len(out_lines) != len(keys):
|
|
raise RuntimeError(f"Unexpected line count for {lang_code}: {len(out_lines)} != {len(keys)}")
|
|
|
|
pack: Dict[str, str] = {}
|
|
for i, k in enumerate(keys):
|
|
restored = _restore_format_placeholders(out_lines[i], placeholder_maps[i])
|
|
|
|
# Ensure python-format placeholders survived
|
|
if not _validate_placeholders(restored, placeholder_originals[i]):
|
|
raise RuntimeError(f"Placeholder lost for key {k} in {lang_code}")
|
|
|
|
pack[k] = restored
|
|
|
|
return pack
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
default=str(DEFAULT_OUTPUT_DIR),
|
|
help=f"Output directory for packs (default: {DEFAULT_OUTPUT_DIR})",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
output_dir = Path(args.output_dir)
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
client = translate_docs.OllamaClient(
|
|
url=os.getenv("OLLAMA_URL", translate_docs.DEFAULT_OLLAMA_URL),
|
|
model=os.getenv("OLLAMA_MODEL", translate_docs.DEFAULT_OLLAMA_MODEL),
|
|
num_ctx=translate_docs.DEFAULT_NUM_CTX,
|
|
num_predict=translate_docs.DEFAULT_NUM_PREDICT,
|
|
temperature=translate_docs.DEFAULT_TEMPERATURE,
|
|
top_p=translate_docs.DEFAULT_TOP_P,
|
|
repeat_penalty=translate_docs.DEFAULT_REPEAT_PENALTY,
|
|
keep_alive=translate_docs.DEFAULT_KEEP_ALIVE,
|
|
)
|
|
|
|
# Always write English as the source pack
|
|
(output_dir / "en.json").write_text(json.dumps(EN_STRINGS, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
|
|
for lang, (name, code) in LANGS.items():
|
|
if lang == "en":
|
|
continue
|
|
|
|
print(f"\n=== Generating pack: {lang} ({name}) ===")
|
|
pack = translate_pack(client, lang_name=name, lang_code=code)
|
|
(output_dir / f"{lang}.json").write_text(
|
|
json.dumps(pack, ensure_ascii=False, indent=2) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
print("\nDone.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|