Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add i18n.setCatalogAndActivate for easier nextjs integration #1541

Merged
merged 1 commit into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions packages/core/src/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,71 @@ describe("I18n", () => {
})
})

describe("I18n.loadAndActivate", () => {
it("should set locale and messages", () => {
const i18n = setupI18n()

const cbChange = jest.fn()
i18n.on("change", cbChange)

i18n.loadAndActivate("en", {
message: "My Message",
})

expect(i18n.locale).toEqual("en")
expect(i18n.locales).toBeNull()

expect(cbChange).toBeCalled()
})

it("should don't emit event if notify = false", () => {
const i18n = setupI18n()

const cbChange = jest.fn()
i18n.on("change", cbChange)

i18n.loadAndActivate(
"en",
{
message: "My Message",
},
false
)

expect(cbChange).not.toBeCalled()
})

it("should support locales as array", () => {
const i18n = setupI18n()

i18n.loadAndActivate(["en-GB", "en"], {
message: "My Message",
})

expect(i18n.locale).toEqual("en-GB")
expect(i18n.locales).toEqual(["en-GB", "en"])
})

it("should override existing data", () => {
const i18n = setupI18n({
locale: "en",
locales: ["en-GB", "en"],
messages: {
en: {
message: "My Message",
},
},
})

i18n.loadAndActivate("ru", {
message: "My Message",
})

expect(i18n.locale).toEqual("ru")
expect(i18n.locales).toBeNull()
})
})

it("._ should format message from catalog", () => {
const messages = {
Hello: "Salut",
Expand Down
28 changes: 28 additions & 0 deletions packages/core/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,34 @@ export class I18n extends EventEmitter<Events> {
this.emit("change")
}

/**
* @param locales one locale or array of locales.
* If array of locales is passed they would be used as fallback
* locales for date and number formatting
* @param messages compiled message catalog
* @param notify Should emit `change` event for all subscribers.
* This is useful for integration with frameworks as NextJS to avoid race-conditions during initialization.
*/
loadAndActivate(
locales: Locale | Locales,
messages: Messages,
notify = true
) {
if (Array.isArray(locales)) {
this._locale = locales[0]
this._locales = locales
} else {
this._locale = locales
this._locales = null
}

this._messages[this._locale] = messages

if (notify) {
this.emit("change")
}
}

activate(locale: Locale, locales?: Locales) {
if (process.env.NODE_ENV !== "production") {
if (!this._messages[locale]) {
Expand Down
58 changes: 37 additions & 21 deletions website/docs/ref/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ npm install --save @lingui/core

`@lingui/core` package exports the global instance of `i18n` object. Simply import it and use it:

```tsx
```ts
import { i18n } from "@lingui/core"

/**
Expand All @@ -22,8 +22,7 @@ import { i18n } from "@lingui/core"
*/
async function activate(locale: string) {
const { messages } = await import(`${locale}/messages.js`)
i18n.load(locale, messages)
i18n.activate(locale)
i18n.loadAndActivate(locale, messages)
}

activate("cs")
Expand All @@ -34,7 +33,7 @@ const translation = i18n._("Hello World")

If you don't want to use the global `i18n` instance and you want to setup your own, you can use [`setupI18n`](#setupi18n) method. You also need to set [`runtimeConfigModule`](/docs/ref/conf.md#runtimeconfigmodule) for macros to work correctly:

``` js
```ts
// If you import `i18n` object from custom module like this:
import { i18n } from "./custom-i18n-config"

Expand All @@ -51,7 +50,7 @@ import { i18n } from "./custom-i18n-config"

Load catalog for given locale or load multiple catalogs at once.

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

const messages = {
Expand Down Expand Up @@ -90,7 +89,7 @@ Formatting of messages as strings (e.g: `"My name is {name}"`) works in developm

The same example would in real application look like this:

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

// File generated by `lingui compile`
Expand All @@ -104,7 +103,7 @@ i18n.load('en', messagesEn)

Activate a locale and locales. `_` from now on will return messages in given locale.

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

i18n.activate("en")
Expand All @@ -114,6 +113,23 @@ i18n.activate("cs")
i18n._("Hello") // Return "Hello" in Czech
```

### `i18n.loadAndActivate(locales: Locale | Locales, messages: Messages, notify = true)` {#i18n.loadAndActivate}

Load catalog and activate given locale. This method is `i18n.load()` + `i18n.activate()` in one pass.

`locales` Could be one locale or array of locales. If array of locales is passed they would be used as fallback locales for date and number formatting.

`messages` **compiled** message catalog.

`notify` Should emit `change` event for all subscribers. This is useful for integration with frameworks as NextJS to avoid race-conditions during initialization.

```ts
import { i18n } from "@lingui/core"

const { messages } = await import(`${locale}/messages.js`)
i18n.loadAndActivate(locale, messages)
```

### `i18n._(messageId[, values[, options]])` {#i18n._}

The core method for translating and formatting messages.
Expand All @@ -124,7 +140,7 @@ The core method for translating and formatting messages.

`options.message` is the default translation (optional). This is mostly used when application doesn't use message IDs in natural language (e.g.: `msg.id` or `Component.title`).

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

// Simple message
Expand All @@ -145,7 +161,7 @@ i18n._("msg.id", { name: "Tom" }, { message: "My name is {name}" })

`format` is an object passed to the `options` argument of the [Intl.DateTimeFormat constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) (optional).

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

const d = new Date("2021-07-23T16:23:00")
Expand Down Expand Up @@ -175,7 +191,7 @@ Format a number using the conventional format for the active language.

`format` is an object passed to the `options` argument of the [Intl.NumberFormat constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) (optional).

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

i18n.activate("en")
Expand Down Expand Up @@ -206,7 +222,7 @@ In most cases you can use the global `i18n` object exported from `@lingui/core`

However, if you do need to setup your own `i18n` instance, remember to also set [`runtimeConfigModule`](/docs/ref/conf.md#runtimeconfigmodule) work macros to work properly:

``` js
```ts
// If you import `i18n` object from custom module like this:
import { i18n } from "./custom-i18n-config"

Expand All @@ -215,7 +231,7 @@ import { i18n } from "./custom-i18n-config"
```
:::

``` js
```ts
import { setupI18n } from "@lingui/core"

const i18n = setupI18n()
Expand All @@ -227,7 +243,7 @@ The factory function accepts one optional parameter, `options`:

Initial active locale.

``` jsx
```tsx
import { setupI18n } from "@lingui/core"

const i18n = setupI18n({ locale: "en" })
Expand All @@ -241,7 +257,7 @@ const i18n = setupI18n({ locale: "en" })

List of alternative locales (BCP 47 language tags) which are used for number and date formatting (some countries use more than one number/date format). If not set, active locale is used instead.

``` jsx
```tsx
import { setupI18n } from "@lingui/core"

const i18n = setupI18n({
Expand All @@ -258,7 +274,7 @@ const i18n = setupI18n({

Initial [`Messages`](#messages).

``` jsx
```tsx
import { setupI18n } from "@lingui/core"

const messages: {
Expand All @@ -276,7 +292,7 @@ const i18n = setupI18n({ messages })

Custom message to be returned when translation is missing. This is useful for debugging:

``` jsx
```tsx
import { setupI18n } from "@lingui/core"

const i18n = setupI18n({ missing: "🚨" })
Expand All @@ -285,7 +301,7 @@ i18n._('missing translation') === "🚨"

This might be also a function which is called with active locale and message ID:

``` jsx
```tsx
import { setupI18n } from "@lingui/core"

function missing(locale, id) {
Expand All @@ -301,7 +317,7 @@ i18n._('missing translation') // raises alert

Type of `catalogs` parameters in [`I18n.load`](#i18n.load(catalogs)) method:

``` js
```ts
type Catalogs = {[locale: string]: Catalog}

// Example:
Expand All @@ -325,7 +341,7 @@ const catalogs: Catalogs = {

Message catalog contains messages and language data (plurals). This object is usually generated in CLI:

``` js
```ts
type Catalog = {
languageData: {
plurals: Function
Expand All @@ -338,7 +354,7 @@ type Catalog = {

Type of messages in [`Catalogs`](#catalogs). It's a mapping of a **messageId** to a translation in given language. This may be a function if messages are compiled.

``` js
```ts
type Messages = {[messageId: string]: string | Function}

// Example
Expand All @@ -358,7 +374,7 @@ Triggered **after** locale is changed or new catalog is loaded. There are no arg

Triggered when a translation is requested with [`i18n._`](/docs/ref/core.md#i18n._) that does not exist in the active locale's messages. Information about the locale and message are available from the event.

``` js
```ts
i18n.on('missing', (event) => {
alert(`alert(`Translation in ${event.locale} for ${event.id} is missing!`)`)
})
Expand Down