Internationalization & Timezones
Starlette Admin allows you to localize UI strings per user and convert displayed datetimes to the viewer's local timezone, completely independent of your database storage format.
Full Example: You can find a complete, working application demonstrating internationalization & timezone in the GitHub repository at examples/10-i18n-timezone
Install the i18n extra
Translation support requires Babel. Without it, the admin interface still functions but defaults to English without locale-aware date or number formatting.
Set the locale
from sqlalchemy import create_engine
from starlette_admin import I18nConfig
from starlette_admin.contrib.sqla import Admin
from starlette_admin.i18n import SUPPORTED_LOCALES
engine = create_engine("sqlite:///admin.sqlite")
admin = Admin(
engine,
title="My Admin",
i18n_config=I18nConfig(
default_locale="en",
language_switcher=SUPPORTED_LOCALES,
),
secret_key="a-long-random-string",
)
i18n_config defaults to None, running the admin in English. Passing an I18nConfig installs LocaleMiddleware. This middleware resolves a locale on every request and exposes it to templates and translation functions (gettext or lazy_gettext) throughout your field and view code.
The language_switcher parameter adds a dropdown to the admin navigation bar, allowing users to select their preferred locale. Leave it as None (the default) to hide the switcher and rely entirely on default_locale and per-request detection.
I18nConfig Reference
| Attribute | Type | Default | Description |
|---|---|---|---|
default_locale |
str |
"en" |
Locale used when no cookie or header matches a supported locale. |
language_cookie_name |
str | None |
"language" |
Cookie read to detect the user's locale. Set to None to disable. |
language_header_name |
str | None |
"Accept-Language" |
Header read when the cookie is absent. Set to None to disable. |
language_switcher |
list[str] | None |
None |
Locales offered in the navbar switcher. None hides the switcher. |
How the locale is detected
LocaleMiddleware resolves the locale once per request in the following order:
- Cookie: The value of
language_cookie_name, provided it matches a built-in supported locale. - Header: The
Accept-Languageheader (or your configuredlanguage_header_name), following the same validity check. - Default: The
default_localeis used when neither the cookie nor the header matches.
The built-in supported locales (starlette_admin.i18n.SUPPORTED_LOCALES) include German, English, French, Portuguese, Russian, Turkish, and both Simplified and Traditional Chinese. When a user selects a language from the navigation bar, the switcher writes the language cookie. This ensures the choice persists across requests without requiring server-side session storage.
Timezones
from sqlalchemy import create_engine
from starlette_admin import TimezoneConfig
from starlette_admin.contrib.sqla import Admin
engine = create_engine("sqlite:///admin.sqlite")
admin = Admin(
engine,
title="My Admin",
timezone_config=TimezoneConfig(
default_timezone="UTC",
database_timezone="UTC",
timezone_switcher=["UTC", "Europe/Paris", "America/New_York", "Asia/Tokyo"],
),
secret_key="a-long-random-string",
)
Note
Unlike i18n_config, timezone_config is not None by default. The Admin class constructs a TimezoneConfig() automatically if omitted. Timezone conversion is enabled out of the box. Naive datetimes are treated as the database_timezone (defaulting to "UTC") and displayed to all users in "UTC" (the default default_timezone) unless they select an alternative.
How the timezone is detected
TimezoneMiddleware resolves the timezone once per request:
- Cookie: The value of
timezone_cookie_name("timezone"by default), if present. - Default: The
default_timezoneis used otherwise.
The navbar timezone switcher writes this cookie, functioning exactly like the language switcher. There is no Accept-Language equivalent header for timezones since browsers do not send one. Detection relies entirely on cookies. These are typically set by client-side JavaScript reading Intl.DateTimeFormat().resolvedOptions().timeZone or by the switcher itself.
How field values are converted
DateTimeField and ArrowField automatically convert values between the database_timezone and the viewer's resolved timezone.
- Reading (list, detail, export): A naive value retrieved from your database is assumed to be in the
database_timezone. It is converted to the viewer's timezone before formatting. - Writing (create, edit forms): A value submitted through a form is assumed to be in the viewer's timezone. It is converted to the
database_timezonebefore reaching your model.
Because of this workflow, two admins in different timezones can edit the same row and see times in their respective local time zones, while the database maintains a single, consistent timezone.
TimezoneConfig Reference
| Attribute | Type | Default | Description |
|---|---|---|---|
default_timezone |
str |
"UTC" |
Timezone used when no cookie is set. Accepts any IANA timezone name. |
timezone_cookie_name |
str | None |
"timezone" |
Cookie read to detect the viewer's timezone. Set to None to disable. |
database_timezone |
str |
"UTC" |
Timezone your stored datetimes are assumed to be in. |
timezone_switcher |
list[str] | None |
None |
Timezones offered in the navbar switcher. None hides the switcher. |
use_user_locale_timezone |
bool |
True |
Prefer a timezone inferred from the user's locale over default_timezone. |
You can restrict the selectable list by passing a shorter timezone_switcher list. To enforce a single timezone for every viewer, set timezone_switcher to None and define the default_timezone directly (for example, a company-wide "Europe/Paris").
The navbar switcher renders each timezone's display name and UTC offset using the get_timezone and get_timezone_display_name template globals. Full documentation for these, along with all other template globals, is available in the Templates guide.
What's next: