-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(i18n): add new i18n library and its documentation (#18)
* feat: add i18n library * feat(i18n): integration with basic example * feat(i18n): add documentation and fix minor issues * minor fixes * fix types * fix deps * remove caching temporariliy
- Loading branch information
Showing
32 changed files
with
582 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { readFile } from 'fs'; | ||
|
||
import { mapTranslations } from './map-translations.mjs'; | ||
|
||
export function i18nSchemaLoader(filePath) { | ||
return () => new Promise((resolve, reject) => { | ||
readFile(filePath, (err, data) => { | ||
if (err) return reject(err); | ||
resolve(mapTranslations(JSON.parse(data.toString()))); | ||
}); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
function getTranslations(propOrObj, stack = []) { | ||
if (typeof propOrObj === 'string') { | ||
return [{ key: stack.join('.'), value: propOrObj }]; | ||
} | ||
|
||
const keys = Object.keys(propOrObj); | ||
return keys.flatMap(key => getTranslations(propOrObj[key], stack.concat(key))); | ||
} | ||
|
||
export function mapTranslations(propOrObj) { | ||
return getTranslations(propOrObj).reduce((obj, translation) => { | ||
obj[translation.key] = translation.value; | ||
return obj; | ||
}, {}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"home": { | ||
"title": "Sample page", | ||
"description": "{companyName} sample page built by {ownerName}!" | ||
}, | ||
"rating": { | ||
"title": "Rate {library:string}!", | ||
"stars": "This library has {stars:number} stars!", | ||
"empty": "There are no votes yet!" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"home": { | ||
"title": "Página de ejemplo", | ||
"description": "Página de ejemplo de {companyName} construida por {ownerName}" | ||
}, | ||
"rating": { | ||
"title": "Valora {library:string}!", | ||
"stars": "Esta librería tiene {stars:number} estrellas!" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
apps/docs/src/content/docs/internationalization/_examples/i18n/ClientSideExample.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<script lang="ts"> | ||
import { Output } from '@astro-tools/docs-utils/components'; | ||
import { notifyHydration } from '@astro-tools/docs-utils/hydration/index.js'; | ||
import { t } from '@astro-tools:i18n'; | ||
import { onMount } from 'svelte'; | ||
export let id: string; | ||
let count: number; | ||
let hydrated = false; | ||
onMount(() => { | ||
count = 10; | ||
hydrated = true; | ||
notifyHydration(id); | ||
}); | ||
</script> | ||
|
||
<Output text={t('rating.title', { library: '@astro-tools/i18n' })} {hydrated} /> | ||
{#if count > 0} | ||
<Output text={t('rating.stars', { stars: 5 })} {hydrated} /> | ||
{:else} | ||
<!-- This translation is missing for es-ES --> | ||
<Output text={t('rating.empty')} {hydrated} /> | ||
{/if} |
22 changes: 22 additions & 0 deletions
22
apps/docs/src/content/docs/internationalization/_examples/i18n/Example.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
--- | ||
import { Output } from '@astro-tools/docs-utils/components'; | ||
import { t } from '@astro-tools:i18n'; | ||
import { useTranslations } from './use-translations'; | ||
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" /> |
21 changes: 21 additions & 0 deletions
21
apps/docs/src/content/docs/internationalization/_examples/i18n/map-translations.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { I18nTranslations } from '@astro-tools/i18n'; | ||
|
||
interface Translations { | ||
[key: string]: Translations | string; | ||
} | ||
|
||
function getTranslations(propOrObj: Translations | string, stack: Array<string> = []): Array<{ key: string, value: string }> { | ||
if (typeof propOrObj === 'string') { | ||
return [{ key: stack.join('.'), value: propOrObj }]; | ||
} | ||
|
||
const keys = Object.keys(propOrObj); | ||
return keys.flatMap(key => getTranslations(propOrObj[key]!, stack.concat(key))); | ||
} | ||
|
||
export function mapTranslations(propOrObj: Translations): I18nTranslations { | ||
return getTranslations(propOrObj).reduce<I18nTranslations>((obj, translation) => { | ||
obj[translation.key] = translation.value; | ||
return obj; | ||
}, {}); | ||
} |
11 changes: 11 additions & 0 deletions
11
apps/docs/src/content/docs/internationalization/_examples/i18n/setup.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { defineConfig } from 'astro/config'; | ||
|
||
import { i18n } from '@astro-tools/i18n'; | ||
|
||
import { i18nSchemaLoader } from '@/config/i18n-schema-loader.mjs'; | ||
|
||
export default defineConfig({ | ||
integrations: [ | ||
i18n({ loader: i18nSchemaLoader('./i18n/en-US.json') }), | ||
], | ||
}); |
26 changes: 26 additions & 0 deletions
26
apps/docs/src/content/docs/internationalization/_examples/i18n/use-translations.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import type { I18nTranslations } from '@astro-tools/i18n'; | ||
|
||
import { i18n } from '@astro-tools:i18n'; | ||
|
||
import { mapTranslations } from './map-translations'; | ||
|
||
async function translationsLoader(locale: string): Promise<I18nTranslations> { | ||
switch (locale) { | ||
case 'en-US': | ||
return import('../../../../../../i18n/en-US.json') | ||
.then(json => mapTranslations(json.default)); | ||
case 'es-ES': | ||
return import('../../../../../../i18n/es-ES.json') | ||
.then(json => mapTranslations(json.default)); | ||
default: | ||
throw new Error(`Missing translations file for ${locale}`); | ||
} | ||
} | ||
|
||
export async function useTranslations(locale: string, fallbackLocale?: string): Promise<void> { | ||
await i18n({ | ||
locale, | ||
fallbackLocale: fallbackLocale || 'en-US', | ||
loader: translationsLoader, | ||
}); | ||
} |
5 changes: 5 additions & 0 deletions
5
apps/docs/src/content/docs/internationalization/_helpers/get-translate-types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import typesRaw from '../../../../../.astro/integrations/_astro-tools_i18n/types.d.ts?raw'; | ||
|
||
export function getTranslateTypes(): string { | ||
return typesRaw.split('\n').filter(line => line.includes('function t(')).join('\n'); | ||
} |
129 changes: 129 additions & 0 deletions
129
apps/docs/src/content/docs/internationalization/i18n.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
--- | ||
title: i18n | ||
description: i18n integration for applications which have high number of locales and requires type-checking | ||
sidebar: | ||
order: 1 | ||
--- | ||
import { Code, Tabs, TabItem, Aside, Steps } from '@astrojs/starlight/components'; | ||
|
||
import { Example, ExamplePreview } from '@astro-tools/docs-utils/example'; | ||
|
||
import setupExampleRaw from './_examples/i18n/setup.ts?raw'; | ||
import i18nSchemaMapTranslationsRaw from '../../../../config/map-translations.mjs?raw'; | ||
import i18nSchemaLoaderRaw from '../../../../config/i18n-schema-loader.mjs?raw'; | ||
import enUSRaw from '../../../../i18n/en-US.json?raw'; | ||
import mapTranslationsRaw from './_examples/i18n/map-translations.ts?raw'; | ||
import useTranslationsRaw from './_examples/i18n/use-translations.ts?raw'; | ||
import i18nExampleRaw from './_examples/i18n/Example.astro?raw'; | ||
import i18nClientSideExampleRaw from './_examples/i18n/ClientSideExample.svelte?raw'; | ||
|
||
import I18nExample from './_examples/i18n/Example.astro'; | ||
import { getTranslateTypes } from './_helpers/get-translate-types'; | ||
|
||
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](https://docs.astro.build/en/guides/internationalization/) or any other existing type-safe libraries. In any case, give it a try! | ||
|
||
## Format | ||
|
||
The `key` of the translation can be any string in any format. The supported format for the translation is a `string` with interpolation values surrounded by `{}`, like the following example: | ||
```text | ||
Hello {name}! | ||
``` | ||
|
||
Variables in the translations can be typed to `number` and `string` using the following format: | ||
``` | ||
Hello {name:string}! click here to get {points:number} points! | ||
``` | ||
|
||
<Aside type="note">We are improving the format to support plurals and more types!</Aside> | ||
|
||
## Setup | ||
|
||
For setting up the internationalization, include the integration in your Astro project: | ||
|
||
<Steps> | ||
1. Install the library and its dependencies using your preferred package manager: | ||
``` | ||
npm i -D @astro-tools/i18n @astro-tools/transfer-state astro-integration-kit | ||
``` | ||
2. Add the integration to your project configuration: | ||
<Code title="astro.config.mjs" code={setupExampleRaw} lang="typescript" /> | ||
</Steps> | ||
|
||
### Loader | ||
|
||
To extract the typings properly, add the `loader` function which loads every translation from the main locale file following a specific model. | ||
|
||
It is delegated to the project, allowing to store the translations in any format. | ||
|
||
For example, you can use a loader like the referenced in the setup section, in Javascript or Typescript, depending on your configuration language: | ||
|
||
<Example> | ||
<Tabs> | ||
<TabItem label="@/config/i18n-schema-loader.mjs"> | ||
<Code code={i18nSchemaLoaderRaw} lang="javascript" /> | ||
</TabItem> | ||
<TabItem label="./map-translations.mjs"> | ||
<Code code={i18nSchemaMapTranslationsRaw} lang="javascript" /> | ||
</TabItem> | ||
<TabItem label="./i18n/en-US.json"> | ||
<Code code={enUSRaw} lang="json" /> | ||
</TabItem> | ||
</Tabs> | ||
</Example> | ||
|
||
The result is the overload functions of the function `t`, which can be used to render translations: | ||
<Code title="types.d.ts" code={getTranslateTypes()} lang="typescript" /> | ||
|
||
## Use | ||
|
||
The integration exposes the virtual module `@astro-tools:i18n` with the required functions for managing translations. | ||
As we do for the integration, the loading of the translation files is delegated to the project. | ||
|
||
### Configure | ||
|
||
Before render any translation, configure the locale, fallback locale and the translation loader using the function `i18n()`: | ||
|
||
```typescript | ||
const esES = { title: '¡Hola mundo!' }; | ||
const enUS = { title: 'Hello world!' }; | ||
|
||
i18n({ | ||
locale: 'es-ES', | ||
fallbackLocale: 'en-US', | ||
loader: (locale) => locale === 'es-ES' ? esES : enUS, | ||
}); | ||
``` | ||
|
||
It is recommended to create a wrapper with your own logic following DRY principle. For example, create a function `useTranslations`: | ||
|
||
<Tabs> | ||
<TabItem label="use-translations.ts"> | ||
<Code code={useTranslationsRaw} lang="typescript" /> | ||
</TabItem> | ||
<TabItem label="map-translations.ts"> | ||
<Code code={mapTranslationsRaw} lang="typescript" /> | ||
</TabItem> | ||
</Tabs> | ||
|
||
### 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. | ||
|
||
<Example> | ||
<Tabs> | ||
<TabItem label="Example.astro"> | ||
<Code code={i18nExampleRaw} lang="astro" /> | ||
</TabItem> | ||
<TabItem label="ClientSideExample.svelte"> | ||
<Code code={i18nClientSideExampleRaw} lang="svelte" /> | ||
</TabItem> | ||
</Tabs> | ||
<ExamplePreview hydration="i18n-example"> | ||
<I18nExample id="i18n-example" /> | ||
</ExamplePreview> | ||
</Example> |
Oops, something went wrong.