@gramio/i18n
Version:
i18n plugin for GramIO with type-safety
312 lines (233 loc) • 7.92 kB
Markdown
[](https://www.npmjs.org/package/@gramio/i18n)
[](https://www.npmjs.org/package/@gramio/i18n)
[](https://jsr.io/@gramio/i18n)
[](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`](
For [I18n-in-TS syntax](
```bash
npm install @gramio/i18n
```
For [Fluent syntax](
```bash
npm install @gramio/i18n @fluent/bundle
```
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")
);
});
```
```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 яблок.
```
```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"]>;
```
This plugin provide internationalization for your bots with [Fluent](https://projectfluent.org/) syntax.

You can [setup type-safety](
```fluent
hello-user = Hello, {$userName}!
shared-photos =
{$userName} {$photoCount ->
[] added a new photo
*[other] added {$photoCount} new photos
} to {$userGender ->
[] his stream
[] 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)
```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();
```
| Key | Type | Default | Description |
| -------------- | ------ | --------------------- | ----------------------------------------- |
| defaultLocale? | string | first loaded language | Default locale |
| directory? | string | "locales" | The path to the folder with `*.ftl` files |
```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" }));
});
```
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!
```
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,
})
);
});
```
Get current user locale.
```ts
bot.command("lang", async (context) => {
return context.send(context.i18n.locale);
});
```
Get loaded locales
```ts
bot.command("languages", async (context) => {
return context.send(context.i18n.locales.join(", "));
});
```
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();
```