Skip to content

openscript/astro-loader-i18n

Repository files navigation

astro-loader-i18n

codecov NPM Downloads npm bundle size

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.

Features

โœ… Automatic locale detection

  • 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
    
  • Supports nested folders:

    ๐Ÿ“‚ Nested folder structure example
    . (project root)
    โ””โ”€โ”€ src
        โ””โ”€โ”€ content
            โ””โ”€โ”€ pages
                โ”œโ”€โ”€ de-CH
                โ”‚   โ”œโ”€โ”€ about.mdx
                โ”‚   โ””โ”€โ”€ projects
                โ”‚       โ”œโ”€โ”€ project1.mdx
                โ”‚       โ””โ”€โ”€ project2.mdx
                โ””โ”€โ”€ zh-CN
                    โ”œโ”€โ”€ about.mdx
                    โ””โ”€โ”€ projects
                        โ”œโ”€โ”€ project1.mdx
                        โ””โ”€โ”€ project2.mdx
    

โœ… Translation mapping

  • Generates a translation identifier to easily match different language versions of content.

โœ… Schema support

  • Helps to define schemas for your localized content.
  • Add translationId 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.

โœ… getStaticPaths() helpers included

  • 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.

โœ… Type safety

  • Keeps Astro.props type-safe.

Usage

  1. Install the package astro-loader-i18n:

    npm
    npm install astro-loader-i18n
    yarn
    yarn add astro-loader-i18n
    pnpm
    pnpm add astro-loader-i18n
  2. 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",
        },
      },
    };
  3. 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,
      },
    });
  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:

    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:

    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:

    import { i18nPropsAndParams } from "astro-loader-i18n";
    import sluggify from "limax"; // sluggify is used to create a slug from the title
    
    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,
        // `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.

In-file localized content

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.

  1. Create a collection:

    ๐Ÿ“„ Infile collection example
    . (project root)
    โ””โ”€โ”€ src
       โ””โ”€โ”€ content
           โ””โ”€โ”€ navigation
               โ”œโ”€โ”€ footer.yml
               โ””โ”€โ”€ main.yml
    
    ๐Ÿ“„ Content of main.yml
    # 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: ๅ…ณไบŽๆˆ‘
  2. Use extendI18nLoaderSchema and localized to define the schema:

    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
          ),
        })
      ),
    });
  3. 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:

    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" }, ...]

Virtual i18n collections

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:

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.

i18nLoader

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.

i18nContentLoader

i18nContentLoader creates multiple entries from a single yaml or json file.

See i18nLoader for more information.

localized

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.

extendI18nLoaderSchema

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.

i18nLoaderSchema

i18nLoaderSchema is a schema that is used by the i18nLoader and i18nContentLoader. It defines the properties that are added to the entrys data object.

i18nPropsAndParams

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.

createI18nCollection

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.

resolvePath

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

  • Improve types of params returned by i18nPropsAndParams
  • Include a language switcher Astro component

Wish list

To make internationalization easier, Astro could offer the following features:

  • Provide routing information to getStaticPaths() such as the routePattern to avoid manual repetition. Also see this pull request: withastro/astro#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.

About

An Astro glob content loader for i18n files and folder structures. ๐ŸŒ

Topics

Resources

License

Stars

Watchers

Forks

Contributors 4

  •  
  •  
  •  
  •