Translations¶
The admin speaks English unless you choose another language. These ship with it, each translated in full, and the layout mirrors for languages written right to left:
| Language | language |
A browser set to it gets it |
|---|---|---|
| English | en |
en, en-US, en-GB and any other English |
| فارسی, Persian | fa |
fa, fa-IR |
| Русский, Russian | ru |
ru, ru-RU |
| Español, Spanish | es |
es, es-ES, es-MX, es-419 and any other Spanish |
| Português (Brasil), Brazilian Portuguese | pt-BR |
pt-BR, pt, and pt-PT until European Portuguese ships |
| 日本語, Japanese | ja |
ja, ja-JP |
| Deutsch, German | de |
de, de-DE, de-AT, de-CH |
| Français, French | fr |
fr, fr-FR, fr-CA, fr-BE |
| Türkçe, Turkish | tr |
tr, tr-TR |
| العربية, Arabic | ar |
ar, ar-SA, ar-EG, ar-AE and any other Arabic |
| 简体中文, Simplified Chinese | zh-Hans |
zh-CN, zh-SG, zh and zh-Hans; never zh-TW, zh-HK or zh-MO |
| 繁體中文, Traditional Chinese | zh-Hant |
zh-TW, zh-HK, zh-MO and zh-Hant; never zh-CN, zh-SG or zh |
admin = Admin(engine, views=[OrderView], language="fa")
In Persian or Arabic, every page reads from right to left, with the sidebar on the right.
The examples on this page use the example models.
Let people choose¶
Offer more than one language, and the account menu at the foot of the sidebar lets people choose:
admin = Admin(engine, language="en", languages=["fa"])
The first visit follows the browser's Accept-Language, and a choice made in the menu is kept for
a year. language is the default for a browser that asks for neither.
adminsite translates its own texts, not yours: your labels stay as you wrote them in every language. Offer several languages where your team can read your labels in each.
Your own words¶
Your views' labels, groups and field labels are yours, so write them in the language you want:
class OrderView(ModelView[Order]):
label = "سفارش"
label_plural = "سفارشها"
fields = [Order.id, EnumField(Order.status, label="وضعیت", choices=STATUSES)]
To change one of adminsite's own texts, or to add a language it does not ship, pass
translations, keyed by the English texts:
admin = Admin(
engine,
language="it",
translations={
"it": {
"Save": "Salva",
"Cancel": "Annulla",
"Search {things}": "Cerca {things}",
},
"fa": {"Save": "ثبت"},
},
)
Your translations win over the ones adminsite ships, and a text with no translation stays in
English. Keep the {placeholders} as they are, though their place in the sentence may move.
A text with a count has a translation for each plural category of its language, as the Unicode CLDR names them, such as one, few and many for Russian. It is keyed by the English for one:
admin = Admin(
engine,
language="ru",
translations={
"ru": {
"{count} row": {
"one": "{count} строка",
"few": "{count} строки",
"many": "{count} строк",
},
},
},
)
English works the same way, so one of the admin's words can change without copying a template, as for a project that signs people in with their email address:
admin = Admin(engine, auth=auth, translations={"en": {"Username": "Email"}})
In your own code, such as a hook's refusal or an action's message, use the same function, and add
the text to translations:
from adminsite.i18n import gettext as _
raise RefusedError(_("Shipped orders cannot be changed."))
For a text with a count, give the English for one and for any other number, and the count:
from adminsite.i18n import ngettext
def shipped(count: int) -> str:
return ngettext("{count} order shipped.", "{count} orders shipped.", count)
Time zones¶
Every time is shown on the clock of the person reading it, in the time zone their browser is set to, and a time typed in is read on the same clock. Dates and times of day stay as they are.
Until the browser has said which zone it is in, and for a request with an API token, times are in
timezone, which is UTC unless you choose. More zones listed in timezones show in the account
menu:
admin = Admin(engine, timezone="Asia/Tehran", timezones=["Europe/Paris", "Asia/Tehran"])
A datetime the database keeps without a zone is taken to be in UTC. A database that keeps local times says which zone:
admin = Admin(engine, database_timezone="Asia/Tehran")
A column declared DateTime(timezone=True) keeps its zone. In your own code, such as a dashboard
card that counts today's orders, current_timezone is the zone of the person reading:
from datetime import datetime
from adminsite.timezones import current_timezone
today = datetime.now(current_timezone.get()).date()
Add a language to adminsite¶
The catalogs live in src/adminsite/locales, one JSON file per language. To list every text a
catalog is missing:
uv run python -m tests.messages de
It prints a JSON object of the missing texts, ready to fill in, with a place for each plural form. The test suite checks that every shipped catalog translates every text and keeps every placeholder.