QUESTPIE
Build Your BackendData Modeling

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:

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",
		},
	},
});
OptionTypeDescription
codestringLocale identifier, such as "en", "sk", or "en-US"
labelstringDisplay name shown in locale pickers
fallbackbooleanMetadata for marking the primary fallback locale in UI
flagCountryCodestringOptional country code for flag display
defaultLocalestringLocale used when no locale is requested
fallbacksRecord<string, string>Maps unsupported requested locales to supported locales
localesLocale[] | (() => Locale[])Static locale list or a resolver for dynamic locale availability

Add A Language

  1. Add the locale object to config/app.ts.
  2. Add or update fallbacks if regional variants should resolve to an existing locale.
  3. Add translated values for localized fields in the admin or through the CRUD API.
  4. Re-run codegen only if you just created config/app.ts; editing the locale list itself does not change generated types.
  5. 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:

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:

collections/pages.ts
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:

routes/published-pages.ts
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.

On this page