feat(email): add DE email templates + locale-based template resolver#2623
feat(email): add DE email templates + locale-based template resolver#26238emk10 wants to merge 29 commits intoseerr-team:developfrom
Conversation
…pstream layout; remove action button
- add required locals for media-issue templates (EN+DE) - localize DE issue subjects (Audio/Video/Untertitel; no prefix for OTHER) - fix grammar in DE issue body (Dativ media label) - fix colon + plural handling in media-request extras (EN+DE)
📝 WalkthroughWalkthroughThis pull request implements German language localization for email templates with locale-aware template resolution. It adds helper functions in TypeScript and Pug to select appropriate email templates based on user locale, modifies the email agent to pass locale information, and introduces German email templates for password, media request, and media issue notifications. A GitHub Actions workflow fix corrects commit message quoting to enable variable expansion. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/templates/email/media-issue/html.pug (1)
59-63:⚠️ Potential issue | 🟡 MinorCTA button appears to be missing its text content.
The action URL button template at lines 59-63 ends without any text content inside the span. The button would render empty. Compare with other templates that include text like
| View Issue in #{applicationTitle}.🔧 Suggested fix
a(href=actionUrl style='display: block; margin: 1.5rem 3rem 0; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: `#fff`; display: block; border: 1px solid rgba(255,255,255,0.2);') + | View Issue in #{applicationTitle}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/templates/email/media-issue/html.pug` around lines 59 - 63, The CTA button span inside the media-issue HTML pug template is empty so the button renders with no label; update the conditional block that uses actionUrl to include visible text in the span (for example: "View Issue in #{applicationTitle}" or another appropriate label), ensuring the span sits between the opening and closing tags and references the existing applicationTitle variable so the button displays a proper call-to-action.
🧹 Nitpick comments (2)
server/entity/User.ts (1)
33-72: Consider consolidating locale-template resolution into one shared utility.This logic now exists in both
server/entity/User.tsandserver/lib/notifications/agents/email.ts, which is easy to desync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/entity/User.ts` around lines 33 - 72, Extract the locale/template resolution logic currently implemented in __normalizeLocale and __userEmailTemplateDir into a single shared utility (e.g., server/lib/emailTemplateResolver.ts) and replace the duplicate implementations in User.ts and server/lib/notifications/agents/email.ts with calls to that utility; ensure the utility exposes functions with the same semantics (normalizeLocale(locale?: string) and resolveUserEmailTemplateDir(templateName: string, userLocale?: string, globalLocale?: string)) and preserves the existing candidate search order, existence checks (existsSync), and fallback behavior so both callers can import and use the same implementation.server/lib/notifications/agents/email.ts (1)
18-60: Recommend reusing the resolver from one shared module.Locale resolution + existence probing is now duplicated here and in
server/entity/User.ts. Centralizing it will prevent behavior drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@server/lib/notifications/agents/email.ts` around lines 18 - 60, Replace the duplicated locale-resolution and template-probing logic in __normalizeLocale and __templateDirFor by importing and using the shared resolver from the common module used by server/entity/User.ts; remove the local __normalizeLocale, __templateDirFor and tryLoc implementations and call the shared function (the one that handles candidate paths, EN fallback, and existence checks) so the same behavior is used across User.ts and email.ts, preserving the existing behavior of probing built vs source templates and checking for html.pug existence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/create-tag.yml:
- Line 81: The commit uses single quotes so ${TAG_VERSION} won't expand; update
the git commit invocation (the line with git commit -m 'chore(release): prepare
${TAG_VERSION}') to use double quotes instead so the TAG_VERSION environment
variable expands (e.g., change the commit message quoting around chore(release):
prepare ${TAG_VERSION}).
In `@server/entity/User.ts`:
- Around line 41-55: The base-selection loop in __userEmailTemplateDir currently
checks for 'generatedpassword/html.pug' and should instead look for the
requested template; update the candidates probing to check path.join(c,
templateName, 'html.pug') (so the chosen base actually contains the requested
templateName), preserve the existing try/catch behavior and the same fallback
return, and ensure references to templateName, userLocale, and globalLocale
remain unchanged elsewhere in __userEmailTemplateDir.
In `@server/lib/notifications/agents/email.ts`:
- Around line 31-59: The template directory resolution can return non-existent
paths because the initial base detection only checks 'test-email/html.pug' and
the final fallback returns path.join(base, templateName) without verifying it
exists; update __templateDirFor's base candidate check to look for path.join(c,
templateName, 'html.pug') (so the chosen base actually contains the requested
template), modify tryLoc to only return the 'en' path when
existsSync(path.join(base, templateName, 'html.pug')) is true, and change the
final return to verify existence (e.g., return the first existing of tryLoc(u),
tryLoc(g), or path.join(base, templateName) only if existsSync(...), otherwise
return '' or throw) referencing the existing symbols base, tryLoc, templateName,
__normalizeLocale, u, and g.
In `@server/templates/email/de/_helpers.pug`:
- Around line 5-7: The media-type detection incorrectly checks mediaType ===
'tv' and uses self-referential fallbacks; update the __isSeries branch to check
mediaType === 'series' (to match the sender locals) and replace the
self-referential fallback (typeof __isSeries !== 'undefined' && __isSeries) with
a non-self fallback flag passed in the locals (e.g., typeof series !==
'undefined' && series or a distinct flag like typeof isSeriesFallback !==
'undefined' && isSeriesFallback); similarly ensure __isMovie uses a reliable
non-self fallback (e.g., typeof movie !== 'undefined' && movie or a dedicated
fallback flag) so both __isMovie and __isSeries derive from actual provided
locals and not from their own prior values.
In `@server/templates/email/de/media-request/body.pug`:
- Line 16: The template references __isMovie in the line that builds the
descriptive phrase but never defines it; add a local definition (as done in
subject.pug) that derives __isMovie from the available context (e.g., const
__isMovie = typeof isMovie !== "undefined" ? isMovie : (__mediaType ===
"movie")) before the line using it so the conditional expression ((typeof is4k
!== "undefined" && is4k) ? (__isMovie ? "dem 4K Film" : "der 4K Serie") :
__mediaLabelDat()) always evaluates reliably.
In `@server/templates/email/de/media-request/html.pug`:
- Line 42: The div that builds the inline style currently always injects
imageUrl into url(...), causing url(undefined) when no poster exists; update the
template so the background image is only appended when imageUrl is truthy (e.g.,
produce background: linear-gradient(...) + (imageUrl ? ", url('"+imageUrl+"')
center 25%/cover" : "") ), or alternatively switch to two separate style/class
paths: one with the gradient+image and one with only the gradient/fallback;
refer to the div that constructs the style string and the imageUrl variable to
implement the conditional.
- Around line 40-66: The media-card table row (the top-level "tr" that contains
the div with imageUrl, actionUrl, mediaName, requestedBy and mediaExtra) was
dedented and sits outside the main table, which causes email clients to drop it;
move that "tr" so it is nested inside the parent table element (the table with
style='color: `#fff`; width: 100%;') so the row and its child td blocks (including
the conditional td for imageUrl and the timestamp row) are properly wrapped by
the table; ensure the existing child nodes (a with class 'title', the spans
using requestedBy.replace, the each mediaExtra loop, the conditional if imageUrl
block, and the timestamp td) keep their relative structure and indentation under
that table.
In `@server/templates/email/media-request/body.pug`:
- Around line 4-18: The notification templates use __prefix4k (either "4K " or
"") and currently interpolate as "#{__followingAcc()} #{__prefix4k}is..."
causing double spaces when __prefix4k is empty; update every branch (the lines
for MEDIA_AUTO_REQUESTED, MEDIA_APPROVED, MEDIA_AUTO_APPROVED, MEDIA_DECLINED,
MEDIA_AVAILABLE, the default branch, and the initial line) to use the same
conditional spacing logic used in the MEDIA_FAILED case so spacing is only added
when __prefix4k is non-empty (i.e., replace the "#{__followingAcc()}
#{__prefix4k}..." pattern with the conditional approach that trims and prefixes
a single space when needed, referencing __prefix4k and __followingAcc()).
In `@server/templates/email/media-request/html.pug`:
- Line 38: The second table row in the email template has its tr token
de-indented to column 0 which breaks table nesting; restore proper indentation
by moving that tr back to the same nesting level as the first tr (indent by 4
spaces so its child td lines remain at 6 spaces) in
server/templates/email/media-request/html.pug so the tr/td hierarchy is
consistent.
---
Outside diff comments:
In `@server/templates/email/media-issue/html.pug`:
- Around line 59-63: The CTA button span inside the media-issue HTML pug
template is empty so the button renders with no label; update the conditional
block that uses actionUrl to include visible text in the span (for example:
"View Issue in #{applicationTitle}" or another appropriate label), ensuring the
span sits between the opening and closing tags and references the existing
applicationTitle variable so the button displays a proper call-to-action.
---
Nitpick comments:
In `@server/entity/User.ts`:
- Around line 33-72: Extract the locale/template resolution logic currently
implemented in __normalizeLocale and __userEmailTemplateDir into a single shared
utility (e.g., server/lib/emailTemplateResolver.ts) and replace the duplicate
implementations in User.ts and server/lib/notifications/agents/email.ts with
calls to that utility; ensure the utility exposes functions with the same
semantics (normalizeLocale(locale?: string) and
resolveUserEmailTemplateDir(templateName: string, userLocale?: string,
globalLocale?: string)) and preserves the existing candidate search order,
existence checks (existsSync), and fallback behavior so both callers can import
and use the same implementation.
In `@server/lib/notifications/agents/email.ts`:
- Around line 18-60: Replace the duplicated locale-resolution and
template-probing logic in __normalizeLocale and __templateDirFor by importing
and using the shared resolver from the common module used by
server/entity/User.ts; remove the local __normalizeLocale, __templateDirFor and
tryLoc implementations and call the shared function (the one that handles
candidate paths, EN fallback, and existence checks) so the same behavior is used
across User.ts and email.ts, preserving the existing behavior of probing built
vs source templates and checking for html.pug existence.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
.github/workflows/create-tag.ymlserver/entity/User.tsserver/lib/notifications/agents/email.tsserver/templates/email/_helpers.pugserver/templates/email/de/_helpers.pugserver/templates/email/de/generatedpassword/html.pugserver/templates/email/de/generatedpassword/subject.pugserver/templates/email/de/media-issue/html.pugserver/templates/email/de/media-issue/subject.pugserver/templates/email/de/media-request/body.pugserver/templates/email/de/media-request/html.pugserver/templates/email/de/media-request/subject.pugserver/templates/email/de/resetpassword/html.pugserver/templates/email/de/resetpassword/subject.pugserver/templates/email/de/test-email/html.pugserver/templates/email/de/test-email/subject.pugserver/templates/email/media-issue/html.pugserver/templates/email/media-issue/subject.pugserver/templates/email/media-request/body.pugserver/templates/email/media-request/html.pugserver/templates/email/media-request/subject.pugserver/templates/email/test-email/html.pug
| run: | | ||
| git add package.json | ||
| git commit -m "chore(release): prepare ${TAG_VERSION}" | ||
| git commit -m 'chore(release): prepare ${TAG_VERSION}' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the actual file content at line 81
sed -n '75,85p' .github/workflows/create-tag.ymlRepository: seerr-team/seerr
Length of output: 381
🏁 Script executed:
# Run the provided test script to confirm variable expansion behavior
#!/bin/bash
set -euo pipefail
TAG_VERSION="1.2.3"
printf 'single-quoted -> %s\n' 'chore(release): prepare ${TAG_VERSION}'
printf 'double-quoted -> %s\n' "chore(release): prepare ${TAG_VERSION}"Repository: seerr-team/seerr
Length of output: 162
🏁 Script executed:
# Check the full workflow file to see where TAG_VERSION is defined
cat .github/workflows/create-tag.ymlRepository: seerr-team/seerr
Length of output: 2705
Use double quotes to expand TAG_VERSION in the commit message.
Single quotes prevent the variable from expanding, resulting in a commit message containing the literal text ${TAG_VERSION} instead of the actual version number. This is inconsistent with other uses of TAG_VERSION in the same job (lines 76, 85).
💡 Proposed fix
- git commit -m 'chore(release): prepare ${TAG_VERSION}'
+ git commit -m "chore(release): prepare ${TAG_VERSION}"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| git commit -m 'chore(release): prepare ${TAG_VERSION}' | |
| git commit -m "chore(release): prepare ${TAG_VERSION}" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/create-tag.yml at line 81, The commit uses single quotes
so ${TAG_VERSION} won't expand; update the git commit invocation (the line with
git commit -m 'chore(release): prepare ${TAG_VERSION}') to use double quotes
instead so the TAG_VERSION environment variable expands (e.g., change the commit
message quoting around chore(release): prepare ${TAG_VERSION}).
| function __userEmailTemplateDir(templateName: string, userLocale?: string, globalLocale?: string) { | ||
| const base = (() => { | ||
| // Prefer built templates (prod), then source templates (dev) | ||
| const candidates = [ | ||
| path.join(process.cwd(), 'dist/templates/email'), | ||
| path.join(process.cwd(), 'server/templates/email'), | ||
| path.join(__dirname, '../templates/email'), | ||
| ]; | ||
| for (const c of candidates) { | ||
| try { | ||
| if (existsSync(path.join(c, 'generatedpassword', 'html.pug'))) return c; | ||
| } catch (_) {} | ||
| } | ||
| return path.join(process.cwd(), 'server/templates/email'); | ||
| })(); |
There was a problem hiding this comment.
Probe base directories using the requested templateName, not generatedpassword.
At Line 51, base selection is tied to generatedpassword/html.pug. This can pick a base where templateName doesn’t exist, causing later render failures.
Suggested fix
- if (existsSync(path.join(c, 'generatedpassword', 'html.pug'))) return c;
+ if (existsSync(path.join(c, templateName, 'html.pug'))) return c;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function __userEmailTemplateDir(templateName: string, userLocale?: string, globalLocale?: string) { | |
| const base = (() => { | |
| // Prefer built templates (prod), then source templates (dev) | |
| const candidates = [ | |
| path.join(process.cwd(), 'dist/templates/email'), | |
| path.join(process.cwd(), 'server/templates/email'), | |
| path.join(__dirname, '../templates/email'), | |
| ]; | |
| for (const c of candidates) { | |
| try { | |
| if (existsSync(path.join(c, 'generatedpassword', 'html.pug'))) return c; | |
| } catch (_) {} | |
| } | |
| return path.join(process.cwd(), 'server/templates/email'); | |
| })(); | |
| function __userEmailTemplateDir(templateName: string, userLocale?: string, globalLocale?: string) { | |
| const base = (() => { | |
| // Prefer built templates (prod), then source templates (dev) | |
| const candidates = [ | |
| path.join(process.cwd(), 'dist/templates/email'), | |
| path.join(process.cwd(), 'server/templates/email'), | |
| path.join(__dirname, '../templates/email'), | |
| ]; | |
| for (const c of candidates) { | |
| try { | |
| if (existsSync(path.join(c, templateName, 'html.pug'))) return c; | |
| } catch (_) {} | |
| } | |
| return path.join(process.cwd(), 'server/templates/email'); | |
| })(); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/entity/User.ts` around lines 41 - 55, The base-selection loop in
__userEmailTemplateDir currently checks for 'generatedpassword/html.pug' and
should instead look for the requested template; update the candidates probing to
check path.join(c, templateName, 'html.pug') (so the chosen base actually
contains the requested templateName), preserve the existing try/catch behavior
and the same fallback return, and ensure references to templateName, userLocale,
and globalLocale remain unchanged elsewhere in __userEmailTemplateDir.
| const base = (() => { | ||
| // Prefer built templates (prod), then source templates (dev) | ||
| const candidates = [ | ||
| path.join(process.cwd(), 'dist/templates/email'), | ||
| path.join(process.cwd(), 'server/templates/email'), | ||
| ]; | ||
| for (const c of candidates) { | ||
| try { | ||
| if (existsSync(path.join(c, 'test-email', 'html.pug'))) return c; | ||
| } catch (_) {} | ||
| } | ||
| return path.join(process.cwd(), 'server/templates/email'); | ||
| })(); | ||
| const tryLoc = (loc: string) => { | ||
| // EN lives at base/<templateName>, localized at base/<loc>/<templateName> | ||
| if (!loc) return ''; | ||
| if (loc === 'en') return path.join(base, templateName); | ||
| const candidate = path.join(base, loc, templateName); | ||
| // Check html.pug existence as "template exists" signal | ||
| if (existsSync(path.join(candidate, 'html.pug'))) return candidate; | ||
| return ''; | ||
| }; | ||
|
|
||
| const u = __normalizeLocale(userLocale); | ||
| const g = __normalizeLocale(globalLocale); | ||
|
|
||
| return ( | ||
| tryLoc(u) || tryLoc(g) || path.join(base, templateName) // EN fallback | ||
| ); |
There was a problem hiding this comment.
__templateDirFor can return template paths that don’t exist.
Line 39 probes only test-email/html.pug, then Line 58 falls back to path.join(base, templateName) without existence checks. This can resolve to a missing template directory for non-test templates.
Suggested fix
- if (existsSync(path.join(c, 'test-email', 'html.pug'))) return c;
+ if (existsSync(path.join(c, templateName, 'html.pug'))) return c;
...
- return (
- tryLoc(u) || tryLoc(g) || path.join(base, templateName) // EN fallback
- );
+ const enFallback = path.join(base, templateName);
+ if (tryLoc(u)) return tryLoc(u);
+ if (tryLoc(g)) return tryLoc(g);
+ if (existsSync(path.join(enFallback, 'html.pug'))) return enFallback;
+ return path.join(process.cwd(), 'server/templates/email', templateName);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/lib/notifications/agents/email.ts` around lines 31 - 59, The template
directory resolution can return non-existent paths because the initial base
detection only checks 'test-email/html.pug' and the final fallback returns
path.join(base, templateName) without verifying it exists; update
__templateDirFor's base candidate check to look for path.join(c, templateName,
'html.pug') (so the chosen base actually contains the requested template),
modify tryLoc to only return the 'en' path when existsSync(path.join(base,
templateName, 'html.pug')) is true, and change the final return to verify
existence (e.g., return the first existing of tryLoc(u), tryLoc(g), or
path.join(base, templateName) only if existsSync(...), otherwise return '' or
throw) referencing the existing symbols base, tryLoc, templateName,
__normalizeLocale, u, and g.
| - var __isMovie = (typeof mediaType !== 'undefined' && mediaType === 'movie') || (typeof __isMovie !== 'undefined' && __isMovie) | ||
| - var __isSeries = (typeof mediaType !== 'undefined' && mediaType === 'tv') || (typeof __isSeries !== 'undefined' && __isSeries) | ||
|
|
There was a problem hiding this comment.
Align media-type detection with provided locals (series) and non-self fallback flags.
Line 6 checks mediaType === 'tv', but sender locals use 'series'. Also, typeof __isSeries !== 'undefined' && __isSeries is self-referential and not a reliable fallback.
Suggested fix
- var __isMovie = (typeof mediaType !== 'undefined' && mediaType === 'movie') || (typeof __isMovie !== 'undefined' && __isMovie)
- var __isSeries = (typeof mediaType !== 'undefined' && mediaType === 'tv') || (typeof __isSeries !== 'undefined' && __isSeries)
+ var __isMovie = (typeof mediaType !== 'undefined' && mediaType === 'movie') || (typeof isMovie !== 'undefined' && isMovie)
+ var __isSeries = (typeof mediaType !== 'undefined' && (mediaType === 'series' || mediaType === 'tv')) || (typeof isSeries !== 'undefined' && isSeries)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - var __isMovie = (typeof mediaType !== 'undefined' && mediaType === 'movie') || (typeof __isMovie !== 'undefined' && __isMovie) | |
| - var __isSeries = (typeof mediaType !== 'undefined' && mediaType === 'tv') || (typeof __isSeries !== 'undefined' && __isSeries) | |
| - var __isMovie = (typeof mediaType !== 'undefined' && mediaType === 'movie') || (typeof isMovie !== 'undefined' && isMovie) | |
| - var __isSeries = (typeof mediaType !== 'undefined' && (mediaType === 'series' || mediaType === 'tv')) || (typeof isSeries !== 'undefined' && isSeries) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/templates/email/de/_helpers.pug` around lines 5 - 7, The media-type
detection incorrectly checks mediaType === 'tv' and uses self-referential
fallbacks; update the __isSeries branch to check mediaType === 'series' (to
match the sender locals) and replace the self-referential fallback (typeof
__isSeries !== 'undefined' && __isSeries) with a non-self fallback flag passed
in the locals (e.g., typeof series !== 'undefined' && series or a distinct flag
like typeof isSeriesFallback !== 'undefined' && isSeriesFallback); similarly
ensure __isMovie uses a reliable non-self fallback (e.g., typeof movie !==
'undefined' && movie or a dedicated fallback flag) so both __isMovie and
__isSeries derive from actual provided locals and not from their own prior
values.
| when "MEDIA_AVAILABLE" | ||
| | #{__followingAcc()} #{__prefix4k}ist jetzt verfügbar: | ||
| when "MEDIA_FAILED" | ||
| | Bei #{((typeof is4k !== "undefined" && is4k) ? (__isMovie ? "dem 4K Film" : "der 4K Serie") : __mediaLabelDat())} ist ein Fehler aufgetreten: |
There was a problem hiding this comment.
__isMovie is referenced but not defined in this template.
Line 16 uses __isMovie in the conditional expression, but unlike subject.pug which defines const __isMovie = ..., this template doesn't define it. This could result in a runtime error or incorrect behavior if __isMovie is not provided by the included helpers.
🔧 Suggested fix - add __isMovie definition
- const __nt = (typeof notificationType !== "undefined" ? String(notificationType) : "")
+- const __isMovie = (typeof isMovie !== "undefined" ? !!isMovie : false)
case __nt🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/templates/email/de/media-request/body.pug` at line 16, The template
references __isMovie in the line that builds the descriptive phrase but never
defines it; add a local definition (as done in subject.pug) that derives
__isMovie from the available context (e.g., const __isMovie = typeof isMovie !==
"undefined" ? isMovie : (__mediaType === "movie")) before the line using it so
the conditional expression ((typeof is4k !== "undefined" && is4k) ? (__isMovie ?
"dem 4K Film" : "der 4K Serie") : __mediaLabelDat()) always evaluates reliably.
| tr | ||
| td | ||
| div(style='box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: #fff; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%), url(' + imageUrl + ') center 25%/cover') | ||
| table(style='color: #fff; width: 100%;') | ||
| tr | ||
| td(style='vertical-align: top;') | ||
| a(href=actionUrl style='display: block; max-width: 20rem; color: #fff; font-weight: 700; text-decoration: none; margin: 0 1rem 0.25rem 0; font-size: 1.3em; line-height: 1.25em; margin-bottom: 5px;' class='title') | ||
| | #{mediaName} | ||
| div(style='overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #d1d5db; font-size: .975em; line-height: 1.45em; padding-top: .25rem; padding-bottom: .25rem;') | ||
| span(style='display: block;') | ||
| b(style='color: #9ca3af; font-weight: 700;') | ||
| | Angefragt von | ||
| | #{requestedBy.replace(/\.|@/g, ((x) => x + '\ufeff'))} | ||
| each extra in mediaExtra | ||
| span(style='display: block;') | ||
| b(style='color: #9ca3af; font-weight: 700;') | ||
| | #{__extraLabel(extra.name, extra.value)}: | ||
| | #{extra.value} | ||
| if imageUrl | ||
| td(rowspan='2' style='width: 7rem;') | ||
| a(style='display: block; width: 7rem; overflow: hidden; border-radius: .375rem;' href=actionUrl) | ||
| div(style='overflow: hidden; box-sizing: border-box; margin: 0px;') | ||
| img(alt='' src=imageUrl style='box-sizing: border-box; padding: 0px; border: none; margin: auto; display: block; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%;') | ||
| tr | ||
| td(style='font-size: .85em; color: #9ca3af; line-height: 1em; vertical-align: bottom; margin-right: 1rem') | ||
| span | ||
| | #{timestamp} |
There was a problem hiding this comment.
Keep the media card row inside the main table.
At Line 40, tr is dedented out of the table created at Line 22. In email clients, orphan table rows are often dropped, so this section may not render.
Suggested fix
-tr
- td
- div(style='box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: `#fff`; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%), url(' + imageUrl + ') center 25%/cover')
+ tr
+ td
+ div(style='box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: `#fff`; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%), url(' + imageUrl + ') center 25%/cover')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/templates/email/de/media-request/html.pug` around lines 40 - 66, The
media-card table row (the top-level "tr" that contains the div with imageUrl,
actionUrl, mediaName, requestedBy and mediaExtra) was dedented and sits outside
the main table, which causes email clients to drop it; move that "tr" so it is
nested inside the parent table element (the table with style='color: `#fff`;
width: 100%;') so the row and its child td blocks (including the conditional td
for imageUrl and the timestamp row) are properly wrapped by the table; ensure
the existing child nodes (a with class 'title', the spans using
requestedBy.replace, the each mediaExtra loop, the conditional if imageUrl
block, and the timestamp td) keep their relative structure and indentation under
that table.
| include ./body.pug | ||
| tr | ||
| td | ||
| div(style='box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: #fff; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%), url(' + imageUrl + ') center 25%/cover') |
There was a problem hiding this comment.
Avoid rendering url(undefined) in the card background.
Line 42 always appends imageUrl into url(...). When no poster is available, this becomes url(undefined).
Suggested fix
+ - const cardBg = imageUrl
+ - ? 'box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: `#fff`; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%), url(' + imageUrl + ') center 25%/cover'
+ - : 'box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: `#fff`; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%)'
- div(style='box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: `#fff`; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%), url(' + imageUrl + ') center 25%/cover')
+ div(style=cardBg)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/templates/email/de/media-request/html.pug` at line 42, The div that
builds the inline style currently always injects imageUrl into url(...), causing
url(undefined) when no poster exists; update the template so the background
image is only appended when imageUrl is truthy (e.g., produce background:
linear-gradient(...) + (imageUrl ? ", url('"+imageUrl+"') center 25%/cover" :
"") ), or alternatively switch to two separate style/class paths: one with the
gradient+image and one with only the gradient/fallback; refer to the div that
constructs the style string and the imageUrl variable to implement the
conditional.
| | A new request for #{__followingAcc()} #{__prefix4k}is pending approval: | ||
| when "MEDIA_AUTO_REQUESTED" | ||
| | A new request for #{__followingAcc()} #{__prefix4k}is pending approval: | ||
| when "MEDIA_APPROVED" | ||
| | A request for #{__followingAcc()} #{__prefix4k}was approved: | ||
| when "MEDIA_AUTO_APPROVED" | ||
| | A request for #{__followingAcc()} #{__prefix4k}was auto-approved: | ||
| when "MEDIA_DECLINED" | ||
| | A request for #{__followingAcc()} #{__prefix4k}was declined: | ||
| when "MEDIA_AVAILABLE" | ||
| | #{__followingAcc()} #{__prefix4k}is now available: | ||
| when "MEDIA_FAILED" | ||
| | There was an error processing #{__followingAcc()}#{__prefix4k ? " " + String(__prefix4k).trim() : ""}: | ||
| default | ||
| | Notification for #{__followingAcc()} #{__prefix4k}: |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg "__prefix4k" -n --type pug,js,typescriptRepository: seerr-team/seerr
Length of output: 101
🏁 Script executed:
rg "__prefix4k" -nRepository: seerr-team/seerr
Length of output: 4833
Fix spacing inconsistency in notification messages.
__prefix4k is defined as either '4K ' (with trailing space) or '' (empty string). Lines 4, 6, 8, 10, 12, 14, and 18 use the pattern #{__followingAcc()} #{__prefix4k}is..., which produces double spaces when __prefix4k is empty (e.g., "A new request for the movie is pending").
Line 16 correctly handles this with #{__prefix4k ? " " + String(__prefix4k).trim() : ""}, but the other cases don't. Apply consistent spacing logic across all cases by either:
- Always using the conditional approach from line 16, or
- Adjusting the pattern to handle empty
__prefix4kconsistently
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/templates/email/media-request/body.pug` around lines 4 - 18, The
notification templates use __prefix4k (either "4K " or "") and currently
interpolate as "#{__followingAcc()} #{__prefix4k}is..." causing double spaces
when __prefix4k is empty; update every branch (the lines for
MEDIA_AUTO_REQUESTED, MEDIA_APPROVED, MEDIA_AUTO_APPROVED, MEDIA_DECLINED,
MEDIA_AVAILABLE, the default branch, and the initial line) to use the same
conditional spacing logic used in the MEDIA_FAILED case so spacing is only added
when __prefix4k is non-empty (i.e., replace the "#{__followingAcc()}
#{__prefix4k}..." pattern with the conditional approach that trims and prefixes
a single space when needed, referencing __prefix4k and __followingAcc()).
| | #{body} | ||
| tr | ||
| include ./body.pug | ||
| tr |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="server/templates/email/media-request/html.pug"
echo "Lines 34-42 with numbers:"
nl -ba "$FILE" | sed -n '34,42p'
echo
echo "Leading-space counts (expect Line 38 to align with other rows under table):"
python - <<'PY'
from pathlib import Path
p = Path("server/templates/email/media-request/html.pug")
for i, line in enumerate(p.read_text().splitlines(), 1):
if 34 <= i <= 42:
indent = len(line) - len(line.lstrip(" "))
print(f"Line {i}: indent={indent} | {line.lstrip()}")
PYRepository: seerr-team/seerr
Length of output: 145
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="server/templates/email/media-request/html.pug"
echo "Lines 34-42 with line numbers:"
cat -n "$FILE" | sed -n '34,42p'
echo
echo "Indentation analysis:"
python3 - <<'PY'
from pathlib import Path
p = Path("server/templates/email/media-request/html.pug")
lines = p.read_text().splitlines()
for i in range(33, min(42, len(lines))): # 0-indexed, so 33 = line 34
line = lines[i]
indent = len(line) - len(line.lstrip(" "))
line_num = i + 1
print(f"Line {line_num}: indent={indent:2d} | {repr(line)}")
PYRepository: seerr-team/seerr
Length of output: 1371
Line 38's tr element is de-indented to the root level (0 spaces) and breaks the table structure.
The second tr should be indented to 4 spaces to align with the first tr on line 34, matching the nesting pattern where child td elements follow at indent 6 (line 39).
Proposed fix
-tr
+ tr📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| tr | |
| tr |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@server/templates/email/media-request/html.pug` at line 38, The second table
row in the email template has its tr token de-indented to column 0 which breaks
table nesting; restore proper indentation by moving that tr back to the same
nesting level as the first tr (indent by 4 spaces so its child td lines remain
at 6 spaces) in server/templates/email/media-request/html.pug so the tr/td
hierarchy is consistent.
|
👋 @8emk10, thank you for your contribution! |
Description
server/templates/email/de/*) for:recipient locale → global locale → EN fallback (with existence check).
email.ts.__extraLabel.No changes to business logic outside of email rendering.
How Has This Been Tested?
Summary by CodeRabbit
New Features
Bug Fixes