From dd760ef3f9d83e2e0cbe63fa3ed8b87207d3cb8f Mon Sep 17 00:00:00 2001 From: Kravets <57632712+kravetsone@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:43:17 +0300 Subject: [PATCH] feat: add buildT helper and place @fluent/bundle to optional peer dependency with /fluent export --- README.md | 19 ++++++++--- package.json | 14 +++++--- src/index.ts | 78 ++++++++++++++++++++++++++++++++------------- src/types.ts | 2 +- tests/index.test.ts | 29 ++++++++++++++--- 5 files changed, 105 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 209cc3a..11f1eef 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,20 @@ const i18n = defineI18n({ }, }); -// TODO: plugin - i18n.t("en", "greeting", "World"); // Hello, World! + +const bot = new Bot(process.env.BOT_TOKEN as string) + .derive("message", (context) => { + // u can take language from database or whatever u want and bind it to context without loosing type-safety + return { + t: i18n.buildT(context.from?.languageCode ?? "en"), + }; + }) + .on("message", (context) => { + return context.send( + context.t("greeting", context.from?.firstName ?? "World") + ); + }); ``` ## [Fluent](https://projectfluent.org/) syntax @@ -81,7 +92,7 @@ shared-photos = ```ts // src/index.ts import { Bot } from "gramio"; -import { i18n } from "@gramio/i18n"; +import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.TOKEN as string) .extend(i18n()) @@ -200,7 +211,7 @@ We set this type as a **generic** for the `i18n` plugin. And now we have **type- ```ts import type { TypedFluentBundle } from "./locales.types"; import { Bot } from "gramio"; -import { i18n } from "@gramio/i18n"; +import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.TOKEN as string) .extend(i18n()) diff --git a/package.json b/package.json index 052fc1c..a51605a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gramio/i18n", - "version": "0.0.6", + "version": "1.0.0", "description": "i18n plugin for GramIO with type-safety", "main": "dist/index.cjs", "module": "dist/index.js", @@ -47,7 +47,8 @@ "localization", "translation", "telegram-bot-api", - "telegram-bot" + "telegram-bot", + "i18n-in-ts" ], "devDependencies": { "@biomejs/biome": "1.9.3", @@ -58,10 +59,13 @@ "typescript": "^5.6.2" }, "peerDependencies": { - "gramio": ">=0.0.39 <1.0.0" + "gramio": ">=0.0.39 <1.0.0", + "@fluent/bundle": "^0.18.0" }, "files": ["dist"], - "dependencies": { - "@fluent/bundle": "^0.18.0" + "peerDependenciesMeta": { + "@fluent/bundle": { + "optional": true + } } } diff --git a/src/index.ts b/src/index.ts index 22f6369..8f7b3f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,6 @@ import type { } from "./types.js"; export * from "./types.js"; -export * from "./fluent/index.js"; export interface I18nOptions< Languages extends LanguagesMap, @@ -20,33 +19,66 @@ export function defineI18n< Languages extends LanguagesMap, PrimaryLanguage extends keyof Languages, >({ languages, primaryLanguage }: I18nOptions) { - return { - t: < - Language extends SoftString, - Key extends keyof Languages[Language], - Item extends Languages[Language][Key], - // @ts-expect-error trust me bro - FallbackItem extends Languages[PrimaryLanguage][Key], - >( - language: Language, - key: Key, - ...args: ExtractArgsParams - ): ExtractItemValue => { - // @ts-expect-error trust me bro - const fallbackItem = languages[primaryLanguage][key]; - console.log(languages[primaryLanguage], primaryLanguage); + // TODO: вынести из скоупа + function t< + Language extends SoftString, + Key extends Language extends keyof Languages + ? keyof Languages[Language] + : keyof Languages[PrimaryLanguage], + Item extends Language extends keyof Languages + ? // @ts-expect-error + Languages[Language][Key] + : // @ts-expect-error + Languages[PrimaryLanguage][Key], + // @ts-expect-error trust me bro + FallbackItem extends Languages[PrimaryLanguage][Key], + >( + language: Language, + key: Key, + ...args: Item extends unknown + ? ExtractArgsParams + : ExtractArgsParams + ): ExtractItemValue { + // @ts-expect-error trust me bro + const fallbackItem = languages[primaryLanguage][key]; + console.log(languages[primaryLanguage], primaryLanguage); - const item = languages[language] - ? (languages[language][key] ?? fallbackItem) - : fallbackItem; + const item = languages[language] + ? // @ts-expect-error + (languages[language][key] ?? fallbackItem) + : fallbackItem; - // @ts-expect-error trust me bro - if (typeof item === "function") return item(...args); + // @ts-expect-error trust me bro + if (typeof item === "function") return item(...args); - return item; - }, + return item; + } + + return { + t, languages: Object.keys(languages) as (keyof Languages)[], primaryLanguage, + buildT: >( + language: Language, + ) => { + return < + Key extends Language extends keyof Languages + ? keyof Languages[Language] + : keyof Languages[PrimaryLanguage], + Item extends Language extends keyof Languages + ? // @ts-expect-error trust me bro + Languages[Language][Key] + : // @ts-expect-error trust me bro + Languages[PrimaryLanguage][Key], + // @ts-expect-error trust me bro + FallbackItem extends Languages[PrimaryLanguage][Key], + >( + key: Key, + ...args: Item extends unknown + ? ExtractArgsParams + : ExtractArgsParams + ): ExtractItemValue => t(language, key, ...args); + }, // plugin: () => {}, }; } diff --git a/src/types.ts b/src/types.ts index 62485c8..04fd536 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,7 +25,7 @@ export type ExtractArgsParams = export type ExtractItemValue< Item extends LocaleItem, FallbackItem extends LocaleItem, -> = Item extends never | undefined +> = Item extends never | undefined | unknown ? FallbackItem : Item extends LocaleArgs ? ReturnType diff --git a/tests/index.test.ts b/tests/index.test.ts index 5e6f5bb..a2d8a76 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "bun:test"; -import { format } from "gramio"; +import { Bot, format } from "gramio"; import { defineI18n } from "../src"; import type { ShouldFollowLanguage } from "../src/types"; @@ -37,8 +37,29 @@ describe("I18n", () => { }); expect(i18n.t("en", "greeting", "World").toString()).toBe("Hello, World!"); - expect(i18n.t("rus" as string, "greeting", "World").toString()).toBe( - "Hello, World!", - ); + expect(i18n.t("ens", "greeting", "World").toString()).toBe("Hello, World!"); + }); + + it("Just pickup primary translation", () => { + const i18n = defineI18n({ + primaryLanguage: "en", + languages: { + en, + ru, + }, + }); + + const bot = new Bot("s") + .derive("message", (context) => { + return { + t: i18n.buildT((context.from?.languageCode as string) ?? "en"), + }; + }) + .on("message", (context) => { + context.t("greeting", "s"); + }); + + expect(i18n.t("en", "greeting", "World").toString()).toBe("Hello, World!"); + expect(i18n.t("en", "greeting", "World").toString()).toBe("Hello, World!"); }); });