Translation on Firefox Translations models; new logo

- Email translation moves from LibreTranslate to our own translator
  service (translator/): Mozilla's Firefox Translations models on the
  Bergamot engine, fastText language detection (176 languages). Polish,
  English, German, French, Spanish and Italian on first start (~350 MB
  instead of ~10 GB); Admin panel > Email translation installs and
  removes languages. Built and pushed as the `translator` image.
- Every message is translated paragraph by paragraph as plain text;
  names travel with their sentence and are kept only where the model
  changed them; a sentence start the model copied ("Enclosed") is tried
  again in lower case; list markers are not sent
- The Translate button shows only for installed languages
- New logo: favicon, app/maskable/iPhone icons, notification badge,
  email logo and calendar copy regenerated; BIMI logo rebuilt in SVG
  Tiny PS with its gradients; masks render in browsers
- Launch screen background is the app's dark grey

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 22:04:04 +02:00
co-authored by Claude Opus 5.5
parent 8a1b9979e9
commit 972e10876b
38 changed files with 1627 additions and 223 deletions
+23 -1
View File
@@ -436,6 +436,24 @@ print('OFF' if not url else urllib.request.urlopen(url + '/v2/languages', timeou
printf ' %s\n' "$lt" ;;
esac
# Email translation: is the translator answering, with which languages?
tr="$(compose exec -T api python -c "
import json, os, urllib.request
url = os.environ.get('TRANSLATOR_URL', '')
if not url:
print('OFF')
else:
d = json.load(urllib.request.urlopen(url + '/languages', timeout=5))
busy = ', '.join(d.get('installing') or {})
print('OK ' + ' '.join(d['installed']) + (' (installing: ' + busy + ')' if busy else ''))
" 2>&1 | tail -1)" || true
case "$tr" in
OK*) grn " translation answering; languages: ${tr#OK }" ;;
OFF) ylw " translation is off (TRANSLATOR_URL is empty)" ;;
*) ylw " translation not answering — the Translate button stays hidden"
printf ' %s\n' "$tr" ;;
esac
if command -v ss >/dev/null 2>&1; then
local p
for p in "WEB_PORT:${WEB_PORT:-8088}" "ADMIN_PORT:${ADMIN_PORT:-8089}"; do
@@ -503,7 +521,7 @@ cmd_pull() {
Set REGISTRY_PREFIX and IMAGE_TAG in ${ENV_FILE}, or use: webmail.sh up --build"
detect_compose
IMAGE_TAG="$tag" compose pull || die "pull failed — is the registry reachable and logged in?"
grn "pulled ${REGISTRY_PREFIX}/{backend,web}:${tag}. Next: ./deploy/webmail.sh up"
grn "pulled ${REGISTRY_PREFIX}/{backend,web,translator}:${tag}. Next: ./deploy/webmail.sh up"
}
cmd_down() { require_env; compose --profile meili down; grn "stopped (data kept in ${DATA_ROOT})"; }
@@ -587,6 +605,10 @@ cmd_update() {
# then the API, then the web front end.
IMAGE_TAG="$target" compose up -d worker-sync worker-jobs
IMAGE_TAG="$target" compose up -d api web
# The translator is ours too, so it follows the tag. Named here (not left
# to --no-recreate below) so it is replaced on every update, and so init,
# which it depends on, runs again when a release adds a data directory.
IMAGE_TAG="$target" compose up -d translator
# Converge anything the ordered roll does not name — a release that adds a
# service (the antispam engine did) would otherwise never start it, and the
# stack would look updated while missing a container. --no-recreate so this