From db4247c214a6aa1f836a4b97285fd77386b8dcc8 Mon Sep 17 00:00:00 2001 From: Andrii Bodnar Date: Tue, 22 Oct 2024 17:10:52 +0300 Subject: [PATCH 1/4] docs: Guides section improvements --- .../docs/guides/dynamic-loading-catalogs.md | 21 ++- .../docs/guides/explicit-vs-generated-ids.md | 146 +++++++++--------- website/docs/guides/message-extraction.md | 117 ++++++++------ website/docs/guides/message-format.md | 9 +- website/docs/guides/plurals.md | 73 +++------ website/docs/guides/pseudolocalization.md | 42 ++--- website/docs/guides/testing.md | 6 +- website/docs/releases/migration-4.md | 2 +- 8 files changed, 214 insertions(+), 202 deletions(-) diff --git a/website/docs/guides/dynamic-loading-catalogs.md b/website/docs/guides/dynamic-loading-catalogs.md index 623a5ff13..0fc61fb98 100644 --- a/website/docs/guides/dynamic-loading-catalogs.md +++ b/website/docs/guides/dynamic-loading-catalogs.md @@ -1,12 +1,19 @@ +--- +title: Dynamic Loading of Message Catalogs +description: Learn how to set up dynamic loading of message catalogs in Lingui to reduce bundle size and improve performance +--- + # Dynamic Loading of Message Catalogs -[`I18nProvider`](/docs/ref/react.md#i18nprovider) doesn't assume anything about your app and it's the developer responsibility to load messages based on active language. +Internationalization in modern applications requires an efficient way to manage and load localized messages without overwhelming the initial bundle size. With Lingui's flexible approach, the developer is responsible for dynamically loading message catalogs based on the active language. + +The [`I18nProvider`](/docs/ref/react.md#i18nprovider) component doesn't make assumptions about your app's structure, giving you the freedom to load only the necessary messages for the currently selected language. -Here's an example of a basic setup with a dynamic load of catalogs. +This guide shows how to set up dynamic loading of message catalogs, ensuring only the needed catalogs are loaded, which reduces bundle size and improves performance. ## Final i18n Loader Helper -Here's the full source of `i18n.ts` logic: +The following code defines the complete logic for dynamically loading and activating message catalogs based on the selected locale. It ensures that only the required catalog is loaded at runtime, optimizing performance: ```tsx title="i18n.ts" import { i18n } from "@lingui/core"; @@ -28,7 +35,9 @@ export async function dynamicActivate(locale: string) { } ``` -**How should I use the dynamicActivate in our application?** +### Usage in Your Application + +To use the `dynamicActivate` function in your application, you must call it on application startup. The following example shows how to use it in a React application: ```jsx import React, { useEffect } from "react"; @@ -62,10 +71,10 @@ i18n-1.f0cf2e3d.chunk.js main.ab4626ef.js ``` -When page is loaded initially, only main bundle and bundle for the first language are loaded: +When the page is first loaded, only the main bundle and the bundle for the first language are loaded: ![Requests during the first render](/img/docs/dynamic-loading-catalogs-1.png) -After changing language in UI, the second language bundle is loaded: +After changing the language in the UI, the second language bundle is loaded: ![Requests during the second render](/img/docs/dynamic-loading-catalogs-2.png) diff --git a/website/docs/guides/explicit-vs-generated-ids.md b/website/docs/guides/explicit-vs-generated-ids.md index 7aed61591..5ed395d83 100644 --- a/website/docs/guides/explicit-vs-generated-ids.md +++ b/website/docs/guides/explicit-vs-generated-ids.md @@ -7,13 +7,15 @@ description: Learn about the differences between explicit and generated IDs and When internationalizing your application, you may need to decide whether to use explicit or generated IDs for your messages. -In this guide, we will explore the fundamental concepts of explicit and generated IDs, and then delve into a comprehensive comparison, highlighting the benefits and drawbacks of each approach. +In this guide, we will explore the basic concepts of explicit and generated IDs, and then delve into a comprehensive comparison, highlighting the advantages and disadvantages of each approach. ## What are Explicit IDs and Generated IDs? ### Explicit ID -An explicit ID, often referred to as a user-defined or custom ID, is a manually assigned identifier associated with a specific message. These IDs are typically chosen by developers and are explicitly specified within your code. The typical explicit id may look like `index.header.title` or `modal.buttons.cancel`. +An explicit ID, often referred to as a user-defined or custom ID, is a manually assigned identifier associated with a specific message. These IDs are typically chosen by developers and are explicitly specified within your code. + +The typical explicit id may look like `index.header.title` or `modal.buttons.cancel`. Lingui example: @@ -29,7 +31,7 @@ Lingui example: ### Generated IDs -On the other hand, generated IDs are automatically created by the internalization library. In Lingui, such IDs are created based on the source message and [context](#context). +On the other hand, generated IDs are created automatically by the internalization library. In Lingui, such IDs are created based on the source message and [context](#context). Lingui example: @@ -45,23 +47,23 @@ Lingui example: ### Benefits of Generated IDs -1. **Avoiding the "Naming Things" problem:** You don't need to come up with a name for each single phrase in the app. Lingui generates the IDs (in the form of short hashes) from the messages. -2. **Better Developer Experience:** Developers can focus on coding without needing to manually assign IDs, leading to a more streamlined development process. Searching for a user-facing string will lead to the place in code where it's used, as opposed to taking you to a (typically JSON) file full of translations. -3. **Avoiding Duplicates:** Duplicate messages are merged together automatically. Your translators will not have to translate the same phrases again and again. This could lead to cost savings, especially if translators charge by word count. -4. **Smaller bundle:** Lingui generates short IDs such as `uxV9Xq` which are typically shorter than manually crafted IDs like `index.header.title`. This results in a smaller bundle size, optimizing your application's performance. -5. **Preventing ID collisions:** As your application scales, explicit IDs can potentially lead to conflicts. Lingui's generated IDs ensure you steer clear of such collisions. +- **Avoiding the "naming things" problem:** You don't need to come up with a name for each single phrase in the app. Lingui generates the IDs (in the form of short hashes) from the messages. +- **Better developer experience:** Developers can focus on coding without needing to manually assign IDs, leading to a more streamlined development process. Searching for a user-facing string will lead to the place in code where it's used, as opposed to taking you to a (typically JSON) file full of translations. +- **Avoiding duplicates:** Duplicate messages are merged together automatically. Your translators will not have to translate the same phrases again and again. This could lead to cost savings, especially if translators charge by word count. +- **Smaller bundle:** Lingui generates short IDs such as `uxV9Xq` which are typically shorter than manually crafted IDs like `index.header.title`. This results in a smaller bundle size, optimizing your application's performance. +- **Preventing ID collisions:** As your application scales, explicit IDs can potentially lead to conflicts. Lingui's generated IDs ensure you steer clear of such collisions. ### Benefits of Explicit IDs -1. **Control:** Developers have full control over the naming and assignment of explicit IDs. This control allows for precise targeting and easy maintenance of internationalization keys. -2. **Loose Coupling:** Explicit IDs are loosely coupled with the messages (as opposed to when they are generated from the messages). This means that if the message changes, the ID remains the same. When your team uses a TMS (Translation Management System), this makes it easier even for non-technical people to update the strings. -3. **Readability:** Explicit IDs often have meaningful names, making it easier for developers, translators, and content creators to understand their purpose within the codebase. -4. **Predictability:** Since explicit IDs are manually assigned, they remain stable across different versions of your application, reducing the likelihood of breaking changes during updates. +- **Control:** Developers have full control over the naming and assignment of explicit IDs. This control allows for precise targeting and easy maintenance of internationalization keys. +- **Loose coupling:** Explicit IDs are loosely coupled with the messages (as opposed to when they are generated from the messages). This means that if the message changes, the ID remains the same. When your team uses a TMS (Translation Management System), this makes it easier even for non-technical people to update the strings. +- **Readability:** Explicit IDs often have meaningful names, making it easier for developers, translators, and content creators to understand their purpose within the codebase. +- **Predictability:** Since explicit IDs are manually assigned, they remain stable across different versions of your application, reducing the likelihood of breaking changes during updates. In conclusion, the choice between these two strategies depends on your project requirements and priorities. However, it's important to note that Lingui provides the full range of benefits, especially with generated IDs. :::note -You don't need to worry about the readability of IDs because you would barely see them. When extracted into a PO file, translators would see source string and its corresponding translation, while the IDs remain behind the scenes. +You don't have to worry about the readability of the IDs because you would hardly see them. When extracted into a PO file, translators would see the source string and its corresponding translation, while the IDs remain behind the scenes: ```gettext #: src/App.tsx:1 @@ -71,55 +73,7 @@ msgstr "LinguiJS przyklad" ::: -## Using ID generated from a message - -### With [`Trans`](/docs/ref/macro.mdx#trans) macro - -```jsx -import { Trans } from "@lingui/react/macro"; - -function render() { - return ( - <> -

- LinguiJS example -

-

- - Hello {name}. - -

- - ); -} -``` - -In the example code above, the content of [`Trans`](/docs/ref/macro.mdx#trans) is transformed into a message in MessageFormat syntax. By default, this message is used for generating the ID. Considering the example above, the catalog would contain these entries: - -```js -const catalog = [ - { - id: "uxV9Xq", - message: "LinguiJS example", - }, - { - id: "9/omjw", - message: "Hello <0>{name}.", - }, -]; -``` - -### With non-JSX macro - -In the following example, the message `Hello World` will be extracted and used to create an ID. - -```jsx -import { msg } from "@lingui/core/macro"; - -msg`Hello World`; -``` - -### Context {#context} +## Context By default, when using generated IDs, the same text elements are extracted with the same ID, and then translated once. However, this might not always be desirable since the same text can have different meanings and translations. For example, consider the word "right" and its two possible meanings: @@ -130,7 +84,7 @@ To distinguish these two cases, you can add `context` to messages. The same text Regardless of whether you use generated IDs or not, adding context makes the translation process less challenging and helps translators interpret the source accurately. You, in return, get translations of better quality faster and decrease the number of context-related issues you would need to solve. -#### Providing context for a message +Examples: ```jsx import { Trans } from "@lingui/react/macro"; @@ -144,7 +98,7 @@ import { Trans } from "@lingui/react"; ; ``` -or with non-jsx macro +or with non-JSX macro: ```jsx import { msg } from "@lingui/core/macro"; @@ -160,6 +114,7 @@ const ex2 = msg({ }); // ↓ ↓ ↓ ↓ ↓ ↓ + const ex1 = { id: "d1wX4r", message: `right`, @@ -170,11 +125,59 @@ const ex2 = { }; ``` -## Using custom ID +## Using Generated IDs -### With [`Trans`](/docs/ref/macro.mdx#trans) +### With JSX Macro -If you're using custom IDs in your project, add `id` prop to i18n components: +```jsx +import { Trans } from "@lingui/react/macro"; + +function render() { + return ( + <> +

+ LinguiJS example +

+

+ + Hello {name}. + +

+ + ); +} +``` + +In the example code above, the content of [`Trans`](/docs/ref/macro.mdx#trans) is transformed into a message in MessageFormat syntax. By default, this message is used for generating the ID. Considering the example above, the catalog would contain these entries: + +```js +const catalog = [ + { + id: "uxV9Xq", + message: "LinguiJS example", + }, + { + id: "9/omjw", + message: "Hello <0>{name}.", + }, +]; +``` + +### With Core Macro + +In the following example, the message `Hello World` will be extracted and used to create an ID: + +```jsx +import { msg } from "@lingui/core/macro"; + +msg`Hello World`; +``` + +## Using Custom IDs + +### With JSX Macro + +To use custom IDs in JSX macros, pass the ID as a prop to the component: ```jsx import { Trans } from "@lingui/react/macro"; @@ -197,9 +200,9 @@ function render() { The messages with IDs `msg.header` and `msg.hello` will be extracted with their default values as `LinguiJS example` and `Hello <0>{name}.` respectively. -### With non-JSX macro +### With Core Macro -If you're using custom IDs in your project, call the [`msg`](/docs/ref/macro.mdx#definemessage) function with a message descriptor object, passing the ID using the `id` property: +To use custom IDs in non-JSX macros, call the [`msg`](/docs/ref/macro.mdx#definemessage) function with a message descriptor object, passing the ID using the `id` property: ```jsx import { msg } from "@lingui/core/macro"; @@ -222,3 +225,8 @@ msg({ }), }); ``` + +## See Also + +- [Message Extraction](/docs/guides/message-extraction.md) +- [Macros Reference](/docs/ref/macro.mdx) diff --git a/website/docs/guides/message-extraction.md b/website/docs/guides/message-extraction.md index a4e87e694..3a056e13e 100644 --- a/website/docs/guides/message-extraction.md +++ b/website/docs/guides/message-extraction.md @@ -5,41 +5,54 @@ description: Learn about message extraction in i18n and how to use Lingui to ext # Message Extraction -Message extraction is an essential step in the internationalization process. It involves analyzing your code and extracting all messages defined in it so that your message catalogs are always up-to-date with the source code. +Message extraction is a key part of the internationalization process. It involves scanning your codebase to identify and extract all the defined messages, ensuring that your message catalogs stay synchronized with the source code. -To extract messages (as marked with ``, `t` or other macros) from your application, use the `lingui extract` cli command. +In practice, developers define messages directly in the source code, and the extraction tool automatically collects these messages and stores them in a message catalog for translation. -## Supported patterns +Read more about the [`lingui extract`](/docs/ref/cli.md) command. -The extractor operates on a static level and doesn't execute your code. As a result, complex patterns and dynamic code are not supported. +## Supported Patterns -### Macro usages +The extractor operates at a static level, meaning that it analyzes the source code without executing it. As a result, it doesn't support complex patterns or dynamic code, and only simple, statically defined messages are collected. -Extractor supports all macro usages, such as the following examples: +### Macros Usage + +> Macros are JavaScript functions that run at build time. The value returned by a macro is inlined into the bundle instead of the original function call. + +The Lingui Macro provides powerful macros to transform JavaScript objects and JSX elements into [ICU MessageFormat](/docs/guides/message-format.md) messages at compile time. + +Extractor supports all macro usage, such as the following examples: ```tsx +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; + t`Message`; t({ - id: "ID Some", - message: "Message with id some", + id: "custom.id", + message: "Message with custom ID", }); const jsx = Hi, my name is {name}; ``` -For more usage examples, refer to the [macro documentation](/docs/ref/macro.mdx). +The extractor matches the `t` and `Trans` macro calls and extracts the messages from them. + +For more usage examples, see to the [Macros](/docs/ref/macro.mdx) reference. -### Non-Macro usages +### Non-Macros Usage -Note that the non-macro usage is not common. We recommend you use macros. +Non-macro use is also supported, but it's not common. It's recommended to use macros. The extractor matches `i18n._` or `i18n.t` function calls. It also matches when these functions are called from other member expressions, such as `ctx.i18n.t()`. -:::note -Extractor matches calls only by name. It doesn't check whether they were really imported from Lingui packages. +:::caution +The extractor only matches calls by name. It doesn't check if they are really imported from Lingui packages. ::: +For example: + ```ts i18n._("message.id"); i18n._({ id: "message.id" }); @@ -52,18 +65,18 @@ ctx.request.i18n.t("message.id"); // and so on ``` -You can ignore a specific call expression by adding a `lingui-extract-ignore` comment. +To ignore a specific call expression during extraction, you can add a `lingui-extract-ignore` comment: ```ts /* lingui-extract-ignore */ ctx.i18n._("Message"); ``` -This message would not be extracted. +Messages marked with this comment will be excluded from extraction. -### Explicitly marking messages +### Explicitly Marking Messages -Apart from call expressions, which are the most commonly used method, the extractor tool also supports simple string literals and message descriptors with explicit annotations. +In addition to call expressions, which are the most commonly used method, the extractor tool also supports simple string literals and message descriptors with explicit annotations. To do this, simply prefix your expression with the `/*i18n*/` comment, like so: @@ -74,58 +87,54 @@ const stringLiteral = /*i18n*/ "Message"; ## Unsupported Patterns -The extractor is limited to extracting messages from code that is written in a certain way. It cannot extract messages from variables or function calls. It also cannot follow program structure and get the value of a variable defined elsewhere. +The extractor is limited to extracting messages from code that is written a certain way. It cannot extract messages from variables or function calls. It also cannot follow the program structure and get the value of a variable defined elsewhere. -This means that in order for a message to be extracted, it must be defined directly in the function call. - -For example, the following code cannot be extracted: +This means that in order for a message to be extracted, it must be defined directly in the function call: ```ts +// ❌ This message will not be extracted const message = "Message"; i18n._(message); -``` -Instead, you should define the message directly in the function arguments: - -```ts +// ✅ This message will be extracted i18n._("Message"); ``` -## Defining sources for analyzing +## Defining Sources for Analyzing -The lingui extract command can discover source files in two ways: by using a glob pattern or by crawling the dependency tree. +The extractor can locate source files in two ways: by specifying a glob pattern or by crawling the dependency tree. ### Glob Pattern -By default, `lingui extract` uses a glob pattern to search for source files that contain messages. +By default, `lingui extract` uses a glob pattern to search for source files containing messages. -The pattern is defined in the `catalogs` property in the `lingui.config.js` file, which is located in the root directory of your project. +The pattern is defined in the [`catalogs`](/docs//ref/conf.md#catalogs) property of the Lingui configuration file in your project's root directory. ![Scheme of discovering by glob pattern](/img/docs/extractor-glob-scheme.jpg#gh-light-mode-only) ![Scheme of discovering by glob pattern](/img/docs/extractor-glob-scheme-dark.jpg#gh-dark-mode-only) -### Dependency tree crawling (experimental) +### Dependency Tree Crawling -:::caution -This is experimental feature. Experimental features not covered by semver and might be subject of a change. -::: +While the glob-based extraction process works well for most projects, it can be challenging for multi-page applications (MPAs) such as Next.js. In such cases, the glob approach generates a single catalog that includes all messages from each page, which may not be ideal for effectively managing translations. -Although the glob-based extraction process is effective for most projects, however, multipage (MPA) frameworks such as NextJS pose a problem because the glob-based approach creates a catalog consisting of all messages from all pages. +This means that the entire catalog must be loaded for each page or navigation, resulting in unnecessary loading of messages that aren't utilized on that specific page. -This means that the entire catalog must be loaded for each page/navigation, which results in loading messages that are not used on that page. +To address this issue, an `experimental-extractor` has been introduced in Lingui v4. -To address this issue, a new `experimental-extractor` has been introduced in version 4. +:::caution Experimental +This is an experimental feature. Experimental features are not covered by semver and may be subject to change. +::: This extractor uses the dependency tree of files, rather than just a glob pattern, to crawl imports and discover files more accurately. By doing so, it creates a more optimized catalog that only contains the messages needed for each page. -The catalogs would still contain duplicating messages for common components, but it would be much better than the current approach. +The catalogs would still contain duplicate messages for common components, but it would be much better than the current approach. ![Scheme of discovering by dependencies](/img/docs/extractor-deps-scheme.jpg#gh-light-mode-only) ![Scheme of discovering by dependencies](/img/docs/extractor-deps-scheme-dark.jpg#gh-dark-mode-only) -To start using `experimental-extractor`, you need to add the following section to lingui config: +To start using `experimental-extractor`, add the following section to your Lingui configuration: ```ts /** @@ -150,19 +159,19 @@ module.exports = { }; ``` -And then call in the terminal: +Then run the following command in your terminal: ```bash lingui extract-experimental ``` -#### Notes +#### Important Notes -It's worth noting that the accuracy of the catalog heavily relies on tree-shaking, a technique used by modern bundlers to eliminate unused code from the final bundle. +It's worth noting that the accuracy of the catalog depends heavily on tree-shaking, a technique used by modern bundlers to remove unused code from the final bundle. -If the code passed to the extractor is written in a tree-shakeable way, the user will receive highly accurate catalogs. +If the code passed to the extractor is written in a tree-shakeable way, the user will get highly accurate catalogs. -While you might think that your code is tree-shakeable, in practice, tree-shaking might work differently than what you expect and some unwanted strings may be included in the catalogs. +While you may think that your code is tree-shakeable, in practice tree-shaking may work differently than you expect, and some unwanted strings may be included in the catalogs. To illustrate, let's consider the following code: @@ -185,16 +194,24 @@ export const species = { }; ``` -On the surface, it may appear that this code can be safely removed from the final bundle if it's not used. However, the `msg` function call can potentially produce a side effect, preventing the bundler from removing the entire `species` object from the final bundle. As a result, messages defined in this snippet may be included in more catalogs than expected. +On the surface, it may appear that this code can be safely removed from the final bundle if it isn't used. However, the `msg` function call may have a side effect that prevents the bundler from removing the entire `species` object from the final bundle. As a result, messages defined in this snippet may be included in more catalogs than expected. + +:::tip +To avoid this issue, one solution is to wrap the `species` object inside an _Immediately Invoked Function Expression_ (IIFE) and add the `/* @__PURE__ */` annotation. +::: + +By adding this annotation to the IIFE, we tell the bundler that the entire `species' object can be safely removed if it is not used or exported elsewhere in the code. -To avoid this issue, one solution is to wrap the `species` object inside an Immediately Invoked Function Expression (IIFE) and add the `/* @__PURE__ */` annotation. +## Supported Source Types -By adding this annotation to the IIFE, we are telling the bundler that the entire `species` object can be safely removed if it is not used or exported elsewhere in the code. +The extractor supports TypeScript, Flow, and JavaScript (Stage 3) out of the box. -## Supported source types +If you are using some experimental features (Stage 0 - Stage 2) or frameworks with custom syntax such as Vue.js or Svelte, you may want to implement your own custom extractor. -The extractor supports TypeScript, Flow and JavaScript (Stage 3) out of the box. +Visit [Custom Extractor](/docs/guides/custom-extractor.md) to learn how to create a custom extractor. -If you use some experimental features (Stage 0 - Stage 2) or frameworks with custom syntax such as Vue.js or Svelte, you may want to implement your custom extractor. +## See Also -Visit [Advanced: Custom Extractor](/docs/guides/custom-extractor.md) to learn how to create a custom extractor. +- [Lingui CLI Reference](/docs/ref/cli.md) +- [Macros Reference](/docs/ref/macro.mdx) +- [Catalog Formats](/docs/ref/catalog-formats.md) diff --git a/website/docs/guides/message-format.md b/website/docs/guides/message-format.md index 2d068fafd..3ea3a44e8 100644 --- a/website/docs/guides/message-format.md +++ b/website/docs/guides/message-format.md @@ -1,6 +1,6 @@ # ICU MessageFormat -ICU MessageFormat is a flexible yet powerful syntax to express all nuances of grammar for each language. +ICU MessageFormat is a flexible and powerful syntax designed to express the grammatical nuances of different languages. Its flexibility ensures that your application can handle grammatical variations, making it essential for effective internationalization. ## Overview @@ -20,7 +20,7 @@ Example: `Attachment {name} saved` {count, plural, one {Message} other {Messages}} ``` -> Using exact matches (`=0`): +> Using exact matches for specific counts (`=0`): ```icu-message-format {count, plural, =0 {No messages} @@ -28,7 +28,7 @@ Example: `Attachment {name} saved` other {# messages}} ``` -> Offsetting plural form: +> Offsetting plural forms: ```icu-message-format {count, plural, offset:1 @@ -55,6 +55,7 @@ Example: `Attachment {name} saved` other {#th message}} ``` -## Further reading +## See Also +- [Pluralization](/docs/guides/plurals.md) - [ICU Playground](https://format-message.github.io/icu-message-format-for-translators/editor.html) diff --git a/website/docs/guides/plurals.md b/website/docs/guides/plurals.md index baf169373..b8d834158 100644 --- a/website/docs/guides/plurals.md +++ b/website/docs/guides/plurals.md @@ -5,26 +5,30 @@ description: Learn about pluralization and how to use it in your application wit # Pluralization -Plurals are essential when dealing with internationalization. [LinguiJS](https://github.com/lingui/js-lingui) uses [CLDR Plural Rules](https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html). -In general, there are 6 plural forms (taken from [CLDR Plurals](https://cldr.unicode.org/index/cldr-spec/plural-rules) page): +Pluralization is essential for effective internationalization, allowing applications to display messages or select options based on the number. In this article, you'll explore various categories of pluralization, see implementation examples, and learn how to customize your application for different languages. + +Lingui uses the [CLDR Plural Rules](https://www.unicode.org/cldr/charts/42/supplemental/language_plural_rules.html) to determine the correct plural form for each language. + +In general, there are 6 plural forms (taken from the [CLDR Plurals](https://cldr.unicode.org/index/cldr-spec/plural-rules) page): - zero - one (singular) - two (dual) - few (paucal) - many (also used for fractions if they have a separate class) -- other (required — general plural form — also used if the language - only has a single form) +- other (required — general plural form — also used if the language only has a single form) -Only the last one, _other_, is required because it's the only common plural form used in all languages. +:::info +Only the _other_ form is required, because it's the only common plural form used in all languages. -All other plural forms depends on language. For example, English has only two: _one_ and _other_ (1 book vs. 2 books). In Czech, we have four: _one_, _few_, _many_ and _other_ (1 kniha, 2 knihy, 1,5 knihy, 5 knih). Some languages have even more, like Arabic. +All other plural forms depend on the language. For example, English has only two forms: _one_ and _other_ (1 book vs. 2 books). In Czech, we have four: _one_, _few_, _many_ and _other_ (1 kniha, 2 knihy, 1,5 knihy, 5 knih). Some languages have even more, such as Arabic. +::: -## Using plural forms +## Using Plural Forms -Good thing is that **as developers, we have to know only plural forms for the source language**. +The good thing is that **as developers, we only need to know the plural forms of the source language**. -If we use English in the source code, then we'll use only _one_ and _other_: +When we use English in the source code, we'll only use _one_ and _other_: ```js plural(numBooks, { @@ -33,14 +37,9 @@ plural(numBooks, { }); ``` -When `numBooks == 1`, this will render as _1 book_ and for `numBook == 2` it will be _2 books_. - -Interestingly, for `numBooks == -1`, it will be _-1 book_. This is because the "one" plural form also applies to -1. It is therefore important to remember that the plural forms (such as "one" or "two") do not represent the numbers themselves, but rather _categories_ of numbers. -If you want to specify a message for an exact number, use [`exact matches`](/guides/message-format#plurals). +When `numBooks == 1`, this will render as _1 book_ and for `numBook == 2` it will be _2 books_. Interestingly, for `numBooks == -1`, it will be _-1 book_. This is because the "one" plural form also applies to -1. It is therefore important to remember that the plural forms (such as "one" or "two") do not represent the numbers themselves, but rather _categories_ of numbers. -> Funny fact for non-English speakers: In English, 0 uses plural form too, _0 books_. - -Under the hood, [`plural`](/docs/ref/macro.mdx#plural) is replaced with low-level [`i18n._`](/docs/ref/core.md#i18n._). For production, the above example will become: +Under the hood, the [`plural`](/docs/ref/macro.mdx#plural) macro is replaced with a low-level [`i18n._`](/docs/ref/core.md#i18n._) call. In production, the example will look like this: ```js i18n._({ @@ -51,51 +50,25 @@ i18n._({ }); ``` -When we extract messages from source code using the [CLI tool](/docs/ref/cli.md), we get: +When we extract messages from the source code using the [Lingui CLI](/docs/ref/cli.md), we get: ```icu-message-format {numBooks, plural, one {# book} other {# books}} ``` -Now, we give it to our Czech translator, and they'll translate it as: +This is then translated by our Czech translator as: ```icu-message-format {numBooks, plural, one {# kniha} few {# knihy} many {# knihy} other {# knih}} ``` -The important thing is that _we don't need to change our code to support languages with different plural rules_. Here's a step-by-step description of the process: - -1. In source code, we have: - - ```js - plural(numBooks, { - one: "# book", - other: "# books", - }); - ``` - -2. Code is compiled to: - - ```js - i18n._({ - id: "d1wX4r", - // stripped on production - // message: '{numBooks, plural, one {# book} other {# books}}', - values: { numBooks }, - }); - ``` - -3. Message `{numBooks, plural, one {# book} other {# books}}` is translated to: - - ```icu-message-format - {numBooks, plural, one {# kniha} few {# knihy} many {# knihy} other {# knih}} - ``` - -4. Finally, message is formatted using Czech plural rules. +:::tip Important +The important thing is that _we don't have to change our code to support languages with different plural rules_. +::: -## Source code in language other than English +## Source Code in Language Other than English -As mentioned above, as developers, we have to know and use only plural forms for the source language. Go see what [plural forms](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html) your languages has and then you can use them. Here's the example in Czech: +As developers, we only need to be aware of the plural forms for the source language. Check the [plural forms](https://www.unicode.org/cldr/charts/42/supplemental/language_plural_rules.html) for your language, and then you can use them accordingly. Here's an example in Czech: ```js plural(numBooks, { @@ -106,4 +79,4 @@ plural(numBooks, { }); ``` -This make [LinguiJS](https://github.com/lingui/js-lingui) useful also for unilingual projects, i.e: if you don't translate your app at all. Plurals, number and date formatting are common in every language. +This makes Lingui also valuable for monolingual projects, meaning that you can benefit from it even if you don't translate your application. Pluralization, along with number and date formatting, is relevant to all languages. diff --git a/website/docs/guides/pseudolocalization.md b/website/docs/guides/pseudolocalization.md index 87a10c91c..63e12acb0 100644 --- a/website/docs/guides/pseudolocalization.md +++ b/website/docs/guides/pseudolocalization.md @@ -5,44 +5,48 @@ description: Learn how to use pseudolocalization to test the internationalizatio # Pseudolocalization -There is built in support for [pseudolocalization](https://en.wikipedia.org/wiki/Pseudolocalization). Pseudolocalization is a method for testing the internationalization aspects of your application by replacing your strings with altered versions and maintaining string readability. It also makes hard coded strings and improperly concatenated strings easy to spot so that they can be properly localized. +Pseudo-localization is a method used to check the software's readiness to be localized. This method shows how the product's UI will look after translation. Use this feature to reduce potential rework by checking whether any source strings should be altered before the translation process begins. + +It also makes it easy to identify hard-coded strings and improperly concatenated strings so that they can be localized properly. > Example: Ţĥĩś ţēxţ ĩś ƥśēũďōĺōćàĺĩźēď ## Configuration -To setup pseudolocalization add [`pseudoLocale`](/docs/ref/conf.md#pseudolocale) to your lingui [`configuration file`](/docs/ref/conf.md): +To configure pseudolocalization, add the [`pseudoLocale`](/docs/ref/conf.md#pseudolocale) property to your Lingui configuration file: -```json +```json title="lingui.config.js" { - "lingui": { - "locale": ["en", "pseudo-LOCALE"], - "pseudoLocale": "pseudo-LOCALE", - "fallbackLocales": { - "pseudo-LOCALE": "en" - } + "locales": ["en", "pseudo-LOCALE"], + "pseudoLocale": "pseudo-LOCALE", + "fallbackLocales": { + "pseudo-LOCALE": "en" } } ``` -[`pseudoLocale`](/docs/ref/conf.md#pseudolocale) option can be any string that is in `locale` +The `pseudoLocale` option must be set to any string that matches a value in the [`locales`](/docs/ref/conf.md#locales) configuration. If this is not set correctly, no folder or pseudolocalization will be created. + +If the `fallbackLocales` is configured, the pseudolocalization will be generated from the translated fallback locale instead. + +## Generate Pseudolocalization -Examples: `en-PL`, `pseudo-LOCALE`, `pseudolocalization` or `en-UK` +After running the [`extract`](/docs/ref/cli.md#extract) command, verify that the folder for the pseudolocale has been created. -## Create pseudolocalization +Pseudolocalization is automatically generated during the [`compile`](/docs/ref/cli.md#compile) process, using the messages. -[`pseudoLocale`](/docs/ref/conf.md#pseudolocale) string has to be in [`locales`](/docs/ref/conf.md#locales) config as well. Otherwise, no folder and no pseudolocalization is going to be created. After running [`extract`](/docs/ref/cli.md#extract) verify that the folder has been created. The pseudolocalization is automatically created on [`compile`](/docs/ref/cli.md#compile) from messages. In case fallbackLocales has been used, the pseudolocalization is going to be created from translated fallback locale. +## Switch Browser Into Specified Pseudolocale -## Switch browser into specified pseudoLocale +You can switch your browser to a pseudolocale either by adjusting the browser settings or by using extensions. Extensions provide flexibility by allowing you to use any locale, while browser settings are usually limited to valid language tags (BCP 47). -We can use browsers settings or extensions. Extensions allow to use any locale. Browsers are usually limited into valid language tags (BCP 47). In that case, the locale for pseudolocalization has to be standard locale, which is not used in your application for example `zu_ZA` Zulu - SOUTH AFRICA +In such cases, the pseudolocale must be a standard locale that isn't already used in your application. For example, you could use `zu_ZA` (Zulu - South Africa). Chrome: -- With extension (valid locale) - [Locale Switcher](https://chrome.google.com/webstore/detail/locale-switcher/kngfjpghaokedippaapkfihdlmmlafcc) -- Without extension (valid locale) - [chrome://settings/?search=languages](chrome://settings/?search=languages) +- With extension (valid locale) - [Locale Switcher](https://chrome.google.com/webstore/detail/locale-switcher/kngfjpghaokedippaapkfihdlmmlafcc). +- Without extension (valid locale) - [chrome://settings/?search=languages](chrome://settings/?search=languages). Firefox: -- With extension (any string) - [Quick Accept-Language Switcher](https://addons.mozilla.org/en-GB/firefox/addon/quick-accept-language-switc/?src=search) -- Without extension (valid locale) - [about:preferences#general](about:preferences#general) > _Language_ +- With extension (any string) - [Quick Accept-Language Switcher](https://addons.mozilla.org/en-GB/firefox/addon/quick-accept-language-switc/?src=search). +- Without extension (valid locale) - [about:preferences#general](about:preferences#general) > _Language_. diff --git a/website/docs/guides/testing.md b/website/docs/guides/testing.md index 7041dbdf9..a88dd17ed 100644 --- a/website/docs/guides/testing.md +++ b/website/docs/guides/testing.md @@ -1,8 +1,8 @@ # Testing -Components using [`Trans`](/docs/ref/react.md#trans) or [`useLingui`](/docs/ref/react.md#uselingui) require access to the context of [`I18nProvider`](/docs/ref/react.md#i18nprovider). How you can wrap your component with the I18nProvider depends on the test library you use. +In a React application, components that use [`Trans`](/docs/ref/react.md#trans) or [`useLingui`](/docs/ref/react.md#uselingui) need access to the context provided by [`I18nProvider`](/docs/ref/react.md#i18nprovider). How you wrap your component with the I18nProvider depends on the testing library you're using. -Here is a working example with [react-testing-library](https://testing-library.com/docs/react-testing-library/intro/), using the [wrapper-property](https://testing-library.com/docs/react-testing-library/api#wrapper): +Below is an example using [react-testing-library](https://testing-library.com/docs/react-testing-library/intro/) and its [wrapper-property](https://testing-library.com/docs/react-testing-library/api#wrapper): ```tsx title="index.js" import React from "react"; @@ -40,4 +40,4 @@ test("Content should be translated correctly in Czech", () => { }); ``` -You could define a custom renderer to re-use this TestingProvider, see [react testing library - Custom Render](https://testing-library.com/docs/react-testing-library/setup#custom-render) +To avoid repeating the `TestingProvider` setup in multiple tests, consider defining a custom renderer. You can find more about this in the [react testing library - Custom Render](https://testing-library.com/docs/react-testing-library/setup#custom-render) documentation. diff --git a/website/docs/releases/migration-4.md b/website/docs/releases/migration-4.md index fbf5b3113..c4950ae33 100644 --- a/website/docs/releases/migration-4.md +++ b/website/docs/releases/migration-4.md @@ -87,7 +87,7 @@ Enabling this mode will swap the logic, and the formatter will treat all message You can read more about the motivation behind this change in the [original RFC](https://github.com/lingui/js-lingui/issues/1360) -Also, we've added a possibility to provide a context for the message. For more details, see the [Providing a context for a message](/docs/guides/explicit-vs-generated-ids.md#providing-context-for-a-message). +Also, we've added a possibility to provide a context for the message. For more details, see the [Providing a context for a message](/docs/guides/explicit-vs-generated-ids.md#context). The context feature affects the message ID generation and adds the `msgctxt` parameter in case of the PO catalog format extraction. From 0eac67c24ae7867c97be8b14fe79c296f98ece8f Mon Sep 17 00:00:00 2001 From: Andrii Bodnar Date: Thu, 31 Oct 2024 11:20:03 +0200 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Vojtech Novak --- website/docs/guides/explicit-vs-generated-ids.md | 2 +- website/docs/guides/message-extraction.md | 2 +- website/docs/guides/plurals.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/guides/explicit-vs-generated-ids.md b/website/docs/guides/explicit-vs-generated-ids.md index 5ed395d83..e1376875c 100644 --- a/website/docs/guides/explicit-vs-generated-ids.md +++ b/website/docs/guides/explicit-vs-generated-ids.md @@ -7,7 +7,7 @@ description: Learn about the differences between explicit and generated IDs and When internationalizing your application, you may need to decide whether to use explicit or generated IDs for your messages. -In this guide, we will explore the basic concepts of explicit and generated IDs, and then delve into a comprehensive comparison, highlighting the advantages and disadvantages of each approach. +In this guide, we explore the basic concepts of explicit and generated IDs, and then delve into a comprehensive comparison, highlighting the advantages and disadvantages of each approach. ## What are Explicit IDs and Generated IDs? diff --git a/website/docs/guides/message-extraction.md b/website/docs/guides/message-extraction.md index 3a056e13e..ca06a74c6 100644 --- a/website/docs/guides/message-extraction.md +++ b/website/docs/guides/message-extraction.md @@ -17,7 +17,7 @@ The extractor operates at a static level, meaning that it analyzes the source co ### Macros Usage -> Macros are JavaScript functions that run at build time. The value returned by a macro is inlined into the bundle instead of the original function call. +> Macros are JavaScript transformers that run at build time. The value returned by a macro is inlined into the bundle instead of the original function call. The Lingui Macro provides powerful macros to transform JavaScript objects and JSX elements into [ICU MessageFormat](/docs/guides/message-format.md) messages at compile time. diff --git a/website/docs/guides/plurals.md b/website/docs/guides/plurals.md index b8d834158..b05661d41 100644 --- a/website/docs/guides/plurals.md +++ b/website/docs/guides/plurals.md @@ -5,7 +5,7 @@ description: Learn about pluralization and how to use it in your application wit # Pluralization -Pluralization is essential for effective internationalization, allowing applications to display messages or select options based on the number. In this article, you'll explore various categories of pluralization, see implementation examples, and learn how to customize your application for different languages. +Pluralization is essential for effective internationalization, allowing applications to display messages or select options based on the number. In this article, we explore various categories of pluralization, see implementation examples, and learn how to customize your application for different languages. Lingui uses the [CLDR Plural Rules](https://www.unicode.org/cldr/charts/42/supplemental/language_plural_rules.html) to determine the correct plural form for each language. From 27e2a471ddf9c8df9360cba730177ced5d2cbe29 Mon Sep 17 00:00:00 2001 From: Andrii Bodnar Date: Thu, 31 Oct 2024 11:44:59 +0200 Subject: [PATCH 3/4] improvements --- website/docs/guides/dynamic-loading-catalogs.md | 14 +++++++++++--- website/docs/guides/message-extraction.md | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/website/docs/guides/dynamic-loading-catalogs.md b/website/docs/guides/dynamic-loading-catalogs.md index 0fc61fb98..51eb1a686 100644 --- a/website/docs/guides/dynamic-loading-catalogs.md +++ b/website/docs/guides/dynamic-loading-catalogs.md @@ -5,7 +5,7 @@ description: Learn how to set up dynamic loading of message catalogs in Lingui t # Dynamic Loading of Message Catalogs -Internationalization in modern applications requires an efficient way to manage and load localized messages without overwhelming the initial bundle size. With Lingui's flexible approach, the developer is responsible for dynamically loading message catalogs based on the active language. +Internationalization in modern applications requires careful handling of localized messages to avoid bloating the initial bundle size. By default, Lingui makes it easy to load all strings for a single active locale. For even greater efficiency, developers can selectively load only the messages needed on demand using [`i18n.load`](/ref/core#i18n.load), ensuring minimal resource usage. The [`I18nProvider`](/docs/ref/react.md#i18nprovider) component doesn't make assumptions about your app's structure, giving you the freedom to load only the necessary messages for the currently selected language. @@ -61,9 +61,9 @@ const I18nApp = () => { }; ``` -## Conclusion +### Optimized Bundle Structure -Looking at the content of build dir, we see one chunk per language: +Looking at the contents of the build directory, we find a separate chunk for each language: ```bash i18n-0.c433b3bd.chunk.js @@ -78,3 +78,11 @@ When the page is first loaded, only the main bundle and the bundle for the first After changing the language in the UI, the second language bundle is loaded: ![Requests during the second render](/img/docs/dynamic-loading-catalogs-2.png) + +## Dependency Tree Extractor (experimental) + +The Dependency Tree Extractor is an experimental feature designed to improve message extraction by analyzing the dependency tree of your application. This tool improves the efficiency of loading localized messages by identifying only the necessary messages for each component or page, rather than extracting all messages into a single catalog. + +This allows for a more granular extraction process, resulting in smaller and more relevant message catalogs. + +For detailed guidance on message extraction, refer to the [Message Extraction](/guides/message-extraction) guide. diff --git a/website/docs/guides/message-extraction.md b/website/docs/guides/message-extraction.md index ca06a74c6..95ce5c2c1 100644 --- a/website/docs/guides/message-extraction.md +++ b/website/docs/guides/message-extraction.md @@ -200,7 +200,7 @@ On the surface, it may appear that this code can be safely removed from the fina To avoid this issue, one solution is to wrap the `species` object inside an _Immediately Invoked Function Expression_ (IIFE) and add the `/* @__PURE__ */` annotation. ::: -By adding this annotation to the IIFE, we tell the bundler that the entire `species' object can be safely removed if it is not used or exported elsewhere in the code. +By adding this annotation to the IIFE, we tell the bundler that the entire `species` object can be safely removed if it is not used or exported elsewhere in the code. ## Supported Source Types From 4bcdbafbdbc7af4ada27214c0e0a9c61bcc030d2 Mon Sep 17 00:00:00 2001 From: Andrii Bodnar Date: Thu, 31 Oct 2024 14:22:55 +0200 Subject: [PATCH 4/4] add info about the default locale --- website/docs/guides/dynamic-loading-catalogs.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/docs/guides/dynamic-loading-catalogs.md b/website/docs/guides/dynamic-loading-catalogs.md index 51eb1a686..263ebc4d0 100644 --- a/website/docs/guides/dynamic-loading-catalogs.md +++ b/website/docs/guides/dynamic-loading-catalogs.md @@ -35,6 +35,12 @@ export async function dynamicActivate(locale: string) { } ``` +:::caution Important +Even the "default" locale requires a catalog to be loaded. Lingui aims to keep the internationalization footprint to a minimum by dropping default messages from the production build. This way, only the catalog for the active language is loaded, avoiding duplication. + +Without this optimization, switching from the default language (e.g. EN) to another (e.g. FR) would require loading both language catalogs. This strategy ensures efficiency by loading only the necessary messages for one language at a time. +::: + ### Usage in Your Application To use the `dynamicActivate` function in your application, you must call it on application startup. The following example shows how to use it in a React application: