1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-05 05:58:23 +00:00
trezor-firmware/tests/translations.py
2024-11-28 13:59:17 +01:00

135 lines
4.2 KiB
Python

import json
import re
import threading
import typing as t
import warnings
from hashlib import sha256
from pathlib import Path
from trezorlib import cosi, device, models
from trezorlib._internal import translations
from trezorlib.debuglink import SessionDebugWrapper as Session
from . import common
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent
TRANSLATIONS_DIR = ROOT / "core" / "translations"
FONTS_DIR = TRANSLATIONS_DIR / "fonts"
ORDER_FILE = TRANSLATIONS_DIR / "order.json"
LANGUAGES = [file.stem for file in TRANSLATIONS_DIR.glob("??.json")]
_CURRENT_TRANSLATION = threading.local()
def prepare_blob(
lang_or_def: translations.JsonDef | Path | str,
model: models.TrezorModel,
version: translations.VersionTuple | tuple[int, int, int] | None = None,
) -> translations.TranslationsBlob:
order = translations.order_from_json(json.loads(ORDER_FILE.read_text()))
if isinstance(lang_or_def, str):
lang_or_def = get_lang_json(lang_or_def)
if isinstance(lang_or_def, Path):
lang_or_def = t.cast(translations.JsonDef, json.loads(lang_or_def.read_text()))
# generate raw blob
if version is None:
version = translations.version_from_json(lang_or_def["header"]["version"])
elif len(version) == 3:
# version coming from client object does not have build item
version = *version, 0
return translations.blob_from_defs(lang_or_def, order, model, version, FONTS_DIR)
def sign_blob(blob: translations.TranslationsBlob) -> bytes:
# build 0-item Merkle proof
digest = sha256(b"\x00" + blob.header_bytes).digest()
signature = cosi.sign_with_privkeys(digest, common.PRIVATE_KEYS_DEV)
blob.proof = translations.Proof(
merkle_proof=[],
sigmask=0b111,
signature=signature,
)
return blob.build()
def build_and_sign_blob(
lang_or_def: translations.JsonDef | Path | str,
session: Session,
) -> bytes:
blob = prepare_blob(lang_or_def, session.model, session.version)
return sign_blob(blob)
def set_language(session: Session, lang: str):
if lang.startswith("en"):
language_data = b""
else:
language_data = build_and_sign_blob(lang, session)
with session:
device.change_language(session, language_data) # type: ignore
def get_lang_json(lang: str) -> translations.JsonDef:
assert lang in LANGUAGES
lang_json = json.loads((TRANSLATIONS_DIR / f"{lang}.json").read_text())
if (fonts_safe3 := lang_json.get("fonts", {}).get("##Safe3")) is not None:
lang_json["fonts"]["T2B1"] = fonts_safe3
lang_json["fonts"]["T3B1"] = fonts_safe3
return lang_json
class Translation:
FORMAT_STR_RE = re.compile(r"\\{\d+\\}")
def __init__(self, lang: str) -> None:
self.lang = lang
self.lang_json = get_lang_json(lang)
@property
def translations(self) -> dict[str, str]:
return self.lang_json["translations"]
def _translate_raw(self, key: str, _stacklevel: int = 0) -> str:
tr = self.translations.get(key)
if tr is not None:
return tr
if self.lang != "en":
warnings.warn(
f"Translation key '{key}' not found in '{self.lang}' translation file",
stacklevel=_stacklevel + 2,
)
return TRANSLATIONS["en"]._translate_raw(key)
raise KeyError(key)
def translate(self, key: str, _stacklevel: int = 0) -> str:
tr = self._translate_raw(key, _stacklevel=_stacklevel + 1)
return tr.replace("\xa0", " ").strip()
def as_regexp(self, key: str, _stacklevel: int = 0) -> re.Pattern:
tr = self.translate(key, _stacklevel=_stacklevel + 1)
re_safe = re.escape(tr)
return re.compile(self.FORMAT_STR_RE.sub(r".*?", re_safe))
TRANSLATIONS = {lang: Translation(lang) for lang in LANGUAGES}
_CURRENT_TRANSLATION.TR = TRANSLATIONS["en"]
def translate(key: str, _stacklevel: int = 0) -> str:
return _CURRENT_TRANSLATION.TR.translate(key, _stacklevel=_stacklevel + 1)
def regexp(key: str) -> re.Pattern:
return _CURRENT_TRANSLATION.TR.as_regexp(key, _stacklevel=1)
def __getattr__(key: str) -> str:
try:
return translate(key, _stacklevel=1)
except KeyError as e:
raise AttributeError(f"Translation key '{key}' not found") from e