Corrects issues found reviewing the incoming translations across 20 languages.
CRITICAL:
- Turkish: revert 8 vandalized strings to faithful translations — the
"Open external link?" security dialog, app-upgrade prompt, relay-status
labels, and the "Privacy: for owners and subscribers" line had been
replaced with joke/offensive text.
- Ukrainian: fix a files/media privacy toggle that showed an unrelated
"chat with admins" string.
HIGH/MEDIUM: badge voice/tense inversions (uk, vi, ro, de), meaning
mistranslations (fr, es, id, vi, zh-Hans, ja, nl, ru), the Danish relay-bar
"/" separator loss, re-added role-name quotes (de, es, it, ar, ru, zh-Hans,
zh-Hant), restored dropped \n line breaks and sentences (id), and typos
(hu, es, uk, ro, cs, bg).
Placeholders, XML well-formedness, and .strings validity verified intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* desktop: fix Windows in-app updater corrupting install by exiting before the MSI runs
The Windows install path ran msiexec while the app was still running and waited for
it. The running JVM holds SimpleX.exe, the JRE and DLLs open, so the per-machine MSI
upgrade cannot replace them, defers to a reboot, and the install ends up broken - the
app fails to launch (#7105). Launch the installer and exit instead, so the files are
unlocked; the user reopens from the Start Menu. Also pass the path via the array form
of exec (fixes spaces in the path) and remove a leftover installer before the next
download, since the exiting app can no longer delete it itself.
* docs: plan justifying the Windows in-app MSI updater fix
removeSameColors stored colors.primary in the primaryVariant2 slot instead
of colors.primaryVariant2, so importing a theme with a customized
primaryVariant2 ("Additional accent 2") replaced it with the primary
accent color. Fixed on both Android/desktop and iOS.
* android, desktop, ios: drop stale mention map entry when @name is edited in place
The compose-state mentions map was only pruned when fewer @name tokens were
parsed from the text than were in the map. Editing an inserted @Name token in
place (without re-picking from the picker) keeps the count at 1 while the
parsed name no longer matches the stored key, so the stale entry was sent and
the core rejected it with SEInvalidMention. Pruning now triggers whenever any
map key is absent from the parsed mention names; the modified token is then
sent as an unresolved formatted @ token.
* android, desktop, ios: filter mentions at send time so in-place edits round-trip
Treat the compose-state mentions map as a sticky cache of name -> memberId
bindings recorded by the picker. The previous removeUnusedMentions pruned
it on every text change, which dropped the binding the instant a letter was
removed from an inserted @name token. Now the map is no longer mutated by
text edits: memberMentions, the picker max-reached check, and the
mentionMemberName disambiguator all filter against the names currently
parsed from the message, so deleting and retyping the original characters
re-resolves the original member, and the SEInvalidMention error from an
in-place edit no longer occurs.
* android, desktop, ios: cap memberMentions at MAX so stale cache entries cannot overflow the server limit
With the sticky cache, manually typing an @name that happens to match a
stale cache entry could push memberMentions above maxSndMentions and have
the core reject the send with SEInvalidMention. The getter now walks
parsedMessage in text order and stops at MAX_NUMBER_OF_MENTIONS; later
@-tokens past the cap become visual-only formatting and the message still
goes through. Also hoists the iOS activeMentions computation out of the
per-row ForEach so it runs once per picker open rather than once per row.
* android, desktop, ios: simplify mention restoration to minimal surgical diff
Reverts the unnecessary mentionMemberName changes (the original
mentions.containsKey behaviour preserves bindings better than the
parsed-only variant), switches the picker checks to memberMentions.size
(no helper variable needed since the getter already caps at MAX), and
collapses the Kotlin memberMentions getter into a flat chain.
* android, desktop, ios: gate picker mention-id and max-reached banner by memberMentions
With the sticky cache, the picker's "currently bound member" highlight
and the showMaxReachedBox banner-suppression clause read mentions[name]
directly, so a stale cache entry made one row clickable (as a no-op) at
the MAX limit and could suppress the banner when the user is actually
adding a new mention. Both now gate the lookup by membership in the
capped memberMentions, restoring the pre-fix UX where at MAX all rows
are disabled and the banner shows when adding past the limit.