Contact nicknames: TouchPrefsStore nick blob (48 x 38 B), display-only lookups (contactShownName, threadShownName, senderShownName), Contact options > Nickname on the shared text prompt (an empty OK clears it). Control panel restyled in the launcher's tile language, with a Battery saver tile; keyboard tooltips (navSetTip), an amber cursor and touch long-press tooltips (the T-Deck touch read hid them on every poll). T-LoRa Pager: M/C/A/S on Home, H/S tap and hold, Backspace = Back, N = jump to new, held Enter = long press, per-tab focus memory, Power off confirm; LVGL -O2 and IRAM, INT-gated keyboard, idle yield, status-bar setter guards, instant tabs; PagerGps (u-blox software standby for rests, GPS rail cut when off), panel sleep, amp off at boot, power-off parks the radio (NSS held), GPS, panel and keyboard light. Fixes: the DM duplicate check keys on the attempt and a path reset clears it, the direct timeout masks the packed path byte, web remote hops (#636); changelog over http (#638); Lua SDK send budgets and duplicate guard (#606); GPS speed note (#613, CAP_GPS_BAUD_AUTO); emoji chat icon (#640); default wallpaper (#626). Heltec V4: Tetris, 2048 and Breakout unseeded and the built-in Lua stripped of comments (identical bytecode), 27 KB freed; the baked language table is pinned to beta_92's files (scripts/build/lang-pin-beta92) so it still fits. Translations: 39 new keys in 14 languages, language store v24. Site: theme, dark mode, aurora and guide refresh, SDK send limits, nicknames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
5.2 KiB
Python
129 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Bake deploy/apps/lang/*.lang into a compiled-in fallback table.
|
|
|
|
Boards that can reach the Lua Store download their language as a file. Boards
|
|
that cannot (the V4: ~14 KB of free internal heap, so the net worker and the
|
|
store are unreliable there) still need translations, and before this they were
|
|
stuck on English.
|
|
|
|
This generates src/ui-touch/i18n_builtin.h from the SAME .lang files the store
|
|
serves, so the two can never drift: translators edit deploy/apps/lang/*.lang,
|
|
and both paths follow. Rows are emitted sorted by key so TR() can binary-search
|
|
them, and the table lives in .rodata — flash, not RAM.
|
|
|
|
Run from the repo root (a PlatformIO pre-build step does this automatically):
|
|
python3 scripts/build/gen-lang-builtin.py
|
|
"""
|
|
import glob, os, sys
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
LANGDIR = os.path.join(ROOT, 'deploy/apps/lang')
|
|
OUT = os.path.join(ROOT, 'src/ui-touch/i18n_builtin.h')
|
|
|
|
# The baked table can be pinned to a frozen copy of the .lang files instead of the live
|
|
# ones. beta_93 (2026-10-10): the new translations pushed the 2 MB Heltec V4 5.6 KB over
|
|
# its flash, so the V4 keeps exactly the table it shipped with in beta_92 (the beta_92
|
|
# .lang files, copied to scripts/build/lang-pin-beta92/) and shows English for strings
|
|
# added since; every other board downloads the live files from the Store. Set to None
|
|
# to bake the live files again once the table has room (a trim of keys no source uses
|
|
# any more is the pending decision).
|
|
PIN_DIR = os.path.join(ROOT, 'scripts/build/lang-pin-beta92')
|
|
|
|
# Index order must match the UiLang enum in i18n.h.
|
|
CODES = ["en", "hu", "nl", "de", "fr", "es", "it",
|
|
"ru", "uk", "bg", "sr", "el", "pt-br", "ro", "cs"]
|
|
|
|
|
|
def c_escape(s):
|
|
out = []
|
|
for ch in s:
|
|
if ch == '\\': out.append('\\\\')
|
|
elif ch == '"': out.append('\\"')
|
|
elif ch == '\n': out.append('\\n')
|
|
elif ch == '\t': out.append('\\t')
|
|
elif ch == '\r': out.append('\\r')
|
|
elif ord(ch) < 0x20: out.append('\\x%02x' % ord(ch))
|
|
else: out.append(ch)
|
|
return ''.join(out)
|
|
|
|
|
|
def read_lang(path):
|
|
"""key -> value, with the file's own \\n/\\t/\\\\ escapes decoded."""
|
|
rows = {}
|
|
for line in open(path, encoding='utf-8'):
|
|
line = line.rstrip('\n').rstrip('\r')
|
|
if '\t' not in line: # header/comment lines carry no tab
|
|
continue
|
|
k, v = line.split('\t', 1)
|
|
def un(t):
|
|
o, i = [], 0
|
|
while i < len(t):
|
|
if t[i] == '\\' and i + 1 < len(t):
|
|
n = t[i + 1]
|
|
o.append('\n' if n == 'n' else '\t' if n == 't' else n)
|
|
i += 2
|
|
else:
|
|
o.append(t[i]); i += 1
|
|
return ''.join(o)
|
|
k, v = un(k), un(v)
|
|
if k and v:
|
|
rows[k] = v
|
|
return rows
|
|
|
|
|
|
def main():
|
|
langs = {}
|
|
src_dir = PIN_DIR or LANGDIR
|
|
for code in CODES[1:]: # English is the key itself
|
|
p = os.path.join(src_dir, code + '.lang')
|
|
if os.path.exists(p):
|
|
langs[code] = read_lang(p)
|
|
|
|
if not langs:
|
|
sys.exit('no .lang files found in ' + LANGDIR)
|
|
|
|
w = ['// Generated by scripts/build/gen-lang-builtin.py — DO NOT EDIT.',
|
|
'// Source: deploy/apps/lang/*.lang (the same files the Lua Store serves).',
|
|
'// Compiled-in fallback for boards that cannot rely on downloading a language.',
|
|
'#pragma once', '#include "i18n.h"', '']
|
|
|
|
for code in CODES[1:]:
|
|
rows = langs.get(code, {})
|
|
sym = 'kBuiltin_' + code.replace('-', '_')
|
|
w.append('static const I18nPair %s[] = {' % sym)
|
|
# A row whose translation IS the key does nothing: TR() already returns the
|
|
# key when it finds no match. The .lang files carry those deliberately, as
|
|
# placeholders telling translators the string exists, but baking them costs
|
|
# flash on exactly the board that needs the flash (the V4 sits at ~90%).
|
|
for k in sorted(rows): # sorted: TR() binary-searches
|
|
if rows[k] == k:
|
|
continue
|
|
w.append(' { "%s", "%s" },' % (c_escape(k), c_escape(rows[k])))
|
|
w.append('};')
|
|
|
|
w.append('')
|
|
w.append('static const I18nPair* const kBuiltinLang[LANG_COUNT] = {')
|
|
w.append(' nullptr,') # LANG_EN: the key is the English
|
|
for code in CODES[1:]:
|
|
sym = 'kBuiltin_' + code.replace('-', '_')
|
|
w.append(' %s,' % (sym if langs.get(code) else 'nullptr'))
|
|
w.append('};')
|
|
w.append('static const int kBuiltinLangCount[LANG_COUNT] = {')
|
|
w.append(' 0,')
|
|
for code in CODES[1:]:
|
|
sym = 'kBuiltin_' + code.replace('-', '_')
|
|
w.append(' %s,' % (('(int)(sizeof(%s)/sizeof(%s[0]))' % (sym, sym))
|
|
if langs.get(code) else '0'))
|
|
w.append('};')
|
|
w.append('')
|
|
|
|
open(OUT, 'w', encoding='utf-8').write('\n'.join(w))
|
|
total = sum(sum(1 for k, v in r.items() if v != k) for r in langs.values())
|
|
print('%s: %d languages, %d translated rows, %d bytes' %
|
|
(os.path.relpath(OUT, ROOT), len(langs), total, os.path.getsize(OUT)))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|