Language & Translations
The built-in texts of the atscript-ui components (buttons, empty states, filter chips, Yes / No, workflow error messages) are UI texts with stable codes: UI_CANCEL, UI_FORM_ADD, and so on. They render in English until you provide a locale and a source of translations.
<!-- App.vue -->
<script setup lang="ts">
import { provideAsLocale } from "@atscript/vue-form";
import { locale, i18n } from "./i18n"; // your locale ref and i18n library
provideAsLocale(
() => locale.value,
(id, params) => (i18n.te(id) ? i18n.t(id, params ?? {}) : undefined),
);
</script>
<template>
<RouterView :key="locale" />
</template>The catalog — AS_UI_MESSAGES
@atscript/ui/messages exports AS_UI_MESSAGES: every built-in text by code, in English, with {name} placeholders (UI_FORM_ADD: "Add {item}"). Use it as the source list for your translation files. The subpath imports nothing (no Vue, no DOM), so it also loads on a server: @atscript/moost-wf registers it with moost's registerMessages on import, an app without workflows registers it itself (Translations on the server). Translate the code and keep the placeholders: "{item} hinzufügen". All codes start with UI_.
provideAsLocale(getter, translate?)
Call it once at the app root (from @atscript/vue-form). The locale drives Intl formatting in forms (Locale & Currency) and the language of the built-in texts of forms, tables and workflow forms. A text is looked up in this order:
- your
translate(id, params, locale): its result is final (fill the params yourself);undefined, or the id itself (what i18n libraries answer for a missing key), falls through; - texts the server sent for the current locale (see below);
- English from
AS_UI_MESSAGES.
Each app has its own texts, so a server rendering many users at once keeps them apart and renders the same text the browser hydrates.
Switching the language. A new locale applies to what renders next. Remount the view to switch an open page: <RouterView :key="locale" />. Table and value-help metadata (/meta) and the labels built from it (boolean Yes / No) are cached per locale, so switching back is instant. Without a remount, a form's value-help picker asks in the new locale from its next search, and a table's filter pickers are rebuilt in it — through the form's or table's client factory, after a provideAsLocale getter change or a setUiLocale call alike. The rest of an open page keeps the locale it mounted in.
In your own components useAsLocale().t reads the same texts:
<script setup lang="ts">
import { useAsLocale } from "@atscript/vue-form";
const { t } = useAsLocale();
</script>
<template>
<button type="button">{{ t("UI_CANCEL") }}</button>
</template>Texts sent by the server
Workflow forms (<AsWfForm> / useWfForm) send Accept-Language with the provided locale and merge the $i18n texts of the form schema the server answers with. See AsWfForm. A moost server with a translator answers in that language — form texts, validation messages, the built-in UI texts and the texts of expressions: see Translations on the server.
Tables, value help and action forms
Since 0.1.165. Tables (<AsTableRoot> / useTable), value-help pickers and the action forms of a table ask their /meta in the provided locale: the library passes locale to your client factory, and the @atscript/db-client Client sends it as Accept-Language. With a translator on the @atscript/moost-db server, labels, action texts and error bodies come back in that language, and the $i18n texts /meta serves are merged into the app's texts — the same way as for workflow forms. An action form merges its own $i18n too (the translated texts of its expressions) when its dialog opens. See Translations (i18n) in the atscript-db docs for the server side.
- A custom client factory spreads the options it gets into the
Client(new Client(url, { ...options, fetch })); one that caches clients keeps one per URL and locale. - Server rendering asks in each request's locale and merges into that render's texts (its
provideAsLocale); nothing is shared between requests. Without a provider, a server render keeps the built-in texts in English: the process-wide texts never take what a request was served. Hand the render's texts to the browser app — see Server rendering. - Table presets and app prefs (
usePresets,useAppPrefs) ask in the app's locale too (since 0.1.168), so their error and preflight messages come back translated.
Server rendering
Since 0.1.168. A server render merges what the server served (/meta$i18n, …) into that request's texts, but the browser app starts with an empty dict until its own requests return — the page would hydrate in English and switch back. Transfer the render's texts with the state your server already hands to the browser:
// entry-server.ts
import { getAsServedTexts } from "@atscript/vue-form";
const ssrContext = {};
const html = await renderToString(app, ssrContext);
const state = { asTexts: getAsServedTexts(ssrContext) };
// inline it escaped: JSON.stringify(state).replace(/</g, "\\u003c")// entry-client.ts
import { provideAsServedTexts } from "@atscript/vue-form";
const { app } = createApp();
provideAsServedTexts(app, window.__SSR_STATE__?.asTexts);
app.mount("#app");provideAsLocale starts with these texts. Only the app's own texts are collected (kept on the render's SSR context, never in module state); a render without provideAsLocale collects nothing.
What hydrates cleanly — the browser's first render matches the server's HTML:
- A text the render read after the server served it (a page that awaits its data during the render — an
async setup,onServerPrefetch) is transferred: both sides show the translation. - A text the render read before the server served it shows the fallback (your
translate, else English) everywhere in that render — also where it is read again after the translation arrived — and is left out: the hydrating browser shows the same fallback, and its own request switches it afterwards. - Several
provideAsLocaleof one locale in an app agree: a text one of them was served is the others' too, on both sides. - Tables, filter pickers and form value-help pickers ask nothing during a server render; their texts arrive after hydration.
Validation messages in the browser
Since 0.1.165. Errors the form finds itself (before anything is sent) are worded in the app's language too. A validator message is looked up by its code (MIN_LENGTH, filled with the error's params), a message of your own by its text: your translate first, then the texts the server served for the locale ($i18n of /meta or of a workflow form), else the English message stays. So a client-side error reads like the server's would:
provideAsLocale(
() => "de",
(id, params) => (id === "MIN_LENGTH" ? `Mindestens ${params?.min} Zeichen` : undefined),
);Errors returned by @atscript/db-client writes (its preflight and the server's 400s) are already in the client's locale.
Without Vue — setUiLocale
An app that renders atscript-ui models without the Vue packages sets the locale and a translator once, browser-wide:
import { setUiLocale } from "@atscript/ui";
setUiLocale("de", (id, params) => myI18n.t(id, params));The framework-agnostic helpers that produce text (temporalShortcuts(), conditionLabel(), formatFilterExpr(), createTableDef() boolean labels, …) read it, and also take an explicit t — see the @atscript/ui-table reference. Never call setUiLocale on a server that renders for many users.
DOs and DON'Ts
- Do branch on codes, never on text: every text changes with the locale.
- Do keep placeholders in translations (
"{count} Zeilen"). - Don't call
setUiLocalein a Vue app that usesprovideAsLocale: in the browser the provider already serves code outside components. - One/many pairs (
UI_TABLE_ROWS_ONE/UI_TABLE_ROWS) cover English plurals; yourtranslategets the realcount, so it can apply your language's rules. - Model texts (
@meta.label, validation messages) are not part of this catalog: the server translates them (Translations on the server); validator messages reported in the browser are looked up by their code.
See also
- Translations on the server: what a moost server translates,
$i18n,tin expressions. - Locale & Currency: number and date formatting.
- AsWfForm: the workflow client.
- @atscript/ui reference:
AS_UI_MESSAGES,setUiLocale. - @atscript/vue-form reference:
provideAsLocale,useAsLocale.