Skip to content

Commit

Permalink
docs: Tutorials section improvements (#2075)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrii-bodnar authored Nov 4, 2024
1 parent 9853713 commit 965231a
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 566 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 }
]
}
135 changes: 135 additions & 0 deletions website/docs/guides/lazy-translations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
title: Lazy Translations
description: Lazy translations allow you to defer translation of a message until it is actually displayed
---

# Lazy Translations

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";
import { Trans } from "@lingui/react";

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

export default function ColorList() {
return (
<ul>
{favoriteColors.map((color) => (
<li>
<Trans id={color.id} />
</li>
))}
</ul>
);
}
```

:::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.
:::

### Picking a Message Based on a Variable

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.

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";
import { useLingui } from "@lingui/react";

const statusMessages = {
["STATUS_OPEN"]: msg`Open`,
["STATUS_CLOSED"]: msg`Closed`,
["STATUS_CANCELLED"]: msg`Cancelled`,
["STATUS_COMPLETED"]: msg`Completed`,
};

export default function StatusDisplay({ statusCode }) {
const { _ } = useLingui();
return <div>{_(statusMessages[statusCode])}</div>;
}
```

### 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>;
}
```
2 changes: 1 addition & 1 deletion website/docs/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Learn how to install Lingui in your project, whether you use JavaScript, React (
- Install [Lingui CLI](/docs/ref/cli.md) to manage your translations and catalogs.

:::tip
Don't miss the [Lingui ESLint Plugin](/docs/ref/eslint-plugin.md) which can help you find and prevent common l10n mistakes in your code.
Don't miss the [Lingui ESLint Plugin](/docs/ref/eslint-plugin.md) which can help you find and prevent common i18n mistakes in your code.
:::

## Choosing a Transpiler
Expand Down
51 changes: 50 additions & 1 deletion 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 @@ -764,6 +764,55 @@ function MyComponent() {
The `useLingui` React macro is available from **Lingui v5**.
:::

## Important Notes

### Using Macros

All Core Macros cannot be used at the module level:

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

// ❌ Bad! This won't work because the `t` macro is used at the module level.
// The `t` macro returns a string, and once this string is assigned, it won't react to locale changes.
const colors = [t`Red`, t`Orange`, t`Yellow`, t`Green`];

// ✅ Good! Every time the function is executed, the `t` macro will be re-executed as well,
// and the correctly translated color labels will be returned.
function getColors() {
return [t`Red`, t`Orange`, t`Yellow`, t`Green`];
}
```

:::tip
There is an [ESLint Plugin](/docs/ref/eslint-plugin.md) rule designed to check for this misuse: `t-call-in-function`.
:::

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

### Global `i18n` Instance

When you use the [`t`](#t) macro (or [`plural`](#plural), [`select`](#select), [`selectOrdinal`](#selectordinal)), it uses a global [`i18n`](/docs/ref/core.md#i18n) instance. While this generally works, there are situations, such as server-side rendering (SSR) applications, where it may not be the best solution.

For better control and flexibility, it's a good idea to avoid the global `i18n` instance and instead use a specific instance tailored to your needs:

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

export function showAlert(i18n) {
alert(i18n._(msg`...`));
}

function MyComponent() {
// Get i18n instance from React Context
const { i18n } = useLingui();

// Pass the instance from outside
showAlert(i18n);
}
```

## More Examples

### Examples of JS macros
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 @@ -135,7 +135,7 @@ The `useLingui` hook provides access to the Lingui context. It returns an object
| `_` | `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](/guides/lazy-translations#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
36 changes: 24 additions & 12 deletions website/docs/tutorials/javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,40 @@ description: Learn how to use Lingui's internationalization features in your van

# JavaScript Apps Internationalization

In this tutorial, we'll learn how to use Lingui's internationalization features that don't depend on React. We'll take a minimalist approach and cover the main features of the `@lingui/core` package.
This tutorial will walk you through using Lingui's internationalization (i18n) features in a vanilla JavaScript application. We'll cover the essentials of the `@lingui/core` package, which handles all translation and message catalog management.

:::tip Example
If you're looking for a working solution, check out the [Vanilla JS example](https://github.com/lingui/js-lingui/tree/main/examples/js). This example application shows a complete setup using Lingui and vanilla JavaScript.
:::

## Installing Lingui

1. Follow the [Installation and Setup](/docs/installation.mdx) page for initial setup.
2. Install the [`@lingui/core`](/docs/ref/core.md) package, which is responsible for translation and handling of message catalogs.
2. Install the [`@lingui/core`](/docs/ref/core.md) package, which is responsible for translation and message catalog handling.

## Setting up i18n

First we need to setup the i18n singleton, which is pretty simple:
First, we need to set up the i18n singleton, which is pretty simple:

```js
import { i18n } from "@lingui/core";

// messages.js is generated by the cli
import { messages } from "path-to-locale/en/messages.js";

i18n.load("en", messages);
i18n.activate("en");
```

The `messages.js` is generated by the Lingui CLI and contains compiled message catalogs.

:::tip
Alternatively, you can load catalogs dynamically using the [`@lingui/loader`](/docs/ref/loader.md) or [`@lingui/vite-plugin`](/docs/ref/vite-plugin.md) without the need to import compiled messages manually.
:::

## Localizing Your App

Once that is done, we can go ahead and use it! Wrap your text in [`t`](/docs/ref/macro.mdx#t) macro:
To localize your application, you need to wrap your localizable texts in [Macros](/docs/ref/macro.mdx). Lingui provides a set of Core Macros that transform tagged template literals and can be used in any JavaScript context.

Let's wrap our text in the [`t`](/docs/ref/macro.mdx#t) macro:

```js
import { t } from "@lingui/core/macro";
Expand All @@ -41,7 +51,7 @@ t`My name is ${name}`;
// becomes "Je m'appelle Fred"
```

Plurals and selections are possible using plural and select methods:
Plurals and selections are possible using [`plural`](/docs/ref/macro.mdx#plural) and [`select`](/docs/ref/macro.mdx#select) macros:

```js
import { plural } from "@lingui/core/macro";
Expand All @@ -55,10 +65,10 @@ plural(count, {
// becomes "42 livres"
```

It's also possible to nest message formats. Each message format method in i18n has a standalone companion, which only returns message without performing the translation:
It's also possible to nest message formats. Each message format method in `i18n` has a standalone companion, which only returns message without performing the translation:

```js
import { t, select, plural } from "@lingui/core/macro"
import { t, select, plural } from "@lingui/core/macro";

select(gender, {
offset: 1,
Expand All @@ -71,10 +81,12 @@ select(gender, {
}),
male: plural(value, {...}),
other: plural(value, {...}),
})
});
```

## Further Reading
## See Also

- [CLI Reference](/docs/ref/cli.md)
- [Message Extraction Guide](/docs/guides/message-extraction.md)
- [Pluralization Guide](/docs/guides/plurals.md)
- [Dynamic Loading of Message Catalogs](/docs/guides/dynamic-loading-catalogs.md)
- [`@lingui/cli` Reference](/docs/ref/cli.md)
Loading

0 comments on commit 965231a

Please sign in to comment.