Skip to content

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.

pip install "starlette-admin[i18n]"
uv add "starlette-admin[i18n]"

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:

  1. Cookie: The value of language_cookie_name, provided it matches a built-in supported locale.
  2. Header: The Accept-Language header (or your configured language_header_name), following the same validity check.
  3. Default: The default_locale is 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:

  1. Cookie: The value of timezone_cookie_name ("timezone" by default), if present.
  2. Default: The default_timezone is 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_timezone before 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:

  • Fields: DateTimeField, ArrowField, and the rest of the field reference.
  • Templates: Override templates and use the get_timezone globals directly.
  • Concepts: How Admin wires up middleware and config objects.