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 and rich text fields. Other field types (numbers, dates, booleans, references, media) share a single value across all locales
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 argument on the per-model query. The fallback chain is applied server-side and the resolved values for that locale (or its fallback) come back directly on the typed fields:
query {
page(naturalKey: "about", locale: "fr-FR") {
_id
title
body
}
}Batch locale requests
Fetch multiple records in the same locale with the plural connection, filtering by natural key:
query {
pages(
where: { naturalKey: { in: ["about", "contact", "faq"] } }
locale: "de-DE"
) {
edges {
node {
_naturalKey
title
}
}
}
}Writing translations
The public per-model write surface is locale-agnostic: createPage / updatePage accept a single set of field values, and there is no translations input or locale argument on mutations. Author per-locale content in the admin editor with the locale switcher (see Translating content above). The public API’s role in localization is read-time resolution via the locale argument.
Reading locale configuration
There is no public GraphQL locales query. Manage and inspect a project’s configured locales in the admin (Settings > Locales) or via the CLI (foir locales list, shown above).
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). - Keep translatable copy (text and rich text fields) in dedicated fields so it can be localized per locale; values like alt text are part of the media value and shared 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.