Ship an Agent in Another Language¶
Everything Tryll offers for localization, in the order you will need it: choose languages your models can serve, set one language for the session, ship translated content files, and make the model actually write in that language.
The language setting does not translate anything
Setting a language makes an NPC speak German. It does not make the model write German — that comes from your prompts, your content files, and the model you chose. Sections 4 and 5 are the load-bearing ones; skip them and you get a German voice reading English words.
1. Choose languages your models can serve¶
Open Window ▸ Tryll ▸ Model Manager (Unity) or the Model Manager panel (Unreal), connect, and register the models you ship. Each model's detail pane lists the languages it declares and whether it covers your project language.
Registering caches those languages into the project's model manifest — which is why the language picker and the inspector warnings keep working with no server running.
Three facts the catalog will tell you, which decide your shippable locale set:
- Supertonic 3 speaks 31 languages from one loaded model. Switching costs nothing at runtime.
- Pocket TTS — the voice-cloning family — is English-only. Cloned character voices cannot go multilingual.
- The shipped streaming STT choices cover English, bilingual zh-en, and Russian. Other languages use an offline recognizer.
The voice stack caps your locale set, not the language model. A 1–4B model reads almost anything; your microphone and speaker decide what you can ship.
From code
ListModels reports a languages list per model. A language is offerable
only if every model in your pipeline covers it, so the answer is an
intersection — TryllLanguages (Unity) and UTryllLanguages (Unreal,
Blueprint-callable) do that for you:
var (models, err) = await TryllClient.Instance.RequestListModelsAsync();
var codes = TryllLanguages.Offerable(models,
"Supertonic 3 (int8)", "Whisper Base Multilingual (int8)");
foreach (var code in codes)
dropdown.options.Add(new Dropdown.OptionData(
TryllLanguages.DisplayName(code))); // "de" → "German"
A model that declares nothing is skipped rather than treated as
restrictive — undeclared means "the catalog didn't say", not "supports
nothing". Use HasLanguageInfo to tell "no overlap" apart from "no data"
before showing a player an empty menu.
2. Set the language¶
Pick it in Project Settings ▸ Tryll Client ▸ Language. The list comes from
your registered models, annotated with how many catalog models can serve each
one — German (de) — 7 models — so you can spot a language that hangs off a
single model before committing to it. New projects start on English.
Every agent created afterwards uses it automatically, including agents created later in the session.
From code, you can override per session:
Tags are BCP-47: de, de-DE, pt-BR, zh-Hans-CN. A malformed tag is
rejected with InvalidLocale (2004) rather than silently ignored.
Changing it mid-session¶
| Follows the change, from each agent's next turn | Does not |
|---|---|
| Speech-output language on every agent | A knowledge base already bound to a node |
The {{language}} template keys |
Canned lines already bound to a node |
| Ticked non-structural prompt fields (system prompt, template, instruction, acknowledge text) | A ticked ClassifyIntent entry prompt — that list is structural, so recreate the agent |
| File resolution for the next load | A voice input already open |
Ticked prompts are resolved by your localization system. See Localize prompts with your localization system.
The right-hand column is structural — those parameters are fixed at agent creation. So a live switch is the right tool for the voice and prompt half; changing authored content still means recreating agents. Most games treat a language change as "set it, then reload the scene", which gets both halves.
It is also not turn-atomic: switch mid-sentence and that one line may be written in the old language and spoken in the new one. Send it between turns.
A live switch does not re-check existing agents. If their TTS model does not
declare the new language, no 3017 is raised — the voice just speaks with the
model's own handling of that tag. Check coverage before switching live (for
example with TryllLanguages.Offerable).
3. Ship translated content files¶
Translations live in a loc/<language>/ folder beside the file you link.
StorageData/
deathonset/
lore.json ← you link this; it is the default content
canned.txt
loc/
de/lore.json ← used when the session runs in German
pt-BR/lore.json
shared/
intent_ids.json ← no loc/ folder = language-neutral, on purpose
Resolution, most specific first:
deathonset/loc/pt-BR/lore.json ← used if it exists
deathonset/loc/pt/lore.json ← else if it exists
deathonset/lore.json ← else: the file you linked
Three consequences worth internalising:
- You always link the default file. Never a path inside
loc/. The graph names one path and the shipped folder decides which language answers. If you browse to a translation by mistake, the editor offers to link the default instead. - A folder with no
loc/is language-neutral. That is the whole declaration — no flag, no metadata. Intent IDs, guardrail patterns and other config just sit there and are shared by every language. - Fallback is per file. Ship
loc/de/lore.jsonbeforeloc/de/canned.txtexists and only the knowledge base switches. A partially translated game works.
This covers every file-backed parameter: knowledge bases and their
.usearch/.bm25 sidecars, canned responses, guardrail patterns, intent maps,
voice-input hotwords, and the voice profile's reference clips (including each
delivery mode's clip).
loc is a reserved folder name
Do not use loc/ for your own content inside a storage folder. A project
that wants symmetry may also ship loc/en/, but nothing requires it — the
linked file is the default.
You may not need to translate knowledge bases at all
With a multilingual embedding model, Retrieve and ClassifyIntent match a
German question against an English knowledge base, so the model only has to
read English and write German. That removes most of the translation
cost. It only helps the dense leg — BM25 cannot match across languages, so
Hybrid retrieval degrades to dense-only.
4. Make the model write the language¶
The generated half has no string to translate, so it needs a prompt strategy. Three options, in increasing order of quality:
a. Ask, from the template. Three keys are available wherever
Mustache rendering is supported — on Generate, GenerateAndSpeak, ToolCall
and Transform:
| Key | Renders |
|---|---|
{{language}} |
German — the English language name, which models follow best |
{{locale}} |
de-DE — the full tag |
{{language_code}} |
de — the bare code |
The section collapses to nothing when no language is set, so the same template works unlocalized.
b. Author the prompt in the target language. Writing the system prompt in
German reduces mid-response drift far more reliably than an English prompt
asking for German. Tick Localized on that field and store the text in
your localization system — see
Localize prompts with your localization system.
The loc/ lookup in §3 applies to file-backed storage params. Inline prompt
text on the workflow is sent as authored until you tick Localized.
c. Use a different model per language. A node's model_name can differ per
locale. A model genuinely strong in the target language beats prompt engineering
on a weak one.
The criticality gradient. Critical narrative lines → CannedResponse +
Speak with fully translated text (zero model risk). Constrained barks → a
per-language grammar. Ambient chatter → free generation. This lets you ship a
locale where the model is mediocre without shipping mediocre important lines.
5. Voice in and out¶
TTS. Every agent's voice profile uses the
session language as its lang default. A profile that sets its own Lang
still wins — use that only to override one character (a French witness in a
German playthrough), never to set the game's language. Fixed-language voices
(Pocket TTS, Orpheus, Maya1) reject a non-empty Lang; leave it empty.
STT. A multilingual recognizer (Whisper, Canary) receives the language as a hint. A recognizer whose weights fix its language ignores it: with STT the model choice is the language choice.
Speaking a language the recognizer was not told about
A wrong language hint does not fail loudly — Whisper returns confident nonsense. This is why Tryll only passes the hint to models whose catalog declares that language, and refuses the combination outright rather than guessing.
6. Errors you will see¶
| Error | Means | Fix |
|---|---|---|
InvalidLocale (2004) |
The tag is not language[-script][-region] |
Use de, de-DE, pt-BR |
ModelLanguageUnsupported (3017) |
A voice or voice input would rely on the session language but its model does not declare it | Pick a covering model, set the voice profile's own lang, or change the session language |
3017 is raised when a speaking agent's voice is set (at agent creation or
SetAgentVoice) and at CreateVoiceInput for STT — the same mismatch the
inspector warned about, at the point it starts to matter. Three deliberate
exemptions: a voice with its own lang is never checked (you opted out of the
session language), a model that declares no languages is never checked
(undeclared means unknown, not unsupported), and an agent whose graph never
speaks is never checked.
7. Never translate these¶
Translating identifiers breaks routing silently rather than loudly:
intents_ids · slot names (output_name, input) · node names · exit names
(*_exit) · variable names and __MARKER__ names · storage names · model
catalog names · tool names and parameter names.
Related: branch on variables or intent IDs, never on literal model output —
BranchParams.values matched against text is a localization trap.
What to expect¶
- Token cost rises. Non-English text runs roughly 2–4× the tokens for the
same content, which changes
context_sizesizing, when history starts being trimmed, and per-turn latency. A graph tuned in English can degrade in Japanese with no visible cause. - Writing quality drops before reading quality does. Small on-device models understand a language long before they write it well. If prose is poor, the fix is §4b or §4c — not more instructions.
- Most studios ship the AI feature in fewer locales than the game. That is a legitimate answer, and usually the right one.