Localization (i18n)
Ship a Studio app in more than one language. When localization is on, the
generator emits a message catalog + a locale switcher, and routes the
app's user-facing strings - nav labels, screen titles, the New button, and
grid column headers - through a reactive t() translator.
Turn it on
In the visual designer, open the inspector with no block
selected and expand Localization (i18n). Tick Emit a message catalog +
locale switcher, list your locales (comma-separated, e.g. en, es, fr),
and pick the default. The config lives on the project model too:
const project = { /* ... */, i18n: { enabled: true, locales: ['en', 'es', 'fr'], defaultLocale: 'en' } }
What gets generated
src/lib/i18n.ts - the whole localization layer:
import { writable, derived } from 'svelte/store'
export type Locale = 'en' | 'es' | 'fr'
export const currentLocale = writable<Locale>('en')
const messages: Record<Locale, Record<string, string>> = {
en: { 'nav.customers': 'Customers', 'screen.customers': 'Customers', 'col.customers.name': 'Name', /* ... */ },
es: {}, // fill these in - missing keys fall back to the default locale
fr: {},
}
export const t = derived(currentLocale, ($l) => (key: string, fallback?: string) =>
messages[$l]?.[key] ?? messages.en?.[key] ?? fallback ?? key)
export function localizeCols(cols, entity, translate) { /* headers via col.<entity>.<field> */ }
The default locale is seeded from your schema + screen labels, so en
(or whichever you chose) is complete out of the box. The other locales start
empty - fill them in with the same keys. Every generated page imports t and
renders labels as {$t('key', 'fallback')}, so a missing translation gracefully
falls back to the default, then the original label.
The keys
| Key | What it labels |
|---|---|
nav.<screenId> |
A navigation link. |
screen.<screenId> |
A screen's <h1> title. |
new.<entity> |
The + New button. |
col.<entity>.<field> |
A grid column header (localized via localizeCols). |
Translating
Open src/lib/i18n.ts and fill in each non-default locale with the same keys:
es: {
'nav.customers': 'Clientes',
'screen.customers': 'Clientes',
'new.customers': '+ Nuevo Cliente',
'col.customers.name': 'Nombre',
'col.customers.email': 'Correo',
},
The locale switcher in the app shell writes currentLocale, and everything
- titles, nav, headers - re-renders reactively. Persist the choice (a cookie or
localStorage) in your+layoutif you want it to stick across visits.
Numbers and dates already localize. The grid formats currency, numbers, and dates via
Intl, so those follow the browser locale without a catalog. i18n here covers the text labels.
Custom strings
t is a normal Svelte store, so use it anywhere in your own components:
<script lang="ts">
import { t } from '$lib/i18n'
</script>
<button>{$t('actions.export', 'Export')}</button>
Add the key to each locale in the catalog and it's covered.
See also
- The visual designer - where you enable localization
- The EntitySchema - where field labels (the seed) come from
- Theming - the other half of white-labeling an app