From a590f449b86823aa520f8830557f95ffe7585b33 Mon Sep 17 00:00:00 2001 From: Raul Macarie Date: Wed, 27 Dec 2023 20:50:13 +0100 Subject: [PATCH 1/2] feat(t): export everything through index file --- packages/t/source/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/t/source/index.ts b/packages/t/source/index.ts index a4ac473..f68b8b2 100644 --- a/packages/t/source/index.ts +++ b/packages/t/source/index.ts @@ -1 +1 @@ -export { createTranslationsFactory } from "./factory.ts"; +export * from "./factory.ts"; From f06787c20b5f011300f14b8df7f902df3b15136b Mon Sep 17 00:00:00 2001 From: Raul Macarie Date: Wed, 27 Dec 2023 20:51:02 +0100 Subject: [PATCH 2/2] feat(t-react): add locale and translations getter functions --- packages/t-react/source/index.ts | 83 ++++++++++++++++++--------- packages/t-react/tests/index.spec.tsx | 45 ++++++++++----- 2 files changed, 89 insertions(+), 39 deletions(-) diff --git a/packages/t-react/source/index.ts b/packages/t-react/source/index.ts index 58701d0..1519a38 100644 --- a/packages/t-react/source/index.ts +++ b/packages/t-react/source/index.ts @@ -4,12 +4,57 @@ import delve from "dlv"; import { atom, getDefaultStore, useAtomValue } from "jotai"; import { useMemo } from "react"; +import type { CreateTranslationsFactoryOptions } from "@wluwd/t"; + const $locale = atom(undefined); const defaultStore = getDefaultStore(); +const loadAndCacheTranslations = async ( + locale: string | undefined, + { + cache, + loaders, + }: CreateTranslationsFactoryOptions< + boolean, + string, + string, + string, + string + >["resources"], +) => { + if (locale === undefined) { + throw new NoLocaleSet(); + } + + let translations = cache.get(locale); + + if (translations !== undefined) { + return translations; + } + + const loader = loaders.get(locale); + + if (loader === undefined) { + throw new NoTranslationsSet({ + availableLocales: Array.from(loaders.keys()), + desiredLocale: locale, + }); + } + + translations = await loader(); + + cache.set(locale, translations); + + return translations; +}; + export const createTranslations = createTranslationsFactory({ hasSignalLikeInterface: false, locale: { + fn: { + factory: () => () => defaultStore.get($locale)!, + name: "getLocale", + }, hook: { factory: () => () => useAtomValue($locale)!, name: "useLocale", @@ -23,35 +68,21 @@ export const createTranslations = createTranslationsFactory({ loaders: new Map(), }, translations: { + fn: { + factory: (resources) => async (prefix) => { + const locale = defaultStore.get($locale); + + // eslint-disable-next-line ts/no-unsafe-return + return delve(await loadAndCacheTranslations(locale, resources), prefix); + }, + name: "getTranslations", + }, hook: { - factory: ({ cache, loaders }) => { - const $translations = atom(async (get) => { + factory: (resources) => { + const $translations = atom((get) => { const locale = get($locale); - if (locale === undefined) { - throw new NoLocaleSet(); - } - - let translations = cache.get(locale); - - if (translations !== undefined) { - return translations; - } - - const loader = loaders.get(locale); - - if (loader === undefined) { - throw new NoTranslationsSet({ - availableLocales: Array.from(loaders.keys()), - desiredLocale: locale, - }); - } - - translations = await loader(); - - cache.set(locale, translations); - - return translations; + return loadAndCacheTranslations(locale, resources); }); return (prefix) => { diff --git a/packages/t-react/tests/index.spec.tsx b/packages/t-react/tests/index.spec.tsx index a7382e5..5d6e32d 100644 --- a/packages/t-react/tests/index.spec.tsx +++ b/packages/t-react/tests/index.spec.tsx @@ -11,8 +11,6 @@ import { describe, expect, it } from "vitest"; import type { ReactNode } from "react"; -const nextTick = () => Promise.resolve(); - const enGB = { default: { some: { deep: { string: "en-GB" } } } } as const; const enUS = { default: { some: { deep: { string: "en-US" } } } } as const; @@ -45,16 +43,14 @@ describe("throws", () => { expect(locale.current).toBe(undefined); const error = { current: undefined }; - const { rerender } = renderHook(() => useTranslations("some.deep"), { + + renderHook(() => useTranslations("some.deep"), { wrapper: ({ children }) => ( {children} ), }); - await nextTick(); - rerender(); - - expect(error.current).toBeInstanceOf(NoLocaleSet); + await waitFor(() => expect(error.current).toBeInstanceOf(NoLocaleSet)); }); it("`NoTranslationsSet` when trying to load translations that have no loader", async () => { @@ -67,7 +63,8 @@ describe("throws", () => { expect(locale.current).toBe(undefined); const error = { current: undefined }; - const { rerender } = renderHook(() => useTranslations("some.deep"), { + + renderHook(() => useTranslations("some.deep"), { wrapper: ({ children }) => ( {children} ), @@ -76,14 +73,13 @@ describe("throws", () => { // @ts-expect-error testing undefined translations setLocale("en-AU"); - await nextTick(); - rerender(); - - expect(error.current).toBeInstanceOf(NoTranslationsSet); + await waitFor(() => + expect(error.current).toBeInstanceOf(NoTranslationsSet), + ); }); }); -it("creates a working `createTranslations`", async () => { +it("creates working hooks", async () => { const { setLocale, t, useLocale, useTranslations } = createTranslations( translations, { localeFrom: ["en-GB"], translator }, @@ -121,3 +117,26 @@ it("creates a working `createTranslations`", async () => { expect(t(translation.current.string)).toBe(enGB.default.some.deep.string), ); }); + +it("creates working functions", async () => { + const { getLocale, getTranslations, setLocale } = createTranslations( + translations, + { + localeFrom: ["en-US"], + translator, + }, + ); + + expect(getLocale()).toBe("en-US"); + expect(await getTranslations("some")).toBe(enUS.default.some); + + setLocale("en-GB"); + + expect(getLocale()).toBe("en-GB"); + expect(await getTranslations("some")).toBe(enGB.default.some); + + setLocale("en-US"); + + expect(getLocale()).toBe("en-US"); + expect(await getTranslations("some")).toBe(enUS.default.some); +});