Metadata-Version: 2.4
Name: l10n-lint
Version: 1.23.30
Summary: Linter for localization files (.po, .ts, .xlf/.xliff, .json, .arb, .ftl, .resx)
Author-email: Daniel Nylander <daniel@danielnylander.se>
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/yeager/l10n-lint
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Localization
Classifier: Environment :: Console
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fluent.syntax>=0.19
Requires-Dist: tomli>=2.0; python_version < "3.11"
Provides-Extra: gtk
Requires-Dist: PyGObject>=3.42; extra == "gtk"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: build>=1; extra == "test"
Dynamic: license-file

# l10n-lint

[![Version](https://img.shields.io/badge/version-1.23.30-blue)](https://github.com/yeager/l10n-lint/releases/tag/v1.23.30)
![License](https://img.shields.io/badge/license-GPL--3.0-green)
![Python](https://img.shields.io/badge/python-3.9+-blue)

## Svenska

l10n-lint granskar gettext-PO, Qt-TS, Fluent, XLIFF 1.2/2.x, JSON-kataloger,
Dart/Flutter-ARB-filer, Microsoft ResX-filer och CUPS/libcups `.strings`-kataloger. Det
hittar tomma översättningar, syntaxfel, skillnader i platshållare och pluraler,
felaktig formatering, brutna taggar och URL:er. Kod, kommandon, filnamn,
funktionsnamn och identifierliknande variabelnamn i DocBook-markup kontrolleras
också mot källtexten. Använd det i CI eller med
GTK-gränssnittet vid manuell granskning.

### Installera och köra

Installera den publicerade Python-versionen från PyPI:

```bash
python -m pip install l10n-lint
l10n-lint translations/sv.po
```

Alternativt kan du köra den aktuella källkoden:

```bash
git clone https://github.com/yeager/l10n-lint
cd l10n-lint
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .
l10n-lint translations/sv.po
```

Kör `l10n-lint ./po/` för en katalog, `l10n-lint --help` för samtliga
alternativ och `l10n-lint --list-rules` för regelidentifierare och nivåer.

### Svensk kvalitetskontroll

Kör l10n-lint först för katalogstruktur, formatsträngar, markup och URL:er.
Granska sedan med svlang, hunspell-sv, swedish-foss-terminology och swedish-tm.
Kontrollera varje träff i dess gränssnittskontext. Svenska regler omfattar ingen
komma omedelbart före `och`, korrekt skiljeteckensmellanrum, citattecken,
utelämningstecknet `…`, procentformat, siffergruppering och ordet `till` i
intervall. Tankstreck flaggas i svenska måltexter när de
inte måste bevaras från källans syntax eller layout. Crowdin-text före `|` är metadata och får inte synas i måltexten.
Invarianta svenska pluraler som `byte`, `träd` och `resultat` flaggas inte som
fel bara för att singular och plural har samma form.
Plattformsspecifika kortkommandon som `⌘ + Backspace` behandlas som syntax och
flaggar inte som oöversatt text.
Även `objekt` godtas som invariant plural, och den svenska skrivningen
`Obs!` godtas för engelska `Note:`.

Betydelse, tilltal, stilnivå och kontextberoende facktermer kräver alltid
mänsklig granskning.

## English reference

l10n-lint checks gettext PO, Qt TS, Fluent, XLIFF 1.2/2.x, JSON translation catalogs, Dart/Flutter ARB files, Microsoft ResX files, and CUPS/libcups `.strings` catalogs for missing translations,
invalid syntax, placeholder mismatches, plural errors and inconsistent formatting. It also preserves
executable DocBook content such as code, commands, filenames, function names and identifiers.
Use the command line in CI or the GTK4 desktop interface for interactive review.
Swedish-specific checks cover spelling patterns, terminology and localization conventions.
The identical-plural diagnostic accepts common invariant Swedish forms such as
`byte`, `träd`, and `resultat`.
Platform keyboard shortcuts such as `⌘ + Backspace` are treated as syntax,
not untranslated prose.
`objekt` is also accepted as an invariant plural, and Swedish `Obs!` is
accepted for the English cue `Note:`.

[Download 1.23.30](https://github.com/yeager/l10n-lint/releases/tag/v1.23.30)
· [Changelog](CHANGELOG.md) · [Report a bug](https://github.com/yeager/l10n-lint/issues)

Version **1.23.0** adds Dart/Flutter ARB catalogs, protects more DocBook content and improves Swedish time checks. Version **1.22.6** recognizes Swedish idiomatic number equivalents and localized grouped thousands. Version **1.22.1** accepts Swedish cardinal words through
twelve in numerical checks, and version **1.22.0** adds Swedish typography and
number-format diagnostics.

## Features

- Validate PO/TS/Fluent/XLIFF/JSON/ARB/ResX/`.strings` syntax, plural forms, printf/Python/Qt placeholders, tags, whitespace and URLs.
- Share diagnostic IDs and input handling between the CLI and GTK4 interface.
- Scan local files, directories, remote PO/TS/Fluent/XLIFF/JSON/ARB/ResX/`.strings` URLs and GitHub repositories.
- Select individual rules or groups, configure severities and load custom TSV glossaries.
- Store project settings in `pyproject.toml` and baseline existing findings.
- Compare translations with a source catalog to find missing or obsolete entries.
- Preview and apply conservative whitespace and ellipsis fixes to local PO files.
- Export text, JSON, HTML, GNU, GitHub Actions annotations or SARIF 2.1.0.

## Installation

### Install the current release

[Release 1.23.30](https://github.com/yeager/l10n-lint/releases/tag/v1.23.30)
records the current annotated tag and release notes. Build from the checked-out
source below to use this exact revision. Package repositories and PyPI can lag
behind the GitHub release.

### APT repository (Debian/Ubuntu)

Install the repository's current public key and scope it to this source:

```bash
key_file=$(mktemp)
curl -fsSL https://yeager.github.io/debian-repo/yeager-repo-key.asc -o "$key_file" &&
  sudo install -D -m 0644 "$key_file" /etc/apt/keyrings/yeager-l10n.asc
rm -f "$key_file"
echo "deb [signed-by=/etc/apt/keyrings/yeager-l10n.asc] https://yeager.github.io/debian-repo ./" | sudo tee /etc/apt/sources.list.d/yeager-l10n.list
sudo apt update
sudo apt install l10n-lint
```

This replaces the earlier `stable main` entry in `yeager-l10n.list`. If you put
that entry in another file, replace it there instead; keep only one Yeager
source definition. The obsolete `yeager-l10n.gpg` URL no longer exists.
The current key's primary fingerprint is
`7CEE83C9C621B18667DD1BFECAED4975DAB053A8`; its signing subkey is
`37986EFBED62D629C0CF268EE318C7DE3DA87C5B` (the key reported in issue #2).

## Building from source

```bash
git clone https://github.com/yeager/l10n-lint
cd l10n-lint
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .
```

## Usage

Basic usage:
```bash
# Lint a single file
l10n-lint translations/sv.po

# Lint a directory recursively
l10n-lint ./po/

# Open the GTK4 interface
l10n-lint-gtk

# Lint a GitHub repository
l10n-lint --github yeager/l10n-lint
```

Advanced options:
```bash
# Generate HTML report
l10n-lint -f html -o report.html ./translations/

# JSON output for CI pipelines
l10n-lint -f json -o results.json ./translations/

# SARIF output for code-scanning integrations
l10n-lint -f sarif -o results.sarif ./translations/

# Run only specific checks
l10n-lint --checks terminology,false-friends,consistency sv.po

# CI mode (exit code only)
l10n-lint --check --strict .
```

The target language is detected from PO metadata or Qt TS attributes, with a filename
fallback for PO files. Use `--language sv` to override detection when needed.

See the manual or CLI help for complete options:
```bash
man l10n-lint
l10n-lint --help
```

## Swedish review workflow

Run l10n-lint first for catalog structure, placeholders, markup, URLs and
format. Follow with [svlang](https://github.com/yeager/svlang) for style,
[hunspell-sv](https://github.com/yeager/hunspell-sv) for spelling,
[swedish-foss-terminology](https://github.com/yeager/swedish-foss-terminology)
for established terms and [swedish-tm](https://github.com/yeager/swedish-tm)
for context-aware suggestions. Never accept an automatic finding without reading
source and target in their product context.

Swedish checks include the project rule against a comma directly before `och`,
punctuation spacing, Swedish quotation marks, `…` rather than three dots,
percent formatting, digit grouping and the word `till` in ranges. Em dashes are flagged in Swedish targets unless the source syntax or layout requires the same character. A Crowdin prefix
before `|` is metadata and must not be translated or retained in visible text.
Meaning, address, register and context-sensitive terminology always need manual
review.

## Checks and rule selection

Use `l10n-lint --list-rules` for every diagnostic ID, default severity and language
scope. These are some useful rule groups and individual IDs:

| Rules or groups | What they check |
|-----------------|-----------------|
| `placeholders` | Typed printf arguments, Python fields and Qt placeholders |
| `plural-forms` | Plural headers, formulas and translation variants |
| `missing-translation`, `fuzzy` | Empty translations and entries awaiting review |
| `untranslated-words`, `source-equals-translation` | Possible untranslated text |
| `length`, `whitespace` | Length limits, missing boundary spaces and extra whitespace |
| `punctuation`, `capitalization`, `quotes` | Text formatting and consistency |
| `html-tags`, `xml-tags-mismatch`, `url-preservation` | Markup and URLs |
| `context-prefix-leak` | CrowdIn `key|Text` context keys are excluded from source checks; leaked prefixes are reported |
| `escapes`, `newline-mismatch`, `accelerators` | Escapes, newlines and shortcuts |
| `numerics`, `option-values` | Numbers and command-line option values |
| `typo`, `terminology`, `false-friends` | Swedish spelling patterns and word choices |
| `domain-terminology` | Swedish terminology for music and issue-tracking contexts |
| `date-format`, `number-localization`, `currency-localization` | Swedish formatting conventions |
| `consistency`, `duplicate` | Inconsistent translations and duplicate catalog entries |
| `glossary` | Project-specific TSV terminology |
| `catalog-missing`, `catalog-obsolete`, `catalog-plural-changed` | Differences from `--reference` |

### Format flags and documentation text

Explicit `c-format` and `python-format` flags enable full printf validation,
including space flags such as `% d`. Without those flags, only clear conversion
syntax is inferred: prose such as `100% coverage` or `50 % anger` is not a
printf argument. Real `%s`, positional/named arguments and width/precision
specifications are still checked. Use an explicit flag for ambiguous cases
such as a conversion immediately after a numeric literal. Attached integer time
units such as `%ds` are recognized. Other literal suffixes such as `%iHz` are
accepted when the complete typed argument contracts match on both sides.
Prose percentages such as `2% change` are ignored even with a `c-format` flag.
Standalone JSON keys are identifiers, so their spelling is not used as a source
placeholder contract; explicit JSON source/translation entries are still checked.

Python-format checks ignore reST `:math:` roles (including suffix role syntax)
and inline literal brace delimiters such as ` ``{`` ` and ` ``}`` `. Complete
fields such as ` ``{name}`` ` and real placeholders outside those constructs
remain checked, even in a message that also contains mathematics. These
exclusions affect format parsing only; the original text is retained for
other checks and reports.

### Swedish terminology sources

The built-in Swedish rules are deliberately conservative. For IT terms, curated
terms and recommendations use [Computer Swedens IT-ord](https://it-ord.computersweden.se/)
as the first reference source. [SAOL, SO and SAOB](https://svenska.se/),
[TEPA](https://termipankki.fi/tepa/sv/), [IATE](https://iate.europa.eu/home),
[Rikstermbanken](https://www.rikstermbanken.se/) and [ISOF's guidance on
fackspråk och terminologi](https://www.isof.se/svenska-spraket/facksprak-och-terminologi)
then support general Swedish, public-sector terminology and domains outside IT,
together with project documentation and the configured project glossary. These
sources guide human review; l10n-lint does not scrape or redistribute their data.

For Swedish compounds, three equal consecutive consonants are flagged as a likely
spelling error; for example, `processstatus` is normally written `processtatus`.
Hyphenated compounds, intentional dialogue exclamations, date-format tokens and
technical source tokens remain exempt. Review warnings in context.

### Context for Swedish checks

Date-format tokens such as `yyyy` in `dd/mm/yyyy` are excluded from spelling
heuristics. Date and placeholder checks still run on the original text.

The `View` → `Visa` menu recommendation requires an explicit `menu` or `menubar`
context in PO `msgctxt` or the Qt TS context/comment. Without that evidence, `View`
may be a noun, for example a color-management view translated as `Vy`.
The `linje` → `rad` recommendation requires a command-line or code/text-line
context; geometric lines and unrelated compounds such as `riktlinjer` are ignored.
Likewise, `File` → `Arkiv` applies only where PO `msgctxt` or Qt TS context/comment
identifies a menu. A label or command category may correctly use `Fil`. Empty Qt TS
source and translation pairs are structural keys and are ignored.

## Output Formats

| Format | Flag | Use case |
|--------|------|----------|
| `text` | `-f text` | Terminal output (default) |
| `json` | `-f json` | CI pipelines, integrations |
| `html` | `-f html` | Shareable reports |
| `gnu` | `-f gnu` | Emacs `compile-mode` compatible |
| `github` | `-f github` | GitHub Actions annotations |
| `sarif` | `-f sarif` | SARIF 2.1.0 code-scanning reports |

## Project configuration

The CLI reads `[tool.l10n-lint]` from the nearest `pyproject.toml`, searching
from the current working directory upwards. Use `--config FILE` to select one
explicitly. Command-line values override configuration values. Paths in the
configuration are relative to that TOML file; command-line paths are relative
to the working directory.

```toml
[tool.l10n-lint]
language = "sv"
checks = ["placeholders", "whitespace", "terminology", "glossary"]
exclude = ["vendor/**", "generated/**"]
glossary = "translations/glossary.tsv"
translation-memory = "translations/memory.json"
forbidden-terms = ["Dokumentera"]
required-terms = [{ source = "Save", target = "Spara", context = "menu" }]
max-length = 500
length-ratio = 3.0
max-errors = 0
max-warnings = 0
strict = true
json-format = "auto"

[tool.l10n-lint.severity]
terminology = "warning"
glossary = "error"
```

`--list-rules` prints all diagnostic IDs, default severities and language scopes.
`--checks` selects only the listed rules or groups. Groups include
`placeholders`, `length`, `whitespace`, `plural-forms` and `accelerators`.
`--disable` and `--skip-checks` accept the same names. Unknown names are errors.
Operational errors (invalid syntax, unreadable/missing paths, incomplete
network fetches and invalid references) cannot be disabled. An empty scan also
fails, including when exclusions remove every input. Repeated local paths are
deduplicated. Directory discovery accepts `.po`, `.ts`, `.xlf`, `.xliff`, `.json`
and `.strings`
case-insensitively.

## XLIFF, JSON and CUPS strings catalogs

XLIFF support covers XLIFF 1.2 `trans-unit` elements and XLIFF 2.x `unit`/`segment`
elements. The linter reads the target language, skips units marked `translate="no"`,
and preserves inline placeholders expressed through `equiv-text` or `equiv`.

JSON support accepts nested key/value catalogs, where the leaf key is the source text,
and explicit entries with `source` plus `target`, `translation`, or `value`. Use
`@locale`, `locale`, `targetLanguage`, or `target_language` for language metadata.
Entries marked `translate: false` or `translatable: false` are skipped.
Plural objects with CLDR forms such as `one` and `other` are validated form by form.
Transifex JSON exports with `string`, `context`, `developer_comment`, nullable
`character_limit`, and numeric plural indexes are recognized as translation entries;
metadata is ignored.

Svenska: Transifex-exporter med fälten `string`, `context`,
`developer_comment`, valfria `character_limit` och numeriska pluralindex behandlas
som översättningsposter. Metadata granskas inte som översättningstext.

Set `json-format = "nested"` to allow only nested key/value catalogs, or
`json-format = "entries"` to allow only explicit `source`/`target` entries.
This policy is useful when a repository also contains non-localization JSON files.

CUPS/libcups `.strings` catalogs use the English source as the quoted key and
the target text as the quoted value. l10n-lint validates their syntax and runs
the ordinary placeholder, whitespace, punctuation and Swedish-style checks on
each key/value pair.

```json
{
  "@locale": "sv",
  "menu": {"Save %s": "Spara %s"}
}
```

A glossary contains two or three tab-separated fields per line:
`wrong<TAB>correct[<TAB>context]`. Blank lines and lines beginning with `#` are
ignored. Matching is case-insensitive at word boundaries; context is explanatory
text in the diagnostic. Missing or malformed glossary files fail the command.
Custom glossaries apply to every target language; built-in Swedish terminology
and spelling checks run only for Swedish.

Exit codes: **0** when findings are within the configured thresholds, **1** when
warnings exceed `--max-warnings`, **2** when errors exceed `--max-errors` or an
operational error occurs. `--strict` makes excess warnings return **2** as well.
Informational findings do not fail a run. `--check` suppresses normal output.
Invalid options/configuration still produce an error on stderr. `--skip-fuzzy`
suppresses the fuzzy diagnostic; it does not skip validation of those entries.

## Efficient review workflows

Use `--changed BASE` in a pull-request job to lint only localization files changed
since a Git revision, for example `l10n-lint --changed origin/main --format github`.
Use `translation-memory` for cross-catalog consistency, `required-terms` and
`forbidden-terms` for domain policy, and a JSON object of source-to-translation
string pairs for the memory file. The linter also reports introduced bidi controls,
non-NFC text, and missing CLDR plural categories in JSON plural objects.

## Baselines: report only new findings

```sh
# Save existing findings. This command still returns the normal lint exit code.
l10n-lint --write-baseline l10n-baseline.json translations/

# Fail only for findings exceeding the recorded baseline.
l10n-lint --baseline l10n-baseline.json translations/
```

Baselines include project-relative file identity, rule, severity, diagnostic
message and context, with occurrence counts. They ignore physical line numbers,
so moving an unchanged entry normally preserves its baseline. New duplicate
occurrences remain visible. Changes to diagnostic text, severity, translation
context or UI language may require baseline regeneration. Use the same project
root and UI language in developer and CI runs. Syntax/read/network failures are
never baselined. Commit the baseline for review; do not regenerate it on every
CI run. `baseline = "l10n-baseline.json"` is also supported in configuration.

## Compare translations with a source catalog

```sh
l10n-lint --reference messages.pot po/sv.po
l10n-lint --reference source.ts translations/sv.ts
l10n-lint --reference source.xlf translations/sv.xlf
l10n-lint --reference app_en.arb app_sv.arb
```

PO comparison keys include `msgctxt` and `msgid`; Qt keys use explicit IDs or
context, disambiguation comment and source text. Missing entries, entries absent
from the reference, and changed plural/source definitions are reported separately.
Obsolete PO and vanished/obsolete Qt entries are excluded. One reference applies
to every input in the command; use separate runs for different domains. Reference
and target formats must match (`.pot`/`.po`, `.ts`, `.xlf`/`.xliff`, or `.json`).

## Preview and apply conservative fixes

```sh
# Print a unified diff on stderr without changing files.
l10n-lint --fix whitespace,ellipsis po/sv.po

# Print the diff, write the changes, and lint the resulting files.
l10n-lint --fix whitespace,ellipsis --apply po/sv.po
```

Fixes currently support **local UTF-8 PO files**. They touch translation fields
only, skip headers and fuzzy/obsolete entries, and preserve unrelated comments,
source strings, newline style and file permissions. Whitespace fixes remove
extra boundary spaces/tabs only when the source has no boundary whitespace.
Ellipsis fixes convert a trailing `...` to `…` only when the source also ends in
an ellipsis. Interior spaces and meaningful source boundary whitespace are
preserved. Multiline translation fields that change may be serialized onto one
line. Each write is atomic; changed files and symlinks are rejected. Multi-file
application is not a transaction, so review the preview before `--apply`.

## SARIF and CI

```sh
l10n-lint -f sarif -o l10n-results.sarif translations/
```

SARIF 2.1.0 reports include diagnostic rule IDs, severity and source locations.
Text, JSON, HTML, GNU and GitHub Actions output remain available. GitHub annotation
properties and messages are escaped; informational findings use `notice`.

GitHub repository scans discover the repository's actual default branch and
fetch all files from the same tree revision. Download failures and truncated
GitHub trees fail the scan instead of silently producing partial success.

The GTK frontend uses the same input pipeline and rule IDs as the CLI, including
URL/GitHub loading, plural checks and operational errors. CLI project baselines,
reference comparison, SARIF export and fix commands are currently CLI workflows;
GTK retains its own preferences.

## Development and verification

```sh
python -m pip install '.[test]'
python -m pytest -q
python -m build
```

CI runs regression tests on Python 3.9, 3.11 and 3.14 and installs the built wheel
into a fresh environment for CLI smoke tests outside the checkout. GNU gettext validates the documentation
fixtures. A separate Xvfb job opens the GTK window/preferences and displays a URL
lint result. Debian and RPM packages also pass CLI checks against the release source.

For local GTK verification, install the system GTK dependencies and Xvfb, then run:

```sh
xvfb-run -a /usr/bin/python3 tests/gtk_smoke.py
```

Check desktop integration when changing widgets.

## Translation

Translations are managed on Transifex: https://app.transifex.com/danielnylander/l10n-lint/

Currently supported: Swedish, Danish, German, Spanish, Finnish, French, Italian, Norwegian Bokmål, Dutch, Polish, Portuguese (Brazil)

Contributions welcome!

## Changelog

- **1.21.7**: Preserve natural Swedish word order in boundary and numeric checks
- **1.21.2**: Add incremental review, project policy and CLDR/Unicode validation
- **1.20.2**: Recognize year tokens in typo checks; require context for ambiguous Swedish terminology
- **1.20.1**: Fix false format errors in percentage prose and reST documentation; repair APT setup
- **1.20.0**: Reliable parsing, shared rules, project configuration, baselines, catalog comparison, previewed fixes and SARIF
- **1.19.0**: Enhanced check accuracy
- **1.17.0**: Added terminology intelligence, domain-specific rules, false friends detection
- **1.16.0**: 76% reduction in false positives (26,576 → 6,308 issues)
- **1.15.x**: GTK4 GUI, GitHub integration, custom glossaries
- **1.14.x**: Multiple output formats, CI integration

See [CHANGELOG.md](CHANGELOG.md) for the full release history.

## License

[GPL-3.0-or-later](LICENSE)

## Author

Daniel Nylander (daniel@danielnylander.se)
