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-poste/fr/blog/mon-articlefunzionano 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" }:
- Lingua richiesta (
fr) - Lingua di fallback (
en) - 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
- Querying Content — Riferimento completo alle query
- Working with Content — Gestione contenuti nell’admin
- Astro i18n routing — Configurazione routing Astro