Code Monkey home page Code Monkey logo

next-translate's Introduction

next-translate

i18n for Next.js >= 10.0.0

Translations in prerendered pages

1. About next-translate

Next-translate is a tool to translate Next.js pages.

The main goal of this library is to keep the translations as simple as possible in a Next.js environment.

This library is very tiny and tree shakable.

Bundle size

How translations are added in each page?

Instead of working on /pages directory to write our pages, we are going to generate this folder before building the app, and each page will have all the necessary translations from the locale.

This "build step" is designed to make it easy to download the necessary translations for each page in an easy way.

In the configuration, you specify each page that namespaces needs:

{
  "pages": {
    "*": ["common"],
    "/": ["home"],
    "/cart": ["cart"],
    "/content/[slug]": ["content"],
    "rgx:^/account": ["account"]
  }
  // rest of config here...
}

Read here about how to add the namespaces JSON files.

Then, during the build step:

  • The download of the page namespaces are added on corresponding loader method (getInitialProps, getServerSideProps or getStaticProps). In the case that the page doesn't have any loader method is using the getStaticProps by default, except:
    • For dynamic pages that is using getServerSideProps to avoid to write a getStaticPaths.
    • For pages that have a HOC is using getInitialProps in order to avoid possible conflicts.
  • Each page is wrapped with an i18nProvider with its namespaces.

This whole process is transparent, so in your pages you can directly consume the useTranslate hook to use the namespaces, and you don't need to do anything else, because the 'build step' does it.

Example of page and how is converted

pages_/example.js

import useTranslation from 'next-translate/useTranslation'

export default function Examples() {
  const { t } = useTranslation()
  const exampleWithVariable = t('examples:example-with-variable', {
    count: 42,
  })

  return <div>{exampleWithVariable}</div>
}

And after the build step, this is converted to:

pages/example.js

// @ts-nocheck
import I18nProvider from 'next-translate/I18nProvider'
import React from 'react'
import C from '../../pages_/example'

export default function Page({ _ns, _lang, ...p }) {
  return (
    <I18nProvider lang={_lang} namespaces={_ns}>
      <C {...p} />
    </I18nProvider>
  )
}

Page = Object.assign(Page, { ...C })

export const getStaticProps = async (ctx) => {
  const _lang = ctx.locale || ctx.router?.locale || 'en'
  const ns0 = await import(`../../locales/${_lang}/common.json`).then(
    (m) => m.default
  )
  const ns1 = await import(`../../locales/${_lang}/more-examples.json`).then(
    (m) => m.default
  )
  const _ns = { common: ns0, examples: ns1 }

  let res = {}
  if (typeof res.then === 'function') res = await res

  return {
    ...res,
    props: {
      ...(res.props || {}),
      _ns,
      _lang,
    },
  }
}

2. Getting started

This is the recommended way to get started. However, if you don't like the "build step" you can use an alternative.

Install

  • yarn add next-translate

Note: For a Next.js version below than 10.0.0, use [email protected] or below

In your package.json:

"scripts": {
  "dev": "next-translate && next dev",
  "build": "next-translate && next build",
  "start": "next start"
}

Add the i18n.js config file

You should create your namespaces files inside /locales. See how to do it

Add a configuration file i18n.json (or i18n.js with module.exports) in the root of the project. Each page should have its namespaces. Take a look at it in the config section for more details.

{
  "locales": ["en", "ca", "es"],
  "defaultLocale": "en",
  "currentPagesDir": "pages_",
  "finalPagesDir": "pages",
  "localesPath": "locales",
  "pages": {
    "*": ["common"],
    "/": ["home", "example"],
    "/about": ["about"]
  }
}

Use Next.js i18n routing

From version 10.0.0 of Next.js the i18n routing is in the core, so the following must be added to the next.config.js file:

const { locales, defaultLocale } = require('./i18n.json')

module.exports = {
  i18n: { locales, defaultLocale },
}

Use translations in your pages

Then, use the translations in the page and its components:

pages_/example.js

import useTranslation from 'next-translate/useTranslation'
// ...
const { t, lang } = useTranslation()
const example = t('common:variable-example', { count: 42 })
// ...
return <div>{example}</div>

Remember that we must work in the alternative directory pages_. The pages directory will be generated during the build step.

⚠️ Important: _app.js and _document.js are not going to be wrapped with the translations context, so it's not possible to directly translate these files.

Add /pages to .gitignore

/pages directory is going to be generated every time based on /pages_, so it's not necessary to track it in git.

3. Translation JSONs folder

The /locales directory should be like this:

/locales

.
β”œβ”€β”€ ca
β”‚Β Β  β”œβ”€β”€ common.json
β”‚Β Β  └── home.json
β”œβ”€β”€ en
β”‚Β Β  β”œβ”€β”€ common.json
β”‚Β Β  └── home.json
└── es
    β”œβ”€β”€ common.json
    └── home.json

Each filename matches the namespace, while each file content should be similar to this:

{
  "title": "Hello world",
  "variable-example": "Using a variable {{count}}"
}

In order to use each translation in the project, use the translation id composed by namespace:key(ex: common:variable-example).

4. Configuration

Option Description Type Default
defaultLocale ISO of the default locale ("en" as default). string|function "en"
locales An array with all the languages to use in the project. Array<string> []
currentPagesDir A string with the directory where you have the pages code. This is needed for the "build step". string "pages_"
finalPagesDir A string with the directory that is going to be used to build the pages. Only "pages" and "src/pages" are possible. This is needed for the "build step". string "pages"
localesPath A string with the directory of JSONs locales. . This is needed for the "build step". string "locales"
package Indicate that the localesPath is a package or yarn workspace. boolean false
loadLocaleFrom As an alternative to localesPath, if appWithI18n is used instead of the "build step". It's an async function that returns the dynamic import of each locale. Function null
pages An object that defines the namespaces used in each page. Example of object: {"/": ["home", "example"]}. To add namespaces to all pages you should use the key "*", ex: {"*": ["common"]}. It's also possible to use regex using rgx: on front: {"rgx:/form$": ["form"]}. In case of using a custom server as an alternative of the "build step", you can also use a function instead of an array, to provide some namespaces depending on some rules, ex: { "/": ({ req, query }) => query.type === 'example' ? ['example'] : []} Object<Array<string>/Function {}
logger Function to log the missing keys in development and production. If you are using i18n.json as config file you should change it to i18n.js. function By default the logger is a function doing a console.warn only in development.
logBuild Configure if the build result should be logged to the console Boolean true

5. API

useTranslation

πŸ“¦Size: ~150b

This hook is the recommended way to use translations in your pages / components.

  • Input: void
  • Output: Object { t: Function, lang: string }

Example:

import React from 'react'
import useTranslation from 'next-translate/useTranslation'

export default function Description() {
  const { t, lang } = useTranslation()
  const title = t('common:title')
  const description = t`common:description` // also works as template string
  const example = t('common:example', { count: 3 }) // and with query params

  return (
    <>
      <h1>{title}</h1>
      <p>{description}</p>
      <p>{example}</p>
    <>
  )
}

The t function:

  • Input:
    • i18nKey: string (namespace:key)
    • query: Object (example: { name: 'Leonard' })
  • Output: string

withTranslation

πŸ“¦Size: ~560b

It's an alternative to useTranslation hook, but in a HOC for these components that are no-functional.

The withTranslation HOC returns a Component with an extra prop named i18n (Object { t: Function, lang: string }).

Example:

import React from 'react'
import withTranslation from 'next-translate/withTranslation'

class Description extends React.Component {
  render() {
    const { t, lang } = this.props.i18n
    const description = t('common:description')

    return <p>{description}</p>
  }
}

export default withTranslation(NoFunctionalComponent)

Trans Component

πŸ“¦Size: ~1.4kb

Sometimes we need to do some translations with HTML inside the text (bolds, links, etc), the Trans component is exactly what you need for this. We recommend to use this component only in this case, for other cases we highly recommend the usage of useTranslation hook instead.

Example:

// The defined dictionary enter is like:
// "example": "<0>The number is <1>{{count}}</1></0>",
<Trans
  i18nKey="common:example"
  components={[<Component />, <b className="red" />]}
  values={{ count: 42 }}
/>
  • Props:
    • i18nKey - string - key of i18n entry (namespace:key)
    • components - Array - Each index corresponds to the defined tag <0>/<1>.
    • values - Object - query params

appWithI18n

πŸ“¦Size: ~3.7kb

Using the "build step" you'll never need this.

This HOC is the way to wrap all your app under translations in the case that you are using a custom server as an alternative to the "build step", adding logic to the getInitialProps to download the necessary namespaces in order to use it in your pages.

Example:

_app.js

import appWithI18n from 'next-translate/appWithI18n'
import i18nConfig from '../i18n'

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default appWithI18n(MyApp, i18nConfig)

See more details about the config you can use.

DynamicNamespaces

πŸ“¦Size: ~1.5kb

The DynamicNamespaces component is useful to load dynamic namespaces, for example, in modals.

Example:

import React from 'react'
import Trans from 'next-translate/Trans'
import DynamicNamespaces from 'next-translate/DynamicNamespaces'

export default function ExampleWithDynamicNamespace() {
  return (
    <DynamicNamespaces
      dynamic={(lang, ns) =>
        import(`../../locales/${lang}/${ns}.json`).then((m) => m.default)
      }
      namespaces={['dynamic']}
      fallback="Loading..."
    >
      {/* ALSO IS POSSIBLE TO USE NAMESPACES FROM THE PAGE */}
      <h1>
        <Trans i18nKey="common:title" />
      </h1>

      {/* USING DYNAMIC NAMESPACE */}
      <Trans i18nKey="dynamic:example-of-dynamic-translation" />
    </DynamicNamespaces>
  )
}

Remember that ['dynamic'] namespace should not be listed on pages configuration:

 pages: {
    '/my-page': ['common'], // only common namespace
  }

I18nProvider

πŸ“¦Size: ~3kb

The I18nProvider is a context provider internally used by next-translate to provide the current lang and the page namespaces. SO MAYBE YOU'LL NEVER NEED THIS.

However, it's exposed to the API because it can be useful in some cases. For example, to use multi-language translations in a page.

The I18nProvider is accumulating the namespaces, so you can rename the new ones in order to keep the old ones.

import React from 'react'
import I18nProvider from 'next-translate/I18nProvider'
import useTranslation from 'next-translate/useTranslation'

// Import English common.json
import commonEN from '../../locales/en/common.json'

function PageContent() {
  const { t, lang } = useTranslation()

  console.log(lang) // -> current language

  return (
    <div>
      <p>{t('common:example') /* Current language */}</p>
      <p>{t('commonEN:example') /* Force English */}</p>
    </div>
  )
}

export default function Page() {
  const { lang } = useTranslation()

  return (
    <I18nProvider lang={lang} namespaces={{ commonEN }}>
      <PageContent />
    </I18nProvider>
  )
}

6. Plurals

You can define plurals this way:

{
  "plural-example": "This is singular because the value is {{count}}",
  "plural-example_0": "Is zero because the value is {{count}}",
  "plural-example_2": "Is two because the value is {{count}}",
  "plural-example_plural": "Is in plural because the value is {{count}}"
}

Example:

function PluralExample() {
  const [count, setCount] = useState(0)
  const { t } = useTranslation()

  useEffect(() => {
    const interval = setInterval(() => {
      setCount((v) => (v === 5 ? 0 : v + 1))
    }, 1000)

    return () => clearInterval(interval)
  }, [])

  return <p>{t('namespace:plural-example', { count })}</p>
}

Result:

plural

Note: Only works if the name of the variable is {{count}}.

7. Use HTML inside the translation

You can define HTML inside the translation this way:

{
  "example-with-html": "<0>This is an example <1>using HTML</1> inside the translation</0>"
}

Example:

import Trans from 'next-translate/Trans'
// ...
const Component = (props) => <p {...props} />
// ...
<Trans
  i18nKey="namespace:example-with-html"
  components={[<Component />, <b className="red" />]}
/>

Rendered result:

<p>This is an example <b class="red">using HTML</b> inside the translation</p>

Each index of components array corresponds with <index></index> of the definition.

In the components array, it's not necessary to pass the children of each element. Children will be calculated.

8. Nested translations

In the namespace, it's possible to define nested keys like this:

{
  "nested-example": {
    "very-nested": {
      "nested": "Nested example!"
    }
  }
}

In order to use it, you should use "." as id separator:

t`namespace:nested-example.very-nested.nested`

Also is possible to use as array:

{
  "array-example": [
    { "example": "Example {{count}}" },
    { "another-example": "Another example {{count}}" }
  ]
}

And get all the array translations with the option returnObjects:

t('namespace:array-example', { count: 1 }, { returnObjects: true })
/*
[
  { "example": "Example 1" },
  { "another-example": "Another example 1" }
]
*/

9. How to change the language

In order to change the current language you can use the Next.js navigation (Link and Router) passing the locale prop.

An example of a possible ChangeLanguage component:

import React from 'react'
import Link from 'next/link'
import useTranslation from 'next-translate/useTranslation'
import i18nConfig from '../i18n.json'

const { locales } = i18nConfig

function ChangeLanguage() {
  const { t, lang } = useTranslation()

  return locales.map((lng) => {
    if (lng === lang) return null

    // Or you can attach the current pathname at the end
    // to keep the same page
    return (
      <Link href="/" locale={lng} key={lng}>
        {t(`layout:language-name-${lng}`)}
      </Link>
    )
  })
}

10. How to use multi-language in a page

In some cases, when the page is in the current language, you may want to do some exceptions displaying some text in another language.

In this case, you can achieve this by using the I18nProvider.

Learn how to do it here.

11. Do I need this "build step"? Is there an alternative?

The "build step" exists only to simplify work with Automatic Static Optimization, so right now it is the recommended way. However, if you prefer not to do the "build step", there are two alternatives.

First alternative

If you don't need Automatic Static Optimization in your project, you can achieve the same by using a appWithI18n.

Pros and cons:

  • πŸ”΄ Automatic Static Optimization is not an option
  • 🟒 Easy to configure

Learn more: Docs Β· Example

Second alternative

You can achieve the same that the "build step" by adding some helper to load the namespaces en each page (similar than the "build step" does).

Pros and cons:

  • 🟒 Automatic Static Optimization
  • πŸ”΄ Hard to configure

12. Demos

Demo from Next.js

There is a demo of next-translate on the Next.js repo:

To use it:

npx create-next-app --example with-next-translate with-next-translate-app
# or
yarn create next-app --example with-next-translate with-next-translate-app

Basic demo: With the "build step"

This demo is in this repository:

  • git clone [email protected]:vinissimus/next-translate.git
  • cd next-translate
  • yarn && yarn example:with-build-step

Basic demo: Using the appWithI18n alternative

This demo is in this repository:

  • git clone [email protected]:vinissimus/next-translate.git
  • cd next-translate
  • yarn && yarn example:with-server

Basic demo: Without the "build step"

This demo is in this repository:

  • git clone [email protected]:vinissimus/next-translate.git
  • cd next-translate
  • yarn && yarn example:without-build-step

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Aral Roca Gomez

🚧 πŸ’»

Vincent Ducorps

πŸ’»

BjΓΆrn Rave

πŸ’»

Justin

πŸ’»

Pol

πŸš‡

AdemΓ­lson F. Tonato

πŸ’»

Faul

πŸ’»

bickmaev5

πŸ’»

Pierre Grimaud

πŸ“–

Roman Minchyn

πŸ“– πŸ’»

Egor

πŸ’»

Darren

πŸ’»

Giovanni Giordano

πŸ’»

Eugene

πŸ’»

Andrew Chung

πŸ’»

Thanh Minh

πŸ’»

crouton

πŸ’»

This project follows the all-contributors specification. Contributions of any kind welcome!

next-translate's People

Contributors

allcontributors[bot] avatar aralroca avatar bickmaev5 avatar bjoernrave avatar croutonn avatar dependabot[bot] avatar dhobbs avatar dnepro avatar faulik avatar ftonato avatar giovannigiordano avatar hibearpanda avatar justincy avatar lone-cloud avatar pgrimaud avatar psanlorenzo avatar rekomat avatar shunkakinoki avatar thanhlmm avatar ugened47 avatar vincentducorps avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.