Localization
Foir supports multi-language content out of the box. Configure locales for your project, add translations to content fields, and serve the right language to each visitor automatically.
Overview
Localization in Foir works by attaching per-locale translations to translatable fields on your records. The system uses BCP-47 language codes and supports a configurable fallback chain so content is always returned, even when translations are incomplete.
Key Concepts
- Locale — A BCP-47 language code (e.g.,
en-US,fr-FR,de-DE) representing a language and region - Default locale — The locale used when no locale is specified in a request
- Fallback chain — When a translation is missing for the requested locale, the system falls back through configured locales
- Translatable fields — Text, rich text, textarea, link text, and media metadata (alt text, captions)
Fallback Chain
If a translation is missing for the requested locale, Foir follows the fallback chain:
Requested locale (fr-CA)
-> Fallback locale (fr-FR)
-> Default locale (en-US)This ensures content is always returned. You configure the fallback locale per locale, so fr-CA can fall back to fr-FR, which falls back to the project default.
Locale Properties
Each locale has:
| Property | Description |
|---|---|
locale | BCP-47 code (e.g., en-US) |
displayName | Human-readable name (e.g., “English (United States)“) |
nativeName | Name in the native script (e.g., “English (US)“) |
isDefault | Whether this is the project’s default locale |
isRtl | Whether the language is right-to-left |
fallbackLocale | Locale to fall back to when a translation is missing |
In the Admin
Configuring locales
- Go to Settings > Locales
- Click Add Locale
- Select or enter the BCP-47 code (e.g.,
fr-FR) - Set the display name
- Optionally configure a fallback locale
- Mark one locale as the default
Translating content
- Open any record in the editor
- Use the locale switcher at the top of the editor to switch between languages
- Translatable fields show separate inputs for each locale
- Save when translations are complete
Non-translatable fields (numbers, dates, booleans, references) share the same value across all locales.
Via the CLI
List locales
foir locales listGet a locale
By ID:
foir locales get loc_abc123By code:
foir locales get fr-FRGet the default locale
foir locales defaultCreate a locale
foir locales create --data '{
"locale": "fr-FR",
"displayName": "French (France)",
"nativeName": "Fran\u00e7ais (France)",
"fallbackLocale": "en-US",
"isRtl": false
}'Update a locale
foir locales update loc_abc123 --data '{
"displayName": "French (France)",
"fallbackLocale": "en-US"
}'Delete a locale
foir locales delete loc_abc123Via the API
Requesting localized content
Pass the locale parameter when resolving content:
query {
recordByKey(modelKey: "page", naturalKey: "about") {
resolved(locale: "fr-FR") {
content
resolvedWith {
locale
}
}
}
}The response includes resolvedWith.locale so you know which locale was actually used (helpful when fallbacks are involved).
Batch locale requests
Resolve multiple records in the same locale:
query {
records(modelKey: "page", naturalKeys: ["about", "contact", "faq"]) {
items {
naturalKey
resolved(locale: "de-DE") {
content
}
}
}
}Translations in mutations
When creating or updating records, include translations alongside the base content:
mutation {
createRecord(input: {
modelKey: "blog-post"
data: {
title: "Hello World"
body: "Welcome to our blog."
}
translations: {
"fr-FR": {
title: "Bonjour le monde"
body: "Bienvenue sur notre blog."
}
"de-DE": {
title: "Hallo Welt"
body: "Willkommen in unserem Blog."
}
}
}) {
record {
id
data
}
}
}Locale management queries
query {
locales {
locale
displayName
nativeName
isDefault
isRtl
fallbackLocale
}
}Localization vs Variants
Localization and variants work together but serve different purposes:
| Feature | Purpose | Example |
|---|---|---|
| Localization | Translate the same content into different languages | English -> French translation |
| Variants | Show different content to different audiences | Mobile users see a simplified layout |
A single variant can have translations in multiple locales. For example, your “VIP” variant can have both English and French translations.
Best Practices
- Set up fallback locales to prevent empty content. A missing translation should fall back gracefully, not show blank fields.
- Use standard BCP-47 codes consistently (e.g.,
en-USnoten_usorenglish). - Translate media metadata (alt text and captions) for accessibility across all locales.
- Start with your primary market locales and expand as needed. You can add locales at any time.
- Test right-to-left rendering if you support Arabic, Hebrew, or other RTL languages.