170 lines
5.1 KiB
Markdown
170 lines
5.1 KiB
Markdown
---
|
|
title: "Localization — Content Internationalization"
|
|
aliases: [payload-localization, payload-i18n-content, payload-multilingual]
|
|
tags: [payloadcms, localization, i18n, multilingual, content]
|
|
sources: [raw/configuration__localization.md]
|
|
created: 2026-05-15
|
|
updated: 2026-05-15
|
|
---
|
|
|
|
## Overview
|
|
|
|
Localization manages **data translations** (content in multiple languages), while [[wiki/payloadcms/i18n|I18n]] manages **UI translations** (admin panel labels). They complement each other but are separate concerns.
|
|
|
|
Enable globally via `localization` key in Payload Config, then opt individual fields in with `localized: true`.
|
|
|
|
## Config Setup
|
|
|
|
```ts
|
|
import { buildConfig } from 'payload'
|
|
|
|
export default buildConfig({
|
|
localization: {
|
|
locales: ['en', 'es', 'de'], // required
|
|
defaultLocale: 'en', // required
|
|
fallback: true, // default: true
|
|
},
|
|
})
|
|
```
|
|
|
|
### Config Options
|
|
|
|
| Option | Description |
|
|
|--------|-------------|
|
|
| `locales` | Array of locale codes or full locale objects |
|
|
| `defaultLocale` | Fallback locale when none specified (required) |
|
|
| `fallback` | Auto-fallback to default locale when field value missing (default: `true`) |
|
|
| `filterAvailableLocales` | Async fn `({ req, locales })` → filtered locales for admin UI selector |
|
|
|
|
### Locale Object (Full Form)
|
|
|
|
```ts
|
|
{
|
|
code: 'ar', // required — used in API params
|
|
label: 'Arabic', // shown in admin locale selector
|
|
rtl: true, // enables Right-To-Left input fields
|
|
fallbackLocale: 'en' // per-locale fallback (string or array)
|
|
}
|
|
```
|
|
|
|
Locale codes are arbitrary — use `'en'`, `'en-US'`, `'en-UK'`, etc.
|
|
|
|
## Field-Level Localization
|
|
|
|
Localization is **field-level**, not document-level. Add `localized: true` to each field:
|
|
|
|
```js
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
localized: true,
|
|
}
|
|
```
|
|
|
|
- Works on **all field types** including `array`, `blocks`, `relationship`
|
|
- Enabling on a `blocks` field localizes the **entire layout** (all nested fields)
|
|
- Alternatively, localize only specific nested fields
|
|
|
|
> **Warning:** Changing `localized` on an existing field changes the DB structure — existing data for that field **will be lost**. Plan a migration strategy before toggling.
|
|
|
|
## Status Localization (Experimental)
|
|
|
|
Manage draft/published status **per locale** independently. Two-step setup:
|
|
|
|
**Step 1 — Global flag:**
|
|
```ts
|
|
experimental: {
|
|
localizeStatus: true,
|
|
}
|
|
```
|
|
|
|
**Step 2 — Per collection:**
|
|
```ts
|
|
versions: {
|
|
drafts: {
|
|
localizeStatus: true,
|
|
},
|
|
}
|
|
```
|
|
|
|
Result — `status` stored as object:
|
|
```ts
|
|
status: { en: 'published', es: 'draft', de: 'published' }
|
|
```
|
|
|
|
## Filtering Locales Per Tenant
|
|
|
|
```ts
|
|
filterAvailableLocales: async ({ req, locales }) => {
|
|
const tenant = await getTenantFromRequest(req)
|
|
if (tenant?.supportedLocales?.length) {
|
|
return locales.filter(l => tenant.supportedLocales.includes(l.code))
|
|
}
|
|
return locales
|
|
}
|
|
```
|
|
|
|
Filtering runs server-side at app root — not per page navigation. Call `router.refresh()` when `supportedLocales` changes.
|
|
|
|
## Querying Localized Documents
|
|
|
|
### REST API
|
|
|
|
```
|
|
GET /api/posts?locale=es&fallback-locale=none
|
|
GET /api/posts?locale=all // returns all locales in one request
|
|
```
|
|
|
|
| Param | Values |
|
|
|-------|--------|
|
|
| `locale` | Any locale code, `'all'`, `'*'` |
|
|
| `fallback-locale` | Locale code, `'null'`, `'false'`, `'none'` |
|
|
|
|
### GraphQL API
|
|
|
|
```graphql
|
|
query {
|
|
Posts(locale: de, fallbackLocale: none) {
|
|
docs { title }
|
|
}
|
|
}
|
|
```
|
|
|
|
- Locale specified at top level auto-applies to nested relationship fields
|
|
- Locale codes auto-converted to valid GraphQL enum values (dashes → underscores)
|
|
|
|
### Local API
|
|
|
|
```js
|
|
const posts = await payload.find({
|
|
collection: 'posts',
|
|
locale: 'es',
|
|
fallbackLocale: false,
|
|
})
|
|
```
|
|
|
|
`fallbackLocale` accepts: locale string, array of locales, `'null'`, `'false'`, `false`, `'none'`.
|
|
|
|
## Key Takeaways
|
|
|
|
- **Field-level granularity** — opt each field into localization individually
|
|
- **`localized: true` on `blocks` = whole layout is localized** — or go field-by-field inside
|
|
- **`fallback: true` (default)** — missing locale values automatically show `defaultLocale` value
|
|
- **`locale=all`** in REST/Local API returns all locale values in one request (useful for admin/export)
|
|
- **Status localization is experimental** — test thoroughly before production
|
|
- **Data migration required** when toggling `localized` on existing fields
|
|
- **`filterAvailableLocales`** enables per-tenant locale scoping in multi-tenant apps
|
|
- **RTL support** built-in via `rtl: true` on locale object
|
|
|
|
## Related
|
|
|
|
- [[wiki/payloadcms/i18n|I18n — Interface Internationalization]] — admin panel UI translations (separate from content)
|
|
- [[wiki/payloadcms/collection-config|Collection Config]] — field definitions and schema options
|
|
- [[wiki/payloadcms/rest-api|REST API]] — locale query params in detail
|
|
- [[wiki/payloadcms/local-api|Local API]] — locale options in `payload.find()` / `payload.findByID()`
|
|
- [[wiki/payloadcms/globals-config|Globals Config]] — globals also support localized fields
|
|
|
|
## Sources
|
|
|
|
- `raw/configuration__localization.md`
|
|
- https://payloadcms.com/docs/configuration/localization
|