Welcome to the utities.online technical tutorial series! Today, we’ll dissect the multilingual prerendering implementation of our open-source video-splitter tool—a browser-based utility for splitting videos into custom segments. This guide will walk you through the architecture, workflow, and optimizations that enable seamless multilingual support for global users.
Multilingual prerendering is a technique that generates static HTML files for each language at build time, combining the benefits of Server-Side Rendering (SSR) and Static Site Generation (SSG):
This approach is ideal for performance-critical, multilingual web apps like video-splitter, where speed and accessibility matter.
Technology | Purpose | Why It Was Chosen |
---|---|---|
React 18 | Frontend UI | Component-based architecture with built-in SSR support via ReactDOMServer . |
i18next + react-i18next | Internationalization (i18n) | Robust, flexible, and supports advanced features like namespaces and interpolation. |
Vite | Build Tool | Blazing-fast builds and HMR, with native ESM support. |
Node.js | Runtime Environment | Powers prerendering scripts and server-side logic. |
ReactDOMServer | Server-Side Rendering | Official React SSR solution for generating HTML on the server. |
The video-splitter project follows a modular structure for clarity and maintainability:
├── src/
│ ├── i18n/
│ │ ├── index.ts # i18n configuration and initialization
│ │ └── locales/ # Translation files (e.g., `en/`, `zh/`, `es/`)
│ ├── entry-server.tsx # SSR entry point with the `render` function
│ └── constants.ts # Global constants (e.g., `DOMAIN_NAME`)
├── prerender.js # Prerendering script for static HTML generation
└── package.json # Build and prerendering scripts
Key design principles:
/i18n
.The prerender.js
script sets up the environment and prepares to generate static pages for each language:
javascriptimport fs from 'node:fs'; import path from 'node:path'; import url from 'node:url'; const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); const templateHtml = fs.readFileSync('./dist/static/index.html', 'utf-8');
Instead of hardcoding languages, the script scans the locales/
directory:
javascriptconst localesDir = path.resolve(__dirname, 'src/i18n/locales'); const languages = fs.readdirSync(localesDir).filter(name => fs.statSync(path.join(localesDir, name)).isDirectory() );
Routes are generated dynamically:
/
/{lang}/
(e.g., /zh/
, /es/
)javascriptconst routes = ['/'].concat( languages.filter(lang => lang !== 'en').map(lang => `/${lang}`) );
The render
function in entry-server.tsx
handles SSR for each language:
javascriptexport async function render(url: string, languages: string[]) { const seoContent = await getSeoContent(url); const i18nInstance = await initI18n(seoContent.language); i18nInstance.changeLanguage(seoContent.language); const html = renderToString(); // Generate hreflang tags and other SEO metadata... }
For each route, the script:
dist/static/{lang}/index.html
.javascriptfor (const route of routes) { const lang = route === '/' ? 'en' : route.split('/')[1]; const rendered = await render(route, languages); const html = template .replace(``, rendered.head) .replace(``, rendered.html) .replace(/]*)>/, ``); fs.writeFileSync(`dist/static${route}/index.html`, html); }
The src/i18n/index.ts
file centralizes i18n logic:
javascriptexport const languages = [ 'en', 'zh', 'es', 'fr', 'de', 'ja', 'ko', /* ... 28 total */ ]; export const initI18n = async (language?: string) => { try { const translations = await preloadCommonTranslations(); return await sharedInitI18n(translations, language); } catch (error) { return await sharedInitI18n({}); // Fallback to empty config } };
Key Features:
Translations are organized in JSON files with namespaces:
json{ "videoSplitter": { "title": "VideoSplitter - Fast Video Splitting Tool", "uploadVideo": "Upload Video", "dragAndDrop": "Drag and drop your video file here" }, "common": { "processing": "Processing", "success": "Success", "error": "Error" } }
Advantages:
videoSplitter
) and shared (common
) translations.The package.json
scripts automate the process:
json"scripts": { "build": "vite build", "build:server": "vite build --config vite.config.server.ts", "generate": "pnpm run build && pnpm run build:server && node prerender" }
Steps:
prerender.js
to generate static HTML for all languages.Dynamic Language Detection
locales/
to auto-discover supported languages.Automated Route Generation
/{lang}/
directories and HTML files automatically.SEO Optimization
lang
attributes and hreflang
tags for search engines.Robust Fallback Mechanism
Performance Optimizations
javascript// Set a globaltag to resolve asset paths const head = renderToString( );
The video-splitter project demonstrates how to combine React SSR, i18next, and Vite to create a scalable, multilingual static site. This approach balances performance, SEO, and maintainability—ideal for global tools.
Explore more:
Question for you: Are you implementing multilingual support in your projects? What challenges have you faced, and how did you solve them? Let’s discuss!