Skip to content

Commit

Permalink
lazy translations
Browse files Browse the repository at this point in the history
  • Loading branch information
andrii-bodnar committed Oct 30, 2024
1 parent 68574ad commit 47c3a89
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 121 deletions.
3 changes: 2 additions & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
{ "source": "/guides/excluding-build-files", "destination": "/ref/cli#compile", "permanent": true},
{ "source": "/tutorials/explicit-vs-generated-ids", "destination": "/guides/explicit-vs-generated-ids", "permanent": true},
{ "source": "/tutorials/setup-vite", "destination": "/installation#vite", "permanent": true },
{ "source": "/tutorials/setup-react", "destination": "/installation", "permanent": true }
{ "source": "/tutorials/setup-react", "destination": "/installation", "permanent": true },
{ "source": "/tutorials/react-patterns", "destination": "/tutorials/react", "permanent": true }
]
}
46 changes: 26 additions & 20 deletions website/docs/guides/lazy-translations.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
---
title: Lazy Translations
description: Lazy translations allow you to defer translation of a message until it is actually displayed.
description: Lazy translations allow you to defer translation of a message until it is actually displayed
---

# Lazy Translations

You don't need to declare messages at the same code location where they are displayed. Tag a string with the [`msg`](/docs/ref/macro.mdx#definemessage) macro, and you've created a "message descriptor", which can then be passed around as a variable, and can be displayed as a translated string by passing its `id` to [`Trans`](/docs/ref/macro.mdx#trans) as its `id` prop:
Lazy translation allows you to defer translation of a message until it's rendered, giving you flexibility in how and where you define messages in your code. With lazy translation, you can tag a string with the [`msg`](/docs/ref/macro.mdx#definemessage) macro to create a _message descriptor_ that can be saved, passed around as a variable, and rendered later.

## Usage Example

To render the message descriptor as a string-only translation, pass it to the [`i18n._()`](/docs/ref/core.md#i18n._) method:

```jsx
import { msg } from "@lingui/core/macro";
import { i18n } from "@lingui/core";

const favoriteColors = [msg`Red`, msg`Orange`, msg`Yellow`, msg`Green`];

export function getTranslatedColorNames() {
return favoriteColors.map((color) => i18n._(color));
}
```

## Usage in React

To render the message descriptor in a React component, pass its `id` to the [`Trans`](/docs/ref/react.md#trans) component as a value of the `id` prop:

```jsx
import { msg } from "@lingui/core/macro";
Expand All @@ -26,28 +45,15 @@ export default function ColorList() {
}
```

:::note
Note that we import `<Trans>` component from `@lingui/react`, because we want to use the runtime `Trans` component here, not the (compile-time) macro.
:::info Important
Please note that we import the `<Trans>` component from `@lingui/react` to use the runtime version, as the message is already defined and we don't need the compile-time macro here.
:::

To render the message descriptor as a string-only translation, pass it to the [`i18n._()`](/docs/ref/core.md#i18n._) method:

```jsx
import { i18n } from "@lingui/core";
import { msg } from "@lingui/core/macro";

const favoriteColors = [msg`Red`, msg`Orange`, msg`Yellow`, msg`Green`];

export function getTranslatedColorNames() {
return favoriteColors.map((color) => i18n._(color));
}
```

### Picking a message based on a variable
### Picking a Message Based on a Variable

Sometimes you need to pick between different messages to display, depending on the value of a variable. For example, imagine you have a numeric "status" code that comes from an API, and you need to display a message representing the current status.
Sometimes you need to choose between different messages to display depending on the value of a variable. For example, imagine you have a numeric "status" code that comes from an API, and you need to display a message that represents the current status.

A simple way to do this is to create an object that maps the possible values of "status" to message descriptors (tagged with the [`msg`](/docs/ref/macro.mdx#definemessage) macro), and render them as needed with deferred translation:
An easy way to do this is to create an object that maps the possible values of "status" to message descriptors (tagged with the [`msg`](/docs/ref/macro.mdx#definemessage) macro) and render them as needed with deferred translation:

```jsx
import { msg } from "@lingui/core/macro";
Expand Down
6 changes: 2 additions & 4 deletions website/docs/ref/macro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ const message = t({

### `defineMessage` / `msg` {#definemessage}

The `defineMessage` (alias: `msg`) macro allows to define a message for later use. It has the same signature as `t` and returns a `MessageDescriptor` that you can pass to `i18n._` to get a translated string at any time later. This is useful for [lazy translations](/tutorials/react-patterns#lazy-translations).
The `defineMessage` (alias: `msg`) macro allows to define a message for later use. It has the same signature as `t` and returns a `MessageDescriptor` that you can pass to `i18n._` to get a translated string at any time later. This is useful for [Lazy Translations](/guides/lazy-translations).

In other words, `t` returns a translated string at the time when it's called, while `msg` returns a `MessageDescriptor` that can produce translated strings later:

Expand Down Expand Up @@ -815,9 +815,7 @@ function getColors() {
There is an [ESLint Plugin](/docs/ref/eslint-plugin.md) rule designed to check for this misuse: `t-call-in-function`.
:::

[//]: # (TODO: link to the article)

A better option would be to use the Lazy Translations pattern.
A better option would be to use the [Lazy Translations](/guides/lazy-translations) pattern.

### Global `i18n` Instance

Expand Down
2 changes: 1 addition & 1 deletion website/docs/ref/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ This hook allows access to the Lingui context. It returns an object with the fol
| `_` | `I18n[_]` | reference to the [`i18n._`](/ref/core#i18n._) function, explained below |
| `defaultComponent` | `React.ComponentType` | the same `defaultComponent` you passed to `I18nProvider`, if provided |

Components that use `useLingui` hook will re-render when locale and / or catalogs change. However, the reference to the `i18n` object is stable and doesn't change between re-renders. This can lead to unexpected behavior with memoization (see [memoization pitfall](/tutorials/react-patterns#memoization-pitfall)).
Components that use `useLingui` hook will re-render when locale and / or catalogs change. However, the reference to the `i18n` object is stable and doesn't change between re-renders. This can lead to unexpected behavior with memoization (see [memoization pitfall](/tutorials/react#memoization-pitfall)).

To alleviate the issue, `useLingui` provides the `_` function, which is the same as [`i18n._`](/ref/core#i18n._) but _its reference changes_ with each update of the Lingui context. Thanks to that, you can safely use this `_` function as a hook dependency.

Expand Down
2 changes: 1 addition & 1 deletion website/docs/releases/migration-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ You will need to make some changes as this is a misuse of the library that actua

Due to the changes caused by hash-based message ID feature described earlier, this approach will no longer work.

Instead, please use [recommended](/docs/tutorials/react-patterns.md#lazy-translations) pattern for such translations:
Instead, please use [recommended](/guides/lazy-translations) pattern for such translations:

```tsx
import { msg } from "@lingui/macro";
Expand Down
1 change: 0 additions & 1 deletion website/docs/tutorials/react-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ The important point here is that the sentence isn't broken into pieces but remai
## See Also

- [Message Extraction Guide](/docs/guides/message-extraction.md)
- [Common i18n Patterns in React](/docs/tutorials/react-patterns.md)
- [`@lingui/react` Reference](/docs/ref/react.md)
- [`@lingui/cli` Reference](/docs/ref/cli.md)
- [Localizing React Native apps talk from React Native EU 2022](https://www.youtube.com/live/uLicTDG5hSs?feature=share&t=7512)
Expand Down
85 changes: 0 additions & 85 deletions website/docs/tutorials/react-patterns.md

This file was deleted.

3 changes: 1 addition & 2 deletions website/docs/tutorials/react-rsc.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,8 @@ export default function SomePage() {
}
```

Read more about [lazy translation](/docs/tutorials/react-patterns.md#translations-outside-react-components) to see how to handle translation defined on the module level.
Read more about [Lazy Translation](/docs/guides/lazy-translations.md) to see how to handle translation defined on the module level.

## See Also

- [Common i18n Patterns in React](/docs/tutorials/react-patterns.md)
- [`@lingui/react` Reference](/docs/ref/react.md)
64 changes: 63 additions & 1 deletion website/docs/tutorials/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ export default function ImageWithCaption() {
return <img src="..." alt={t`Image caption`} />;
}
```

:::

## Dates and Numbers
Expand Down Expand Up @@ -629,6 +630,68 @@ export default function Inbox() {

This will format the date using the conventional format for the active language.

## Memoization Pitfall

In the following contrived example, we document how a welcome message will or will not be updated when locale changes. The documented behavior may not be intuitive at first, but it is expected, because of the way the `useMemo` dependencies work.

To avoid bugs with stale translations, use the `_` function returned from the [`useLingui`](/docs/ref/react.md#uselingui) hook: it is safe to use with memoization because its reference changes whenever the Lingui context updates.

:::tip
You can also use the `t` function from the [`useLingui`](/docs/ref/macro.mdx#uselingui) macro hook, which behaves the same way as `_` from the runtime [`useLingui`](/docs/ref/react.md#uselingui) counterpart.
:::

Keep in mind that `useMemo` is primarily a performance optimization tool in React. Because of this, there might be no need to memoize your translations. Additionally, this issue is not present when using the `Trans` component, which we recommend using whenever possible.

```jsx
import { msg } from "@lingui/core/macro";
import { i18n } from "@lingui/core";
import { useLingui } from "@lingui/react";

const welcomeMessage = msg`Welcome!`;

// ❌ Bad! This code won't work
export function Welcome() {
const buggyWelcome = useMemo(() => {
return i18n._(welcomeMessage);
}, []);

return <div>{buggyWelcome}</div>;
}

// ❌ Bad! This code won't work either because the reference to i18n does not change
export function Welcome() {
const { i18n } = useLingui();

const buggyWelcome = useMemo(() => {
return i18n._(welcomeMessage);
}, [i18n]);

return <div>{buggyWelcome}</div>;
}

// ✅ Good! `useMemo` has i18n context in the dependency
export function Welcome() {
const linguiCtx = useLingui();

const welcome = useMemo(() => {
return linguiCtx.i18n._(welcomeMessage);
}, [linguiCtx]);

return <div>{welcome}</div>;
}

// 🤩 Better! `useMemo` consumes the `_` function from the Lingui context
export function Welcome() {
const { _ } = useLingui();

const welcome = useMemo(() => {
return _(welcomeMessage);
}, [_]);

return <div>{welcome}</div>;
}
```

## Review

After all modifications, the final i18n-ready component looks like this:
Expand Down Expand Up @@ -677,4 +740,3 @@ export default function Inbox() {
```

That's it for this tutorial! For more details, see the reference documentation or check out additional tutorials. Happy Internationalizing!

5 changes: 0 additions & 5 deletions website/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ const sidebar = [
label: "React Native",
id: "tutorials/react-native",
},
{
type: "doc",
label: "React - Common Patterns",
id: "tutorials/react-patterns",
},
{
type: "doc",
label: "JavaScript",
Expand Down

0 comments on commit 47c3a89

Please sign in to comment.