EmDash si integra con il routing i18n integrato di Astro per fornire una gestione di contenuti multilingue. Astro gestisce il routing degli URL e il rilevamento delle locale; EmDash gestisce l’archiviazione e il recupero dei contenuti tradotti.
Ogni traduzione è una voce di contenuto completa e indipendente con il proprio slug, stato e cronologia delle revisioni. La versione francese di un post può essere in bozza mentre la versione inglese è pubblicata.
Configurazione
Abilita i18n aggiungendo un blocco i18n alla tua configurazione Astro. EmDash legge questa stessa configurazione per l’elenco delle locale, la locale predefinita e la catena di fallback.
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",
}),
}),
],
});
Quando i18n non è presente nella configurazione Astro, tutte le funzionalità i18n sono disabilitate ed EmDash si comporta come un CMS monolingue.
Come funzionano le traduzioni
EmDash utilizza un modello riga-per-locale. Ogni traduzione è la propria riga nel database con il proprio ID, slug e stato, collegata ad altre traduzioni tramite un identificatore translation_group condiviso. Una tabella di post con tre traduzioni appare così:
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
Questo design significa:
- Slug per locale —
/blog/my-poste/fr/blog/mon-articlefunzionano naturalmente - Pubblicazione per locale — pubblica la versione inglese mantenendo il francese in bozza
- Revisioni per locale — ogni traduzione ha la propria cronologia delle revisioni
- Query singola-locale — le query di lista restituiscono voci solo per una locale
Interrogare contenuti tradotti
Voce singola
Passa locale a getEmDashEntry per recuperare una traduzione specifica. Quando omesso, utilizza per impostazione predefinita la locale 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
Quando non esiste contenuto per la locale richiesta, EmDash segue la catena di fallback definita nella tua configurazione Astro. Dato fallback: { fr: "en" }:
- Prova la locale richiesta (
fr) - Prova la locale di fallback (
en) - Prova la locale predefinita
Il fallback si applica solo alle query di voci singole. Le query di lista restituiscono voci solo per la locale richiesta.
Menu
I menu sono per locale — lo stesso name (es. "primary") può esistere in più locale, tutti collegati tramite un translation_group condiviso. Gli elementi del menu risolvono i loro riferimenti di contenuto rispetto alla versione della locale attiva del contenuto referenziato.
Il seguente componente recupera il menu principale per la locale attiva:
---
import { getMenu } from "emdash";
const menu = await getMenu("primary", { locale: Astro.currentLocale });
---
<nav aria-label="Primary">
<ul>
{menu?.items.map((item) => (
<li><a href={item.url}>{item.label}</a></li>
))}
</ul>
</nav>
Crea traduzioni di un menu esistente dall’elenco Menu dell’admin — gli elementi vengono clonati con reference_id intatto (memorizza il translation_group del contenuto referenziato), quindi i link del nuovo menu puntano automaticamente al contenuto corretto per locale.
Tassonomie (categorie, tag)
I termini sono per locale. Le definizioni (_emdash_taxonomy_defs) sono anch’esse per locale, quindi anche label / labelSingular possono essere tradotti. Il pivot content_taxonomies.taxonomy_id memorizza il translation_group del termine, quindi una singola assegnazione copre ogni locale del contenuto.
L’esempio seguente recupera le categorie e i termini di un post per la locale attiva:
---
import { getTaxonomyTerms, getEntryTerms } from "emdash";
const categories = await getTaxonomyTerms("category", {
locale: Astro.currentLocale,
});
const terms = await getEntryTerms("posts", post.id, undefined, {
locale: Astro.currentLocale,
});
---
Tradurre un contenuto eredita automaticamente le assegnazioni dei termini dalla fonte — devi solo tradurre i termini stessi una volta, e ogni post che li utilizza verrà risolto alla locale corretta al momento della lettura.
Elenco di collezioni
Filtra una collezione per locale:
---
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 di lingua
Usa getTranslations per costruire un selettore di lingua che collega 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>
La funzione getTranslations restituisce tutte le varianti di locale nello stesso gruppo di traduzione:
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" },
// ]
Gestione delle traduzioni nell’Admin
Elenco contenuti
Quando i18n è abilitato, l’elenco dei contenuti mostra:
- Una colonna locale che visualizza la locale di ogni voce
- Un filtro locale nella barra degli strumenti per passare tra le locale
Creazione di traduzioni
Apri qualsiasi voce di contenuto nell’editor. La barra laterale visualizza un pannello Traduzioni che elenca tutte le locale configurate. Per ogni locale:
- “Traduci” appare per le locale senza traduzione — fai clic per crearne una
- “Modifica” appare per le locale con traduzione esistente — fai clic per navigarvi
- La locale corrente è contrassegnata con un segno di spunta
Quando crei una traduzione, la nuova voce viene precompilata con i dati dalla locale di origine e le viene assegnato uno slug predefinito di {slug-origine}-{locale}. Regola lo slug e il contenuto secondo necessità, quindi salva.
Pubblicazione per locale
Ogni traduzione ha il proprio stato. Pubblica, rimuovi la pubblicazione o pianifica le traduzioni in modo indipendente. La versione francese può essere in bozza mentre la versione inglese è live.
API dei contenuti
Parametro locale
Tutte le route dell’API dei contenuti accettano un parametro di query locale opzionale:
GET /_emdash/api/content/posts?locale=fr
GET /_emdash/api/content/posts/my-post?locale=fr
Quando omesso, utilizza per impostazione predefinita la locale predefinita configurata.
Creazione di traduzioni tramite API
Crea una traduzione passando locale e translationOf all’endpoint di creazione del contenuto:
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 della voce di origine e inizia come bozza.
Elenco delle traduzioni
Recupera tutte le traduzioni per una data voce:
GET /_emdash/api/content/posts/01ABC.../translations
Restituisce l’ID del gruppo di traduzione e un array di varianti di locale con i loro ID, slug e stati.
CLI
La CLI supporta i flag --locale sui comandi di contenuto:
# Elenca i post in francese
emdash content list posts --locale fr
# Ottieni una voce specifica in francese
emdash content get posts my-post --locale fr
# Crea una traduzione francese di una voce esistente
emdash content create posts --locale fr --translation-of 01ABC...
Seeding di contenuti multilingue
I file seed esprimono le traduzioni utilizzando 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 locale di origine deve apparire prima delle sue traduzioni nel file seed affinché i riferimenti translationOf si risolvano correttamente.
Traducibilità dei campi
Ogni campo ha un’impostazione translatable (predefinito: true). Quando si crea una traduzione:
- I campi traducibili vengono precompilati dalla locale di origine per la modifica
- I campi non traducibili vengono copiati e mantenuti sincronizzati in tutte le traduzioni del gruppo
I campi di sistema come status, published_at e author_id sono sempre per locale e mai sincronizzati.
Strategia URL
EmDash non gestisce gli URL delle locale — Astro gestisce il routing. Modelli comuni:
# prefix-other-locales (predefinito Astro)
/blog/my-post → en (locale predefinita, nessun prefisso)
/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.
Importazione di contenuti multilingue
Importa contenuti WordPress tramite lo strumento di migrazione admin — vedi Importazione contenuti e Migrazione da WordPress. Un’esportazione WXR non porta la struttura di locale e gruppo di traduzione che WPML o Polylang aggiungono, quindi i contenuti importati arrivano nella tua locale predefinita.
Per costruire traduzioni da contenuti importati, crea la voce tradotta e collegala all’originale:
emdash content create posts --locale fr --translation-of 01ABC...
Questo è lo stesso flusso di lavoro --locale / --translation-of mostrato in Seeding di contenuti multilingue sopra, applicato dopo il completamento dell’importazione.
Prossimi passi
- Interrogare contenuti — Riferimento completo dell’API di query
- Lavorare con i contenuti — Gestione dei contenuti admin
- Routing i18n Astro — Configurazione del routing Astro