Skip to content

Commit

Permalink
feat(core): add i18n.setCatalogAndActivate for easier nextjs integration
Browse files Browse the repository at this point in the history
  • Loading branch information
timofei-iatsenko committed Mar 21, 2023
1 parent 10633f3 commit bd20146
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 21 deletions.
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

0 comments on commit bd20146

Please sign in to comment.