Source code for cadrumo.domain.calculations.registry.modelo_localization

"""Canonical locale-key identity and scalar resolution for Modelo schemas."""

from __future__ import annotations

import base64
import re
from collections.abc import Callable, Mapping
from enum import StrEnum
from typing import Final, Literal, cast

from ....core.external_constants import UTF_8_ENCODING
from ....core.i18n.render import MissingTranslationError, lookup_translation
from ....core.type_adapters import OBJECT_TUPLE_ADAPTER
from ._toml_helpers import as_toml_table as _as_toml_table
from .ids import RevisionId


[docs] class ModeloLocalizationFieldKind(StrEnum): """Which localizable text a modelo locale key addresses.""" LABEL = "label" HELP = "help" TITLE = "title" OFFICIAL_NAME = "official_name"
"""Every localizable field.""" CasillaLocalizationField = Literal[ ModeloLocalizationFieldKind.LABEL, ModeloLocalizationFieldKind.HELP, ] """The fields a CASILLA carries, which are only its label and its help text. A narrowing written out twice before this existed. A casilla has no title and no official name -- those belong to the modelo and its revision -- so keeping this narrow stops a caller asking for a casilla key that can never resolve.""" _PLAIN_SEGMENT: Final = re.compile(r"^[A-Za-z0-9_-]+$") _ENCODED_PREFIX: Final[str] = "x-" _SOURCE_LOCALE: Final[str] = "es"
[docs] def encode_modelo_locale_segment(value: str) -> str: """Encode one dynamic identity as an injective dotted-key segment.""" if _PLAIN_SEGMENT.fullmatch(value) and not value.startswith(_ENCODED_PREFIX): return value encoded = base64.b32hexencode(value.encode(UTF_8_ENCODING)).decode("ascii").rstrip("=").lower() return f"{_ENCODED_PREFIX}{encoded}"
[docs] def modelo_locale_key(modelo_id: str, field: Literal["title", "official_name"]) -> str: """Derive a Modelo-level presentation key.""" return f"modelo.schema.{encode_modelo_locale_segment(modelo_id)}.field.{field}"
[docs] def revision_locale_key( modelo_id: str, revision_id: RevisionId, field: Literal[ModeloLocalizationFieldKind.LABEL] = ModeloLocalizationFieldKind.LABEL, ) -> str: """Derive a revision-level presentation key.""" return ( f"modelo.schema.{encode_modelo_locale_segment(modelo_id)}.revision." f"{encode_modelo_locale_segment(revision_id)}.field.{field}" )
[docs] def construct_locale_key( modelo_id: str, revision_id: RevisionId, construct_id: str, field: Literal["title"] = "title", ) -> str: """Derive the edition-scoped presentation key for one construct.""" return ( f"modelo.schema.{encode_modelo_locale_segment(modelo_id)}.revision." f"{encode_modelo_locale_segment(revision_id)}.construct." f"{encode_modelo_locale_segment(construct_id)}.field.{field}" )
[docs] def construct_lineage_locale_key( modelo_id: str, construct_id: str, field: Literal["title"] = "title", ) -> str: """Derive the modelo-wide key a construct's title shares across the editions that keep its id. A construct id is the identity keyed-family inheritance carries from one edition to the next, so it plays the role ``continuidad_id`` plays for a casilla: the tier below the edition key where text every edition agrees on is stored once. """ return ( f"modelo.schema.{encode_modelo_locale_segment(modelo_id)}.construct." f"{encode_modelo_locale_segment(construct_id)}.field.{field}" )
[docs] def casilla_occurrence_locale_key( modelo_id: str, revision_id: RevisionId, casilla_id: str, field: CasillaLocalizationField, ) -> str: """Derive the exact revision-occurrence key for one casilla field.""" return ( f"modelo.schema.{encode_modelo_locale_segment(modelo_id)}.revision." f"{encode_modelo_locale_segment(revision_id)}.casilla." f"{encode_modelo_locale_segment(casilla_id)}.{field}" )
[docs] def casilla_continuity_locale_key( modelo_id: str, continuidad_id: str, field: CasillaLocalizationField, ) -> str: """Derive the stable continuity key for one grounded casilla field.""" return ( f"modelo.schema.{encode_modelo_locale_segment(modelo_id)}.casilla.continuidad." f"{encode_modelo_locale_segment(continuidad_id)}.{field}" )
[docs] def casilla_alias_locale_key( modelo_id: str, revision_id: RevisionId, casilla_id: str, alias_id: str, field: Literal[ModeloLocalizationFieldKind.LABEL] = ModeloLocalizationFieldKind.LABEL, ) -> str: """Derive the presentation key for one casilla alias occurrence.""" return ( f"modelo.schema.{encode_modelo_locale_segment(modelo_id)}.revision." f"{encode_modelo_locale_segment(revision_id)}.casilla." f"{encode_modelo_locale_segment(casilla_id)}.alias." f"{encode_modelo_locale_segment(alias_id)}.{field}" )
def _passthrough_localization_row(raw: object) -> dict[str, object]: """Carry malformed locale-owned rows through to the schema validator.""" if isinstance(raw, Mapping): return dict(cast(Mapping[str, object], raw)) return {"value": raw} def _localised_casilla_aliases( raw_aliases: object, *, modelo_id: str, revision_id: RevisionId, casilla_id: str, ) -> tuple[dict[str, object], ...] | None: """Return one casilla's aliases with derived locale keys, or ``None`` if absent.""" aliases_array = as_toml_array(raw_aliases) if aliases_array is None: return None aliases: list[dict[str, object]] = [] for alias_index, raw_alias in enumerate(aliases_array): alias = _as_toml_table(raw_alias) if alias is None: aliases.append(_passthrough_localization_row(raw_alias)) continue aliases.append( { **alias, "localization_key": casilla_alias_locale_key( modelo_id, revision_id, casilla_id, str(alias_index), ), }, ) return tuple(aliases) def _localised_casilla(raw_casilla: object, *, modelo_id: str, revision_id: RevisionId) -> dict[str, object]: """Return one casilla with its derived locale keys attached.""" casilla = _as_toml_table(raw_casilla) if casilla is None: return _passthrough_localization_row(raw_casilla) casilla_id = casilla.get("id") if not isinstance(casilla_id, str): return dict(casilla) keys = [casilla_occurrence_locale_key(modelo_id, revision_id, casilla_id, ModeloLocalizationFieldKind.LABEL)] continuidad_id = casilla.get("continuidad_id") if isinstance(continuidad_id, str): keys.append(casilla_continuity_locale_key(modelo_id, continuidad_id, ModeloLocalizationFieldKind.LABEL)) payload: dict[str, object] = {**casilla, "localization_keys": tuple(keys)} aliases = _localised_casilla_aliases( casilla.get("aliases"), modelo_id=modelo_id, revision_id=revision_id, casilla_id=casilla_id, ) if aliases is not None: payload["aliases"] = aliases return payload def _localised_construct(raw_construct: object, *, modelo_id: str, revision_id: RevisionId) -> dict[str, object]: """Return one construct with its derived locale key attached.""" construct = _as_toml_table(raw_construct) if construct is None: return _passthrough_localization_row(raw_construct) construct_id = construct.get("id") if not isinstance(construct_id, str): return dict(construct) return { **construct, "localization_keys": ( construct_locale_key(modelo_id, revision_id, construct_id), construct_lineage_locale_key(modelo_id, construct_id), ), }
[docs] def as_toml_array(value: object) -> tuple[object, ...] | None: """Narrow a frozen TOML array to object entries, or return ``None``.""" if not isinstance(value, tuple): return None return OBJECT_TUPLE_ADAPTER.validate_python(value)
[docs] def enroll_revision_localization( *, modelo_id: str, revision_id: RevisionId, raw_revision: Mapping[str, object], ) -> dict[str, object]: """Attach derived locale identities without copying presentation values.""" payload: dict[str, object] = { "id": revision_id, **raw_revision, "localization_key": revision_locale_key(modelo_id, revision_id), } raw_casillas = as_toml_array(raw_revision.get("casillas")) if raw_casillas is not None: payload["casillas"] = tuple( _localised_casilla(raw_casilla, modelo_id=modelo_id, revision_id=revision_id) for raw_casilla in raw_casillas ) raw_constructs = as_toml_array(raw_revision.get("constructs")) if raw_constructs is not None: payload["constructs"] = tuple( _localised_construct(raw_construct, modelo_id=modelo_id, revision_id=revision_id) for raw_construct in raw_constructs ) return payload
[docs] def resolve_modelo_localization( keys: tuple[str, ...], *, locale: str, year: int | None = None, ) -> str | None: """Resolve exact/variant keys, then continuity, with Spanish fallback. ``keys`` is ordered from most-specific to least-specific. The requested locale is exhausted before the same identity chain is resolved through the mandatory Spanish source. No non-Spanish locale can become another non-Spanish locale's fallback. Resolution advances on the absence of a VALUE, never on the absence of a key. That distinction is the whole reason the less-specific tiers can fire: the locale scaffold emits an occurrence key for every casilla in every revision, null until translated, and exempts those nulls from the untranslated-string honesty ratchet. So the first key in the chain always exists. A chain that stopped at key EXISTENCE therefore stopped at index zero every time, for every casilla in every revision, and the continuity tier below it was written, scaffolded and never once consulted. The stop it used to perform was meant to let a revision deliberately blank an inherited label, but the catalogue cannot express that intent: an explicit null and a scaffolded-but-untranslated null are the same bytes, so the stop could only ever suppress translations nobody chose to suppress. Restoring the override needs a way to say "blanked on purpose" in the data, not a stop that fires on every untranslated key. A requested locale is read only down to the tier at which Spanish resolves. Catalogues store a value at the least specific key that yields the same text, so a Spanish value on a more specific key marks text that differs from the lineage-wide one; a translation stored below that tier translates the other text, and serving it would render a different meaning. Past that barrier the Spanish value itself is returned. """ served = _served_modelo_localization(keys, locale=locale) return None if served is None else _format_year(served[2], year)
[docs] def modelo_localization_source( keys: tuple[str, ...], *, locale: str, lookup: LocalizationLookup | None = None, ) -> tuple[str, str] | None: """Return the ``(key, locale)`` that serves a chain, or ``None`` when nothing does. This is the selection :func:`resolve_modelo_localization` renders, exposed for consumers that report which catalogue coordinate supplied a text. ``lookup`` replaces the shared catalogue read, so catalogue tooling can evaluate a proposed catalogue with exactly the runtime selection rule. A translation renders the Spanish source, so it is read only at the tier where Spanish text resolves or a more specific one. A chain with no Spanish text has nothing to translate and is served by no locale. """ served = _served_modelo_localization(keys, locale=locale, lookup=lookup) return None if served is None else (served[0], served[1])
def _served_modelo_localization( keys: tuple[str, ...], *, locale: str, lookup: LocalizationLookup | None = None, ) -> tuple[str, str, str] | None: """Return the ``(key, locale, value)`` that serves a chain, or ``None``. Carries the VALUE the selection already read. Selecting a tier means reading it, and the renderer then looked the very same coordinate up a second time to obtain what the selection had just discarded -- one wasted catalogue read per resolved text, which is every label in the registry. """ read = _catalogue_lookup if lookup is None else lookup spanish_tier: int | None = None spanish_value: str | None = None for index, key in enumerate(keys): candidate = read(key, _SOURCE_LOCALE) if candidate is not None: spanish_tier, spanish_value = index, candidate break if spanish_tier is None or spanish_value is None: return None if locale != _SOURCE_LOCALE: for key in keys[: spanish_tier + 1]: translated = read(key, locale) if translated is not None: return key, locale, translated return keys[spanish_tier], _SOURCE_LOCALE, spanish_value type LocalizationLookup = Callable[[str, str], str | None] """Read one authored value as ``lookup(key, locale)``; ``None`` means no text.""" def _catalogue_lookup(key: str, locale: str) -> str | None: return lookup_translation(key, locale=locale) def _format_year(value: str, year: int | None) -> str: """Substitute the ``{year}`` placeholder when a year is supplied.""" return value.format(year=year) if year is not None and "{year}" in value else value
[docs] def require_modelo_localization( keys: tuple[str, ...], *, locale: str, year: int | None = None, ) -> str: """Resolve a label that must exist, refusing an untranslated identity chain. The same resolution order as :func:`resolve_modelo_localization`; the difference is the contract on absence. A construct title, official modelo name, or revision member label has no honest empty rendering, so an unresolved chain is a :class:`MissingTranslationError` rather than ``None`` for the caller to interpret. """ resolved = resolve_modelo_localization(keys, locale=locale, year=year) if resolved is None: raise MissingTranslationError(key=keys[0], locale=_SOURCE_LOCALE) return resolved
__all__ = [ "CasillaLocalizationField", "LocalizationLookup", "ModeloLocalizationFieldKind", "casilla_alias_locale_key", "casilla_continuity_locale_key", "casilla_occurrence_locale_key", "construct_lineage_locale_key", "construct_locale_key", "encode_modelo_locale_segment", "modelo_locale_key", "modelo_localization_source", "require_modelo_localization", "resolve_modelo_localization", "revision_locale_key", ]