astro-loader-i18n
is a content loader for internationalized content in Astro. It builds on top of Astroโs glob()
loader and helps manage translations by detecting locales, mapping content, and enriching getStaticPaths
.
-
Extracts locale information from file names or folder structures:
๐ Folder structure example
. (project root) โโโ README.md โโโ src โโโ content โโโ pages โโโ de-CH โ โโโ about.mdx โ โโโ projects.mdx โโโ zh-CN โโโ about.mdx โโโ projects.mdx
๐ File name suffix example
. (project root) โโโ src โโโ content โโโ pages โโโ about.de-CH.mdx โโโ about.zh-CN.mdx โโโ projects.de-CH.mdx โโโ projects.zh-CN.mdx
- Generates a translation identifier to easily match different language versions of content.
- Provides predefined schemas for
content.config.ts
- Loader schema:
i18nLoaderSchema
- In-file schema:
i18nInfileSchema
- Loader schema:
- Adds a
translationId
andlocale
to each content item.
- Includes a helper utility called
i18nPropsAndParams
- Helps to fill and translate URL params like
[...locale]/[files]/[slug]
, whereas[...locale]
is the locale,[files]
is a translated segment and[slug]
is the slug of the title. - Adds a
translations
object to each entry, which contains paths to the corresponding content of all existing translations.
- Helps to fill and translate URL params like
- Keeps
Astro.props
type-safe.
-
Install the package
astro-loader-i18n
(andlimax
for slug generation):npm
npm install astro-loader-i18n limax
yarn
yarn add astro-loader-i18n limax
pnpm
pnpm add astro-loader-i18n limax
-
Configure locales, a default locale and segments for example in a file called
site.config.ts
:export const C = { LOCALES: ["de-CH", "zh-CN"], DEFAULT_LOCALE: "de-CH" as const, SEGMENT_TRANSLATIONS: { "de-CH": { files: "dateien", }, "zh-CN": { files: "files", }, }, };
-
Configure i18n in
astro.config.ts
:import { defineConfig } from "astro/config"; import { C } from "./src/site.config"; export default defineConfig({ i18n: { locales: C.LOCALES, defaultLocale: C.DEFAULT_LOCALE, }, });
-
Define collections using
astro-loader-i18n
incontent.config.ts
. Don't forget to useextendI18nLoaderSchema
orextendI18nInfileSchema
to extend the schema with the i18n specific properties:import { defineCollection, z } from "astro:content"; import { extendI18nInfileSchema, extendI18nLoaderSchema, i18nLoader } from "astro-loader-i18n"; import { glob } from "astro/loaders"; import { C } from "./site.config"; const filesCollection = defineCollection({ loader: i18nLoader({ pattern: "**/[^_]*.{md,mdx}", base: "./src/content/files" }), schema: extendI18nLoaderSchema( z.object({ title: z.string(), }) ), }); const folderCollection = defineCollection({ loader: i18nLoader({ pattern: "**/[^_]*.{md,mdx}", base: "./src/content/folder" }), schema: z.object({ title: z.string(), }), }); const infileCollection = defineCollection({ loader: glob({ pattern: "**/[^_]*.{yml,yaml}", base: "./src/content/infile" }), schema: extendI18nInfileSchema( z.array( z.object({ path: z.string(), title: z.string() }) ), C.LOCALES), }); export const collections = { files: filesCollection, folder: folderCollection, infile: infileCollection, };
-
Create content files in the defined structure:
โ ๏ธ WARNING The content files need to be structured according to the inastro.config.ts
defined locales.. (project root) โโโ src โโโ content โโโ pages โโโ about.de-CH.mdx โโโ about.zh-CN.mdx โโโ projects.de-CH.mdx โโโ projects.zh-CN.mdx
-
Retrieve the
locale
andtranslationId
identifier during rendering:import { getCollection } from "astro:content"; const pages = await getCollection("files"); console.log(pages["data"].locale); // e.g. de-CH console.log(pages["data"].translationId); // e.g. src/content/files/about.mdx
-
Use
i18nPropsAndParams
to provide params and get available translations paths via the page props:import { i18nPropsAndParams } from "astro-loader-i18n"; export const getStaticPaths = async () => { // โ ๏ธ Unfortunately there is no way to access the routePattern, that's why we need to define it here again. // see https://github.com/withastro/astro/pull/13520 const routePattern = "[...locale]/[files]/[slug]"; const filesCollection = await getCollection("files"); return i18nPropsAndParams(filesCollection, { defaultLocale: C.DEFAULT_LOCALE, routePattern, segmentTranslations: C.SEGMENT_TRANSLATIONS, }); };
-
Finally check
Astro.props.translations
to link the other pages.
Sometimes to have multilingual content in a single file is more convenient. For example data for menus or galleries. This allows sharing untranslated content across locales.
Use the standard glob()
loader to load infile i18n content.
-
Create a collection:
๐ Infile collection example
. (project root) โโโ src โโโ content โโโ navigation โโโ footer.yml โโโ main.yml
๐ Content of
main.yml
# src/content/navigation/main.yml de-CH: - path: /projekte title: Projekte - path: /ueber-mich title: รber mich zh-CN: - path: /zh/projects title: ้กน็ฎ - path: /zh/about-me title: ๅ ณไบๆ
-
Use
extendI18nInfileSchema
to define the schema:const infileCollection = defineCollection({ loader: glob({ pattern: "**/[^_]*.{yml,yaml}", base: "./src/content/infile" }), schema: extendI18nInfileSchema( z.array( z.object({ path: z.string(), title: z.string(), }) ), C.LOCALES ), });
Sometimes you want to translate that is not based on i18n content. For example an index page or a 404 page.
createI18nCollection
allows you to create a virtual collection that is not based on any content:
export const getStaticPaths = async () => {
const routePattern = "[...locale]/[files]";
const collection = createI18nCollection({ locales: C.LOCALES, routePattern });
return i18nPropsAndParams(collection, {
defaultLocale: C.DEFAULT_LOCALE,
routePattern,
segmentTranslations: C.SEGMENT_TRANSLATIONS,
});
};