Deep-review round 3, targeted at the surfaces round 2's fix commit changed. Five of the eight findings are the same shape: the guards written to stop half-done sweeps were themselves half-done. test_upload.py's display-field guard walked only `ast.Assign`, so the one writer with an unbounded REQUEST HEADER behind it — `spool_upload`'s `display_name=display_text(...)` keyword — was invisible to the guard meant to protect it. It also stopped at `app/routes/`, missing the four writers in `downloads/` that actually construct the row, and asserted `not offenders`, which enforces nothing once a refactor moves the writers out of its view. It now walks assignments AND keywords across `app/`, and holds exact set equality against a registry of all twelve write sites with each one's value. test_body_limit.py's release guard used `any(... ast.walk(fn))`, which accepts a `release_read_connection` that runs AFTER the await it exists to precede — satisfying the letter of the rule while pinning a connection for exactly the transfer it was supposed to free. Position is checked now, and a late release is its own, louder failure. The registry keyed sites by file basename while scanning recursively, and `admin.py`, `discover.py` and `playlists.py` each exist both at the top level and under `plex/` — the same entry-collapse the registry was widened to close. Keyed by relative path now. And all three guards recognised a body read only by the `capped_` naming convention, so a route spelling `await request.body()` inline declared no ceiling, released no connection, and was invisible to every one of them. `release_read_connection`'s precondition claimed the caller "must have NOTHING PENDING, and that is checked rather than described" while checking only UNFLUSHED ORM work. Measured: after `add`+`flush` all three collections are empty and the rollback loses the row, and Core DML never enters them at all — both of them this codebase's canonical write shapes. It now also asks the transaction directly via `pg_current_xact_id_if_assigned()`. `errors.tooLarge` was reworded to "try a smaller file", but that key answers every route's body cap: the issue composer's over-size JSON draft got advice about a file it does not have. Back to payload-neutral; the file-specific sentence keeps its own key. The upload dialog's new danger border outranked `dragOver`, so a user replacing an over-cap file got no sign the drop zone was live — and with a file picked the label shows its name rather than "drop it here", so the border was the only feedback left. Four further findings were refuted 3/3 and are not fixed: `aria-invalid` on the drop zone (measured in Chromium — the AX node does carry it on a native button), a bidi-override spoof through `display_text` (the served filename already strips those categories, and the proposed filter would have broken every ZWJ emoji), and `capped_body` not draining (the screenshot route's edge ceiling equals its own cap, so that refusal is unreachable, and the log sink is sent by beacon with nobody reading the answer).
235 lines
14 KiB
JSON
235 lines
14 KiB
JSON
{
|
|
"title": "Couldn't complete that",
|
|
"generic": "The server couldn't carry out that action. Please try again.",
|
|
"demo": "This isn't available in the demo account.",
|
|
"validation": "Some of the values you entered weren't valid. Please check them and try again.",
|
|
"tooLarge": "That was too large for the server to accept.",
|
|
"server": "Server error",
|
|
"syncCooldown": "You've synced a few times just now — give it a few minutes. The YouTube budget is shared with everyone on this instance, and background syncing keeps running either way.",
|
|
"notFound": {
|
|
"generic": "This item doesn't exist any more.",
|
|
"view": "This view doesn't exist any more.",
|
|
"tag": "This tag doesn't exist any more.",
|
|
"pin": "This isn't on your comfort shelf any more.",
|
|
"playlist": "This playlist doesn't exist any more.",
|
|
"move_target": "The playlist you're moving it to doesn't exist any more.",
|
|
"video": "This video isn't in the library any more.",
|
|
"invite": "This invite doesn't exist any more.",
|
|
"user": "This user doesn't exist any more.",
|
|
"channel": "This channel isn't in the library any more.",
|
|
"subscription": "You're not subscribed to this channel any more.",
|
|
"recipient": "There's no user with that email address.",
|
|
"job": "This job doesn't exist any more.",
|
|
"download": "This download doesn't exist any more.",
|
|
"debug_session": "This debug session doesn't exist any more.",
|
|
"screenshot": "This screenshot doesn't exist any more.",
|
|
"link": "This share link doesn't exist any more.",
|
|
"notification": "This notification doesn't exist any more.",
|
|
"account": "That account no longer exists.",
|
|
"collection": "This collection doesn't exist any more.",
|
|
"config_key": "There's no such setting.",
|
|
"file": "The file is no longer available.",
|
|
"filmstrip": "There's no filmstrip for this.",
|
|
"host": "This host isn't on the list any more.",
|
|
"image": "There's no image for this.",
|
|
"item": "This item doesn't exist any more.",
|
|
"library": "This library doesn't exist any more.",
|
|
"plex_item": "This Plex item isn't in the library any more.",
|
|
"poster": "There's no poster for this.",
|
|
"profile": "This download profile doesn't exist any more.",
|
|
"season": "This season isn't in the library any more.",
|
|
"show": "This show isn't in the library any more."
|
|
},
|
|
"admin": {
|
|
"not_allowed": "Their address isn't approved any more. Add it to the whitelist first.",
|
|
"not_applicable": "This account has nothing left to confirm.",
|
|
"not_approved": "Only an approved address can be invited.",
|
|
"email_off": "No mail was sent: this instance has no working SMTP setup. Check Configuration → Email / SMTP.",
|
|
"send_failed": "The mail couldn't be sent: the mail server was unreachable, or it refused the login or the message. Check Configuration → Email / SMTP, then try again.",
|
|
"demo": "The demo account is shared, so it can't get a set-password link."
|
|
},
|
|
"reason": {
|
|
"unreachable": "The server couldn't be reached. Try again.",
|
|
"forbidden": "You don't have permission to do that.",
|
|
"rateLimited": "Too many requests at once — wait a moment and try again.",
|
|
"unexpected": "Something went wrong in the app. Try again."
|
|
},
|
|
"writeFailed": {
|
|
"hideChannel": "Couldn't change whether the channel is hidden",
|
|
"blockChannel": "Couldn't change the channel's block",
|
|
"unblockChannel": "Couldn't unblock the channel",
|
|
"channelSettings": "Couldn't save the channel's settings",
|
|
"channelPriority": "Couldn't save the channel's priority",
|
|
"exploreChannel": "Couldn't load the channel's videos",
|
|
"downloadQuota": "Couldn't save the download quota",
|
|
"downloadQuotaReset": "Couldn't reset the download quota",
|
|
"job": {
|
|
"pause": "Couldn't pause the download",
|
|
"resume": "Couldn't resume the download",
|
|
"cancel": "Couldn't cancel the download",
|
|
"delete": "Couldn't delete the download",
|
|
"redownload": "Couldn't start the download again"
|
|
},
|
|
"removeShared": "Couldn't remove the shared item",
|
|
"downloadDetails": "Couldn't save the download's details",
|
|
"enqueueDownload": "Couldn't queue the download",
|
|
"downloadProfileSave": "Couldn't save the download profile",
|
|
"downloadProfileDelete": "Couldn't delete the download profile",
|
|
"downloadCookiesRemove": "Couldn't remove those cookies",
|
|
"notifRead": "Couldn't mark the notification as read",
|
|
"notifReadAll": "Couldn't mark the notifications as read",
|
|
"notifDismiss": "Couldn't dismiss the notification",
|
|
"notifClear": "Couldn't clear the notifications",
|
|
"createPlaylist": "Couldn't create the playlist",
|
|
"schedulerRun": "Couldn't start the job",
|
|
"schedulerRunAll": "Couldn't start the jobs",
|
|
"maintenanceBatch": "Couldn't save the maintenance batch size",
|
|
"purgeDiscovery": "Couldn't clear the discovery data",
|
|
"plexWatchLink": "Couldn't change the Plex watch-state sync",
|
|
"plexWatchImport": "Couldn't import the watch state from Plex",
|
|
"shareLinkDownload": "Couldn't change whether the link allows downloading",
|
|
"shareLinkRevoke": "Couldn't revoke the link",
|
|
"shareLinkPassword": "Couldn't change the link's password",
|
|
"syncPause": "Couldn't pause syncing",
|
|
"syncResume": "Couldn't resume syncing",
|
|
"prefs": "Couldn't save your settings",
|
|
"deleteAccount": "Couldn't delete your account",
|
|
"videoState": "Couldn't change the video's state",
|
|
"videoReset": "Couldn't reset the video",
|
|
"watchLaterAdd": "Couldn't add it to Watch later",
|
|
"watchLaterRemove": "Couldn't remove it from Watch later",
|
|
"clearSearch": "Couldn't clear the search results"
|
|
},
|
|
"ok": "OK",
|
|
"offline": {
|
|
"title": "Connection lost",
|
|
"body": "Couldn't reach the server — it may be restarting. This notice will clear once the connection is back."
|
|
},
|
|
"boundary": {
|
|
"title": "Something went wrong.",
|
|
"subtitle": "The app hit an unexpected error.",
|
|
"reload": "Reload",
|
|
"notifTitle": "Something broke",
|
|
"notifMessage": "Unexpected error"
|
|
},
|
|
"youtube": {
|
|
"quota": "Today's YouTube budget for this instance is spent. It resets at midnight Pacific time — try again then.",
|
|
"reconnect": "Google no longer accepts this account's YouTube connection. Sign out and sign in with Google again to reconnect it.",
|
|
"unavailable": "YouTube didn't accept that just now. Please try again."
|
|
},
|
|
"refused": {
|
|
"account_no_access": "That account no longer has access.",
|
|
"account_signed_out": "That account was signed out elsewhere — sign in again.",
|
|
"account_suspended": "That account is suspended.",
|
|
"address_has_account": "That address already has an account — delete the account instead.",
|
|
"admin_first": "Create the admin account first.",
|
|
"admin_only": "Only an admin can do that.",
|
|
"already_set_up": "This instance is already set up.",
|
|
"already_subscribed": "You're already subscribed to this channel.",
|
|
"channel_blocked": "You've blocked this channel.",
|
|
"collection_fields_required": "A title and an item are both needed.",
|
|
"collection_pending_sync": "The collection changed in Plex, but refreshing the local copy failed — it catches up on the next sync.",
|
|
"collection_read_only": "This collection is read-only.",
|
|
"collection_smart": "Smart and auto-list collections can't be made editable.",
|
|
"demo_activate": "The demo account can't be activated.",
|
|
"demo_delete": "The demo account can't be deleted.",
|
|
"demo_role": "The demo account's role can't be changed.",
|
|
"demo_suspend": "The demo account can't be suspended.",
|
|
"download_not_ready": "This download isn't ready.",
|
|
"edit_unfinished": "Only a finished download can be edited.",
|
|
"filters_not_object": "The view's filters are malformed.",
|
|
"google_no_userinfo": "Google didn't send back the account details. Try signing in again.",
|
|
"image_fetch_failed": "The image couldn't be loaded.",
|
|
"image_host": "Images from that address aren't loaded.",
|
|
"invalid_cursor": "The list position is out of date — reload the page.",
|
|
"invalid_email": "Enter a valid email address.",
|
|
"invalid_interval": "The interval must be between {{min}} and {{max}} minutes.",
|
|
"invalid_link": "That doesn't look like a valid video link.",
|
|
"invalid_role": "Unknown role.",
|
|
"invalid_status": "Unknown status.",
|
|
"invite_decided": "This request has already been decided.",
|
|
"job_running": "This job is already running.",
|
|
"last_active_admin": "This is the only active admin — make someone else admin first.",
|
|
"last_admin": "This is the only admin — make someone else admin first.",
|
|
"link_gone": "This link is no longer available.",
|
|
"link_required": "A video link or id is required.",
|
|
"media_missing": "The media file isn't on disk.",
|
|
"message_empty": "The message is empty.",
|
|
"message_rate": "Too many messages — slow down a moment.",
|
|
"message_self": "You can't message yourself.",
|
|
"message_too_long": "The message is too long.",
|
|
"messaging_set_up": "Secure messaging is already set up.",
|
|
"name_required": "A name is required.",
|
|
"no_encryption_key": "Set TOKEN_ENCRYPTION_KEY to store secrets in the database.",
|
|
"no_messaging": "That member hasn't set up messaging yet.",
|
|
"no_profile": "There's no download profile to use.",
|
|
"no_subscription_id": "There's no YouTube subscription on record yet — sync your subscriptions first.",
|
|
"not_authenticated": "You're not signed in.",
|
|
"not_ready": "Not available yet.",
|
|
"not_redownloadable": "This item can't be downloaded again.",
|
|
"not_your_account": "That's not an account you've signed into here.",
|
|
"only_admin_delete": "You're the only admin — make someone else admin before deleting your account.",
|
|
"own_delete": "Delete your own account in Settings → Account.",
|
|
"own_role": "You can't change your own role.",
|
|
"own_suspend": "You can't suspend your own account.",
|
|
"plex_disabled": "The Plex module is turned off.",
|
|
"plex_not_configured": "Plex isn't set up — add the server address and token in Configuration → Plex.",
|
|
"plex_unreachable": "The Plex server didn't answer. Check the address and the token, then try again.",
|
|
"plex_write_failed": "Plex didn't accept the change. Try again.",
|
|
"profile_name_required": "A profile name is required.",
|
|
"quota_reserve": "Today's shared YouTube budget is down to its reserve — try again later.",
|
|
"quota_spent": "Today's shared YouTube budget is spent. Try again tomorrow.",
|
|
"recipient_required": "A recipient email is required.",
|
|
"registration_closed": "Registration is currently closed.",
|
|
"search_daily_limit": "You've reached today's YouTube search limit ({{limit}}). Try again tomorrow.",
|
|
"search_term_required": "Enter something to search for.",
|
|
"session_gone": "The playback session ended.",
|
|
"setting_not_a_choice": "Pick one of: {{choices}}.",
|
|
"setting_not_a_number": "Enter a whole number.",
|
|
"setting_too_high": "The value can be at most {{max}}.",
|
|
"setting_too_low": "The value must be at least {{min}}.",
|
|
"setup_token": "The setup token is missing or wrong.",
|
|
"share_own": "That's already your download.",
|
|
"share_password_required": "This link needs a password.",
|
|
"share_unfinished": "Only a finished download can be shared.",
|
|
"smtp_failed": "The email couldn't be sent — check the SMTP settings.",
|
|
"smtp_off": "SMTP isn't configured.",
|
|
"spec_not_object": "The profile's settings are malformed.",
|
|
"still_downloading": "This download is still in progress.",
|
|
"tag_name_taken": "You already have a tag named “{{name}}”.",
|
|
"title_required": "A title is required.",
|
|
"too_many_attempts": "Too many attempts. Try again shortly.",
|
|
"unknown_setting": "Unknown setting: {{key}}",
|
|
"value_missing": "A value is required.",
|
|
"video_gone": "This video is no longer available.",
|
|
"view_name_taken": "You already have a view named “{{name}}”.",
|
|
"watch_sync_off": "Turn on Plex watch-sync first.",
|
|
"weak_password": "The password must be at least {{min}} characters.",
|
|
"wrong_password": "The current password is incorrect.",
|
|
"wrong_share_password": "Wrong password.",
|
|
"youtube_editing_off": "Turn on YouTube editing in Settings first.",
|
|
"youtube_read_scope": "Connect your YouTube account (read access) first.",
|
|
"debug_session_conflict": "A recording just started in another tab. Reload the page.",
|
|
"issue_already_sent": "This report was already sent; its draft can no longer change.",
|
|
"issue_title_required": "Give the report a title before you send it.",
|
|
"screenshot_already_sent": "The report was already sent; this session takes no more images.",
|
|
"screenshot_too_many": "A session can have at most {{limit}} screenshots.",
|
|
"screenshot_empty": "The image is empty.",
|
|
"screenshot_not_an_image": "This is not a PNG, JPEG or WebP image.",
|
|
"issues_not_configured": "No issue tracker is set up. Download the report as a zip instead.",
|
|
"issues_unreachable": "The issue tracker isn't answering. Try again later.",
|
|
"issues_bad_token": "The issue tracker refused the token, or it lacks the permission.",
|
|
"issues_repo_not_found": "The configured issue repository was not found.",
|
|
"issues_public_repo": "The issue repository is public. Because of the log, reports go only to a private one.",
|
|
"issues_disabled": "The issue repository has its issue tracker switched off.",
|
|
"issues_rejected": "The issue tracker refused the request.",
|
|
"issues_rate_limited": "You can send at most {{limit}} reports an hour. Try again later.",
|
|
"debug_session_rate_limited": "You can start at most {{limit}} new recordings an hour. Try again later.",
|
|
"export_rate_limited": "Too many downloads in a row. Wait a minute.",
|
|
"screenshot_storage_full": "You've reached the {{limit}} MB screenshot allowance. Remove images from older sessions, or wait until they expire.",
|
|
"issue_sending": "The report is being sent right now. Wait for it to finish.",
|
|
"issue_rate_limited": "At most {{limit}} send attempts an hour. Try again later.",
|
|
"preview_rate_limited": "Too many previews in a row. Wait a minute."
|
|
}
|
|
}
|