feat(i18n): Add internationalization support

- Add i18n module with locale loading and translation helpers
- Add English (en) and French (fr) locale files with comprehensive translations
- Inject locale data into dashboard HTML via server
- Add /api/locales endpoint for locale info
- Add t() translation function to dashboard

Translated elements:
- Boot sequence (initialization, connecting, sweep complete)
- Header pills (sweep, sources, delta, risk indicators)
- Left rail panels (sensor grid, nuclear watch, risk gauges, space watch)
- Layer names and descriptions
- Map legend items
- Lower panels (news ticker, sweep delta, macro+markets, trade ideas)
- Right rail (OSINT stream)
- Badges and status indicators

Supported languages: English (default), French
Set CRUCIX_LANG=fr to switch to French
This commit is contained in:
David
2026-03-18 08:36:48 +01:00
parent c29ec93350
commit 9b395b6aa5
5 changed files with 944 additions and 54 deletions

View File

@@ -8,6 +8,7 @@ import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { exec } from 'child_process';
import config from './crucix.config.mjs';
import { getLocale, currentLanguage, getSupportedLocales } from './lib/i18n.mjs';
import { fullBriefing } from './apis/briefing.mjs';
import { synthesize, generateIdeas } from './dashboard/inject.mjs';
import { MemoryManager } from './lib/delta/index.mjs';
@@ -231,12 +232,20 @@ if (discordAlerter.isConfigured) {
const app = express();
app.use(express.static(join(ROOT, 'dashboard/public')));
// Serve loading page until first sweep completes, then the dashboard
// Serve loading page until first sweep completes, then the dashboard with injected locale
app.get('/', (req, res) => {
if (!currentData) {
res.sendFile(join(ROOT, 'dashboard/public/loading.html'));
} else {
res.sendFile(join(ROOT, 'dashboard/public/jarvis.html'));
const htmlPath = join(ROOT, 'dashboard/public/jarvis.html');
let html = readFileSync(htmlPath, 'utf-8');
// Inject locale data into the HTML
const locale = getLocale();
const localeScript = `<script>window.__CRUCIX_LOCALE__ = ${JSON.stringify(locale).replace(/<\/script>/gi, '<\\/script>')};</script>`;
html = html.replace('</head>', `${localeScript}\n</head>`);
res.type('html').send(html);
}
});
@@ -263,6 +272,15 @@ app.get('/api/health', (req, res) => {
llmProvider: config.llm.provider,
telegramEnabled: !!(config.telegram.botToken && config.telegram.chatId),
refreshIntervalMinutes: config.refreshIntervalMinutes,
language: currentLanguage,
});
});
// API: available locales
app.get('/api/locales', (req, res) => {
res.json({
current: currentLanguage,
supported: getSupportedLocales(),
});
});