From 405cf9f3ed1f531f5f04b408d0d0ab373f120830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Thu, 1 May 2025 00:58:59 -0500 Subject: [PATCH 01/11] Add i18n and 10n with lingui and babel --- .babelrc | 6 + lingui.config.js | 20 + next.config.mjs | 6 + package.json | 9 +- src/components/layout.js | 12 +- src/i18n.ts | 39 + src/locales/en/messages.js | 1 + src/locales/en/messages.po | 18 + src/locales/es/messages.js | 1 + src/locales/es/messages.mo | Bin 0 -> 420 bytes src/locales/es/messages.po | 18 + src/locales/pt/messages.js | 1 + src/locales/pt/messages.po | 12 + src/pages/_app.js | 54 +- src/pages/index.js | 13 + yarn.lock | 14802 ++++++++++++++++++++++------------- 16 files changed, 9511 insertions(+), 5501 deletions(-) create mode 100644 .babelrc create mode 100644 lingui.config.js create mode 100644 src/i18n.ts create mode 100644 src/locales/en/messages.js create mode 100644 src/locales/en/messages.po create mode 100644 src/locales/es/messages.js create mode 100644 src/locales/es/messages.mo create mode 100644 src/locales/es/messages.po create mode 100644 src/locales/pt/messages.js create mode 100644 src/locales/pt/messages.po diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..63c84127 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "next/babel" + ], + "plugins": ["@lingui/babel-plugin-lingui-macro"] +} diff --git a/lingui.config.js b/lingui.config.js new file mode 100644 index 00000000..2bfb0a72 --- /dev/null +++ b/lingui.config.js @@ -0,0 +1,20 @@ +const nextConfig = require('./next.config') + +console.log('nextConfig', nextConfig) + +/** @type {import('@lingui/conf').LinguiConfig} */ +module.exports = { + locales: nextConfig.i18n.locales, + pseudoLocale: 'pseudo', + sourceLocale: nextConfig.i18n.defaultLocale, + fallbackLocales: { + default: 'en', + }, + catalogs: [ + { + path: 'src/locales/{locale}/messages', + // path: 'src/locales/{locale}', + include: ['src/'], + }, + ], +} diff --git a/next.config.mjs b/next.config.mjs index 1fd06d66..89c32198 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -18,4 +18,10 @@ export default withMDX({ images: { domains: ["raw.githubusercontent.com", "numpy.org", "dask.org", "chainer.org", ], }, + i18n: { + // These are all the locales you want to support in + // your application + locales: ["en", "es", "pt"], + defaultLocale: "en", + }, }) diff --git a/package.json b/package.json index 60a3d7ae..f6d82d29 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,10 @@ "private": true, "scripts": { "build-cards": "node build-cards.js", + "extract": "lingui extract", + "compile": "lingui compile", "dev": "next dev", - "build": "next build", + "build": "yarn extract && next build", "start": "next start", "lint": "next lint" }, @@ -24,6 +26,8 @@ "@emotion/styled": "^11.13.0", "@fontsource-variable/inter": "^5.2.5", "@giscus/react": "^3.0.0", + "@lingui/core": "^5.0.0-next.2", + "@lingui/react": "^5.0.0-next.2", "@mdx-js/loader": "^3.0.1", "@mdx-js/react": "^3.0.1", "@next/mdx": "^14.2.14", @@ -46,6 +50,9 @@ "swr": "^2.2.5" }, "devDependencies": { + "@lingui/babel-plugin-lingui-macro": "^5.0.0-next.2", + "@lingui/cli": "^5.0.0-next.2", + "@lingui/loader": "^5.0.0-next.2", "@types/react": "^18.3.11", "eslint": "^9.12.0", "eslint-config-next": "14.2.14", diff --git a/src/components/layout.js b/src/components/layout.js index aecd295d..9d372cbd 100644 --- a/src/components/layout.js +++ b/src/components/layout.js @@ -4,6 +4,8 @@ import { Header } from '@/components/header' import { Link } from '@/components/mdx' import { Box, Flex } from '@chakra-ui/react' import Head from 'next/head' +import { Trans, useLingui } from '@lingui/react/macro' +import { useRouter } from 'next/router' export const Layout = ({ title, @@ -13,7 +15,15 @@ export const Layout = ({ url = 'https://xarray.dev', enableBanner = false, }) => { - const bannerTitle = 'Check out the new blog post on DataTree!' + /** + * This macro hook is needed to get `t` which + * is bound to i18n from React.Context + */ + const { t } = useLingui() + const router = useRouter() + const { pathname, asPath, query } = router + + const bannerTitle = t`Check out the new blog post on DataTree!` const bannerDescription = '' const bannerChildren = ( diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 00000000..1e505b82 --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,39 @@ +import { i18n, Messages } from '@lingui/core' +import { useRouter } from 'next/router' +import { useEffect } from 'react' + +/** + * Load messages for requested locale and activate it. + * This function isn't part of the LinguiJS library because there are + * many ways how to load messages — from REST API, from file, from cache, etc. + */ +export async function loadCatalog(locale: string) { + const catalog = await import(`@lingui/loader!./locales/${locale}/messages.po`) + return catalog.messages +} + +export function useLinguiInit(messages: Messages) { + const router = useRouter() + const locale = router.locale || router.defaultLocale! + const isClient = typeof window !== 'undefined' + + if (!isClient && locale !== i18n.locale) { + // there is single instance of i18n on the server + // note: on the server, we could have an instance of i18n per supported locale + // to avoid calling loadAndActivate for (worst case) each request, but right now that's what we do + i18n.loadAndActivate({ locale, messages }) + } + if (isClient && !i18n.locale) { + // first client render + i18n.loadAndActivate({ locale, messages }) + } + + useEffect(() => { + const localeDidChange = locale !== i18n.locale + if (localeDidChange) { + i18n.loadAndActivate({ locale, messages }) + } + }, [locale]) + + return i18n +} diff --git a/src/locales/en/messages.js b/src/locales/en/messages.js new file mode 100644 index 00000000..12a28f41 --- /dev/null +++ b/src/locales/en/messages.js @@ -0,0 +1 @@ +/*eslint-disable*/module.exports={messages:JSON.parse("{\"TBO1mC\":[\"Check out the new blog post on DataTree!\"]}")}; \ No newline at end of file diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po new file mode 100644 index 00000000..15a7fe43 --- /dev/null +++ b/src/locales/en/messages.po @@ -0,0 +1,18 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2025-05-01 08:03-0500\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: en\n" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Plural-Forms: \n" + +#: src/components/layout.js:26 +msgid "Check out the new blog post on DataTree!" +msgstr "Check out the new blog post on DataTree!" diff --git a/src/locales/es/messages.js b/src/locales/es/messages.js new file mode 100644 index 00000000..67fab95e --- /dev/null +++ b/src/locales/es/messages.js @@ -0,0 +1 @@ +/*eslint-disable*/module.exports={messages:JSON.parse("{\"TBO1mC\":[\"¡Consulta la nueva publicación del blog sobre DataTree!\"]}")}; \ No newline at end of file diff --git a/src/locales/es/messages.mo b/src/locales/es/messages.mo new file mode 100644 index 0000000000000000000000000000000000000000..5079fc52762475a7c143d833436bc36521263e6f GIT binary patch literal 420 zcmYk2Jx&8L5QP^6&6d;@Lq~xF2nmG%4I!XNL@dz?(Rz0$8wba;vd7`)9vpx(a09pp zSKtav5()C;ukh3OJ!`(VHr^7^y09(m37f*1Fjpq*3U|Vf@G6`LleHv~v(WtQpSoi# zAAuqRj}aU`L1C$eh5`dQxYVq3kGPX$o+enb>R0L>eK6!YkPfjS&uS2AQ>lws2bGi9 z*{vGl%*=ZNLe3!-$Eo-nET*R4%CKlF|x-?%O4l1 { const handleRouteChange = (url) => { @@ -20,29 +25,32 @@ function MyApp({ Component, pageProps }) { }, [router.events]) return ( - - {/* Google Tag Manager - Global base code */} -