Skip to content

Strapi Internationalization — i18n Plugin, Content Translation, and Locales

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn how to configure Strapi's Internationalization (i18n) plugin — adding multiple locales, translating content across languages, and serving localized content through the API for multilingual websites and applications.

What You'll Learn

  • How to install and configure the i18n plugin
  • How to add and manage locales
  • How to enable content types for localization
  • How to create and manage translations in the admin panel
  • How to serve localized content through the API
  • Best practices for multilingual content management

Why It Matters

A website available in multiple languages reaches a wider audience, performs better in local search engines, and provides a better user experience for non-English speakers. Strapi's i18n plugin makes it possible to manage translations alongside your content model, so the same content type can have English, French, Spanish, and other language versions.

Real-World Use

A global recipe site serves content in 8 languages. A French chef submits a recipe in French. The English editor translates it to English. The API serves the appropriate version based on the user's language preference. The French user sees the original French recipe. The US user sees the English translation. The same content type, the same API, different locales.

Learning Path

flowchart LR
  A["Email & Notifications"] --> B["Internationalization
-- You are here"]:::current B --> C["Lifecycle Hooks"] C --> D["Custom Middleware"] D --> E["Webhooks"] classDef current fill:#4945ff,color:#fff,stroke-width:2px

Installing the i18n Plugin

The i18n plugin is included by default but may need enabling:

# Install if not already installed
npm run strapi install i18n

# Or install via npm
npm install @strapi/plugin-i18n

After installation, configure locales in the admin panel:

Settings > Internationalization > Locales
-- Add new locale
-- Select language: French
-- Locale code: fr
-- Add

-- Add more locales: Spanish (es), German (de), Japanese (ja)

Each locale has a code (BCP 47 standard: en, fr, es, de, ja, zh, ar, etc.) and an optional display name.

Enabling Content Types for i18n

Not all content types need translation. Enable localization per content type:

// In Content-Type Builder, edit a content type schema
// Or directly in the schema.json file:
// src/api/article/content-types/article/schema.json
{
  "kind": "collectionType",
  "info": {
    "singularName": "article",
    "pluralName": "articles",
    "displayName": "Article"
  },
  "options": {
    "draftAndPublish": true,
    "i18n": {
      "localized": true  // Enable translations for this type
    }
  },
  "attributes": {
    "title": { "type": "string", "required": true, "pluginOptions": { "i18n": { "localized": true } } },
    "content": { "type": "richtext", "required": true, "pluginOptions": { "i18n": { "localized": true } } },
    "author": { "type": "relation", "relation": "manyToOne", "target": "api::author.author" }
  }
}

Field-level pluginOptions.i18n.localized controls whether each field is translatable. Non-localized fields (like createdAt) are shared across all locales.

Creating Translations in the Admin Panel

Once i18n is enabled for a content type, the Content Manager shows locale controls:

Content Manager > Articles > Create entry
-- Locale switcher (top of form)
--   [English] [French] [Spanish]
-- Each locale has its own set of fields
-- Non-localized fields are shared

To create a translation:
1. Open an article in English
2. Click "Add new locale" in the locale switcher
3. Select French
4. Strapi creates a copy of the article in French
5. Edit the French fields
6. Save and publish

The Content Manager shows a "Locales" column indicating which translations exist for each entry.

Creating Translations via API

Create and manage translations programmatically:

// Create an article in English
POST /api/articles
Headers: Content-Type: application/json
Body:
{
  "data": {
    "title": "Hello World",
    "content": "This is the English version",
    "locale": "en"
  }
}

// Response includes the locale and localizations:
{
  "data": {
    "id": 1,
    "attributes": {
      "title": "Hello World",
      "locale": "en",
      "localizations": {
        "data": []  // No translations yet
      }
    }
  }
}

// Create a French translation linked to the English article
POST /api/articles?locale=fr
Body:
{
  "data": {
    "title": "Bonjour le monde",
    "content": "Ceci est la version francaise",
    "locale": "fr"
  }
}

To link a translation to an existing article, include the source article's ID in the request:

// Create French translation linked to English article ID 1
POST /api/articles?locale=fr
Body:
{
  "data": {
    "title": "Bonjour le monde",
    "content": "Ceci est la version francaise"
  }
}
// Strapi automatically links this as a localization of the original

Querying Localized Content

The locale parameter controls which language version is returned:

// Get articles in English (default locale)
GET /api/articles?locale=en

// Get articles in French
GET /api/articles?locale=fr

// Get all localizations of all articles
GET /api/articles?locale=all

// Get a single article with its localizations
GET /api/articles/1?populate=localizations

You might be wondering what happens if content does not exist in the requested locale. By default, Strapi returns null or empty data. You can configure fallback behavior:

// config/plugins.js
module.exports = {
  i18n: {
    config: {
      defaultLocale: "en",
      // Fall back to default locale if translation missing
      fallbackToDefaultLocale: true,
    },
  },
};

// With fallback enabled:
// GET /api/articles?locale=fr
// Returns English content for articles without French translation

Best Practices for i18n Content Modeling

  1. Localize only translatable fields. Fields like createdAt, updatedAt, id, and system fields should not be localized. Only localize fields with user-facing text.

  2. Keep shared data separate. Categories, tags, and other reference data might be shared across locales (same tags for all languages) or localized (different category names per language). Configure per content type.

  3. Plan for locale independence. Each locale can have different publication status. An article can be published in English but still a draft in French.

  4. Use locale-specific SEO. Meta titles, descriptions, and URLs should be localized per language for proper SEO. The SEO plugin supports locale-specific metadata.

  5. Consider media localization. Images with text should be different per locale. A banner with English text is not useful for the French site. Create locale-specific media fields.

Locale Management

Manage locales through the API:

// List all configured locales
GET /api/i18n/locales
// Response:
{
  "data": [
    { "code": "en", "name": "English (en)" },
    { "code": "fr", "name": "French (fr)" },
    { "code": "es", "name": "Spanish (es)" }
  ]
}

// Create a new locale (admin only)
POST /api/i18n/locales
Body: { "code": "de", "name": "German (de)" }

Common Mistakes

  1. Enabling i18n on content types that do not need it. Every localized content type adds complexity. Only enable i18n on content types that genuinely need translation.

  2. Forgetting the locale parameter in API requests. Without specifying a locale, Strapi returns the default locale content. Frontends must send the user's preferred locale with every request.

  3. Not configuring fallback behavior. When a translation is missing, the API returns empty data or an error. Configure fallbackToDefaultLocale for a better user experience.

  4. Creating duplicate entries instead of translations. Without i18n, users create separate entries for each language. This breaks the relationship between translations. Always use the i18n plugin for multi-language content.

  5. Translating non-user-facing fields. Fields like slugs, IDs, and technical identifiers should not be localized. Translating them causes confusion and breaks references.

Practice Questions

  1. How do you enable a content type for localization? Answer: In the Content-Type Builder, edit the content type and enable "Internationalization" in the advanced settings. Then configure which fields are translatable.

  2. What API parameter returns content in a specific language? Answer: The locale parameter. Example: GET /api/articles?locale=fr returns articles in French.

  3. How does Strapi handle missing translations? Answer: By default, the API returns empty data for missing translations. With fallbackToDefaultLocale: true, Strapi returns the default locale content as fallback.

  4. Challenge: Build a multilingual content system: (1) Enable the i18n plugin and add 3 locales (English, French, Spanish), (2) Create a "Page" content type with localized title and content fields, (3) Create a "Category" content type that is shared (non-localized), (4) Create the same page in all 3 languages with different content, (5) Write API queries that fetch content in each locale, (6) Implement locale detection in a frontend that shows the appropriate language, (7) Test fallback behavior by creating content only in English and requesting French.

FAQ

How many locales can I add to Strapi?

There is no hard limit. Strapi supports any BCP 47 locale code. However, each locale multiplies the content management effort. Practical limits are 5-20 locales depending on your team size.

Can I have locale-specific media files?

Yes. Create separate media fields for each locale or use different media entries for different locales. A hero image with English text has a different file than the French version.

How do I get all translations of an entry?

Use GET /api/articles/1?populate=localizations to get the entry with all its linked translations. The localizations attribute contains an array of translated entries.

Does i18n affect GraphQL queries?

Yes. The GraphQL schema includes locale arguments on queries. You pass locale: 'fr' to fetch French content. The localizations field is also available on localized types.

Can I set different permissions per locale?

Strapi does not support per-locale permissions. A user with article update permission can update articles in all locales. You would need custom policies for locale-specific access.

Mini Project

Your task: Build a complete multilingual content platform.

  1. Configure i18n with 4 locales: English (en), French (fr), Spanish (es), German (de).
  2. Create a "Product" content type with localized fields: name, description, features (repeatable component). Non-localized fields: price, sku, category.
  3. Create a "Category" content type that is also localized.
  4. Add 5 products — create full translations for at least 2 products in all 4 locales.
  5. Write API queries that:
    • Fetch all products in English
    • Fetch a specific product in French
    • Fetch all localizations of a product
    • Fetch products with fallback to English when French is missing
  6. Build a simple frontend with a locale switcher that changes the API locale parameter and reloads the content.

What's Next

Now that you understand internationalization, proceed to Lifecycle Hooks to learn how to run custom code before and after database operations. After that, explore Custom Middleware for request/response processing.

Related lessons:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro