Language Selector

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.

Server Side Translation API

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.

Overall Architecture Explanation

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.