Internazionalizzazione (i18n)

In questa pagina

EmDash si integra con il routing i18n integrato di Astro per la gestione multilingue. Astro gestisce URL e rilevamento della lingua; EmDash memorizza e recupera i contenuti tradotti.

Ogni traduzione è una voce di contenuto completa e indipendente, con slug, stato e cronologia delle revisioni propri. La versione francese di un articolo può essere in bozza mentre quella inglese è pubblicata.

Configurazione

Abilita i18n aggiungendo un blocco i18n alla configurazione Astro. EmDash legge questa configurazione automaticamente — non è necessaria una configurazione separata delle lingue in 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",
			}),
		}),
	],
});

Se i18n non è presente nella configurazione Astro, tutte le funzionalità i18n sono disattivate e EmDash si comporta come un CMS monolingue.

Come funzionano le traduzioni

EmDash usa un modello una riga per lingua (locale). Ogni traduzione è una propria riga nel database con ID, slug e stato propri, collegata alle altre tramite un translation_group condiviso.

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

Ne consegue:

  • Slug per lingua/blog/my-post e /fr/blog/mon-article funzionano in modo naturale
  • Pubblicazione per lingua — pubblicare in inglese tenendo il francese in bozza
  • Revisioni per lingua — ogni traduzione ha la propria cronologia
  • Niente incroci nelle liste — le query di elenco restituiscono solo voci per una lingua

Interrogare i contenuti tradotti

Singola voce

Passa locale a getEmDashEntry per recuperare una traduzione specifica. Se omesso, si usa la lingua corrente della richiesta (impostata dal middleware i18n di 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>

Catena di fallback

Se non esiste contenuto per la lingua richiesta, EmDash segue la catena definita nella configurazione Astro. Con fallback: { fr: "en" }:

  1. Lingua richiesta (fr)
  2. Lingua di fallback (en)
  3. Lingua predefinita

Il fallback si applica solo alle query su singola voce. Gli elenchi restituiscono solo voci per la lingua richiesta — senza mescolare.

Elenco della collection

Filtra una collection per lingua:

---
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>

Selettore lingua

Usa getTranslations per costruire un selettore che colleghi alle traduzioni esistenti della voce corrente:

---
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 restituisce tutte le varianti linguistiche dello stesso gruppo:

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" },
// ]

Gestire le traduzioni nell’admin

Elenco contenuti

Con i18n abilitato, l’elenco mostra:

  • una colonna lingua con il locale di ogni voce
  • un filtro lingua nella barra degli strumenti

Creare traduzioni

Apri una voce nell’editor. Nella barra laterale compare il pannello Translations. Per ogni lingua configurata:

  • «Translate» se non esiste ancora una traduzione — clic per crearla
  • «Edit» se esiste — clic per aprirla
  • La lingua corrente è indicata con un segno di spunta

Creando una traduzione, la nuova voce è precompilata dalla lingua di origine e riceve uno slug predefinito {slug-origine}-{locale}. Regola slug e contenuto, poi salva.

Pubblicazione per lingua

Ogni traduzione ha il proprio stato. Pubblica, ritira o programma in modo indipendente. Il francese può restare in bozza mentre l’inglese è online.

API dei contenuti

Parametro locale

Tutte le route dell’API contenuti accettano il parametro di query opzionale locale:

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

Se omesso, si usa la lingua predefinita configurata.

Creare traduzioni via API

Crea una traduzione passando locale e translationOf all’endpoint di creazione:

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

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

La nuova voce condivide il translation_group dell’origine e inizia come bozza.

Elencare le traduzioni

Recupera tutte le traduzioni di una voce:

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

Restituisce l’ID del gruppo e un array di varianti con ID, slug e stati.

CLI

La CLI supporta --locale nei comandi sui contenuti:

# 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...

Seed di contenuti multilingue

I file di seed esprimono le traduzioni con locale e 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" }
      }
    ]
  }
}

La voce della lingua di origine deve precedere le traduzioni nel file seed affinché translationOf si risolva correttamente.

Campi traducibili

Ogni campo ha l’impostazione translatable (predefinito true). Creando una traduzione:

  • i campi traducibili sono precompilati dalla lingua di origine per la modifica
  • i non traducibili sono copiati e mantenuti sincronizzati in tutte le traduzioni del gruppo

I campi di sistema come status, published_at e author_id sono sempre per lingua e non si sincronizzano.

Strategia URL

EmDash non gestisce gli URL per lingua — Astro gestisce il routing. Modelli comuni:

# 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

Usa getRelativeLocaleUrl da astro:i18n per costruire URL corretti indipendentemente dalla modalità di routing.

Importare contenuti multilingue

WordPress con WPML o Polylang

La sorgente di importazione WordPress rileva automaticamente WPML e Polylang. Se rilevati, i contenuti importati includono metadati di locale e gruppo di traduzione.

File WXR

Le esportazioni WXR non includono metadati WPML/Polylang. Importa come lingua unica e crea le traduzioni manualmente, oppure usa --locale per assegnare una lingua a tutti gli elementi importati:

# 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

Passi successivi