{"_id":"@crimson2k/grammy-i18n","name":"@crimson2k/grammy-i18n","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.1":{"name":"@crimson2k/grammy-i18n","version":"1.0.1","description":"YAML-based internationalization plugin for grammY","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","require":"./dist/index.js","types":"./dist/index.d.ts"}},"scripts":{"build":"bun build src/index.ts --outdir dist --target node && bun x tsc","prepublishOnly":"bun run build","dev":"bun run --watch src/index.ts","test":"bun run ./tests/simple.test.ts","test:watch":"bun run --watch ./tests/simple.test.ts","test:full":"bun test ./tests/index.test.ts","example":"bun run examples/example.ts","bot":"bun run examples/bot-example.ts","lint":"tsc --noEmit","clean":"rm -rf dist"},"keywords":["grammy","telegram","bot","i18n","internationalization","yaml","translation","localization","telegram-bot","typescript"],"repository":{"type":"git","url":"git+https://github.com/crimson2k/grammy-i18n.git"},"author":{"name":"Crimson","email":"cezeg@proton.me"},"license":"MIT","dependencies":{"yaml":"^2.3.4"},"devDependencies":{"@types/bun":"latest","typescript":"^5.0.0"},"peerDependencies":{"grammy":"^1.10.0","typescript":"^5.0.0"},"engines":{"node":">=16.0.0"},"_id":"@crimson2k/grammy-i18n@1.0.1","gitHead":"548deced3f9a39034caf0b0aa83fc54b99bec6d9","bugs":{"url":"https://github.com/crimson2k/grammy-i18n/issues"},"homepage":"https://github.com/crimson2k/grammy-i18n#readme","_nodeVersion":"22.17.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-JQ56imWNpCm4bPhdevDDGZfbE8g3qD+EIAYL3xgIjCMXD5feT/2t6cl1YMaaTFERIKPk3Xa1/z4npjetbhueqQ==","shasum":"c8c7acc6ba27d57b4f7d5ac0f81204054b09126a","tarball":"https://registry.npmjs.org/@crimson2k/grammy-i18n/-/grammy-i18n-1.0.1.tgz","fileCount":11,"unpackedSize":320259,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCA1wBnvXwypp6MDKQyDxKzY4UcMPBPeD+80YQjko3GUwIhAIOe58M9j4He0GYRhn1KrPnrT5vbuZVOnAOhjQVQYt8R"}]},"_npmUser":{"name":"crimson2k","email":"cezeg@proton.me"},"directories":{},"maintainers":[{"name":"crimson2k","email":"cezeg@proton.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/grammy-i18n_1.0.1_1753004650847_0.07736475909554552"},"_hasShrinkwrap":false}},"time":{"created":"2025-07-20T09:44:10.661Z","1.0.1":"2025-07-20T09:44:11.027Z","modified":"2025-07-20T09:44:11.331Z"},"maintainers":[{"name":"crimson2k","email":"cezeg@proton.me"}],"description":"YAML-based internationalization plugin for grammY","homepage":"https://github.com/crimson2k/grammy-i18n#readme","keywords":["grammy","telegram","bot","i18n","internationalization","yaml","translation","localization","telegram-bot","typescript"],"repository":{"type":"git","url":"git+https://github.com/crimson2k/grammy-i18n.git"},"author":{"name":"Crimson","email":"cezeg@proton.me"},"bugs":{"url":"https://github.com/crimson2k/grammy-i18n/issues"},"license":"MIT","readme":"# YAML i18n Library for grammY\n\nA powerful internationalization (i18n) library for [grammY](https://grammy.dev) 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.\n\n## Features\n\n- 🌍 **Multi-language support** with YAML translation files\n- 🔄 **Automatic locale negotiation** based on user language preferences\n- 💾 **Session persistence** for user language settings\n- 🎯 **Type-safe translations** with TypeScript support\n- 🔧 **Variable interpolation** with `{{variable}}` syntax\n- 🎛️ **Flexible configuration** with custom locale negotiators\n- ⚡ **Performance optimized** with bundle caching\n- 🔍 **Comprehensive warning system** for missing translations\n- 📁 **Directory-based locale loading** or manual locale registration\n\n## Installation\n\n```bash\n# Using bun\nbun add yaml grammy\n\n# Using npm\nnpm install yaml grammy\n\n# Using yarn\nyarn add yaml grammy\n```\n\n## Quick Start\n\n### 1. Create Translation Files\n\nCreate a `locales` directory with YAML files for each language:\n\n**locales/en.yml**\n```yaml\nwelcome: \"Welcome to our bot!\"\ngreeting: \"Hello, {{name}}!\"\nbuttons:\n  start: \"Start\"\n  help: \"Help\"\n  settings: \"Settings\"\n```\n\n**locales/ru.yml**\n```yaml\nwelcome: \"Добро пожаловать в наш бот!\"\ngreeting: \"Привет, {{name}}!\"\nbuttons:\n  start: \"Старт\"\n  help: \"Помощь\"\n  settings: \"Настройки\"\n```\n\n### 2. Setup the Bot\n\n```typescript\nimport { Bot } from \"grammy\";\nimport { I18n } from \"./i18n/index.js\";\n\nconst bot = new Bot(\"YOUR_BOT_TOKEN\");\n\n// Create i18n instance\nconst i18n = new I18n({\n  defaultLocale: \"en\",\n  directory: \"./locales\",\n  useSession: true,\n});\n\n// Use i18n middleware\nbot.use(i18n.middleware());\n\n// Now you can use translations in your handlers\nbot.command(\"start\", (ctx) => {\n  ctx.reply(ctx.t(\"welcome\"));\n});\n\nbot.on(\"message\", (ctx) => {\n  ctx.reply(ctx.t(\"greeting\", { name: ctx.from.first_name }));\n});\n\nbot.start();\n```\n\n## API Reference\n\n### I18n Class\n\n#### Constructor Options\n\n```typescript\ninterface I18nConfig<C extends Context = Context> {\n  defaultLocale: string;           // Default locale (e.g., \"en\")\n  directory?: string;              // Path to locales directory\n  useSession?: boolean;            // Enable session-based locale persistence\n  yamlOptions?: YamlOptions;       // YAML parser options\n  localeNegotiator?: LocaleNegotiator<C>; // Custom locale detection function\n  globalTranslationContext?: (ctx: C) => Record<string, any>; // Global variables\n}\n```\n\n#### Methods\n\n- `loadLocalesDir(directory: string): Promise<void>` - Load all YAML files from directory\n- `loadLocalesDirSync(directory: string): void` - Synchronously load YAML files\n- `loadLocale(locale: string, options: LoadLocaleOptions): Promise<void>` - Load single locale\n- `loadLocaleSync(locale: string, options: LoadLocaleOptions): void` - Synchronously load locale\n- `t(locale: string, key: string, variables?: object): string` - Translate with specific locale\n- `translate(locale: string, key: string, variables?: object): string` - Alias for `t`\n- `middleware(): MiddlewareFn` - Returns grammY middleware\n\n### Context Extensions\n\nWhen using the middleware, the following properties are added to the context:\n\n```typescript\ninterface I18nFlavor {\n  i18n: {\n    yaml: YamlTranslator;                    // Internal YAML translator\n    getLocale(): Promise<string>;            // Get current locale\n    setLocale(locale: string): Promise<void>; // Set locale in session\n    useLocale(locale: string): void;         // Temporarily use locale\n    renegotiateLocale(): Promise<void>;      // Re-run locale negotiation\n  };\n  translate(key: string, variables?: object): string; // Translation function\n  t(key: string, variables?: object): string;         // Alias for translate\n}\n```\n\n## Advanced Usage\n\n### Custom Locale Negotiation\n\n```typescript\nconst i18n = new I18n({\n  defaultLocale: \"en\",\n  localeNegotiator: async (ctx) => {\n    // Get from user database\n    const user = await getUserFromDb(ctx.from.id);\n    return user?.preferredLanguage || ctx.from?.language_code || \"en\";\n  },\n});\n```\n\n### Global Translation Context\n\n```typescript\nconst i18n = new I18n({\n  defaultLocale: \"en\",\n  globalTranslationContext: (ctx) => ({\n    username: ctx.from?.username || \"unknown\",\n    firstName: ctx.from?.first_name || \"User\",\n    chatType: ctx.chat?.type || \"private\",\n  }),\n});\n```\n\n### Manual Locale Loading\n\n```typescript\n// Load from string\nawait i18n.loadLocale(\"es\", {\n  source: `\n    welcome: \"¡Bienvenido!\"\n    goodbye: \"¡Adiós!\"\n  `,\n});\n\n// Load from file\nawait i18n.loadLocale(\"fr\", {\n  filePath: \"./custom-locales/french.yml\",\n});\n```\n\n### Using with Inline Keyboards\n\n```typescript\nbot.command(\"menu\", (ctx) => {\n  const keyboard = {\n    inline_keyboard: [\n      [{ text: ctx.t(\"buttons.start\"), callback_data: \"start\" }],\n      [{ text: ctx.t(\"buttons.help\"), callback_data: \"help\" }],\n      [{ text: ctx.t(\"buttons.settings\"), callback_data: \"settings\" }],\n    ],\n  };\n\n  ctx.reply(ctx.t(\"menu.title\"), { reply_markup: keyboard });\n});\n```\n\n### Language Switching\n\n```typescript\nbot.callbackQuery(/lang_(.+)/, async (ctx) => {\n  const newLocale = ctx.match[1];\n  await ctx.i18n.setLocale(newLocale);\n  \n  await ctx.editMessageText(ctx.t(\"language_changed\"));\n  await ctx.answerCallbackQuery();\n});\n```\n\n### Hearing Localized Text\n\n```typescript\nimport { hears } from \"./i18n/index.js\";\n\n// Listen for localized button text\nbot.filter(hears(\"buttons.help\"), (ctx) => {\n  ctx.reply(ctx.t(\"help_message\"));\n});\n```\n\n## YAML Translation Format\n\n### Basic Translations\n\n```yaml\nsimple_message: \"This is a simple message\"\n```\n\n### Variables\n\n```yaml\ngreeting: \"Hello, {{name}}!\"\nuser_stats: \"User {{username}} has {{count}} messages\"\n```\n\n### Nested Translations\n\n```yaml\nmenu:\n  main:\n    title: \"Main Menu\"\n    subtitle: \"Choose an option\"\n  settings:\n    title: \"Settings\"\n    language: \"Language\"\n    theme: \"Theme\"\n```\n\n### Arrays and Objects\n\n```yaml\nbuttons:\n  - \"Button 1\"\n  - \"Button 2\"\n  - \"Button 3\"\n\ncolors:\n  primary: \"#007bff\"\n  secondary: \"#6c757d\"\n  success: \"#28a745\"\n```\n\n## Error Handling\n\nThe library includes a comprehensive warning system:\n\n```typescript\nimport { createWarningHandler, TranslateWarnings } from \"./i18n/index.js\";\n\nconst i18n = new I18n({\n  defaultLocale: \"en\",\n  yamlOptions: {\n    warningHandler: createWarningHandler(\n      (warning) => console.warn(\"Translation warning:\", warning),\n      [TranslateWarnings.MISSING_MESSAGE] // Ignore missing message warnings\n    ),\n  },\n});\n```\n\n### Warning Types\n\n- `MISSING_MESSAGE` - Translation key not found\n- `MISSING_TRANSLATION` - No translation available for any locale\n- `INVALID_INTERPOLATION` - Error during variable interpolation\n\n## Best Practices\n\n1. **Use nested keys** for better organization:\n   ```yaml\n   buttons:\n     save: \"Save\"\n     cancel: \"Cancel\"\n   ```\n\n2. **Consistent variable naming**:\n   ```yaml\n   welcome: \"Welcome, {{firstName}}!\"\n   goodbye: \"Goodbye, {{firstName}}!\"\n   ```\n\n3. **Provide fallbacks** for all translations:\n   ```typescript\n   const i18n = new I18n({\n     defaultLocale: \"en\", // Always have English as fallback\n   });\n   ```\n\n4. **Use descriptive keys**:\n   ```yaml\n   error_messages:\n     file_too_large: \"File size exceeds 50MB limit\"\n     invalid_format: \"Please upload a valid image file\"\n   ```\n\n## Differences from @grammyjs/i18n\n\n| Feature | @grammyjs/i18n | This Library |\n|---------|----------------|--------------|\n| Translation Format | Fluent (.ftl) | YAML (.yml/.yaml) |\n| Pluralization | Built-in Fluent rules | Manual (can be extended) |\n| Variable Syntax | `{$variable}` | `{{variable}}` |\n| File Extension | `.ftl` | `.yml` or `.yaml` |\n| Parser | Mozilla Fluent | js-yaml |\n\n## Migration from @grammyjs/i18n\n\n1. Convert `.ftl` files to `.yml` format\n2. Change variable syntax from `{$var}` to `{{var}}`\n3. Update import statements\n4. Adjust configuration if needed\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## License\n\nMIT License - see LICENSE file for details.","readmeFilename":"README.md","_rev":"1-a556a5ff50e16da70124ccd727f15265"}