Skip to content

Translation bundles

_interpretation_bundle

Locale translation bundles: schema, loader, and en-US derivation.

Interpretation text is data, not code. Each dataset family ships one JSON bundle per locale under <family package>/locales/<dataset>/<locale>.json with this shape::

{
    "schema_version": 1,
    "locale": "fr-FR",
    "templates": { "<name>": "<format string>", ... },
    "vocab":     { "<group>": { "<key>": "<translated string>", ... }, ... }
}

The dataset builders stay in code and keep deriving structure (lookup keys, keyword tokens, the set and order of signs/stems/cards) from fortune-telling-core. A bundle only supplies the translatable templates and vocab strings; the builder joins them with the structural data. templates and vocab are both optional and may be empty (prose datasets keep all their text in vocab).

en-GB, fr-FR, and ja-JP are authored bundles. en-US is derived in code from the en-GB bundle by :func:americanize (a British -> American spelling normalization), so it never drifts from en-GB.

SCHEMA_VERSION module-attribute

SCHEMA_VERSION = 1

LocaleBundle dataclass

LocaleBundle(locale: str, templates: Mapping[str, str], vocab: Mapping[str, Mapping[str, str]])

Parsed, validated translation bundle for one dataset and locale.

locale instance-attribute

locale: str

templates instance-attribute

templates: Mapping[str, str]

vocab instance-attribute

vocab: Mapping[str, Mapping[str, str]]

template

template(name: str) -> str

Return a named format string, or raise if the bundle omits it.

Source code in src/fortune_telling_core/_interpretation_bundle.py
def template(self, name: str) -> str:
    """Return a named format string, or raise if the bundle omits it."""

    try:
        return self.templates[name]
    except KeyError:
        raise ValidationError(f"bundle {self.locale!r} is missing template {name!r}") from None

group

group(name: str) -> Mapping[str, str]

Return a named vocabulary group, or raise if the bundle omits it.

Source code in src/fortune_telling_core/_interpretation_bundle.py
def group(self, name: str) -> Mapping[str, str]:
    """Return a named vocabulary group, or raise if the bundle omits it."""

    try:
        return self.vocab[name]
    except KeyError:
        raise ValidationError(
            f"bundle {self.locale!r} is missing vocab group {name!r}"
        ) from None

load_bundle

load_bundle(anchor: str, dataset: str, locale: str) -> LocaleBundle

Load and validate a locale bundle shipped as package data.

Parameters:

Name Type Description Default
anchor str

Import path of the package that owns the locales directory, normally the calling module's __package__.

required
dataset str

Dataset family folder under locales (e.g. "signs").

required
locale str

Locale tag and file stem (e.g. "fr-FR").

required

Returns:

Type Description
LocaleBundle

The parsed bundle.

Raises:

Type Description
ValidationError

If the bundle is missing or malformed.

Source code in src/fortune_telling_core/_interpretation_bundle.py
def load_bundle(anchor: str, dataset: str, locale: str) -> LocaleBundle:
    """Load and validate a locale bundle shipped as package data.

    Args:
        anchor: Import path of the package that owns the ``locales`` directory,
            normally the calling module's ``__package__``.
        dataset: Dataset family folder under ``locales`` (e.g. ``"signs"``).
        locale: Locale tag and file stem (e.g. ``"fr-FR"``).

    Returns:
        The parsed bundle.

    Raises:
        ValidationError: If the bundle is missing or malformed.
    """

    resource = files(anchor).joinpath("locales", dataset, f"{locale}.json")
    try:
        raw = json.loads(resource.read_text(encoding="utf-8"))
    except FileNotFoundError:
        raise ValidationError(
            f"no interpretation bundle for dataset {dataset!r} locale {locale!r}"
        ) from None
    return _parse(raw, dataset, locale)

americanize

americanize(bundle: LocaleBundle) -> LocaleBundle

Return an en-US bundle from an en-GB one via spelling normalization.

Source code in src/fortune_telling_core/_interpretation_bundle.py
def americanize(bundle: LocaleBundle) -> LocaleBundle:
    """Return an ``en-US`` bundle from an ``en-GB`` one via spelling normalization."""

    def fix(text: str) -> str:
        return _AMERICAN_PATTERN.sub(lambda m: _BRITISH_TO_AMERICAN[m.group(0)], text)

    return LocaleBundle(
        locale="en-US",
        templates={name: fix(value) for name, value in bundle.templates.items()},
        vocab={
            group: {key: fix(value) for key, value in items.items()}
            for group, items in bundle.vocab.items()
        },
    )

available_locales

available_locales(anchor: str, dataset: str) -> tuple[str, ...]

Return the sorted locale tags that have a bundle file for dataset.

Source code in src/fortune_telling_core/_interpretation_bundle.py
def available_locales(anchor: str, dataset: str) -> tuple[str, ...]:
    """Return the sorted locale tags that have a bundle file for ``dataset``."""

    directory = files(anchor).joinpath("locales", dataset)
    return tuple(
        sorted(
            item.name.removesuffix(".json")
            for item in directory.iterdir()
            if item.name.endswith(".json")
        )
    )

build_all

build_all(anchor: str, dataset: str, builder: Callable[[str, LocaleBundle], T]) -> dict[str, T]

Build a dataset for every authored bundle locale, plus a derived en-US.

Discovers all locales/<dataset>/*.json bundles, builds each with builder(locale, bundle), and adds a derived en-US from the en-GB bundle when no en-US bundle is authored. Adding a locale is therefore a matter of adding a bundle file.

Source code in src/fortune_telling_core/_interpretation_bundle.py
def build_all[T](
    anchor: str,
    dataset: str,
    builder: Callable[[str, LocaleBundle], T],
) -> dict[str, T]:
    """Build a dataset for every authored bundle locale, plus a derived en-US.

    Discovers all ``locales/<dataset>/*.json`` bundles, builds each with
    ``builder(locale, bundle)``, and adds a derived ``en-US`` from the ``en-GB``
    bundle when no ``en-US`` bundle is authored. Adding a locale is therefore a
    matter of adding a bundle file.
    """

    datasets: dict[str, T] = {}
    en_gb: LocaleBundle | None = None
    for locale in available_locales(anchor, dataset):
        bundle = load_bundle(anchor, dataset, locale)
        datasets[locale] = builder(locale, bundle)
        if locale == "en-GB":
            en_gb = bundle
    if en_gb is not None and "en-US" not in datasets:
        datasets["en-US"] = builder("en-US", americanize(en_gb))
    return datasets