FormatJS ships Python 3.12+ bindings for its Rust parsers and runtimes. Wheels use Python's stable ABI, so one cp312-abi3 wheel works on CPython 3.12 and newer for the same platform.

Packages

PackageRust backendUse it for
py_intlformatjs_intlCatalog lookup, locale negotiation, and fallback
icu_messageformatformatjs_icu_messageformatFormatting one ICU message
icu_messageformat_parserformatjs_icu_messageformat_parserParsing or printing ICU MessageFormat ASTs
icu_skeleton_parserformatjs_icu_skeleton_parserParsing ICU number and date/time skeletons

Install only the layer needed by the application:

python -m pip install py_intl

Format messages

from intl import Intl

intl = Intl(
    ["fr-CA", "fr"],
    "en",
    {
        "en": {},
        "fr": {"tasks": "{count, plural, one {# tâche} other {# tâches}}"},
    },
)

assert intl.locale == "fr"
assert intl.format_message("tasks", values={"count": 2}) == "2 tâches"

Use icu_messageformat when catalog lookup happens elsewhere:

from icu_messageformat import IcuMessageFormat

message = IcuMessageFormat(
    "{count, plural, one {# item} other {# items}}",
    locale="en",
)
assert message.format({"count": 2}) == "2 items"

Parse messages and skeletons

from icu_messageformat_parser import parse, print_ast
from icu_skeleton_parser import parse_number_skeleton

ast = parse("Hello, {name}!")
assert print_ast(ast) == "Hello, {name}!"

options = parse_number_skeleton("currency/USD compact-short")

Python APIs use snake_case. The py_intl distribution imports as intl; other distribution and import package names match. Hermetic cross-compiled wheels target macOS and manylinux 2.28 on arm64 and x86_64.