i18n

We recommend using Lingui to create i18n applications.

NOTE

You can choose a template with the i18n option when creating a project using create-pareto.

full examples

Installation

pnpm i @lingui/cli @lingui/macro @types/accepts -D
pnpm i babel-plugin-macros @lingui/core @lingui/react accepts

SetUp

Extract and Compile messages

create lingui.config.js in the root directory of your project.

/** @type {import('@lingui/conf').LinguiConfig} */
module.exports = {
  locales: ['en', 'zh'],
  sourceLocale: 'en',
  catalogs: [
    {
      path: '<rootDir>/app/{name}/locales/{locale}/messages',
      include: ['<rootDir>/app/{name}/'],
    },
  ],
  format: 'po',
}

create babel.config.js in the root directory of your project.

module.exports = {
  plugins: ['macros'],
}

update your package.json file.

{
  "scripts": {
    "extract": "lingui extract",
    "compile": "lingui compile"
  }
}

run pnpm extract to extract messages. run pnpm compile to compile messages.

Use in your project

use Trans of @lingui/macro to translate your text.

Dynamic load route messages every request

create or update global.d.ts in the root directory of your project.

/// <reference types="react/canary" />
/// <reference types="@paretojs/core/env" />

declare global {
  interface Window {
    __INITIAL_DATA__: any
    __LOCALE__: string
    __LOCALE_MESSAGE__: any
  }
}

export {}

create i18n.ts in the root directory of your project.

import { i18n, Messages } from '@lingui/core'

export function loadCatalog(page: string, locale: string) {
  /* when you need to load messages in production,
   ** You can use a script after pnpm build to move the contents of the locales folder
   ** into the .pareto folder and modify the import paths here.
   */
  const data = require(`./app/${page}/locales/${locale}/messages`)
  return data.messages
}

export function initLinguiServer(messages: Messages, locale: string) {
  if (locale !== i18n.locale) {
    i18n.loadAndActivate({ locale, messages })
  }
}

export function initLinguiClient() {
  const locale = window.__LOCALE__
  const messages = window.__LOCALE_MESSAGE__

  i18n.load(locale, messages)
  i18n.activate(locale)
}

modify the server-entry.tsx file in the root directory of your project.

import accepts from 'accepts'
import { initLinguiServer, loadCatalog } from './i18n'
import { I18nProvider } from '@lingui/react'
import { i18n } from '@lingui/core'

const app = express()
const ABORT_DELAY = 5_000

app.get('*', async (req, res, next) => {
  const path = req.path.slice(1)
  const accept = accepts(req)
  const locale = accept.language(['en', 'zh']) || 'en'
  const messages = loadCatalog(path, locale)
  initLinguiServer(messages, locale)

  const handler = paretoRequestHandler({
    delay: ABORT_DELAY,
    pageWrapper: Page => {
      return props => (
        <I18nProvider i18n={i18n}>
          <Page {...props} />
          <script
            dangerouslySetInnerHTML={{
              __html: `
            window.__LOCALE__ = "${locale}";
            window.__LOCALE_MESSAGE__ = JSON.parse('${JSON.stringify(messages)}');
          `,
            }}
          />
        </I18nProvider>
      )
    },
  })

  await handler(req, res)
})

modify client-entry.tsx file in the root directory of your project.

import { I18nProvider } from '@lingui/react'
import { i18n } from '@lingui/core'
import { initLinguiClient } from './i18n'

const startApp = async (Page: ParetoPage) => {
  const root = document.getElementById('main') as HTMLElement
  const __INITIAL_DATA__ = window.__INITIAL_DATA__ as Record<string, any>
  initLinguiClient()

  await Page.setUpClient?.()

  hydrateRoot(
    root,
    <StrictMode>
      <PageContext>
        <I18nProvider i18n={i18n}>
          <Page initialData={__INITIAL_DATA__} />
        </I18nProvider>
      </PageContext>
    </StrictMode>,
  )
}