The custom lower() SQL function registered in cps/db.py transliterates
author names via unidecode, so the case-insensitive lookup in
prepare_authors() also matched authors whose names are genuinely
different but collapse to the same ASCII string (accented vs. plain
Latin names, or Chinese homophones). The code then renamed that matched
author onto the freshly entered name, overwriting a distinct existing
author row and raising:
(sqlite3.IntegrityError) UNIQUE constraint failed: authors.name
Restrict the case-rename branch to matches that are equal ignoring case
only (casefold), so distinct authors are left untouched. Genuine
case-only renames (e.g. "stephen king" -> "Stephen King") still work.
Fixes#3403Fixes#3170
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The download_link endpoint called .lower() on the User-Agent header value without providing a default, causing AttributeError when the header is absent. This matches the safe pattern already used in render_template.py
The scroll detection comparison was too strict for mobile browsers
that render subpixel scroll positions. Added 5px tolerance so the
infinite scroll triggers reliably on mobile devices.
Refs: #3664
The 'Get Started with Calibre-Web' email had a hardcoded English body and a
subject translated in the triggering user's locale (typically the admin), so a
French user received it in English. Wrap subject+body composition in
flask_babel.force_locale(recipient_locale), make the body translatable, and
thread the recipient locale from reset_password and /register. French
translations added; other languages fall back to English until translated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>