Website Internationalization (i18n) Guide: Building Multilingual Sites
Internationalization (i18n) is the technical approach for adapting websites to different languages and regions. 75% of consumers prefer buying in their native language.
Content Types to Localize
| Type | Example | Notes |
|---|---|---|
| UI Text | Buttons, menus | Basic translation |
| Dates | 2026/07/18 vs July 18, 2026 | Format differences |
| Currency | $1,000.50 vs 1.000,50€ | Symbol and format |
| Numbers | 1,234.56 vs 1.234,56 | Decimal/thousand separators |
| Images | Text in images | Prepare per language |
Implementation Options
| Solution | Framework | Features |
|---|---|---|
| react-i18next | React | Most features, SSR support |
| Vue I18n | Vue | Official recommendation |
| nuxt/i18n | Nuxt | Auto routing and SEO |
| next-i18next | Next.js | SSR and SSG integration |
react-i18next Example
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
const resources = {
'zh-CN': { translation: { welcome: 'Welcome to our site' } },
'en-US': { translation: { welcome: 'Welcome to our website' } }
};
i18n.use(initReactI18next).init({ resources, lng: 'zh-CN', fallbackLng: 'en-US' });
Organizing translation files
A common mistake is dumping every string into one giant JSON file. A more maintainable approach is splitting by page or module:
// locales/en/checkout.json
{
"title": "Checkout",
"subtotal": "Subtotal: {{amount}}",
"freeShipping": "Free shipping over {{threshold}}",
"items": {
"one": "{{count}} item",
"other": "{{count}} items"
}
}
A naming convention such as checkout.subtotal keeps the context obvious, and paired with a platform like Crowdin or Lokalise, non-engineers can review translations too. Prefer a "module + semantic name" two-level scheme over numbered keys like key1 or key2 — the latter are nearly impossible to maintain during refactors and give translators no context at all.
URL Strategies
- Sub-path: example.com/zh-cn/page (best SEO, easy)
- Subdomain: zh.example.com/page (independent, complex TLS)
- Domain: example.cn/page (strongest localization, costly)
SEO
<link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/page" />
<link rel="alternate" hreflang="en-US" href="https://example.com/en-us/page" />
Language Switching and UX
Switching languages is not just swapping strings; it involves routing, state, and overall experience:
- After a user switches language, remember the choice in
localStorageor a cookie so the next visit opens in that language; - Default to the browser's
Accept-Languageheader, but always keep a manual switch available; - Store language preference on the server for signed-in users so it syncs across devices;
- Avoid a full-page flash on switch — render immediately with already-loaded translations, then fetch new language resources asynchronously.
Common Pitfalls
- Will German or French break the layout? German typically runs about 30% longer than English, while Chinese is shorter but denser. Leave slack in buttons and cards, and back up long strings with
max-widthand wrapping. - Whose date format do you use? Follow the target region, not the translation language: within the same English interface, US users see
07/18/2026while European users see18/07/2026. That is a locale difference, not a language one. - How do I fill in hreflang? Write language codes per BCP 47 (
zh-CN,en-US); if you localize by language only, usex-defaultas the fallback. - What about text inside images? Prefer CSS or SVG so you do not re-render an image per language; when a bitmap is unavoidable, serve different assets per language through
srcset.
Reference: MDN on internationalization and localization https://developer.mozilla.org/en-US/docs/Glossary/Internationalization_and_localization
Reference: react-i18next official docs https://react.i18next.com/
Summary
Plan i18n from project start. Choose mature libraries like react-i18next or Vue I18n. Consider translation management platforms (Lokalise, Crowdin) rather than hardcoding.