babel icon indicating copy to clipboard operation
babel copied to clipboard

Advice - just use .po files?

Open robertlagrant opened this issue 1 year ago • 2 comments

For the project I'm working on - a simple Litestar-implemented API - we are using Babel to manage translation responses.

For us, .pot, .po and .mo files are overkill. We commit .po files to source control, but then on build/push of our code we generate .mo files so they're available to the application on install. We have commands to create .pot files, apply them as a diff to our .po files, and then instantly delete the .pot files.

Is there a way of working that just involves using .po files? I'd love to be able to commit .po files to my repo, have them included in the build, and then be read and cached on startup. It would also make running tests and other scenarios much simpler to just have one file type.

Has anyone done this? Could I just do read_po() into Catalog files that I cache and read from?

robertlagrant avatar Sep 19 '24 19:09 robertlagrant

I actually bit the bullet and had a go at this myself.

class TranslatorFunction(Protocol):
    def __call__(self, message_id: str, locale: str) -> str:
        ...


def create_t(translations_path: Path) -> TranslatorFunction:
    """Returns a t function populated with all of the translations detected in the file system."""
    translations = {}

    for po_file_path in translations_path.glob("**/LC_MESSAGES/*.po"):
        po_file_relative_path = po_file_path.relative_to(translations_path)

        locale = po_file_relative_path.parts[0]
        domain = po_file_relative_path.parts[2].removesuffix(".po")

        po_file_contents = StringIO(Path(po_file_path).read_text())

        catalogue: Catalog = read_po(po_file_contents)
        catalogue.locale = locale
        catalogue.domain = domain

        translations[locale] = catalogue

    supported_locales = set(translations.keys())

    def gettext(message_id: str, locale: str) -> str:
        if locale not in supported_locales:
            raise ValueError(
                f'Unavailable locale "{locale}" requested. Available: {supported_locales}.'
            )

        return translations[locale].get(message_id).string

    return gettext


t = create_t(TRANSLATIONS_DIRECTORY)

robertlagrant avatar Sep 20 '24 10:09 robertlagrant

Nice! The only issue I see is the gettext implementation. It is possible that the message you are looking for is pluralized in which case you have to look at the plurals as well: https://github.com/python/cpython/blob/342e654b8eda24c68da64cc21bc9583e480d9e8e/Lib/gettext.py#L438-L447 The problem is that the plural method is part of the GNUTranslations class which you want to avoid. You could probably do somehing like this instead:

from gettext import c2py

plural_fn = c2py(catalog._plural_expr)

tomasr8 avatar Sep 21 '24 10:09 tomasr8

Can we close this? It was answered and there has been no activity for a few months, mixing questions and bugs is not the best. At least add the question label?

cc @akx (Apologies for the pings but I don't see any other people triaging)

StanFromIreland avatar Mar 04 '25 21:03 StanFromIreland