Skip to content

Conversation

@albidoo
Copy link

@albidoo albidoo commented Jan 29, 2026

Summary

Adds a Multi-Language Dynamic Cover Image Generator demo to the /community directory, showcasing real-time translation powered by Lingo.dev.


Changes

  • Added community/cover-image-api demo app
  • Integrated Lingo.dev for real-time translation in cover generation
  • Included README with setup instructions and usage examples
  • Added live deployment example links

Testing

Business logic tests added:

  • Validates translation is applied when lang parameter is provided
  • Validates fallback to original text when translation fails
  • Validates parameter validation (width, height, layout constraints)
  • All tests pass locally

Visuals

Required for UI/UX changes:

  • Live demo links attached below
  • Screenshot / preview via generated image

English Preview:
english

Hindi Preview:
hinid


Checklist

  • Changeset added (patch)
  • Tests cover business logic (not just happy path)
  • No breaking changes

Submission for: Subreddit Launch & Community Sprint
Related Issue: #1761

Closes #1761

Summary by CodeRabbit

Release Notes

  • New Features

    • Multi-language cover image generation API with 10 customizable layout options
    • Subtitle support and theme presets (light/dark modes)
    • Configurable styling: custom colors, fonts, dimensions, and padding
    • Built-in caching, rate limiting, gzip compression, and CORS support
    • Health check and API documentation endpoints
  • Documentation

    • Comprehensive README with API reference and usage examples
    • Architecture documentation and deployment guidance

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Core API & Server
api/index.js, package.json
Express application with 5 endpoints: /api/cover for SVG generation with rate limiting and caching, /api/layouts for available layouts, /health for system metrics, /api/docs for documentation, and root / for endpoint listing. Includes error handling, CORS, compression, and Node >=18.0.0 requirement.
Configuration & Constants
config/constants.js
Centralized configuration exports: cache size limits, rate limiting windows/thresholds, default image dimensions, light/dark theme presets, validation ranges (width/height/font-size/padding), and layout positioning strategies with computed coordinates and text anchors.
Translation Service
services/translation.js
TranslationService class integrating Lingo.dev API with try-catch error handling; translate() and translateMultiple() methods with fallback to original text on failure and automatic bypass for English.
SVG Generation Service
services/svg-generator.js
SvgGenerator class providing text wrapping, font size auto-calculation, layout-driven positioning, and SVG assembly with optional dividers; core methods: wrapText(), generateTextElements(), calculateFontSize(), generate().
Utilities
utils/cache.js, utils/rate-limiter.js, utils/validation.js
CacheManager with LRU eviction (configurable max size), RateLimiter with per-IP sliding window tracking, and comprehensive validateParams function with XML escaping, hex color validation, and aggregated error reporting.
Project Setup & Environment
.env.example, .env copy, .gitignore
Environment configuration templates for Lingo.dev API key and port; gitignore patterns for Node.js artifacts, lock files, logs, and build outputs.
Documentation
README.md, CHANGELOG.md, docs/ARCHITECTURE.md
README covering features, quick start, API endpoints with examples, custom styling, multi-language usage, HTML integration patterns, deployment guidance, and troubleshooting; CHANGELOG documenting v2.0.0 features and v1.0.0 baseline; ARCHITECTURE.md detailing modular design, data flow, and extensibility.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related issues

Suggested reviewers

  • vrcprl

Poem

🐰 Whiskers twitching with delight
A cover emerges, translations take flight,
🎨 Ten layouts dance, colors bloom bright,
Cache whispers, rate limits hold tight—
Community hops forward, future so light! 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main contribution: a multi-language dynamic cover image generator is the primary feature being added to the community directory.
Description check ✅ Passed The PR description follows the template with all required sections: Summary, Changes, Testing (with business logic tests checked), Visuals (with live demo links and previews), and Checklist. However, the description references 'community/cover-image-api' while files use 'community/multilangual-cover-image-api', creating a minor inconsistency.
Linked Issues check ✅ Passed The PR meets all core requirements from issue #1761: demo app added to /community directory with full README documentation, Lingo.dev integration for real-time translation demonstrated, and business logic tests included validating translation functionality.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #1761 objectives: the multi-language cover image generator demo showcases Lingo.dev translation features, includes comprehensive documentation, and demonstrates the API with practical examples. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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 tracking package-lock.json for 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 text or plaintext to 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 requestCounts Map 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.stringify produces 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 text or a specific language improves linter compliance and editor highlighting.

-```
+```text
 cover-image-api/
-```
+```text
 GET /api/cover

Also applies to: 89-91

community/multilangual-cover-image-api/utils/validation.js (1)

91-92: Consider adding explicit radix to parseInt calls.

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_KEY is missing or undefined, the TranslationService will be initialized with undefined, 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 text and subtitle need translation, awaiting them sequentially doubles the latency. Consider using Promise.all for parallel execution, or leverage the existing translateMultiple method from TranslationService.

♻️ 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);
+  });
+});

Comment on lines +1 to +2
LINGODOTDEV_API_KEY=your_real_api_key_here
PORT=
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
"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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
## 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.

Comment on lines +112 to +122
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}`,
);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

@github-actions
Copy link
Contributor

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.

@github-actions github-actions bot closed this Jan 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build Demo Apps, Integrations etc & Win Exclusive Lingo.dev Swag!

1 participant