[call-me] - wip...

This commit is contained in:
Miroslav Pejic
2026-02-10 02:14:25 +01:00
parent 8063e51312
commit 2b40e7fdcf
4 changed files with 11 additions and 185 deletions
+8 -3
View File
@@ -29,7 +29,8 @@ This repository supports multiple languages via JSON translation files in `app/l
### Translation source of truth
- Server + client load translations from `app/locales/<locale>.json`.
- Translations live in `app/locales/<locale>.json`.
- The server does not translate strings; it only serves these JSON files to the browser.
- Locales are discovered dynamically by scanning `app/locales/*.json` (no config change required).
### API
@@ -127,13 +128,17 @@ curl http://localhost:8000/translations/es
1. Create `app/locales/<locale>.json` (example: `app/locales/nl.json`).
2. Copy the structure of `app/locales/en.json` and translate only the values.
3. Restart the server.
3. No restart is required: the client will see the new locale via `GET /locales`.
Optional:
- Set `I18N_WATCH=true` to have the server re-scan `app/locales/` and reconfigure i18n automatically when locale files change.
- If you want a friendly label (flag + name) in the Settings dropdown, add your locale to the `getLocaleLabel()` mapping in `public/i18n.js`. Otherwise the dropdown shows the raw locale code.
## Notes
- This project uses client-side i18n (see `public/i18n.js`). The server remains language-agnostic.
- Any server-generated messages (API errors, WebSocket `error` payloads) are currently plain strings and are not translated.
## Adding / Changing Translation Keys
- Keep keys consistent across all locale files.
-45
View File
@@ -13,7 +13,6 @@ const helmet = require('helmet');
const path = require('path');
const yaml = require('js-yaml');
const swaggerUi = require('swagger-ui-express');
const i18n = require('i18n');
const packageJson = require('../package.json');
// Logs
@@ -121,50 +120,6 @@ const corsOptions = {
// Create Express application
const app = express();
function configureI18n() {
const locales = getAvailableLocales();
const effectiveLocales = locales.length > 0 ? locales : ['en'];
const defaultLocale = effectiveLocales.includes('en') ? 'en' : effectiveLocales[0] || 'en';
i18n.configure({
locales: effectiveLocales,
defaultLocale: defaultLocale,
directory: LOCALES_DIR,
objectNotation: true,
updateFiles: false,
syncFiles: false,
api: {
__: 'translate',
__n: 'translateN',
},
});
return { locales: effectiveLocales, defaultLocale };
}
// Configure i18n (dynamic locales from app/locales/*.json)
configureI18n();
// Optional: watch locales folder and reconfigure automatically (no restart)
if (process.env.I18N_WATCH === 'true') {
try {
let debounceTimer;
fs.watch(LOCALES_DIR, { persistent: false }, () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
const state = configureI18n();
log.info('i18n reconfigured', state);
}, 250);
});
log.info('i18n watch enabled', { directory: LOCALES_DIR });
} catch (error) {
log.warn('Unable to watch locales directory', {
directory: LOCALES_DIR,
error: error.message,
});
}
}
// Server configurations
const port = process.env.PORT || 4000;
const host = process.env.HOST || `http://localhost:${port}`;
+2 -135
View File
@@ -1,12 +1,12 @@
{
"name": "call-me",
"version": "1.2.92",
"version": "1.2.93",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "call-me",
"version": "1.2.92",
"version": "1.2.93",
"license": "AGPLv3",
"dependencies": {
"@ngrok/ngrok": "1.7.0",
@@ -17,7 +17,6 @@
"express": "^5.2.1",
"helmet": "^8.1.0",
"httpolyglot": "0.1.2",
"i18n": "^0.15.3",
"js-yaml": "4.1.1",
"socket.io": "^4.8.3",
"swagger-ui-express": "5.0.1"
@@ -27,50 +26,6 @@
"prettier": "3.8.1"
}
},
"node_modules/@messageformat/core": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.4.0.tgz",
"integrity": "sha512-NgCFubFFIdMWJGN5WuQhHCNmzk7QgiVfrViFxcS99j7F5dDS5EP6raR54I+2ydhe4+5/XTn/YIEppFaqqVWHsw==",
"license": "MIT",
"dependencies": {
"@messageformat/date-skeleton": "^1.0.0",
"@messageformat/number-skeleton": "^1.0.0",
"@messageformat/parser": "^5.1.0",
"@messageformat/runtime": "^3.0.1",
"make-plural": "^7.0.0",
"safe-identifier": "^0.4.1"
}
},
"node_modules/@messageformat/date-skeleton": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz",
"integrity": "sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==",
"license": "MIT"
},
"node_modules/@messageformat/number-skeleton": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz",
"integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==",
"license": "MIT"
},
"node_modules/@messageformat/parser": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.1.tgz",
"integrity": "sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==",
"license": "MIT",
"dependencies": {
"moo": "^0.5.1"
}
},
"node_modules/@messageformat/runtime": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.2.tgz",
"integrity": "sha512-dkIPDCjXcfhSHgNE1/qV6TeczQZR59Yx0xXeafVKgK3QVWoxc38ljwpksUpnzCGvN151KUbCJTDZVmahtf1YZw==",
"license": "MIT",
"dependencies": {
"make-plural": "^7.0.0"
}
},
"node_modules/@ngrok/ngrok": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz",
@@ -902,15 +857,6 @@
"node": ">= 0.6"
}
},
"node_modules/fast-printf": {
"version": "1.6.10",
"resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.10.tgz",
"integrity": "sha512-GwTgG9O4FVIdShhbVF3JxOgSBY2+ePGsu2V/UONgoCPzF9VY6ZdBMKsHKCYQHZwNk3qNouUolRDsgVxcVA5G1w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=10.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1185,49 +1131,6 @@
"node": ">=0.10.0"
}
},
"node_modules/i18n": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/i18n/-/i18n-0.15.3.tgz",
"integrity": "sha512-tW/AA5R4lJZLnd60Agcd0PfXB1C2G7UqTrdNewuv/SIYdxcHkCE8w4Zx1SgCjJ+2BLuAAGIG/KXb/xNYF1lO5Q==",
"license": "MIT",
"dependencies": {
"@messageformat/core": "^3.4.0",
"debug": "^4.4.3",
"fast-printf": "^1.6.10",
"make-plural": "^7.4.0",
"math-interval-parser": "^2.0.1",
"mustache": "^4.2.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/mashpie"
}
},
"node_modules/i18n/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/i18n/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/iconv-lite": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
@@ -1324,21 +1227,6 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/make-plural": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.5.0.tgz",
"integrity": "sha512-0booA+aVYyVFoR67JBHdfVk0U08HmrBH2FrtmBqBa+NldlqXv/G2Z9VQuQq6Wgp2jDWdybEWGfBkk1cq5264WA==",
"license": "Unicode-DFS-2016"
},
"node_modules/math-interval-parser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz",
"integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -1400,27 +1288,12 @@
"node": "*"
}
},
"node_modules/moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
"integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
"license": "BSD-3-Clause"
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"license": "MIT"
},
"node_modules/mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"license": "MIT",
"bin": {
"mustache": "bin/mustache"
}
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -1689,12 +1562,6 @@
],
"license": "MIT"
},
"node_modules/safe-identifier": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz",
"integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==",
"license": "ISC"
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+1 -2
View File
@@ -1,6 +1,6 @@
{
"name": "call-me",
"version": "1.2.92",
"version": "1.2.93",
"description": "Your Go-To for Instant Video Calls",
"author": "Miroslav Pejic - miroslav.pejic.85@gmail.com",
"license": "AGPLv3",
@@ -27,7 +27,6 @@
"express": "^5.2.1",
"helmet": "^8.1.0",
"httpolyglot": "0.1.2",
"i18n": "^0.15.3",
"js-yaml": "4.1.1",
"socket.io": "^4.8.3",
"swagger-ui-express": "5.0.1"