UNPKG

@gramio/i18n

Version:

i18n plugin for GramIO with type-safety

312 lines (233 loc) 7.92 kB
# @gramio/i18n [![npm](https://img.shields.io/npm/v/@gramio/i18n?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/i18n) [![npm downloads](https://img.shields.io/npm/dw/@gramio/i18n?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/i18n) [![JSR](https://jsr.io/badges/@gramio/i18n)](https://jsr.io/@gramio/i18n) [![JSR Score](https://jsr.io/badges/@gramio/i18n/score)](https://jsr.io/@gramio/i18n) `i18n` plugin for [GramIO](https://gramio.dev/). This plugin provide good way to add internationalization for your bots! It can be used without GramIO, but it will always keep it in mind. > [!IMPORTANT] > Since `1.0.0`, we have two ways to write localization: [`I18n-in-TS`](#i18n-in-ts-syntax) and [`Fluent`](#fluent-syntax) ### Installation For [I18n-in-TS syntax](#i18n-in-ts-syntax) ```bash npm install @gramio/i18n ``` For [Fluent syntax](#fluent-syntax) ```bash npm install @gramio/i18n @fluent/bundle ``` ## I18n-in-TS syntax This syntax allows you to write localization without leaving `.ts` files and does not require code-generation for **type-safety**, as well as provides convenient integration with the Format API out of the box! ```ts import { format, Bot } from "gramio"; import { defineI18n, type LanguageMap, type ShouldFollowLanguage, } from "@gramio/i18n"; const en = { greeting: (name: string) => format`Hello, ${name}!`, and: { some: { nested: "Hi!!!", }, }, } satisfies LanguageMap; const ru = { greeting: (name: string) => format`Привет, ${name}!`, and: { some: { nested: "Hi!!!", }, }, } satisfies ShouldFollowLanguage<typeof en>; // Strict will show error on missing keys // satisfies ShouldFollowLanguageStrict<typeof en>; const i18n = defineI18n({ primaryLanguage: "en", languages: { en, ru, }, }); i18n.t("en", "greeting", "World"); // Hello, World! i18n.t("en", "and.some.nested"); // Hi!!! 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") ); }); ``` ### Plurals ```ts import { pluralizeEnglish, pluralizeRussian } from "@gramio/i18n"; const count = 5; console.log(`You have ${count} ${pluralizeEnglish(count, "apple", "apples")}.`); // You have 5 apples. console.log( `У вас ${count} ${pluralizeRussian(count, "яблоко", "яблока", "яблок")}.` ); // У вас 5 яблок. ``` ### Get type-safe params for i18n ```ts // Most great way to get type-safe params for i18n export type GetI18nArgs<Key extends GetI18nKeys<typeof i18n>> = GetI18nParams< typeof i18n, Key >; function test(...args: GetI18nArgs<"greeting">) { return args; } type EnLocalization = ExtractLanguages<typeof i18n>["en"]; type EnLocalizationKeys = keyof ExtractLanguages<typeof i18n>["en"]; type EnGreetingArgs = ExtractArgsParams<EnLocalization["greeting"]>; ``` ## [Fluent](https://projectfluent.org/) syntax This plugin provide internationalization for your bots with [Fluent](https://projectfluent.org/) syntax. ![example](https://github.com/gramiojs/i18n/assets/57632712/47e04c22-f442-4a5a-b8b9-15b8512f7c4b) You can [setup type-safety](#type-safety) for it. ## Usage ### Create `locales` folder with `en.ftl` file ```fluent # Simple things are simple. hello-user = Hello, {$userName}! # Complex things are possible. shared-photos = {$userName} {$photoCount -> [one] added a new photo *[other] added {$photoCount} new photos } to {$userGender -> [male] his stream [female] her stream *[other] their stream }. ``` > [!IMPORTANT] > Fluent language support extensions for [VSCode](https://marketplace.visualstudio.com/items?itemName=macabeus.vscode-fluent) and [WebStorm](https://plugins.jetbrains.com/plugin/18416-fluent-language) ### Use plugin ```ts // src/index.ts import { Bot } from "gramio"; import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(i18n()) .command("start", async (context) => { return context.send( context.t("shared-photos", { userName: "Anna", userGender: "female", photoCount: 3, }) ); }) .onError(console.error) .onStart(console.log); bot.start(); ``` ## Options | Key | Type | Default | Description | | -------------- | ------ | --------------------- | ----------------------------------------- | | defaultLocale? | string | first loaded language | Default locale | | directory? | string | "locales" | The path to the folder with `*.ftl` files | ##### Or provide an client ```ts // ... import { getFluentClient, i18n } from "@gramio/i18n/fluent"; const client = getFluentClient({ defaultLocale: "en", directory: "locales", }); const bot = new Bot(process.env.BOT_TOKEN as string) .extend(i18n(client)) .command("start", async (context) => { return context.send(context.t("hello-user", { userName: "Anna" })); }); ``` ### Methods #### t Using this method, you can get the text in your chosen language. For example: ```ftl hello-user = Hello, {$userName}! ``` ```ts context.t("hello-user", { userName: "Anna" }); // Hello, Anna! ``` #### i18n.setLocale You can set user locale by `setLocale` method. > [!WARNING] > At the moment, there is no integration with sessions, and therefore, after the message, the language will again become the one that defaultLocale ```ts bot.command("start", async (context) => { context.i18n.setLocale("ru"); // if ru not found fallback to defaultLocale // context.i18n.setLocale("ru", true); if ru not found throw error return context.send( context.t("shared-photos", { userName: "Anna", userGender: "female", photoCount: 3, }) ); }); ``` #### i18n.locale Get current user locale. ```ts bot.command("lang", async (context) => { return context.send(context.i18n.locale); }); ``` #### i18n.locales Get loaded locales ```ts bot.command("languages", async (context) => { return context.send(context.i18n.locales.join(", ")); }); ``` ## Type-safety You can use this plugin with [fluent2ts](https://github.com/kravetsone/fluent2ts) which code-generates typescript types from your `.ftl` files. See [usage](https://github.com/kravetsone/fluent2ts?tab=readme-ov-file#usage). Npm: ```bash [npm] npx fluent2ts ``` Bun: ```bash [bun] bunx fluent2ts ``` Yarn: ```bash [yarn] yarn dlx fluent2ts ``` Pnpm: ```bash [pnpm] pnpm exec fluent2ts ``` And so we have a generated `locales.types.ts` file in `src` folder that exports the `TypedFluentBundle` interface. We set this type as a **generic** for the `i18n` plugin. And now we have **type-safety**! ```ts import type { TypedFluentBundle } from "./locales.types"; import { Bot } from "gramio"; import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.BOT_TOKEN as string) // or i18n(getFluentClient<TypedFluentBundle>()) .extend(i18n<TypedFluentBundle>()) .command("start", async (context) => { return context.send( context.t("shared-photos", { userName: "Anna", userGender: "female", photoCount: 3, }) ); }) .onError(console.error) .onStart(console.log); bot.start(); ```