feat(docs): validate owner structured translations
Module Package Release / publish-packages (push) Successful in 13s

This commit is contained in:
2026-08-24 01:15:30 +02:00
parent 562d278f60
commit d2e491348d
7 changed files with 98 additions and 8 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "govoplan-core" name = "govoplan-core"
version = "0.1.37" version = "0.1.38"
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components." description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+51 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable, Iterable, Mapping, Sequence from collections.abc import Callable, Iterable, Mapping, Sequence
from dataclasses import dataclass, field from dataclasses import dataclass, field, replace
from typing import Any, Literal, Protocol, TYPE_CHECKING from typing import Any, Literal, Protocol, TYPE_CHECKING
from govoplan_core.core.information_governance import ModuleInformationGovernance from govoplan_core.core.information_governance import ModuleInformationGovernance
@@ -687,3 +687,53 @@ class ModuleManifest:
# runtime module ID changes. # runtime module ID changes.
permission_namespace: str | None = None permission_namespace: str | None = None
workflow_definitions: tuple["WorkflowDefinitionContribution", ...] = () workflow_definitions: tuple["WorkflowDefinitionContribution", ...] = ()
def with_documentation_structured_translations(
manifest: ModuleManifest,
*,
locale: str,
translations: Mapping[str, Mapping[str, Any]],
) -> ModuleManifest:
"""Merge module-owned structured documentation translations by topic id.
The helper keeps feature prose in its owning module while giving every
manifest the same fail-closed merge behavior. Unknown topic ids and
incomplete or shape-changing locale maps are rejected immediately.
"""
locale = locale.strip()
if not locale:
raise ValueError("structured documentation locale must not be empty")
topics_by_id = {topic.id: topic for topic in manifest.documentation}
unknown_topic_ids = sorted(set(translations) - set(topics_by_id))
if unknown_topic_ids:
raise ValueError(
"structured documentation translations reference unknown topic ids: "
+ ", ".join(unknown_topic_ids)
)
localized_topics: list[DocumentationTopic] = []
for topic in manifest.documentation:
translation = translations.get(topic.id)
if translation is None:
localized_topics.append(topic)
continue
structured_translations = dict(topic.structured_translations)
structured_translations[locale] = translation
localized_topic = replace(
topic,
structured_translation_version=DOCUMENTATION_STRUCTURED_TRANSLATION_VERSION,
structured_translations=structured_translations,
)
issues = documentation_structured_translation_issues(localized_topic)
if issues:
raise ValueError(
f"invalid {locale!r} structured documentation translation for "
f"{topic.id!r}: {'; '.join(issues)}"
)
localized_topics.append(localized_topic)
return replace(manifest, documentation=tuple(localized_topics))
@@ -12,6 +12,7 @@ from govoplan_core.core.modules import (
ModuleManifest, ModuleManifest,
localized_documentation_metadata, localized_documentation_metadata,
user_workflow_scope_condition_issues, user_workflow_scope_condition_issues,
with_documentation_structured_translations,
) )
from govoplan_core.core.registry import PlatformRegistry, RegistryError from govoplan_core.core.registry import PlatformRegistry, RegistryError
@@ -136,6 +137,45 @@ class DocumentationTopicContractTests(unittest.TestCase):
with self.assertRaisesRegex(RegistryError, "preserve list length"): with self.assertRaisesRegex(RegistryError, "preserve list length"):
registry_for(incomplete_shape).validate() registry_for(incomplete_shape).validate()
def test_manifest_helper_merges_and_validates_owner_translations(self) -> None:
topic = DocumentationTopic(
id="example.workflow.localized",
title="Run task",
summary="Run the task.",
metadata={"steps": ["Review", "Execute"]},
)
manifest = ModuleManifest(
id="example",
name="Example",
version="1.0.0",
documentation=(topic,),
)
localized = with_documentation_structured_translations(
manifest,
locale="de",
translations={
topic.id: {"steps": ["Prüfen", "Ausführen"]},
},
)
self.assertEqual(
["Prüfen", "Ausführen"],
localized.documentation[0].structured_translations["de"]["steps"],
)
with self.assertRaisesRegex(ValueError, "unknown topic ids"):
with_documentation_structured_translations(
manifest,
locale="de",
translations={"missing.topic": {"steps": ["Prüfen", "Ausführen"]}},
)
with self.assertRaisesRegex(ValueError, "preserve list length"):
with_documentation_structured_translations(
manifest,
locale="de",
translations={topic.id: {"steps": ["Prüfen"]}},
)
def test_documentation_configuration_and_source_extensions_are_validated(self) -> None: def test_documentation_configuration_and_source_extensions_are_validated(self) -> None:
resolver = lambda _context, keys: { # noqa: E731 resolver = lambda _context, keys: { # noqa: E731
key: DocumentationConfigurationDecision(key=key, state="enabled") key: DocumentationConfigurationDecision(key=key, state="enabled")
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.37", "version": "0.1.38",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.37", "version": "0.1.38",
"dependencies": { "dependencies": {
"@govoplan/access-webui": "file:../../govoplan-access/webui", "@govoplan/access-webui": "file:../../govoplan-access/webui",
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui", "@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.37", "version": "0.1.38",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.37", "version": "0.1.38",
"dependencies": { "dependencies": {
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.20", "@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.20",
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.19", "@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.19",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.37", "version": "0.1.38",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/core-webui", "name": "@govoplan/core-webui",
"version": "0.1.37", "version": "0.1.38",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",