fix: 用纯Python解析.po文件,去掉msgfmt/polib依赖确保设备上翻译正常加载

This commit is contained in:
1okko
2026-06-15 21:50:37 +08:00
parent 0ee81b0a59
commit 77120beecb
+138 -30
View File
@@ -1,6 +1,6 @@
import gettext
import os
import subprocess
import re
from openpilot.system.ui.lib.multilang import (
multilang as base_multilang,
TRANSLATIONS_DIR,
@@ -8,11 +8,141 @@ from openpilot.system.ui.lib.multilang import (
)
def _parse_po(po_path):
"""Pure Python .po file parser - returns dict of msgid -> msgstr."""
catalog = {}
with open(po_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
i = 0
n = len(lines)
while i < n:
line = lines[i]
# Skip comments (#, #., #:, #|, #~)
if line.startswith('#'):
i += 1
continue
# Blank line
if line.strip() == '':
i += 1
continue
# Try to match msgid
msgid_match = re.match(r'^msgid\s+"(.*)"\s*$', line)
if msgid_match:
msgid = msgid_match.group(1)
i += 1
# Collect continued msgid strings
while i < n:
cont = re.match(r'^"(.*)"\s*$', lines[i])
if cont:
msgid += cont.group(1)
i += 1
else:
break
# Now expect msgid_plural or msgstr
if i < n and lines[i].startswith('msgid_plural'):
# Plural form
plur_match = re.match(r'^msgid_plural\s+"(.*)"\s*$', lines[i])
msgid_plural = plur_match.group(1) if plur_match else ''
i += 1
while i < n:
cont = re.match(r'^"(.*)"\s*$', lines[i])
if cont:
msgid_plural += cont.group(1)
i += 1
else:
break
# Read msgstr[0], msgstr[1], ...
plural_strs = {}
while i < n:
p_match = re.match(r'^msgstr\[(\d+)\]\s+"(.*)"\s*$', lines[i])
if p_match:
idx = int(p_match.group(1))
val = p_match.group(2)
i += 1
while i < n:
cont = re.match(r'^"(.*)"\s*$', lines[i])
if cont:
val += cont.group(1)
i += 1
else:
break
plural_strs[idx] = val
else:
break
# Store: both singular and plural keys for N=1
s_val = plural_strs.get(0, msgid)
catalog[msgid] = s_val
catalog[msgid + '\x00' + msgid_plural] = plural_strs.get(1, msgid_plural)
else:
# Singular form
if i < n:
str_match = re.match(r'^msgstr\s+"(.*)"\s*$', lines[i])
if str_match:
msgstr = str_match.group(1)
i += 1
else:
# Maybe msgstr is empty on same line
empty_match = re.match(r'^msgstr\s+""\s*$', lines[i])
if empty_match:
msgstr = ''
i += 1
else:
msgstr = ''
# Don't advance - next loop handles it
# Collect continued msgstr strings
while i < n:
cont = re.match(r'^"(.*)"\s*$', lines[i])
if cont:
msgstr += cont.group(1)
i += 1
else:
break
else:
msgstr = ''
# Skip header entry (msgid ""), don't add to catalog
if msgid != '':
catalog[msgid] = msgstr
else:
i += 1
return catalog
class PoTranslations(gettext.NullTranslations):
"""Pure Python .po parser - no msgfmt/polib needed."""
def __init__(self, po_path):
super().__init__()
self._catalog = _parse_po(po_path)
def gettext(self, msgid):
return self._catalog.get(msgid, msgid)
def ngettext(self, singular, plural, n):
return self._catalog.get(singular + '\x00' + plural,
self._catalog.get(singular,
singular if n == 1 else plural))
class DpMultilang:
"""Wrapper that syncs with base multilang and adds dragonpilot translations."""
def __init__(self):
self._dragon_translation: gettext.NullTranslations | gettext.GNUTranslations = gettext.NullTranslations()
self._dragon_translation: gettext.NullTranslations | PoTranslations = gettext.NullTranslations()
self._loaded_language: str = ""
@property
@@ -30,35 +160,13 @@ class DpMultilang:
current_lang = base_multilang.language
if current_lang != self._loaded_language:
self._loaded_language = current_lang
mo_path = TRANSLATIONS_DIR.joinpath(f'dragonpilot_{current_lang}.mo')
po_path = TRANSLATIONS_DIR.joinpath(f'dragonpilot_{current_lang}.po')
try:
with mo_path.open('rb') as fh:
self._dragon_translation = gettext.GNUTranslations(fh)
except FileNotFoundError:
# Auto-compile .po to .mo if source exists
if po_path.exists():
try:
# Try msgfmt first (faster, available on device)
result = subprocess.run(['msgfmt', str(po_path), '-o', str(mo_path)], capture_output=True)
if result.returncode == 0 and mo_path.exists():
with mo_path.open('rb') as fh:
self._dragon_translation = gettext.GNUTranslations(fh)
return
except FileNotFoundError:
pass
# Fallback: try polib
try:
import polib
po = polib.pofile(str(po_path))
po.save_as_mofile(str(mo_path))
with mo_path.open('rb') as fh:
self._dragon_translation = gettext.GNUTranslations(fh)
return
except ImportError:
pass
except Exception:
pass
if po_path.exists():
try:
self._dragon_translation = PoTranslations(po_path)
except Exception:
self._dragon_translation = gettext.NullTranslations()
else:
self._dragon_translation = gettext.NullTranslations()
def tr(self, text: str) -> str: