HesabixChatPlugin/bin/po2mo.py
2026-04-26 04:43:33 +03:30

130 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""
مبدل PO → MO (GNU) برای فایل‌های افزونه؛ بدون وابستگی.
"""
import re
import struct
import sys
from pathlib import Path
def _unescape_one(s: str) -> str:
return (
s.replace("\\\\", "\\")
.replace('\\"', '"')
.replace("\\n", "\n")
.replace("\\t", "\t")
)
def _collect_quoted_parts(after_kw: str) -> list[str]:
return [
_unescape_one(m.group("s"))
for m in re.finditer(
r'"(?P<s>(?:\\.|[^"\\])*)"', after_kw
)
]
def _read_string_continuation(lines: list[str], start: int) -> tuple[str, int]:
"""
خط start باید با msgid یا msgstr شروع شود. مقدار کامل رشته و ایندکس خط بعد از بلوک.
"""
line = lines[start]
for kw in ("msgid ", "msgstr "):
if line.lstrip().startswith(kw):
after = line.split(kw, 1)[1]
segs: list[str] = []
segs.extend(_collect_quoted_parts(after))
nxt = start + 1
while nxt < len(lines):
s = lines[nxt].lstrip()
if s.startswith('"'):
segs.extend(_collect_quoted_parts(s))
nxt += 1
else:
break
return "".join(segs), nxt
return "", start + 1
def parse_po(text: str) -> list[tuple[bytes, bytes]]:
lines: list[str] = []
for raw in text.splitlines():
t = raw.strip()
if t.startswith("#") or t == "":
continue
lines.append(raw)
out: list[tuple[bytes, bytes]] = []
i = 0
while i < len(lines):
if not lines[i].lstrip().startswith("msgid "):
i += 1
continue
msgid, j = _read_string_continuation(lines, i)
if j >= len(lines) or not lines[j].lstrip().startswith("msgstr "):
i += 1
continue
msgstr, k = _read_string_continuation(lines, j)
out.append((msgid.encode("utf-8"), msgstr.encode("utf-8")))
i = k
return out
def build_mo(entries: list[tuple[bytes, bytes]]) -> bytes:
pairs = sorted(entries, key=lambda p: p[0])
n = len(pairs)
o_table_off = 7 * 4
t_table_off = o_table_off + 8 * n
str_start = t_table_off + 8 * n
o_keys: list[tuple[int, int]] = []
t_keys: list[tuple[int, int]] = []
blob = b""
# طول و بایت رشته بدون تغییری که Python gettext (و C) انتظار دارند: بدون nul اضافه
# در بافر و با mlen=0 فقط برای msgid هدر
pos = str_start
for k, v in pairs:
o_keys.append((len(k), pos))
if k:
blob += k
pos += len(k)
for k, v in pairs:
t_keys.append((len(v), pos))
if v:
blob += v
pos += len(v)
o_tab = b"".join(struct.pack("<II", a, b) for a, b in o_keys)
t_tab = b"".join(struct.pack("<II", a, b) for a, b in t_keys)
header = struct.pack(
"<7I",
0x950412DE,
0,
n,
o_table_off,
t_table_off,
0,
0,
)
out = header + o_tab + t_tab + blob
# مفسر gettext پایتون به شرط mend < buflen نیاز دارد (نه ==)
return out + b"\0"
def main() -> int:
if len(sys.argv) < 2:
print("Usage: po2mo.py <file.po> [out.mo]", file=sys.stderr)
return 1
po_path = Path(sys.argv[1])
mo_path = Path(sys.argv[2]) if len(sys.argv) > 2 else po_path.with_suffix(".mo")
text = po_path.read_text(encoding="utf-8")
entries = parse_po(text)
if not entries:
print("No entries in", po_path, file=sys.stderr)
return 1
mo_path.write_bytes(build_mo(entries))
print("Wrote", mo_path, "({} entries)".format(len(entries)))
return 0
if __name__ == "__main__":
raise SystemExit(main())