Browse documentation
DocsIntegrity

Metadata, provenance & plurals

Reference every non-text field in a translation unit and understand how status, history, context, plurals, and extensions survive each processing step.

01

The integrity payload around text

Data is more than a source/target pair. Unit metadata is divided by responsibility so a missing value stays distinguishable from an empty value and target-specific facts do not overwrite source-level facts until a target is deliberately selected.

Integrity map
Data├── plural: Plural | None├── meta: Meta├── status: TranslationStatus├── comments: list[Comment]│   └── origin: Origin | None├── previous_context: AdjacentContext | None├── next_context: AdjacentContext | None├── extensions: dict[str, str]└── targets[locale]: TargetData    ├── plural / meta / status / comments / extensions    └── tags: TargetTags | None
02

Meta field reference

FieldTypeMeaning
usage_count`intNone`
last_used`strNone`
first_used`strNone`
created`strNone`
updated`strNone`
max_length`intNone`
min_length`intNone`
extensionsdict[str, str]Extra metadata such as TMX change IDs or source attributes without a common field.

Timestamp fields are strings because source formats use different lexical conventions. Lokit preserves supplied values; applications that need chronological comparison should parse and normalize them at their boundary.

03

Comments, origins, and project history

A Comment has required context text plus optional timestamp, context_key, origin, and string extensions. Origin contains optional system, project, creator_id, and extensions. Lokit does not invent a separate project-history object: provenance/history is represented by the ordered comments, their timestamps and origins, document-level export_origin/export_timestamp, and source-specific extensions. TMX maps notes and comment-like properties into comments, creationid into creator_id, and x-project/x-system into the origin. PO maps translator/extracted comments and msgctxt; XLIFF maps notes. Preserve list order when an upstream tool treats it as history order.

04

Adjacent context and extensions

AdjacentContext holds optional unit_id, source, target, and extensions for the previous or next unit. TMX reads and writes the x-previous-* and x-next-* property families; database matching can use adjacent source strings for contextual ranking. extensions exists at document, unit, target, plural, metadata, comment, origin, and context levels. Keys and values are strings: copy unknown entries forward, namespace application-owned keys, and do not assume a key from one file type has meaning in every writer.

05

TranslationStatus normalization

The public values are new, draft, translated, reviewed, approved, rejected, and unknown.

Source/outputNormalization
TMX inputApproved aliases include approved, signed-off, and final; reviewed includes reviewed/review; translated includes translated/complete; draft includes draft, notapproved, not-approved, and unapproved. Unknown values remain unknown.
TMX outputA known value is written as an x-status property.
XLIFF inputfinal/signed-off → approved; translated/review-translation states → translated; review-adaptation/localization states → reviewed; new/needs-translation → new.
XLIFF 1.2 outputApproved → final; reviewed → needs-review-l10n; translated → translated; new → new; draft/rejected → needs-translation; unknown is omitted.
PO inputFuzzy → draft; a non-empty translation → translated; missing translation → new.

For a selected TargetData, a non-unknown target status overrides the base unit status. An unknown target status leaves the base value intact.

06

Plural field reference

FieldTypeMeaning
variantstrPlural source variant, such as Gettext msgid_plural.
count`intNone`
category`PluralCategoryNone`
extensionsdict[str, str]Source-specific identity such as the original Gettext plural index.

Data.plural describes the unit-level form. TargetData.plural can carry a locale-specific override; target splitting promotes that override into the resulting single-target Data.

07

How Gettext plurals are processed

PO parsing emits the index-0 form at the base unit ID and additional forms as unit_id[n]. Every form carries the same singular source and Plural.variant from msgid_plural; extensions["gettext_index"] preserves the original numeric index. When a Plural-Forms rule and locale allow one unambiguous CLDR category, Lokit also assigns it. PO export groups plural-marked units again, preferring the retained Gettext index, then a locale category mapping, then the [n] suffix. The .lokit format and PostgreSQL serialization preserve plural fields directly; other writers may write the unit text but do not promise reconstruction of a destination-native plural construct.

inspect_plurals.py
import lokit
document = lokit.parse.po(    "messages.fr.po",    source_locale="en-US",    target_locale="fr-FR",)
for unit_id, unit in document.data.items():    if unit.plural is not None:        print(            unit_id,            unit.plural.variant,            unit.plural.category,            unit.plural.extensions.get("gettext_index"),        )
08

Target overlay rules during splitting

A split document is a deep independent copy. The selected TargetData.text becomes Data.target; a known target status overrides the base status; target plural replaces base plural when present; non-None target Meta fields merge over base metadata and extension dictionaries merge by key. Non-empty target comments replace base comments. Target tags become the selected target side of Data.tags, while source tags remain. Data.targets is cleared so the result is an unambiguous single-target document.

09

Flat projection fields and precedence

TranslationRow is dict[str, str]; the available DictField values are source_language, target_language, source, target, domain, unit_id, source_locale, target_locale, status, resource, and project. Source language resolves from an explicit argument, then document metadata, then the base source locale. An explicit domain wins, otherwise Lokit reads extensions["domain"] and then extensions["property.domain"]. Project resolves from selected-target extensions, selected-target comment origins, unit extensions, then unit comment origins. Target status wins only when it is not unknown; resource comes from unit extensions. A flat projection intentionally omits nested metadata—use the model, .lokit, or database rows when those details must remain structured.

10

Preservation depends on the destination

DestinationIntegrity scope
.lokitRound-trips the complete documented model within v1 representational bounds.
PostgreSQLSerializes units, plurals, metadata, contexts, tags/parts, comments/origins, project/domain, and extensions into typed rows.
TMXRich subset: multilingual text, inline codes, statuses, notes/origin summaries, adjacent context, selected properties, usage count, creation/update/change metadata, and header fields.
XLIFFText, target status, inline codes, notes, original resource, unit/segment IDs, datatype, and whitespace hints; generic Meta/Origin fields have no universal XLIFF mapping.
PO / POTGettext context, comments, flags, references, headers, status semantics, and plural forms.
CSV / XLSXConfigured ID/source/target/status/comment columns; generic nested metadata and tags are not a tabular interchange contract.
JSON i18nNested keys and strings; generic metadata is outside the format.
HTML / IDML / OfficeText plus format-specific inline/layout references; regeneration preserves unrelated source-package content rather than encoding every common-model metadata field.

Conversion therefore preserves every fact the destination can represent, not an impossible union of all source-format features. Use .lokit or the database as the lossless intermediate when metadata must survive a multi-step pipeline.

11

Construct rich metadata explicitly

All values below use the stable public types. Empty optional fields remain distinguishable from absent fields, and custom source/application facts belong in the nearest extensions mapping.

rich_unit.py
from lokit.types import (    AdjacentContext,    Comment,    Data,    Meta,    Origin,    Plural,    PluralCategory,    TranslationStatus,)
unit = Data(    source="{count} file",    target="{count} fichiers",    plural=Plural(        variant="{count} files",        category=PluralCategory.OTHER,        extensions={"gettext_index": "1"},    ),    meta=Meta(usage_count=12, max_length=80),    status=TranslationStatus.REVIEWED,    comments=[        Comment(            context="Approved terminology",            timestamp="2026-08-10T10:30:00Z",            origin=Origin(                system="TMS",                project="desktop-app",                creator_id="reviewer-17",            ),        )    ],    previous_context=AdjacentContext(unit_id="files.heading"),    extensions={"domain": "storage"},)