Localize Prompts with Your Localization System¶
Store a prompt as a Text ID in the localization system you already ship, and have Unity or Unreal resolve it to plain text before the agent is created. The server still receives a string. There is no Tryll string table.
1. Tick Localized and set the table¶
On a workflow asset, set Text Table to the table that holds your prompts.
On each prompt field, tick Localize text. That covers a system prompt, a
template, an instruction, acknowledge text, a TTS delivery instruction, a
ClassifyIntent entry's prompt, a tool description, and on Classify Attributes
the system prompt, the statement, and each choice prompt. A canned response
line and an intent-to-instruction line are the same kind of field: each entry
has its own text, and that text can be a Text ID. The box is then a Text ID,
for example tobin.system. The inline text stays in the asset; only the
resolved string is sent.
Leave the box unticked when that field should stay inline. A Summarize
template that already says Write in {{language}} is a good candidate to
leave inline: the session language fills the key, and the template itself
does not need a translation.
Give prompt authors their own table, and keep it out of machine translation. A prompt is instructions to a model, not UI copy. Tryll does not enforce that split.
2. Point the project at a text provider¶
The provider answers GetText(table, id) and reports the game's current
language. When one is set, that language is the session language unless you
pass a locale into CreateSession. An empty string passed in still means
no session language.
Unity¶
Assign a TryllTextProviderAsset under Project Settings → Tryll Client →
Text Provider. The same asset is used in the editor preview and in Play.
- Unity Localization (
com.unity.localization), when that package is installed: create a Unity Localization Text Provider. Tryll reads the raw table entry. Turn Smart String formatting off for prompt entries.{}placeholders and Mustache{{ }}must not be nested in the same string; a Smart String would format the braces before the server sees the Mustache tags. - JSON Text Provider, for a demo or a prototype: one JSON
TextAssetper language, each a flat object{ "id": "text" }. Lookup tries the current locale, then the language subtag (de-DEthende), then the first other file that has the key. That last step uses another language, so it logs a warning once per key. A file that is not valid JSON logs an error with the line and column, and the inspector shows the same reason. CallSetLocale("de")to switch at runtime; the asset's Locale field is the start language and does not change in Play mode. - Your own class: implement
ITryllTextProvider. A code-only provider works in Play. The inspector preview also needsITryllTextProviderEditor(ListLocales,ListIds,GetTextForPreview,OpenInEditor). Without that, the field says it has no editor support.
A provider set on TryllClient.TextProvider from code wins over the settings
asset. Setting that property to null turns resolution off even when the
settings asset has a provider.
Raise LocaleChanged on the main thread. Tryll sends requests from that event.
I2 Localization, in about ten lines:
public sealed class I2TryllTextProvider : TryllTextProviderAsset
{
public override string CurrentLocale => I2.Loc.LocalizationManager.CurrentLanguageCode;
public override Task<string> GetTextAsync(string table, string id)
{
string text = I2.Loc.LocalizationManager.GetTranslation(id);
return Task.FromResult(string.IsNullOrEmpty(text) ? null : text);
}
void OnEnable() => I2.Loc.LocalizationManager.OnLocalizeEvent += RaiseLocaleChanged;
void OnDisable() => I2.Loc.LocalizationManager.OnLocalizeEvent -= RaiseLocaleChanged;
}
Shared snippets ({{text.some_id}} inside a prompt) are the provider's job
in this version. Resolve them before you return the string.
Unreal¶
Project Settings → Tryll Client → Text Provider is None by default, so
the Tryll Locale field sets the session language, as before. Pick
UTryllStringTableTextProvider to resolve Text IDs from Unreal String Tables.
It reads FText::FromStringTable and follows the game culture. While it is
set, that culture is the session language and wins over the Locale field.
Pick the workflow's Text Table with the String Table asset picker. The
asset reference makes the cooker package the table, and Tryll loads it before
the first lookup, so a table that nothing else uses still works in a packaged
game. A custom UTryllTextProvider subclass may ignore the table and use only
the key. Implement GetText and
GetCurrentLocale, and call UTryllSubsystem::NotifyTextLocaleChanged when
the language changes. Override SupportsEditorPreview when the details panel
should show a preview.
Test a runtime language switch with Play → Standalone Game, not in the
viewport. Play in Editor shares one culture with the editor, so
SetCurrentCulture switches the editor's own language too, and the editor
loads no game translations unless Localization.ForceLoadGameLocalizationInEditor
is set.
C++, Python, and Java¶
Those clients have no provider seam. Resolve the string in your game and assign it:
3. Check the texts before you play¶
Under a ticked field the inspector shows a key list (typing a new key is allowed), Open, and a language switcher.
- Found — the first lines of the entry, with
{{ }}marked. - Missing in this language — red. A fallback from another language is shown and marked as fallback. Language fallback is the provider's job; Tryll does not invent one at send time.
- Missing in some languages — yellow. Check texts is the full matrix across every language the provider lists.
- Empty ID — red, "Enter a Text ID".
- No editor provider — grey. The runtime provider has no preview API.
Check texts on the workflow runs every Text ID against every language the provider lists. A missing cell is red.
Creation fails on the client before anything is sent when a ticked field has
an empty ID, no provider is set, the provider returns no text, or the
provider throws. The error names the asset, node, field, table, and ID
(InvalidParamValue, 3007). There is no silent fallback to the inline string.
ChangeParams follows the same rules. Params with a ticked field are
resolved first, so you can change a Text ID at runtime. With an asynchronous
provider, the change can reach the server a frame later.
A Dialog Lab override on a ticked field is the text under test: it wins, and the field is unticked for that run.
Resolved text is plain text. The server's existing checks still apply:
Mustache, {{intents_block}}, and the rest.
4. Change language on a live agent¶
When the provider raises its language-changed event, Tryll sets the session
locale and re-resolves ticked fields that are not structural, then sends
ChangeAgentParam for each live agent. A turn that is still running returns
AgentBusy (3004); the update is sent when the turn ends, before your
turn-complete handler runs. AgentBusy here is expected and is not reported
as an error.
- Your runtime changes stay. The update starts from the agent's current
params, so values you set with
ChangeParamsare kept. Only nodes whose text changed are sent. - One agent is all or nothing. If any Text ID of an agent is missing in
the new language, nothing is sent to that agent, and it keeps its previous
prompts in full. Unity raises
TryllClient.LocalizedTextFailed; Unreal broadcastsOnLocalizedTextFailedon the Tryll subsystem. The session language has already changed, so recreate the agent or switch back. - A server rejection is not rolled back. Each node's update is its own request. If the server rejects one, that node keeps its previous prompt, while nodes the server accepted already run the new language. The same failed event fires once for the agent, with the first rejection.
- Wait for the update before work that must see it. Unity raises
TryllClient.LocalizedTextApplied; Unreal broadcastsOnLocalizedTextAppliedon the Tryll subsystem. It fires once per agent, when the server has acknowledged every prompt that changed, or right away when none changed. A busy agent gets it after its turn ends. If the language changes again before the acknowledgements arrive, only the latest change fires it. Start a KV prefill, a conversation reset, or your ownChangeParamsfrom this event, not from the language change itself: those run against the old prompts if they reach the server first. - An agent created during the switch is updated as soon as its creation completes.
- An explicit session language wins. If you passed a locale to
CreateSession, a later provider change does not touch the session language. Prompts still follow the provider.
| Reaches a live agent | Needs the agent recreated |
|---|---|
Voice language and the {{language}} keys, via the session locale |
A ticked ClassifyIntent entry prompt (the intents list is structural) |
| Ticked non-structural prompt fields, from the next turn. That includes an attribute's system prompt, statement, and choice prompt | A tool description (the tools list is structural) |
| A canned line or an intent instruction. Those lists are structural, so the Text ID is resolved when the agent is created | |
| Files already bound (a knowledge base, a string-storage file) | |
| A voice input that is already open. Close it and open it again |
There is no restart API. Recreating an agent to pick up structural text or files that are already bound is the same save-and-restore you already use for dialog state: export the dialog, destroy the agent, create it again, and import. See Save and Load Agent State.