bacommon.langstr package¶
Language-agnostic complex strings – the LangStrSpec authoring model.
“LangStr” is the name for this whole generation of string handling
(formerly working-titled Lstr2); the legacy client translation class
keeps the Lstr name, which stays reserved for it to avoid ambiguity.
Within that generation the type split is semantic (see
strings-asset-migration.md D28 in ballistica-internal):
LangStrSpec(here) is the authoring spec form – a claim about a string carrying no guarantee that its asset-package is locally present (or even still exists). This is the currency for authoring surfaces and wire/model dataclasses; consuming ends verify/resolve before display.The native client
babase.LangStr(a later optimized C++ port of this proven model) represents a verified-local string – holding one implies it is displayable there. Its.specproperty projects back to this form (always valid); there is deliberately no public unverified->verified conversion.
The model lets us pass around minimal, language-independent representations of a complex string (substitutions, plurals, nesting) and resolve to a flat string in a particular language only at display – so one representation serves clients of any language.
- type bacommon.langstr.EncodedLangStr = list[str | int | EncodedLangStr]¶
- class bacommon.langstr.LangStrDir(apverid: str, tree: WrapperTree, prefix: str = '')[source]¶
Bases:
objectRuntime root/subdir accessor for a generated wrapper package.
- exception bacommon.langstr.LangStrError[source]¶
Bases:
ExceptionA malformed language-string or encode-context operation.
- class bacommon.langstr.LangStrSpec[source]¶
Bases:
IOMultiType[LangStrSpecTypeID]A deferred, language-agnostic complex string.
The base of a small multitype: a language-string is a
LangStrSpecResource(an asset-package string addressed by apverid + logical name – the common authored form), aLangStrSpecValue(a raw literal that needs no package), or aLangStrSpecResourceIndexed(the compact integer-addressed projection of a resource, for contexts that carry a package-index map). All forms take keyword substitutions whose values may themselves be language-strings, so aLangStrSpecis a recursive tree; it holds tokens, not text, and only decodes to a flat string in some particular locale at display time.Wire notes: the indexed form is the multitype default, so it alone serializes without a type tag (it is the space-sensitive form). Clients older than
LANGSTR_EXT_MIN_BUILDunderstand only tag-free resource values with flat subs; producers that know the client build must gate everything beyond that (nested subs and the value/indexed forms) on it.- classmethod get_default_type_id() LangStrSpecTypeID | None[source]¶
Return a type-id to be assumed when none is present.
By default, dataclassio errors when deserializing multitype data that contains no type-id value. Overriding this to return a type-id changes that behavior: data with no type-id present will be deserialized as the returned type, and instances of that type will be serialized without a type-id value. This both saves a bit of space and allows ‘upgrading’ an existing regular dataclass to a multitype - simply designate the original dataclass type as the default and old serialized data will remain loadable (and data for the default type will remain loadable by old code).
Be aware of the following, however:
Once serialized data exists anywhere without type-id values, the default type-id must never be changed or removed; doing so would cause that existing data to be silently reinterpreted as some other type (or to error).
A missing type-id normally acts as a sanity check when deserializing; defining a default effectively disables that check, meaning malformed data may deserialize successfully as the default type instead of erroring.
- classmethod get_type(type_id: LangStrSpecTypeID) type[LangStrSpec][source]¶
Return the subclass for each of our type-ids.
- classmethod get_type_id() LangStrSpecTypeID[source]¶
Return the type-id for this subclass.
- classmethod get_type_id_storage_name() str[source]¶
Return the key used to store type id in serialized data.
The default is a short obscure value so that it is unlikely to conflict with members of individual type attrs, but in some cases one might prefer to serialize it to something simpler like ‘type’ by overriding this call. One just needs to make sure that no encompassed types serialize anything to that same name themself (dataclassio will error if they do).
- class bacommon.langstr.LangStrSpecResource(apverid: str, name: str, subs: dict[str, str | int | ~bacommon.langstr._core.LangStrSpec]=<factory>)[source]¶
Bases:
LangStrSpecAn asset-package string: the common authored
LangStrSpecform.subsmaps each substitution keyword to its value – a flatstr/intor a nestedLangStrSpec. A no-arg string has emptysubs. The value carries its own exactapverid(so an encode context can discover the package union from the values themselves) and the string’s logicalname(mapped to its integer index at encode time).- classmethod get_type_id() LangStrSpecTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.langstr.LangStrSpecResourceIndexed(pkg: int, index: int, subs: list[str | int | LangStrSpec] = <factory>)[source]¶
Bases:
LangStrSpecThe compact integer-addressed projection of a resource string.
Usable only against a context carrying the
{pkg_int: apverid}package-index map (and package structures for positional-sub ordering); seeLanguageStringDecodeContext. Substitutions are positional here (canonical param order), matching theEncodedLangStrchunk model. This form is the multitype default, so it serializes without a type tag.- classmethod get_type_id() LangStrSpecTypeID[source]¶
Return the type-id for this subclass.
- subs: list[str | int | LangStrSpec]¶
- class bacommon.langstr.LangStrSpecTypeID(*values)[source]¶
Bases:
EnumType IDs for the
LangStrSpecmultitype’s forms.- RESOURCE = 'r'¶
- RESOURCE_INDEXED = 'i'¶
- VALUE = 'v'¶
- class bacommon.langstr.LangStrSpecValue(value: str, subs: dict[str, str | int | ~bacommon.langstr._core.LangStrSpec]=<factory>)[source]¶
Bases:
LangStrSpecA raw literal string value needing no asset package.
For server-generated dynamic text (player names, pre-formatted numbers, etc.) that rides a
LangStrSpec-shaped slot without a package entry. The value is locale-independent;subsare substituted into{name}tokens exactly like a plain resource value (nested language-strings allowed).- classmethod get_type_id() LangStrSpecTypeID[source]¶
Return the type-id for this subclass.
- class bacommon.langstr.LanguageStringDecodeContext(package_index_map: dict[int, str], structures: dict[str, PackageStructure], language: dict[str, dict[str, str | StringSelector]], locale: Locale)[source]¶
Bases:
objectDecodes chunks into flat strings for one target locale.
Holds the
{pkg_int: apverid}map (from the encoder), the package structures, and the per-apverid string values for a single locale.decode()resolves a chunk (recursively rendering nested values) viabacommon.loctext.evaluate().- decode(encoded: EncodedLangStr) str[source]¶
Resolve a chunk to a flat string in this context’s locale.
Fail-visible: any structural problem yields an
LANGSTR_ERROR:…sentinel (and a logged warning) rather than crashing the caller.
- decode_value(lstr: LangStrSpec) str[source]¶
Resolve any language-string form to flat text in this locale.
The tolerant all-forms counterpart of
decode(): handlesLangStrSpecResourceIndexed(via this context’s package-index map + structures),LangStrSpecValue(self-contained), and plainLangStrSpecResource(by name, for legacy/mixed payloads). Fail-visible like everything else on the decode side.
- to_resource(lstr: LangStrSpec, _depth: int = 0) LangStrSpec[source]¶
Convert integer-indexed nodes back to the resource form.
The inverse of
LanguageStringEncodeContext.to_indexed, for consumers that ingest indexed wire values but hold some of them in the self-describing name form (e.g. deferred client effects that outlive their containing payload’s package-index map). Returns new objects (resource/value nodes are rebuilt with converted subs); raisesLangStrErrorfor indices unknown to this context.
- class bacommon.langstr.LanguageStringEncodeContext(lstrs: list[LangStrSpec], structures: dict[str, PackageStructure])[source]¶
Bases:
objectEncodes
LangStrSpecvalues into minimal language-free chunks.Built from the batch of values to send: it computes the union of apverids they reference (recursively – nested values know their own apverid) and assigns each a stable integer index.
encode()then emits[pkg_int, str_int, …subs];package_index_mapis the only mapping the consumer needs (string indices resolve from the content-pinned apverid itself).- encode(lstr: LangStrSpec) EncodedLangStr[source]¶
Encode one value (recursively) into a minimal chunk.
- to_indexed(lstr: LangStrSpec) LangStrSpec[source]¶
Convert a resource/value tree to its integer-indexed form.
Resource nodes become
LangStrSpecResourceIndexed(with positional subs in canonical param order); literalLangStrSpecValuenodes pass through (with their nested subs converted). New objects are returned; the input tree is never mutated. RaisesLangStrErrorloudly for packages/strings unknown to this context (authoring-side errors) or already-indexed input.
- class bacommon.langstr.LanguageStringNameDecodeContext(language: dict[str, dict[str, str | StringSelector]], locale: Locale)[source]¶
Bases:
objectDecodes
LangStrSpecvalues directly, by name, for one locale.The name-based counterpart to
LanguageStringDecodeContext: it resolves an in-memoryLangStrSpec(carrying itsapverid, stringname, and keywordsubs) straight against per-apverid per-locale values – no integer indices, package-index-map, orPackageStructureneeded, since the subs are self-describing keyword->value pairs. This is the client’s primary path: resolve the referenced packages, gather their per-locale values, then decode eachLangStrSpecin the client’s locale.Fail-visible like
LanguageStringDecodeContext– any structural problem yields anLANGSTR_ERROR:…sentinel (and a logged warning) rather than crashing the caller.- decode(lstr: LangStrSpec) str[source]¶
Resolve a
LangStrSpecto a flat string in this locale.Fail-visible: any structural problem yields an
LANGSTR_ERROR:…sentinel (and a logged warning) rather than crashing the caller.
- class bacommon.langstr.PackageDef(apverid: str, strings: tuple[StringDef, ...])[source]¶
Bases:
objectLanguage-free definition of one asset-package-version’s strings.
The shared source the encode/decode
PackageStructureand the type-safe wrapper codegen both derive from (in the real system, from an apverid’s resolved listing; in tests, hand-built).
- class bacommon.langstr.PackageStructure(apverid: str, strings: dict[str, tuple[str, ...]])[source]¶
Bases:
objectLanguage-free structure of one asset-package-version.
Maps string names <-> integer indices (assigned in canonical sorted-name order so both ends agree without shipping the mapping) and holds each string’s ordered substitution-keyword list. Carries no translations – encoding needs only this.
- apverid¶
stringsmaps each logical name to its substitution keywords (()for a no-arg string). Order of the passed keywords is ignored: positional-substitution order is canonically alphabetical, enforced here so producer- and consumer-derived structures can’t disagree on it.
- classmethod from_def(pkgdef: PackageDef) PackageStructure[source]¶
Build the encode/decode structure from a package definition.
- classmethod from_language_values(apverid: str, values: dict[str, str | StringSelector]) PackageStructure[source]¶
Derive the structure from one locale’s complete value set.
The consumer-side counterpart of
from_def(): string indices come from the canonical sorted-name order (the key set is identical across locales by construction – seecomplete_locale_values) and each string’s substitution keywords from its own value viabacommon.loctext.substitution_names(). Both ends canonicalize param order alphabetically, so a structure derived here agrees with the producer’s brief-derived one; producer tests lock that agreement.
- class bacommon.langstr.StringDef(path: str, params: tuple[tuple[str, str], ...] = (), docs: str = '', english: str = '')[source]¶
Bases:
objectOne string’s language-free definition.
paramsis the ordered list of(keyword, kind)where kind is'text'(a text sub ->str | LangStrSpec) or'count'(the plural pivot ->int);()for a no-arg string. The canonical ordering (sorted keyword) is what fixes the positional substitution order.docs(author usage docs) andenglish(an English preview of the rendered text) are optional docstring material for wrapper codegen only – neither participates in the encode/decode structure.
- class bacommon.langstr.WrapParams(min_lines: int = 1, max_lines: int | None = None, max_chars_per_line: int | None = None)[source]¶
Bases:
objectConstraints for splitting a text value into lines client-side.
Mirrors the engine’s simple equal-width line splitter (
babase.split_text_into_lines()): text is broken only at valid line-break opportunities, using the fewest lines that keep every line withinmax_chars_per_line(when provided) while staying betweenmin_linesandmax_lines(Nonemeans unlimited), with line lengths balanced within that count. Somax_chars_per_linealone gives basic wrapping andmin_linesalone gives an exact line count. Constraints are best-effort.Default to pinning an exact line count: set
min_linesto the layout’s designed count and leavemax_chars_per_lineunset. Amax_chars_per_line-driven wrap yields a per-locale varying line count (translation lengths differ), which reads as broken in layouts designed around a specific count — and every legacy-converted string is such a layout, since the legacy pipeline hand-baked newlines at fixed counts (see D21 in the strings-asset-migration initiative). Reservemax_chars_per_linefor surfaces explicitly designed to tolerate a variable number of lines.Per decision D-t these are definition-time presentation hints: a string definition carries them optionally, they ride each locale blob, and evaluation applies them automatically. They are locale-invariant, and width-driven layout consumers may ignore them (they are a fallback presentation default).
- bacommon.langstr.collect_apverids(lstr: LangStrSpec, acc: set[str]) None[source]¶
Gather every asset-package-version a language-string tree references into
acc.Indexed nodes resolve against an out-of-band context so they contribute no apverids themselves, but their substitution values are still walked (a resource-form node can appear anywhere in a mixed tree).
Note to implementers: keep this a module-level function; a self-recursive closure would create a reference cycle (function -> closure cell -> function) at every call site, adding cyclic-gc pressure the engine works hard to avoid.
- bacommon.langstr.contains_resource_form(lstr: LangStrSpec) bool[source]¶
Return whether a language-string tree contains any full resource-form (non-indexed) node.
Used by consumers verifying that a wire payload claiming the integer-indexed form really is fully indexed (a resource-form leak means some producer path skipped indexing).
- bacommon.langstr.package_structure(apverid: str, tree: WrapperTree) PackageStructure[source]¶
Build a
PackageStructurefrom a wrapper’s runtime_TREE.Flattens the nested tree into the
{logical-path: param-keywords}map the encode/decode contexts need – so a consumer of a vendored package just passesmodule.APVERID, module._TREE(both module-level).
- bacommon.langstr.parse_language_blob(text: str) dict[str, str | StringSelector][source]¶
Parse a canonical language blob into a
{name: value}map.The exact inverse of
serialize_language_blob(): reads the top-levelstringsobject, turning each value back into astr(plain) or aStringSelector(a dict). A blob with nostringskey (e.g. a legacy-only package) yields an empty map; malformed values are skipped (fail-soft on the consumer side).
- bacommon.langstr.serialize_language_blob(values: dict[str, str | StringSelector], wraps: dict[str, WrapParams] | None = None) str[source]¶
Serialize a per-locale value map to the canonical language blob.
valuesmaps each string’s logical name to its value – a plainstror aStringSelector.wrapsoptionally maps names to their definition-timeWrapParams(decision D-t); a wrapped entry is emitted as a{'v': value, 'w': wrap}carrier dict (which pre-wrap clients skip fail-soft). Output is deterministic (sorted keys, fixed formatting) for cache stability and diffability.