LVGL Runtime Translation Catalogs: Detect Missing Keys and Refresh Existing Widgets
Validate translation completeness independently of rendering, then give every existing text widget an explicit language-refresh strategy. A successful lookup does not prove a translation exists, because LVGL can return the key itself. A label that copied yesterday’s lookup does not automatically remember which key produced it.
Applicability: this article pins LVGL v9.4.0 with LV_USE_TRANSLATION enabled. It uses the built-in lv_translation module, not lv_i18n. Source-pinned label translation binding is part of this release-specific procedure. Do not infer these APIs from an earlier documentation URL or apply them to v9.3.0. The catalogs and tests are hypothetical and unexecuted; glyph coverage and overflow geometry are outside scope.

1. Treat the catalog as a rectangular contract
The 9.4 translation guide describes static language, tag and translation arrays. Build a manifest containing the supported language identifiers, every required key, whether an empty value is allowed and the approved fallback policy. Keep identifiers stable when visible wording changes.
For static packs, the tagged lookup implementation addresses translations by tag index multiplied by language count, plus language index. Thus each tag contributes one row across all languages. Validate the complete row-major shape before registration; a transposed array can return valid strings for the wrong key without a memory error.
Consider this deliberately incomplete two-language diagnostic catalog:
| Key | en | de | Manifest policy |
|---|---|---|---|
| settings.title | Settings | Einstellungen | Required and nonempty |
| action.resume | Resume | Null cell | Required and nonempty |
| status.idle | Idle | Empty string | Required and nonempty |
The second and third rows fail the proposed completeness gate for different reasons. Preserve that distinction in reports. A null pointer means missing data; an empty string is present data that may be intentional for some keys but is forbidden by this manifest.
2. Verify the actual fallback instead of trusting one comment
In v9.4.0, lookup returns the tag when no language is selected, the language is unavailable, the key is absent or the selected cell is null. A non-null empty string is returned as empty. The implementation does not substitute the first language for a null cell.
This conflicts with the first-language fallback described in the tagged header comment; the implementation and 9.4 guide agree on tag fallback. Pin that finding in the test expectations rather than silently choosing whichever description is more convenient.
Do not detect completeness solely by comparing returned text with the key. A legitimate translation may intentionally equal its key. Inspect the catalog manifest and cells directly. Reject duplicate keys or define a separately tested precedence policy; avoid letting pack registration order become an undocumented product decision.
For required commands, the proposed release policy rejects an incomplete language pack. For an optional diagnostic label, the product may explicitly approve a fallback phrase. Materialize and record that approved value during catalog preparation, with its provenance, instead of assuming LVGL supplies an English fallback.
3. Distinguish a copied lookup from a bound label
Calling lv_label_set_text with the result of lv_tr creates a text snapshot. To retain the key for later updates, v9.4.0 exposes lv_label_set_translation_tag. The label stores a copy of that tag and initially resolves its text.
The tagged label event handler refreshes a bound label on LV_EVENT_TRANSLATION_LANGUAGE_CHANGED. Ordinary text setters remove the translation tag. That is useful for an intentional override, but a convenience formatting call can also silently detach a label from future language changes.
Choose one owner per widget: built-in tag binding, an application language-change callback, or deliberately fixed text. A callback-based widget should retain its key and rebuild its text on the event; do not add both strategies without deciding which owns the final value.
4. Make language selection a controlled update
The tagged module’s language setter stores the requested language and walks widgets to send the change event. It is not a catalog-completeness validator. Check the requested identifier against the application’s supported-language list first, including exact spelling. An identifier such as en-US does not automatically establish a fallback to en.
Use application-owned language storage for the request. Avoid passing a borrowed internal current-language pointer back into a setter that replaces that storage. Keep callbacks limited to text refresh and related presentation work; do not recursively change the language or rebuild the widget tree during the notification walk.
Prepare and validate runtime catalog data before exposing the new selection. For dynamic packs, follow the header’s requirement to add languages before tags. Static arrays and their strings must remain alive while used. Treat pack loading, publication and retirement as explicit application operations rather than mutating a live catalog from another thread.
5. Exercise existing widgets, not only new ones
- Register a complete diagnostic catalog and select en. Create label A with a copied lookup and label B with translation-tag binding for settings.title.
- Switch to de. Expect B to show the German text; A retains its English snapshot unless an application refresh handler updates it.
- Create label C after the switch and bind the same key. Verify it immediately agrees with B.
- Set ordinary text on B as an intentional override, then change language again. Confirm the binding has been removed and the override remains.
- Rebind B explicitly and verify future language changes update it again.
Run the missing-cell and empty-string cases in a separate development catalog. Check raw lookup results and the product’s validation verdict independently. Include an absent key, unsupported language and no selected language. This separates library fallback behavior from the stricter application release policy.
For a runtime edit to an existing catalog value, verify how the application requests text refresh; changing stored translation data alone is not a general promise that every already copied widget updates. Reapply bindings or issue the reviewed language-refresh operation only after the catalog is complete and stable.
6. Keep completeness and refresh evidence together
Save the catalog schema, ordered language/key lists, null-versus-empty findings, approved fallback provenance and per-widget refresh strategy. Keep a trace of language selections and expected label strings. Acceptance requires both correct lookup for each supported pair and correct updates of already existing widgets. A newly created translated screen alone cannot prove runtime switching works.