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 () => { // Retrieve available languages from the API availableLanguages = await fetchAvailableLanguages(); // Set default language based on the user's browser language (optional) const deviceLang = navigator.language || navigator.userLanguage; const baseDeviceLang = deviceLang.split('-')[0]; if (availableLanguages.includes(baseDeviceLang) && baseDeviceLang !== currentLang) { currentLang = baseDeviceLang; } // Populate the language selector and add event listener for language changes 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); }); } // Cache translations in the background and apply the current translations 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'] })); // Define base language names and target languages for translation 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']; // Endpoints to return available languages and get translations 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." }); } // For English, extract live texts directly from the HTML source if (lang === 'en') { // ... extract texts and return translations for English } // For other languages, check for stored translations and generate on demand if needed try { // ... translation retrieval and storage logic 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." }); } }); // Start the server and trigger periodic prebuild of translations 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 worden tevens opgeslagen op de server zodat toekomstige aanvragen sneller kunnen worden beantwoord.
Every comment in the snippet represents a "translate idea" and is marked with a unique data-translate-key. Deze sleutels zorgen ervoor dat de teksten in de interface later eenvoudig vertaald kunnen worden via dezelfde 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.