Skip to content

Translations

Every string the admin panel renders comes from a key. Seven locales ship with it — English, Spanish, German, French, Italian, Portuguese and Hindi — and a project can override any key, add a language of its own, or translate its own components against the same table.

Three things do the work:

<Rebase locale> which language to start in
<Rebase translations> what any key says, per locale
useTranslation() reading a key from your own component
<Rebase client={client} authController={authController} locale="es">
<RebaseCMS collections={collections}/>
<RebaseShell title="My App"/>
</Rebase>

locale is the initial language. A reader who picks one from the language menu has that choice remembered in their browser, and it wins over the prop on their next visit — changing locale in code does not overrule someone’s setting. Anything Rebase has no string for falls back to English rather than rendering a key.

translations is keyed by locale, then by key. Partial: name the handful you want to change and everything else stays as it was.

<Rebase
client={client}
authController={authController}
translations={{
en: { save: "Publish", delete_confirmation_title: "Unpublish?" },
es: { save: "Publicar", delete_confirmation_title: "¿Despublicar?" }
}}
>
<RebaseCMS collections={collections}/>
<RebaseShell title="My App"/>
</Rebase>

The same shape adds a language Rebase does not ship. Give it a full set of keys and set locale to it:

<Rebase
client={client}
authController={authController}
locale="nl"
translations={{ nl: { save: "Opslaan", cancel: "Annuleren" } }}
>
<RebaseCMS collections={collections}/>
<RebaseShell title="My App"/>
</Rebase>

Keys you leave out fall back to English, so a partial set is a working translation, not a broken one.

The whole table is RebaseTranslations — one interface with every key on it, so your editor completes them and a typo is a type error. Its English values live in packages/app/src/locales/en.ts, which is the reference for what each key actually says.

Names follow the surface they belong to: save, cancel, delete_confirmation_title for the panel, studio_* for the developer tools (studio_tool_sql, studio_group_database, studio_backups_denied_title).

useTranslation() gives you the same table your custom views, fields and actions can read:

import { useTranslation } from "@rebasepro/app";
function PublishButton() {
const { t } = useTranslation();
return <Button>{t("save")}</Button>;
}

Interpolation uses the same {{name}} syntax as the built-in strings:

t("add_to_field", { fieldName: "Tags" });

Your own keys go through translations like any others — a key Rebase does not declare is still resolved, so you can put your component’s strings in the same table rather than running a second i18n stack beside it.

t also returns the key itself when nothing matches, which is the honest answer and makes a missing key visible in the UI rather than blank.

An AppView’s group is an identifier: the drawer collapses by it, the Studio home page orders by it, and navigationGroupMappings attaches icons to it. Translating it where it is declared breaks all three in every language but the one you translated into.

Declare the group in English and let the header translate itself — Rebase’s own groups have studio_group_* keys and are looked up by name; a group of your own renders as written.

// Right: the name is stable, the heading is localized for the built-in groups.
{ slug: "queues", name: "Queues", group: "Compute", view: <QueuesView/> }