#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1-or-later # Extracts translatable strings from HTML files in the following forms: # # String # String # # Note that some of the use of the translated may not support all the strings # depending on the code actually using these strings to translate the HTML. import argparse import collections.abc import json import pathlib from collections import defaultdict from html.parser import HTMLParser PO_HEADER = r"""msgid "" msgstr "" "Project-Id-Version: PACKAGE_VERSION\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Cockpit html2po\n" """ class MyHTMLParser(HTMLParser): current_attr: tuple[str | None, str] | None = None filename: str def __init__(self, filename: str): self.strings = defaultdict[tuple[str | None, str], set[str]](set) self.filename = filename super().__init__() def handle_starttag(self, tag, attrs): translatable = False context = None # attrs => (key, value) where value needs to be split for (attr, value) in attrs: if attr == 'translate-context': context = value elif attr == 'context': context = value elif attr == 'translate' and (value is None or 'yes' in value.split(' ')): translatable = True if not translatable: return lineno = self.getpos()[0] filename = f'{self.filename}:{lineno}' self.current_attr = (context, filename) def handle_data(self, data: str): if self.current_attr and data: context, filename = self.current_attr self.strings[context, data].add(filename) self.current_attr = None def main() -> None: parser = argparse.ArgumentParser(prog='html2po', description='Extracts translatable strings from HTML files') parser.add_argument('-d', '--directory', help='Base directory for input files') parser.add_argument('-o', '--output', help='Output file') parser.add_argument('files', nargs='+', help='One or more input files', type=pathlib.Path, metavar='FILE') args = parser.parse_args() strings = defaultdict[tuple[str | None, str], set[str]](set) files: collections.abc.Iterable[pathlib.Path] = args.files for file in files: # Qualify the filename if necessary full_path = args.directory / file if args.directory else file htmlparser = MyHTMLParser(str(file)) with open(full_path, 'r') as fp: htmlparser.feed(fp.read()) for ctx_msgid, filename in htmlparser.strings.items(): strings[ctx_msgid].update(filename) with open(args.output, 'w') as fp: fp.write(PO_HEADER) for (context, msgid), filenames in strings.items(): fp.write(f"\n#: {' '.join(filenames)}\n") # json.dumps() to escape any quotes in translation text or context if context: fp.write(f'msgctxt {json.dumps(context)}\n') fp.write(f'msgid {json.dumps(msgid)}\n') fp.write('msgstr ""\n') if __name__ == "__main__": main()