Productivity features, UI translations, local email translation

- Snooze (with a Snoozed folder), keyboard shortcuts with a Settings
  section, mail templates (Compose > More > Templates), a Subscriptions
  folder with bulk unsubscribe/delete, and tracker blocking with a
  Privacy toggle (on by default)
- Own dialogs instead of browser popups; emoji picker in compose; compose
  windows survive a visit to Settings
- Sentence completion learned from the user's sent mail (no AI)
- Interface in English and Polish (one file per language, English as the
  fallback), picked from the browser on first run, then kept
- Email translation on the server via LibreTranslate: Translate button
  and "Translate to...", messages in several languages translated part
  by part, names/addresses/emoji/file names/phone numbers kept, an
  animated line while translating
- Antispam "Mark as spam" loads with the message, not when the menu opens

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 20:00:31 +02:00
co-authored by Claude Opus 5.5
parent 463f96ad57
commit 8a1b9979e9
83 changed files with 8677 additions and 293 deletions
+30 -1
View File
@@ -71,6 +71,8 @@ x-backend-env: &backend-env
RSPAMD_CONTROLLER_PASSWORD: ${RSPAMD_CONTROLLER_PASSWORD}
# Spelling and grammar check in the compose window. Empty turns it off.
LANGUAGETOOL_URL: ${LANGUAGETOOL_URL-http://languagetool:8010}
# Email translation; empty turns it off.
LIBRETRANSLATE_URL: ${LIBRETRANSLATE_URL-http://libretranslate:5000}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
UNDO_SECONDS: ${UNDO_SECONDS:-10}
SESSION_TTL_DAYS: ${SESSION_TTL_DAYS:-30}
@@ -109,12 +111,13 @@ services:
- |
set -e
mkdir -p /mnt/postgres /mnt/blobs /mnt/backups /mnt/redis /mnt/meili /mnt/state \
/mnt/languagetool-ngrams
/mnt/languagetool-ngrams /mnt/libretranslate
# Only recurse when ownership is actually wrong: `chown -R` over a large
# blob store on every start would be minutes of pointless disk churn.
[ "$$(stat -c %u /mnt/blobs)" = "10001" ] || chown -R 10001:10001 /mnt/blobs
[ "$$(stat -c %u /mnt/backups)" = "999" ] || chown -R 999:999 /mnt/backups
[ "$$(stat -c %u /mnt/redis)" = "999" ] || chown -R 999:999 /mnt/redis
[ "$$(stat -c %u /mnt/libretranslate)" = "1032" ] || chown -R 1032:65534 /mnt/libretranslate
chmod 700 /mnt/backups
echo "[init] data directories ready"
volumes:
@@ -313,6 +316,32 @@ services:
cpus: ${LANGUAGETOOL_CPUS:-1.0}
logging: *logging
# Email translation (app/translation.py), all on this server. The first
# start downloads every language model (a few GB) into DATA_ROOT; later
# starts reuse them. A model is loaded into memory the first time its
# language is used.
libretranslate:
image: libretranslate/libretranslate:v1.9.6
environment:
LT_DISABLE_WEB_UI: "true"
LT_UPDATE_MODELS: "false"
LT_THREADS: ${LIBRETRANSLATE_THREADS:-2}
TZ: ${TZ:-UTC}
volumes:
- ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/libretranslate:/home/libretranslate/.local
healthcheck:
test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:5000/languages', timeout=4)\""]
interval: 30s
timeout: 6s
retries: 5
start_period: 30m # the first start downloads the models
depends_on:
init: { condition: service_completed_successfully }
restart: unless-stopped
mem_limit: ${LIBRETRANSLATE_MEM_LIMIT:-6g}
cpus: ${LIBRETRANSLATE_CPUS:-2.0}
logging: *logging
# Bayes tokens and fuzzy hashes. Separate from the app's redis on purpose
# (§C.2): this data is derived and retrainable, so it trades durability for
# room to grow — RDB snapshots every 5 min, and an eviction policy, where the