-
Notifications
You must be signed in to change notification settings - Fork 829
feat: add Multi-Language Dynamic Cover Image Generator #1929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughThis PR introduces a complete multilingual cover image API in the community directory, featuring an Express server with customizable SVG cover generation, Lingo.dev-powered translation support, built-in caching and rate limiting, comprehensive input validation, and modular architecture with extensive documentation. Changes
Sequence DiagramsequenceDiagram
participant Client
participant API as Express API
participant RateLimiter
participant Validator
participant Cache
participant TranslationSvc
participant SVGGenerator
Client->>API: GET /api/cover?text=...&lang=fr
API->>RateLimiter: checkLimit(clientIP)
alt Limit Exceeded
RateLimiter-->>API: false
API-->>Client: 429 Too Many Requests
else Limit OK
RateLimiter-->>API: true
API->>Validator: validateParams(query)
alt Validation Fails
Validator-->>API: {errors: [...]}
API-->>Client: 400 Bad Request
else Validation OK
Validator-->>API: {params: {...}}
API->>Cache: generateKey(params)
Cache-->>API: cacheKey
API->>Cache: get(cacheKey)
alt Cache Hit
Cache-->>API: cachedSVG
API-->>Client: 200 SVG (from cache)
else Cache Miss
API->>TranslationSvc: translate(text, lang)
TranslationSvc-->>API: translatedText
API->>SVGGenerator: generate({text, ...options})
SVGGenerator-->>API: svgMarkup
API->>Cache: set(cacheKey, svgMarkup)
Cache-->>API: ok
API-->>Client: 200 SVG (fresh)
end
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@community/multilangual-cover-image-api/.env` copy:
- Around line 1-2: Remove the committed secret-containing file named ".env copy"
from the repository, stop tracking it (git rm --cached " .env copy" or remove
and commit), add a proper .env.example with placeholder keys and add ".env" and
".env copy" to .gitignore, and update README with local setup instructions so
developers use .env.example rather than committing real API keys; if the key was
committed to history, rotate the key and purge it from git history (eg. git
filter-repo or BFG) to prevent leakage.
In `@community/multilangual-cover-image-api/api/index.js`:
- Line 66: Replace the deprecated req.connection usage when computing clientIp:
in the line that sets clientIp (const clientIp = req.ip ||
req.connection.remoteAddress), use req.socket.remoteAddress as the fallback
instead (e.g., derive clientIp from req.ip || req.socket.remoteAddress) and
ensure you handle undefined/null values consistently.
In `@community/multilangual-cover-image-api/package.json`:
- Line 22: Update the package.json "author" field which currently contains the
placeholder "Your Name" to the actual author or organization name (or a scoped
organization string) so package metadata is accurate; locate the "author"
property in package.json and replace the placeholder value with the correct
author/organization identifier.
In `@community/multilangual-cover-image-api/README.md`:
- Line 294: The "## � Troubleshooting" heading contains a corrupted emoji
character; open the README and replace the bad glyph in the heading (the string
"## � Troubleshooting") with a valid emoji (e.g., "## 🛠️ Troubleshooting" or
"## 🔧 Troubleshooting") or remove the emoji entirely so the heading reads "##
Troubleshooting", then save and verify the file renders correctly.
In `@community/multilangual-cover-image-api/utils/validation.js`:
- Around line 112-122: The fontSize parsing in utils/validation.js assigns
params.fontSize = parseInt(query.fontSize) but doesn't check for NaN, so
non-numeric input like "abc" slips through; update the fontSize handling (where
params.fontSize and VALIDATION_RULES.FONT_SIZE are referenced) to validate that
parseInt produced a finite number (e.g., use Number.isFinite/Number.isNaN checks
or isNaN) before applying min/max checks, push an error and avoid setting
params.fontSize if it's invalid; apply the identical fix to the padding logic
that sets params.padding (lines that reference VALIDATION_RULES.PADDING) so
non-numeric padding also triggers an error and isn't assigned.
🧹 Nitpick comments (9)
community/multilangual-cover-image-api/.gitignore (1)
1-4: Consider trackingpackage-lock.jsonfor reproducible installs.For an application/demo repo, committing the lockfile usually improves determinism and supply‑chain stability. Removing the ignore entry enables that.
♻️ Suggested tweak
-# Dependencies -node_modules/ -package-lock.json +# Dependencies +node_modules/community/multilangual-cover-image-api/docs/ARCHITECTURE.md (1)
9-51: Consider adding a language specifier to the fenced code block.The ASCII diagram could use a language hint like
textorplaintextto satisfy linters, though this is purely cosmetic for documentation.-``` +```text ┌─────────────────────────────────────────────────────────┐community/multilangual-cover-image-api/utils/rate-limiter.js (1)
29-46: Consider periodic cleanup of stale IP entries.The
requestCountsMap retains entries for all IPs ever seen, even after their requests age out of the window. For long-running instances with many unique IPs, this could cause gradual memory growth.For this demo app, this is acceptable. If needed later, a periodic cleanup could remove IPs with empty or fully-expired request arrays.
💡 Optional improvement
checkLimit(ip) { const now = Date.now(); const userRequests = this.requestCounts.get(ip) || []; // Filter out old requests outside the time window const recentRequests = userRequests.filter( (time) => now - time < this.windowMs, ); + // Clean up if no recent requests + if (recentRequests.length === 0 && userRequests.length > 0) { + this.requestCounts.delete(ip); + } + // Check if limit exceeded if (recentRequests.length >= this.maxRequests) { return false; }community/multilangual-cover-image-api/utils/cache.js (1)
25-27: Cache key generation may miss hits for reordered params.
JSON.stringifyproduces different strings for objects with the same keys in different orders. If the API receives{text: "a", lang: "es"}and{lang: "es", text: "a"}, they'll generate different cache keys despite being semantically identical.For consistent cache hits, consider sorting keys before stringifying:
💡 Optional improvement
generateKey(params) { - return JSON.stringify(params); + return JSON.stringify( + Object.keys(params).sort().reduce((obj, key) => { + obj[key] = params[key]; + return obj; + }, {}) + ); }community/multilangual-cover-image-api/README.md (1)
21-40: Consider adding language specifiers to fenced code blocks.The project structure diagram (line 21) and endpoint definition (line 89) lack language hints. Adding
textor a specific language improves linter compliance and editor highlighting.-``` +```text cover-image-api/-``` +```text GET /api/coverAlso applies to: 89-91
community/multilangual-cover-image-api/utils/validation.js (1)
91-92: Consider adding explicit radix toparseIntcalls.While modern JavaScript defaults to base 10, explicitly specifying the radix improves clarity and prevents edge cases with inputs like
"08"in older environments.♻️ Suggested change
- params.width = parseInt(query.width) || 1200; - params.height = parseInt(query.height) || 630; + params.width = parseInt(query.width, 10) || 1200; + params.height = parseInt(query.height, 10) || 630;community/multilangual-cover-image-api/api/index.js (3)
38-44: Consider validating the API key at startup.If
LINGODOTDEV_API_KEYis missing or undefined, theTranslationServicewill be initialized withundefined, and translation failures will only be discovered at runtime when requests are made. Adding a startup check would provide earlier feedback.♻️ Suggested startup validation
+// Validate required environment variables +if (!process.env.LINGODOTDEV_API_KEY) { + console.warn( + "⚠️ LINGODOTDEV_API_KEY not set. Translation features will not work.", + ); +} + // Initialize services const translationService = new TranslationService( process.env.LINGODOTDEV_API_KEY, );
103-118: Sequential translations could be parallelized.When both
textandsubtitleneed translation, awaiting them sequentially doubles the latency. Consider usingPromise.allfor parallel execution, or leverage the existingtranslateMultiplemethod fromTranslationService.♻️ Suggested parallel translation
- // Translate text if needed - const translatedText = await translationService.translate( - params.text, - params.lang, - ); - const translatedSubtitle = params.subtitle - ? await translationService.translate(params.subtitle, params.lang) - : ""; + // Translate text (and subtitle if present) in parallel + const textsToTranslate = [params.text]; + if (params.subtitle) { + textsToTranslate.push(params.subtitle); + } + const translated = await translationService.translateMultiple( + textsToTranslate, + params.lang, + ); + const translatedText = translated[0]; + const translatedSubtitle = translated[1] || "";
346-352: Consider handling SIGINT for local development.SIGTERM is appropriate for container orchestrators, but SIGINT (Ctrl+C) is commonly used during local development. Adding a handler improves the development experience.
♻️ Suggested addition
process.on("SIGTERM", () => { console.log("SIGTERM signal received: closing HTTP server"); server.close(() => { console.log("HTTP server closed"); process.exit(0); }); }); + +process.on("SIGINT", () => { + console.log("SIGINT signal received: closing HTTP server"); + server.close(() => { + console.log("HTTP server closed"); + process.exit(0); + }); +});
| LINGODOTDEV_API_KEY=your_real_api_key_here | ||
| PORT= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid committing a real .env file.
This tracked file can easily end up containing a real API key and won’t be ignored by .gitignore, creating a high risk of secret leakage. Prefer removing it from the repo and rely on .env.example + README instructions for local setup.
🛡️ Suggested change (remove this file from the repo)
-LINGODOTDEV_API_KEY=your_real_api_key_here
-PORT=📝 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.
| LINGODOTDEV_API_KEY=your_real_api_key_here | |
| PORT= |
🤖 Prompt for AI Agents
In `@community/multilangual-cover-image-api/.env` copy around lines 1 - 2, Remove
the committed secret-containing file named ".env copy" from the repository, stop
tracking it (git rm --cached " .env copy" or remove and commit), add a proper
.env.example with placeholder keys and add ".env" and ".env copy" to .gitignore,
and update README with local setup instructions so developers use .env.example
rather than committing real API keys; if the key was committed to history,
rotate the key and purge it from git history (eg. git filter-repo or BFG) to
prevent leakage.
| * - padding (optional): Padding in pixels (default: 60) | ||
| */ | ||
| app.get("/api/cover", async (req, res) => { | ||
| const clientIp = req.ip || req.connection.remoteAddress; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
req.connection is deprecated in Express 5.
In Express 5 and Node.js 18+, req.connection is deprecated. Use req.socket.remoteAddress as the fallback instead.
🐛 Proposed fix
- const clientIp = req.ip || req.connection.remoteAddress;
+ const clientIp = req.ip || req.socket?.remoteAddress;📝 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.
| const clientIp = req.ip || req.connection.remoteAddress; | |
| const clientIp = req.ip || req.socket?.remoteAddress; |
🤖 Prompt for AI Agents
In `@community/multilangual-cover-image-api/api/index.js` at line 66, Replace the
deprecated req.connection usage when computing clientIp: in the line that sets
clientIp (const clientIp = req.ip || req.connection.remoteAddress), use
req.socket.remoteAddress as the fallback instead (e.g., derive clientIp from
req.ip || req.socket.remoteAddress) and ensure you handle undefined/null values
consistently.
| "og-image", | ||
| "social-media" | ||
| ], | ||
| "author": "Your Name", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace the placeholder author metadata.
"author": "Your Name" looks like a placeholder and can be misleading in published metadata and docs. Please update it to the actual author or organization.
✏️ Suggested change
- "author": "Your Name",
+ "author": "Lingo.dev Community",📝 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.
| "author": "Your Name", | |
| "author": "Lingo.dev Community", |
🤖 Prompt for AI Agents
In `@community/multilangual-cover-image-api/package.json` at line 22, Update the
package.json "author" field which currently contains the placeholder "Your Name"
to the actual author or organization name (or a scoped organization string) so
package metadata is accurate; locate the "author" property in package.json and
replace the placeholder value with the correct author/organization identifier.
| } | ||
| ``` | ||
|
|
||
| ## � Troubleshooting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broken emoji character in heading.
The "Troubleshooting" heading has a corrupted emoji character (�). This should be replaced with a valid emoji or removed.
-## � Troubleshooting
+## 🔧 Troubleshooting📝 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.
| ## � Troubleshooting | |
| ## 🔧 Troubleshooting |
🤖 Prompt for AI Agents
In `@community/multilangual-cover-image-api/README.md` at line 294, The "## �
Troubleshooting" heading contains a corrupted emoji character; open the README
and replace the bad glyph in the heading (the string "## � Troubleshooting")
with a valid emoji (e.g., "## 🛠️ Troubleshooting" or "## 🔧 Troubleshooting")
or remove the emoji entirely so the heading reads "## Troubleshooting", then
save and verify the file renders correctly.
| if (query.fontSize) { | ||
| params.fontSize = parseInt(query.fontSize); | ||
| if ( | ||
| params.fontSize < VALIDATION_RULES.FONT_SIZE.MIN || | ||
| params.fontSize > VALIDATION_RULES.FONT_SIZE.MAX | ||
| ) { | ||
| errors.push( | ||
| `fontSize must be between ${VALIDATION_RULES.FONT_SIZE.MIN} and ${VALIDATION_RULES.FONT_SIZE.MAX}`, | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NaN can slip through validation when non-numeric strings are provided.
When query.fontSize is a non-numeric string like "abc", parseInt returns NaN. Since NaN < 12 and NaN > 200 are both false, no error is pushed, but params.fontSize becomes NaN, which will cause rendering issues in the SVG generator.
The same issue exists for padding (lines 138-148).
🐛 Proposed fix
// Optional: fontSize
if (query.fontSize) {
params.fontSize = parseInt(query.fontSize);
+ if (isNaN(params.fontSize)) {
+ errors.push("fontSize must be a valid number");
+ } else if (
- if (
params.fontSize < VALIDATION_RULES.FONT_SIZE.MIN ||
params.fontSize > VALIDATION_RULES.FONT_SIZE.MAX
) {
errors.push(
`fontSize must be between ${VALIDATION_RULES.FONT_SIZE.MIN} and ${VALIDATION_RULES.FONT_SIZE.MAX}`,
);
}
}Apply the same pattern to padding validation at lines 138-148.
📝 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.
| if (query.fontSize) { | |
| params.fontSize = parseInt(query.fontSize); | |
| if ( | |
| params.fontSize < VALIDATION_RULES.FONT_SIZE.MIN || | |
| params.fontSize > VALIDATION_RULES.FONT_SIZE.MAX | |
| ) { | |
| errors.push( | |
| `fontSize must be between ${VALIDATION_RULES.FONT_SIZE.MIN} and ${VALIDATION_RULES.FONT_SIZE.MAX}`, | |
| ); | |
| } | |
| } | |
| if (query.fontSize) { | |
| params.fontSize = parseInt(query.fontSize); | |
| if (isNaN(params.fontSize)) { | |
| errors.push("fontSize must be a valid number"); | |
| } else if ( | |
| params.fontSize < VALIDATION_RULES.FONT_SIZE.MIN || | |
| params.fontSize > VALIDATION_RULES.FONT_SIZE.MAX | |
| ) { | |
| errors.push( | |
| `fontSize must be between ${VALIDATION_RULES.FONT_SIZE.MIN} and ${VALIDATION_RULES.FONT_SIZE.MAX}`, | |
| ); | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@community/multilangual-cover-image-api/utils/validation.js` around lines 112
- 122, The fontSize parsing in utils/validation.js assigns params.fontSize =
parseInt(query.fontSize) but doesn't check for NaN, so non-numeric input like
"abc" slips through; update the fontSize handling (where params.fontSize and
VALIDATION_RULES.FONT_SIZE are referenced) to validate that parseInt produced a
finite number (e.g., use Number.isFinite/Number.isNaN checks or isNaN) before
applying min/max checks, push an error and avoid setting params.fontSize if it's
invalid; apply the identical fix to the padding logic that sets params.padding
(lines that reference VALIDATION_RULES.PADDING) so non-numeric padding also
triggers an error and isn't assigned.
|
Thanks for your contribution! However, this PR references issue #1761, but you're not currently assigned to that issue. Please either get assigned to the issue first, or find an unassigned issue to work on. You can check the issues page for available tasks that need contributors. |
Summary
Adds a Multi-Language Dynamic Cover Image Generator demo to the
/communitydirectory, showcasing real-time translation powered by Lingo.dev.Changes
community/cover-image-apidemo appTesting
Business logic tests added:
langparameter is providedVisuals
Required for UI/UX changes:
English Preview:

Hindi Preview:

Checklist
Submission for: Subreddit Launch & Community Sprint
Related Issue: #1761
Closes #1761
Summary by CodeRabbit
Release Notes
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.