├── netlify.toml ├── astro.config.js ├── src ├── locales │ ├── index.js │ ├── en │ │ └── index.json │ ├── es │ │ └── index.json │ └── fr │ │ └── index.json ├── components │ └── Trans.jsx ├── pages │ └── index.astro └── utils │ └── i18n.js ├── .gitignore ├── public └── global.css ├── i18next-parser.config.js ├── package.json └── README.md /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "yarn build" 3 | publish = "dist/" -------------------------------------------------------------------------------- /astro.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | renderers: ["@astrojs/renderer-react"], 3 | }; 4 | -------------------------------------------------------------------------------- /src/locales/index.js: -------------------------------------------------------------------------------- 1 | export { default as en } from "./en/index.json"; 2 | export { default as es } from "./es/index.json"; 3 | export { default as fr } from "./fr/index.json"; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | package-lock.json 4 | 5 | dist 6 | .netlify 7 | 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | .idea/ 13 | .vscode/ -------------------------------------------------------------------------------- /src/locales/en/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "Astro with i18next example": "Astro with i18next example", 3 | "An opinionated approach to translation with Astro and i18next.": "An opinionated approach to translation with Astro and i18next.", 4 | "transExample": "This is an example of using the Trans component" 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/es/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "Astro with i18next example": "Ejemplo de Astro con i18next", 3 | "An opinionated approach to translation with Astro and i18next.": "Un enfoque obstinado de la traducción con Astro e i18next.", 4 | "transExample": "Este es un ejemplo de uso del Trans componente." 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/fr/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "Astro with i18next example": "Astro avec i18next exemple", 3 | "An opinionated approach to translation with Astro and i18next.": "Une approche avisée de la traduction avec Astro et i18next.", 4 | "transExample": "Ceci est un exemple d'utilisation du composant Trans." 5 | } 6 | -------------------------------------------------------------------------------- /public/global.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, 7 | Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; 8 | } 9 | 10 | main { 11 | margin: 0 auto; 12 | max-width: 50em; 13 | width: 95%; 14 | } 15 | 16 | nav { 17 | display: flex; 18 | flex-wrap: wrap; 19 | gap: 1rem; 20 | } 21 | -------------------------------------------------------------------------------- /i18next-parser.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | input: ["src/**/*.{astro,jsx,js}", "!**/node_modules/**", "!public/**"], 3 | output: "src/locales/$LOCALE/index.json", 4 | defaultNamespace: "translation", 5 | keySeparator: false, 6 | namespaceSeparator: false, 7 | locales: ["en", "es", "fr"], 8 | useKeysAsDefaultValue: true, 9 | // We'll likely want to turn this off once our setup is a better place 10 | keepRemoved: true, 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-example-i18next", 3 | "version": "1.0.0", 4 | "description": "An opinionated approach to translation with Astro and i18next", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "extract-translations": "i18next" 10 | }, 11 | "author": "me@tylergaw.com", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "astro": "0.20.4", 15 | "i18next": "20.6.0", 16 | "i18next-parser": "4.4.0", 17 | "react-i18next": "11.11.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Trans.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Trans as TransOriginal, useTranslation } from "react-i18next"; 3 | 4 | /** 5 | * This is a wrapper component for react-i18next Trans. We wrap it so we can 6 | * access the useTranslation hook, then we can import this component into 7 | * .astro components for use in translating blocks of text/elements. 8 | */ 9 | const Trans = (props) => { 10 | const { t } = useTranslation(); 11 | 12 | return ( 13 | {props.children} 14 | ) 15 | }; 16 | 17 | export default Trans; -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import i18n, { lang } from "../utils/i18n"; 3 | import Trans from "../components/Trans"; 4 | 5 | const title = i18n.t("Astro with i18next example"); 6 | const desc = i18n.t("An opinionated approach to translation with Astro and i18next."); 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | {title} 14 | 15 | 16 | 17 | 18 |
19 | 25 |

{title}

26 |

{desc}

27 | 28 |

29 | 30 | This is an example of using the Trans component 31 | 32 |

33 |
34 | 35 | -------------------------------------------------------------------------------- /src/utils/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | import { en, es, fr } from "../locales"; 4 | 5 | // We export the lang for use in html attributes, css, logic, etc. 6 | export const lang = process.env.LANGUAGE || "en"; 7 | 8 | // NOTE: Add all new locales to this object 9 | const locales = { en, es, fr }; 10 | 11 | /* 12 | NOTE: This isn't necessary if you want to have namespaces in locale files 13 | We don't want to have to store namespace keys in the locale files since we're 14 | not relying on them. But, i18next needs to have at least the default namespace 15 | as a key. Here, we're munging the local objects a bit to make sure they all 16 | have the required {ns}.translation key (eg, en.translation = {...}) 17 | */ 18 | const resources = Object.entries(locales).reduce((obj, entry) => { 19 | const [ns, translation] = entry; 20 | obj[ns] = { translation }; 21 | return obj; 22 | }, {}); 23 | 24 | i18n.use(initReactI18next).init({ 25 | lng: lang, 26 | debug: true, 27 | fallbackLng: "en", 28 | // Since we're not using namespaces, we turn these off 29 | keySeparator: false, 30 | nsSeparator: false, 31 | interpolation: { 32 | escapeValue: false, 33 | }, 34 | resources, 35 | }); 36 | 37 | export default i18n; 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro with i18next Example 2 | 3 | ⚠️ Note: This isn't complete yet. There's still known issues with the `Trans` component and I'm sure unknown issues. 4 | 5 | An opinionated approach to translation with Astro and i18next. 6 | 7 | - [![Netlify Status](https://api.netlify.com/api/v1/badges/8dd962b7-a461-4ddd-b602-26c5f4e4d18d/deploy-status)](https://app.netlify.com/sites/astro-example-i18next/deploys) English [astro-example-i18next.netlify.app](https://astro-example-i18next.netlify.app/) 8 | - [![Netlify Status](https://api.netlify.com/api/v1/badges/71bdb037-6e05-4cf4-a586-ef91502df91e/deploy-status)](https://app.netlify.com/sites/astro-example-i18next-es/deploys) Spanish [astro-example-i18next-es.netlify.app](https://astro-example-i18next-es.netlify.app/) 9 | - [![Netlify Status](https://api.netlify.com/api/v1/badges/c6414a8d-6b12-4577-9f8c-88d86915e5e1/deploy-status)](https://app.netlify.com/sites/astro-example-i18next-fr/deploys) French [astro-example-i18next-fr.netlify.app](https://astro-example-i18next-fr.netlify.app/) 10 | 11 | ## Technology overview 12 | 13 | - [Astro](https://astro.build/) 14 | - [i18next](https://www.i18next.com/) 15 | - [react-i18next](https://react.i18next.com/) 16 | - [i18next-parser](https://github.com/i18next/i18next-parser) 17 | - Hosted on Netlify 18 | 19 | ## Translation overview 20 | 21 | - Translation is done at build time 22 | - This is a domain (would also work for subdomain) approach for locales. Each language has a different domain 23 | - Each language has a separate site in Netlify 24 | - The `LANGUAGE` env var is used to specify which locale to build for; `LANGUAGE=es yarn build` 25 | - English is the default and fallback language. If a translation is not available in the requested language, we use the English version to avoid blank strings 26 | - When possible, we use the English text as the key for each translation in the locale files. This makes it easier to edit text in pages, components, etc. since it lets us see the text. 27 | --------------------------------------------------------------------------------