mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
Core and the documentation get gettext catalogs for all 184 ISO 639-1
languages listed in meta/languages.yml, 47 of which LibreTranslate can fill.
`make i18n-extract` merges the current sources into locale/<code>/
LC_MESSAGES/{core,docs}.po: role descriptions, category and menu titles and
the logout strings for the core domain, the Sphinx gettext output for the
docs domain. A changed source keeps its previous translation as a fuzzy
entry, and catalogs are written deterministically so an unchanged merge
leaves the files untouched.
`make i18n-translate domain=<core|docs> [languages=<codes>]` fills only the
empty and fuzzy entries with a short-lived LibreTranslate container (the
image web-svc-libretranslate pins) and removes it afterwards. It starts the
container, and loads models, only for languages that still have pending
entries. Code spans, placeholders, URLs, tags and dotted names such as
Infinito.Nexus are masked for the request and a translation that damages
one is discarded. An unreachable server stops the run instead of
discarding every remaining entry, so a rerun continues where it stopped;
machine translations carry the translator comment `libretranslate`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
72 lines
2.3 KiB
Python
72 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import importlib
|
|
import unittest
|
|
import unittest.mock as mock
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
from utils.i18n.catalog import catalog_path, new_catalog, write_catalog
|
|
from utils.i18n.languages import LANGUAGES_FILE
|
|
|
|
cli = importlib.import_module("cli.build.i18n.__main__")
|
|
|
|
LANGUAGES = """\
|
|
en: {name: English, native: English, direction: ltr, libretranslate: true}
|
|
de: {name: German, native: Deutsch, direction: ltr, libretranslate: true}
|
|
fr: {name: French, native: Français, direction: ltr, libretranslate: true}
|
|
"""
|
|
|
|
|
|
def _catalog(root: Path, code: str, translation: str) -> None:
|
|
catalog = new_catalog("core", code)
|
|
catalog.add("Hello", translation, context="menu:x:title")
|
|
path = catalog_path(root, code, "core")
|
|
path.parent.mkdir(parents=True)
|
|
write_catalog(path, catalog)
|
|
|
|
|
|
class TestTranslate(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self._tmp = TemporaryDirectory()
|
|
self.addCleanup(self._tmp.cleanup)
|
|
self.root = Path(self._tmp.name)
|
|
(self.root / LANGUAGES_FILE).parent.mkdir()
|
|
(self.root / LANGUAGES_FILE).write_text(LANGUAGES, encoding="utf-8")
|
|
_catalog(self.root, "de", "Hallo")
|
|
|
|
def _run(self) -> tuple[int, mock.Mock]:
|
|
started = mock.Mock(return_value=contextlib.nullcontext("http://lt"))
|
|
client = mock.Mock()
|
|
client.return_value.translate.side_effect = lambda texts, code: (
|
|
["Bonjour"] * len(texts)
|
|
)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", self.root),
|
|
mock.patch.object(cli, "pinned_image", return_value="lt:1"),
|
|
mock.patch.object(cli, "container", started),
|
|
mock.patch.object(cli, "LibreTranslate", client),
|
|
):
|
|
return cli.translate("core", []), started
|
|
|
|
def test_only_languages_with_pending_entries_start_the_container(self) -> None:
|
|
_catalog(self.root, "fr", "")
|
|
|
|
status, started = self._run()
|
|
|
|
self.assertEqual(status, 0)
|
|
self.assertEqual(started.call_args.args[1], ["fr"])
|
|
|
|
def test_nothing_pending_starts_no_container(self) -> None:
|
|
_catalog(self.root, "fr", "Bonjour")
|
|
|
|
status, started = self._run()
|
|
|
|
self.assertEqual(status, 0)
|
|
started.assert_not_called()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|