{"id":48083450,"url":"https://github.com/Arustinal/fluentogram","last_synced_at":"2026-04-20T00:01:45.696Z","repository":{"id":50304645,"uuid":"477836459","full_name":"Arustinal/fluentogram","owner":"Arustinal","description":"A proper way to use an i18n mechanism with Aiogram3.","archived":false,"fork":false,"pushed_at":"2025-07-20T04:07:48.000Z","size":110,"stargazers_count":97,"open_issues_count":2,"forks_count":14,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-06T07:41:18.695Z","etag":null,"topics":["aiogram","fluent","i18n"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Arustinal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-04-04T19:02:23.000Z","updated_at":"2025-08-21T08:14:59.000Z","dependencies_parsed_at":"2024-02-16T11:23:50.934Z","dependency_job_id":"bc96fc00-5fd6-4481-8f6c-f9e132a8c4fa","html_url":"https://github.com/Arustinal/fluentogram","commit_stats":{"total_commits":35,"total_committers":4,"mean_commits":8.75,"dds":"0.17142857142857137","last_synced_commit":"947c428d0a4e91f548bcbf5a22a0e33b5965bcaf"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/Arustinal/fluentogram","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arustinal%2Ffluentogram","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arustinal%2Ffluentogram/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arustinal%2Ffluentogram/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arustinal%2Ffluentogram/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Arustinal","download_url":"https://codeload.github.com/Arustinal/fluentogram/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arustinal%2Ffluentogram/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32027234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aiogram","fluent","i18n"],"created_at":"2026-04-04T15:00:21.516Z","updated_at":"2026-04-20T00:01:45.680Z","avatar_url":"https://github.com/Arustinal.png","language":"Python","funding_links":[],"categories":["Зависимости/библиотеки"],"sub_categories":["Aiogram"],"readme":"# fluentogram\n\nfluentogram is easy way to use i18n (\u003ca href='https://projectfluent.org/'\u003eFluent\u003c/a\u003e) mechanism in any python app.\n\n## Features\n\n- Customizable storage: You can implement your own storage to save translates.\n- Fallback support: Automatic fallback to root locale when translations are missing.\n- Precompiled fluent messages using \u003ca href='https://github.com/django-ftl/fluent-compiler'\u003efluent_compiler\u003c/a\u003e makes formatting messages faster.\n- Dot access to messages: `translator.hello(name='Alex')`\n- Stub generator\n\n## Installation\n\n```bash\npip install fluentogram\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nfrom fluent_compiler.bundle import FluentBundle\nfrom fluentogram import FluentTranslator, TranslatorHub\n\n# Create translators for different locales\ntranslators = [\n    FluentTranslator(\n        \"en\",\n        translator=FluentBundle.from_string(\n            \"en-US\",\n            \"welcome = Welcome, { $username }!\\n\"\n            \"items-count = You have { $count } items\",\n        ),\n    ),\n]\n\n# Configure locale mapping with fallbacks\nlocales_map = {\n    \"en\": \"en\",\n}\n\n# Create the translator hub\nhub = TranslatorHub(locales_map, translators)\n\n# Get a translator for a specific locale\ntranslator = hub.get_translator_by_locale(\"en\")\n\n# Use translations\nprint(translator.get(\"welcome\", username=\"Alice\"))  # \"Welcome, Alice!\"\n```\n\n### Attribute-based Access\n\nFluentogram supports a convenient attribute-based syntax for accessing translations:\n\n```python\nprint(translator.welcome(username=\"Alice\"))        # \"Welcome, Alice!\"\nprint(translator.items.count(count=5))            # \"You have 5 items\"\n```\n\n### Stub generator with CLI\n\n#### Install with CLI dependencies\n\n```sh\npip install fluentogram[cli]\n```\n\n#### Run generator\n\n```sh\nfluentogram -f tests/assets/test.ftl -o test.pyi\n```\n\n## Storages\n\n### File storage\n\n```python\nfrom fluentogram import TranslatorHub\nfrom fluentogram.storage.file import FileStorage\n\n# Create FileStorage with custom path\nstorage = FileStorage(\"my_translations/{locale}/\")\n\nlocales_map = {\n    \"en\": \"en\",\n}\n\nhub = TranslatorHub(locales_map, storage=storage)\n\ntranslator = hub.get_translator_by_locale(\"en\")\n\nprint(translator.get(\"hello\"))  # Hello, world!\n```\n\n## fluentogram supports real-time translation updates using NATS KV storage:\n\nInstall:\n\n```sh\npip install fluentogram[nats]\n```\n\n```python\nimport asyncio\n\nfrom fluent_compiler.bundle import FluentBundle\nfrom nats.js.api import KeyValueConfig, StorageType\n\nfrom fluentogram import FluentTranslator, TranslatorHub\nfrom fluentogram.nats.storage import NatsKvStorage\n\n\nasync def main():\n    # Configure NATS KV storage\n    kv_config = KeyValueConfig(\n        bucket=\"fluentogram\",\n        storage=StorageType.FILE,\n    )\n\n    # Create NATS storage\n    storage = await NatsKvStorage.from_servers(\n        servers=[\"nats://localhost:4222\"],\n        kv_config=kv_config,\n    )\n\n    # Create translators\n    translators = [\n        FluentTranslator(\n            \"en\",\n            translator=FluentBundle.from_string(\n                \"en-US\",\n                \"greeting = Hello, { $name }!\",\n            ),\n        ),\n    ]\n\n    # Create hub with NATS storage\n    hub = TranslatorHub(\n        {\"en\": \"en\"},\n        translators,\n        storage=storage,\n    )\n\n    translator = hub.get_translator_by_locale(\"en\")\n    print(translator.get(\"greeting\", name=\"World\"))  # \"Hello, World!\"\n\n    # Update translation dynamically\n    await storage.update_translation(\"en\", \"greeting\", \"Hi there, { $name }!\")\n\n    # Wait for the update to propagate\n    await asyncio.sleep(1)\n\n    # Get updated translation\n    print(translator.get(\"greeting\", name=\"World\"))  # \"Hi there, World!\"\n\n    await storage.close()\n\n\nasyncio.run(main())\n```\n\n## Error Handling\n\nFluentogram provides comprehensive error handling:\n\n```python\nfrom fluentogram.exceptions import KeyNotFoundError, FormatError, RootTranslatorNotFoundError\n\ntry:\n    translator = hub.get_translator_by_locale(\"fr\")\n    result = translator.get(\"nonexistent-key\")\nexcept KeyNotFoundError as e:\n    print(f\"Translation key not found: {e.key}\")\nexcept RootTranslatorNotFoundError as e:\n    print(f\"Root locale translator missing: {e.root_locale}\")\nexcept FormatError as e:\n    print(f\"Formatting error for key {e.key}: {e.original_error}\")\n```\n\n# Inside of fluentogram\n\n### TranslatorHub\n\n`TranslatorHub` is unit of distribution TranslatorRunner's.\n\nInit:\n\n```python\ndef __init__(\n    self,\n    locales_map: dict[str, str | Iterable[str]],\n    translators: list[FluentTranslator],\n    root_locale: str = \"en\",\n) -\u003e None:\n```\n\n### Locales map \n\nThat's like a configuration map for \"Rollback\" feature. If you haven't configured translation for current locale - first in collection, \"Rollback\" will look to others locales' data and try to find a translation\n\nFor example:\n\n```python\nlocales_map = {\n    \"ua\": (\"ua\", \"de\", \"en\"),\n    \"de\": (\"de\", \"en\"),\n    \"en\": (\"en\",)\n}\n```\n\nLet's look at this example\n\nIf translator does not find a translation for `ua` locale in `ua` data, next stop is `de` data. If it's failed too - it will look to `en` translations.\n\n### Translators\n\nYou have 3 variants to use translators:\n\n- In memory\n\n```python\ntranslators = [\n    FluentTranslator(\n        \"en\",\n        translator=FluentBundle.from_string(\n            \"en-US\",\n            \"welcome = Welcome, { $username }!\\n\"\n            \"items-count = You have { $count } items\",\n        ),\n    ),\n]\nhub = TranslatorHub(locales_map, translators)\n```\n\n- From files using file storage\n- From NATS KV using storage\n\n[↑ Back to Storages](#storages)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FArustinal%2Ffluentogram","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FArustinal%2Ffluentogram","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FArustinal%2Ffluentogram/lists"}