KB Hungarian i18n + HU/EN toggle — Implementation Plan
For agentic workers: execute task-by-task via subagent-driven development. Each task ends with a runnable
unittestcheck. NO git commits (house rule) and NO code comments (house rule). Steps use- [ ].
Goal: A site-wide HU/EN toggle; Hungarian default; UI chrome + cluster labels + generated articles get Hungarian, with English fallback. Source manuals stay English.
Architecture: Shared lang cookie is the single source of truth. Flask dynamic pages localize via a string table + label_hu; the static mkdocs-material library toggles via mkdocs-static-i18n + Material’s language selector; article bodies get .hu.md siblings produced through the existing draft→review→publish gate.
Design spec: 2026-07-28-kb-hungarian-i18n-design. Related: 2026-07-24-knowledgebase, telep-mainframe.
Global Constraints
- All work on telep-mainframe as
levander;/usr/bin/sshonly. App restart:sudo systemctl restart knowledgebase. - venv:
/home/levander/knowledgebase/venv/. Tests:cd /home/levander/knowledgebase && venv/bin/python -m unittest <module>(pytest not installed). kb-vectors tests:cd /home/levander/kb-vectors && venv/bin/python -m unittest(or system python as that pkg uses). claude -p --model opusonly, serialized, fail-loud, never publish empty. Binary:/home/levander/.local/bin/claude.- No git commit. No code comments. Back up any file before first edit (
.bak-i18n). - English is always the safe fallback — a missing HU value must never 500.
Task 1: Flask lang cookie + string table + /semantics toggle
Files:
- Create:
/home/levander/knowledgebase/i18n.py - Modify:
/home/levander/knowledgebase/app.py(SEMANTICS_HTML,semantics(), add/lang/<code>route +current_lang()helper) - Test:
/home/levander/knowledgebase/test_i18n.py(new) + updatetest_semantics.py
Interfaces produced:
i18n.STRINGS: dict[str, dict[str,str]]with keys"hu"and"en"; every key present in both.i18n.strings(lang) -> dict— returnsSTRINGS.get(lang, STRINGS["hu"]).app.current_lang() -> str—request.cookies.get("lang")if in("hu","en")else"hu".
Build:
i18n.py:STRINGS = {"hu": {...}, "en": {...}}. Seed keys from the existing hardcoded Hungarian inapp.py(their current Hungarian becomes thehuvalue; add Englishen). At minimum for this task:semantics_title(“Szemantika — kapcsolódó szakaszok” / “Semantics — related sections”),sources_badge(“forrás”/“sources”),generate_btn(“Generál összefoglalót”/“Generate summary”),no_clusters(“Nincs klaszter. Futtasd:”/“No clusters. Run:”),back_kb(”← Vissza a tudásbázishoz”/”← Back to the knowledgebase”),summary_fmt(“%d klaszter, ebből %d több kézikönyvet érint” / “%d clusters, %d span multiple manuals”), pluslang_toggle_other_label/href.app.py: addimport i18n;current_lang();GET /lang/<code>→ validate code in("hu","en"),resp = redirect(request.referrer or "/"),resp.set_cookie("lang", code, max_age=31536000, samesite="Lax"), return resp. EditSEMANTICS_HTML: replace hardcoded Hungarian with{{S.key}}; render label as{{ (c.label_hu if lang=='hu' else c.label) or c.label }}; add a header toggle<a href="/lang/en">EN</a> | <a href="/lang/hu">HU</a>. Insemantics():lang = current_lang(); S = i18n.strings(lang); summary = S["summary_fmt"] % (len(clusters), cross); passS=S, lang=langtorender_template_string.
Steps:
- Back up
app.py→app.py.bak-i18n. - Write
test_i18n.py: assertset(STRINGS["hu"]) == set(STRINGS["en"])(parity);strings("en")["generate_btn"]is English;strings("zz")falls back to hu. - Run:
venv/bin/python -m unittest test_i18n→ FAIL (no i18n.py). - Create
i18n.py; rerun → PASS. - Edit
app.py(i18n import,current_lang,/lang/<code>, SEMANTICS_HTML,semantics()). - Update
test_semantics.py: default request → Hungarian title present; request withCookie: lang=en(viatest_client().get("/semantics", headers={"Cookie":"lang=en"})) → English title; cluster label falls back tolabelwhenlabel_huabsent (no 500). - Run:
venv/bin/python -m unittest test_semantics test_i18n→ PASS. -
sudo systemctl restart knowledgebase; curl/semanticsand/semanticswith-b lang=en; confirm HU vs EN chrome./lang/ensets cookie (check-D-).
Deliverable: /semantics toggles HU/EN chrome; labels still English (until Task 2); default HU.
Task 2: Cluster label Hungarian translation (kbclust translate-labels)
Files:
- Create:
/home/levander/kb-vectors/translate_labels.py - Modify:
/home/levander/kb-vectors/kbclust.py(addtranslate-labelssubcommand) - Test:
/home/levander/kb-vectors/test_translate_labels.py
Interfaces produced: each cluster in clusters.json gains top-level label_hu (string). Snapshot clusters.pre-hu.json written before overwrite.
Build (mirror judge.py):
translate_labels.py:build_prompt(labels: list[str]) -> str(instruct: translate each English automotive topic label to natural Hungarian; return ONLY a JSON array of{index, label_hu}; keep it a concise noun phrase);parse_reply(text) -> list[dict](extract JSON array, tolerate code fences, raise on failure);_run_claude(prompt) -> str(copy judge.py’s idiom:subprocess.run(["/home/levander/.local/bin/claude","-p","--model","opus"], input=prompt, capture_output=True, text=True, timeout=300), raise on rc!=0/empty);main(batch_size=20): load clusters, snapshot toclusters.pre-hu.json, batch labels, call claude, maplabel_huback by index,cluster.write_clusters(clusters). Fail-safe: a failed/unparseable batch leaves those clusters’label_huabsent (UI falls back to English) — report count.kbclust.py: addtranslate-labelssubparser (--batch, default 20) →import translate_labels; translate_labels.main(batch_size=args.batch).
Steps:
- Write
test_translate_labels.py:parse_replyextracts a JSON array with/without fences and raises on junk; a stubbed batch mapslabel_huonto the right cluster by index; a failed batch leaveslabel_huabsent (no crash). - Run unittest → FAIL; implement; rerun → PASS.
- Live run:
python kbclust.py translate-labels; confirmclusters.pre-hu.jsonsnapshot exists and clusters now havelabel_hu(spot-check 3 for sane Hungarian). Report raw→translated count. - Verify
/semantics?lang=hunow shows Hungarian labels andlang=enEnglish (Task 1 already wired the fallback).
Deliverable: cluster labels bilingual end-to-end on /semantics.
Task 3: Static library toggle (mkdocs-static-i18n + Material selector)
Files:
- Modify:
/home/levander/knowledgebase/mkdocs.yml - Modify (venv):
pip install mkdocs-static-i18n - Verify:
build.py build_site()still succeeds;/renders.
Build:
venv/bin/pip install mkdocs-static-i18n.mkdocs.yml: back up first. Add toplugins:thei18nplugin withdocs_structure: suffix,languages: [{locale: en, default: true, name: English}, {locale: hu, name: Magyar, build: true}],fallback_to_default: true. Add Material language selector viaextra.alternate(link/for hu-default) OR rely on the plugin’s built-in selector — pick whichever the installed version supports (checkpip show mkdocs-static-i18nversion and its README behavior). Ensuresearchstill listed.- Confirm
gen_index.py-generatedindex.mdstill builds under both locales (English structure is fine; it’s the fallback default).
Steps:
-
pip install;pip show mkdocs-static-i18n(record version). - Back up
mkdocs.yml→.bak-i18n; edit. -
cd /home/levander/knowledgebase && venv/bin/python -c "import build; ok,e=build.build_site(); print(ok,e[:400])"→ expectTrue. - Confirm
site/now has the language trees; curl/and an English-only manual page → renders (English fallback) under default; Material language selector present in header. -
sudo systemctl restart knowledgebasenot needed (static), but confirmsite()route serves the new tree.
Deliverable: the manual library + Material chrome carry a HU/EN selector; English-only manuals fall back cleanly.
Risk gate: if build_site() fails or the selector misbehaves, restore mkdocs.yml.bak-i18n, rebuild, report BLOCKED — do not leave the live site/ broken.
Task 4: Article Hungarian translation (draft→review→publish as .hu.md)
Files:
- Create:
/home/levander/knowledgebase/transprompt.py - Modify:
/home/levander/knowledgebase/gen.py(translate_article, submit/worker reuse),kbgen.py(addtranslate <slug>subcommand),app.py(optionalPOST /translate/<slug>+ a trigger in/draftsor/semantics) - Test:
/home/levander/knowledgebase/test_transprompt.py, updatetest_gen_routes.pyif a route is added.
Build (mirror gen.generate_draft/publish):
transprompt.py:build_prompt(article_md) -> str— rules: translate all prose to natural Hungarian; reproduce EVERY number/unit/torque/part-number/wire-color and every[<manual_id>]source tag and everyref verbatim; keep markdown structure and the# <title>; output only the translated markdown.gen.py:translate_article(slug) -> hu_slug— read the published EN article md (docs/consolidated/<slug>/001-<slug>.md), strip the ASCII banner,claude -p --model opus(same subprocess idiom, timeout 900, fail loud), writedrafts/<slug>-hu/draft.hu.md+ copy the EN article’s images +meta.json({slug, lang:"hu", source_slug:slug, state:"draft", speccheck:[...]}); runspeccheck.check(hu_md, en_md)(numbers must survive). Reuse the worker/queue via asubmit_translate(slug).- Publish path: extend
gen.publish(or addpublish_translation) so a-hudraft writesdocs/consolidated/<slug>/001-<slug>.hu.md(sibling of the EN001-<slug>.md, somkdocs-static-i18nserves it under/hu/), copy images if missing,build_site(), delete draft. Prepend the HU banner (“AI-forditas. Ellenorizd a forras-kezikonyvek alapjan.”). kbgen.py:translate <slug>,publish <slug>-hualready covered by existingpublish.- app.py (optional MVP+):
POST /translate/<slug>enqueuesgen.submit_translate; a “Fordítás” link where consolidated articles are listed (or drive from CLI for v1 — YAGNI the static-page button).
Steps:
- Write
test_transprompt.py: prompt contains the verbatim-numbers + keep-[manual_id]-tags + keep-image-refs rules and the source markdown. - Run unittest → FAIL; implement
transprompt.py; rerun → PASS. - Implement
gen.translate_article+ publish-translation; addkbgen translate. - Live e2e:
python kbgen.py translate engine-cooling→ draftdrafts/engine-cooling-hu/draft.hu.md; read it against the EN: same[manual_id]tags, same torque/spec numbers verbatim, natural Hungarian prose;speccheckclean. Report an honest faithfulness read. - Publish it; confirm
docs/consolidated/engine-cooling/001-engine-cooling.hu.mdexists,build_site()ok, and/hu/consolidated/engine-cooling/…renders Hungarian while/en/…stays English. -
venv/bin/python -m unittest(full suite) → all pass; source manuals untouched.
Deliverable: consolidated articles translatable to Hungarian through the review gate; served under /hu/.
Self-review notes
- Spec coverage: Task1=UI chrome+toggle+cookie; Task2=labels; Task3=static library+Material chrome; Task4=article bodies. Manuals = English fallback (Task 3). All spec scope covered.
- Type consistency:
label_hu(Task2) consumed by Task1’s/semantics;current_lang()/i18n.stringsnames stable across tasks. - Ordering safest-first: 1 (pure Flask, reversible) → 2 (claude label pass, snapshot) → 3 (static build, risk-gated) → 4 (claude article pass, review-gated).