#!/usr/bin/python3# SPDX-License-Identifier: LGPL-2.1-or-later# Extracts translatable strings from HTML files in the following forms:## <tag translate>String</tag># <tag translate context="value">String</tag>## 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 argparseimport collections.abcimport jsonimport pathlibfrom collections import defaultdictfrom html.parser import HTMLParserPO_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 = Nonefilename: strdef __init__(self, filename: str):self.strings = defaultdict[tuple[str | None, str], set[str]](set)self.filename = filenamesuper().__init__()def handle_starttag(self, tag, attrs):translatable = Falsecontext = None# attrs => (key, value) where value needs to be splitfor (attr, value) in attrs:if attr == 'translate-context':context = valueelif attr == 'context':context = valueelif attr == 'translate' and (value is None or 'yes' in value.split(' ')):translatable = Trueif not translatable:returnlineno = 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_attrself.strings[context, data].add(filename)self.current_attr = Nonedef 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.filesfor file in files:# Qualify the filename if necessaryfull_path = args.directory / file if args.directory else filehtmlparser = 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 contextif 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()