<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-hub/tests/test_locales.py, branch 0.16</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.16</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.16'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-19T12:39:38Z</updated>
<entry>
<title>style: the 98 ruff could not fix, so the linter is a signal again</title>
<updated>2026-09-19T12:39:38Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-19T12:39:38Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=9e7b75bb0f6f6649fb00f2dc97059e90b7d52875'/>
<id>urn:sha1:9e7b75bb0f6f6649fb00f2dc97059e90b7d52875</id>
<content type='text'>
The pass before this applied ruff's own fixes. These are the ones needing a
decision, and the point of doing them is that `ruff check .` now passes: a
linter reporting 98 known-acceptable findings reports nothing, because the next
real one arrives invisible.

**Lines over 100 (70).** Mostly wrapped where they stood. Two exceptions: the
aligned trailing comments in `protocol.py`'s message table were shortened rather
than wrapped, because wrapping one row of a table breaks the table; and in
`models.py` the column comments moved above their columns for the same reason.

**Imports below the first statement (14).** `csam.py` kept its FastAPI imports
under a section header halfway down the file; two node tests had a constant and
a `pytestmark` wedged between two import blocks. Moved, not suppressed.

**Bindings nothing reads (4).** Three in tests, where the call stays and only the
name goes — `_user(client, "listener")` is there to create the user, not to
return one. The fourth was in `revocation.py` and was not a lint finding at all:
`_connect_and_listen` opened an httpx stream to the WebSocket URL, did `pass`,
and then opened the real connection through the `websockets` library. One
pointless request per connect, left over from before that library was used
directly. Removed, and `httpx` with it.

**`l` as a name (4)**, **semicolons (6)** in the POC spikes, and the rest.

2893 passed, the same count as the two commits before it.

`meshbay_node/revocation.py` is worth a decision separately: 154 lines that
nothing imports, superseded by `hub_client.maintain_ws`'s `on_revocation`. This
commit only stopped it failing the linter.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(client): three defects a real desktop found in ten minutes</title>
<updated>2026-08-18T10:54:29Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-18T10:54:29Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=06062d2e8352a205fa634970d260ab0f5de97f04'/>
<id>urn:sha1:06062d2e8352a205fa634970d260ab0f5de97f04</id>
<content type='text'>
All three came from the operator running the application on Ubuntu GNOME. None
would have been found by anything already in the suite.

**A second copy of the hub address.** `keyderive.js` carried
`const HUB = '' // same origin` — true of a page the hub served, false of one
loaded from a package, where the origin is `app://meshbay` and `/v1/users/register`
resolves against the application's own protocol handler. **Sign-up and sign-in,
the first two things anybody does, failed with "Not found."** The seam was
changed in `app.js` and in the signalling call and this was missed: the same
shape as the duplicate `MNP_VERSION` in `protocol.py`, a second copy of a
constant that is harmless until the context changes. `test_hub_address_seam.py`
refuses any file that decides where the hub is, and any `fetch('/v1/…')`
relative to the page origin.

**A window handler reading a variable another path reassigns.** Changing the hub
closes one window and opens another; `closed` arrives *after* the replacement is
assigned, so the outgoing window nulled the reference to the incoming one and
its `ready-to-show` crashed on it — a modal "A JavaScript error occurred in the
main process". Every handler now belongs to the window it was created with. The
CDP test wrote `config.json` in advance, so it never took the one path that
creates a second window; it does now, starting from an empty user-data dir.

**A first run that could not be undone.** The hub address was accepted on
anything URL-shaped and there was no way to change it afterwards — the prompt
only appears when none is set, so a typo meant editing JSON by hand. `https`
typed at a hub speaking `http` produced `TypeError: fetch failed`, which names
nothing. Now: the address is probed before being written, failures say which
URL and why ("does not speak https. If this hub is on your own machine, it is
probably http"), Settings can change it, and Electron's
"Error invoking remote method" wrapper is stripped from what a person reads.

Verified on the operator's desktop: **safeStorage really uses the GNOME
keyring** — Settings reports `gnome-libsecret`, and `secrets.bin` is written
0600 with Chromium's `v11` prefix, the marker for keyring-backed encryption
(the fixed-key fallback writes `v10`). Headless, the same code reports
`unavailable` and refuses to store rather than downgrading in silence, which is
now explained in Settings instead of shown as a bare word.

Unrelated but found while testing: `test_locales.py` assigned to
`globalThis.navigator`, which is read-only from Node 22. The client's build
already requires Node 22+, so the first CI machine configured for it would have
failed these tests for no visible reason. 809 tests pass on Node 18 and Node 24.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(hub): translate the web client into nine more languages</title>
<updated>2026-08-15T22:21:07Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-15T22:21:07Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=3b2d03a919e56d14f8097844b97b844ddf526cd6'/>
<id>urn:sha1:3b2d03a919e56d14f8097844b97b844ddf526cd6</id>
<content type='text'>
French, Spanish, Brazilian Portuguese, Simplified Chinese, Japanese,
German, Italian, Dutch and Polish, all in the formal register. `hub`,
`node` and `GEK` stay in English: they name the CLI, node.toml and the
docs, and translating them would cut the interface off from everything an
operator reads and types.

Catalogues move out of i18n.js into locales/, one file per language,
fetched with a dynamic import. A visitor downloads their language plus
English as a fallback — about 36 KB rather than the ~180 KB that ten
inlined catalogues would have cost everyone. i18n.js keeps only the
loader, so the first render now waits for initLocale().

Three things the old code got wrong, none of them visible until there was
a second language:

- Resolution trimmed a tag to its base before matching, so a browser
  reporting pt-BR looked for a `pt` catalogue that does not exist and fell
  back to English. Matching is now exact first, then by base language.
- Counted strings were single strings, so Polish could not express
  1 plik / 2 pliki / 5 plików at all. t() selects through
  Intl.PluralRules; en.js gains the same treatment, which incidentally
  fixes "1 files".
- Interpolation used String.replace, which reads `$&amp;` in the replacement.
  A file named rap$&amp;sody.mp3 rendered corrupted in its own delete dialog.

Five strings were still hardcoded in app.js — the group name placeholder
and the four visibility/join-policy descriptions — and are now keyed.

test_locales.py holds the nine translations to the shape of en.js: same
keys, a counted string stays counted everywhere, every plural entry covers
each category Intl actually produces for that language, and the
{placeholders} survive translation. Verified failing first, against a
catalogue with a key removed, a placeholder dropped and the Polish `few`
form deleted.

The language menu also grew from one entry to ten, which overran a short
viewport inside a dropdown that clipped instead of scrolling.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
