Content Languages
Configure content locales, mark localized fields, and query translated content.
QUESTPIE separates content language from admin UI language.
- Content language controls localized field values stored for records and globals.
- Admin UI language controls the interface strings shown in the admin panel.
Use this page for content languages. For admin interface language and message keys, see UI Language & Messages.
Configure Content Locales
Define content locales in config/app.ts:
import { appConfig } from "questpie/app";
export default appConfig({
locale: {
locales: [
{
code: "en",
label: "English",
fallback: true,
flagCountryCode: "gb",
},
{ code: "sk", label: "Slovencina" },
{ code: "de", label: "Deutsch" },
],
defaultLocale: "en",
fallbacks: {
"en-US": "en",
"en-GB": "en",
"de-AT": "de",
},
},
});| Option | Type | Description |
|---|---|---|
code | string | Locale identifier, such as "en", "sk", or "en-US" |
label | string | Display name shown in locale pickers |
fallback | boolean | Metadata for marking the primary fallback locale in UI |
flagCountryCode | string | Optional country code for flag display |
defaultLocale | string | Locale used when no locale is requested |
fallbacks | Record<string, string> | Maps unsupported requested locales to supported locales |
locales | Locale[] | (() => Locale[]) | Static locale list or a resolver for dynamic locale availability |
Add A Language
- Add the locale object to
config/app.ts. - Add or update
fallbacksif regional variants should resolve to an existing locale. - Add translated values for localized fields in the admin or through the CRUD API.
- Re-run codegen only if you just created
config/app.ts; editing the locale list itself does not change generated types. - Run migrations only if you changed field definitions, such as adding
.localized()to a field.
Adding a new content locale does not require a database migration by itself. Localized values are stored by locale in i18n tables.
Change The Default Language
Change defaultLocale in config/app.ts:
export default appConfig({
locale: {
locales: [
{ code: "en", label: "English" },
{ code: "sk", label: "Slovencina", fallback: true },
],
defaultLocale: "sk",
},
});The default locale is used when no locale is passed to app.createContext(), the HTTP adapter, or the client SDK. Missing localized values fall back to the default locale unless a query explicitly sets localeFallback: false.
Localize Fields
Chain .localized() on fields whose values should vary by content language:
import { collection } from "#questpie/factories";
export const pages = collection("pages").fields(({ f }) => ({
title: f.text(255).required().localized(),
excerpt: f.textarea().localized(),
content: f.blocks().localized(),
slug: f.text(255).required(),
publishedAt: f.datetime(),
}));Only localized fields change when the active content locale changes. Non-localized fields share one value across every language.
Good localized candidates:
f.text(),f.textarea(),f.richText()f.select()when the stored value is language-specific- object/array fields where the whole structure should vary per locale
f.blocks()for page sections and marketing content
Usually not localized:
- IDs, slugs, booleans, numbers, dates, relation IDs, and upload references
Localized Objects And Arrays
Localizing an array or object localizes the whole value for that locale:
navigation: f
.object({
label: f.text().required(),
href: f.text().required(),
})
.array()
.localized(),Switching from "en" to "sk" loads a separate navigation array for Slovak. It does not translate individual rows inside the English array.
Query Localized Content
Route handlers and hooks receive the request locale in context:
import { route } from "questpie/services";
export default route().handler(async ({ collections }) => {
return collections.pages.find({
where: { isPublished: true },
});
});For standalone scripts and tests, pass the locale when creating context:
import { createContext } from "#questpie";
const ctx = await createContext({ locale: "sk" });
const pages = await ctx.collections.pages.find({});The client SDK can use a default locale or a per-call locale:
client.setLocale("sk");
const pages = await client.collections.pages.find({});
const englishPages = await client.collections.pages.find({
locale: "en",
});Disable fallback when you need to detect missing translations:
const page = await client.collections.pages.findOne({
where: { id: "page_123" },
locale: "sk",
localeFallback: false,
});Admin Editing
When any collection or global has localized fields, the admin shows a content locale switcher. The switcher changes only localized values; the admin UI language stays the same.
The admin sends the active content locale on collection/global requests, relation option requests, validation, actions, and block editing. If a form has unsaved changes, switching content locale asks the editor whether to discard those unsaved edits.
Labels And Descriptions
Field labels, collection labels, sidebar items, and help text use I18nText, not content localization. They can be plain strings, inline locale maps, or translation key references:
title: f
.text(255)
.label({ en: "Title", sk: "Nadpis" })
.description({ key: "fields.title.help", fallback: "Shown on the page" })
.localized(),These labels follow the admin UI language. The field value follows the content language.
Related Pages
- Workspace Localization — How editors switch content locale in the admin
- UI Language & Messages — Admin interface locale and message keys
- Fields — Field types and
.localized() - Globals — Site-wide localized settings