The first people I wanted to put Garu in front of were my family, and they live on Gmail in Spanish. An agent that sorts their inbox and drafts replies in English would be worse than no agent. So “add Spanish” could not mean a language menu. It had to mean that every sentence a person reads, every note an agent writes, every line in the terminal and every guide comes out in Spanish, and that it stays that way as the product grows.
One rule for every sentence
Every sentence in the interface goes through one function, with the English sentence as the key and the Spanish in a dictionary next to it. The key is a whole sentence, never a fragment, because fragments glue together differently in each language. Plurals get one key per form. Agent names, tool ids, file paths and commands are never wrapped, so they read the same in both languages and never get “translated” by accident.
t("Waiting for you to approve one action")
t("{n} new since yesterday", { n })
// es.ts
"Waiting for you to approve one action": "Esperando que apruebes una acción",
"{n} new since yesterday": "{n} nuevos desde ayer",The test that fails on a missing sentence
A dictionary drifts the day after it is finished. So a test scans the source for every call to that function and fails on three things: a sentence with no Spanish, a Spanish entry whose English no longer exists, and a translation that lost a placeholder. The same test runs against the terminal’s own dictionary. A new feature cannot ship in English only, because the build says no.
A translation is not done when the buttons change. It is done when the agent writes to you in your language, and when the test fails the first time someone forgets.
Agents that write in Spanish
The part that mattered most was not in the interface. An agent’s file can say language: es. The runtime then hands the model the date spelled out in that language and tells it to write everything meant for its owner in Spanish, and to answer an email in the language the email was written in. Tomay, the morning brief, and Bea, the inbox sorter, ship a Spanish edition beside the English one, with Gmail labels in Spanish and the words that mark an automated sender in Spanish too: no-responder, notificaciones, avisos. A rule that blocks replies to addresses nobody reads has to know what those addresses are called.
The terminal and the guides
The command line has the same layer, following the computer’s language unless told otherwise, and the same test. Each guide has a Spanish twin with a link at the top of both, so nobody lands on a page in the wrong language with no way out. Even the setup for Google, the longest guide, exists twice.
One codebase, three screens
The control room is a Svelte application. The same code, built once more with a phone flag, is packaged with Capacitor as the Android and iPhone app. The phone keeps its own preferences, including the language, so a parent can pin Spanish on their phone without changing anything on the computer that runs the agents. Phone screens never show a command; they say “this needs setup on your computer” and leave the commands to the desktop.
Times are human in both languages, “in 25 min” rather than a timestamp, and dates come out in the person’s language. The one exception is the run timeline, which is the log of one run and keeps its exact stamps.
What I would build first
- Route every sentence through one function, keyed by the whole English sentence.
- Write the test that fails on a missing, stale or broken translation, and run it in the build.
- Give the agent’s file a language, and let the runtime brief the model in it.
- Translate the vocabulary your rules depend on, not just the screens.
- Build the phone app from the same code, with its preferences on the phone.
Limits
The Spanish is mine, and my family has not reviewed all of it yet. The agents write well in Spanish on the model I use, but a different model may need a firmer instruction. And the phone app is still being prepared for its first closed test, so the people this was built for have not held it yet. That is the next thing.
Garu is an independent open-source project under the Apache-2.0 license, unrelated to my employer. Everything in this note comes from its public repository.