Skip to content

Conversation

@BhagyeshPatil2004
Copy link

@BhagyeshPatil2004 BhagyeshPatil2004 commented Jan 24, 2026

Error Message & Validation Localization Engine

🎯 Overview

A production-grade demo showcasing how to automatically localize backend error and validation messages using Lingo.dev. This addresses a common pain point: most applications translate UI labels but leave backend errors in English, creating a disjointed user experience.

🚀 What This Demo Does

This project demonstrates dynamic backend error localization - intercepting validation errors before they reach the client and translating them to the user's preferred language in real-time.

Key Features:

  • Automatic Language Detection - Supports both ?lang=xx query parameters and Accept-Language headers
  • Real-time Translation - Powered by Lingo.dev SDK with AI-driven localization
  • Multi-language Support - Tested with English, Spanish, French, German, Hindi, and Japanese
  • Production-Ready Pattern - Clean middleware architecture that scales to any language
  • Beautiful UI - Modern frontend with gradient design and smooth animations
  • Zero Manual Translation Files - No hardcoded translations needed

🛠️ Tech Stack

  • Backend: Node.js + Express
  • Localization: Lingo.dev SDK (lingo.dev/sdk)
  • Frontend: Vanilla HTML/CSS/JS (framework-agnostic)

📂 Project Structure

community/error-localization-engine
├── server.js # Express server with CORS
├── lingoClient.js # Lingo.dev SDK wrapper
├── middleware/localizer.js # Language detection middleware
├── routes/auth.js # Auth endpoints with validation
└── public/ # Frontend UI
‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ├── index.html
‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ├── styles.css
‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ └── app.js

🧪 How to Test

  1. Install dependencies: npm install
  2. Add your API key to .env: LINGO_API_KEY = "Your_api_key_here"
  3. Start server: npm start
  4. Open http://localhost:3000
  5. Select a language and try invalid inputs to see localized errors!

💡 Why This Matters

Most developers focus on UI localization but forget about backend errors. This demo shows a scalable pattern for:

  • Centralizing error messages
  • Dynamic localization without manual translation files
  • Maintaining consistency across all user touchpoints

🎁 Contest Submission

Submitting this as part of the Lingo.dev community contest. This is a real-world use case that showcases Lingo.dev's power beyond simple UI translation.


Demo Screenshots :

image‎ ‎ ‎ ‎ ‎ ‎

Screenshot 2026-01-24 213936 ‎ ‎ ‎ ‎ ‎ ‎ Screenshot 2026-01-24 213704 ‎ ‎ ‎ ‎ ‎ ‎ Screenshot 2026-01-24 213809 ‎ ‎ ‎ ‎ ‎ ‎ Screenshot 2026-01-24 214019

Summary by CodeRabbit

  • New Features

    • Added a complete Error Message & Validation Localization demo with an interactive signup form and language selector; shows localized success and error messages.
  • Behavior

    • Intelligent language selection via ?lang or Accept-Language header, with English fallback.
  • Documentation

    • Added README with run instructions and localization examples.
  • Tests

    • Included a verification script to exercise localization scenarios.
  • Chores

    • Added example environment variables and .gitignore entries.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

Adds a new Error Message & Validation Localization Engine demo under community/error-localization-engine: Node.js/Express app with Lingo.dev integration, language-detection middleware, localized auth endpoints, frontend signup UI, example verification script, and packaging/config files.

Changes

Cohort / File(s) Summary
Configuration & Packaging
community/error-localization-engine/.env.example, community/error-localization-engine/.gitignore, community/error-localization-engine/package.json
Add env template (LINGO_API_KEY, PORT), .gitignore for Node artifacts, and package.json declaring dependencies, entrypoint, and start/dev scripts.
Localization Core
community/error-localization-engine/lingoClient.js, community/error-localization-engine/middleware/localizer.js
New lingoClient exposing localizeError(content, targetLocale) with mock and SDK modes and safe fallback; middleware detects target language from ?lang then Accept-Language, sets req.targetLang.
Backend Server & Routes
community/error-localization-engine/server.js, community/error-localization-engine/routes/auth.js
New Express app wiring dotenv, CORS, static files, JSON parsing, localizer middleware, root localized welcome, and auth router with in-memory users providing localized responses for /signup and /login. app exported.
Frontend UI
community/error-localization-engine/public/index.html, community/error-localization-engine/public/app.js, community/error-localization-engine/public/styles.css
New static UI: signup form with language selector, client JS POSTs to /api/auth/signup?lang=..., displays localized responses; CSS for responsive styling and states.
Testing & Documentation
community/error-localization-engine/verify.js, community/error-localization-engine/README.md
Add verification script exercising localization scenarios and README documenting setup, usage, API examples, and language detection priority.

Sequence Diagram

sequenceDiagram
    actor User as User/Browser
    participant Frontend as Frontend (public/app.js)
    participant Server as Express Server (server.js)
    participant Middleware as Localizer Middleware
    participant Auth as Auth Routes (routes/auth.js)
    participant Client as LingoClient (lingoClient.js)
    participant Lingo as Lingo.dev API

    User->>Frontend: Select language & submit signup
    Frontend->>Server: POST /api/auth/signup?lang=xx {email,password}
    Server->>Middleware: incoming request
    Middleware->>Server: set req.targetLang
    Server->>Auth: forward request
    Auth->>Auth: validate input & check user store
    alt Validation or conflict
        Auth->>Client: localizeError(message, req.targetLang)
        Client->>Lingo: localizeObject(...) (if API key configured)
        Lingo-->>Client: localized content
        Client-->>Auth: localized message (or mock prefix)
        Auth-->>Server: respond with error JSON
    else Success
        Auth->>Client: localizeError(successMessage, req.targetLang)
        Client->>Lingo: localizeObject(...) (if used)
        Client-->>Auth: localized success
        Auth-->>Server: respond 201 with message
    end
    Server-->>Frontend: JSON response
    Frontend->>User: display localized message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped a little demo near,
Keys and routes made languages clear,
Forms send errors, Lingo replies,
Español, Français — joyful surprise,
A rabbit cheers: translations here! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description provides comprehensive overview, features, tech stack, and testing instructions, but does not follow the required template structure with Summary, Changes, Testing checklist, Visuals, and Checklist sections. Restructure the description to match the template: add a one-sentence summary section, organize features into a bulleted Changes list, and include the Testing checklist with specific test cases marked as completed.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: a demo application for error localization using Lingo.dev as a contest submission.

✏️ 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.

@BhagyeshPatil2004 BhagyeshPatil2004 changed the title Add Error Localization Engine Demo - Contest Submission Error Localization Engine Demo - Contest Submission Jan 24, 2026
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: 3

🤖 Fix all issues with AI agents
In `@community/error-localization-engine/public/app.js`:
- Around line 18-24: Replace the hardcoded absolute URL in the fetch call
(fetch(`http://localhost:3000/api/auth/signup?lang=${language}`, ...)) with a
relative path and properly encode the query parameter; specifically, call fetch
with '/api/auth/signup?lang=' + encodeURIComponent(language) (keeping the same
method, headers and body) so requests respect the current origin and avoid
mixed-content issues.

In `@community/error-localization-engine/routes/auth.js`:
- Around line 1-3: The code currently stores plaintext passwords and compares
them directly in the auth routes; replace that with Argon2id hashing using the
argon2 package: import argon2, in the registration route (the router.post
handler that saves a new user / stores password) call await
argon2.hash(password, { type: argon2.argon2id }) and store the resulting hash
instead of raw password, and in the login/auth route (the handler that compares
submitted password to stored value) call await argon2.verify(storedHash,
submittedPassword) to validate credentials; ensure both handlers are async,
handle/propagate errors via localizeError/process logger, and never log or
persist plaintext passwords.

In `@community/error-localization-engine/verify.js`:
- Around line 20-25: The current check in verify.js reads
err.response.data.message into msg and looks for the mock prefix '[es]'
(msg.includes('[es]')), which fails for real SDK translations; replace that
brittle check in the block using msg (and the similar block at the later
occurrence) with a resilient language-detection approach: either integrate a
lightweight language detection call (e.g., franc/langdetect) to assert the
message language is "es"/"fr", or implement a heuristic regex/stopword check for
Spanish/French (look for common tokens like Spanish stopwords or punctuation ¿¡
or French stopwords/accents) and use that result instead of checking for literal
prefixes so the checks in the functions that read err.response.data.message
correctly validate real localized output.
🧹 Nitpick comments (9)
community/error-localization-engine/public/styles.css (2)

64-67: Consider adding a visible focus indicator for better accessibility.

The outline: none on focus removes the default focus ring, which can be problematic for keyboard users. Consider adding a custom focus indicator.

♿ Suggested focus style
 .language-selector select:focus {
     outline: none;
     border-color: `#667eea`;
+    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
 }

102-105: Same focus visibility concern for form inputs.

Apply a similar visible focus indicator here for consistency and accessibility.

♿ Suggested focus style
 .form-group input:focus {
     outline: none;
     border-color: `#667eea`;
+    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
 }
community/error-localization-engine/middleware/localizer.js (2)

11-17: Accept-Language parsing may include quality values.

The current parsing takes the first segment (e.g., en-US), but if the header is en-US;q=0.9,es, the result would be en-US;q=0.9 including the quality suffix. Consider stripping the quality value.

🔧 Suggested fix
     if (!lang && req.headers['accept-language']) {
-        const acceptLang = req.headers['accept-language'].split(',')[0].trim();
-        lang = acceptLang;
+        const acceptLang = req.headers['accept-language'].split(',')[0].trim();
+        // Strip quality value (e.g., "en-US;q=0.9" -> "en-US")
+        lang = acceptLang.split(';')[0];
     }

22-23: Debug logging on every request.

For a demo this is fine, but in production consider using a configurable log level or removing verbose request logging.

community/error-localization-engine/lingoClient.js (1)

32-42: Mock translation doesn't handle nested objects.

The mock translation only processes top-level string properties. Nested objects or arrays won't be translated in mock mode. For a demo this may be acceptable, but consider adding recursive handling if deeper structures are expected.

🔧 Recursive mock translation
+    const mockTranslateObject = (obj) => {
+        const translated = {};
+        for (const [key, value] of Object.entries(obj)) {
+            if (typeof value === 'string') {
+                translated[key] = mockTranslate(value);
+            } else if (value && typeof value === 'object' && !Array.isArray(value)) {
+                translated[key] = mockTranslateObject(value);
+            } else {
+                translated[key] = value;
+            }
+        }
+        return translated;
+    };
+
     if (isString) {
         return mockTranslate(content);
     } else {
-        const translated = {};
-        for (const [key, value] of Object.entries(content)) {
-            if (typeof value === 'string') {
-                translated[key] = mockTranslate(value);
-            } else {
-                translated[key] = value;
-            }
-        }
-        return translated;
+        return mockTranslateObject(content);
     }
community/error-localization-engine/package.json (2)

10-15: Consider moving axios to devDependencies.

Based on the PR description, axios appears to be used only in verify.js for testing. If it's not needed at runtime, move it to devDependencies.

📦 Suggested package.json update
   "dependencies": {
-    "axios": "^1.6.2",
     "dotenv": "^16.3.1",
     "express": "^4.18.2",
     "lingo.dev": "^0.125.1"
+  },
+  "devDependencies": {
+    "axios": "^1.6.2"
   }

6-9: The --watch flag requires Node.js 18.11+.

Consider adding an engines field to document the minimum Node.js version requirement for the dev script.

📦 Add engines field
   "scripts": {
     "start": "node server.js",
     "dev": "node --watch server.js"
   },
+  "engines": {
+    "node": ">=18.11.0"
+  },
   "dependencies": {
community/error-localization-engine/server.js (1)

30-34: Move require to top of file.

The inline require('./lingoClient') inside the route handler is evaluated on every request. While Node.js caches modules, it's cleaner and more conventional to import at the top of the file.

🔧 Suggested fix

Add import at top of file with other requires:

const { localizeError } = require('./lingoClient');

Then update the route handler:

 app.get('/', async (req, res) => {
-    const { localizeError } = require('./lingoClient');
     const msg = await localizeError("Welcome to the Error Localization Engine Demo API!", req.targetLang);
     res.send(msg);
 });
community/error-localization-engine/public/index.html (1)

32-37: Prefer semantic input types and built-in validation.
Using type="email"/required and a minlength on password improves UX and aligns client-side validation with backend rules.

♻️ Suggested update
-                    <input type="text" id="email" placeholder="Enter your email">
+                    <input type="email" id="email" placeholder="Enter your email" autocomplete="email" required>
...
-                    <input type="password" id="password" placeholder="Enter your password">
+                    <input type="password" id="password" placeholder="Enter your password" minlength="6" autocomplete="new-password" required>

Comment on lines 18 to 24
const response = await fetch(`http://localhost:3000/api/auth/signup?lang=${language}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password })
});
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 hardcoded localhost in fetch URL.
This breaks when the demo is hosted on a different origin and can trigger mixed-content errors. Use a relative path and encode the query parameter.

🔧 Suggested fix
-        const response = await fetch(`http://localhost:3000/api/auth/signup?lang=${language}`, {
+        const response = await fetch(`/api/auth/signup?lang=${encodeURIComponent(language)}`, {
             method: 'POST',
             headers: {
                 'Content-Type': 'application/json',
             },
             body: JSON.stringify({ email, password })
         });
📝 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 response = await fetch(`http://localhost:3000/api/auth/signup?lang=${language}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password })
});
const response = await fetch(`/api/auth/signup?lang=${encodeURIComponent(language)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password })
});
🤖 Prompt for AI Agents
In `@community/error-localization-engine/public/app.js` around lines 18 - 24,
Replace the hardcoded absolute URL in the fetch call
(fetch(`http://localhost:3000/api/auth/signup?lang=${language}`, ...)) with a
relative path and properly encode the query parameter; specifically, call fetch
with '/api/auth/signup?lang=' + encodeURIComponent(language) (keeping the same
method, headers and body) so requests respect the current origin and avoid
mixed-content issues.

Comment on lines +1 to +3
const express = require('express');
const router = express.Router();
const { localizeError } = require('../lingoClient');
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

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "auth.js" | head -20

Repository: lingodotdev/lingo.dev

Length of output: 117


🏁 Script executed:

cat -n community/error-localization-engine/routes/auth.js | head -80

Repository: lingodotdev/lingo.dev

Length of output: 2815


🌐 Web query:

Node.js password hashing library bcrypt vs argon2 recommended 2025

💡 Result:

For new Node.js apps in 2025, the general recommendation is:

  • Use Argon2id (preferred): OWASP’s Password Storage Cheat Sheet recommends Argon2id as the first choice for password hashing (memory-hard, better resistance to GPU/ASIC cracking), with example minimum settings like m=19 MiB, t=2, p=1 (or equivalent tradeoffs). [1]

    • In Node.js, the widely used argon2 package (“node-argon2”) defaults to Argon2id and exposes timeCost / memoryCost / parallelism tuning. [2]
  • Use bcrypt mainly for legacy compatibility: OWASP positions bcrypt as a legacy/second-line option when Argon2id (or scrypt) isn’t available, recommending a cost/work factor ≥ 10, and noting bcrypt’s common 72-byte password input limit (important with UTF‑8). [1] The bcrypt Node package also documents the 72-byte limit and cost behavior. [3]

Practical takeaway: pick Argon2id (via argon2) unless you specifically need bcrypt compatibility (existing hashes, ecosystem constraints, etc.). [1][2][3]

Sources:
[1] OWASP Password Storage Cheat Sheet (Argon2id preferred; bcrypt legacy guidance; parameters/limits)
[2] npm: argon2 (node-argon2) package docs (Argon2id default; tunable parameters)
[3] npm: bcrypt package docs (72-byte limit; cost rounds)


Use Argon2id for password hashing, not plaintext storage.
Line 42 stores raw passwords and line 61 compares them directly. This is unsafe for any application, including a "production-grade" demo. Use Argon2id (OWASP's 2025 preferred choice) via the argon2 package instead of plaintext storage.

🔐 Suggested fix (Argon2id example)
const express = require('express');
const router = express.Router();
const { localizeError } = require('../lingoClient');
+const argon2 = require('argon2');

// Mock database
const users = [];
...
    // Success
-    users.push({ email, password });
+    const passwordHash = await argon2.hash(password);
+    users.push({ email, passwordHash });
...
    // Simulate finding user
-    const user = users.find(u => u.email === email && u.password === password);
+    const user = users.find(u => u.email === email);
+    const ok = user && await argon2.verify(user.passwordHash, password);

-    if (!user) {
+    if (!ok) {
         // Generic error for security, but localized

Also applies to: 41-43, 61-68

🤖 Prompt for AI Agents
In `@community/error-localization-engine/routes/auth.js` around lines 1 - 3, The
code currently stores plaintext passwords and compares them directly in the auth
routes; replace that with Argon2id hashing using the argon2 package: import
argon2, in the registration route (the router.post handler that saves a new user
/ stores password) call await argon2.hash(password, { type: argon2.argon2id })
and store the resulting hash instead of raw password, and in the login/auth
route (the handler that compares submitted password to stored value) call await
argon2.verify(storedHash, submittedPassword) to validate credentials; ensure
both handlers are async, handle/propagate errors via localizeError/process
logger, and never log or persist plaintext passwords.

Comment on lines +20 to +25
const msg = err.response.data.message || '';
if (msg.includes('[es]')) {
console.log('✅ PASS: Localized to Spanish');
} else {
console.log('❌ FAIL: Not localized correctly');
}
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

Make localization checks robust to real SDK output.
The [es] / [fr] prefix looks mock-specific; real translations will likely fail these checks.

✅ Suggested resilient check
-            const msg = err.response.data.message || '';
-            if (msg.includes('[es]')) {
+            const msg = err.response.data.message || '';
+            const englishBaseline = "Please provide a valid email address.";
+            if (msg.includes('[es]') || msg !== englishBaseline) {
                 console.log('✅ PASS: Localized to Spanish');
             } else {
                 console.log('❌ FAIL: Not localized correctly');
             }
...
-             const msg = err.response.data.message || '';
-            if (msg.includes('[fr]')) {
+             const msg = err.response.data.message || '';
+             const englishBaseline = "Password must be at least 6 characters long.";
+            if (msg.includes('[fr]') || msg !== englishBaseline) {
                 console.log('✅ PASS: Localized to French');
             } else {
                 console.log('❌ FAIL: Not localized correctly');
             }

Also applies to: 45-50

🤖 Prompt for AI Agents
In `@community/error-localization-engine/verify.js` around lines 20 - 25, The
current check in verify.js reads err.response.data.message into msg and looks
for the mock prefix '[es]' (msg.includes('[es]')), which fails for real SDK
translations; replace that brittle check in the block using msg (and the similar
block at the later occurrence) with a resilient language-detection approach:
either integrate a lightweight language detection call (e.g., franc/langdetect)
to assert the message language is "es"/"fr", or implement a heuristic
regex/stopword check for Spanish/French (look for common tokens like Spanish
stopwords or punctuation ¿¡ or French stopwords/accents) and use that result
instead of checking for literal prefixes so the checks in the functions that
read err.response.data.message correctly validate real localized output.

@github-actions
Copy link
Contributor

Thank you for your contribution! However, this PR needs to reference an issue that you're assigned to. Please reference an assigned issue (using "Closes #123" or similar) in the PR description, or get assigned to a relevant issue first. You can find unassigned issues to work on in the repository's issue tracker.

@github-actions github-actions bot closed this Jan 24, 2026
@BhagyeshPatil2004 BhagyeshPatil2004 changed the title Error Localization Engine Demo - Contest Submission Add community demo: error localization engine (Closes #1761) Jan 24, 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.

1 participant