npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

grammy-i18n-yaml

v1.0.1

Published

YAML-based internationalization plugin for grammY

Downloads

3

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 grammy

Quick 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 directory
  • loadLocalesDirSync(directory: string): void - Synchronously load YAML files
  • loadLocale(locale: string, options: LoadLocaleOptions): Promise<void> - Load single locale
  • loadLocaleSync(locale: string, options: LoadLocaleOptions): void - Synchronously load locale
  • t(locale: string, key: string, variables?: object): string - Translate with specific locale
  • translate(locale: string, key: string, variables?: object): string - Alias for t
  • middleware(): 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 found
  • MISSING_TRANSLATION - No translation available for any locale
  • INVALID_INTERPOLATION - Error during variable interpolation

Best Practices

  1. Use nested keys for better organization:

    buttons:
      save: "Save"
      cancel: "Cancel"
  2. Consistent variable naming:

    welcome: "Welcome, {{firstName}}!"
    goodbye: "Goodbye, {{firstName}}!"
  3. Provide fallbacks for all translations:

    const i18n = new I18n({
      defaultLocale: "en", // Always have English as fallback
    });
  4. 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

  1. Convert .ftl files to .yml format
  2. Change variable syntax from {$var} to {{var}}
  3. Update import statements
  4. Adjust configuration if needed

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details.