You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/tests/translations.py

151 lines
4.6 KiB

import json
import typing as t
from hashlib import sha256
from pathlib import Path
from trezorlib import cosi, device, models
from trezorlib._internal import translations
from trezorlib.debuglink import TrezorClientDebugLink as Client
from . import common
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent
TRANSLATIONS = ROOT / "core" / "translations"
FONTS_DIR = TRANSLATIONS / "fonts"
ORDER_FILE = TRANSLATIONS / "order.json"
LANGUAGES = [file.stem for file in TRANSLATIONS.glob("??.json")]
def build_and_sign_blob(
lang_or_def: translations.JsonDef | Path | str,
model: models.TrezorModel,
) -> bytes:
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
version = translations.version_from_json(lang_or_def["header"]["version"])
blob = translations.blob_from_defs(lang_or_def, order, model, version, FONTS_DIR)
# 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 set_language(client: Client, lang: str):
if lang.startswith("en"):
language_data = b""
else:
language_data = build_and_sign_blob(lang, client.model)
with client:
device.change_language(client, language_data) # type: ignore
def get_lang_json(lang: str) -> translations.JsonDef:
assert lang in LANGUAGES
return json.loads((TRANSLATIONS / f"{lang}.json").read_text())
def _get_all_language_data() -> list[dict[str, str]]:
return [_get_language_data(language) for language in LANGUAGES]
def _get_language_data(lang: str) -> dict[str, str]:
return get_lang_json(lang)["translations"]
all_language_data = _get_all_language_data()
def _resolve_path_to_texts(
path: str, template: t.Iterable[t.Any] = (), lower: bool = True
) -> list[str]:
texts: list[str] = []
lookups = path.split(".")
for language_data in all_language_data:
data: dict[str, t.Any] | str = language_data
for lookup in lookups:
assert isinstance(data, dict), f"{lookup} is not a dict"
data = data[lookup]
assert isinstance(data, str), f"{path} is not a string"
if template:
data = data.format(*template)
texts.append(data)
if lower:
texts = [t.lower() for t in texts]
texts = [t.strip() for t in texts]
return texts
def assert_equals(text: str, path: str, template: t.Iterable[t.Any] = ()) -> None:
# TODO: we can directly pass in the current device language
texts = _resolve_path_to_texts(path, template)
assert text.lower() in texts, f"{text} not found in {texts}"
def assert_equals_multiple(
text: str, paths: list[str], template: t.Iterable[t.Any] = ()
) -> None:
texts: list[str] = []
for path in paths:
texts += _resolve_path_to_texts(path, template)
assert text.lower() in texts, f"{text} not found in {texts}"
def assert_in(text: str, path: str, template: t.Iterable[t.Any] = ()) -> None:
texts = _resolve_path_to_texts(path, template)
for tt in texts:
if tt in text.lower():
return
assert False, f"{text} not found in {texts}"
def assert_in_multiple(
text: str, paths: list[str], template: t.Iterable[t.Any] = ()
) -> None:
texts: list[str] = []
for path in paths:
texts += _resolve_path_to_texts(path, template)
for tt in texts:
if tt in text.lower():
return
assert False, f"{text} not found in {texts}"
def assert_startswith(text: str, path: str, template: t.Iterable[t.Any] = ()) -> None:
texts = _resolve_path_to_texts(path, template)
for tt in texts:
if text.lower().startswith(tt):
return
assert False, f"{text} not found in {texts}"
def assert_template(text: str, template_path: str) -> None:
templates = _resolve_path_to_texts(template_path)
for tt in templates:
# Checking at least the first part
first_part = tt.split("{")[0]
if text.lower().startswith(first_part):
return
assert False, f"{text} not found in {templates}"
def translate(
path: str, template: t.Iterable[t.Any] = (), lower: bool = False
) -> list[str]:
# Do not converting to lowercase, we want the exact value
return _resolve_path_to_texts(path, template, lower=lower)