Skip to content

Commit

Permalink
feat: refactor loadAndActivate (#1609)
Browse files Browse the repository at this point in the history
  • Loading branch information
vonovak committed Apr 26, 2023
1 parent 73081c2 commit 7e04168
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 75 deletions.
2 changes: 1 addition & 1 deletion examples/create-react-app/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const catalogs: Record<string, () => Promise<Messages>> = {
*/
export async function dynamicActivate(locale: string) {
const messages = await catalogs[locale as any]()
i18n.loadAndActivate(locale, messages)
i18n.loadAndActivate({ locale, messages })
}

// If not we can just load all the catalogs and do a simple i18n.active(localeToActive)
Expand Down
21 changes: 20 additions & 1 deletion examples/nextjs-babel/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { i18n, Messages } from "@lingui/core"
import { useRouter } from "next/router"
import { useEffect } from "react"

/**
* Load messages for requested locale and activate it.
Expand All @@ -14,7 +15,25 @@ export async function loadCatalog(locale: string) {
export function useLinguiInit(messages: Messages) {
const router = useRouter()
const locale = router.locale || router.defaultLocale!
i18n.loadAndActivate(locale, messages, false)
const isClient = typeof window !== "undefined"

if (!isClient && locale !== i18n.locale) {
// there is single instance of i18n on the server
// note: on the server, we could have an instance of i18n per supported locale
// to avoid calling loadAndActivate for (worst case) each request, but right now that's what we do
i18n.loadAndActivate({ locale, messages })
}
if (isClient && i18n.locale === undefined) {
// first client render
i18n.loadAndActivate({ locale, messages })
}

useEffect(() => {
const localeDidChange = locale !== i18n.locale
if (localeDidChange) {
i18n.loadAndActivate({ locale, messages })
}
}, [locale])

return i18n
}
21 changes: 20 additions & 1 deletion examples/nextjs-swc/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { i18n, Messages } from '@lingui/core'
import { useRouter } from 'next/router'
import { useEffect } from 'react'

export async function loadCatalog(locale: string) {
const catalog = await import(`@lingui/loader!./locales/${locale}.po`)
Expand All @@ -9,7 +10,25 @@ export async function loadCatalog(locale: string) {
export function useLinguiInit(messages: Messages) {
const router = useRouter()
const locale = router.locale || router.defaultLocale!
i18n.loadAndActivate(locale, messages, false)
const isClient = typeof window !== 'undefined'

if (!isClient && locale !== i18n.locale) {
// there is single instance of i18n on the server
// note: on the server, we could have an instance of i18n per supported locale
// to avoid calling loadAndActivate for (worst case) each request, but right now that's what we do
i18n.loadAndActivate({ locale, messages })
}
if (isClient && i18n.locale === undefined) {
// first client render
i18n.loadAndActivate({ locale, messages })
}

useEffect(() => {
const localeDidChange = locale !== i18n.locale
if (localeDidChange) {
i18n.loadAndActivate({ locale, messages })
}
}, [locale])

return i18n
}
2 changes: 1 addition & 1 deletion examples/vite-project-react-babel/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import { i18n } from "@lingui/core"
*/
export async function loadCatalog(locale: string) {
const catalog = await import(`./locales/${locale}.po`)
i18n.loadAndActivate(locale, catalog.messages)
i18n.loadAndActivate({ locale, messages: catalog.messages })
}
43 changes: 16 additions & 27 deletions packages/core/src/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,42 +132,28 @@ describe("I18n", () => {
const cbChange = jest.fn()
i18n.on("change", cbChange)

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

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

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",
i18n.loadAndActivate({
locale: "ar",
locales: ["en-UK", "ar-AS"],
messages: { message: "My Message" },
})

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

it("should override existing data", () => {
Expand All @@ -181,12 +167,15 @@ describe("I18n", () => {
},
})

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

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

Expand Down
35 changes: 14 additions & 21 deletions packages/core/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ type Events = {
missing: (event: MissingMessageEvent) => void
}

type LoadAndActivateOptions = {
/** initial active locale */
locale: Locale
/** list of alternative locales (BCP 47 language tags) which are used for number and date formatting */
locales?: Locales
/** compiled message catalog */
messages: Messages
}

export class I18n extends EventEmitter<Events> {
private _locale: Locale
private _locales: Locales
Expand Down Expand Up @@ -173,31 +182,15 @@ export class I18n extends EventEmitter<Events> {
}

/**
* @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.
* @param options {@link LoadAndActivateOptions}
*/
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
}
loadAndActivate({ locale, locales, messages }: LoadAndActivateOptions) {
this._locale = locale
this._locales = locales || undefined

this._messages[this._locale] = messages

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

activate(locale: Locale, locales?: Locales) {
Expand Down
48 changes: 25 additions & 23 deletions website/docs/ref/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,29 @@ import { i18n } from "./custom-i18n-config"

### Class `i18n()` {#i18n}

### `i18n.load(catalogs: Catalogs)` {#i18n.load(catalogs)}
### `i18n.load(locale: string, catalog: Catalog)` {#i18n.load}
### `i18n.loadAndActivate(options)` {#i18n.loadAndActivate}

Load catalog for given locale or load multiple catalogs at once.
`options` is an object with following properties:

- `locale`: initial active locale
- `locales`: list of alternative locales (BCP 47 language tags) which are used for number and date formatting
- `messages`: **compiled** message catalog

Sets (overwrites) the catalog for given locale and activates the locale.

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

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

### `i18n.load(allMessages: AllMessages)` {#i18n.load(allMessages)}
### `i18n.load(locale: string, messages: Messages)` {#i18n.load}

Load messages for given locale or load multiple message catalogs at once.

When some messages for the provided locale are already loaded, calling `i18n.load` will merge the new messages with the existing ones using `Object.assign`.

```ts
import { i18n } from "@lingui/core"
Expand Down Expand Up @@ -101,7 +120,7 @@ i18n.load('en', messagesEn)

### `i18n.activate(locale[, locales])` {#i18n.activate}

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

```ts
import { i18n } from "@lingui/core"
Expand All @@ -113,23 +132,6 @@ 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 Down Expand Up @@ -299,7 +301,7 @@ const i18n = setupI18n({

// This is a shortcut for:
// const i18n = setupI18n()
// i18n.activate("en", ["en-UK", "ar-AS"])
// i18n.activate("ar", ["en-UK", "ar-AS"])
```

### `options.messages`
Expand Down Expand Up @@ -347,7 +349,7 @@ i18n._('missing translation') // raises alert

### Catalogs

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

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

0 comments on commit 7e04168

Please sign in to comment.