Astro
Feeds Astro's i18n. Doesn't replace it.
An integration that plugs into content collections. Translations live in the graph; Astro renders them at build time or on demand.
One config block.
import { defineConfig } from "astro/config";
import paragraphs from "@paragraphs/astro";
export default defineConfig({
integrations: [
paragraphs({
apiKey: process.env.PARAGRAPHS_API_KEY,
projectId: process.env.PARAGRAPHS_PROJECT_ID,
locales: ["en-GB", "es-ES", "fr-FR"],
defaultLocale: "en-GB",
// Sync mode: at-build or on-demand
mode: "at-build",
}),
],
i18n: {
defaultLocale: "en-GB",
locales: ["en-GB", "es-ES", "fr-FR"],
routing: { prefixDefaultLocale: false },
},
}); Locale-aware reads, no boilerplate.
---
import { getCollection } from "astro:content";
import { getEntry } from "@paragraphs/astro";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.flatMap((post) =>
["en-GB", "es-ES", "fr-FR"].map((locale) => ({
params: { locale, slug: post.slug },
props: { post },
}))
);
}
const { post } = Astro.props;
const { locale } = Astro.params;
const translated = await getEntry(post, locale);
---
<h1>{translated.data.title}</h1>
<article set:html={translated.body} /> Incremental, fingerprint-aware.
The integration caches translations by fingerprint between builds. Edit a single blog post, only the affected fingerprints get re-fetched. A 500-post site re-translates incrementally — a build that took 4 minutes still takes 4 minutes after adding 8 locales.
Astro 5 starter on GitHub.
No credit card. 100,000 words on the free tier. Self-serve onboarding.