Skip to content

Commit

Permalink
Support Effect and async function as instance (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev authored Apr 8, 2024
1 parent f8b24f4 commit 6989158
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 122 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-bears-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@withease/i18next': minor
---

Allow to pass async `instance` to integration
12 changes: 12 additions & 0 deletions apps/website/docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ export default defineConfig({
sidebar: {
...createSidebar('i18next', [
{ text: 'Get Started', link: '/i18next/' },
{
text: 'API',
items: [
{ text: '$t', link: '/i18next/t' },
{ text: 'translated', link: '/i18next/translated' },
{ text: '$isReady', link: '/i18next/is_ready' },
{ text: 'reporting', link: '/i18next/reporting' },
{ text: '$language', link: '/i18next/language' },
{ text: 'changeLanguageFx', link: '/i18next/change_language_fx' },
{ text: '$instance', link: '/i18next/instance' },
],
},
{ text: 'Release policy', link: '/i18next/releases' },
]),
...createSidebar('redux', [
Expand Down
15 changes: 15 additions & 0 deletions apps/website/docs/i18next/change_language.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# `changeLanguageFx`

An [_Effect_](https://effector.dev/en/api/effector/effect/) that can be called with a language code to change the current language.

```ts
const { changeLanguageFx } = createI18nextIntegration({
/* ... */
});

sample({
clock: someButtonClicked,
fn: () => 'en',
target: changeLanguageFx,
});
```
173 changes: 59 additions & 114 deletions apps/website/docs/i18next/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
---
outline: [2, 3]
---

# i18next

A powerful internationalization framework for Effector which is based on [i18next](https://www.i18next.com/).
Expand All @@ -26,17 +22,19 @@ npm install @withease/i18next i18next

:::

## API

### Initialization
## Initialization

All you need to do is to create an integration by calling `createI18nextIntegration` with an integration options:

- `instance`: an instance of i18next or [_Store_](https://effector.dev/docs/api/effector/store) with i18next instance; it is better to pass a [_Store_](https://effector.dev/docs/api/effector/store) because it will be possible to use isolated i18next instance and [avoid using global state](/magazine/global_variables).
- `instance`: an instance of i18next in various forms.
- `setup`: after this [_Event_](https://effector.dev/en/api/effector/event/) all listeners will be installed, and the integration will be ready to use; it is required because it is better to use [explicit initialization _Event_ in the application](/magazine/explicit_start).
- `teardown?`: after this [_Event_](https://effector.dev/en/api/effector/event/) all listeners will be removed, and the integration will be ready to be destroyed.

```ts
### Use replaceable static `i18next` instance

In the simplest case, you can pass an i18next instance to the integration.

```ts{9-11}
import i18next from 'i18next';
import { createStore, createEvent, fork, allSettled } from 'effector';
import { createI18nextIntegration } from '@withease/i18next';
Expand All @@ -45,144 +43,91 @@ import { createI18nextIntegration } from '@withease/i18next';
const appStarted = createEvent();
// Create Store for i18next instance
const $i18nextInstance = createStore(null);
const $i18nextInstance = createStore(i18next.createInstance(/* ... */), {
serialize: 'ignore',
});
const integration = createI18nextIntegration({
// Pass Store with i18next instance to the integration
instance: $i18nextInstance,
setup: appStarted,
});
// You can fill $someInstance later during runtime
// You can replace $someInstance later during runtime
// e.g., during fork on client or server

const scope = fork({
values: [[$i18nextInstance, i18next.createInstance(/* ... */)]],
});

await allSettled(appStarted, { scope });
```

### Usage

Returned from `createI18nextIntegration` integration contains the following fields:

#### `$t`

A [_Store_](https://effector.dev/docs/api/effector/store) containing a [translation function](https://www.i18next.com/overview/api#t), can be used anywhere in your app.

```ts
const { $t } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = $t.map((t) => t('cityPois.buttonText'));
```
### Use replaceable asynchronous `i18next` instance <Badge text="since v23.2.0" />

The second argument is an optional object with options for the translation function.
Sometimes you need to create an instance asynchronously. In this case, you can pass an [_Effect_](https://effector.dev/docs/api/effector/effect) that creates an instance.

```ts
const $city = createStore({ name: 'Moscow' });
```ts{9-11}
import i18next from 'i18next';
import { createStore, createEvent, fork, allSettled } from 'effector';
import { createI18nextIntegration } from '@withease/i18next';
const { $t } = createI18nextIntegration({
/* ... */
});
// Event that should be called after application initialization
const appStarted = createEvent();
const $someTranslatedString = combine({ city: $city, t: $t }, ({ city, t }) =>
t('cityPois.buttonText', {
cityName: city.name,
})
// Create Effect that creates i18next instance
const createI18nextFx = createEffect(() =>
i18next.use(/* ... */).init(/* ... */)
);
```

In both cases, result will be a [_Store_](https://effector.dev/docs/api/effector/store) containing a translated string. It will be updated automatically when the language or available translations will be changed.

#### `translated`

A factory that returns [_Store_](https://effector.dev/docs/api/effector/store) containing a translated string.

```ts
const { translated } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = translated('premiumLabel.BrandOne');
```

The second argument is an optional object with options for the translation function. Options can be a [_Store_](https://effector.dev/docs/api/effector/store) or a plain value.

```ts
const $city = createStore({ name: 'Moscow' });
const { translated } = createI18nextIntegration({
/* ... */
const integration = createI18nextIntegration({
// Pass Effect that creates i18next instance to the integration
instance: createI18nextFx,
setup: appStarted,
});
const $someTranslatedString = translated('cityPois.buttonText', {
cityName: $city.map((city) => city.name),
});
// You can replace createI18nextFx later during runtime
// e.g., during fork on client or server
```

Also, you can pass a template string with [_Store_](https://effector.dev/docs/api/effector/store) parts of a key:

```ts
const $pathOfAKey = createStore('BrandOne');

const { translated } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = translated`premiumLabel.${$pathOfAKey}`;
```
### Use static `i18next` instance

Result of the factory will be a [_Store_](https://effector.dev/docs/api/effector/store) containing a translated string. It will be updated automatically when the language or available translations will be changed.
Even though it is better to use a replaceable instance to [avoid global state](/magazine/global_variables) and make it possible to replace the instance during runtime, you can pass a static instance as well.

#### `$isReady`
```ts{9}
import i18next from 'i18next';
import { createStore, createEvent } from 'effector';
import { createI18nextIntegration } from '@withease/i18next';
A [_Store_](https://effector.dev/docs/api/effector/store) containing a boolean value that indicates whether the integration is ready to use.
// Event that should be called after application initialization
const appStarted = createEvent();
```ts
const { $isReady } = createI18nextIntegration({
/* ... */
const integration = createI18nextIntegration({
instance: i18next.createInstance(/* ... */),
setup: appStarted,
});
```

#### `$language` <Badge text="since v23.2.0" />
### Use static asynchronous `i18next` instance <Badge text="since v23.2.0" />

A [_Store_](https://effector.dev/docs/api/effector/store) containing the current language.
The same approach can be used with an asynchronous instance.

```ts
const { $language } = createI18nextIntegration({
/* ... */
});
```

#### `changeLanguageFx` <Badge text="since v23.2.0" />

An [_Effect_](https://effector.dev/en/api/effector/effect/) that can be called with a language code to change the current language.
```ts{9}
import i18next from 'i18next';
import { createStore, createEvent } from 'effector';
import { createI18nextIntegration } from '@withease/i18next';
```ts
const { changeLanguageFx } = createI18nextIntegration({
/* ... */
});
// Event that should be called after application initialization
const appStarted = createEvent();
sample({
clock: someButtonClicked,
fn: () => 'en',
target: changeLanguageFx,
const integration = createI18nextIntegration({
instance: () => i18next.use(/* ... */).init(/* ... */),
setup: appStarted,
});
```

#### `reporting`

An object with the following fields:

- `missingKey`, [_Event_](https://effector.dev/en/api/effector/event/) will be triggered when a key is missing in the translation resources, requires [adding `saveMissing` option to the i18next instance](https://www.i18next.com/overview/api#onmissingkey).
## Usage

```ts
const { reporting } = createI18nextIntegration({
/* ... */
});
Returned from `createI18nextIntegration` integration contains the following fields:

sample({ clock: reporting.missingKey, target: sendWarningToSentry });
```
- [`$t`](/i18next/t) is a [_Store_](https://effector.dev/docs/api/effector/store) containing a [translation function](https://www.i18next.com/overview/api#t)
- [`translated`](/i18next/translated) which can be used as a shorthand for `$t`
- [`$isReady`](/i18next/is_ready) is a [_Store_](https://effector.dev/docs/api/effector/store) containing a boolean value that indicates whether the integration is ready to use
- [`reporting`](/i18next/reporting) is an object with the fields that allow you to track different events of the integration
- <Badge text="since v23.2.0" /> [`$language`](/i18next/language) is a [_Store_](https://effector.dev/docs/api/effector/store) containing the current language
- <Badge text="since v23.2.0" /> [`changeLanguageFx`](/i18next/change_language) is an [_Effect_](https://effector.dev/docs/api/effector/effect) that changes the current language
- <Badge text="since v23.2.0" /> [`$instance`](/i18next/instance) is a [_Store_](https://effector.dev/docs/api/effector/store) containing the instance of i18next that is used by the integration
3 changes: 3 additions & 0 deletions apps/website/docs/i18next/instance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `$instance`

The instance of i18next that is used by the integration can be accessed via the `$instance` [_Store_](https://effector.dev/docs/api/effector/store).
9 changes: 9 additions & 0 deletions apps/website/docs/i18next/is_ready.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# `$isReady`

A [_Store_](https://effector.dev/docs/api/effector/store) containing a boolean value that indicates whether the integration is ready to use.

```ts
const { $isReady } = createI18nextIntegration({
/* ... */
});
```
9 changes: 9 additions & 0 deletions apps/website/docs/i18next/language.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# `$language` <Badge text="since v23.2.0" />

A [_Store_](https://effector.dev/docs/api/effector/store) containing the current language.

```ts
const { $language } = createI18nextIntegration({
/* ... */
});
```
13 changes: 13 additions & 0 deletions apps/website/docs/i18next/reporting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# `reporting`

An object with the following fields:

- `missingKey`, [_Event_](https://effector.dev/en/api/effector/event/) will be triggered when a key is missing in the translation resources, requires [adding `saveMissing` option to the i18next instance](https://www.i18next.com/overview/api#onmissingkey).

```ts
const { reporting } = createI18nextIntegration({
/* ... */
});

sample({ clock: reporting.missingKey, target: sendWarningToSentry });
```
29 changes: 29 additions & 0 deletions apps/website/docs/i18next/t.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# `$t`

A [_Store_](https://effector.dev/docs/api/effector/store) containing a [translation function](https://www.i18next.com/overview/api#t), can be used anywhere in your app.

```ts
const { $t } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = $t.map((t) => t('cityPois.buttonText'));
```

The second argument is an optional object with options for the translation function.

```ts
const $city = createStore({ name: 'Moscow' });

const { $t } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = combine({ city: $city, t: $t }, ({ city, t }) =>
t('cityPois.buttonText', {
cityName: city.name,
})
);
```

In both cases, result will be a [_Store_](https://effector.dev/docs/api/effector/store) containing a translated string. It will be updated automatically when the language or available translations will be changed.
39 changes: 39 additions & 0 deletions apps/website/docs/i18next/translated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# `translated`

A factory that returns [_Store_](https://effector.dev/docs/api/effector/store) containing a translated string.

```ts
const { translated } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = translated('premiumLabel.BrandOne');
```

The second argument is an optional object with options for the translation function. Options can be a [_Store_](https://effector.dev/docs/api/effector/store) or a plain value.

```ts
const $city = createStore({ name: 'Moscow' });

const { translated } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = translated('cityPois.buttonText', {
cityName: $city.map((city) => city.name),
});
```

Also, you can pass a template string with [_Store_](https://effector.dev/docs/api/effector/store) parts of a key:

```ts
const $pathOfAKey = createStore('BrandOne');

const { translated } = createI18nextIntegration({
/* ... */
});

const $someTranslatedString = translated`premiumLabel.${$pathOfAKey}`;
```

Result of the factory will be a [_Store_](https://effector.dev/docs/api/effector/store) containing a translated string. It will be updated automatically when the language or available translations will be changed.
Loading

0 comments on commit 6989158

Please sign in to comment.