Home
Softono

Fluentogram

Open source MIT Python
104
Stars
15
Forks
2
Issues
1
Watchers
1 year
Last Commit

 About Fluentogram

A proper way to use an i18n mechanism with Aiogram3.

Platforms

Web Self-hosted

Languages

Python

Links

Need Help Installing Fluentogram?

We provide expert installation service for this software. Our team will install, configure, and secure Fluentogram on your server. plans start at just $30.

Fluentogram

View on GitHub

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

pip install fluentogram

Quick Start

Basic Usage

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:

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

pip install fluentogram[cli]

Run generator

fluentogram -f tests/assets/test.ftl -o test.pyi

Storages

File storage

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:

pip install fluentogram[nats]
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:

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:

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:

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
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