https://github.com/Arustinal/fluentogram
A proper way to use an i18n mechanism with Aiogram3.
https://github.com/Arustinal/fluentogram
aiogram fluent i18n
Last synced: 2 months ago
JSON representation
A proper way to use an i18n mechanism with Aiogram3.
- Host: GitHub
- URL: https://github.com/Arustinal/fluentogram
- Owner: Arustinal
- License: mit
- Created: 2022-04-04T19:02:23.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2025-07-20T04:07:48.000Z (12 months ago)
- Last Synced: 2025-10-06T07:41:18.695Z (9 months ago)
- Topics: aiogram, fluent, i18n
- Language: Python
- Homepage:
- Size: 107 KB
- Stars: 97
- Watchers: 1
- Forks: 14
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesomeeverything - fluentogram: механизм локализации для aiogram
README
# fluentogram
fluentogram is easy way to use i18n (Fluent) mechanism in any python app.
## Features
- Customizable storage: You can implement your own storage to save translates.
- Fallback support: Automatic fallback to root locale when translations are missing.
- Precompiled fluent messages using fluent_compiler makes formatting messages faster.
- Dot access to messages: `translator.hello(name='Alex')`
- Stub generator
## Installation
```bash
pip install fluentogram
```
## Quick Start
### Basic Usage
```python
from fluent_compiler.bundle import FluentBundle
from fluentogram import FluentTranslator, TranslatorHub
# Create translators for different locales
translators = [
FluentTranslator(
"en",
translator=FluentBundle.from_string(
"en-US",
"welcome = Welcome, { $username }!\n"
"items-count = You have { $count } items",
),
),
]
# Configure locale mapping with fallbacks
locales_map = {
"en": "en",
}
# Create the translator hub
hub = TranslatorHub(locales_map, translators)
# Get a translator for a specific locale
translator = hub.get_translator_by_locale("en")
# Use translations
print(translator.get("welcome", username="Alice")) # "Welcome, Alice!"
```
### Attribute-based Access
Fluentogram supports a convenient attribute-based syntax for accessing translations:
```python
print(translator.welcome(username="Alice")) # "Welcome, Alice!"
print(translator.items.count(count=5)) # "You have 5 items"
```
### Stub generator with CLI
#### Install with CLI dependencies
```sh
pip install fluentogram[cli]
```
#### Run generator
```sh
fluentogram -f tests/assets/test.ftl -o test.pyi
```
## Storages
### File storage
```python
from fluentogram import TranslatorHub
from fluentogram.storage.file import FileStorage
# Create FileStorage with custom path
storage = FileStorage("my_translations/{locale}/")
locales_map = {
"en": "en",
}
hub = TranslatorHub(locales_map, storage=storage)
translator = hub.get_translator_by_locale("en")
print(translator.get("hello")) # Hello, world!
```
## fluentogram supports real-time translation updates using NATS KV storage:
Install:
```sh
pip install fluentogram[nats]
```
```python
import asyncio
from fluent_compiler.bundle import FluentBundle
from nats.js.api import KeyValueConfig, StorageType
from fluentogram import FluentTranslator, TranslatorHub
from fluentogram.nats.storage import NatsKvStorage
async def main():
# Configure NATS KV storage
kv_config = KeyValueConfig(
bucket="fluentogram",
storage=StorageType.FILE,
)
# Create NATS storage
storage = await NatsKvStorage.from_servers(
servers=["nats://localhost:4222"],
kv_config=kv_config,
)
# Create translators
translators = [
FluentTranslator(
"en",
translator=FluentBundle.from_string(
"en-US",
"greeting = Hello, { $name }!",
),
),
]
# Create hub with NATS storage
hub = TranslatorHub(
{"en": "en"},
translators,
storage=storage,
)
translator = hub.get_translator_by_locale("en")
print(translator.get("greeting", name="World")) # "Hello, World!"
# Update translation dynamically
await storage.update_translation("en", "greeting", "Hi there, { $name }!")
# Wait for the update to propagate
await asyncio.sleep(1)
# Get updated translation
print(translator.get("greeting", name="World")) # "Hi there, World!"
await storage.close()
asyncio.run(main())
```
## Error Handling
Fluentogram provides comprehensive error handling:
```python
from fluentogram.exceptions import KeyNotFoundError, FormatError, RootTranslatorNotFoundError
try:
translator = hub.get_translator_by_locale("fr")
result = translator.get("nonexistent-key")
except KeyNotFoundError as e:
print(f"Translation key not found: {e.key}")
except RootTranslatorNotFoundError as e:
print(f"Root locale translator missing: {e.root_locale}")
except FormatError as e:
print(f"Formatting error for key {e.key}: {e.original_error}")
```
# Inside of fluentogram
### TranslatorHub
`TranslatorHub` is unit of distribution TranslatorRunner's.
Init:
```python
def __init__(
self,
locales_map: dict[str, str | Iterable[str]],
translators: list[FluentTranslator],
root_locale: str = "en",
) -> None:
```
### Locales map
That'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
For example:
```python
locales_map = {
"ua": ("ua", "de", "en"),
"de": ("de", "en"),
"en": ("en",)
}
```
Let's look at this example
If 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.
### Translators
You have 3 variants to use translators:
- In memory
```python
translators = [
FluentTranslator(
"en",
translator=FluentBundle.from_string(
"en-US",
"welcome = Welcome, { $username }!\n"
"items-count = You have { $count } items",
),
),
]
hub = TranslatorHub(locales_map, translators)
```
- From files using file storage
- From NATS KV using storage
[↑ Back to Storages](#storages)