#!/usr/bin/env python3
"""
Generate gettext Portable Objects and message catalogs (gettext MO and a
Univention specific JSON-based format) from multiple source files by file type.
"""
#
# SPDX-FileCopyrightText: 2013-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
import os
from collections.abc import Iterable
import polib
from lxml import etree
from . import message_catalogs, umc
[docs]
class UnsupportedSourceType(Exception):
pass
[docs]
class SourceFileSet:
def __init__(self, src_pkg_path: str, binary_pkg_name: str, files: Iterable[str]) -> None:
self.files = files
self.src_pkg_path = src_pkg_path
self.binary_pkg_name = binary_pkg_name
[docs]
def process_po(self, pot_path: str) -> None:
self._create_po_template(pot_path)
[docs]
def process_target(self, po_path: str, output_path: str) -> None:
if os.path.isabs(output_path):
output_path = os.path.relpath(output_path, '/')
output_path = os.path.join(os.getcwd(), 'debian', self.binary_pkg_name, output_path)
self._compile(po_path, output_path)
def _create_po_template(self, pot_path: str) -> None:
raise NotImplementedError()
def _compile(self, po_path: str, output_path: str) -> None:
raise NotImplementedError()
[docs]
class SourceFilesXgettext(SourceFileSet):
def _create_po_file(self, gettext_lang: str, pot_path: str) -> None:
umc.create_po_file(pot_path, self.binary_pkg_name, self.files, language=gettext_lang)
def _compile(self, po_path: str, mo_output_path: str) -> None:
umc.create_mo_file(po_path, mo_output_path)
[docs]
class SourceFilesShell(SourceFilesXgettext):
def _create_po_template(self, pot_path: str) -> None:
super()._create_po_file('Shell', pot_path)
[docs]
class SourceFilesPython(SourceFilesXgettext):
def _create_po_template(self, pot_path: str) -> None:
super()._create_po_file('Python', pot_path)
[docs]
class SourceFilesJavaScript(SourceFilesXgettext):
def _create_po_template(self, pot_path: str) -> None:
super()._create_po_file('JavaScript', pot_path)
def _compile(self, po_path: str, json_output_path: str) -> None:
"""
With UMC and univention-web based applications a custom, JSON-based
message format is used.
"""
umc.po_to_json(po_path, json_output_path)
[docs]
class SourceFilesHTML(SourceFileSet):
def _create_po_template(self, pot_path: str) -> None:
po_template = polib.POFile()
html_parser = etree.HTMLParser()
js_paths: list[str] = []
for html_path in self.files:
with open(html_path, 'rb') as html_file:
tree = etree.parse(html_file, html_parser) # noqa: S320
for element in tree.xpath('//*[@data-i18n]'):
msgid = element.get('data-i18n')
loc = (os.path.basename(html_path), element.sourceline)
entry = po_template.find(msgid)
if entry:
if loc not in entry.occurrences:
entry.occurrences.append(loc)
else:
new_entry = polib.POEntry(msgid=msgid, occurrences=[loc])
po_template.append(new_entry)
if tree.xpath('//script'):
js_paths.append(html_path)
po_template.save(pot_path)
# Inline JavaScript may use underscorce function, e.g. univention/management/index.html
if js_paths:
message_catalogs.join_existing('JavaScript', pot_path, js_paths)
def _compile(self, po_path: str, json_output_path: str) -> None:
umc.po_to_json(po_path, json_output_path)
[docs]
class SourceFileSetCreator:
process_by_type = {
'text/x-shellscript': SourceFilesShell,
'text/x-python': SourceFilesPython,
'text/html': SourceFilesHTML,
'application/javascript': SourceFilesJavaScript}
[docs]
@classmethod
def from_mimetype(cls, src_pkg_path: str, binary_pkg_name: str, mimetype: str, files: Iterable[str]) -> SourceFileSet:
try:
obj = cls.process_by_type[mimetype](src_pkg_path, binary_pkg_name, files)
except KeyError:
raise UnsupportedSourceType(files)
else:
return obj
[docs]
def from_mimetype(src_pkg_path: str, binary_pkg_name: str, mimetype: str, files: Iterable[str]) -> SourceFileSet:
return SourceFileSetCreator.from_mimetype(src_pkg_path, binary_pkg_name, mimetype, files)