Skip to content

Internationalization & Timezones

With starlette-admin, you can localize UI strings per user and convert displayed datetimes to the viewer's local timezone, independently of how your database stores them.

Full example: For a complete, working application that demonstrates internationalization and timezones, see examples/10-i18n-timezone in the GitHub repository.

Install the i18n extra

Translation support requires Babel. Without it, the admin still works, but it falls back to English and skips locale-aware date and 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, which runs the admin in English. When you pass an I18nConfig, the admin installs LocaleMiddleware. The middleware resolves a locale on every request and exposes it to templates and to the translation functions (gettext and lazy_gettext) throughout your field and view code.

The language_switcher parameter adds a dropdown to the admin navigation bar so users can select their locale. Leave it as None, the default, to hide the switcher and rely 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 this order:

  1. Cookie: The value of language_cookie_name, if it matches a built-in supported locale.
  2. Header: The Accept-Language header, or the header you set in language_header_name, subject to the same validity check.
  3. Default: The default_locale, when neither the cookie nor the header matches.

The built-in supported locales (starlette_admin.i18n.SUPPORTED_LOCALES) are German, English, French, Portuguese, Russian, Turkish, and both Simplified and Traditional Chinese. When a user selects a language in the navigation bar, the switcher writes the language cookie, so the choice persists across requests without 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 isn't None by default. If you omit it, the Admin class constructs a TimezoneConfig() for you, so timezone conversion is on out of the box. The admin treats naive datetimes as the database_timezone, which defaults to "UTC", and displays them to every user in "UTC", the default default_timezone, unless the user selects another one.

How the timezone is detected

TimezoneMiddleware resolves the timezone once per request:

  1. Cookie: The value of timezone_cookie_name, which is "timezone" by default, if it's present.
  2. Default: The default_timezone, otherwise.

The navbar timezone switcher writes this cookie, exactly like the language switcher. Timezones have no equivalent of the Accept-Language header because browsers don't send one, so detection relies entirely on cookies. Client-side JavaScript that reads Intl.DateTimeFormat().resolvedOptions().timeZone typically sets the cookie, as does the switcher itself.

How field values are converted

DateTimeField and ArrowField convert values between the database_timezone and the viewer's resolved timezone.

  • Reading (list, detail, export): The admin treats a naive value from your database as a value in the database_timezone and converts it to the viewer's timezone before formatting it.
  • Writing (create and edit forms): The admin treats a submitted value as a value in the viewer's timezone and converts it to the database_timezone before it reaches your model.

As a result, two admins in different timezones can edit the same row and each see times in their own local time, while the database keeps 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.

To restrict what users can select, pass a shorter timezone_switcher list. To enforce a single timezone for every viewer, set timezone_switcher to None and set default_timezone directly, for example to a company-wide "Europe/Paris".

The navbar switcher renders each timezone's display name and UTC offset with the get_timezone and get_timezone_display_name template globals. The Templates guide documents these globals and all the others.


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.