An Astro content loader for i18n files and folder structures.
npm install astro-loader-i18n

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
``plaintext`
. (project root)
โโโ README.md
โโโ src
โโโ content
โโโ pages
โโโ de-CH
โ โโโ about.mdx
โ โโโ projects.mdx
โโโ zh-CN
โโโ about.mdx
โโโ projects.mdx
๐ File name suffix example
`plaintext`
. (project root)
โโโ src
โโโ content
โโโ pages
โโโ about.de-CH.mdx
โโโ about.zh-CN.mdx
โโโ projects.de-CH.mdx
โโโ projects.zh-CN.mdx
- Supports nested folders:
๐ Nested folder structure example
`plaintext`
. (project root)
โโโ src
โโโ content
โโโ pages
โโโ de-CH
โ โโโ about.mdx
โ โโโ projects
โ โโโ project1.mdx
โ โโโ project2.mdx
โโโ zh-CN
โโโ about.mdx
โโโ projects
โโโ project1.mdx
โโโ project2.mdx
and locale to the schema by using extendI18nLoaderSchema. You need this when using i18nLoader or i18nContentLoader.
- When you have multiple locales in a single file, you can use localized to define the necessary schema. This is useful when using i18nContentLoader.$3
- 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.$3
- Keeps Astro.props type-safe.Usage
1. Install the package
astro-loader-i18n:
npm
`bash
npm install astro-loader-i18n
`
yarn
`bash
yarn add astro-loader-i18n
`
pnpm
`bash
pnpm add astro-loader-i18n
`
2. Configure locales, a default locale and segments for example in a file called
site.config.ts:
`typescript
export const C = {
LOCALES: ["de-CH", "zh-CN"],
DEFAULT_LOCALE: "de-CH" as const,
SEGMENT_TRANSLATIONS: {
"de-CH": {
files: "dateien",
},
"zh-CN": {
files: "files",
},
},
};
`3. Configure i18n in
astro.config.ts:
`typescript
import { defineConfig } from "astro/config";
import { C } from "./src/site.config"; export default defineConfig({
i18n: {
locales: C.LOCALES,
defaultLocale: C.DEFAULT_LOCALE,
},
});
`4. Define collections using
astro-loader-i18n in content.config.ts. Don't forget to use extendI18nLoaderSchema or localized to extend the schema with the i18n specific properties:
`typescript
import { defineCollection, z } from "astro:content";
import { extendI18nLoaderSchema, i18nContentLoader, i18nLoader, localized } from "astro-loader-i18n";
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: extendI18nLoaderSchema(
z.object({
title: z.string(),
})
),
});
/*
Example of a content file:
navigation:
de-CH:
- path: /projekte
title: Projekte
- path: /ueber-mich
title: รber mich
zh-CN:
- path: /zh/projects
title: ้กน็ฎ
- path: /zh/about-me
title: ๅ
ณไบๆ
*/
const infileCollection = defineCollection({
loader: i18nContentLoader({ pattern: "*/[^_].{yml,yaml}", base: "./src/content/infile" }),
schema: extendI18nLoaderSchema( //
extendI18nLoaderSchema defines translationId and locale for you in the schema.
z.object({
navigation: localized( // localized defines an object with the locale as key and applies the schema you provide to the value.
z.array(
z.object({
path: z.string(),
title: z.string(),
})
),
C.LOCALES
),
})
),
}); export const collections = {
files: filesCollection,
folder: folderCollection,
infile: infileCollection,
};
`5. Create content files in the defined structure:
> โ ๏ธ WARNING
> The content files need to be structured according to the locales defined in
astro.config.ts.
`
. (project root)
โโโ src
โโโ content
โโโ pages
โโโ about.de-CH.mdx
โโโ about.zh-CN.mdx
โโโ projects.de-CH.mdx
โโโ projects.zh-CN.mdx
`6. Retrieve the
locale and translationId identifier during rendering:
`typescript
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
`7. Use
i18nPropsAndParams to provide params and get available translations paths via the page props:
`typescript
import { i18nPropsAndParams } from "astro-loader-i18n";
import sluggify from "limax"; // sluggify is used to create a slug from the title export const getStaticPaths = async ({ routePattern }) => {
// โ ๏ธ If you are using Astro <5.14.0, you need to hardcode the routePattern here.
// 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,
//
generateSegments is a function that generates per entry individual segments.
generateSegments: (entry) => ({ slug: sluggify(entry.data.title) }),
});
};
`8. Use
Astro.props.translations to provide a same site language switcher.$3
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
i18nContentLoader loader to load in-file localized content. If you want to load a single file, you can use the i18nFileLoader.1. Create a collection:
๐ Infile collection example
`plaintext
. (project root)
โโโ src
โโโ content
โโโ navigation
โโโ footer.yml
โโโ main.yml
`
๐ Content of main.yml
`yaml
# src/content/navigation/main.yml
navigation:
de-CH:
- path: /projekte
title: Projekte
- path: /ueber-mich
title: รber mich
zh-CN:
- path: /zh/projects
title: ้กน็ฎ
- path: /zh/about-me
title: ๅ
ณไบๆ
`
1. Use
extendI18nLoaderSchema and localized to define the schema:
`typescript
const infileCollection = defineCollection({
loader: i18nContentLoader({ pattern: "*/[^_].{yml,yaml}", base: "./src/content/infile" }),
schema: extendI18nLoaderSchema( // extendI18nLoaderSchema defines translationId and locale for you in the schema.
z.object({
navigation: localized( // localized defines an object with the locale as key and applies the schema you provide to the value.
z.array(
z.object({
path: z.string(),
title: z.string(),
})
),
C.LOCALES
),
})
),
});
`1. When you get the collection, you will receive for each locale the localized content. For example if you have two locales
de-CH and zh-CN with two files main.yml and footer.yml, you will get four entries in the collection:
`typescript
import { getCollection } from "astro:content"; const navigation = await getCollection("infile");
console.log(navigation[0].data.locale); // e.g. de-CH
console.log(navigation[0].data.translationId); // e.g. src/content/infile/main.yml
console.log(navigation[0].data.navigation); // e.g. [{ path: "/projekte", title: "Projekte" }, ...]
`$3
Sometimes you want to translate a page 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:`typescript
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,
});
};
`API
Below you can find a description of all exported functions and types.
$3
i18nLoader parses i18n information from file names or folder structures.As this is a wrapper around the
glob() loader, you can use all options from the glob() loader. See the Astro documentation for more information.It adds the following properties to an entrys
data object:-
locale: The locale of the entry. This is either the folder name or the file name suffix.
- translationId: The translation identifier. This helps to identify the same content in different languages.
- contentPath: The path to the file relative to the content base path. This is useful if you want to add the folder names into the path. For example src/content/pages/de-CH/deeply/nested/about.mdx it would be deeply/nested.
- basePath: The base path from the Astro config. This is a workaround, because from getStaticPaths() you don't have access to the base path and you need it for generating paths.$3
i18nContentLoader creates multiple entries based on yaml or json files that contain i18n text fields.See i18nLoader for more information.
$3
i18nFileLoader creates multiple entries based on a single yaml or json that contain i18n text fields.It is a wrapper around the
file() loader. See the Astro documentation for more information.$3
localized is a helper function to define a schema for in-file localized content. It takes a schema and an array of locales and returns a schema that is an object with the locale as key and the schema as value.Parameters:
-
schema: The schema to apply to the value of the object.
- locales: An array of locales to use as keys for the object.
- partial: Optional. If true, not all locales need to be defined in the schema.$3
extendI18nLoaderSchema is a helper function to extend the schema of the i18nLoader and i18nContentLoader. It adds the translationId, locale, contentPath and basePath properties to the schema.$3
i18nLoaderSchema is a schema that is used by the i18nLoader and i18nContentLoader. It defines the properties that are added to the entrys data object.$3
i18nPropsAndParams is a helper function to generate the params and props object for getStaticPaths() and to provide the translations object to the page props.Parameters:
-
collection: The collection to use. This can be a collection from getCollection() or a virtual collection created with createI18nCollection().
- options: An object with the following properties:
- defaultLocale: The default locale to use.
- routePattern: The route pattern to use. This is the pattern that is used in the getStaticPaths() function. Unfortunately there is no way to access the routePattern, that's why we need to define it here again.
- segmentTranslations: An object with the segment translations. This is used to translate the segments in the URL.
- generateSegments: (Optional) A function that generates the segments for each entry. This is useful if you want to generate slugs or other segments.
- localeParamName: (Optional) The name of the locale parameter in the URL. This is used to generate the URL for the translations object.
- prefixDefaultLocale: (Optional) If true, the default locale will be prefixed to the URL. This is useful if you want to have a clean URL for the default locale.It returns an object with
params and props. props contains additionally a translations object with the paths to the corresponding content of all existing translations. The translatedPath is the current entry path.$3
createI18nCollection creates a virtual collection that is not based on any content. This is useful if you want to create a collection for a page that is not based on i18n content.$3
resolvePath is a helper function that connects path segments and deals with slashes.Examples
Made by the author of
astro-loader-i18n:- Test project (Source): Minimal example of how to use
astro-loader-i18n with Astro.
- Astro Theme International (Demo / Source): A demo theme with the goal to be as international as possible.
- r.obin.ch (Demo / Source): A personal website with a blog and projects, built with Astro and astro-loader-i18n.Made by the community:
- eCamp3 (Demo / Source): A website of an application for camp planning.
Roadmap
- [x] Add
i18nFileLoader that is based on Astros file() loader
- [ ] Improve types of params returned by i18nPropsAndParams
- [ ] Include a language switcher Astro componentWish list
To make internationalization easier, Astro could offer the following features:
- [x] Provide routing information to
getStaticPaths() such as the routePattern to avoid manual repetition. Also see this pull request: https://github.com/withastro/astro/pull/13520
- [ ] Allow to define custom parameters for getStaticPaths() like paginate from integrations and loaders. This makes integrating additional helpers for building getStaticPaths() way easier.
- [ ] Allow to define different schemas for input (this already exists, today) and output of a loader. This is useful if a loader transforms the data. Currently the schema wouldn't match the output of the loader anymore.
- [ ] Allow to define additional custom properties from loaders apart from the data object, that are available inside getStaticPaths()` and while rendering. This is useful if a loader calculates additional properties that later used in the template and are not necessarily part of the data object to avoid collisions with the user provided data.