obsidian/wiki/payloadcms/localization.md
2026-05-15 15:13:56 +01:00

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