EmDash Multilingual Sites: i18n Built In
Internationalization is one of those features that most CMS platforms treat as an afterthought. With WordPress you're buying a plugin. With many headless CMSes you're stitching together locale routing and translation fields manually. EmDash takes a different approach: i18n is part of the core data model, not a bolt-on. Every content entry has a locale. Every entry can belong to a translation group. The query APIs are locale-aware from the start.
The Core Model: Locale and Translation Groups
Each content entry in EmDash carries a locale field that identifies what language it's written in — en, fr, de, and so on using standard IETF language tags. When you create a translated version of an entry, you link it to the original via a translationGroup ID. This group ID is what lets EmDash surface the alternate-language version of any given page — for hreflang tags, for language switchers, for alternate URL routing.
The translation group model is flexible. You don't have to translate every entry. An English article can exist without a French counterpart. The site just won't show that article when the active locale is French unless you've configured a fallback.
Setting Up Locales
Locales are configured in your astro.config.mjs via EmDash's locales option. You specify a default locale and any additional locales you want to support. Once configured, EmDash's content APIs respect the active locale automatically when you pass a locale parameter.
// Locale-aware content query
const { entries, cacheHint } = await getEmDashCollection("learn", {
locale: "fr",
orderBy: { published_at: "desc" },
limit: 10,
});
// Fetching a single entry in a specific locale
const { entry, cacheHint: hint } = await getEmDashEntry("learn", slug, {
locale: "fr",
});
// Getting all translations of an entry (via MCP: content_translations)
// Returns all entries in the same translationGroup Per-Field Translatability
Not every field needs to be translated. A publication date, a sort order, or a numeric field might be the same regardless of locale. EmDash lets you mark individual fields as translatable or not in the schema. Fields marked as non-translatable sync their value across all locales in a translation group — change it in one locale, it updates everywhere. Fields marked as translatable are independent per locale.
In practice, you'll typically mark text fields (title, body, excerpt, description) as translatable and leave structural fields (sort order, parent reference, category assignments) as shared. This cuts down the translation workload significantly and prevents structural drift between locale variants.
Multilingual Routing Patterns
The standard routing pattern for multilingual Astro sites uses locale prefixes: /en/learn/my-article and /fr/learn/mon-article. Each locale gets its own slug. The translation group links them so search engines and language switchers can connect the variants. For the default locale, you can optionally omit the prefix (/learn/my-article instead of /en/learn/my-article), which is common practice for English-default sites.
i18n Feature Comparison
Here's how EmDash's i18n capabilities compare to the most common alternatives:
| Feature | EmDash | WordPress + WPML | Contentful |
| Locale-aware queries | Built in | Plugin required ($99+/yr) | Built in |
| Translation groups | Built in | Plugin required | Built in |
| Per-field translatability | Yes | Limited (via WPML settings) | Yes |
| Cost to enable | $0 | $99–$199/year | Plan-gated |
| hreflang tag generation | Via EmDashHead | Via plugin | Manual |
Current State in EmDash v0.1
I want to be honest about where things stand. EmDash v0.1 has the data model and query APIs for i18n in place. The locale field on content entries works. Translation groups work. The content_translations MCP tool works. The admin UI for managing translations is still being developed — right now, creating a translated entry means creating a new entry via MCP or the API with the translationOf parameter, rather than clicking a "Translate" button in the admin panel. The foundation is solid; the editorial UX is coming.
If you're building a multilingual site on EmDash today, you can do it — it just requires working at the API level for translation management. For teams that are already comfortable with MCP-based workflows, this isn't a significant barrier.
i18n built into the core means you're not paying a tax on multilingualism. The data model handles it from day one, so the decision to go multilingual later doesn't require rearchitecting anything.
Creating a Translation via MCP
To create a French translation of an existing English article, you'd call content_create with the translationOf parameter pointing to the original entry's ID and locale set to fr. EmDash automatically adds the new entry to the same translation group and populates any non-translatable fields from the original. You then provide translated values for the translatable fields.
Multilingual content is one of the highest-leverage moves for reaching a global audience. The barrier to doing it well has historically been tooling cost and complexity. EmDash removes both.
What Is EmDash?