From 60af5db9729884d58a37f22a4bef67431a6d8c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Kamy=C5=9Fev?= Date: Mon, 8 Apr 2024 11:49:02 +0700 Subject: [PATCH] API for handling language in `@withease/i18next` (#77) --- .changeset/poor-carrots-argue.md | 5 +++ apps/website/docs/i18next/index.md | 26 +++++++++++++++ packages/i18next/src/integration.ts | 35 +++++++++++++++++++- packages/i18next/src/is_ready.test.ts | 2 +- packages/i18next/src/language.test.ts | 43 +++++++++++++++++++++++++ packages/i18next/src/reporting.test.ts | 2 +- packages/i18next/src/translated.test.ts | 2 +- 7 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 .changeset/poor-carrots-argue.md create mode 100644 packages/i18next/src/language.test.ts diff --git a/.changeset/poor-carrots-argue.md b/.changeset/poor-carrots-argue.md new file mode 100644 index 00000000..21d42292 --- /dev/null +++ b/.changeset/poor-carrots-argue.md @@ -0,0 +1,5 @@ +--- +'@withease/i18next': minor +--- + +Add _Store_ `$language` and _EventCallable_ `changeLanguage` to integration diff --git a/apps/website/docs/i18next/index.md b/apps/website/docs/i18next/index.md index b6cc31a1..c48cd754 100644 --- a/apps/website/docs/i18next/index.md +++ b/apps/website/docs/i18next/index.md @@ -147,6 +147,32 @@ const { $isReady } = createI18nextIntegration({ }); ``` +#### `$language` + +A [_Store_](https://effector.dev/docs/api/effector/store) containing the current language. + +```ts +const { $language } = createI18nextIntegration({ + /* ... */ +}); +``` + +#### `changeLanguage` + +An [_EventCallable_](https://effector.dev/en/api/effector/event/) that can be called with a language code to change the current language. + +```ts +const { changeLanguage } = createI18nextIntegration({ + /* ... */ +}); + +sample({ + clock: someButtonClicked, + fn: () => 'en', + target: changeLanguage, +}); +``` + #### `reporting` An object with the following fields: diff --git a/packages/i18next/src/integration.ts b/packages/i18next/src/integration.ts index 83a62797..b8846aed 100644 --- a/packages/i18next/src/integration.ts +++ b/packages/i18next/src/integration.ts @@ -1,6 +1,7 @@ import { type Event, type Store, + type EventCallable, attach, combine, createEffect, @@ -28,6 +29,8 @@ type I18nextIntegration = { $t: Store; translated: Translated; $isReady: Store; + $language: Store; + changeLanguage: EventCallable; reporting: { missingKey: Event; }; @@ -80,6 +83,12 @@ export function createI18nextIntegration({ missingKey: createEvent(), }; + const $language = createStore(null, { serialize: 'ignore' }); + + const changeLanguage = createEvent(); + + // -- End of public API + sample({ clock: [ instanceInitialized, @@ -89,6 +98,28 @@ export function createI18nextIntegration({ target: $stanaloneT, }); + sample({ + clock: [ + instanceInitialized, + sample({ clock: contextChanged, source: $instance, filter: Boolean }), + ], + fn: (i18next) => i18next.language, + target: $language, + }); + + const changeLanguageFx = attach({ + source: $instance, + async effect(instance, nextLangauge: string) { + if (!instance) { + return; + } + + await instance.changeLanguage(nextLangauge); + }, + }); + + sample({ clock: changeLanguage, target: changeLanguageFx }); + sample({ clock: instanceInitialized, fn: () => true, @@ -223,12 +254,14 @@ export function createI18nextIntegration({ sample({ clock: destroy, target: destroyListenersFx }); sample({ clock: destroyListenersFx.done, - target: [$contextChangeListener.reinit!, $missingKeyListener.reinit!], + target: [$contextChangeListener.reinit, $missingKeyListener.reinit], }); return { $isReady, $t, + $language, + changeLanguage, translated: (firstArg, ...args: any[]) => { if (typeof firstArg === 'string') { return translatedWithVariables(firstArg, args[0]); diff --git a/packages/i18next/src/is_ready.test.ts b/packages/i18next/src/is_ready.test.ts index 3f8a716f..da54bc95 100644 --- a/packages/i18next/src/is_ready.test.ts +++ b/packages/i18next/src/is_ready.test.ts @@ -4,7 +4,7 @@ import { describe, expect, test } from 'vitest'; import { createI18nextIntegration } from './integration'; -describe('integration.$t', () => { +describe('integration.$isReady', () => { test('not ready if not initialized', async () => { const setup = createEvent(); diff --git a/packages/i18next/src/language.test.ts b/packages/i18next/src/language.test.ts new file mode 100644 index 00000000..c0da044b --- /dev/null +++ b/packages/i18next/src/language.test.ts @@ -0,0 +1,43 @@ +import { describe, test, expect } from 'vitest'; +import { allSettled, createEvent, fork } from 'effector'; +import { createInstance } from 'i18next'; + +import { createI18nextIntegration } from './integration'; + +describe('integration.$language/changeLanguage', () => { + test('change language', async () => { + const setup = createEvent(); + + const instance = createInstance({ + resources: { + th: { common: { key: 'th value' } }, + en: { common: { key: 'en value' } }, + }, + lng: 'th', + }); + + const { $language, changeLanguage, translated } = createI18nextIntegration({ + instance, + setup, + }); + + const $val = translated('common:key'); + + const scope = fork(); + + // Before initialization + expect(scope.getState($language)).toBeNull(); + + await allSettled(setup, { scope }); + + // Initial value + expect(scope.getState($language)).toBe('th'); + expect(scope.getState($val)).toBe('th value'); + + await allSettled(changeLanguage, { scope, params: 'en' }); + + // After change + expect(scope.getState($language)).toBe('en'); + expect(scope.getState($val)).toBe('en value'); + }); +}); diff --git a/packages/i18next/src/reporting.test.ts b/packages/i18next/src/reporting.test.ts index d0d096bf..0a671828 100644 --- a/packages/i18next/src/reporting.test.ts +++ b/packages/i18next/src/reporting.test.ts @@ -1,5 +1,5 @@ import { allSettled, createEvent, createStore, fork } from 'effector'; -import { createInstance, type i18n } from 'i18next'; +import { createInstance } from 'i18next'; import { describe, expect, test, vi } from 'vitest'; import { createI18nextIntegration } from './integration'; diff --git a/packages/i18next/src/translated.test.ts b/packages/i18next/src/translated.test.ts index bc06325f..50f80873 100644 --- a/packages/i18next/src/translated.test.ts +++ b/packages/i18next/src/translated.test.ts @@ -4,7 +4,7 @@ import { describe, expect, test } from 'vitest'; import { createI18nextIntegration } from './integration'; -describe('integration.$t', () => { +describe('integration.translated', () => { describe('overload: key', () => { test('supports simple key', async () => { const instance = createInstance({