Add internationalization support with astro-i18next integration

- Implemented astro-i18next for multi-language support, including English, Dutch, and Italian.
- Configured default locale and language fallback settings.
- Defined routes for localized content in the configuration.
- Updated package.json and package-lock.json to include new dependencies for i18next and related plugins.
This commit is contained in:
becarta
2025-05-23 15:10:00 +02:00
parent 8a3507dce0
commit 3168826fa8
581 changed files with 88691 additions and 494 deletions

20
node_modules/astro-i18next/LICENSE.md generated vendored Normal file
View File

@@ -0,0 +1,20 @@
MIT License
Copyright (c) 2022 Yassine Doghri
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

660
node_modules/astro-i18next/README.md generated vendored Normal file
View File

@@ -0,0 +1,660 @@
<div align="center">
# 🧪 astro-i18next <!-- omit in toc -->
An [astro](https://astro.build/) integration of
[i18next](https://www.i18next.com/) + some utility components to help you
translate your astro websites!
</div>
<div align="center">
[![npm-badge]][npm]&nbsp;[![build-badge]][build]&nbsp;[![codecov-badge]][codecov]&nbsp;[![license-badge]][license]&nbsp;[![contributions-badge]][contributions]&nbsp;[![semantic-release-badge]][semantic-release]&nbsp;[![stars-badge]][stars]
</div>
> **Note**
>
> Status - 🚧 **Beta**
>
> [👉 **Road to v1.0.0**](https://github.com/yassinedoghri/astro-i18next/issues/19)
>
> You can use it, and feedback is more than welcome! Note that some breaking
> changes may still be introduced during this phase as the goal for v1 is to get
> the best possible DX for translating your Astro pages.
## Examples
| Example | Status |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [SSG - **Basics**](examples/basics) | [![example-up-badge]](examples/basics) |
| [SSR - **Node**](examples/node) | [![example-up-badge]](examples/node) |
| [**React**](examples/react) | [![example-up-badge]](examples/react) |
| [SSR - **Netlify**](examples/netlify) | [![example-down-badge]](examples/netlify) (https://github.com/yassinedoghri/astro-i18next/issues/26) |
| SSR - **Deno** | [![example-down-badge]](examples/basics) (https://github.com/yassinedoghri/astro-i18next/issues/55) |
- [Examples](#examples)
- [🚀 Getting started](#-getting-started)
- [1. Install](#1-install)
- [2. Configure](#2-configure)
- [3. Start translating](#3-start-translating)
- [💻 CLI commands](#-cli-commands)
- [generate](#generate)
- [🔄 Translate Routes](#-translate-routes)
- [📦 Utility components](#-utility-components)
- [Trans component](#trans-component)
- [LanguageSelector component](#languageselector-component)
- [HeadHrefLangs component](#headhreflangs-component)
- [📦 Utility functions](#-utility-functions)
- [interpolate function](#interpolate-function)
- [localizePath function](#localizepath-function)
- [localizeUrl function](#localizeurl-function)
- [👀 Going further](#-going-further)
- [Namespaces](#namespaces)
- [AstroI18nextConfig Props](#astroi18nextconfig-props)
- [✨ Contributors](#-contributors)
- [❤️ Acknowledgments](#-acknowledgments)
- [📜 License](#-license)
## 🚀 Getting started
### 1. Install
```bash
npm install astro-i18next
```
or
```bash
pnpm add astro-i18next
```
or
```bash
yarn add astro-i18next
```
### 2. Configure
1. Add `astro-i18next` to your `astro.config.mjs`:
```js
import { defineConfig } from "astro/config";
import astroI18next from "astro-i18next";
export default defineConfig({
integrations: [astroI18next()],
});
```
2. Configure `astro-i18next` in your `astro-i18next.config.mjs` file:
```js
/** @type {import('astro-i18next').AstroI18nextConfig} */
export default {
defaultLocale: "en",
locales: ["en", "fr"],
};
```
Your `astro-i18next` config file can be a javascript (`.js` | `.mjs` |
`.cjs`) or typescript (`.ts` | `.mts` | `.cts`) file.
For a more advanced configuration, see the
[AstroI18nextConfig props](#astroi18nextconfig-props).
3. By default, `astro-i18next` expects your translations to be organized inside
your
[astro's `publicDir`](https://docs.astro.build/en/reference/configuration-reference/#publicdir),
in a `locales` folder:
```bash
public
└── locales # create this folder to store your translation strings
├── en
| └── translation.json
└── fr
└── translation.json
```
`astro-i18next` loads your translation files both server-side and
client-side using
[i18next-fs-backend](https://github.com/i18next/i18next-fs-backend) and
[i18next-http-backend](https://github.com/i18next/i18next-http-backend)
plugins.
You may choose to organize your translations into multiple files instead
of a single file per locale [using namespaces](#namespaces).
### 3. Start translating
You may now start translating your pages by using
[i18next's `t` function](https://www.i18next.com/overview/api#t) or the
[Trans component](#trans-component) depending on your needs.
Here's a quick tutorial to get you going:
1. Use translation keys in your Astro pages
```astro
---
// src/pages/index.astro
import i18next, { t } from "i18next";
import { Trans, HeadHrefLangs } from "astro-i18next/components";
---
<html lang={i18next.language}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{t("site.title")}</title>
<meta name="description" content={t("site.description")} />
<HeadHrefLangs />
</head>
<body>
<h1>{t("home.title")}</h1>
<p>
<Trans i18nKey="home.subtitle">
This is a <em>more complex</em> string to translate, mixed with <strong
>html elements
</strong> such as <a href="https://example.com/">a cool link</a>!
</Trans>
</p>
</body>
</html>
```
```json
// public/locales/en/translation.json
{
"site": {
"title": "My awesome website!",
"description": "Here is the description of my awesome website!"
},
"home": {
"title": "Welcome to my awesome website!",
"subtitle": "This is a <0>more complex</0> string to translate, mixed with <1>html elements</1>, such as a <2>a cool link</2>!"
}
}
```
```json
// public/locales/fr/translation.json
{
"site": {
"title": "Mon super site web !",
"description": "Voici la description de mon super site web !"
},
"home": {
"title": "Bienvenue sur mon super site web !",
"subtitle": "Ceci est une chaine de charactères <0>plus compliquée</0> à traduire, il y a des <1>éléments html</1>, comme <2>un super lien</2> par exemple !"
}
}
```
2. Create localized pages using the [generate command](#generate)
```bash
npx astro-i18next generate
```
3. You're all set! Have fun translating and generate localized pages as you go
🚀
> **Note**
>
> For a real world example, see the [website](./website/) or check out the
> [examples](./examples/).
---
## 💻 CLI commands
### generate
```bash
npx astro-i18next generate
```
This command will generate localized pages depending on your config and set
i18next's language change on each page.
For instance, with `locales = ["en", "fr", "es"]`, and `"en"` being the default
locale and having:
```bash
src
└── pages
├── about.astro
└── index.astro
```
👇 Running `npx astro-i18next generate` will create the following pages
```bash
src
└── pages
├── es
| ├── about.astro
| └── index.astro
├── fr
| ├── about.astro
| └── index.astro
├── about.astro
└── index.astro
```
## 🔄 Translate Routes
`astro-i18next` let's you translate your pages routes for each locale!
For instance, with support for 3 locales (`en`, `fr`, `es`), `en` being the
default and the following pages:
```bash
src
└── pages
├── about.astro
├── contact-us.astro
└── index.astro
```
1. Set route mappings in your `astro-i18next` config:
```js
/** @type {import('astro-i18next').AstroI18nextConfig} */
export default {
defaultLocale: "en",
locales: ["en", "fr", "es"],
routes: {
fr: {
"about": "a-propos",
"contact-us": "contactez-nous",
"products": {
"index": "produits",
"categories": "categories",
}
}
es: {
"about": "a-proposito",
"contact-us": "contactenos",
"products": {
"index": "productos",
"categories": "categorias",
}
}
},
};
```
2. Generate your localized pages using the [generate CLI command](#generate),
they will be translated for you!
```bash
src
└── pages
├── es
| ├── productos
| | ├── categorias.astro
| | └── index.astro
| ├── a-proposito.astro
| ├── contactenos.astro
| └── index.astro
├── fr
| ├── produits
| | ├── categories.astro
| | └── index.astro
| ├── a-propos.astro
| ├── contactez-nous.astro
| └── index.astro
├── products
| ├── categories.astro
| └── index.astro
├── about.astro
├── contact-us.astro
└── index.astro
```
> **Note**
>
> The [localizePath](#localizepath-function) and
> [localizeUrl](#localizeurl-function) utility functions will retrieve the
> correct route based on your mappings.
---
## 📦 Utility components
### Trans component
A component that takes care of interpolating its children with the translation
strings. Inspired by
[react-i18next's Trans component](https://react.i18next.com/latest/trans-component).
```astro
---
import { Trans } from "astro-i18next/components";
---
<Trans i18nKey="superCoolKey">
An <a href="https://astro.build" title="Astro website">astro</a> integration of
<a href="https://www.i18next.com/" title="i18next website">i18next</a> and utility
components to help you translate your astro websites!
</Trans>
```
```json
// fr.json
{
"superCoolKey": "Une intégration <0>astro</0> d'<1>i18next</1> + quelques composants utilitaires pour vous aider à traduire vos sites astro !"
}
```
#### Trans Props
| Prop name | Type (default) | Description |
| --------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| i18nKey | ?string (undefined) | Internationalization key to interpolate to. Can contain the namespace by prepending it in the form 'ns:key' (depending on i18next.options.nsSeparator). If omitted, a key is automatically generated using the content of the element. |
| ns | ?string (undefined) | Namespace to use. May also be embedded in i18nKey but not recommended when used in combination with natural language keys. |
### LanguageSelector component
Unstyled custom select component to choose amongst supported locales.
```astro
---
import { LanguageSelector } from "astro-i18next/components";
---
<LanguageSelector showFlag={true} class="my-select-class" />
<!-- LanguageSelector with custom language naming -->
<LanguageSelector
showFlag={true}
languageMapping={{ en: "Anglais" }}
class="my-select-class"
/>
```
#### LanguageSelector Props
| Prop name | Type (default) | Description |
| --------------- | --------------------- | ------------------------------------------------------------------------------------------- |
| showFlag | ?boolean (`false`) | Choose to display the language emoji before language name |
| languageMapping | ?object (`undefined`) | Rewrite language names by setting the locale as key and the wording of your choice as value |
### HeadHrefLangs component
HTML tags to include in your page's `<head>` section to let search engines know
about its language and region variants. To know more, see
[Google's advanced localized versions](https://developers.google.com/search/docs/advanced/crawling/localized-versions#html).
```astro
---
import i18next from "i18next";
import { HeadHrefLangs } from "astro-i18next/components";
---
<html lang={i18next.language}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>...</title>
<meta name="description" content="..." />
<HeadHrefLangs />
</head>
<body>...</body>
</html>
```
The HeadHrefLangs component will generate all of the alternate links depending
on the current url and supported locales.
For example, if you are on the `/about` page and support 3 locales (`en`, `fr`,
`es`) with `en` being the default locale, this will render:
```html
<link rel="alternate" hreflang="en" href="https://www.example.com/about/" />
<link rel="alternate" hreflang="fr" href="https://www.example.com/fr/about/" />
<link rel="alternate" hreflang="es" href="https://www.example.com/es/about/" />
```
## 📦 Utility functions
### interpolate function
`interpolate(i18nKey: string, reference: string, namespace: string | null): string`
`astro-i18next` exposes the logic behind the Trans component, you may want to
use it directly.
```ts
import { interpolate } from "astro-i18next";
const interpolated = interpolate(
"superCoolKey",
'An <a href="https://astro.build" title="Astro website">astro</a> integration of <a href="https://www.i18next.com/" title="i18next website">i18next</a> and utility components to help you translate your astro websites!'
);
```
### localizePath function
`localizePath(path: string, locale: string | null = null, base: string = import.meta.env.BASE_URL): string`
Sets a path within a given locale. If the locale param is not specified, the
current locale will be used.
> **Note**
>
> This should be used instead of hard coding paths to other pages. It will take
> care of setting the right path depending on the locale you set.
```astro
---
import { localizePath } from "astro-i18next";
import i18next from "i18next";
i18next.changeLanguage("fr");
---
<a href={localizePath("/about")}>...</a>
<!-- renders: <a href="/fr/about">...</a> -->
```
### localizeUrl function
`localizeUrl(url: string, locale: string | null = null, base: string = import.meta.env.BASE_URL): string`
Sets a url within a given locale. If the locale param is not specified, the
current locale will be used.
> **Note**
>
> This should be used instead of hard coding urls for internal links. It will
> take care of setting the right url depending on the locale you set.
```astro
---
import { localizeUrl } from "astro-i18next";
import i18next from "i18next";
i18next.changeLanguage("fr");
---
<a href={localizeUrl("https://www.example.com/about")}>...</a>
<!-- renders: <a href="https://www.example.com/fr/about">...</a> -->
```
---
## 👀 Going further
### Namespaces
i18next allows you to organize your translation keys into
[namespaces](https://www.i18next.com/principles/namespaces).
You can have as many namespaces as you wish, have one per page and one for
common translation strings for example:
```bash
public
├-- locales
| |-- en
| | |-- about.json # "about" namespace
| | |-- common.json # "common" namespace
| | └-- home.json # "home" namespace
| └-- fr # same files in other locale folders
src
└-- pages
|-- about.astro
└-- index.astro
```
1. It can easily be setup using the `namespaces` and `defaultNamespace` keys,
like so:
```ts
/** @type {import('astro-i18next').AstroI18nextConfig} */
export default {
defaultLocale: "en",
locales: ["en", "fr"],
namespaces: ["about", "common", "home"],
defaultNamespace: "common",
};
```
2. Load the namespace globally using `i18next.setDefaultNamespace(ns: string)`
or specify it in the `t` function or the `Trans` component:
```astro
---
import { t, setDefaultNamespace } from "i18next";
import { Trans } from "astro-i18next/components";
setDefaultNamespace("home");
---
<h1>{t("myHomeTitle")}</h1>
<p>
<Trans i18nKey="myHomeDescription">
This translation is loaded from the default <strong>home</strong> namespace!
</Trans>
</p>
<p>
<Trans i18nKey="myCommonCTA" ns="common">
This translation is loaded from the <strong>common</strong> namespace!
</Trans>
</p>
<!-- t function loads the "buttonCTA" key from the "common" namespace -->
<button>{t("common:buttonCTA")}</button>
```
### AstroI18nextConfig Props
`astro-i18next`'s goal is to abstract most of the configuration for you so that
you don't have to think about it. Just focus on translating!
Though if you'd like to go further in customizing i18next, feel free to tweak
your config!
| Prop name | Type (default) | Description |
| -------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| defaultLocale | `string` (undefined) | The default locale for your website. |
| locales | `string[]` (undefined) | Your website's supported locales. |
| namespaces | `string` or `string[]` ('translation') | String or array of namespaces to load. |
| defaultNamespace | `string` (translation') | Default namespace used if not passed to the translation function. |
| load | `Array<"server" or "client">` (`["server"]`) | Load i18next on server side only, client side only or both. |
| resourcesBasePath | `?string` | Set base path for i18next resources. Defaults to `/locales`. |
| i18nextServer | `?InitOptions` | The i18next server side configuration. See [i18next's documentation](https://www.i18next.com/overview/configuration-options). |
| i18nextServerPlugins | `?{[key: string]: string}` (`{}`) | Set i18next server side plugins. See [available plugins](https://www.i18next.com/overview/plugins-and-utils). |
| i18nextClient | `?InitOptions` | The i18next client side configuration . See [i18next's documentation](https://www.i18next.com/overview/configuration-options). |
| i18nextClientPlugins | `?{[key: string]: string}` (`{}`) | Set i18next client side plugins. See [available plugins](https://www.i18next.com/overview/plugins-and-utils). |
| routes | `[segment: string]: string or object`(`{}`) | The translations mapping for your routes. See [translate routes](#-translate-routes). |
| showDefaultLocale | `boolean`(`false`) | Whether or not the defaultLocale should show up in the url just as other locales. |
## ✨ Contributors
Thanks goes to these wonderful people
([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://yassinedoghri.com/"><img src="https://avatars.githubusercontent.com/u/11021441?v=4?s=100" width="100px;" alt="Yassine Doghri"/><br /><sub><b>Yassine Doghri</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=yassinedoghri" title="Code">💻</a> <a href="https://github.com/yassinedoghri/astro-i18next/commits?author=yassinedoghri" title="Documentation">📖</a> <a href="#ideas-yassinedoghri" title="Ideas, Planning, & Feedback">🤔</a> <a href="#design-yassinedoghri" title="Design">🎨</a> <a href="#example-yassinedoghri" title="Examples">💡</a> <a href="#maintenance-yassinedoghri" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://gdevs.io/"><img src="https://avatars.githubusercontent.com/u/10165264?v=4?s=100" width="100px;" alt="Davide Ceschia"/><br /><sub><b>Davide Ceschia</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=killpowa" title="Code">💻</a> <a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3Akillpowa" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/preetamslot"><img src="https://avatars.githubusercontent.com/u/5420582?v=4?s=100" width="100px;" alt="preetamslot"/><br /><sub><b>preetamslot</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3Apreetamslot" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/dklymenko"><img src="https://avatars.githubusercontent.com/u/1391015?v=4?s=100" width="100px;" alt="Dmytro"/><br /><sub><b>Dmytro</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3Admythro" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://duskmoon314.com/"><img src="https://avatars.githubusercontent.com/u/20477228?v=4?s=100" width="100px;" alt="Campbell He"/><br /><sub><b>Campbell He</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3Aduskmoon314" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://t.me/mellkam"><img src="https://avatars.githubusercontent.com/u/51422045?v=4?s=100" width="100px;" alt="MelKam"/><br /><sub><b>MelKam</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=MellKam" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://webslc.com/"><img src="https://avatars.githubusercontent.com/u/34887287?v=4?s=100" width="100px;" alt="L1lith"/><br /><sub><b>L1lith</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3AL1lith" title="Bug reports">🐛</a> <a href="#ideas-L1lith" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Anomander43"><img src="https://avatars.githubusercontent.com/u/14289502?v=4?s=100" width="100px;" alt="Anomander43"/><br /><sub><b>Anomander43</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=Anomander43" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dschoeni"><img src="https://avatars.githubusercontent.com/u/1913623?v=4?s=100" width="100px;" alt="Dominik Schöni"/><br /><sub><b>Dominik Schöni</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=dschoeni" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dallyh"><img src="https://avatars.githubusercontent.com/u/6968534?v=4?s=100" width="100px;" alt="Dalibor Hon"/><br /><sub><b>Dalibor Hon</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=dallyh" title="Code">💻</a> <a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3Adallyh" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aquaminer"><img src="https://avatars.githubusercontent.com/u/17113289?v=4?s=100" width="100px;" alt="Oleksii Lozoviahin"/><br /><sub><b>Oleksii Lozoviahin</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=aquaminer" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://talale.it/"><img src="https://avatars.githubusercontent.com/u/68308554?v=4?s=100" width="100px;" alt="Alessandro Talamona"/><br /><sub><b>Alessandro Talamona</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3AxTalAlex" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jkjustjoshing"><img src="https://avatars.githubusercontent.com/u/813192?v=4?s=100" width="100px;" alt="Josh Kramer"/><br /><sub><b>Josh Kramer</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=jkjustjoshing" title="Code">💻</a> <a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3Ajkjustjoshing" title="Bug reports">🐛</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alexandre-Fernandez"><img src="https://avatars.githubusercontent.com/u/79476242?v=4?s=100" width="100px;" alt="Alexandre Fernandez"/><br /><sub><b>Alexandre Fernandez</b></sub></a><br /><a href="https://github.com/yassinedoghri/astro-i18next/commits?author=Alexandre-Fernandez" title="Code">💻</a> <a href="https://github.com/yassinedoghri/astro-i18next/issues?q=author%3AAlexandre-Fernandez" title="Bug reports">🐛</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the
[all-contributors](https://github.com/all-contributors/all-contributors)
specification. Contributions of any kind welcome!
## ❤️ Acknowledgments
This wouldn't have been possible without the awesome work from the
[Locize](https://locize.com/) and [Astro](https://astro.build/) teams.
Inspired by some of the greatly thought-out i18n implementations:
- [next-i18next](https://github.com/i18next/next-i18next)
- [react-i18next](https://github.com/i18next/react-i18next)
- [NextJS's Internationalized Routing](https://nextjs.org/docs/advanced-features/i18n-routing)
## 📜 License
Code released under the [MIT License](https://choosealicense.com/licenses/mit/).
Copyright (c) 2022-present, Yassine Doghri
([@yassinedoghri](https://twitter.com/yassinedoghri))
[npm]: https://www.npmjs.com/package/astro-i18next
[npm-badge]: https://img.shields.io/npm/v/astro-i18next
[build]:
https://github.com/yassinedoghri/astro-i18next/actions/workflows/publish.yml
[build-badge]:
https://img.shields.io/github/actions/workflow/status/yassinedoghri/astro-i18next/publish.yml
[license]:
https://github.com/yassinedoghri/astro-i18next/blob/develop/LICENSE.md
[license-badge]:
https://img.shields.io/github/license/yassinedoghri/astro-i18next?color=blue
[contributions]: https://github.com/yassinedoghri/astro-i18next/issues
[contributions-badge]:
https://img.shields.io/badge/contributions-welcome-blueviolet.svg
[semantic-release]: https://github.com/semantic-release/semantic-release
[semantic-release-badge]:
https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[stars]: https://github.com/yassinedoghri/astro-i18next/stargazers
[stars-badge]:
https://img.shields.io/github/stars/yassinedoghri/astro-i18next?style=social
[codecov]: https://codecov.io/gh/yassinedoghri/astro-i18next
[codecov-badge]:
https://codecov.io/gh/yassinedoghri/astro-i18next/branch/develop/graph/badge.svg?token=IFWNB6UJDJ
[example-up-badge]: https://img.shields.io/badge/status-up-brightgreen
[example-down-badge]: https://img.shields.io/badge/status-down-red

411
node_modules/astro-i18next/dist/cli/index.js generated vendored Executable file

File diff suppressed because one or more lines are too long

9
node_modules/astro-i18next/dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
import { AstroI18nextConfig } from "../types";
import { FileToGenerate } from "./utils";
/**
* Reads all files inside inputPath
*
* @param inputPath
* @param locales
* @param outputPath
*/
export declare const generate: (inputPath: string, defaultLocale: AstroI18nextConfig["defaultLocale"], locales: AstroI18nextConfig["locales"], showDefaultLocale?: boolean, flatRoutes?: AstroI18nextConfig["flatRoutes"], outputPath?: string) => {
filesToGenerate: FileToGenerate[];
timeToProcess: number;
};

2
node_modules/astro-i18next/dist/types/cli/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};

View File

@@ -0,0 +1,4 @@
import { MiddlewareFunction } from "yargs";
import { GenerateArgs, GlobalArgs } from "./types";
export declare const loadConfig: MiddlewareFunction<GlobalArgs & GenerateArgs>;
export declare const normalizePath: MiddlewareFunction<GlobalArgs & GenerateArgs>;

View File

@@ -0,0 +1,7 @@
import ts from "typescript";
/**
* Traverse ts' AST to inject i18next's language switch
* @param context
* @returns
*/
export declare const transformer: ts.TransformerFactory<ts.SourceFile>;

9
node_modules/astro-i18next/dist/types/cli/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,9 @@
import { AstroI18nextConfig } from "../types";
export interface GlobalArgs {
verbose: boolean;
}
export interface GenerateArgs {
path: string;
config: AstroI18nextConfig;
output: string;
}

40
node_modules/astro-i18next/dist/types/cli/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,40 @@
import { PathsOutput } from "fdir";
import ts from "typescript";
import { AstroI18nextConfig } from "../types";
export interface FileToGenerate {
path: string;
source: string;
}
export declare const doesStringIncludeFrontmatter: (source: string) => boolean;
export declare const extractFrontmatterFromAstroSource: (source: string) => string;
export declare const overwriteAstroFrontmatter: (source: string, frontmatter: string) => string;
export declare const addDepthToRelativePath: (relativePath: string, depth?: number) => string;
/**
* file is hidden if its name or any of its parent folders start with an underscore
*/
export declare const isFileHidden: (filepath: string) => boolean;
export declare const resolveRelativePathsLevel: (fileContents: string, fileDepth: number) => string;
/**
* parse frontmatter using typescript compiler
*
* @param source
*/
export declare const parseFrontmatter: (source: string) => ts.SourceFile;
export declare const generateLocalizedFrontmatter: (tsNode: ts.SourceFile, locale: string) => string;
/**
* Crawls pages directory and returns all Astro pages
* except for locale folders and excluded pages / directories (starting with underscore).
* (https://docs.astro.build/en/core-concepts/routing/#excluding-pages)
*
* @param pagesDirectoryPath
* @param childDirToCrawl will make the function crawl inside the given
* `childDirToCrawl` (doesn't take paths, only dirname).
*/
export declare const getAstroPagesFullPaths: (pagesDirectoryPath: string, childDirToCrawl?: AstroI18nextConfig["defaultLocale"] | undefined, locales?: AstroI18nextConfig["locales"]) => PathsOutput;
export declare const createFiles: (filesToGenerate: FileToGenerate[]) => void;
/**
* Resolves the right translated path based on
* a given `astroFilePath` and a locale,
* with the `routeTranslations` mapping.
*/
export declare const resolveTranslatedAstroPath: (astroFilePath: string, locale?: string | null, basePath?: string, flatRoutes?: AstroI18nextConfig["flatRoutes"]) => string;

11
node_modules/astro-i18next/dist/types/config.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import { AstroI18nextConfig, AstroI18nextGlobal } from "./types";
export declare const AstroI18next: AstroI18nextGlobal;
export declare const setAstroI18nextConfig: (config: AstroI18nextConfig) => void;
export declare const astroI18nextConfigBuilder: (config: AstroI18nextConfig) => AstroI18nextConfig;
/**
* This will create a mapping of translated routes to search them easily.
*
* TODO: render all routes mappings in here (even those not translated),
* this will help simplify utility functions logic
*/
export declare const flattenRoutes: (routes: AstroI18nextConfig["routes"], previous?: string[], translatedPrevious?: string[], prevResult?: AstroI18nextConfig["flatRoutes"]) => AstroI18nextConfig["flatRoutes"];

8
node_modules/astro-i18next/dist/types/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
import { AstroIntegration } from "astro";
import { AstroI18nextConfig, AstroI18nextOptions } from "./types";
declare const _default: (options?: AstroI18nextOptions) => AstroIntegration;
export default _default;
export declare function initAstroI18next(config: AstroI18nextConfig): void;
export { AstroI18next } from "./config";
export { createReferenceStringFromHTML, detectLocaleFromPath, interpolate, localizePath, localizeUrl, } from "./utils";
export { AstroI18nextConfig, AstroI18nextOptions } from "./types";

116
node_modules/astro-i18next/dist/types/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,116 @@
import { InitOptions } from "i18next";
export interface AstroI18nextGlobal {
config: AstroI18nextConfig;
}
export interface AstroI18nextOptions {
/**
* Path to your astro-i18next config file
*
* @default 'astro-i18next.config.js'
*/
configPath?: string;
}
export interface Routes {
[segment: string]: string | Record<string, string | Routes>;
}
export interface Plugins {
[importName: string]: string | null;
}
export interface AstroI18nextConfig {
/**
* The default locale for your website.
*
* @default "cimode"
*/
defaultLocale: string;
/**
* The locales that are supported by your website.
*
* @default []
*/
locales: string[];
/**
* String or array of namespaces to load
*
* @default "translation"
*/
namespaces?: string | string[];
/**
* Default namespace used if not passed to the translation function
*
* @default "translation"
*/
defaultNamespace?: string;
/**
* Load i18next on server side only, client side only or both.
*
* @default ["server"]
*/
load?: ("server" | "client")[];
/**
* Set base path for i18next resources.
*
* @default "/locales"
*/
resourcesBasePath?: string;
/**
* i18next server side config. See https://www.i18next.com/overview/configuration-options
*/
i18nextServer?: InitOptions;
/**
* i18next client side config. See https://www.i18next.com/overview/configuration-options
*/
i18nextClient?: InitOptions;
/**
* The translations for your routes.
*
* @default {}
*/
routes?: Routes;
/**
* Generated mappings based on the routes
*
* @default {}
*/
readonly flatRoutes?: Record<string, string>;
/**
* The display behaviour for the URL locale.
*
* @default false
*/
showDefaultLocale?: boolean;
/**
* i18next server side plugins. See https://www.i18next.com/overview/plugins-and-utils
*
* Include the plugins with the key being the import name and the value being the plugin name.
*
* Eg.:
* ```
* {
* "Backend": "i18next-fs-backend",
* }
* ```
*/
i18nextServerPlugins?: Plugins;
/**
* i18next client side plugins. See https://www.i18next.com/overview/plugins-and-utils
*
* Include the plugins with the key being the import name and the value being the plugin name.
*
* Eg.:
* ```
* {
* "{initReactI18next}": "react-i18next",
* }
* ```
*/
i18nextClientPlugins?: Plugins;
/**
* Set the route matching behavior of the dev server. Choose from the following options:
*
* 'always' - Only match URLs that include a trailing slash (ex: "/foo/")
* 'never' - Never match URLs that include a trailing slash (ex: "/foo")
* 'ignore' - Match URLs regardless of whether a trailing "/" exists
*/
trailingSlash?: "always" | "never" | "ignore";
}

35
node_modules/astro-i18next/dist/types/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,35 @@
/// <reference types="node" />
import load from "@proload/core";
import { AstroI18nextConfig } from "./types";
/**
* Adapted from astro's tailwind integration:
* https://github.com/withastro/astro/tree/main/packages/integrations/tailwind
*/
export declare const getUserConfig: (root: URL, configPath?: string) => Promise<load.Config<AstroI18nextConfig>>;
/**
* Moves the default locale in the first index
*/
export declare const moveDefaultLocaleToFirstIndex: (locales: string[], baseLocale: string) => void;
/**
* Interpolates a localized string (loaded with the i18nKey) to a given reference string.
*/
export declare const interpolate: (i18nKey: string, referenceString: string, namespace?: string | null) => string;
/**
* Creates a reference string from an HTML string. The reverse of interpolate(), for use
* with <Trans> when not explicitly setting a key
*/
export declare const createReferenceStringFromHTML: (html: string) => string;
export declare const handleTrailingSlash: (path: string, trailingSlash: AstroI18nextConfig["trailingSlash"]) => string;
/**
* Injects the given locale to a path
*/
export declare const localizePath: (path?: string, locale?: string | null, base?: string) => string;
/**
* Injects the given locale to a url
*/
export declare const localizeUrl: (url: string, locale?: string | null, base?: string) => string;
/**
* Returns the locale detected from a given path
*/
export declare const detectLocaleFromPath: (path: string) => string;
export declare const deeplyStringifyObject: (obj: object | Array<any>) => string;

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2022 i18next
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,63 @@
# i18next: learn once - translate everywhere [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Awesome%20i18next:%20learn%20once%20-%20translate%20everywhere%20-%20the%20internationalization%20ecosystem%20&url=https://github.com/i18next/i18next&via=jamuhl&hashtags=i18n,javascript,dev)
[![CircleCI](https://circleci.com/gh/i18next/i18next.svg?style=svg)](https://circleci.com/gh/i18next/i18next)
[![Code Climate](https://codeclimate.com/github/codeclimate/codeclimate/badges/gpa.svg)](https://codeclimate.com/github/i18next/i18next)
[![Coveralls](https://img.shields.io/coveralls/i18next/i18next/master.svg?style=flat-square)](https://coveralls.io/github/i18next/i18next)
[![Package Quality](https://packagequality.com/shield/i18next.svg)](https://packagequality.com/#?package=i18next)
[![cdnjs version](https://img.shields.io/cdnjs/v/i18next.svg?style=flat-square)](https://cdnjs.com/libraries/i18next)
[![npm version](https://img.shields.io/npm/v/i18next.svg?style=flat-square)](https://www.npmjs.com/package/i18next)
![npm](https://img.shields.io/npm/dw/i18next)
i18next is a very popular internationalization framework for browser or any other javascript environment (eg. Node.js, Deno).
![ecosystem](https://raw.githubusercontent.com/i18next/i18next/master/assets/i18next-ecosystem.jpg)
i18next provides:
- Flexible connection to [backend](https://www.i18next.com/overview/plugins-and-utils#backends) (loading translations via xhr, ...)
- Optional [caching](https://www.i18next.com/how-to/caching), user [language detection](https://www.i18next.com/overview/plugins-and-utils#language-detector), ...
- Proper [pluralizations](https://www.i18next.com/translation-function/plurals)
- Translation [context](https://www.i18next.com/translation-function/context)
- [Nesting](https://www.i18next.com/translation-function/nesting), [Variable replacement](https://www.i18next.com/translation-function/interpolation)
- Flexibility: [Use it everywhere](https://www.i18next.com/overview/supported-frameworks)
- Extensibility: eg. [sprintf](https://www.i18next.com/overview/plugins-and-utils#post-processors)
- ...
For more information visit the website:
- [Getting started](https://www.i18next.com/overview/getting-started)
- [Translation Functionality](https://www.i18next.com/translation-function/essentials)
- [API](https://www.i18next.com/overview/api)
Our focus is providing the core to building a booming ecosystem. Independent of the building blocks you choose, be it react, angular or even good old jquery proper translation capabilities are just [one step away](https://www.i18next.com/overview/supported-frameworks).
### Documentation
The general i18next documentation is published on [www.i18next.com](https://www.i18next.com) and PR changes can be supplied [here](https://github.com/i18next/i18next-gitbook).
The react specific documentation is published on [react.i18next.com](https://react.i18next.com) and PR changes can be supplied [here](https://github.com/i18next/react-i18next-gitbook).
---
<h3 align="center">Gold Sponsors</h3>
<p align="center">
<a href="https://locize.com/" target="_blank">
<img src="https://raw.githubusercontent.com/i18next/i18next/master/assets/locize_sponsor_240.gif" width="240px">
</a>
<a href="https://localistars.com/" target="_blank">
<img src="https://raw.githubusercontent.com/i18next/i18next/master/assets/localistars_sponsor_240.gif" width="240px">
</a>
</p>
---
**From the creators of i18next: localization as a service - locize.com**
A translation management system built around the i18next ecosystem - [locize.com](https://locize.com).
![locize](https://locize.com/img/ads/github_locize.png)
With using [locize](https://locize.com/?utm_source=i18next_readme&utm_medium=github) you directly support the future of i18next.
---

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
{"type":"module","version":"22.5.1"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
/* eslint no-var: 0 */
var main = require('./dist/cjs/i18next.js');
module.exports = main;
module.exports.default = main;

View File

@@ -0,0 +1,129 @@
{
"name": "i18next",
"version": "22.5.1",
"description": "i18next internationalization framework",
"main": "./dist/cjs/i18next.js",
"module": "./dist/esm/i18next.js",
"types": "./index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./index.d.ts",
"import": "./dist/esm/i18next.js",
"require": "./dist/cjs/i18next.js"
}
},
"keywords": [
"i18next",
"internationalization",
"i18n",
"translation",
"localization",
"l10n",
"globalization",
"gettext"
],
"homepage": "https://www.i18next.com",
"bugs": "https://github.com/i18next/i18next/issues",
"repository": {
"type": "git",
"url": "https://github.com/i18next/i18next.git"
},
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.20.6"
},
"devDependencies": {
"@babel/core": "^7.20.5",
"@babel/plugin-proposal-async-generator-functions": "^7.20.1",
"@babel/plugin-transform-modules-commonjs": "^7.19.6",
"@babel/plugin-transform-runtime": "^7.19.6",
"@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/register": "^7.18.9",
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@babel/eslint-parser": "^7.19.1",
"babelify": "^10.0.0",
"browserify": "17.0.0",
"browserify-istanbul": "3.0.1",
"chai": "4.3.7",
"coveralls": "3.1.1",
"cpy-cli": "^4.2.0",
"dtslint": "^0.4.2",
"eslint": "8.32.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "7.32.1",
"gh-release": "6.0.4",
"husky": "^7.0.2",
"i18next-browser-languagedetector": "7.0.1",
"i18next-fs-backend": "2.1.0",
"i18next-http-backend": "2.1.0",
"i18next-localstorage-cache": "1.1.1",
"i18next-sprintf-postprocessor": "0.2.2",
"karma": "6.4.1",
"karma-browserify": "8.1.0",
"karma-chai": "0.1.0",
"karma-chrome-launcher": "3.1.1",
"karma-cli": "2.0.0",
"karma-coverage": "2.2.0",
"karma-coveralls": "2.1.0",
"karma-mocha": "2.0.1",
"karma-rollup-preprocessor": "7.0.8",
"karma-sinon": "1.0.5",
"karma-spec-reporter": "0.0.34",
"lint-staged": "^11.1.2",
"mocha": "10.1.0",
"nyc": "^15.1.0",
"prettier": "^2.8.0",
"rimraf": "3.0.2",
"rollup": "^2.79.1",
"rollup-plugin-terser": "^5.3.0",
"sinon": "11.1.2",
"tslint": "^5.20.1",
"typescript": "^4.9.3",
"watchify": "4.0.0"
},
"scripts": {
"pretest": "npm run test:typescript && npm run test:typescript:customtypes && npm run test:typescript:noninterop",
"lint": "eslint src",
"test": "npm run lint && npm run test:new && npm run test:compat",
"test:new": "karma start karma.conf.js --singleRun",
"test:compat": "karma start karma.backward.conf.js --singleRun",
"test:typescript": "tslint --project tsconfig.json",
"test:typescript:customtypes": "tslint --project test/typescript/custom-types/tsconfig.json",
"test:typescript:noninterop": "tslint --project tsconfig.nonEsModuleInterop.json",
"tdd": "karma start karma.conf.js",
"tdd:compat": "karma start karma.backward.conf.js",
"build": "rimraf dist && rollup -c && echo '{\"type\":\"module\"}' > dist/esm/package.json && cpy \"./dist/umd/*.js\" ./",
"fix_dist_package": "node -e 'console.log(`{\"type\":\"module\",\"version\":\"${process.env.npm_package_version}\"}`)' > dist/esm/package.json",
"preversion": "npm run test && npm run build && git push",
"postversion": "npm run fix_dist_package && git push && git push --tags && npm run release",
"prettier": "prettier --write \"{,**/}*.{ts,tsx,js,json,md}\"",
"prepare": "husky install",
"release": "gh-release"
},
"author": "Jan Mühlemann <jan.muehlemann@gmail.com> (https://github.com/jamuhl)",
"license": "MIT",
"lint-staged": {
"*.{ts,tsx,js,json,md}": "prettier --write"
}
}

130
node_modules/astro-i18next/package.json generated vendored Normal file
View File

@@ -0,0 +1,130 @@
{
"name": "astro-i18next",
"version": "1.0.0-beta.21",
"description": "An astro integration of i18next + some utility components to help you translate your astro websites!",
"scripts": {
"test": "vitest",
"test:coverage": "pnpm test -- --coverage",
"preview": "astro preview",
"build": "./build.cjs && pnpm run typecheck:emit",
"pack": "pnpm run build && pnpm pack",
"lint": "eslint --ext js,cjs,ts .",
"prettier": "prettier --check --ignore-path .prettierignore .",
"prettier:fix": "prettier --write --ignore-path .prettierignore .",
"typecheck": "tsc --noEmit",
"typecheck:emit": "tsc --declaration --emitDeclarationOnly --outDir dist/types",
"commit": "cz",
"prepare": "is-ci || husky install",
"semantic-release": "semantic-release"
},
"repository": {
"type": "git",
"url": "https://github.com/yassinedoghri/astro-i18next.git"
},
"files": [
"src",
"!src/index.d.ts",
"!src/__tests__",
"dist"
],
"type": "module",
"main": "./dist/index.js",
"types": "./dist/types/index.d.ts",
"exports": {
".": "./dist/index.js",
"./components": "./src/components/index.ts"
},
"typesVersions": {
"*": {
"index": [
"./dist/types/index.d.ts"
],
"components": [
"./src/components/index.d.ts"
]
}
},
"bin": {
"astro-i18next": "./dist/cli/index.js"
},
"keywords": [
"astro-component",
"seo",
"i18next",
"i18n",
"internationalization",
"i10n",
"localization"
],
"author": {
"name": "Yassine Doghri",
"email": "yassine@doghri.fr",
"url": "https://yassinedoghri.com/"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/yassinedoghri/astro-i18next/issues"
},
"homepage": "https://astro-i18next.yassinedoghri.com/",
"peerDependencies": {
"astro": ">=1.0.0"
},
"dependencies": {
"@proload/core": "^0.3.3",
"@proload/plugin-tsm": "^0.2.1",
"i18next": "^22.4.10",
"i18next-browser-languagedetector": "^7.0.1",
"i18next-fs-backend": "^2.1.1",
"i18next-http-backend": "^2.1.1",
"iso-639-1": "^2.1.15",
"locale-emoji": "^0.3.0",
"pathe": "^1.1.0"
},
"devDependencies": {
"@commitlint/cli": "^17.4.4",
"@commitlint/config-conventional": "^17.4.4",
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/fs-extra": "^11.0.1",
"@types/yargs": "^17.0.22",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"@vitest/coverage-c8": "^0.29.2",
"all-contributors-cli": "^6.24.0",
"astro": "2.0.17",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"esbuild-plugin-fileloc": "^0.0.6",
"esbuild": "^0.17.11",
"eslint-config-prettier": "^8.6.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint": "^8.35.0",
"fdir": "^6.0.1",
"fs-extra": "^11.1.0",
"husky": "^8.0.3",
"i18next": "^22.4.10",
"is-ci": "^3.0.1",
"lint-staged": "^13.1.2",
"prettier-plugin-astro": "^0.8.0",
"prettier": "2.8.4",
"semantic-release": "^20.1.1",
"typescript": "^4.9.5",
"vitest": "^0.29.2",
"yargs": "^17.7.1"
},
"lint-staged": {
"*.{js,cjs,ts}": "eslint --ext js,cjs,ts . --cache --fix",
"*.{js,cjs,ts,astro,css,md}": "prettier --write --ignore-path .prettierignore ."
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

94
node_modules/astro-i18next/src/cli/generate.ts generated vendored Normal file
View File

@@ -0,0 +1,94 @@
import fs from "fs";
import { resolve } from "pathe";
import { AstroI18nextConfig } from "../types";
import {
getAstroPagesFullPaths,
createFiles,
FileToGenerate,
generateLocalizedFrontmatter,
overwriteAstroFrontmatter,
parseFrontmatter,
resolveTranslatedAstroPath,
resolveRelativePathsLevel,
} from "./utils";
/**
* Reads all files inside inputPath
*
* @param inputPath
* @param locales
* @param outputPath
*/
export const generate = (
inputPath: string,
defaultLocale: AstroI18nextConfig["defaultLocale"],
locales: AstroI18nextConfig["locales"],
showDefaultLocale = false,
flatRoutes?: AstroI18nextConfig["flatRoutes"],
outputPath: string = inputPath
): { filesToGenerate: FileToGenerate[]; timeToProcess: number } => {
const start = process.hrtime();
// default locale page paths
const astroPagesFullPaths = showDefaultLocale
? getAstroPagesFullPaths(inputPath, defaultLocale, locales)
: getAstroPagesFullPaths(inputPath, undefined, locales);
const filesToGenerate: FileToGenerate[] = [];
astroPagesFullPaths.forEach(async function (astroFileFullPath: string) {
const astroFilePath = resolve(astroFileFullPath).replace(
resolve(inputPath),
""
);
const inputFilePath = showDefaultLocale
? [inputPath, defaultLocale, astroFilePath].join("/")
: [inputPath, astroFilePath].join("/");
const fileContents = fs.readFileSync(inputFilePath);
const fileContentsString = fileContents.toString();
const parsedFrontmatter = parseFrontmatter(fileContentsString);
locales.forEach((locale) => {
const isOtherLocale = locale !== defaultLocale;
const fileDepth = showDefaultLocale ? 0 : Number(isOtherLocale);
// add i18next's changeLanguage function to frontmatter
const frontmatterCode = generateLocalizedFrontmatter(
parsedFrontmatter,
locale
);
// get the astro file contents
let newFileContents = overwriteAstroFrontmatter(
fileContentsString,
frontmatterCode
);
// add depth to imports and Astro.glob pattern
newFileContents = resolveRelativePathsLevel(newFileContents, fileDepth);
const createLocaleFolder = showDefaultLocale ? true : isOtherLocale;
filesToGenerate.push({
path: resolve(
resolveTranslatedAstroPath(
astroFilePath,
createLocaleFolder ? locale : undefined,
outputPath,
flatRoutes
)
),
source: newFileContents,
});
});
});
createFiles(filesToGenerate);
return {
filesToGenerate,
timeToProcess: process.hrtime(start)[1] / 1000000,
};
};

74
node_modules/astro-i18next/src/cli/index.ts generated vendored Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env node
import { flattenRoutes } from "../config";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { generate } from "./generate";
import { loadConfig, normalizePath } from "./middlewares";
import { GenerateArgs } from "./types";
yargs(hideBin(process.argv))
.usage("usage: $0 <command>")
.command<GenerateArgs>(
"generate [path] [options]",
"generates localized Astro pages",
(yargs) => {
return yargs
.positional("path", {
type: "string",
description: "Path to the Astro project folder",
default: "./",
})
.option("output", {
alias: "o",
type: "string",
description:
"Set the output of the generated pages if different from input",
});
},
async (argv) => {
if (argv.verbose) {
console.info(`Generating localized pages: ${argv.config.locales}`);
}
const pagesPath = argv.path + "src/pages";
const flatRoutes = flattenRoutes(argv.config.routes);
const result = generate(
pagesPath,
argv.config.defaultLocale,
argv.config.locales,
argv.config.showDefaultLocale,
flatRoutes,
argv.output
);
if (argv.verbose) {
const filepaths = result.filesToGenerate.map(
(fileToGenerate) => fileToGenerate.path
);
console.log(`\n✨ ${filepaths.join("\n✨ ")}\n`);
}
// All good! Show success feedback
console.log(
`🧪 Localized .astro pages were generated successfully, it took ${result.timeToProcess.toFixed()}ms!`
);
}
)
.middleware([normalizePath, loadConfig], true)
.options({
config: {
alias: "c",
type: "string",
description:
"Set the output of the generated pages if different from input",
},
verbose: {
alias: "v",
type: "boolean",
description: "Run with verbose logging",
},
})
.parse();

32
node_modules/astro-i18next/src/cli/middlewares.ts generated vendored Normal file
View File

@@ -0,0 +1,32 @@
import { pathToFileURL } from "url";
import { MiddlewareFunction } from "yargs";
import { getUserConfig } from "../utils";
import { GenerateArgs, GlobalArgs } from "./types";
// @ts-ignore
export const loadConfig: MiddlewareFunction<GlobalArgs & GenerateArgs> = async (
argv
): Promise<GlobalArgs & GenerateArgs> => {
const { path, config } = argv;
const userConfig = await getUserConfig(pathToFileURL(path), config as any);
if (path && !userConfig?.value) {
throw new Error(
`Could not find a config file at ${JSON.stringify(
path
)}. Does the file exist?`
);
}
return { ...argv, config: userConfig?.value };
};
// @ts-ignore
export const normalizePath: MiddlewareFunction<
GlobalArgs & GenerateArgs
> = async (argv): Promise<GlobalArgs & GenerateArgs> => {
const { path } = argv;
return { ...argv, path: path.endsWith("/") ? path : path + "/" };
};

162
node_modules/astro-i18next/src/cli/transformer.ts generated vendored Normal file
View File

@@ -0,0 +1,162 @@
import ts from "typescript";
/**
* Traverse ts' AST to inject i18next's language switch
* @param context
* @returns
*/
export const transformer: ts.TransformerFactory<ts.SourceFile> =
(context: ts.TransformationContext) => (rootNode) => {
const { factory, getCompilerOptions } = context;
let doesI18nextImportExist = false;
const { locale } = getCompilerOptions();
function visit(node: ts.Node): ts.Node {
// isolate i18next import statement
if (
ts.isImportDeclaration(node) &&
ts.isStringLiteral(node.moduleSpecifier) &&
node.moduleSpecifier.text === "i18next" &&
ts.isImportClause(node.importClause)
) {
doesI18nextImportExist = true;
if (!node.importClause.namedBindings) {
return factory.updateImportDeclaration(
node,
node.modifiers,
factory.createImportClause(
node.importClause.isTypeOnly,
node.importClause.name,
factory.createNamedImports([
factory.createImportSpecifier(
false,
undefined,
factory.createIdentifier("changeLanguage")
),
])
),
node.moduleSpecifier,
node.assertClause
);
}
if (
ts.isImportClause(node.importClause) &&
ts.isNamedImports(node.importClause.namedBindings) &&
!node.importClause.namedBindings.elements.find(
(namedImport) => namedImport.name.escapedText === "changeLanguage"
)
) {
// import changeLanguage function in i18next import declaration
return factory.updateImportDeclaration(
node,
node.modifiers,
factory.updateImportClause(
node.importClause,
false,
node.importClause.name,
factory.updateNamedImports(node.importClause.namedBindings, [
...node.importClause.namedBindings.elements,
factory.createImportSpecifier(
false,
undefined,
factory.createIdentifier("changeLanguage")
),
])
),
node.moduleSpecifier,
node.assertClause
);
}
return node;
}
// remove any occurrence of changeLanguage() call
if (
ts.isExpressionStatement(node) &&
ts.isCallExpression(node.expression) &&
((ts.isPropertyAccessExpression(node.expression.expression) &&
node.expression.expression.name.escapedText === "changeLanguage") ||
(ts.isIdentifier(node.expression.expression) &&
node.expression.expression.escapedText === "changeLanguage"))
) {
return undefined;
}
return ts.visitEachChild(node, visit, context);
}
let visitedNode = ts.visitNode(rootNode, visit);
const statements = [...visitedNode.statements];
if (!doesI18nextImportExist) {
statements.unshift(
factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([
factory.createImportSpecifier(
false,
undefined,
factory.createIdentifier("changeLanguage")
),
])
),
factory.createStringLiteral("i18next"),
undefined
)
);
}
// append the changeLanguage statement after imports
// get a boolean array with values telling whether or not a statement is an import
const importDeclarationsMap = statements.map((statement) =>
ts.isImportDeclaration(statement)
);
const lastIndexOfImportDeclaration =
importDeclarationsMap.lastIndexOf(true);
// insert changeLanguage statement after the imports
// and surrounded by line breaks
statements.splice(
lastIndexOfImportDeclaration + 1,
0,
factory.createIdentifier("\n") as unknown as ts.Statement
);
statements.splice(
lastIndexOfImportDeclaration + 1,
0,
factory.createExpressionStatement(
factory.createCallExpression(
factory.createIdentifier("changeLanguage"),
undefined,
[factory.createStringLiteral(locale as string)]
)
)
);
statements.splice(
lastIndexOfImportDeclaration + 1,
0,
factory.createIdentifier("\n") as unknown as ts.Statement
);
visitedNode = factory.updateSourceFile(
visitedNode,
statements,
visitedNode.isDeclarationFile,
visitedNode.referencedFiles,
visitedNode.typeReferenceDirectives,
visitedNode.hasNoDefaultLib,
visitedNode.libReferenceDirectives
);
return visitedNode;
};

11
node_modules/astro-i18next/src/cli/types.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import { AstroI18nextConfig } from "../types";
export interface GlobalArgs {
verbose: boolean;
}
export interface GenerateArgs {
path: string;
config: AstroI18nextConfig;
output: string;
}

182
node_modules/astro-i18next/src/cli/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,182 @@
import { fdir, PathsOutput } from "fdir";
import fsExtra from "fs-extra";
import path from "path";
import fs from "fs";
import ts from "typescript";
import { transformer } from "./transformer";
import { AstroI18nextConfig } from "../types";
export interface FileToGenerate {
path: string;
source: string;
}
export const doesStringIncludeFrontmatter = (source: string): boolean =>
/---.*---/s.test(source);
export const extractFrontmatterFromAstroSource = (source: string): string => {
if (doesStringIncludeFrontmatter(source)) {
const {
groups: { frontmatter },
} = /---(?<frontmatter>(.*))---/s.exec(source);
return frontmatter;
}
return "";
};
export const overwriteAstroFrontmatter = (
source: string,
frontmatter: string
): string => {
if (doesStringIncludeFrontmatter(source)) {
return source.replace(/---[\s\S]*---/g, `---\n${frontmatter.trim()}\n---`);
}
return `---\n${frontmatter.trim()}\n---\n\n` + source;
};
export const addDepthToRelativePath = (
relativePath: string,
depth: number = 1
): string => {
if (relativePath.startsWith("./") && depth > 0) {
// remove "./" from relativePath
relativePath = relativePath.slice(2);
}
return relativePath.padStart(relativePath.length + depth * 3, "../");
};
/**
* file is hidden if its name or any of its parent folders start with an underscore
*/
export const isFileHidden = (filepath: string): boolean => {
return /((^_)|(\/_))/.test(filepath);
};
export const resolveRelativePathsLevel = (
fileContents: string,
fileDepth: number
) => {
fileContents = fileContents.replace(
/(import\s+.*["'])(\..*)(["'])/g,
(_, before, relativePath, after) =>
`${before}${addDepthToRelativePath(relativePath, fileDepth)}${after}`
);
fileContents = fileContents.replace(
/(Astro.glob\(["'])(\..*)(["']\))/g,
(_, before, relativePath, after) =>
`${before}${addDepthToRelativePath(relativePath, fileDepth)}${after}`
);
fileContents = fileContents.replace(
/(<script\s+src=["'])(\..*)(["'])/g,
(_, before, relativePath, after) =>
`${before}${addDepthToRelativePath(relativePath, fileDepth)}${after}`
);
return fileContents;
};
/* c8 ignore start */
/**
* parse frontmatter using typescript compiler
*
* @param source
*/
export const parseFrontmatter = (source: string): ts.SourceFile =>
ts.createSourceFile(
"x.ts",
extractFrontmatterFromAstroSource(source),
ts.ScriptTarget.Latest
);
export const generateLocalizedFrontmatter = (
tsNode: ts.SourceFile,
locale: string
) => {
// generate for default locale, then loop over locales to generate other pages
const result: ts.TransformationResult<ts.SourceFile> = ts.transform(
tsNode,
[transformer],
{ locale }
);
const printer = ts.createPrinter();
return printer.printNode(
ts.EmitHint.Unspecified,
result.transformed[0],
tsNode
);
};
/**
* Crawls pages directory and returns all Astro pages
* except for locale folders and excluded pages / directories (starting with underscore).
* (https://docs.astro.build/en/core-concepts/routing/#excluding-pages)
*
* @param pagesDirectoryPath
* @param childDirToCrawl will make the function crawl inside the given
* `childDirToCrawl` (doesn't take paths, only dirname).
*/
export const getAstroPagesFullPaths = (
pagesDirectoryPath: string,
childDirToCrawl: AstroI18nextConfig["defaultLocale"] | undefined = undefined,
locales: AstroI18nextConfig["locales"] = []
): PathsOutput => {
// eslint-disable-next-line new-cap
const api = new fdir()
.filter(
(filepath) => !isFileHidden(filepath) && filepath.endsWith(".astro")
)
.exclude((dirName) => locales.includes(dirName))
.withFullPaths();
return childDirToCrawl
? (api
.crawl(`${pagesDirectoryPath}${path.sep}${childDirToCrawl}`)
.sync() as PathsOutput)
: (api.crawl(pagesDirectoryPath).sync() as PathsOutput);
};
export const createFiles = (filesToGenerate: FileToGenerate[]): void => {
filesToGenerate.forEach((fileToGenerate) => {
fsExtra.ensureDirSync(path.dirname(fileToGenerate.path));
fs.writeFileSync(fileToGenerate.path, fileToGenerate.source);
});
};
/* c8 ignore stop */
/**
* Resolves the right translated path based on
* a given `astroFilePath` and a locale,
* with the `routeTranslations` mapping.
*/
export const resolveTranslatedAstroPath = (
astroFilePath: string,
locale: string | null = null,
basePath: string = "",
flatRoutes: AstroI18nextConfig["flatRoutes"] = {}
) => {
astroFilePath = astroFilePath.replace(/^\/+|\/+$/g, "");
// remove trailing slash of basePath if any
basePath = basePath.replace(/\/+$/g, "");
if (locale === null) {
return `${basePath}/${astroFilePath}`;
}
astroFilePath = astroFilePath.replace(/.astro$/, "");
const filePath = `/${locale}/${astroFilePath}`;
// is route translated?
if (Object.prototype.hasOwnProperty.call(flatRoutes, filePath)) {
return `${basePath}${flatRoutes[filePath]}.astro`;
}
return `${basePath}/${locale}/${astroFilePath}.astro`;
};

View File

@@ -0,0 +1,17 @@
---
import i18next from "i18next";
import { localizeUrl } from "../..";
const supportedLanguages = i18next.languages;
const currentUrl = Astro.url.href;
---
{
supportedLanguages.map((supportedLanguage) => (
<link
rel="alternate"
hreflang={supportedLanguage}
href={localizeUrl(currentUrl, supportedLanguage)}
/>
))
}

View File

@@ -0,0 +1,49 @@
---
import i18next from "i18next";
import { localizePath } from "../..";
import localeEmoji from "locale-emoji";
import ISO6991 from "iso-639-1";
interface languageMapping {
[localeCode: string]: string;
}
export interface Props extends astroHTML.JSX.SelectHTMLAttributes {
showFlag?: boolean;
languageMapping?: languageMapping;
}
const supportedLanguages = i18next.languages;
const currentLanguage = i18next.language;
const { pathname } = Astro.url;
const { showFlag = false, languageMapping, ...attributes } = Astro.props;
---
<select onchange="location = this.value;" {...attributes}>
{
supportedLanguages.map((supportedLanguage: string) => {
let value = localizePath(pathname, supportedLanguage);
const flag = showFlag ? localeEmoji(supportedLanguage) + " " : "";
let nativeName = "";
if (
languageMapping &&
languageMapping.hasOwnProperty(supportedLanguage)
) {
nativeName = languageMapping[supportedLanguage];
} else {
nativeName = ISO6991.getNativeName(supportedLanguage);
}
const label = flag + nativeName;
return (
<option value={value} selected={supportedLanguage === currentLanguage}>
{label}
</option>
);
})
}
</select>

21
node_modules/astro-i18next/src/components/Trans.astro generated vendored Normal file
View File

@@ -0,0 +1,21 @@
---
import { interpolate, createReferenceStringFromHTML } from "..";
export interface Props {
i18nKey?: string;
ns?: string;
}
const { i18nKey, ns } = Astro.props;
const referenceString = await Astro.slots.render("default");
let key: string;
if (typeof i18nKey === "string") {
key = i18nKey;
} else {
key = createReferenceStringFromHTML(referenceString);
}
---
<Fragment set:html={interpolate(key, referenceString, ns)} />

3
node_modules/astro-i18next/src/components/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export { default as Trans } from "./Trans.astro";
export { default as LanguageSelector } from "./LanguageSelector.astro";
export { default as HeadHrefLangs } from "./HeadHrefLangs.astro";

3
node_modules/astro-i18next/src/components/index.ts generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export { default as Trans } from "./Trans.astro";
export { default as LanguageSelector } from "./LanguageSelector.astro";
export { default as HeadHrefLangs } from "./HeadHrefLangs.astro";

89
node_modules/astro-i18next/src/config.ts generated vendored Normal file
View File

@@ -0,0 +1,89 @@
import { AstroI18nextConfig, AstroI18nextGlobal, Routes } from "./types";
export const AstroI18next: AstroI18nextGlobal = {
config: {
defaultLocale: "cimode",
locales: [],
namespaces: "translation",
defaultNamespace: "translation",
load: ["server"],
routes: {},
flatRoutes: {},
showDefaultLocale: false,
trailingSlash: "ignore",
resourcesBasePath: "/locales",
},
};
/* c8 ignore start */
export const setAstroI18nextConfig = (config: AstroI18nextConfig) => {
let flatRoutes = {};
for (const key in config) {
if (key === "routes") {
flatRoutes = flattenRoutes(config[key]);
}
AstroI18next.config[key] = config[key];
}
// @ts-ignore
AstroI18next.config.flatRoutes = flatRoutes;
};
export const astroI18nextConfigBuilder = (
config: AstroI18nextConfig
): AstroI18nextConfig => {
return { ...AstroI18next.config, ...config };
};
/* c8 ignore stop */
/**
* This will create a mapping of translated routes to search them easily.
*
* TODO: render all routes mappings in here (even those not translated),
* this will help simplify utility functions logic
*/
export const flattenRoutes = (
routes: AstroI18nextConfig["routes"],
previous: string[] = [],
translatedPrevious: string[] = [],
prevResult: AstroI18nextConfig["flatRoutes"] = null
): AstroI18nextConfig["flatRoutes"] => {
let result = prevResult || {};
for (const i in routes) {
if (typeof routes[i] === "object" && routes[i] !== null) {
// Recursion on deeper objects
flattenRoutes(
routes[i] as Routes,
[...previous, i],
[
...translatedPrevious,
Object.prototype.hasOwnProperty.call(routes[i], "index")
? routes[i]["index"]
: i,
],
result
);
} else {
let key = "/" + previous.join("/");
let value = "/" + translatedPrevious.join("/");
if (i === "index") {
result[key] = value;
key += "/" + i;
value += "/" + i;
result[key] = value;
} else {
key += "/" + i;
value += "/" + routes[i];
result[key] = value;
}
}
}
return result;
};

185
node_modules/astro-i18next/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,185 @@
import { AstroIntegration } from "astro";
import { InitOptions } from "i18next";
import { astroI18nextConfigBuilder, setAstroI18nextConfig } from "./config";
import { AstroI18nextConfig, AstroI18nextOptions, Plugins } from "./types";
import {
moveDefaultLocaleToFirstIndex,
deeplyStringifyObject,
getUserConfig,
} from "./utils";
import { resolve } from "pathe";
export default (options?: AstroI18nextOptions): AstroIntegration => {
const customConfigPath = options?.configPath;
return {
name: "astro-i18next",
hooks: {
"astro:config:setup": async ({ config, injectScript }) => {
/**
* 0. Get user config
*/
const userConfig = await getUserConfig(config.root, customConfigPath);
if (customConfigPath && !userConfig?.value) {
throw new Error(
`[astro-i18next]: Could not find a config file at ${JSON.stringify(
customConfigPath
)}. Does the file exist?`
);
}
const astroI18nextConfig: AstroI18nextConfig =
astroI18nextConfigBuilder(userConfig?.value as AstroI18nextConfig);
/**
* 1. Validate and prepare config
*/
if (
!astroI18nextConfig.defaultLocale ||
astroI18nextConfig.defaultLocale === ""
) {
throw new Error(
"[astro-i18next]: you must set a `defaultLocale` in your astro-i18next config!"
);
}
if (!astroI18nextConfig.locales) {
astroI18nextConfig.locales = [astroI18nextConfig.defaultLocale];
}
if (
!astroI18nextConfig.locales.includes(astroI18nextConfig.defaultLocale)
) {
astroI18nextConfig.locales.unshift(astroI18nextConfig.defaultLocale);
}
// make sure to have default locale set as first element in locales (for supportedLngs)
if (
astroI18nextConfig.locales[0] !== astroI18nextConfig.defaultLocale
) {
moveDefaultLocaleToFirstIndex(
astroI18nextConfig.locales as string[],
astroI18nextConfig.defaultLocale
);
}
// add trailingSlash config from astro if not set
astroI18nextConfig.trailingSlash = config.trailingSlash;
if (astroI18nextConfig.load.includes("server")) {
// Build server side i18next config
// set i18next supported and fallback languages (same as locales)
const serverConfig: InitOptions = {
supportedLngs: astroI18nextConfig.locales as string[],
fallbackLng: astroI18nextConfig.locales as string[],
ns: astroI18nextConfig.namespaces,
defaultNS: astroI18nextConfig.defaultNamespace,
initImmediate: false,
backend: {
loadPath: resolve(
`${config.publicDir.pathname}/${astroI18nextConfig.resourcesBasePath}/{{lng}}/{{ns}}.json`
),
},
...astroI18nextConfig.i18nextServer,
};
const defaultI18nextServerPlugins: Plugins = {
fsBackend: "i18next-fs-backend",
};
const i18nextServerPlugins = {
...defaultI18nextServerPlugins,
...astroI18nextConfig.i18nextServerPlugins,
};
let { imports: serverImports, i18nextInit: i18nextInitServer } =
i18nextScriptBuilder(serverConfig, i18nextServerPlugins);
// initializing runtime astro-i18next config
serverImports += `import {initAstroI18next} from "astro-i18next";`;
const astroI18nextInit = `initAstroI18next(${deeplyStringifyObject(
astroI18nextConfig
)});`;
// server side i18next instance
injectScript(
"page-ssr",
serverImports + i18nextInitServer + astroI18nextInit
);
}
if (astroI18nextConfig.load?.includes("client")) {
const clientConfig: InitOptions = {
supportedLngs: astroI18nextConfig.locales as string[],
fallbackLng: astroI18nextConfig.locales as string[],
ns: astroI18nextConfig.namespaces,
defaultNS: astroI18nextConfig.defaultNamespace,
detection: {
order: ["htmlTag"],
caches: [],
},
backend: {
loadPath: `${astroI18nextConfig.resourcesBasePath}/{{lng}}/{{ns}}.json`,
},
...astroI18nextConfig.i18nextClient,
};
const defaultI18nextClientPlugins: Plugins = {
httpBackend: "i18next-http-backend",
LanguageDetector: "i18next-browser-languagedetector",
};
const i18nextClientPlugins = {
...defaultI18nextClientPlugins,
...astroI18nextConfig.i18nextClientPlugins,
};
let { imports: clientImports, i18nextInit: i18nextInitClient } =
i18nextScriptBuilder(clientConfig, i18nextClientPlugins);
// client side i18next instance
injectScript("before-hydration", clientImports + i18nextInitClient);
}
},
},
};
};
const i18nextScriptBuilder = (config: InitOptions, plugins: Plugins) => {
let imports = `import i18next from "i18next";`;
let i18nextInit = "i18next";
if (Object.keys(plugins).length > 0) {
for (const key of Object.keys(plugins)) {
// discard plugin if it does not have import name
if (plugins[key] === null) {
continue;
}
imports += `import ${key} from "${plugins[key]}";`;
i18nextInit += `.use(${key.replace(/[{}]/g, "")})`;
}
}
i18nextInit += `.init(${deeplyStringifyObject(config)});`;
return { imports, i18nextInit };
};
export function initAstroI18next(config: AstroI18nextConfig) {
// init runtime config
setAstroI18nextConfig(config);
}
export { AstroI18next } from "./config";
export {
createReferenceStringFromHTML,
detectLocaleFromPath,
interpolate,
localizePath,
localizeUrl,
} from "./utils";
export { AstroI18nextConfig, AstroI18nextOptions } from "./types";

134
node_modules/astro-i18next/src/types.ts generated vendored Normal file
View File

@@ -0,0 +1,134 @@
import { InitOptions } from "i18next";
export interface AstroI18nextGlobal {
config: AstroI18nextConfig;
}
export interface AstroI18nextOptions {
/**
* Path to your astro-i18next config file
*
* @default 'astro-i18next.config.js'
*/
configPath?: string;
}
export interface Routes {
[segment: string]: string | Record<string, string | Routes>;
}
export interface Plugins {
[importName: string]: string | null;
}
export interface AstroI18nextConfig {
/**
* The default locale for your website.
*
* @default "cimode"
*/
defaultLocale: string;
/**
* The locales that are supported by your website.
*
* @default []
*/
locales: string[];
/**
* String or array of namespaces to load
*
* @default "translation"
*/
namespaces?: string | string[];
/**
* Default namespace used if not passed to the translation function
*
* @default "translation"
*/
defaultNamespace?: string;
/**
* Load i18next on server side only, client side only or both.
*
* @default ["server"]
*/
load?: ("server" | "client")[];
/**
* Set base path for i18next resources.
*
* @default "/locales"
*/
resourcesBasePath?: string;
/**
* i18next server side config. See https://www.i18next.com/overview/configuration-options
*/
i18nextServer?: InitOptions;
/**
* i18next client side config. See https://www.i18next.com/overview/configuration-options
*/
i18nextClient?: InitOptions;
/**
* The translations for your routes.
*
* @default {}
*/
routes?: Routes;
/**
* Generated mappings based on the routes
*
* @default {}
*/
readonly flatRoutes?: Record<string, string>;
/**
* The display behaviour for the URL locale.
*
* @default false
*/
showDefaultLocale?: boolean;
/**
* i18next server side plugins. See https://www.i18next.com/overview/plugins-and-utils
*
* Include the plugins with the key being the import name and the value being the plugin name.
*
* Eg.:
* ```
* {
* "Backend": "i18next-fs-backend",
* }
* ```
*/
i18nextServerPlugins?: Plugins;
/**
* i18next client side plugins. See https://www.i18next.com/overview/plugins-and-utils
*
* Include the plugins with the key being the import name and the value being the plugin name.
*
* Eg.:
* ```
* {
* "{initReactI18next}": "react-i18next",
* }
* ```
*/
i18nextClientPlugins?: Plugins;
/**
* Set the route matching behavior of the dev server. Choose from the following options:
*
* 'always' - Only match URLs that include a trailing slash (ex: "/foo/")
* 'never' - Never match URLs that include a trailing slash (ex: "/foo")
* 'ignore' - Match URLs regardless of whether a trailing "/" exists
*/
trailingSlash?: "always" | "never" | "ignore";
}

387
node_modules/astro-i18next/src/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,387 @@
import i18next, { t } from "i18next";
import { fileURLToPath } from "url";
import load from "@proload/core";
import { AstroI18nextConfig } from "./types";
import typescript from "@proload/plugin-tsm";
import { AstroI18next } from "./config";
/**
* Adapted from astro's tailwind integration:
* https://github.com/withastro/astro/tree/main/packages/integrations/tailwind
*/
/* c8 ignore start */
export const getUserConfig = async (
root: URL,
configPath?: string
): Promise<load.Config<AstroI18nextConfig>> => {
const resolvedRoot = fileURLToPath(root);
let userConfigPath: string | undefined;
if (configPath) {
const configPathWithLeadingSlash = /^\.*\//.test(configPath)
? configPath
: `./${configPath}`;
userConfigPath = fileURLToPath(new URL(configPathWithLeadingSlash, root));
}
load.use([typescript]);
return (await load("astro-i18next", {
mustExist: false,
cwd: resolvedRoot,
filePath: userConfigPath,
})) as load.Config<AstroI18nextConfig>;
};
/* c8 ignore stop */
/**
* Moves the default locale in the first index
*/
export const moveDefaultLocaleToFirstIndex = (
locales: string[],
baseLocale: string
): void => {
const baseLocaleIndex = locales.indexOf(baseLocale);
locales.splice(baseLocaleIndex, 1);
locales.unshift(baseLocale);
};
/**
* Interpolates a localized string (loaded with the i18nKey) to a given reference string.
*/
export const interpolate = (
i18nKey: string,
referenceString: string,
namespace: string | null = null
): string => {
const localizedString = t(i18nKey, { ns: namespace });
if (localizedString === i18nKey) {
console.warn(`WARNING(astro-i18next): missing translation key ${i18nKey}.`);
return referenceString;
}
const tagsRegex = /<([\w\d]+)([^>]*)>/gi;
const referenceStringMatches = referenceString.match(tagsRegex);
if (!referenceStringMatches) {
console.warn(
"WARNING(astro-i18next): default slot does not include any HTML tag to interpolate! You should use the `t` function directly."
);
return localizedString;
}
const referenceTags = [];
referenceStringMatches.forEach((tagNode) => {
const [, name, attributes] = tagsRegex.exec(tagNode);
referenceTags.push({ name, attributes });
// reset regex state
tagsRegex.exec("");
});
let interpolatedString = localizedString;
for (let index = 0; index < referenceTags.length; index++) {
const referencedTag = referenceTags[index];
// Replace opening tags
interpolatedString = interpolatedString.replaceAll(
`<${index}>`,
`<${referencedTag.name}${referencedTag.attributes}>`
);
// Replace closing tags
interpolatedString = interpolatedString.replaceAll(
`</${index}>`,
`</${referencedTag.name}>`
);
}
return interpolatedString;
};
/**
* Creates a reference string from an HTML string. The reverse of interpolate(), for use
* with <Trans> when not explicitly setting a key
*/
export const createReferenceStringFromHTML = (html: string) => {
// Allow these tags to carry through to the output
const allowedTags = ["strong", "br", "em", "i", "b"];
let forbiddenStrings: { key: string; str: string }[] = [];
if (i18next.options) {
forbiddenStrings = [
"keySeparator",
"nsSeparator",
"pluralSeparator",
"contextSeparator",
]
.map((key) => {
return {
key,
str: i18next.options[key],
};
})
.filter(function <T>(val: T | undefined): val is T {
return typeof val !== "undefined";
});
}
const tagsRegex = /<([\w\d]+)([^>]*)>/gi;
const referenceStringMatches = html.match(tagsRegex);
if (!referenceStringMatches) {
console.warn(
"WARNING(astro-i18next): default slot does not include any HTML tag to interpolate! You should use the `t` function directly."
);
return html;
}
const referenceTags = [];
referenceStringMatches.forEach((tagNode) => {
const [, name, attributes] = tagsRegex.exec(tagNode);
referenceTags.push({ name, attributes });
// reset regex state
tagsRegex.exec("");
});
let sanitizedString = html.replace(/\s+/g, " ").trim();
for (let index = 0; index < referenceTags.length; index++) {
const referencedTag = referenceTags[index];
if (
allowedTags.includes(referencedTag.name) &&
referencedTag.attributes.trim().length === 0
) {
continue;
}
sanitizedString = sanitizedString.replaceAll(
new RegExp(`<${referencedTag.name}[^>]*?\\s*\\/>`, "gi"),
`<${index}/>`
);
sanitizedString = sanitizedString.replaceAll(
`<${referencedTag.name}${referencedTag.attributes}>`,
`<${index}>`
);
sanitizedString = sanitizedString.replaceAll(
`</${referencedTag.name}>`,
`</${index}>`
);
}
for (let index = 0; index < forbiddenStrings.length; index++) {
const { key, str } = forbiddenStrings[index];
if (sanitizedString.includes(str)) {
console.warn(
`WARNING(astro-i18next): "${str}" was found in a <Trans> translation key, but it is also used as ${key}. Either explicitly set an i18nKey or change the value of ${key}.`
);
}
}
return sanitizedString;
};
export const handleTrailingSlash = (
path: string,
trailingSlash: AstroI18nextConfig["trailingSlash"]
) => {
if (path === "/") {
return path;
}
switch (trailingSlash) {
case "always":
return path.endsWith("/") ? path : path + "/";
case "never":
return path.replace(/\/$/, "");
default:
return path;
}
};
/**
* Injects the given locale to a path
*/
export const localizePath = (
path: string = "/",
locale: string | null = null,
base: string = import.meta.env.BASE_URL
): string => {
if (!locale) {
locale = i18next.language;
}
let pathSegments = path.split("/").filter((segment) => segment !== "");
const baseSegments = base.split("/").filter((segment) => segment !== "");
if (
JSON.stringify(pathSegments).startsWith(
JSON.stringify(baseSegments).replace(/]+$/, "")
)
) {
// remove base from path
pathSegments.splice(0, baseSegments.length);
}
path = pathSegments.length === 0 ? "" : pathSegments.join("/");
base = baseSegments.length === 0 ? "/" : "/" + baseSegments.join("/") + "/";
const {
flatRoutes,
showDefaultLocale,
defaultLocale,
locales,
trailingSlash,
} = AstroI18next.config;
if (!locales.includes(locale)) {
console.warn(
`WARNING(astro-i18next): "${locale}" locale is not supported, add it to the locales in your astro config.`
);
return handleTrailingSlash(`${base}${path}`, trailingSlash);
}
if (pathSegments.length === 0) {
if (showDefaultLocale) {
return handleTrailingSlash(`${base}${locale}`, trailingSlash);
}
return handleTrailingSlash(
locale === defaultLocale ? base : `${base}${locale}`,
trailingSlash
);
}
// check if the path is not already present in flatRoutes
if (locale === defaultLocale) {
const translatedPathKey = Object.keys(flatRoutes).find(
(key) => flatRoutes[key] === "/" + path
);
if (typeof translatedPathKey !== "undefined") {
pathSegments = translatedPathKey
.split("/")
.filter((segment) => segment !== "");
}
}
// remove locale from pathSegments (if there is any)
for (const locale of locales) {
if (pathSegments[0] === locale) {
pathSegments.shift();
break;
}
}
// prepend the given locale if it's not the base one (unless showDefaultLocale)
if (showDefaultLocale || locale !== defaultLocale) {
pathSegments = [locale, ...pathSegments];
}
const localizedPath = base + pathSegments.join("/");
// is path translated?
if (
Object.prototype.hasOwnProperty.call(
flatRoutes,
localizedPath.replace(/\/$/, "")
)
) {
return handleTrailingSlash(
flatRoutes[localizedPath.replace(/\/$/, "")],
trailingSlash
);
}
return handleTrailingSlash(localizedPath, trailingSlash);
};
/**
* Injects the given locale to a url
*/
export const localizeUrl = (
url: string,
locale: string | null = null,
base: string = import.meta.env.BASE_URL
): string => {
const [protocol, , host, ...path] = url.split("/");
const baseUrl = protocol + "//" + host;
return baseUrl + localizePath(path.join("/"), locale, base);
};
/**
* Returns the locale detected from a given path
*/
export const detectLocaleFromPath = (path: string) => {
// remove all leading slashes
path = path.replace(/^\/+/g, "");
const { defaultLocale, locales } = AstroI18next.config;
const pathSegments = path.split("/");
if (
JSON.stringify(pathSegments) === JSON.stringify([""]) ||
JSON.stringify(pathSegments) === JSON.stringify(["", ""])
) {
return defaultLocale;
}
// make a copy of supported locales
let otherLocales = [...locales];
otherLocales = otherLocales.filter((locale) => locale !== defaultLocale); // remove base locale (first index)
// loop over all locales except the base one
for (const otherLocale of otherLocales) {
if (pathSegments[0] === otherLocale) {
// if the path starts with one of the other locales, then detected!
return otherLocale;
}
}
// return default locale by default
return defaultLocale;
};
export const deeplyStringifyObject = (obj: object | Array<any>): string => {
const isArray = Array.isArray(obj);
let str = isArray ? "[" : "{";
for (const key in obj) {
if (obj[key] === null || obj[key] === undefined) {
continue;
}
let value = null;
// see typeof result: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#description
switch (typeof obj[key]) {
case "string": {
value = `"${obj[key]}"`;
break;
}
case "number":
case "boolean": {
value = obj[key];
break;
}
case "object": {
value = deeplyStringifyObject(obj[key]);
break;
}
case "function": {
value = obj[key].toString().replace(/\s+/g, " ");
break;
}
case "symbol": {
value = `Symbol("${obj[key].description}")`;
break;
}
/* c8 ignore start */
default:
break;
/* c8 ignore stop */
}
str += isArray ? `${value},` : `"${key}": ${value},`;
}
return `${str}${isArray ? "]" : "}"}`;
};