translation of all project, and new translation mechanism
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Проверка каталога переводов. Только чтение; ненулевой код возврата при ошибке.
|
||||
|
||||
Запуск: python3 tools/check_translations.py
|
||||
"""
|
||||
import io, json, os, re, sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
CATALOG = os.path.join(ROOT, "i18n", "translations.json")
|
||||
|
||||
CPP_TEXT = re.compile(r'Loc::text\("([^"]+)"\)')
|
||||
CPP_LIST = re.compile(r'Loc::list\("([^"]+)"\)')
|
||||
QML_PATH = re.compile(r'\bLoc\.t((?:\.[A-Za-z_][A-Za-z0-9_]*)+)')
|
||||
STRAGGLER = re.compile(r'\b(tr|qsTr)\("|QCoreApplication::translate')
|
||||
|
||||
errors, warnings = [], []
|
||||
|
||||
|
||||
def sources(*exts):
|
||||
for name in sorted(os.listdir(ROOT)):
|
||||
if name.endswith(exts):
|
||||
yield name, io.open(os.path.join(ROOT, name), encoding="utf-8").read()
|
||||
|
||||
|
||||
doc = json.load(io.open(CATALOG, encoding="utf-8"))
|
||||
languages = doc["_meta"]["languages"]
|
||||
strings = doc["strings"]
|
||||
|
||||
# 1. Языки: одинаковый набор ключей, ничего не пустует, типы совпадают.
|
||||
for key, entry in strings.items():
|
||||
missing = [c for c in languages if c not in entry]
|
||||
if missing:
|
||||
errors.append(f"{key}: нет языков {missing}")
|
||||
continue
|
||||
kinds = {c: type(entry[c]).__name__ for c in languages}
|
||||
if len(set(kinds.values())) != 1:
|
||||
errors.append(f"{key}: разный тип значения по языкам — {kinds}")
|
||||
continue
|
||||
if isinstance(entry[languages[0]], list):
|
||||
sizes = {c: len(entry[c]) for c in languages}
|
||||
if len(set(sizes.values())) != 1:
|
||||
errors.append(f"{key}: разная длина списка — {sizes}")
|
||||
for c in languages:
|
||||
if any(not v.strip() for v in entry[c]):
|
||||
errors.append(f"{key}: пустой элемент списка в «{c}»")
|
||||
else:
|
||||
for c in languages:
|
||||
if not entry[c].strip():
|
||||
errors.append(f"{key}: пустой перевод в «{c}»")
|
||||
# 2. Набор %N обязан совпадать: порядок слов может отличаться, состав — нет.
|
||||
holders = {c: set(re.findall(r"%\d", entry[c])) for c in languages}
|
||||
if len(set(map(frozenset, holders.values()))) != 1:
|
||||
errors.append(f"{key}: разный набор подстановок — {holders}")
|
||||
|
||||
lists = {k for k, e in strings.items() if isinstance(e[languages[0]], list)}
|
||||
singles = set(strings) - lists
|
||||
|
||||
# 3. Каждый ключ из C++ существует и совпадает по типу.
|
||||
used = set()
|
||||
for name, src in sources(".cpp", ".h"):
|
||||
for key in CPP_TEXT.findall(src):
|
||||
if key in singles:
|
||||
used.add(key)
|
||||
else:
|
||||
errors.append(f"{name}: Loc::text(\"{key}\") — нет такого ключа-строки")
|
||||
for key in CPP_LIST.findall(src):
|
||||
if key in lists:
|
||||
used.add(key)
|
||||
else:
|
||||
errors.append(f"{name}: Loc::list(\"{key}\") — нет такого ключа-списка")
|
||||
|
||||
# 4. Каждый путь Loc.t.a.b.c из QML разворачивается в существующий ключ.
|
||||
for name, src in sources(".qml"):
|
||||
for path in QML_PATH.findall(src):
|
||||
key = path.lstrip(".")
|
||||
if key in strings:
|
||||
used.add(key)
|
||||
continue
|
||||
# Loc.t.a.b.slice(0, 2) и подобное: последний сегмент — метод JS.
|
||||
parent = key.rsplit(".", 1)[0]
|
||||
if parent in strings:
|
||||
used.add(parent)
|
||||
continue
|
||||
errors.append(f"{name}: Loc.t.{key} — нет такого ключа")
|
||||
|
||||
# 5. Ни одного оставшегося tr()/qsTr()/translate.
|
||||
for name, src in sources(".cpp", ".h", ".qml"):
|
||||
for i, line in enumerate(src.splitlines(), 1):
|
||||
if STRAGGLER.search(line):
|
||||
errors.append(f"{name}:{i}: остался вызов старой схемы перевода")
|
||||
|
||||
# 6. Ключи, которые никто не использует, — предупреждение.
|
||||
for key in sorted(set(strings) - used):
|
||||
warnings.append(f"ключ {key} нигде не используется")
|
||||
|
||||
print(f"ключей: {len(strings)} языки: {languages} использовано: {len(used)}")
|
||||
for w in warnings:
|
||||
print(" предупреждение:", w)
|
||||
for e in errors:
|
||||
print(" ОШИБКА:", e)
|
||||
print("ОШИБОК НЕТ" if not errors else f"\nошибок: {len(errors)}")
|
||||
sys.exit(1 if errors else 0)
|
||||
Reference in New Issue
Block a user