Skip to content

Arustinal/fluentogram

Repository files navigation

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

About

A proper way to use an i18n mechanism with Aiogram3.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 12