The language selector is dynamically generated by fetching the list of available languages from the API. When a user changes the selected language, the script retrieves and applies the corresponding translation data.
Example snippet from the script:
document.addEventListener("DOMContentLoaded", async () => { availableLanguages = await fetchAvailableLanguages(); const deviceLang = navigator.language || navigator.userLanguage; const baseDeviceLang = deviceLang.split('-')[0]; if (availableLanguages.includes(baseDeviceLang) && baseDeviceLang !== currentLang) { currentLang = baseDeviceLang; } if (languageSelector) { languageSelector.innerHTML = ''; availableLanguages.forEach(lang => { const option = document.createElement('option'); option.value = lang; option.textContent = lang; languageSelector.appendChild(option); }); languageSelector.addEventListener('change', async (event) => { currentLang = event.target.value; await loadTranslations(currentLang); }); } await fetchAndCacheAllTranslations(); await loadTranslations(currentLang); });
This client-side snippet ensures a fast user experience by first checking for cached translations in the local storage, then fetching the latest data from multiple fallback URLs to update the cache in the background.
The server-side code is built with Node.js and Express. It offers several endpoints to retrieve available languages and translation data. The server prebuilds translations using a translator process and caches the output for rapid responses.
Example snippet from the server code (server.js):
const express = require('express'); const { spawn } = require('child_process'); const { JSDOM } = require('jsdom'); const cors = require('cors'); const path = require('path'); const fs = require('fs-extra'); const axios = require('axios'); const app = express(); app.use(express.json()); app.use(cors({ origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] })); const BASE_LANGUAGE_NAMES = { 'en': 'English', 'nl': 'Dutch', 'fr': 'French', 'de': 'German', 'es': 'Spanish', 'it': 'Italian', 'pt': 'Portuguese', 'ru': 'Russian', 'zh-CN': 'Chinese (Simplified)', 'ja': 'Japanese' }; const LANGUAGES_TO_TRANSLATE = ['nl', 'fr', 'de', 'es', 'it', 'pt', 'ru', 'zh-CN', 'ja']; app.get('/api/available-languages', (req, res) => { const availableLanguages = ['en', ...LANGUAGES_TO_TRANSLATE].sort(); res.json({ languages: availableLanguages }); }); app.get('/api/get-translations', async (req, res) => { const { lang, version: requestedVersion } = req.query; if (!lang || !requestedVersion) { return res.status(400).json({ error: "Missing 'lang' or 'version' query parameter." }); } if (lang === 'en') { } try { return res.json({ translations: /* translated page texts */, languageNames: /* translated language names */, version: /* meta version */, source: 'generated_on_demand' }); } catch (error) { console.error(`Translation retrieval error: ${error.message}`); return res.status(500).json({ error: "Internal server error during translation retrieval." }); } }); const PORT = process.env.PORT || 10000; app.listen(PORT, async () => { console.log(`Server running on port ${PORT}`); await prebuildTranslations(); setInterval(prebuildTranslations, 60000); });
In this code, the server fetches HTML sources from multiple fallback URLs, extracts translatable texts, and translates them using a child process that runs a custom translator. Translations are also stored on the server so future requests can be answered faster.
Every comment in the snippet represents a "translate idea" and is marked with a unique data-translate-key. These keys ensure that the texts in the interface can later be easily translated via the same API.
The overall architecture employs a two-layer strategy:
1. Client-side: Uses local storage caching to display immediate translations and asynchronously updates the cache with live data from multiple fallback servers.
2. Server-side: Provides translation services by extracting texts directly from HTML sources, translating them through a dedicated process, and caching them for subsequent requests.
This approach ensures both fast page loads and up-to-date translations while maintaining a robust fallback mechanism in case of temporary connectivity issues.