From de7af38881c3eb01c8427a7f5aaff8eac1df4c27 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/16] Add i18n and 10n with lingui and babel --- .babelrc | 6 + lingui.config.js | 20 + next.config.mjs | 6 + package.json | 58 +- src/components/layout.js | 10 + 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 | 2048 +++++++++++++++++++++++++++--------- 16 files changed, 1757 insertions(+), 548 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 000000000..63c841270 --- /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 000000000..2bfb0a72a --- /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 1fd06d66a..89c321987 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 6d198be52..232f71d9e 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" }, @@ -16,42 +18,48 @@ "author": "Xarray Developers & Contributors", "license": "Apache 2.0", "dependencies": { - "@chakra-ui/icons": "^2.2.1", - "@chakra-ui/next-js": "^2.3.1", - "@chakra-ui/react": "^2.9.1", + "@chakra-ui/icons": "^2.2.4", + "@chakra-ui/next-js": "^2.4.2", + "@chakra-ui/react": "^2.10.9", "@chakra-ui/system": "^2.6.2", - "@emotion/react": "^11.13.3", - "@emotion/styled": "^11.13.0", - "@fontsource-variable/inter": "^5.2.5", - "@giscus/react": "^3.0.0", - "@mdx-js/loader": "^3.0.1", - "@mdx-js/react": "^3.0.1", - "@next/mdx": "^14.2.14", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fontsource-variable/inter": "^5.2.8", + "@giscus/react": "^3.1.0", + "@lingui/core": "^5.6.0", + "@lingui/react": "^5.6.0", + "@mdx-js/loader": "^3.1.1", + "@mdx-js/react": "^3.1.1", + "@next/mdx": "^14.2.33", "apexcharts": "3.54.0", "d3": "^7.9.0", - "date-fns": "^3.0.0", + "date-fns": "^3.6.0", "date-fns-tz": "^3.2.0", "feed": "^5.1.0", - "framer-motion": "^11.9.0", - "glob": "^11.0.3", + "framer-motion": "^11.18.2", + "glob": "^11.1.0", "gray-matter": "^4.0.3", - "isomorphic-dompurify": "^2.16.0", - "next": "^14.2.30", + "isomorphic-dompurify": "^2.32.0", + "next": "^14.2.33", "next-mdx-remote": "^5.0.0", "react": "^18.3.1", - "react-apexcharts": "^1.4.1", + "react-apexcharts": "^1.8.0", "react-dom": "^18.3.1", - "react-icons": "^5.3.0", - "react-syntax-highlighter": "^15.5.0", + "react-icons": "^5.5.0", + "react-syntax-highlighter": "^15.6.6", "rehype-slug": "^6.0.0", - "swr": "^2.2.5" + "swr": "^2.3.6", + "yarn": "^1.22.22" }, "devDependencies": { - "@types/react": "^18.3.11", - "eslint": "^9.25.1", + "@lingui/babel-plugin-lingui-macro": "^5.6.0", + "@lingui/cli": "^5.6.0", + "@lingui/loader": "^5.6.0", + "@types/react": "^18.3.26", + "eslint": "^9.39.1", "eslint-config-next": "15.3.3", - "playwright": "^1.47.2", - "typescript": ">=5.9.2", - "webpack": "^5.101.0" + "playwright": "^1.56.1", + "typescript": "^5.9.3", + "webpack": "^5.102.1" } } diff --git a/src/components/layout.js b/src/components/layout.js index aa216cfd0..82fce6b0b 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,6 +15,14 @@ export const Layout = ({ url = 'https://xarray.dev', enableBanner = false, }) => { + /** + * 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 = 'Check out the latest blog post:' // The first link will be the main description for the banner const bannerDescription = ( diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 000000000..1e505b820 --- /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 000000000..12a28f41c --- /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 000000000..15a7fe439 --- /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 000000000..67fab95e0 --- /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 */} -