Building a truly global platform requires more than just translating words; it demands a robust internationalization (i18n) strategy deeply woven into the fabric of our application. At MisuJob, where we aggregate from multiple sources and processes 1M+ job listings across Europe, serving content in multiple languages isn’t just a nice-to-have, it’s a necessity for connecting talent with opportunity.
The Challenge: Scaling Internationalization in a Static Site
We chose a static site generator (SSG) for MisuJob’s marketing website and blog due to its performance, security, and ease of deployment. However, scaling i18n across 10+ languages presented unique challenges. Unlike dynamic server-side rendering, static sites require all content to be pre-built. This means generating separate HTML files for each language, multiplying the build time and complexity.
Our initial approach involved a simple directory structure: /en/, /de/, /fr/, etc., each containing language-specific content. While straightforward, this quickly became unwieldy as the site grew. Maintaining consistency across languages, managing translations, and ensuring proper SEO localization became a significant burden.
We needed a more scalable and maintainable solution. Our core requirements were:
- SEO-friendliness: Language-specific URLs (e.g.,
/de/gehaltsvergleich/) are crucial for search engine rankings. - Maintainability: Easy to update translations and add new languages without massive code changes.
- Performance: Fast build times and optimal website performance for all users.
- Scalability: Able to handle a growing number of languages and content without significant performance degradation.
Our Solution: A Multi-pronged Approach
We adopted a multi-pronged approach leveraging a combination of tools and techniques to achieve our i18n goals. This included:
- Centralized Translation Management: Using a dedicated translation management system (TMS).
- Static Site Generator Integration: Deep integration with our SSG (Next.js).
- Localized Routing: Dynamic routing based on user locale and language preferences.
- Content Delivery Network (CDN): Distributing localized content globally.
1. Centralized Translation Management
Manually managing translation files (e.g., .json, .po) quickly becomes a nightmare. We opted for a TMS to streamline the translation workflow. This provided several benefits:
- Collaboration: Multiple translators can work simultaneously on different languages.
- Version Control: Tracks changes and ensures consistency across translations.
- Contextualization: Translators can see the context of each string, improving accuracy.
- Automation: Automates the process of importing, exporting, and managing translation files.
We use a TMS that integrates seamlessly with our CI/CD pipeline. When new content is added or updated, the TMS automatically detects the changes and notifies translators. Once translations are complete, they are automatically pushed to our repository.
2. Static Site Generator Integration (Next.js)
Next.js’s built-in i18n support provided a solid foundation. We configured it using the next.config.js file:
// next.config.js
module.exports = {
i18n: {
locales: ['en', 'de', 'fr', 'nl', 'es', 'it', 'pl', 'pt', 'da', 'fi', 'sv'],
defaultLocale: 'en',
localeDetection: false, // Disables automatic locale detection
},
// ...other configurations
};
This configuration tells Next.js which locales we support and sets the default locale to English. We disabled automatic locale detection to provide more control over the user’s language preference, allowing us to persist preferences across sessions via cookies.
We then created a custom useTranslation hook to access translated strings within our components:
// hooks/useTranslation.js
import { useRouter } from 'next/router';
import en from '../locales/en.json';
import de from '../locales/de.json';
import fr from '../locales/fr.json';
import nl from '../locales/nl.json';
import es from '../locales/es.json';
import it from '../locales/it.json';
import pl from '../locales/pl.json';
import pt from '../locales/pt.json';
import da from '../locales/da.json';
import fi from '../locales/fi.json';
import sv from '../locales/sv.json';
const translations = {
en,
de,
fr,
nl,
es,
it,
pl,
pt,
da,
fi,
sv,
};
const useTranslation = () => {
const { locale } = useRouter();
const t = (key) => {
if (!locale || !translations[locale] || !translations[locale][key]) {
return key; // Fallback to the key if translation is missing
}
return translations[locale][key];
};
return { t, locale };
};
export default useTranslation;
This hook allows us to easily access translated strings in our components:
// components/MyComponent.js
import useTranslation from '../hooks/useTranslation';
const MyComponent = () => {
const { t } = useTranslation();
return (
<h1>{t('welcome_message')}</h1>
);
};
export default MyComponent;
Each language has a corresponding JSON file (e.g., locales/en.json, locales/de.json) containing the translated strings.
3. Localized Routing
Maintaining SEO-friendly URLs for each language was crucial. Next.js automatically prefixes URLs with the locale. For example, the English version of a page might be /en/blog/my-article, while the German version would be /de/blog/my-article.
However, we needed to ensure that users were automatically redirected to their preferred language based on their browser settings or previous selections. We implemented a middleware function that intercepts requests and redirects users to the appropriate locale if necessary.
// middleware.js
import { NextResponse } from 'next/server'
import acceptLanguage from 'accept-language'
import { fallbackLng, languages } from './i18n/settings'
acceptLanguage.languages(languages)
export const config = {
matcher: ['/((?!api|_next/static|_next/image|assets|favicon.ico).*)']
}
export function middleware(req) {
let lng
if (req.cookies.has('NEXT_LOCALE')) lng = acceptLanguage.fromString(req.cookies.get('NEXT_LOCALE').value)
if (!lng) lng = acceptLanguage.fromString(req.headers.get('accept-language'))
if (!lng) lng = fallbackLng
// Redirect if lng in path is not supported
if (
!languages.some((loc) => req.nextUrl.pathname.startsWith(`/${loc}/`)) &&
!req.nextUrl.pathname.startsWith('/_next')
) {
return NextResponse.redirect(new URL(`/${lng}${req.nextUrl.pathname}`, req.url))
}
if (req.headers.has('referer')) {
const refererUrl = new URL(req.headers.get('referer'))
const lngInReferer = languages.find((l) => refererUrl.pathname.startsWith(`/${l}`))
const response = NextResponse.next()
if (lngInReferer) return response.cookies.set('NEXT_LOCALE', lngInReferer)
return response
}
return NextResponse.next()
}
This middleware checks for a language cookie (NEXT_LOCALE). If it exists, it uses that language. Otherwise, it uses the accept-language header from the browser. If neither is available, it defaults to our fallback language (English). It then redirects the user to the appropriate URL if necessary.
4. Content Delivery Network (CDN)
Serving static content from a CDN is essential for performance, especially for a global audience. We use a CDN that automatically caches and distributes our static assets across multiple edge locations. This ensures that users around the world can access our content quickly and reliably.
We configured our CDN to automatically invalidate the cache whenever we deploy new content. This ensures that users always see the latest version of our website.
Performance and SEO Considerations
Internationalization can significantly impact website performance and SEO. We took several steps to mitigate these risks:
- Code Splitting: We use code splitting to load only the necessary code for each language. This reduces the initial page load time and improves overall performance.
- Image Optimization: We optimize images for each language by using appropriate file formats and compression levels.
- hreflang Tags: We added
hreflangtags to our HTML to tell search engines which language each page is in. This helps search engines serve the correct version of the page to users based on their language preferences.
Here’s an example of how we implement hreflang tags within the <head> section of our pages:
<link rel="alternate" href="https://www.misujob.com/en/blog/my-article" hreflang="en" />
<link rel="alternate" href="https://www.misujob.com/de/blog/my-article" hreflang="de" />
<link rel="alternate" href="https://www.misujob.com/fr/blog/my-article" hreflang="fr" />
<link rel="alternate" href="https://www.misujob.com/nl/blog/my-article" hreflang="nl" />
<!-- ... more language links -->
<link rel="alternate" href="https://www.misujob.com/blog/my-article" hreflang="x-default" />
The x-default tag is used for users whose language preferences don’t match any of our supported languages. They will be served the English version by default.
The Impact: Connecting Talent Across Borders
Our i18n strategy has significantly improved our ability to connect talent with opportunity across Europe. By serving content in multiple languages, we’ve seen a significant increase in user engagement and conversions. Users are more likely to engage with content that is presented in their native language.
We’ve also seen a positive impact on our SEO. By using language-specific URLs and hreflang tags, we’ve improved our search engine rankings in multiple countries. This has resulted in more organic traffic and more qualified job seekers finding our platform.
For example, consider the search volume for “data scientist jobs” in different European languages:
| Language | Search Term | Estimated Monthly Search Volume |
|---|---|---|
| English | data scientist jobs | 12,000 |
| German | data scientist jobs | 4,000 |
| German | data scientist jobs deutschland | 2,000 |
| French | offres data scientist | 3,000 |
| Dutch | data scientist vacatures | 2,500 |
| Spanish | trabajos data scientist | 1,800 |
By targeting these language-specific keywords, we can reach a wider audience of job seekers.
Furthermore, providing localized salary insights is crucial for professionals. Here’s a comparison of average Data Scientist salaries across several European countries based on our internal data and publicly available information:
| Country | Average Annual Salary (EUR) |
|---|---|
| Germany | 75,000 - 95,000 |
| Switzerland | 100,000 - 130,000 |
| UK | 65,000 - 85,000 |
| Netherlands | 60,000 - 80,000 |
| France | 55,000 - 75,000 |
| Spain | 45,000 - 65,000 |
Presenting this data in the user’s preferred language allows them to make more informed career decisions.
Conclusion
Internationalization is a complex but essential process for any platform that wants to reach a global audience. By adopting a multi-pronged approach and leveraging the right tools, we were able to successfully scale i18n across our static site and serve content in 10+ languages.
The key takeaways from our experience are:
- Centralized translation management is crucial for maintainability and collaboration.
- Static site generators like Next.js provide excellent i18n support.
- Localized routing and middleware are essential for SEO and user experience.
- CDNs are critical for performance and scalability.
- Performance and SEO considerations should be addressed early in the development process.
By implementing these strategies, we’ve created a truly global platform that connects talent with opportunity across Europe, powered by AI-powered job matching and a commitment to providing localized experiences. This allows us to effectively serve our users and further our mission of connecting professionals with their dream jobs.

