Skip to main content
CLIFF — the Contextual Localization Integrated File Format

cliff-python

Python parser, serializer, validator and converter for CLIFF 1.0 / 1.1 — the Contextual Localization Integrated File Format.

PyPI Python versions License CLIFF 1.0 | 1.1


CLIFF keeps translation context — namespace, clan, group path, entry id, type, emotion, width — next to every string, in a line-oriented format that stays cheap for translators and LLMs alike. cliff-python is the reference implementation of that format for Python: one typed data model, one parser, and every converter a localization pipeline needs.

Parse Strict mode rejects exactly what the grammar rejects; tolerant mode applies the seven documented repairs of Appendix C and reports each one.
Serialize Canonical output that round-trips, preserving the spec version the document declared.
Validate Required fields, closed vocabularies, status rules, duplicate ids, ICU brace balance, layout, display width, and style.
Convert JSON, YAML, CSV, PO, XLIFF, Fluent, Android strings.xml, iOS Localizable.strings — in both directions.
Typed Ships py.typed; the public API is annotated and checked with mypy.

The parser, serializer, validator and converters target CLIFF 1.1 and are checked against the normative examples of both 1.0 and 1.1 plus the cliff-test conformance fixtures.

Installation

pip install cliff-python

Optional converter support (YAML) is a separate extra, so the core package has no dependencies:

pip install "cliff-python[converters]"

Requires Python 3.11 or newer.

Quick start

import cliff_format

text = '''CLIFF 1.1
namespace: demo
clan: settings
source-language: en-US
target-language: zh-CN

[Video]
type: label

<Resolution>
source: "Resolution"
target: "分辨率"
status: final
'''

doc = cliff_format.parse(text)
print(doc.spec_version)              # 1.1 — the version the document declared
print(doc.header.namespace)          # demo
print(doc.groups[0].path)            # Video
print(doc.groups[0].entries[0].id)   # Resolution

print(cliff_format.serialize(doc))   # canonical CLIFF

for issue in cliff_format.validate(text):
    print(issue.line, issue.category, issue.message)

Strict and tolerant parsing

CLIFF 1.1 defines two readings of the same syntax, and cliff_format exposes both. Strict is the default; the mode is never chosen for you.

Strict (parse, load) rejects everything the grammar rejects, and accepts everything it accepts:

  • identifiers may use A-Z a-z 0-9 _ - and never ., are case-sensitive, and are never rewritten — bad_id and BadVersion stay as written, because an identifier is a translation match key;
  • tags (type, emotion, status, variant) are the one thing 1.1 did not relax: lowercase words from a closed vocabulary;
  • one optional trailing , or ; per line is standard syntax, means nothing, and is never re-emitted by the serializer;
  • a file-layout mismatch is a warning — the layout is a recommendation;
  • resource limits are enforced.

Tolerant (parse(..., tolerant=True), parse_tolerant) adds the seven relaxations of Appendix C and reports every repair:

Relaxation Input Result
C.2.1 bare scalar in a list-typed field emotion: objective ["objective"]
C.2.2 repeated field in one scope two target: lines concatenated / appended in order
C.2.3 quoted tag status: "final" final
C.2.4 quoted entry id <"resolution"> resolution
C.2.5 identifier with a reserved character <Save & Load> Save-Load (+ collision handling)
C.2.6 version-line spelling cliff 1.1.0 CLIFF 1.1
C.2.7 quoted key "context": "…" context: "…"

C.2.7 removes the quotes and nothing else: the enclosed text must be a name, no escape is processed, and the key is then checked against the legal keys of its scope exactly as a bare key is. A quoted word that is not a legal key, or a key quoted in a scope that does not allow it, is still an error (§C.5) — the relaxation cannot legalize a word.

C.2.5 applies to an identifier that begins with a name character (ignoring surrounding whitespace), or that uses the quoted form of C.2.4. Angle brackets that enclose anything else are not an entry marker: a stray closing tag </terms> — what a model writes when it treats a section like an XML element — or <.hidden> is rejected under C.5 rather than normalized. Without that limit the reading turned /terms into the entry terms and then failed the document on a required field of an entry the file never contained, which is the guessing C.5 forbids. An identifier that is empty or whitespace-only is unchanged: C.3 step 7 still gives the scope's fallback name.

Every repair is recorded, so nothing is fixed silently:

doc, corrections = cliff_format.parse_tolerant(text)
for c in corrections:
    print(c.line, c.category, c.message, c.before, "->", c.after)

Categories: list-shape, field-repeat, tag-quote, name-quote, name-normalized, id-collision, version, casing. A category names the operation, not the clause, so name-quote covers both a quoted entry id (C.2.4) and a quoted key (C.2.7).

The mode never guesses. Appendix C.5 lists what a tolerant parser MUST refuse — a missing required field, a word outside a closed vocabulary, an unbalanced ICU brace, a malformed line, an unsupported version — and those raise CliffParseError with a line number and a category, exactly as in strict mode.

Two guarantees make the mode usable in a pipeline:

# 1. the repaired document is valid under the strict grammar
once = cliff_format.serialize(doc)
assert not [i for i in cliff_format.validate(once) if i.category not in ("warning", "extension")]

# 2. canonical output is a fixed point
assert cliff_format.serialize(cliff_format.parse(once)) == once

Command line

cliff-format parse path/to/file.cliff
cliff-format serialize path/to/file.cliff
cliff-format validate path/to/file.cliff
cliff-format convert path/to/file.cliff --format po
cliff-format convert path/to/file.po --from po --format cliff

# Tolerant reading of a model's output, with the repair report on stderr
cliff-format parse path/to/model-output.cliff --tolerant
cliff-format validate path/to/model-output.cliff --tolerant
cliff-format convert path/to/model-output.cliff --tolerant --format json

# Opt-in checks: a layout mismatch as an error, style deviations as warnings
cliff-format validate path/to/file.cliff --check-layout
cliff-format validate path/to/file.cliff --style
cliff-format validate path/to/file.cliff --check-width

# Canonical output for a specific version, or with a canonical id style applied
cliff-format serialize path/to/file.cliff --spec-version 1.0
cliff-format serialize path/to/file.cliff --style

Supported convert formats: cliff, json, yaml, csv, po, xliff, fluent, android, ios.

validate exits 1 when the file has errors and 0 when it only has warnings, extension notes, corrections, or style findings. Every diagnostic carries a line number, a category, and the offending line.

Converters

Bidirectional for JSON, YAML, CSV, PO, XLIFF 2.x, Fluent, Android strings.xml and iOS Localizable.strings:

po_text = cliff_format.to_po(doc)
doc = cliff_format.from_po(po_text)

# XLIFF 2.0 / 2.1 / 2.2; 2.2 also writes the glossary module for variant: glossary
xml = cliff_format.to_xliff(doc, version="2.2")

CLIFF carries context as first-class data, and every converter moves that data through the target format's own documented metadata channel — never through an invented one:

Format Channel used for CLIFF attributes Channel used for context
JSON / YAML / CSV native fields of the CLIFF-shaped document native field
XLIFF 2.x metadata module (mda:metadata / mda:metaGroup category="cliff" / mda:meta), plus state on segment for status mda:meta type="context" and a plain note
XLIFF 2.2 glossary additionally the glossary module (gls:glossEntry) for variant: glossary gls:definition
gettext PO extracted comments #. cliff:<key>: <value>; #: for references; msgctxt for the group path and entry id plain #. comment
Fluent message comment # cliff:<key> = <value> (attributes are translatable content, so they are not used for metadata) plain # comment
Android strings.xml XML comment above the resource, cliff:<key>: <value> first line of that comment
iOS Localizable.strings /* ... */ block comment above the pair, cliff:<key>: <value> first line of that comment

Because formats without a group level cannot express inheritance, the exporters fold the group values into each entry (the effective context, type, emotion and max-width) so nothing is lost.

The reverse direction is deliberately conservative. An importer reads only the metadata the source format actually defines: namespaced cliff: entries become CLIFF attributes, an ordinary comment becomes context, and everything else falls back to a documented default (type: sentence, status derived from whether a translation exists). A converter never invents context, emotion, references, widths or reviewers that the source file did not contain.

Round-trip fidelity

JSON, YAML, CSV and XLIFF preserve the whole CLIFF data model, so cliff -> format -> cliff is lossless. The remaining formats have no slot for some CLIFF fields:

Format Fidelity Dropped on the way back
JSON / YAML / CSV yes —
XLIFF 2.0 / 2.1 / 2.2 yes — (CLIFF-only fields travel as cliff:-categorised notes)
PO entry level header prose (variant, version, title, info, standard, dependency) and the group structure
Fluent entry level header prose and the group structure; Fluent attributes of foreign files cannot be represented
Android strings.xml entry level header prose and the group structure
iOS Localizable.strings entry level header prose and the group structure

Importers keep foreign identifiers that are already valid CLIFF 1.1 names — InvSwordIron and bad_id are valid names, and rewriting one silently would break a translation memory. Only characters outside the name set are replaced, by the documented normalization of specification Appendix C.3, and duplicates are disambiguated with a numeric suffix so every generated document validates.

API

Function Description
cliff_format.parse(text, path=None, tolerant=False, corrections=None, **limits) Parse CLIFF text into CliffDocument.
cliff_format.parse_tolerant(text, path=None) Parse in tolerant mode; returns (document, corrections).
cliff_format.load(path, tolerant=False) Read and parse a .cliff file.
cliff_format.serialize(doc, style=False) Serialize a CliffDocument to canonical CLIFF text.
cliff_format.validate(text, check_width=False, check_layout=False, style=False, tolerant=False) Validate CLIFF text and return list[ValidationIssue].
cliff_format.validate_document(doc, ...) Validate an already parsed document.
cliff_format.effective_context / effective_type / effective_emotion / effective_max_width Resolve a group-inherited value for one entry.
cliff_format.is_name / is_tag / is_style_identifier / normalize_identifier / strip_line_terminator The identifier rules of §5.5 and Appendix C.3, and the terminator rule of §5.6.
cliff_format.to_dict / from_dict Convert between CliffDocument and a JSON-shaped dict.
cliff_format.to_json / from_json Convert between CliffDocument and JSON text.

Diagnostics

Category Meaning
syntax, semantic, vocabulary, icu, id Errors: the file does not conform.
warning A recommendation (layout mismatch, glossary shape, width overflow).
extension An x- field was ignored, as the specification requires.
correction A tolerant parse repaired something; reports before → after.
style A deviation from the style guide (--style); never an error.

Resource limits

parse and load accept max_lines, max_line_length and max_string_length; the defaults bound a document at 1 000 000 lines, 1 MiB per line and 1 MiB per decoded string. Exceeding a limit raises a located error instead of exhausting memory. The limits apply in strict and tolerant mode alike (§19).

Repository layout

cliff-python/
├── pyproject.toml
├── src/
│   └── cliff_format/
│       ├── identifiers.py   # name / tag / terminator rules (§5.5, §5.6, Appendix C.3)
│       ├── vocabulary.py    # the closed tag vocabularies (§12, §13)
│       ├── model.py         # dataclasses for the CLIFF data model
│       ├── parser.py        # single-pass line parser, strict and tolerant
│       ├── serializer.py    # canonical serializer
│       ├── validator.py     # semantic validation, inheritance helpers, style and layout checks
│       ├── converter.py     # bidirectional format converters
│       └── cli.py           # cliff-format command line
├── tools/
│   └── regenerate_examples.py
├── examples/
└── tests/

Conversion samples live in examples/cliff_to_<format>/ and examples/<format>_to_cliff/. The generated side is produced by tools/regenerate_examples.py; run it after changing a converter, and python tools/regenerate_examples.py --check verifies the samples are current.

License

MIT — see LICENSE. The CLIFF wordmark under docs/assets/ comes from the CLIFF project and is licensed under the MIT License as well.

Metadata

Release files for cliff-python 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cliff-python 0.2.0
File Size Uploaded Provenance
cliff_python-0.2.0.tar.gz 77.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cliff-python 0.2.0
File Interpreter ABI Platform
cliff_python-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 132.3 kB

Release files / cliff_python-0.2.0.tar.gz

Download URL cliff_python-0.2.0.tar.gz
Size 77.5 kB
Tags Source
SHA-256 checksum
How to use checksums
933bcd288a6cec2f43bebd9deeed589d74f19f7f85bded2567a3d5c18825b3d1
BLAKE2b-256 checksum
How to use checksums
987c326253be5e22203c010bc4784a91aa9cd15060cda145ae92d850ca1ca35f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / cliff_python-0.2.0-py3-none-any.whl

Download URL cliff_python-0.2.0-py3-none-any.whl
Size 54.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3f2403648c6588441c4fc5eaf1558cf0edc46e222bf9e96f83289b07a99a9fb2
BLAKE2b-256 checksum
How to use checksums
ccfeb6498013eee86d52fbdbfc9b9a9ef0a32fe37a205dbad934048dd61de5a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for cliff-python 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for cliff_python-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page