grammy-i18n-yaml
v1.0.1
Published
YAML-based internationalization plugin for grammY
Downloads
3
Maintainers
Readme
YAML i18n Library for grammY
A powerful internationalization (i18n) library for grammY Telegram bots using YAML files for translations. This library provides a TypeScript-first approach to managing multilingual bot interfaces with an API similar to @grammyjs/i18n but using YAML instead of Fluent.
Features
- 🌍 Multi-language support with YAML translation files
- 🔄 Automatic locale negotiation based on user language preferences
- 💾 Session persistence for user language settings
- 🎯 Type-safe translations with TypeScript support
- 🔧 Variable interpolation with
{{variable}}syntax - 🎛️ Flexible configuration with custom locale negotiators
- ⚡ Performance optimized with bundle caching
- 🔍 Comprehensive warning system for missing translations
- 📁 Directory-based locale loading or manual locale registration
Installation
# Using bun
bun add yaml grammy
# Using npm
npm install yaml grammy
# Using yarn
yarn add yaml grammyQuick Start
1. Create Translation Files
Create a locales directory with YAML files for each language:
locales/en.yml
welcome: "Welcome to our bot!"
greeting: "Hello, {{name}}!"
buttons:
start: "Start"
help: "Help"
settings: "Settings"locales/ru.yml
welcome: "Добро пожаловать в наш бот!"
greeting: "Привет, {{name}}!"
buttons:
start: "Старт"
help: "Помощь"
settings: "Настройки"2. Setup the Bot
import { Bot } from "grammy";
import { I18n } from "./i18n/index.js";
const bot = new Bot("YOUR_BOT_TOKEN");
// Create i18n instance
const i18n = new I18n({
defaultLocale: "en",
directory: "./locales",
useSession: true,
});
// Use i18n middleware
bot.use(i18n.middleware());
// Now you can use translations in your handlers
bot.command("start", (ctx) => {
ctx.reply(ctx.t("welcome"));
});
bot.on("message", (ctx) => {
ctx.reply(ctx.t("greeting", { name: ctx.from.first_name }));
});
bot.start();API Reference
I18n Class
Constructor Options
interface I18nConfig<C extends Context = Context> {
defaultLocale: string; // Default locale (e.g., "en")
directory?: string; // Path to locales directory
useSession?: boolean; // Enable session-based locale persistence
yamlOptions?: YamlOptions; // YAML parser options
localeNegotiator?: LocaleNegotiator<C>; // Custom locale detection function
globalTranslationContext?: (ctx: C) => Record<string, any>; // Global variables
}Methods
loadLocalesDir(directory: string): Promise<void>- Load all YAML files from directoryloadLocalesDirSync(directory: string): void- Synchronously load YAML filesloadLocale(locale: string, options: LoadLocaleOptions): Promise<void>- Load single localeloadLocaleSync(locale: string, options: LoadLocaleOptions): void- Synchronously load localet(locale: string, key: string, variables?: object): string- Translate with specific localetranslate(locale: string, key: string, variables?: object): string- Alias fortmiddleware(): MiddlewareFn- Returns grammY middleware
Context Extensions
When using the middleware, the following properties are added to the context:
interface I18nFlavor {
i18n: {
yaml: YamlTranslator; // Internal YAML translator
getLocale(): Promise<string>; // Get current locale
setLocale(locale: string): Promise<void>; // Set locale in session
useLocale(locale: string): void; // Temporarily use locale
renegotiateLocale(): Promise<void>; // Re-run locale negotiation
};
translate(key: string, variables?: object): string; // Translation function
t(key: string, variables?: object): string; // Alias for translate
}Advanced Usage
Custom Locale Negotiation
const i18n = new I18n({
defaultLocale: "en",
localeNegotiator: async (ctx) => {
// Get from user database
const user = await getUserFromDb(ctx.from.id);
return user?.preferredLanguage || ctx.from?.language_code || "en";
},
});Global Translation Context
const i18n = new I18n({
defaultLocale: "en",
globalTranslationContext: (ctx) => ({
username: ctx.from?.username || "unknown",
firstName: ctx.from?.first_name || "User",
chatType: ctx.chat?.type || "private",
}),
});Manual Locale Loading
// Load from string
await i18n.loadLocale("es", {
source: `
welcome: "¡Bienvenido!"
goodbye: "¡Adiós!"
`,
});
// Load from file
await i18n.loadLocale("fr", {
filePath: "./custom-locales/french.yml",
});Using with Inline Keyboards
bot.command("menu", (ctx) => {
const keyboard = {
inline_keyboard: [
[{ text: ctx.t("buttons.start"), callback_data: "start" }],
[{ text: ctx.t("buttons.help"), callback_data: "help" }],
[{ text: ctx.t("buttons.settings"), callback_data: "settings" }],
],
};
ctx.reply(ctx.t("menu.title"), { reply_markup: keyboard });
});Language Switching
bot.callbackQuery(/lang_(.+)/, async (ctx) => {
const newLocale = ctx.match[1];
await ctx.i18n.setLocale(newLocale);
await ctx.editMessageText(ctx.t("language_changed"));
await ctx.answerCallbackQuery();
});Hearing Localized Text
import { hears } from "./i18n/index.js";
// Listen for localized button text
bot.filter(hears("buttons.help"), (ctx) => {
ctx.reply(ctx.t("help_message"));
});YAML Translation Format
Basic Translations
simple_message: "This is a simple message"Variables
greeting: "Hello, {{name}}!"
user_stats: "User {{username}} has {{count}} messages"Nested Translations
menu:
main:
title: "Main Menu"
subtitle: "Choose an option"
settings:
title: "Settings"
language: "Language"
theme: "Theme"Arrays and Objects
buttons:
- "Button 1"
- "Button 2"
- "Button 3"
colors:
primary: "#007bff"
secondary: "#6c757d"
success: "#28a745"Error Handling
The library includes a comprehensive warning system:
import { createWarningHandler, TranslateWarnings } from "./i18n/index.js";
const i18n = new I18n({
defaultLocale: "en",
yamlOptions: {
warningHandler: createWarningHandler(
(warning) => console.warn("Translation warning:", warning),
[TranslateWarnings.MISSING_MESSAGE] // Ignore missing message warnings
),
},
});Warning Types
MISSING_MESSAGE- Translation key not foundMISSING_TRANSLATION- No translation available for any localeINVALID_INTERPOLATION- Error during variable interpolation
Best Practices
Use nested keys for better organization:
buttons: save: "Save" cancel: "Cancel"Consistent variable naming:
welcome: "Welcome, {{firstName}}!" goodbye: "Goodbye, {{firstName}}!"Provide fallbacks for all translations:
const i18n = new I18n({ defaultLocale: "en", // Always have English as fallback });Use descriptive keys:
error_messages: file_too_large: "File size exceeds 50MB limit" invalid_format: "Please upload a valid image file"
Differences from @grammyjs/i18n
| Feature | @grammyjs/i18n | This Library |
|---------|----------------|--------------|
| Translation Format | Fluent (.ftl) | YAML (.yml/.yaml) |
| Pluralization | Built-in Fluent rules | Manual (can be extended) |
| Variable Syntax | {$variable} | {{variable}} |
| File Extension | .ftl | .yml or .yaml |
| Parser | Mozilla Fluent | js-yaml |
Migration from @grammyjs/i18n
- Convert
.ftlfiles to.ymlformat - Change variable syntax from
{$var}to{{var}} - Update import statements
- Adjust configuration if needed
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details.
