ci(core): add a workflow to generate translation context
What changed, and why it matters
This commit adds a new GitHub Actions workflow that builds Trezor firmware emulators, runs two selected UI tests in different languages, and uploads screenshot reports to an Amazon S3 bucket. It also adds a helper script that formats a comment with links to those reports, a list specifying which tests to run, and a minor change to slow down the animated GIF viewer from 200 ms to 1000 ms per frame. There is nothing in the changes that alters how the Trezor device handles secrets, cryptography, user input, or firmware security.
No security action required. This is a CI/localization tooling change. Reviewers may want to confirm the S3 upload path and IAM role are intended for public dev data, which the bucket name and role naming suggest.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff introduces .github/workflows/crowdin-ui-check.yml, a manually-triggered workflow for generating localization context. It builds debug emulators for four models, runs the tests listed in tests/context_tests.txt (two PIN-related UI tests) under TEST_LANG=en and a chosen language, then uploads HTML reports to s3://data.trezor.io/dev/firmware/ui_report/. ci/make_crowdin_comment.py generates a markdown summary linking to the uploaded reports. tests/context_tests.txt selects test_correct_pin and test_pin_short. tests/ui_tests/reporting/create-gif.js only changes the default animation delay from 200 ms to 1000 ms. No firmware source code, cryptographic code, or secret-handling code is modified.
Changed components
GitHub Actions CI workflow (.github/workflows/crowdin-ui-check.yml)CI helper script (ci/make_crowdin_comment.py)UI test selection list (tests/context_tests.txt)UI test reporting GIF viewer (tests/ui_tests/reporting/create-gif.js)Inspect captured patch +182 / −3
diff --git a/.github/workflows/crowdin-ui-check.yml b/.github/workflows/crowdin-ui-check.yml
new file mode 100644
index 00000000..77f74834
--- /dev/null
+++ b/.github/workflows/crowdin-ui-check.yml
@@ -0,0 +1,117 @@
+name: Crowdin - context generation (manual)
+run-name: Crowdin - <${{ inputs.language }}> language context generation
+
+on:
+ workflow_dispatch:
+ inputs:
+ language:
+ description: Select a language to generate UI flows for
+ required: true
+ type: choice
+ default: cs
+ options:
+ - cs
+ - fr
+ - de
+ - es
+ - pt
+
+permissions:
+ id-token: write # for fetching OIDC token for AWS
+
+env:
+ CROWDIN_COMMENT_PATH: ${{ github.workspace }}/tests/core-crowdin-comment.md
+ TREZOR_PYTEST_LOGS_DIR: ${{ github.workspace }}/tests/
+
+jobs:
+ core_emu:
+ name: Build emulator (${{ matrix.model }}, universal, debuglink, noasan)
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ model: [T2T1, T3B1, T3T1, T3W1]
+ env:
+ TREZOR_MODEL: ${{ matrix.model }}
+ BITCOIN_ONLY: 0
+ PYOPT: 0
+ ADDRESS_SANITIZER: 0
+ QUIET_MODE: 1
+ DISABLE_TROPIC: 0
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ submodules: recursive
+ - uses: ./.github/actions/environment
+ - run: nix-shell --run "uv run make -C core build_unix_frozen"
+ - uses: actions/upload-artifact@v6
+ with:
+ name: core-emu-${{ matrix.model }}-universal-debuglink-noasan
+ path: |
+ core/build/unix/trezor-emu-core*
+ retention-days: 7
+
+ # Context tests for Core. Generates screenshots of UI flows of curated list of tests
+ # for selected language. To be used as context for Crowdin localization work.
+ core_context_tests:
+ name: Context tests (${{ matrix.model }}, universal, noasan, ${{ matrix.language }})
+ runs-on: ubuntu-latest
+ needs: core_emu
+ strategy:
+ fail-fast: false
+ matrix:
+ model: [T2T1, T3B1, T3T1, T3W1]
+ language: ["en", "${{ inputs.language }}"]
+
+ env:
+ TREZOR_PROFILING: 0
+ TREZOR_MODEL: ${{ matrix.model }}
+ ADDRESS_SANITIZER: 0
+ PYTEST_TIMEOUT: 400
+ TEST_LANG: ${{ matrix.language }}
+ TESTOPTS: "@../tests/context_tests.txt --ui=test"
+
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ submodules: recursive
+ - uses: actions/download-artifact@v7
+ with:
+ name: core-emu-${{ matrix.model }}-universal-debuglink-noasan
+ path: core/build/unix
+ - run: chmod +x core/build/unix/trezor-emu-core*
+ - uses: ./.github/actions/environment
+ - run: nix-shell --run "uv run make -C core test_emu_multicore"
+ - run: tail -v -n50 tests/trezor*.log || true
+ if: failure()
+ - uses: ./.github/actions/ui-report
+ with:
+ model: ${{ matrix.model }}
+ lang: ${{ matrix.language }}
+ status: ${{ job.status }}
+ if: always()
+ continue-on-error: true
+
+ core_ui_comment:
+ name: Comment with UI flows
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - run: sleep 1m # to avoid GitHub API rate limit
+ - run: |
+ python ci/make_crowdin_comment.py "${{ github.run_id }}" '["en", "${{ inputs.language }}"]' > ${{ env.CROWDIN_COMMENT_PATH }}
+ cat ${{ env.CROWDIN_COMMENT_PATH }} >> $GITHUB_STEP_SUMMARY
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: arn:aws:iam::538326561891:role/gh_actions_deploy_dev_firmware_data
+ aws-region: eu-west-1
+ - run: python ci/make_summary_htmls.py "index" '["en", "${{ inputs.language }}"]' "tests/ui_tests/reporting/"
+ - run: python ci/make_summary_htmls.py "diff" '["en", "${{ inputs.language }}"]' "tests/ui_tests/reporting/"
+ - name: Upload per-language HTML summaries to S3
+ run: |
+ cd tests/ui_tests/reporting/
+ for F in *.html
+ do
+ aws s3 cp $F s3://data.trezor.io/dev/firmware/ui_report/${{ github.run_id }}/$F
+ done
diff --git a/ci/make_crowdin_comment.py b/ci/make_crowdin_comment.py
new file mode 100644
index 00000000..6b0dcaf8
--- /dev/null
+++ b/ci/make_crowdin_comment.py
@@ -0,0 +1,60 @@
+import json
+import sys
+
+(RUN_ID, LANGS_JSON) = sys.argv[1:]
+MAIN = json.loads(LANGS_JSON)
+
+REPORT_URL = f"https://data.trezor.io/dev/firmware/ui_report/{RUN_ID}"
+CI_RUN_URL = f"https://github.com/trezor/trezor-firmware/actions/runs/{RUN_ID}"
+MODELS_INFO = [
+ {"id": "T2T1", "name": "Trezor Model T", "layout": "Bolt"},
+ {"id": "T3B1", "name": "Trezor Safe 3", "layout": "Caesar"},
+ {"id": "T3T1", "name": "Trezor Safe 5", "layout": "Delizia"},
+ {"id": "T3W1", "name": "Trezor Safe 7", "layout": "Eckhart"},
+]
+LANG_NAMES = {
+ "cs": "Czech",
+ "de": "German",
+ "en": "English",
+ "es": "Spanish",
+ "fr": "French",
+ "pt": "Portuguese",
+}
+
+
+def display_lang(code: str) -> str:
+ return LANG_NAMES.get(code, code) # fallback to the code if unknown
+
+
+def main():
+ # a special marker for finding this comment (via CI)
+ print("<!-- ui-comment-Crowdin -->")
+ for lang in MAIN:
+ print_table(lang)
+
+
+def format_table_row(cells: list[str]) -> str:
+ """Format a list of cells as a markdown table row with proper delimiters."""
+ return "| " + " | ".join(cells) + " |"
+
+
+def print_table(lang):
+ print(f"\n# `{display_lang(lang)}`\n")
+
+ header = ["layout", "context tests"]
+
+ print(format_table_row(header))
+ print(format_table_row(["---"] * len(header)))
+
+ for model in MODELS_INFO:
+ test_prefix = f"{REPORT_URL}/{model['id']}-{lang}-core_context_tests"
+ row = [
+ f"{model['layout']} / {model['name']}",
+ f"[UI flows]({test_prefix}-index.html)",
+ ]
+
+ print(format_table_row(row))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/context_tests.txt b/tests/context_tests.txt
new file mode 100644
index 00000000..4e6eaea4
--- /dev/null
+++ b/tests/context_tests.txt
@@ -0,0 +1,2 @@
+../tests/device_tests/test_pin.py::test_correct_pin
+../tests/click_tests/test_pin.py::test_pin_short
diff --git a/tests/ui_tests/reporting/create-gif.js b/tests/ui_tests/reporting/create-gif.js
index a7d91413..5153baac 100644
--- a/tests/ui_tests/reporting/create-gif.js
+++ b/tests/ui_tests/reporting/create-gif.js
@@ -2,8 +2,8 @@
function createGif() {
// Finds all the screenshots on the screen, creates a new img
- // element at the top and switches the src attribute every 200ms
- // to create a notion of GIF.
+ // element at the top and switches the src attribute periodically
+ // (interval based on defaultDelay) to create a notion of GIF.
// Adds some controlling possibilities - buttons, input fields
// and sliders to enable pausing, stepping back and forth, changing
// the delay, etc.
@@ -28,7 +28,7 @@ function createGif() {
const delayText = 'Delay (ms):';
const sliderText = 'Progress:';
- const defaultDelay = 200;
+ const defaultDelay = 1000;
const keyboardShortcutPrev = 'ArrowLeft';
const keyboardShortcutNext = 'ArrowRight';
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.