Noctalia i18n Core
Noctalia i18n Core is a read-only Python client for project progress, current translations, and changes from Noctalia Translate. Storage, scheduling, delivery, and application policy belong to the caller.
Capabilities
- Read project details, translation context, and project-wide and per-locale progress.
- Read current translations as a complete catalog, exact-key results, or text-search results.
- Query missing translations and translations that need review in a selected locale.
- List dot-separated translation keys and filter them by hierarchy prefix.
- Read complete changes for selected keys in one locale.
- Lazily read the project-wide change stream with locale, time, and anchor boundaries.
- Read validated change pages with a durable cursor for resumable scans.
- Retry temporary connection, timeout, rate-limit, and server failures.
- Use a caller-owned
requests.Sessionwhen connection policy must be configured by the application.
Project structure
noctalia_i18n_core/
├── client.py # Website requests and response validation
└── models.py # Normalized project and change values
Installation
uv add noctalia-i18n-core
Or with pip:
pip install noctalia-i18n-core
Usage
Create a client
from noctalia_i18n_core import NoctaliaClient
with NoctaliaClient("noctalia", timeout=30) as client:
key = "settings.widgets.settings.scroll-repeat.label"
value = client.translations(key, locales=("zh-Hans",))[key]["zh-Hans"]
changes = client.changes(key, locale="zh-Hans")
print(value)
print(changes[0].occurred_at if changes else "No changes")
The client retries temporary failures and closes the session it creates. Pass a configured requests.Session when the application owns connection settings such as proxies or retry policy; caller-owned sessions are used without modification.
Project details and progress
project() makes one project request and returns the public name, description, translation context, and current progress. Project and locale progress use the integer percentage shown by the website. The review count identifies translations whose English source changed after that translation was last saved.
project = client.project()
print(project.name, project.key_count, project.language_count, project.progress)
for locale in project.locales:
print(
locale.code,
locale.progress,
locale.missing_count,
locale.review_count,
)
Current translations
translations() reads one or more exact keys in website-sized batches. Its key-first result preserves the requested key and locale order. A requested key remains present with an empty mapping when no selected locale has a value.
values = client.translations(
"settings.widgets.settings.scroll-repeat.description",
"settings.widgets.settings.scroll-repeat.label",
locales=("zh-Hans", "en"),
)
catalog() makes one complete export request and returns {key: {locale: value}}. It is intended for callers that need every current translation.
catalog = client.catalog()
source_text = catalog["settings.widgets.settings.scroll-repeat.description"]["en"]
Text search
search_translations() searches the key and current value in search_locale, follows all result pages, and reads the requested result locales for the matching keys. If locales is omitted, the result contains only search_locale.
matches = client.search_translations(
"确认",
search_locale="zh-Hans",
locales=("zh-Hans", "en"),
)
Missing translations and reviews
missing() returns source entries without a current translation in one locale. reviews() returns entries whose English source changed after the translation was last saved. Both results include the key, locale, current source, and translation page; reviews additionally include the previous source. Both queries accept query to filter their results.
missing = client.missing("fr", query="scroll")
reviews = client.reviews("fr")
for item in missing:
print(item.key, item.source, item.translation_url)
for review in reviews:
print(review.key, review.previous_source, review.source)
Translation keys
keys() makes one key-tree request and returns the complete key list. A prefix matches a complete dot-separated hierarchy rather than an arbitrary string prefix.
widget_keys = client.keys(prefix="settings.widgets")
Changes for selected keys
changes() reads the complete changes for one or more exact keys in one locale. The returned tuple is ordered from newest to oldest; multiple key results are merged in that order.
changes = client.changes(
"settings.widgets.settings.scroll-repeat.description",
locale="zh-Hans",
)
for change in changes:
print(change.occurred_at, change.action, change.new_value)
Project change stream
iter_changes() lazily reads the project-wide change stream in the order supplied by the website. since and until are inclusive, timezone-aware boundaries and are normalized to UTC. locale restricts the stream to one locale.
from datetime import UTC, datetime
recent = client.iter_changes(
locale="zh-Hans",
since=datetime(2026, 8, 1, tzinfo=UTC),
)
for change in recent:
print(change.key, change.action)
after is a website change ID. The client confirms the anchor before yielding a result and raises RuntimeError when it is absent. on_progress receives the completed page number and current total page count after each request.
change_pages() exposes the same stream as immutable ChangePage values. start_page selects a one-based page. cursor is an opaque resume position stored by the caller; the client discards that position and newer records before continuing with older pages.
page = next(client.change_pages(start_page=1))
print(page.number, page.total_pages, page.cursor)
API reference
NoctaliaClient
NoctaliaClient(project, *, timeout, session=None)
project is the website project identifier. timeout is a positive finite number of seconds applied to each attempt. By default, temporary connection, timeout, rate-limit, and server failures are retried three times with backoff. session may be a caller-owned requests.Session; its transport policy is preserved and the client does not close it.
| Member | Result |
|---|---|
slug |
The project identifier passed to the constructor |
translation_url(key, locale) |
Translation page for one key and locale |
project() |
Project details and current translation progress |
keys(*, prefix=None) |
tuple[str, ...] of complete or prefix-matched keys |
catalog() |
dict[str, dict[str, str]] containing the complete current catalog |
translations(key, *keys, locales=None) |
Key-first exact translations |
search_translations(query, *, search_locale, locales=None) |
Key-first translations for text matches |
missing(locale, *, query=None) |
Missing translations with their source text |
reviews(locale, *, query=None) |
Translations needing review in one locale |
changes(key, *keys, locale) |
Newest-first exact-key changes |
iter_changes(*, locale=None, since=None, until=None, after=None, on_progress=None) |
Lazy project-wide change iterator |
change_pages(*, locale=None, start_page=1, cursor=None) |
Lazy validated pages with an opaque resume cursor |
close() |
Closes an owned HTTP session |
translations() and changes() require at least one positional key. translations() returns requested keys even when their result is empty. changes() returns a tuple and requests each selected key from the website. iter_changes() never accepts keys and remains lazy until iteration begins.
Invalid arguments raise ValueError. Network failures that remain after retries and responses that do not match the website contract raise RuntimeError.
Project and Locale
Project contains public project details and progress aggregated from the current locale states:
| Field or property | Description |
|---|---|
slug |
Website project identifier |
name |
Project name |
description |
Project description, or None when absent |
translation_context |
Translation guidance and terminology, or None when absent |
key_count |
Current translation key count |
locales |
Website-ordered tuple[Locale, ...] |
language_count |
Configured locale count |
translation_count |
Current translation count |
missing_count |
Current missing translation count |
review_count |
Current translations that need review |
progress |
Integer completion percentage shown by the website |
Each Locale contains code, translation_count, missing_count, review_count, and the derived progress. Locales without any current translations remain present in the project result.
Missing and Review
Both models contain key, locale, source, and translation_url. Review additionally contains previous_source, which is None when no previous source text exists. This source difference is distinct from changes to the translation itself.
Change
Each change has the following fields:
| Field | Description |
|---|---|
id |
Website change identifier |
key |
Translation key |
locale |
Locale changed by the record |
action |
added, modified, or deleted |
old_value |
Previous text, or None for an addition |
new_value |
New text, or None for a deletion |
occurred_at |
UTC-aware datetime |
translation_url |
Translation page for the key and locale |
actor_login |
Contributor login, or None for an automated change |
actor_url |
Contributor profile URL, when available |
actor_avatar_url |
Contributor avatar URL, when available |
ChangeAction is the literal type "added" | "modified" | "deleted". SOURCE_LOCALE is "en".
ChangePage
ChangePage contains number, total_pages, changes, and cursor. cursor corresponds to the final raw website record on the page, including a record that normalizes to no Change; callers should treat it only as an opaque resume value.
Performance reference
These reference measurements were taken from the public noctalia project in August 2026. They describe website requests without a local cache.
| Operation | Requests | Reference result |
|---|---|---|
project() |
One project page request | Progress for 25 locales, about 23 KB |
catalog() |
One complete export | 42,857 strings, about 2.23 MB, 4.4–4.8 s |
translations() |
One batch request per 50 keys | Two keys and two locales, about 1.3 KB, about 1.4 s |
search_translations() |
Search pages plus one batch request | 19 Simplified Chinese matches, about 1.7 s |
missing() |
One missing query plus batched source queries | Matching entries and their English source text |
reviews() |
One review query | Matching keys and their English source difference |
keys() |
One key-tree page | About 2,474 keys and 147 KB |
changes() |
One request per selected key | Example key, about 1.3 KB and 1.4 s |
iter_changes() |
Recent Changes pages until the boundary | Depends on the requested range |
Latency, server load, and project growth affect these values. Exact queries and text searches do not download the complete catalog.
Development
Install the locked development environment:
uv sync --locked
Run formatting, linting, type checking, and tests:
uv run ruff format --check .
uv run ruff check .
uv run basedpyright
uv run python -m unittest discover -v
The default suite validates models, response parsing, and client behavior without accessing external services.
Run the live contract tests explicitly to verify every public operation against the current public projects on Noctalia Translate:
uv run python -m unittest tests.live -v
Build and inspect both distributions:
uv build --no-sources --clear
uvx twine check --strict dist/*
License
Metadata
Release files for noctalia-i18n-core 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| noctalia_i18n_core-0.3.2.tar.gz | 27.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| noctalia_i18n_core-0.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 45.3 kB
Release files / noctalia_i18n_core-0.3.2.tar.gz
| Download URL | noctalia_i18n_core-0.3.2.tar.gz |
|---|---|
| Size | 27.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b3f18422265fe9f11df3d09fb8d8a86eb10d76536b0008cbe6cb3af8b15d12f1
|
|
BLAKE2b-256 checksum How to use checksums |
a9750cdda4772e142c7f86057d7b59e548b738b06f95e2af2ef2dabad7d12d2c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 10, 2026.
Transparency logRelease files / noctalia_i18n_core-0.3.2-py3-none-any.whl
| Download URL | noctalia_i18n_core-0.3.2-py3-none-any.whl |
|---|---|
| Size | 17.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
96a831d5adb6ec405818e78317bfbbdfe8d0e7be9943c1dc83e8fc0ef8e63531
|
|
BLAKE2b-256 checksum How to use checksums |
23e0c36e9a431e8e2362aee7f259216cdb4481088da561b2a75d62e5cfc87091
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Aug 10, 2026.
Transparency log