Building an AI Website Editor That Speaks Hebrew First: RTL Constraints That Shape the Stack
- Direction belongs to the document
- Two input languages, one output contract
- Address elements by id, not by text
- Show the model the real page
- Small outputs are easier to trust
- Reversibility as a language-neutral safety net
- What this post doesn’t claim
- Takeaways
By Zahi Raifer
At Orivox, a site owner builds and edits a website by chatting. They can write in Hebrew or in English, and the site the system produces has Hebrew built in from the first render. That sounds like a localization feature. In practice it works more like an architectural constraint, because several decisions I’d now defend on general grounds started as answers to one question: how do you let a language model edit a right-to-left site without breaking it?
This post covers those decisions. It isn’t about generating RTL markup on the server. It’s about what changes once an LLM sits between the owner and the HTML.
Direction belongs to the document
The first rule is the simplest one. Every generated site declares lang and dir on the root element, and every page loads a font with full Hebrew coverage. Python sets this when the site is built, outside the model’s free-form output. The model produces the page’s HTML and Tailwind in one pass from a short brief: what the business does, the main call to action, a phone or WhatsApp number. Python then handles the parts that have to be exact, and document direction is one of them.
The reason is predictability. If direction lives on the root, every later edit inherits it. A new section, a moved card or a rewritten hero doesn’t need to restate which way the text runs. The shared header, footer and head scripts follow the same logic: they’re partials with include markers, so document-level settings live in one place and can’t drift between pages.
Two input languages, one output contract
An owner may type “make the FAQ cards dark” in English or ask for the same thing in Hebrew. The editing pipeline doesn’t care which. The request goes to the planner, and the planner never writes files. It returns a list of operations, and Python applies them with BeautifulSoup.
That contract is what keeps a bilingual chat manageable. The owner’s language affects how the planner understands the request. It has no effect on what reaches the disk. An operation list is the same shape whether the request came in Hebrew or English, so it can be validated, logged and replayed the same way. The audit trail doesn’t have a Hebrew branch.
Address elements by id, not by text
Every element in a generated page carries a short, unique data-ai-id, and every edit targets an element by that id, for example [data-ai-id='a0f0'].
For a Hebrew-first site this matters more than it first seems. If the model has to find “the second paragraph under the pricing heading” by matching text, every edit depends on reproducing Hebrew strings exactly as they appear in the page. With an id, the model reads the page, picks the element, and names it with a short ASCII token. The Hebrew content stays where it is unless the operation is explicitly about changing that text. “Make the FAQ cards dark” turns into a few class changes on the cards, and nothing else on the page moves.
Ids also make moves safe. One of the save-time checks is that moved text is preserved: when a paragraph is split or relocated, every sentence survives. For content the owner wrote carefully in their own language, losing a sentence would be a serious failure, and this check means it can’t happen silently.
Show the model the real page
The planner works from a full, predictable context assembled the same way on every turn. The page is part of it, sent as raw HTML with header and footer inlined.
We benchmarked raw HTML against a compact JSON summary of the page on real edit requests. Raw HTML gave the same understanding at about half the tokens, and the planner sees every element on the page. Only very large pages switch to the summary automatically. For an RTL site there’s a side benefit: the actual markup, including the document’s lang and dir, is in front of the model on every turn instead of being abstracted away.
Markup still doesn’t tell you everything about how a page looks. The planner has a tool to look at a screenshot of the rendered page, so it can check visual layout rather than reasoning only from source. It can also read another page of the site before editing it, and one of the guards requires that a page be read before it’s written. That way, cross-page edits are based on the page as it currently is.
Small outputs are easier to trust
Across 24 benchmark runs, the planner’s output had a median of 141 tokens. That’s a handful of operations, not a rewritten page. This is the most useful property of the design for a bilingual product. A small, targeted diff on a page full of Hebrew copy is easy to check and easy to reverse. A full regenerated page would force someone to compare every line of the site’s text to make sure nothing changed by accident.
When a whole-page rewrite is genuinely needed, the planner has a tool for it. Even then, a guard checks that every section the owner didn’t name stays exactly as it was. Changing the hero leaves the rest of the page alone.
Reversibility as a language-neutral safety net
Every edit turn is its own Git commit, and the original site is committed as a baseline before the first edit. Undo is a Git restore, so it doesn’t depend on reconstructing what the page used to say. If an edit produces something the owner doesn’t like, in either language, going back doesn’t require the model to recall the previous wording. The previous version is in the project’s history.
The contact form’s privacy notice is protected the same way. A guard keeps the legally required notice next to the form, whatever the model proposes.
What this post doesn’t claim
The source material I’m working from is specific about a few Hebrew-related facts: the root declares lang and dir, pages load a font with full Hebrew coverage, and owners write in Hebrew or English. It says nothing about bidirectional text handling inside mixed Hebrew/English content, mirrored layout utilities, number or phone formatting, or how the build prompt instructs the model on RTL. I’ve left those out rather than guess. They deserve their own write-up once the details are documented.
Takeaways
- Put direction and language on the document root and set them in code, not in model output.
- Make the model’s output a structured operation list, so input language has no effect on what reaches disk.
- Address elements by stable ids so edits never depend on matching non-Latin text.
- Give the model the real markup and a rendered screenshot.
- Keep diffs small and make every turn a commit, so reviewing and reverting are cheap in any language.
Zahi Raifer is a software developer with 23 years of experience in AI development, Python, .NET and databases, and the founder of Orivox.
Disclosure: this article was drafted with AI assistance and reviewed by the author.
- Reference: https://www.orivox.pro/
Write a comment