Locale Negotiation in Web Apps — Choosing the Right Language
Strategies for determining the best locale for users in web applications, including header-based, URL-based, and cookie-based approaches.
Detailed Explanation
What Is Locale Negotiation?
Locale negotiation is the process of determining which language/locale to display to a user. Modern web apps typically combine multiple signals to make this decision.
Signal Priority (Recommended Order)
- Explicit user preference (language selector, saved in profile/database)
- URL path or subdomain (
/ja/page,ja.example.com) - Cookie or localStorage (remembers previous choice)
- Accept-Language header (browser/OS setting)
- GeoIP lookup (server-side IP geolocation)
- Default fallback (e.g., English)
URL-Based Locale Patterns
| Pattern | Example | Pros | Cons |
|---|---|---|---|
| Path prefix | /ja/about |
SEO-friendly, simple | Requires routing logic |
| Subdomain | ja.example.com |
Clean URLs | DNS/cert complexity |
| TLD | example.jp |
Strong geo signal | Expensive, complex |
| Query param | ?lang=ja |
Easy to implement | Poor for SEO |
Implementation with Next.js
// middleware.ts — locale detection and redirect
import { NextRequest, NextResponse } from "next/server";
const locales = ["en", "ja", "de", "fr"];
const defaultLocale = "en";
function getLocale(request: NextRequest): string {
// 1. Check cookie
const cookieLocale = request.cookies.get("locale")?.value;
if (cookieLocale && locales.includes(cookieLocale)) return cookieLocale;
// 2. Parse Accept-Language
const acceptLang = request.headers.get("accept-language") || "";
const preferred = acceptLang
.split(",")
.map(lang => lang.split(";")[0].trim().substring(0, 2))
.find(code => locales.includes(code));
return preferred || defaultLocale;
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const hasLocale = locales.some(
loc => pathname.startsWith(`/${loc}/`) || pathname === `/${loc}`
);
if (hasLocale) return;
const locale = getLocale(request);
return NextResponse.redirect(
new URL(`/${locale}${pathname}`, request.url)
);
}
Best Practices
- Never auto-redirect based on IP alone — travelers and VPN users get frustrated
- Always provide a language switcher visible on every page
- Remember the user's choice in a cookie or user profile
- Use BCP 47 tags internally, even if you display friendly names to users
- Handle fallbacks gracefully — show content in a related locale rather than a 404
Use Case
Every multilingual web application needs a locale negotiation strategy. Frameworks like Next.js, Nuxt.js, and Django have built-in i18n routing, but the logic for determining the user's preferred locale still requires careful implementation, especially for SEO and user experience.
Try It — Language Code Reference
Related Topics
Accept-Language Header — HTTP Content Negotiation
Web Development
BCP 47 Language Tags — The Web Standard for Locale Identifiers
Standards
Language Codes in SEO (hreflang) — Multilingual SEO Guide
SEO
Language Tags in HTML — The lang Attribute Guide
Web Development
Language Codes in Translation Files — i18n File Organization
Internationalization