Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d83515c
Created language file synch-tool
maggo83 Jan 15, 2026
f04ccc6
Added helper to synchronize language files for i18n with actual gui s…
maggo83 Jan 15, 2026
5e805e0
Removed unnecessary try/except logic from sync_i18n.py
maggo83 Jan 16, 2026
1027e88
feat: add MockUI firmware scenario for STM32F469 Discovery
Jan 29, 2026
ab2d58b
Icon: Removed unnecessary "color" argument from create_icon_from_bitm…
maggo83 Jan 30, 2026
2c17ec7
feat: implement binary i18n system for flash storage
maggo83 Feb 2, 2026
33be125
tentatively replaces platform with os for filesystem operations in i1…
maggo83 Feb 3, 2026
a381c71
refactored lang_compiler.py to have better interface to gui at runtim…
maggo83 Feb 4, 2026
6997c28
updated i18n_manager to properly use flash file system and interactio…
maggo83 Feb 4, 2026
edb6b4c
removed non-micropython code from lang_compiler
maggo83 Feb 4, 2026
22605b4
Added LED debugging tool
maggo83 Feb 5, 2026
1bbc449
Fixed Bug in status bar to handle no available language
maggo83 Feb 5, 2026
ee533ca
Fixed build for i18n: changed precompiled filesystem to FAT + related…
maggo83 Feb 19, 2026
d5ca7d3
Replaced hardcoded language_code -> language_name mapping with lookup…
maggo83 Feb 19, 2026
6dec594
Moved led_debug.py to /tools
maggo83 Feb 19, 2026
3f271d0
Moved language files (JSON) into own folder
maggo83 Feb 20, 2026
f925461
Updated README.md
maggo83 Feb 20, 2026
e124cd8
Updated / Added Units Tests
maggo83 Feb 20, 2026
9cd45cd
Added HiL tests on actual device
maggo83 Feb 20, 2026
6350689
Enable USB at startup
maggo83 Feb 20, 2026
d917373
feat: add MockUI firmware for STM32F469 Discovery
Jan 29, 2026
2f0a6c4
feat: implement binary i18n system for embedded flash
maggo83 Feb 2, 2026
15a2bca
build: add FAT filesystem tooling for i18n deployment
maggo83 Feb 19, 2026
c34623f
docs: update i18n documentation for binary format
maggo83 Feb 20, 2026
d9f8971
test: add comprehensive i18n test suite
maggo83 Feb 20, 2026
ca54142
Merge branch 'PortMockUIUpdatesToDiscoBoard' of https://github.com/ma…
maggo83 Feb 20, 2026
2cc9b68
Merge branch 'PortMockUIUpdatesToDiscoBoard' into Create-CleanUpLangu…
maggo83 Feb 20, 2026
38b8ac3
i18n: overhaul sync tool, add FILL_PLACEHOLDER, integrate into build
maggo83 Feb 20, 2026
336c40d
Merge remote-tracking branch 'upstream/main' into Create-CleanUpLangu…
maggo83 Feb 20, 2026
a890142
Fix post-merge: update i18n paths and lang_compiler after file reorga…
maggo83 Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ $(TARGET_DIR):
$(MPY_DIR)/mpy-cross/Makefile:
git submodule update --init --recursive

# Sync JSON language files with source code (dry run — warns about drift,
# does NOT modify files; run 'python3 tools/sync_i18n.py' manually to apply)
sync-i18n:
@echo Checking i18n language files for sync with source code...
@mkdir -p build
python3 tools/sync_i18n.py --dry-run

# i18n compilation
build-i18n:
build-i18n: sync-i18n
@echo Building i18n files...
@mkdir -p build/flash_image/i18n
@cd scenarios/MockUI/src/MockUI/i18n && python3 lang_compiler.py generate_keys languages/specter_ui_en.json
Expand Down Expand Up @@ -194,4 +201,4 @@ rag-index:
rag-search:
cd .rag && .venv/bin/python search.py "$(QUERY)"

.PHONY: all clean build-i18n rag-setup rag-index rag-search
.PHONY: all clean sync-i18n build-i18n rag-setup rag-index rag-search
50 changes: 44 additions & 6 deletions scenarios/MockUI/src/MockUI/i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,13 @@ Filesystem in git repository:
# Build-time tools
/tools/
├── make_fat_image.py # Creates FAT12 image from staged files in build/flash_image/
└── merge_firmware_flash.py # Combines firmware binary with filesystem image for flashing
├── merge_firmware_flash.py # Combines firmware binary with filesystem image for flashing
└── sync_i18n.py # Sync JSON language files with source code and default language file (adds/removes keys)

# Temporary build artefacts:
/build/
├── i18n_sync_master.log # Master log from last sync-i18n run (always written, incl. dry-run)
├── i18n_sync_specter_ui_XX.log # Per-language log from last sync-i18n run
└── flash_image/ # Staging area for files to be included in flash filesystem image
├── flash_fs.img # Generated FAT12 image containing staged files (created by build-flash-image target)
├── i18n/
Expand Down Expand Up @@ -266,6 +269,10 @@ Binary File Format (.bin):

**Purpose:** JSON ↔ binary conversion and file I/O (location-agnostic)

**Key Constants:**

- `FILL_PLACEHOLDER = "<FILL>"` → Sentinel value written by `sync_i18n.py` for keys that have not yet been translated. `i18n_manager.t()` detects this value at runtime and falls back to English so the placeholder never appears in the UI. Imported by `sync_i18n.py` to keep both in sync.

**Key Functions:**

- `generate_translation_keys(json_path)` → Creates `translation_keys.py` from default language [used during build process to generate key mapping]
Expand Down Expand Up @@ -312,9 +319,10 @@ Binary File Format (.bin):

- **Graceful Degradation:**
1. Call `lang_compiler.read_translation_from_binary(current_lang_file, key_index)`
2. If returns "missing", call same function with `default_lang_file`
2. If returns `"missing"`, call same function with `default_lang_file`
3. If still missing, return `"STR_MISSING"`
4. **No embedded strings** (except fallback `"STR_MISSING"`)
4. If the resolved text equals `FILL_PLACEHOLDER` (`"<FILL>"`), fall back to the default language (English). If even English has `<FILL>` (brand-new key not yet given an English value), return `"STR_MISSING"`.
5. **No placeholder text ever reaches the UI** — `<FILL>` is always caught before rendering

**Responsibility Boundary:**

Expand Down Expand Up @@ -360,7 +368,8 @@ KEY_TO_INDEX = {

#### Dedicated Makefile targets

- `build-i18n`: Generates translation keys and compiles default language JSON to binary to `build/flash_image/i18n/` via `lang_compiler.py`
- `sync-i18n`: Runs `tools/sync_i18n.py --dry-run` during the build to **detect** drift between source code and language files. Writes logs to `build/` but **does not modify any files**. If drift is detected, run `python3 tools/sync_i18n.py` manually to apply changes before the next build. **Called automatically as the first step of `build-i18n`.**
- `build-i18n`: Depends on `sync-i18n`. Generates translation keys and compiles default language JSON to binary to `build/flash_image/i18n/` via `lang_compiler.py`
- `build-flash-image`: Creates FAT12 image from staged file in `build/flash_image/` via `tools/make_fat_image.py`
- `mockui`: Builds firmware and merges it with embedded filesystem for language file via `tools/merge_firmware_flash.py`

Expand All @@ -376,8 +385,37 @@ make mockui ADD_LANG=de,fr, ..
#### Used helper scripts

- `lang_compiler.py`: For JSON ↔ binary conversion and validation
- `make_fat_image.py`: For creating FAT12 image from staged files in `build/flash_image/`
- `merge_firmware_flash.py`: For combining firmware binary with filesystem image for flashing (if needed; otherwise can flash separately via ST-Link)
- `tools/sync_i18n.py`: Synchronizes JSON language files with source code and the default language file (see below)
- `tools/make_fat_image.py`: For creating FAT12 image from staged files in `build/flash_image/`
- `tools/merge_firmware_flash.py`: For combining firmware binary with filesystem image for flashing (if needed; otherwise can flash separately via ST-Link)

#### Synchronizing language files with source code

The build target `sync-i18n` runs the tool in **dry-run mode** to detect — but not fix — drift. To actually apply changes, run manually:

```bash
# Apply changes (add/remove keys, update ref_en)
python3 tools/sync_i18n.py

# Dry run — show what would change without touching any file
python3 tools/sync_i18n.py --dry-run

# Override directories
python3 tools/sync_i18n.py \
--languages-dir scenarios/MockUI/i18n/languages \
--source-dir scenarios/MockUI \
--log-dir build
```

What the tool does:

1. **Scans source code** for all i18n key usages — recognises `t("KEY")`, `i18n["KEY"]`, `i18n("KEY")`, `i18n_manager["KEY"]`, `i18n_manager("KEY")` (single and double quotes).
2. **Syncs English master** (`specter_ui_en.json`): adds missing keys with `<FILL>`, removes obsolete keys.
3. **Syncs all other language files**: adds new keys with `<FILL>` + `ref_en`, updates `ref_en` when English text changed, removes obsolete keys, migrates any plain-string values to the standard `{text, ref_en}` format (with a warning).
4. **Validates filenames** using `extract_language_code_from_filename()` — non-conforming files are skipped with a warning.
5. **Writes logs to `build/`** immediately as changes are detected (open/write/close per entry — crash-safe). Log files are always created, even in dry-run mode. The header line `*** DRY RUN — no changes will be made ***` appears at the top of each log file when applicable.

`<FILL>` values in compiled binaries are automatically caught at runtime by `i18n_manager.t()` and replaced with the English fallback — they never reach the UI.

#### Translation Key Generation

Expand Down
14 changes: 13 additions & 1 deletion scenarios/MockUI/src/MockUI/i18n/i18n_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
JSON_FILE_SUFFIX,
extract_language_code_from_filename,
extract_language_name_from_file,
json_to_binary
json_to_binary,
FILL_PLACEHOLDER,
)


Expand Down Expand Up @@ -238,6 +239,17 @@ def t(self, key):
print(f"Warning: Error reading translation: {error}")
return self.STR_MISSING

# If the translation is the untranslated placeholder, fall back to the
# default language (English) so the placeholder never reaches the UI.
if text == FILL_PLACEHOLDER:
if self.current_language != self.DEFAULT_LANGUAGE:
default_text, default_error = read_translation_from_binary(
self.default_lang_file, key_index
)
if default_text is not None and default_text != FILL_PLACEHOLDER:
return default_text
return self.STR_MISSING

return text

def __getitem__(self, key):
Expand Down
6 changes: 6 additions & 0 deletions scenarios/MockUI/src/MockUI/i18n/lang_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
JSON_FILE_PREFIX = "specter_ui_"
JSON_FILE_SUFFIX = ".json"

# Translation Placeholder
# Used by sync_i18n.py when adding a new key that has not yet been translated.
# i18n_manager.t() recognises this value and falls back to the default language,
# preventing the raw placeholder from leaking into the UI.
FILL_PLACEHOLDER = "<FILL>"

# Binary Format Size Constants (in bytes)
MAGIC_SIZE = 4 # "LANG" signature
VERSION_SIZE = 4 # uint32 version number
Expand Down
6 changes: 6 additions & 0 deletions scenarios/MockUI/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
import shutil
from pathlib import Path

"""Pytest configuration for MockUI tests."""
import json
import os
import shutil
from pathlib import Path

import pytest

# Import state classes (micropython/lvgl already mocked by scenarios/conftest.py)
Expand Down
Loading
Loading