Internationalization
Internationalization covers more than replacing one sentence with another. Citry's built-in i18n extension coordinates:
- translated messages and accessible labels;
- locale fallback;
lang, left-to-right, and right-to-left output;- numbers, percentages, currencies, dates, times, lists, and units;
- strict parsing of localized form input; and
- optional browser-owned translations inside one subtree.
The extension has three modes:
- with no settings and no message assets, it is dormant;
- a registered component
messagesormessages_fileasset activates server-side source mode; and - engine settings activate selectable locales, catalog packages, named formats, parsing, and optional browser switching.
Source mode needs no application settings and adds no browser code. It exists so a reusable component can translate its own defaults through the ordinary tr() API without forcing every application to configure i18n.
Configure the locales your application supports
Give the built-in extension a source locale and an ordered set of selectable locales:
from citry import Citry
app = Citry(
extensions_defaults={
"i18n": {
"source_locale": "en-US",
"default_locale": "en-US",
"locales": ("en-US", "cs-CZ", "ar-EG"),
},
},
)
Citry checks the complete configuration when it creates the engine. Invalid locale names, duplicate canonical names, an unknown default locale, and cycles in the fallback graph are errors.
Write source messages beside the component
The messages asset contains Fluent source for the component. Keep it below the template, JavaScript, and CSS:
from citry import Component
class AccountCard(Component):
class I18n:
messages_locale = "en-US"
class Kwargs:
name: str
citry = app
template = """
<article>
<h2>{{ tr("my-app-account-greeting", name=name) }}</h2>
</article>
"""
messages = """
# @param {str} $name - User name.
my-app-account-greeting = Welcome, { $name }.
"""
messages_locale says which language the component's defining Fluent source is written in. Defining a message asset makes the engine-wide registered source catalog available: another registered component may call this public message ID even when AccountCard is not rendered.
tr() always returns text, so the template escapes it normally. Citry reads the message ID, variables, and @param types and checks literal calls against that interface.
The inline block is the defining source. Put translator-owned locales in a catalog package.
Pass the locale into the render
For selectable locales, create a context from an explicit request value, then provide that context to the root render:
from citry.ext.i18n import make_context
def render_account_page(locale: str):
context = make_context(app, locale=locale)
return AccountPage().render(
provides={"citry_i18n": context},
)
This rule keeps each render predictable. A component rendered separately inside template_data() starts another tree and does not silently take the caller's locale. Pass the context to that render when it should use the same locale.
Inside a component, use self.i18n. Outside a component, create a service for one explicit context:
i18n = app.extensions.get_extension("i18n")
service = i18n.for_context(context)
text = service.tr("my-app-account-greeting", name="Ada")
Neither call changes process-wide or task-wide state.
Choose server-owned or browser-owned text
Use server rendering by default:
<h1>{{ tr("my-app-account-title") }}</h1>
The result is ordinary HTML. If the browser later switches locale, it does not know that this text came from tr() and does not rewrite it.
Use $i18n only for a control that genuinely needs to change in place:
<c-i18n tag="section" client>
<h1 x-text="$i18n.tr('my-app-account-title')"></h1>
<button @click="$i18n.switchLocale('cs-CZ')">ΔeΕ‘tina</button>
</c-i18n>
For a page-wide language change, send the new locale as a URL, form, cookie, or other explicit request input and render the page again. This updates all server-owned content and avoids making initial page interactivity wait for a large number of Alpine expressions.
Continue by user need
- Locales and context explains configuration, canonical locale names, fallback, and subtree providers.
- Write messages covers Fluent syntax and typed variables.
- Organize catalogs covers application-wide translations and installable catalog packages.
- Rich messages shows how translators can position application-owned links and inline components without writing HTML.
- Format values and parse localized input cover locale-sensitive data.
- Browser i18n explains
$i18n, loading, and subtree switching. - Language direction and accessibility covers
lang,dir, fallback language, and bidirectional text. - Translation workflow covers project checks and catalog commands.
- Production and deployment covers compiled catalog packages, browser partitions, and cache identity.