Localizing Responses¶
Response localization allows your assistant to reply in the user's language. The key idea: store a translation key with format arguments, so the translated template is resolved first, then the arguments are injected into it.
# Without localization — hardcoded language:
Response(text=f"Hello, {name}!")
# gives "Hello, Mark!"
# With localization — deferred:
Response(text=LocalizableString("greeting", "fr", name=str(name)))
# Localizer resolves "greeting" for "fr" → "Bonjour, {name}!"
# Then formats → "Bonjour, Mark!"
This matters because argument positions and surrounding text differ between languages — you can't just translate a pre-formatted string.
LocalizableString¶
LocalizableString stores a key, a language code, and format arguments. At response time, Localizer.localize() looks up the key in localizable.strings for the given language, then calls .format(**arguments) on the resolved template.
from stark.general.localisation import LocalizableString
LocalizableString("greeting", "ru", name="Mark")
# .string = "greeting" — the key
# .language_code = "ru" — which translation to use
# .arguments = {"name": "Mark"} — injected after translation
If the key is not found, localize() emits a RuntimeWarning and falls back to the raw key string.
Using in Commands¶
Response.text and Response.voice accept both plain str and LocalizableString:
@manager.new({
"base": "hello $name:Word",
"ru": "привет $name:Word",
})
async def greet(name: Word) -> Response:
lang = name.value.language_code # from the parsed input
return Response(
text=LocalizableString("greeting_response", lang, name=str(name)),
voice=LocalizableString("greeting_response", lang, name=str(name)),
)
Resolving at Response Time¶
The core framework stores LocalizableString as-is in the Response object — it does not resolve it automatically. Resolution happens at the delegate level (e.g., VoiceAssistant provided by stark), where the Localizer is available:
# In your custom delegate / response handler:
if isinstance(response.text, LocalizableString):
text = localizer.localize(response.text)
else:
text = response.text
This keeps the core framework decoupled from any specific output target — the same Response can be rendered differently by a voice assistant (TTS), a chat UI, or a logging system.
Fallback Behavior¶
If the key is not found in localizable.strings for the requested language, localize() falls back to:
- The
baselanguage strings - The raw key string itself (with a
RuntimeWarning)
String Bundles¶
Response strings use the same .strings bundle format and directory structure as pattern localization. The localizable.strings files are the output counterpart to recognizable.strings:
strings/
en/
localizable.strings ← response strings
recognizable.strings ← pattern strings
ru/
localizable.strings
recognizable.strings
See Localizing Parsing for the full bundle format reference.
Formatting Complex Values with PyICU¶
For formatting locale-sensitive values like numbers, dates, units, and currencies in responses, PyICU is a great companion library. It wraps the ICU C++ library (the same engine behind iOS/Swift's Foundation formatting) and provides ready-made locale-aware formatting for:
- Numbers — decimal, percent, currency, and spelled-out (e.g.,
"five","пять") - Dates/Times — locale-specific patterns, relative dates (
"yesterday","in 2 days") - Units —
"5 kilometers","3 lbs","2 hours"with localized names - Messages — pluralization and gender rules (
"{num, plural, one {# item} other {# items}}")
PyICU is not a dependency of S.T.A.R.K — use it alongside when you need locale-aware value formatting in your responses. See Command Response for more examples of response building with formatted values.