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

Support Effect and async function as instance #79

Merged
merged 6 commits into from
Apr 8, 2024
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
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,
});
```
Comment on lines +10 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to also add a error handling example here 🤔

Suggested change
sample({
clock: someButtonClicked,
fn: () => 'en',
target: changeLanguageFx,
});
```
sample({
clock: someButtonClicked,
fn: () => 'en',
target: changeLanguageFx,
});
sample({
clock: changeLanguageFx.fail,
target: showErrorNotification,
})

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
Loading