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

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