-
Notifications
You must be signed in to change notification settings - Fork 824
Add community demo: error localization engine (Closes #1761) #1879
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
📝 WalkthroughWalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 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: noneon 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 isen-US;q=0.9,es, the result would been-US;q=0.9including 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 movingaxiosto devDependencies.Based on the PR description,
axiosappears to be used only inverify.jsfor testing. If it's not needed at runtime, move it todevDependencies.📦 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--watchflag requires Node.js 18.11+.Consider adding an
enginesfield to document the minimum Node.js version requirement for thedevscript.📦 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: Moverequireto 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.
Usingtype="email"/requiredand aminlengthon 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>
| const response = await fetch(`http://localhost:3000/api/auth/signup?lang=${language}`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify({ email, password }) | ||
| }); |
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 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.
| 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.
| const express = require('express'); | ||
| const router = express.Router(); | ||
| const { localizeError } = require('../lingoClient'); |
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.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "auth.js" | head -20Repository: lingodotdev/lingo.dev
Length of output: 117
🏁 Script executed:
cat -n community/error-localization-engine/routes/auth.js | head -80Repository: 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
argon2package (“node-argon2”) defaults to Argon2id and exposes timeCost / memoryCost / parallelism tuning. [2]
- In Node.js, the widely used
-
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
bcryptNode 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 localizedAlso 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.
| const msg = err.response.data.message || ''; | ||
| if (msg.includes('[es]')) { | ||
| console.log('✅ PASS: Localized to Spanish'); | ||
| } else { | ||
| console.log('❌ FAIL: Not localized correctly'); | ||
| } |
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.
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.
|
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. |
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:
?lang=xxquery parameters andAccept-Languageheaders🛠️ Tech Stack
lingo.dev/sdk)📂 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
npm installLINGO_API_KEY = "Your_api_key_here"npm starthttp://localhost:3000💡 Why This Matters
Most developers focus on UI localization but forget about backend errors. This demo shows a scalable pattern for:
🎁 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 :
Summary by CodeRabbit
New Features
Behavior
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.