-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Bringing frontend and README docs up to date (plus a few updates for typos, formatting, etc.) * Note that internationalization file was largely copy pasted from the Nava platform
- Loading branch information
1 parent
cff9c79
commit 41be850
Showing
3 changed files
with
175 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,74 @@ | ||
# Internationalization (i18n) | ||
|
||
- [I18next](https://www.i18next.com/) is used for internationalization. | ||
- Next.js's [internationalized routing](https://nextjs.org/docs/advanced-features/i18n-routing) feature is enabled. Toggling between languages is done by changing the URL's path prefix (e.g. `/about` ➡️ `/es/about`). | ||
- Configuration for the i18n routing and i18next libraries are located in [`next-i18next.config.js`](../../frontend/next-i18next.config.js). | ||
- [storybook-react-i18next](https://storybook.js.org/addons/storybook-react-i18next) adds a globe icon to Storybook's toolbar for toggling languages. | ||
- [next-intl](https://next-intl-docs.vercel.app) is used for internationalization. Toggling between languages is done by changing the URL's path prefix (e.g. `/about` ➡️ `/es-US/about`). | ||
- Configuration is located in [`i18n/config.ts`](../frontend/src/i18n/config.ts). For the most part, you shouldn't need to edit this file unless adding a new formatter or new language. | ||
|
||
## Managing translations | ||
|
||
- Translations are managed in the `public/locales` directory, where each language has its own directory (e.g. `en` and `es`). | ||
- [Namespaces](https://www.i18next.com/principles/namespaces) can be used to organize translations into smaller files. For large sites, it's common to create a namespace for each controller, page, or feature (whatever level makes most sense). | ||
- There are a number of built-in formatters based on [JS's `Intl` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) that can be used in locale strings, and custom formatters can be added as well. [See the i18next formatting docs for details](https://www.i18next.com/translation-function/formatting#built-in-formats). | ||
- Translations are managed as files in the [`i18n/messages`](../frontend/src/i18n/messages/) directory, where each language has its own directory (e.g. `en-US` and `es-US`). | ||
- How you organize translations is up to you, but here are some suggestions: | ||
- Group your messages. It's recommended to use component/page names as namespaces and embrace them as the primary unit of organization in your app. | ||
- By default, all messages are in a single file, but you can split them into multiple files if you prefer. Continue to export all messages from `i18n/messages/{locale}/index.ts` so that they can be imported from a single location, and so files that depend on the messages don't need to be updated. | ||
- There are a number of built-in formatters based on [JS's `Intl` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) that can be used in locale strings, and custom formatters can be added as well. [See the formatting docs for details](https://next-intl-docs.vercel.app/docs/usage/numbers). | ||
- If a string's translation is missing in another language, the default language (English) will be used as a fallback. | ||
|
||
### Type-safe translations | ||
|
||
The app is configured to report errors if you attempt to reference an i18n key path that doesn't exist in a locale file. | ||
|
||
[Learn more about using TypeScript with next-intl](https://next-intl-docs.vercel.app/docs/workflows/typescript). | ||
|
||
## Load translations | ||
|
||
1. `serverSideTranslations` must be called in [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching/get-static-props) or [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props) to load translations for a page. | ||
Locale messages should only ever be loaded on the server-side, to avoid bloating the client-side bundle. If a client component needs to access translations, only the messages required by that component should be passed into it. | ||
|
||
```tsx | ||
import type { GetServerSideProps } from "next"; | ||
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; | ||
[See the Internationalization of Server & Client Components docs](https://next-intl-docs.vercel.app/docs/environments/server-client-components) for more details. | ||
|
||
export const getServerSideProps: GetServerSideProps = async ({ locale }) => { | ||
// serverSideTranslations takes an optional second argument to limit | ||
// which namespaces are sent to the client | ||
const translations = await serverSideTranslations(locale ?? "en"); | ||
return { props: { ...translations } }; | ||
}; | ||
``` | ||
## Add a new language | ||
|
||
Note that `serverSideTranslations` needs imported in the same file as the `getServerSideProps` / `getStaticProps` function, so that Next.js properly excludes it from the client-side bundle, where Node.js APIs (e.g. `fs`) aren't available. | ||
1. Add a language folder, using the same BCP47 language tag: `mkdir -p src/i18n/messages/<lang>` | ||
1. Add a language file: `touch src/i18n/messages/<lang>/index.ts` and add the translated content. The JSON structure should be the same across languages. However, non-default languages can omit keys, in which case the default language will be used as a fallback. | ||
1. Update [`i18n/config.ts`](../../app/src/i18n/config.ts) to include the new language in the `locales` array. | ||
|
||
1. Then use the `useTranslation` hook's `t()` method, or the `Trans` component to render localized strings. | ||
## Structuring your messages | ||
|
||
```tsx | ||
import { Trans, useTranslation } from "next-i18next"; | ||
In terms of best practices, [it is recommended](https://next-intl-docs.vercel.app/docs/usage/messages#structuring-messages) to structure your messages such that they correspond to the component that will be using them. You can nest these definitions arbitrarily deep if you have a particularly complex need. | ||
|
||
const Page = () => { | ||
// Optionally pass in the namespace of the translation file (e.g. common) to use | ||
const { t } = useTranslation("common"); | ||
return ( | ||
<> | ||
<h1>{t("About.title")}</h1> | ||
<Trans i18nKey="About.summary" /> | ||
</> | ||
); | ||
}; | ||
``` | ||
It is always preferable to structure messages per their usage rather than due to some side effect of a technological implementation. The idea is to group them semantically but also preserve maximum flexibility for a translator. For instance, splitting up a paragraph in order to separate out a link might lead to awkward translation, so it is best to keep it as a single message. The info below shows techniques for common needs that prevent unnecessary splits of content. | ||
|
||
Refer to the [i18next](https://www.i18next.com/) and [react-i18next](https://react.i18next.com/) documentation for more usage docs. | ||
### Variables | ||
|
||
## Add a new language | ||
Messages do not need to be split in order to incorporate dynamic data. Instead, these can be inserted via the [interpolation functionality](https://next-intl-docs.vercel.app/docs/usage/messages#interpolation-of-dynamic-values): | ||
|
||
```json | ||
"message": "Hello {name}!" | ||
``` | ||
|
||
```tsx | ||
t("message", { name: "Jane" }); // "Hello Jane!" | ||
``` | ||
|
||
### Rich text messages | ||
|
||
If your app needs a particular chunk of content to contain something other than plain text (such as links, formatting, or a custom component), you can utilize the "rich text" functionality ([see docs](https://next-intl-docs.vercel.app/docs/usage/messages#rich-text)). This allows one to embed arbitrary custom tags into the translation content strings and specify how each of those should be handled. | ||
|
||
Example from their docs: | ||
|
||
```json | ||
{ | ||
"message": "Please refer to <guidelines>the guidelines</guidelines>." | ||
} | ||
``` | ||
|
||
```tsx | ||
// Returns `<>Please refer to <a href="/guidelines">the guidelines</a>.</>` | ||
t.rich("message", { | ||
guidelines: (chunks) => <a href="/guidelines">{chunks}</a>, | ||
}); | ||
``` | ||
|
||
If you have something that you are going to use repeatedly throughout your app, you can specify it in the [`defaultTranslationValues` config](https://next-intl-docs.vercel.app/docs/usage/configuration#default-translation-values). | ||
|
||
### Other needs | ||
|
||
1. Edit `next-i18next.config.js` and add the language to `locales`, using the BCP47 language tag (e.g. `en` or `es`). | ||
1. Add a language folder, using the same BCP47 language tag: `mkdir -p public/locales/<lang>` | ||
1. Add a language file: `touch public/locales/<lang>/common.json` and add the translated content. The JSON structure should be the same across languages. However, non-default languages can omit keys, in which case the default language will be used as a fallback. | ||
1. Optionally, add a label for the language to the `locales` object in [`.storybook/preview.js`](../../frontend/.storybook/preview.js) | ||
For examples of other functionality such as pluralization, arrays of content, etc, please [see the docs](https://next-intl-docs.vercel.app/docs/usage/messages). |
Oops, something went wrong.