#!/usr/bin/env python3 """Minimal POT generator for this plugin (fallback when WP-CLI is unavailable). Usage: python3 bin/make-pot.py Prefer `wp i18n make-pot . languages/m365-login.pot` when WP-CLI is installed. """ import os import re import sys from collections import OrderedDict ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DOMAIN = "m365-login" FUNCS = r"(?:__|_e|esc_html__|esc_html_e|esc_attr__|esc_attr_e|_x|_ex|esc_html_x|esc_attr_x|_n|_nx)" STR = r"(?:'((?:[^'\\]|\\.)*)'|\"((?:[^\"\\]|\\.)*)\")" PATTERN = re.compile(FUNCS + r"\s*\(\s*" + STR + r"(?:\s*,\s*" + STR + r")?(?:\s*,\s*" + STR + r")?\s*[,)]", re.S) COMMENT = re.compile(r"/\*\s*translators:\s*(.*?)\*/", re.S) def unescape(s): return s.replace("\\'", "'").replace('\\"', '"').replace("\\\\", "\\") def po_escape(s): return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") entries = OrderedDict() for dirpath, dirnames, filenames in os.walk(ROOT): dirnames[:] = [d for d in dirnames if d not in (".git", "vendor", "node_modules", "build", "bin", "docs", ".github", ".wordpress-org")] for fn in sorted(filenames): if not fn.endswith(".php"): continue path = os.path.join(dirpath, fn) rel = os.path.relpath(path, ROOT) src = open(path, encoding="utf-8").read() for m in PATTERN.finditer(src): func = m.group(0).split("(")[0].strip() msgid = unescape(m.group(1) if m.group(1) is not None else m.group(2)) second = m.group(3) if m.group(3) is not None else m.group(4) third = m.group(5) if m.group(5) is not None else m.group(6) context = None plural = None if func in ("_x", "_ex", "esc_html_x", "esc_attr_x"): context = unescape(second) if second is not None else None elif func in ("_n",): plural = unescape(second) if second is not None else None elif func == "_nx": plural = unescape(second) if second is not None else None context = unescape(third) if third is not None else None line = src.count("\n", 0, m.start()) + 1 before = src[max(0, m.start() - 400):m.start()] cm = COMMENT.findall(before) comment = " ".join(cm[-1].split()) if cm else None key = (context, msgid, plural) e = entries.setdefault(key, {"refs": [], "comment": None}) e["refs"].append("%s:%d" % (rel, line)) if comment: e["comment"] = comment header = '''# Copyright (C) 2026 friloo # This file is distributed under the GPL-2.0-or-later. msgid "" msgstr "" "Project-Id-Version: M365 Login 1.1.0\\n" "Report-Msgid-Bugs-To: https://github.com/friloo/wp-m365-login/issues\\n" "MIME-Version: 1.0\\n" "Content-Type: text/plain; charset=UTF-8\\n" "Content-Transfer-Encoding: 8bit\\n" "POT-Creation-Date: 2026-09-23T00:00:00+00:00\\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" "Last-Translator: FULL NAME \\n" "Language-Team: LANGUAGE \\n" "X-Generator: bin/make-pot.py\\n" "X-Domain: m365-login\\n" ''' out = [header] for (context, msgid, plural), e in entries.items(): if e["comment"]: out.append("#. translators: %s\n" % e["comment"]) out.append("#: %s\n" % " ".join(e["refs"])) if context: out.append('msgctxt "%s"\n' % po_escape(context)) out.append('msgid "%s"\n' % po_escape(msgid)) if plural: out.append('msgid_plural "%s"\n' % po_escape(plural)) out.append('msgstr[0] ""\nmsgstr[1] ""\n\n') else: out.append('msgstr ""\n\n') dest = os.path.join(ROOT, "languages", DOMAIN + ".pot") open(dest, "w", encoding="utf-8").write("".join(out)) print("Wrote %s (%d strings)" % (os.path.relpath(dest, ROOT), len(entries)))