Commit Graph
22 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 5 84f2ec02e2 i18n: publish the corrected language packs as v22
The "System Information" key fix (6bb98a1) changed the flat packs in
deploy/apps/lang/, but devices never download those: they fetch the immutable
copy at apps/lang/<ver>/<code>.lang, keyed on the version in langs.json. With
everything still at v21, the store kept serving the old key from lang/21/ and
no device re-downloaded anything, so the fix reached only the Heltec V4, which
bakes the table into its firmware.

Bumped "# ver:" and the catalog to 22 in all 13 languages and snapshotted the
packs into lang/22/, the documented three-step publish. The generator ignores
the version line, so the baked table and the published beta_80 firmware are
unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 12:42:30 +02:00
Kaj SchittecatandClaude Opus 5 fff611692f touch: show the hop hash beside the repeater name in a route (#348, #347)
jrote1: a route lists resolved repeater names, and when a name is wrong or two
repeaters share one there is nothing left to identify the hop by. The hash is
what the node is actually keyed by on the wire, so it is the part that stays
true exactly when you are reading a route to work out where a message went.

Each hop now reads "3A Repeater Name" rather than just the name. The unresolved
case is unchanged, since it was already showing the hash.

Also pisti87's two corrections (#347): "Known regions" had never been
translated, and the Bluetooth pairing hint gets his shorter wording. Languages
to v21.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 17:37:24 +02:00
Kaj SchittecatandClaude Opus 5 afecfc0224 i18n: two Hungarian strings pisti87 asked for (#340)
"Mark read" was an untranslated placeholder. "Join the public channel" had the
long form, which he wants shortened to fit the button.

His text for the second reads "nyílvános"; the file already uses "nyilvános"
everywhere else, so this follows the file rather than introducing a second
spelling. Easy to change back if the long i was deliberate.

The other line numbers in that issue are from his own build and no longer point
at what he means, so they need his source rather than mine to resolve.

Languages go to v20.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 09:38:22 +02:00
Kaj SchittecatandClaude Opus 5 cecede23be i18n: undo the keys my extractor invented, and stop it inventing more
pisti87 read v18 and called it chaos. He was right, and it was mine.

Two defects in the extractor I added yesterday:

  - It joined every string literal in a call argument, so a ternary became one
    key. `cut ? "Paste (move)" : "Paste (copy)"` shipped as the single key
    "Paste (move)Paste (copy)", and with it "Unblock  Block",
    "Unfav  Favorite", "Other networksNetworks", "Batteryactivityon" and
    "Stop sharing loc  Share my loc". None of those strings exist anywhere in
    the firmware. Literals are now grouped only when genuinely adjacent, which
    is what the compiler concatenates, so both branches become their own key.

  - It scanned raw source, comments included. That is how "Geblokkeerde
    gebruikers" -- Dutch, appearing only inside a comment about how a long
    translation degrades -- became a KEY in the Hungarian file. Comments are
    stripped now, string literals preserved.

202 invented rows removed across the thirteen files. Only rows that were both
unknown to the extractor AND still untranslated were touched, so no
translator's work could be lost either way.

Also his: the curly quotes in the Hungarian credits render as boxes because no
bundled font carries U+201E/U+201D. Five values de-curled. And five strings he
found raw are wrapped: the Wi-Fi rescan and hidden-network rows, the update
check, and the downgrade prompt. The two Wi-Fi rows build their label at
runtime, since TR() returns a pointer and cannot join a glyph literal at
compile time.

Languages go to v19. v18 is deleted rather than left behind: it was only ever
correct for about a day and everything in it is superseded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 10:23:09 +02:00
Kaj SchittecatandClaude Opus 5 c044f75d11 i18n: pisti87's Hungarian for the strings the audit could not see (#294, #277)
33 of the pairs he posted apply directly to keys that only became visible when
the extractor learned about helper-wrapped and table-held literals. The rest
did not, and the reasons are worth recording rather than dropping:

  - Four were escaped in his markdown, so the key read `Node\\nRegion` against a
    real key of `Node\nRegion`. Matched after unescaping.
  - `Export crash report` is `Export crash report (%uK)` in source; the size is
    part of the label. Applied with the conversion appended.
  - `Distance` and `Heard` are column headers he read off the screen; the keys
    are `Distance: km` and `Recently heard`. Not guessed.
  - `Sent only when that contact asks...` is a translation of wording the
    English has since changed. Applying it would ship a Hungarian sentence that
    no longer describes what the setting does.
  - `nothing heard yet` and `Other (hidden) network...` match no key at all,
    which usually means a raw string somewhere the audit still cannot see.

88 Hungarian rows are still English: the remainder of the newly visible keys,
plus console mode, which is new.

Languages go to v18. The v17 snapshot is deleted rather than kept: it was cut
before the audit fix added 116 keys, so it was already wrong, and it never
reached a device.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:06:11 +02:00
Kaj SchittecatandClaude Opus 5 a9cff6f113 i18n: translate the map credits sheet, and make the baked table follow the files
The map About/credits sheet was 820 bytes of raw English built with snprintf and
no TR() anywhere in it, so it stayed English in every language (#257). It is now
three keys: the two attribution headers (the OpenTopoMap variant is credited
separately because its style is CC-BY-SA) and the body, kept whole rather than
split per paragraph so translators get prose instead of fragments. The buffer
grows 820 -> 2048 because Hungarian runs about 1.5x English here and the
Cyrillic and Greek files are two bytes a letter.

Hungarian text from pisti87 (#257). Two edits to what he posted, both flagged on
the issue: the hard line breaks he inserted at the English wrap points are gone,
because the label wraps itself and a fixed break lands mid-sentence on any other
panel width; and the header reads "Terkep adatok" rather than "Map adatok",
which looked like a copy-paste artifact given the rest is fully translated. The
OpenTopoMap variant is derived from his own wording and is his to correct.

Also raw, from the same report: the Discovered auto-add hint and the four type
words it interpolates. The hint buffer goes to 240 bytes and the type list to
128, since the translated plurals are longer than "chats, repeaters".

The reason none of that would have shipped: gen-lang-builtin.py exists so the
baked-in table and the .lang files the store serves cannot drift, and its
docstring promises a pre-build step that runs it. Nothing ran it. Editing a
.lang and building produced an image carrying the OLD translations, silently.
It is now a real pre: hook on all seven PlatformIO envs and a line in both IDF
build scripts, regenerating only when a .lang is newer than the header.

deploy-apps.sh grew the matching check for the other half of that path: a
catalog version that disagrees with the file's own "# ver:" publishes
translations to a version no device asks for.

All thirteen languages snapshot to v17 -- the merged region and SD work added
keys to every file, not just Hungarian.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:06:39 +02:00
Kaj SchittecatandClaude Opus 5 b15d422bdc touch: beta_68 — Lua SDK gains the map, lists, packet delivery and discovery
The four capabilities that kept third-party apps a sketch of a built-in one:

* wada.map      the firmware's own tiles, projection and cache inside an app
                page, with its own capped pool so it never evicts the Map tab's
* wada.ui.list  the missing "pick one of N" widget; rows are real buttons, so
                keyboard and trackball nav walk them for free
* on_packet     each frame delivered once instead of polling a 16-deep ring,
                which sampled rather than observed
* wada.mesh.discover  the active zero-hop probe, behind its own permission
                because it spends every neighbour's airtime, not just ours

Plus the surface those need to be useful: packet identity reported only where
the frame actually carries it, exact micro-degree coordinates (Lua is built
LUA_32BITS, so its floats were quietly costing a metre), altitude and satellite
time, wada.geo, wada.ui.input, named and one-shot timers, http_post, windowed
fs.read, and wada.sys.env on a hardware gate rather than the memory one.

Fixes:
* Both ESP32-P4 targets could not link. g_wifi_last_disc_reason was defined in
  src/main.cpp, which the IDF builds never compile, so all nine S3 envs stayed
  green while Tanmatsu and T-Display P4 were dead.
* Map zoom level was invisible in +/- buttons mode; the readout was hidden with
  the slider it was anchored to.
* Hungarian and Dutch held each other's "No SD card" translation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 21:50:01 +02:00
Kaj SchittecatandClaude Opus 5 25c76a3798 i18n: the audit could not see strings reached through a table, and one was missing
pisti87 asked whether the obsolete entries in the language files will be removed
(#262). Measuring it first was the right move, because the tool that would have
driven that cleanup was wrong.

source_keys() only ever matched TR("literal"). Strings reached indirectly, as in
TR(kSettingsCats[c].label), were invisible to it, so every settings-category name
looked unreferenced: About, Backups, Language, MQTT bridge, App permissions and
the rest. Anyone pruning the .lang files on that output would have deleted live
translations for some of the most visible labels in the UI.

The audit now also pulls literals out of any table whose name appears inside a
TR(...) subscript. Deliberately greedy: over-collecting keeps a translation alive,
under-collecting deletes one.

That immediately found a real gap it had been hiding. "App permissions", the
settings category added with the permissions page, was missing from all 13
languages and the old audit reported everything as covered. Added, builtin
regenerated, published as language v15.

Also adds --obsolete, the reverse check pisti87 actually needs: rows in each .lang
file that no TR() key matches. It reports and never deletes, and says plainly that
these are candidates to check rather than a delete list, for the reason above.
Numbers now: 950 keys reachable, hu.lang carries 1044 rows, 94 unreferenced. The
earlier figure was 119, so 25 of those were the false positives just fixed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 19:19:21 +02:00
Kaj SchittecatandClaude Opus 5 42207bca2e docs + i18n: cover the new SDK surface before it ships
Pre-release tidy-up, so the same mistake as last week does not repeat: the SDK
page described a lifecycle the firmware never had, and shipping an expanded SDK
against a page describing the old two-permission model would be the same thing
again.

sdk.html now documents wada.mesh.send_dm and wada.mesh.channels, the m.kind field
on on_message, all four permissions with why they are four rather than one, and a
new wada.crypto section including why it exists (pure-Lua HMAC does not fit the
instruction budget) and why it is on every board rather than ext-gated.

i18n: 11 keys were missing across all 13 languages, six from the SDK work and
five from PR #287 which added strings without lang entries. All covered; audit
reports 924 keys with nothing missing, builtin fallback regenerated, published as
language v14 and the store is live.

Also removes an em dash from "Wi-Fi off, new map areas can't download", new in
#287 and not yet translated, so the key could be corrected for free.

All 8 S3 boards build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 17:50:37 +02:00
Kaj SchittecatandClaude Opus 5 1c686553e7 i18n: pisti87's Hungarian, plus the compiled-in table I forgot to regenerate (#277)
pisti87 pointed at a commit of his (dc94f44) with 64 Hungarian rows, including
translations for the strings added over the last few releases.

He put them in src/ui-touch/i18n_builtin.h, which says "DO NOT EDIT" at the top
because it is GENERATED from deploy/apps/lang/*.lang. That turns out to be my
fault rather than his: yesterday I added 29 keys to the .lang files and never
ran gen-lang-builtin.py, so the compiled-in fallback still had the old table and
a device that has not downloaded a pack still showed English. Editing the
generated file was a reasonable guess at where the strings lived.

So: his rows are merged into deploy/apps/lang/hu.lang, the canonical source, and
the builtin is regenerated from it (13 languages, 12514 rows). Both paths agree
again.

Four of his rows would not have compiled - two used a tab where a comma belongs,
and two left the quotes inside the English unescaped, one of which had pasted
"Nincs válasz." into the middle of the English key. Reconstructed rather than
dropped; the intent was unambiguous in each.

A fifth pointed at something real: he translated "Battery life: geathering
data..." and no such key exists. Ours has no typo, but more importantly it was
never wrapped in TR() at all - a bare literal snprintf'd straight into a
user-visible buffer, which the unwrapped-literal scanner does not look at since
it only inspects LVGL setters. It is wrapped now, along with the one literal the
scanner did flag (the UI size dropdown), so the scan is at zero and both strings
are translatable for the first time.

Audit: 916 keys covered in all 13 languages. Published as language v13 and the
store is live.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:35:00 +02:00
Kaj SchittecatandClaude Opus 5 4709c8197d i18n: cover the 29 strings that had drifted out of the language files (#277)
pisti87 listed the untranslated text he was seeing. Running the audit tool from
#254 found 29 keys missing from all 13 languages, which is a superset of his
list: the position-sharing strings and the note under them, the app-permission
prompts and page, the 1-character spam filter, the map "Max dots" and "%d of %d
on map", the room "Join w/ password" and its escalation prompt, and the new
path-hash hint. Most of them are mine from today.

All 13 are now complete: the audit reports 914 keys covered with nothing
missing. Untranslated rows carry the English on both sides, which is the
existing convention in these files (49 rows already looked like that), so a
translator sees the row and knows what to fill rather than the key simply not
existing.

Hungarian gets the one real translation: pisti87 supplied the telemetry-note
text in the issue, so it ships translated rather than English.

Published as language v12 for every language, and the store is live. Note the
canonical files the audit checks are the FLAT deploy/apps/lang/*.lang; the
numbered directories are published snapshots, so both were updated.

Also removes an em dash from the position-sharing note. It was written before
that rule landed and had no translations yet, so the key could be corrected for
free. The remaining em dashes in older strings are deliberately left alone:
changing those keys would orphan their existing translations in all 13 files,
which is a decision worth making on purpose rather than as a side effect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:21:48 +02:00
Kaj SchittecatandClaude Opus 5 d192bf53db fix: never trust a translation as a printf format string (#258)
pisti87's T-Deck rebooted every time he logged into a repeater, but only in
Hungarian. The login clock-skew warning does:

  snprintf(msg, n, TR("Device clock differs from \"%s\" by %lu min%s"),
           name, minutes, suffix);

and the Hungarian row reordered the conversions to "%lu ... %s ... %s". Varargs
are positional, so snprintf read the name POINTER as an unsigned long and then
took the minute count -- the integer 3 -- as a char* and dereferenced address 3.
Instant panic, every login, Hungarian only. English fit the declared order, so
it never showed there.

Fixing the four bad Hungarian rows is not sufficient: TR() returns a format
string and translations come from .lang files that users download or hand-write,
so any file can crash any device. TR() now compares the ordered conversion
signatures of key and translation and falls back to the English key on a
mismatch -- the key IS the call site's format string, so it is always correct.
The scan runs only for keys containing '%', which is a small minority.

Also fixed the four rows (three were Hungarian-only crashes or dropped values),
bumped hu to v11, and taught audit-lang.py to fail the build on a mismatch so a
future translation PR cannot reintroduce this. Unit-checked that the audit
detects the original bad row and accepts the repaired one.

Reported by pisti87.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 08:14:05 +02:00
Kaj SchittecatandClaude Opus 5 c00734cfe8 apps: publish the pending language updates (de v9, nl v9, hu v10)
langs.json had not moved since the v8 publish, but four commits changed
language content after it: pisti87's Hungarian v9, the glyph swap that
followed, the 48 placeholder rows, and pisti87's German, Dutch and Hungarian
translations of those rows. The device asks langs.json for the version, then
fetches the immutable /apps/lang/<ver>/<code>.lang copy — so with the catalog
still reporting v8 for everything, every one of those updates was invisible on
device. That is the "it downloads an old version" report.

Publish the immutable v9 and v10 snapshots and bump the three languages that
changed. The other ten stay at v8: they only gained empty placeholder rows,
which fall back to English either way, so bumping them would cost every device
a download for no visible change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:17:46 +02:00
Kaj SchittecatandClaude Opus 4.8 ad6446b312 apps: publish the v8 language files at their immutable path
The Store fetches a language from /apps/lang/<ver>/<code>.lang, not from the
mutable copy at the top of the directory -- so bumping langs.json to v8 without
creating lang/8/ would have offered every device an Update that 404s.

Also normalised the "ver" spacing in langs.json back to the file's own style.
The device parser skips whitespace after the colon so both forms work, but
there is no reason for the file to be inconsistent with itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 00:54:43 +02:00
Kaj SchittecatandClaude Opus 4.8 dbff43dbd7 beta_59 prep: language files v8, the SDK docs page, and device-status corrections
The audit found three keys the Store work added or renamed since v7 -- Store,
the out-of-memory message and the Languages hint -- untranslated in all 13
languages. Filled, using each language's own word for the Use button so the
hint matches the control it points at, and bumped the files to v8. Every
language is back to full coverage at 856 keys.

New deploy/site/sdk.html: the wada.* reference. The beta ships a public API and
had no user-facing documentation for it, so nobody outside the repo could write
an app. Covers the lifecycle, all seven tables, the app format, side-loading and
how to publish, plus the two constraints that surprise people -- the mesh is
read-only in v1 and http_get is plain HTTP because TLS does not fit in the heap
left after Wi-Fi associates.

Site device list: the Attaky Core and the T-Display P4 are fully supported now
(Kaj's call), so their not-hardware-verified caveats are gone. Dropped the "one
of the two primary development boards" wording, which stopped being true once
the P4 joined the test loop. The terms section named only the T-Deck and Heltec
V4 as flashable when the flasher offers ten boards; it now points at the list on
the page instead of going stale again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-05 00:37:22 +02:00
Kaj SchittecatandClaude Opus 4.8 cac3a44074 i18n: scan by literal, not by function name — 103 more wrapped incl. Remove channel (v7)
'Remove channel' was built by mk_full(), a local row helper my scanner did
not know about. Listing the functions that take UI text has the same blind
spot as grepping for the strings themselves: the one helper nobody wrote
down keeps its labels English forever.

So the scanner gained a --wide mode that inverts the question — every prose
literal in the file is suspect unless it is consumed by a known NON-UI sink
(logging, strcmp, paths, Lua glue, format-only args). That surfaced the
whole family of local builders: mk_full, mk_btn, mk_label, mk_ta, mk_switch,
setupHeader, setupBtn, fmActionBtn, ccToggle, make_launcher,
setAddChannelError. --fix now wraps prose in ANY argument of those helpers
rather than a hardcoded index, so their differing signatures don't matter.

103 calls wrapped: the channel sheet (Remove channel, Delete chat), every
contact action (Open chat, Ping, Message, Telemetry, Trace SNR, Admin,
Range test, Sightline, Block, Favorite, Reset path), the Profile/Radio
settings rows, auto-add and experimental switches, the add/join channel
menu and its errors, file-manager actions, control-center chips, the
launcher tiles and the whole first-boot wizard. 40 new keys translated in
all 13 languages; units and protocol tokens (MHz, SF, CR, TX, AF, BT, GPS,
DND, Wi-Fi) map to themselves. Files at ver 7, both tools green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 17:58:50 +02:00
Kaj SchittecatandClaude Opus 4.8 15415fd0f5 i18n: enumerate every user-visible literal instead of grepping for them (49 wrapped, v6)
My earlier sweeps searched for strings I could think of, which is why you
kept finding more. scripts/build/i18n-scan-unwrapped.py now parses every
call that takes user text — LVGL setters (label/checkbox/dropdown/roller/
placeholder/tab/table/msgbox), showAlert, showConfirm, appPageBegin,
createSettingsModal and the settings row helpers — splits the argument list
and reports any human-readable literal not wrapped in TR(). It found 55;
49 got wrapped (--fix does it mechanically), 6 skipped as brand/technical
(WADAMESH, CPU, PSK, OK, and two unit readouts). It now reports zero.

The 33 newly reachable keys are translated in all 13 languages: the Flood/
Zero-hop scope buttons, Share QR, Search contacts, Play / Set as
notification sound / Set as wallpaper, Probe now, On map, Install update,
Save update bin to SD, Choose .wav from files, Power, Clear history, the
Bluetooth/MQTT/Logs/System info sheet titles, both dropdown option lists,
the size preset list, Import, the URL bar, and Snake's New game.

Two format bugs behind the last stragglers: keys whose text begins with
LVGL recolor markup ('#7A7F87 Wardrive: ...#') were dropped as comment
lines by BOTH the device loader and the audit — a line is a comment only
when it has no tab, fixed in both. And several rows had been written with
literal '\xE2\x80\xA6' text where the file needs the real character; the
loader only unescapes \n \t \\, so those keys never matched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 17:46:00 +02:00
Kaj SchittecatandClaude Opus 4.8 7a034d303a lang: fix the download for real (PSRAM->SD + short reads), and say why before restarting
Three real bugs behind the endless 'Download failed', found by putting a
diagnostic on the device instead of guessing:

1. HTTP read quit early. The loop treated a gap in the stream as
   end-of-data, so a 49 KB file came back as 16 KB (n=16492 measured) and,
   worse, that truncated buffer was returned as success. It now reads to
   Content-Length, gives stalls a 4 s progress grace, and FAILS on a short
   read instead of handing back half a file.
2. SD writes from PSRAM died after the first 4 KB (wr=4096, measured twice).
   Writes now go through a small internal-RAM bounce buffer, which is what
   every proven-good SD writer in this firmware already does. Full 49344
   bytes written and installed, confirmed on the bench.
3. A truncated write still stamped the new '# ver:' (it is the file's FIRST
   line) while the appended rows were lost — so the tab claimed v4, offered
   no Update, and showed English. Downloads are atomic now: temp file,
   verify on-card size, then rename over the target. A failure leaves the
   old file and its old version untouched.

Plus: if a /lang write fails, an identical control write to /apps proves
whether the card is healthy, and if so /lang is rebuilt and the install
retried — self-healing the FAT damage that mid-write resets can leave.

Language changes now show 'Restarting to apply the language...' and reboot
on a timer, so the notice actually paints. showAlert() followed immediately
by rebootDevice() never rendered a frame — the device just went dark under
your finger. Applies to Update, Use, and the built-in picker alike.

The boot self-heal no longer toasts its failures (that was the 'download
failed' that greeted you when merely opening the store). Files at ver 5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 17:28:06 +02:00
Kaj SchittecatandClaude Opus 4.8 535471613d lang: chunked SD write fixes the false 'Download failed', audit sees glyph-prefixed TR, 16 more keys (v4)
The update download was actually succeeding: one 48 KB f.write on FAT can
return a short count without error, so the file landed while the code
reported failure — which also suppressed the reboot-to-apply (Kaj saw the
new version only after re-entering the tab and switching languages by
hand). The write is chunked with a real completion check now, and
s_langdl_ok is volatile.

The audit's TR() extractor required the argument to start with a quote, so
every TR(LV_SYMBOL_X "  text") call — all the sheet rows wrapped in the
previous commit — was invisible to it, and 'full coverage' was reported
while 16 keys had no rows anywhere: the chat/channel sheet actions (Mark
as read, Share secret, Region & scope, Chat icon, Delete history, Log in
again, Reset path), contacts overflow (Search, Auto-add settings, Blocked
list), URL menu (Open in web, Create QR), power menu (Power off, Download
mode), plus '(device behind)' and 'Copy internal data to SD'. The regex
now accepts the macro prefix and strips glyphs exactly like runtime TR().
All 16 translated in all 13 languages; files at ver 4, audit green at 731
keys per file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 16:48:50 +02:00
Kaj SchittecatandClaude Opus 4.8 6a5738946f i18n: wrap the bare-literal sheets and buttons — months-old translations finally render
The channel/chat long-press sheets (Mark as read, Share secret, Region &
scope, Chat icon, Delete history, Log in again, Reset path, Blocked users),
the contacts overflow menu, the URL menu, the power menu rows, five confirm
buttons and four page labels were built from glyph-concatenated string
literals that never passed through TR() — so their translations, which have
existed in the table (now the .lang files) for months, never rendered. All
wrapped now; TR() strips the glyph prefix and rebuilds the label, which is
exactly the machinery it grew for. Only five keys were genuinely new
(Install, Map, Save report, Set, Stop) — translated in all 13 files, bumped
to ver 3 (immutable /apps/lang/3/), audit fully green.

Also fixes the audit appender's substring presence check: '© OpenStreetMap'
contains 'Map', which silently skipped the new Map row in every file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 16:41:59 +02:00
Kaj SchittecatandClaude Opus 4.8 7ec5e92414 lang: language files are versioned like apps — Update button, immutable URLs
Each .lang carries a '# ver:' header (missing = v1) and langs.json carries
the catalog ver. An installed file whose ver differs from the catalog shows
a blue Update on its row (sub line shows 'code.lang vN'); updating the
ACTIVE language reboots on success to apply the fresh overlay, updating an
inactive one just refreshes the row. Downloads go to the immutable
/apps/lang/<ver>/<code>.lang path so the day-long edge cache can never
serve a stale file after a translation bump — same lesson as the flasher's
mutable-latest cache bite. The boot self-heal resolves the ver from the
catalog on the worker (flat-path fallback) and never reboots on its own.
Canonical files bumped to ver 2 (the full-coverage sweep).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 16:28:42 +02:00
Kaj SchittecatandClaude Opus 4.8 f3f421398c store: three tabs (Apps / Built-in / Languages) + languages as downloadable files
The Lua Store is now a segmented three-tab page. Apps keeps the catalog and
sideloaded scripts; Built-in holds the show/hide switches; Languages is new.
Opening is instant: the card re-listing that ran synchronously on open now
runs on the net worker (request/done flags like the catalog), and the app
cache pre-warms at boot. Card text no longer runs under the action button —
title and description both stop at the button column.

Languages as files: every translation column of the i18n table exports to a
.lang file (scripts/build/gen-lang-files.py -> apps/lang/<code>.lang on the
firmware host + langs.json catalog). The device downloads them from the
Languages tab into <data>/lang/, and at boot the active file loads into
PSRAM and overlays TR() lookups via binary search — the file wins, then the
built-in ui_lang column, then English, so a partial or hand-edited file
degrades gracefully. The file's '# base:' header names that fallback column.
Users can drop their own .lang files on the card (or edit a downloaded one)
for languages the firmware never shipped; switching reboots to apply, same
as the Settings picker (which now also clears the file overlay). Prefs v48
appends the active file code, trailing per the schema law.

This is stage one of un-baking translations from the image: the mechanism,
catalog and files ship now while the built-in table stays as the fallback;
the flash win lands when the table columns are dropped after field proof.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 15:40:35 +02:00