The i18n integration provides runtime translations while keeps type-checking, preventing most of the runtime errors in this kind of libraries.
It is intended to be used for projects with a large number of languages or locales and websites with Astro Islands that require some translations. For static or small projects, I’d recommend using the official Astro i18n or any other existing type-safe libraries. In any case, give it a try!
This package uses
@astro-tools/transfer-state
for managing the state.
The model managed by the integration and the library is an object of type I18nTranslations
. This object must be serializable and it will be transferred to the client.
Translations
Section titled “Translations”The key
of the translation must be type string
. The supported types for the translation value is a string
or Record<string, string>
(plurals only) with interpolation values surrounded by {}
, like the following example:
{ "hello": "Hello {name}!"}
Variables in the translations can be typed to number
and string
using the following format:
{ "hello": "Hello {name:string}! click here to get {points:number} points!"}
Plurals
Section titled “Plurals”A plural is a Record<string, string>
in which the keys are each plural variant with the string
value following the same format as any other non-plural translation.
For example, a plural translation compatible with Intl.PluralRules
could be:
{ "animals": { "zero": "There isn't any animal", "one": "There is {value} animal", "other": "There are {value} animals" }}
For setting up the internationalization, include the integration in your Astro project:
- Install the library and its dependencies using your preferred package manager:
npm i -D @astro-tools/i18n
- Add the integration to your project configuration:
astro.config.ts import { dirname, join } from 'node:path';import { fileURLToPath } from 'node:url';import { defineConfig } from 'astro/config';import { i18n } from '@astro-tools/i18n';import { typesLoader } from '@/i18n/types-loader';const resolve = (path: string) =>join(dirname(fileURLToPath(import.meta.url)), path);export default defineConfig({integrations: [i18n({types: typesLoader(),providers: {translations: resolve('./src/i18n/translations-provider.ts'),plural: resolve('./src/i18n/plural-provider.ts'),},}),],});
For more details about the integration options, continue reading.
To extract the typings properly, define the types
function which should return an object of type I18nTranslations
:
import { readFile } from 'node:fs/promises';import { join, parse } from 'node:path';import { fileURLToPath } from 'node:url';import type { I18nIntegrationTypesLoader } from '@astro-tools/i18n';
export function typesLoader(): I18nIntegrationTypesLoader { return async () => { const base = parse(fileURLToPath(import.meta.url)).dir; const buffer = await readFile(join(base, 'translations', 'en-US.json')); return JSON.parse(buffer.toString('utf-8')); };}
{ "home.title": "Sample page", "home.description": "{companyName} sample page built by {ownerName}!", "rating.title": "Rate {library:string}!", "rating.stars": { "zero": "There are no votes yet!", "one": "This library has {stars:number} star!", "other": "This library has {stars:number} stars!" }}
The result is the overload functions of the function t
, which can be used to render translations:
export function t(key: 'home.title'): string;export function t(key: 'home.description', values: { companyName: string, ownerName: string }): string;export function t(key: 'rating.title', values: { library: string }): string;export function t(key: 'rating.stars', count: number, values: { stars: number }): string;
Pluralized translations require an additional argument
count: number
, which is used to select the proper plural.
Providers
Section titled “Providers”The providers allow to configure the behaviors of the library in runtime. For example, a provider could execute a block of code for the server and other block for the client.
The implementation is delegated to the project but the sub-package @astro-tools/i18n/providers
provides default providers that should be enough for simple use cases.
I18nTranslationsProvider
Section titled “I18nTranslationsProvider”This provider loads the proper translations for a locale: string
:
import type { I18nTranslationsProvider } from '@astro-tools/i18n';
const translations = new Map();translations.set('en-US', () => import('./translations/en-US.json').then((json) => json.default),);translations.set('es-ES', () => import('./translations/es-ES.json').then((json) => json.default),);
const translationsProvider: I18nTranslationsProvider = async (locale) => { const loader = translations.get(locale); if (!loader) { throw new Error(`Missing translations for locale ${locale}`); }
return await loader();};
export default translationsProvider;
A context can be recieved as second argument which is arbitrary data coming from the
use
function explained below.
I18nPluralProvider
Section titled “I18nPluralProvider”This provider selects the proper plural from a translation using the count: number
argument.
The default provider uses Intl.PluralRules
for selecting the plural:
import { pluralProviderFactory } from '@astro-tools/i18n/providers';
const pluralProvider = pluralProviderFactory();
export default pluralProvider;
The integration exposes the virtual module @astro-tools:i18n
with the required functions for managing translations.
Configure
Section titled “Configure”Before render any translation, configure the locale and fallback locale using the function use(options: I18nUseOptions): Promise<void>
.
It is recommended to create a wrapper with your own logic following DRY principle:
import { use } from '@astro-tools:i18n';
export async function useTranslations( locale: string, fallbackLocale?: string,): Promise<void> { await use({ locale, fallbackLocale: fallbackLocale || 'en-US', });}
Context
Section titled “Context”There is a context
option that be used in the use
function for pass arbitrary data to the translation provider. For example, the context could be used to load partial translations.
The context won’t be transferred to the client side, so it can be any type of data.
Translate
Section titled “Translate”Then, for translating keys, just use the previous useTranslations
function with the desired locale for the page being rendered and use the t
function to render a translation.
If a translation does not exists in the configured locale
, then the fallbackLocale
will be used. If the translation still missing, the key
will be rendered.
---import Output from '@/libs/examples/Output.svelte';
import { useTranslations } from '@/i18n/use-translations';
import { t } from '@astro-tools:i18n';
import ClientSideExample from './ClientSideExample.svelte';
interface Props { id: string;}
const { id } = Astro.props;
await useTranslations('es-ES');---<Output text={t('home.title')}></Output><Output text={t('home.description', { ownerName: 'Doc', companyName: 'Astro Tools' })}></Output><br /><button type="button" id="i18n-button">Click me to hydrate!</button><br /><ClientSideExample {id} client:on="click #i18n-button" />
<script lang="ts">import { onMount } from 'svelte';
import Output from '@/libs/examples/Output.svelte';import { notifyHydration } from '@/libs/examples/hydration';
import { t } from '@astro-tools:i18n';
export let id: string;
let hydrated = false;let stars = 0;
onMount(() => { setInterval(() => stars++, 1000); hydrated = true; notifyHydration(id);});</script>
<p>{stars}</p><Output text={t('rating.title', { library: '@astro-tools/i18n' })} {hydrated} /><Output text={t('rating.stars', stars, { stars: stars })} {hydrated} />
{ "home.title": "Página de ejemplo", "home.description": "Página de ejemplo de {companyName} construida por {ownerName}", "rating.title": "Valora {library:string}!", "rating.stars": { "one": "¡Esta librería tiene {stars:number} estrella!", "other": "¡Esta librería tiene {stars:number} estrellas!" }}
{ "home.title": "Sample page", "home.description": "{companyName} sample page built by {ownerName}!", "rating.title": "Rate {library:string}!", "rating.stars": { "zero": "There are no votes yet!", "one": "This library has {stars:number} star!", "other": "This library has {stars:number} stars!" }}
0