From 57c766fad4db1d50b009806bb3fc8a6d16a9c689 Mon Sep 17 00:00:00 2001 From: m10rten Date: Sun, 25 Aug 2024 12:46:43 +0200 Subject: [PATCH] Added lazy load example and updated types for better experience --- .changeset/six-bats-push.md | 5 + examples/i4n-json/package.json | 2 +- examples/i4n-lazy/README.md | 11 + examples/i4n-lazy/fr.json | 6 + examples/i4n-lazy/main.ts | 47 +++++ examples/i4n-lazy/package.json | 20 ++ examples/i4n-lazy/pnpm-lock.yaml | 332 +++++++++++++++++++++++++++++++ src/i4n.ts | 11 +- tests/i4n.test.ts | 16 +- 9 files changed, 439 insertions(+), 11 deletions(-) create mode 100644 .changeset/six-bats-push.md create mode 100644 examples/i4n-lazy/README.md create mode 100644 examples/i4n-lazy/fr.json create mode 100644 examples/i4n-lazy/main.ts create mode 100644 examples/i4n-lazy/package.json create mode 100644 examples/i4n-lazy/pnpm-lock.yaml diff --git a/.changeset/six-bats-push.md b/.changeset/six-bats-push.md new file mode 100644 index 0000000..8124add --- /dev/null +++ b/.changeset/six-bats-push.md @@ -0,0 +1,5 @@ +--- +"i4n": patch +--- + +Updated types to allow string to be put in loaded and switch diff --git a/examples/i4n-json/package.json b/examples/i4n-json/package.json index 9df00f9..315f02f 100644 --- a/examples/i4n-json/package.json +++ b/examples/i4n-json/package.json @@ -1,5 +1,5 @@ { - "name": "i4n-node", + "name": "i4n-json", "version": "1.0.0", "description": "", "main": "main.ts", diff --git a/examples/i4n-lazy/README.md b/examples/i4n-lazy/README.md new file mode 100644 index 0000000..efb8d20 --- /dev/null +++ b/examples/i4n-lazy/README.md @@ -0,0 +1,11 @@ +# `i4n`-lazy + +This example demonstrates how to use the `i4n` package with lazy loading. + +Run it with: + +```bash +npm run start +``` + +This will start the `main.ts` file, present in this same directory. diff --git a/examples/i4n-lazy/fr.json b/examples/i4n-lazy/fr.json new file mode 100644 index 0000000..58253e7 --- /dev/null +++ b/examples/i4n-lazy/fr.json @@ -0,0 +1,6 @@ +{ + "fr": { + "hello": "Bonjour", + "world": "Terre" + } +} diff --git a/examples/i4n-lazy/main.ts b/examples/i4n-lazy/main.ts new file mode 100644 index 0000000..f86cdb0 --- /dev/null +++ b/examples/i4n-lazy/main.ts @@ -0,0 +1,47 @@ +import { I4n } from "i4n"; + +interface LanguageData { + hello: string; + key?: string; +} +type Language = "en" | "nl" | "fr"; +type TranslationSet = { + [lang in Language]?: LanguageData; +}; + +async function loadFrench(): Promise { + return await import("./fr.json"); +} + +export const translations = { + en: { + hello: "Hello World!", + key: "Somehow we do", + }, + nl: { + hello: "Hallo Wereld!", + }, +} satisfies TranslationSet; + +const i4n = new I4n({ + translations, + language: "en", + fallbackLanguage: "en", +}); + +const main = async () => { + console.log(i4n.t("hello")); // "Hello" + + console.log("Swapping", i4n.switch("nl")); + + console.log(i4n.t("hello")); + + console.log(i4n.t("key")); + + i4n.lazy({ loader: loadFrench }); + await i4n.loaded({ lang: "fr" }); + i4n.switch("fr"); + console.log(i4n.t("hello")); +}; + +main(); diff --git a/examples/i4n-lazy/package.json b/examples/i4n-lazy/package.json new file mode 100644 index 0000000..f40a567 --- /dev/null +++ b/examples/i4n-lazy/package.json @@ -0,0 +1,20 @@ +{ + "name": "i4n-lazy", + "version": "1.0.0", + "description": "", + "main": "main.ts", + "scripts": { + "start": "tsx main.ts" + }, + "keywords": [], + "author": "", + "license": "", + "devDependencies": { + "@types/node": "^22.4.0", + "tsx": "^4.17.0", + "typescript": "^5.5.4" + }, + "dependencies": { + "i4n": "^0.5.0" + } +} diff --git a/examples/i4n-lazy/pnpm-lock.yaml b/examples/i4n-lazy/pnpm-lock.yaml new file mode 100644 index 0000000..deeff2c --- /dev/null +++ b/examples/i4n-lazy/pnpm-lock.yaml @@ -0,0 +1,332 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + i4n: + specifier: ^0.5.0 + version: 0.5.0 + devDependencies: + '@types/node': + specifier: ^22.4.0 + version: 22.5.0 + tsx: + specifier: ^4.17.0 + version: 4.18.0 + typescript: + specifier: ^5.5.4 + version: 5.5.4 + +packages: + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@types/node@22.5.0': + resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==} + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.7.6: + resolution: {integrity: sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==} + + i4n@0.5.0: + resolution: {integrity: sha512-cbk/s1wRAvOzfx7FrJJDCVJUDuk0qPCq0LcF8907LGs2rZ5QIfJBJTLxEsAW6eQsXi193OCh2hi9+3AfhB6xmA==} + engines: {node: '>=20.11.x < 22.x'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + tsx@4.18.0: + resolution: {integrity: sha512-a1jaKBSVQkd6yEc1/NI7G6yHFfefIcuf3QJST7ZEyn4oQnxLYrZR5uZAM8UrwUa3Ge8suiZHcNS1gNrEvmobqg==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + +snapshots: + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@types/node@22.5.0': + dependencies: + undici-types: 6.19.8 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.7.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + i4n@0.5.0: {} + + resolve-pkg-maps@1.0.0: {} + + tsx@4.18.0: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.7.6 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.5.4: {} + + undici-types@6.19.8: {} diff --git a/src/i4n.ts b/src/i4n.ts index 447a355..02e2bef 100644 --- a/src/i4n.ts +++ b/src/i4n.ts @@ -185,7 +185,12 @@ export class I4n, L extends keyof T & string = return this._loading === false || this._data !== undefined; } - public async loaded(options?: { interval?: number; signal?: AbortSignal; key?: string | undefined; lang?: L }) { + public async loaded(options?: { + interval?: number; + signal?: AbortSignal; + key?: string | undefined; + lang?: L | (string & {}); + }) { const _options = { interval: 50, ...options }; if (_options.signal?.aborted) return; @@ -223,7 +228,7 @@ export class I4n, L extends keyof T & string = | { data?: never; lang?: never; - loader?: undefined | (() => Record | Promise>); + loader?: undefined | (() => T[L] | Promise | Record | Promise>); }) { if (data && loader) throw new I4nException({ @@ -247,7 +252,7 @@ export class I4n, L extends keyof T & string = * @param language language to switch to * @returns {void} - nothing. */ - public switch(language: keyof T): void { + public switch(language: keyof T | (string & {})): void { if (!language) throw new I4nException({ type: "invalid-language", message: "Language cannot be empty." }); if (!this._data[language]) throw new I4nException({ type: "invalid-language", message: "Language is not in the translations" }); diff --git a/tests/i4n.test.ts b/tests/i4n.test.ts index 202bc3d..3068f5f 100644 --- a/tests/i4n.test.ts +++ b/tests/i4n.test.ts @@ -13,9 +13,12 @@ type TranslationData = { object_template: ({ type, count }: { type: string; count: number }) => string; }; -type TranslationSet = Record; +type Language = "fr" | "en" | "es"; -const testTranslations: TranslationSet = { +type TranslationSet = { + [lang in Language]?: TranslationData; +}; +const testTranslations = { en: { earth: "world", @@ -41,7 +44,7 @@ const testTranslations: TranslationSet = { object_template: ({ type, count }) => `[${type}]: a ${count}`, }, -}; +} satisfies TranslationSet; const testDefaultLanguage = "en" satisfies keyof typeof testTranslations; @@ -54,7 +57,7 @@ const testAnotherLoader = async () => ({ }); describe("I4n", () => { - let i4n: I4n; + let i4n: I4n; beforeEach(() => { i4n = new I4n({ translations: testTranslations, @@ -215,9 +218,8 @@ describe("I4n", () => { }); test("If the lazy loading works with just data and no loader", () => { - const i4n = new I4n({ language: "en", translations: {} }); + const i4n = new I4n({ language: "en", translations: {} }); expect(i4n.t("earth")).toBe(undefined); - i4n.lazy({ data: { ...testTranslations["en"], @@ -229,7 +231,7 @@ describe("I4n", () => { }); test("If the lazy loading throws when no data and loader is passed, and when both are passed", () => { - const i4n = new I4n({ language: "en", translations: {} }); + const i4n = new I4n({ language: "en", translations: {} }); try { i4n.lazy({ data: testTranslations as any, loader: testJsonLoader as any }); } catch (error) {