Internationalisation (i18n)

Sur cette page

EmDash s’intègre au routage i18n intégré d’Astro pour la gestion multilingue. Astro gère les URL et la détection de langue ; EmDash stocke et récupère les traductions.

Chaque traduction est une entrée de contenu complète et indépendante, avec son propre slug, statut et historique de révisions. La version française d’un article peut être en brouillon pendant que la version anglaise est publiée.

Configuration

Activez i18n en ajoutant un bloc i18n à la configuration Astro. EmDash lit cette configuration automatiquement — il n’y a pas de réglage de langue séparé dans EmDash.

import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";

export default defineConfig({
	i18n: {
		defaultLocale: "en",
		locales: ["en", "fr", "es"],
		fallback: { fr: "en", es: "en" },
	},
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data.db" }),
			storage: local({
				directory: "./uploads",
				baseUrl: "/_emdash/api/media/file",
			}),
		}),
	],
});

Sans bloc i18n dans la config Astro, toutes les fonctions i18n sont désactivées et EmDash se comporte comme un CMS monolingue.

Fonctionnement des traductions

EmDash utilise un modèle une ligne par langue. Chaque traduction est sa propre ligne en base avec son ID, slug et statut, liée aux autres via un translation_group partagé.

ec_posts:
id       | slug        | locale | translation_group | status
---------|-------------|--------|-------------------|----------
01ABC... | my-post     | en     | 01ABC...          | published
01DEF... | mon-article | fr     | 01ABC...          | draft
01GHI... | mi-entrada  | es     | 01ABC...          | published

Cela implique :

  • Slugs par langue/blog/my-post et /fr/blog/mon-article fonctionnent naturellement
  • Publication par langue — publier l’anglais tout en gardant le français en brouillon
  • Révisions par langue — chaque traduction a son propre historique
  • Pas de mélange dans les listes — les requêtes de liste ne renvoient qu’une langue à la fois

Interroger le contenu traduit

Entrée unique

Passez locale à getEmDashEntry pour charger une traduction précise. S’il est omis, la langue courante de la requête s’applique (définie par le middleware i18n d’Astro).

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug, {
  locale: Astro.currentLocale,
});

if (!post) return Astro.redirect("/404");
---

<article>
  <h1>{post.data.title}</h1>
</article>

Chaîne de repli

S’il n’y a pas de contenu pour la langue demandée, EmDash suit la chaîne définie dans la config Astro. Avec fallback: { fr: "en" } :

  1. Langue demandée (fr)
  2. Langue de repli (en)
  3. Langue par défaut

Le repli ne s’applique qu’aux requêtes d’entrée unique. Les listes ne renvoient que la langue demandée — pas de mélange.

Liste d’une collection

Filtrez une collection par langue :

---
import { getEmDashCollection } from "emdash";

const { entries: posts } = await getEmDashCollection("posts", {
  locale: Astro.currentLocale,
  status: "published",
});
---

<ul>
  {posts.map((post) => (
    <li><a href={`/${post.data.slug}`}>{post.data.title}</a></li>
  ))}
</ul>

Sélecteur de langue

Utilisez getTranslations pour lier vers les traductions existantes de l’entrée courante :

---
import { getTranslations } from "emdash";
import { getRelativeLocaleUrl } from "astro:i18n";

interface Props {
  collection: string;
  entryId: string;
}

const { collection, entryId } = Astro.props;
const { translations } = await getTranslations(collection, entryId);
---

<nav aria-label="Language">
  <ul>
    {translations.map((t) => (
      <li>
        <a
          href={getRelativeLocaleUrl(t.locale, `/blog/${t.slug}`)}
          aria-current={t.locale === Astro.currentLocale ? "page" : undefined}
        >
          {t.locale.toUpperCase()}
        </a>
      </li>
    ))}
  </ul>
</nav>

getTranslations renvoie toutes les variantes du même groupe :

const { translationGroup, translations } = await getTranslations("posts", post.entry.id);
// translations: [
//   { locale: "en", id: "01ABC...", slug: "my-post", status: "published" },
//   { locale: "fr", id: "01DEF...", slug: "mon-article", status: "draft" },
// ]

Gérer les traductions dans l’admin

Liste de contenu

Avec i18n activé, la liste affiche :

  • Une colonne langue pour chaque entrée
  • Un filtre langue dans la barre d’outils

Créer des traductions

Ouvrez une entrée dans l’éditeur. Le panneau latéral Translations liste les langues configurées. Pour chacune :

  • « Translate » s’il n’y a pas encore de traduction — cliquez pour en créer une
  • « Edit » si une traduction existe — cliquez pour y aller
  • La langue courante est indiquée par une coche

À la création, la nouvelle entrée est préremplie depuis la langue source avec un slug par défaut {slug-source}-{locale}. Ajustez slug et contenu, puis enregistrez.

Publication par langue

Chaque traduction a son propre statut. Publiez, dépubliez ou planifiez indépendamment. Le français peut rester en brouillon pendant que l’anglais est en ligne.

API de contenu

Paramètre locale

Toutes les routes de l’API de contenu acceptent un paramètre de requête optionnel locale :

GET /_emdash/api/content/posts?locale=fr
GET /_emdash/api/content/posts/my-post?locale=fr

S’il est omis, la langue par défaut configurée s’applique.

Créer des traductions via l’API

Créez une traduction en passant locale et translationOf au point de création :

POST /_emdash/api/content/posts
Content-Type: application/json

{
  "locale": "fr",
  "translationOf": "01ABC...",
  "data": {
    "title": "Mon Article",
    "slug": "mon-article"
  }
}

La nouvelle entrée partage le translation_group de la source et commence en brouillon.

Lister les traductions

Récupérez toutes les traductions d’une entrée :

GET /_emdash/api/content/posts/01ABC.../translations

Renvoie l’ID du groupe et un tableau de variantes avec ID, slug et statut.

CLI

La CLI accepte --locale sur les commandes de contenu :

# List French posts
emdash content list posts --locale fr

# Get a specific entry in French
emdash content get posts my-post --locale fr

# Create a French translation of an existing entry
emdash content create posts --locale fr --translation-of 01ABC...

Données de départ multilingues

Les fichiers seed expriment les traductions avec locale et translationOf :

{
  "content": {
    "posts": [
      {
        "id": "welcome",
        "slug": "welcome",
        "locale": "en",
        "status": "published",
        "data": { "title": "Welcome" }
      },
      {
        "id": "welcome-fr",
        "slug": "bienvenue",
        "locale": "fr",
        "translationOf": "welcome",
        "status": "draft",
        "data": { "title": "Bienvenue" }
      }
    ]
  }
}

L’entrée dans la langue source doit apparaître avant ses traductions pour que translationOf se résolve correctement.

Champs traduisibles

Chaque champ a l’option translatable (défaut : true). Lors de la création d’une traduction :

  • Les champs traduisibles sont préremplis depuis la langue source pour édition
  • Les non traduisibles sont copiés et restent synchronisés dans tout le groupe

Les champs système comme status, published_at et author_id sont toujours par langue et ne sont pas synchronisés.

Stratégie d’URL

EmDash ne gère pas les URL par langue — Astro gère le routage. Exemples :

# prefix-other-locales (Astro default)
/blog/my-post          → en (default locale, no prefix)
/fr/blog/mon-article   → fr

# prefix-always
/en/blog/my-post       → en
/fr/blog/mon-article   → fr

Utilisez getRelativeLocaleUrl depuis astro:i18n pour des URL correctes quel que soit le mode.

Importer du contenu multilingue

WordPress avec WPML ou Polylang

La source d’import WordPress détecte WPML et Polylang. Le contenu importé inclut alors les métadonnées de langue et de groupe de traduction.

Fichiers WXR

Les exports WXR n’incluent pas ces métadonnées. Importez en une seule langue et créez les traductions manuellement, ou utilisez --locale pour assigner une langue à tous les éléments :

# Import a French WXR export
emdash import wordpress export-fr.xml --execute --locale fr

# Match against existing English content by slug
emdash import wordpress export-fr.xml --execute --locale fr --translation-of-locale en

Prochaines étapes