Locales (i18n)
Strapi ships the i18n plugin in the box. Its content API exposes the list of configured locales, and the client wraps it as client.i18n — typed, and going through the same auth pipeline, baseURL, error handling, and Next.js cache integration as the rest of the client.
Before this existed you had to reach for @strapi/client (or a hand-written fetch) just to fill a language switcher.
Methods
| Method | Endpoint | Returns |
|---|---|---|
locales() | GET /api/i18n/locales | I18nLocale[] |
I18nLocale
interface I18nLocale {
id: number
documentId: string
name: string
code: string
isDefault: boolean
createdAt: string
updatedAt: string
publishedAt: string | null
}Listing locales
const locales = await client.i18n.locales()
const codes = locales.map(locale => locale.code)
// ['en', 'de', 'fr']
const fallback = locales.find(locale => locale.isDefault)?.code ?? 'en'A language switcher, with the display names Strapi already stores:
const options = (await client.i18n.locales()).map(locale => ({
value: locale.code,
label: locale.name,
}))Pass a code straight to a collection query:
const articles = await client.articles.find({ locale: 'de' })Response shape
This route answers with a bare array — there is no { data, meta } envelope, so locales() resolves to I18nLocale[] directly.
The route ignores query parameters
listLocales reads nothing off the query string: fields, filters, sort and pagination are all silently ignored, and you always get every locale in full. Narrow the result in your own code instead.
Permissions
The endpoint is not public by default — an anonymous request gets 403 Forbidden. Either grant the role the i18n → locale → find permission under Settings → Users & Permissions → Roles, or call it with an API token.
Locale codes are not always two letters
Strapi accepts regional codes such as en-US and pt-BR, so treat code as an opaque string rather than assuming a two-letter shape.