Content Localization with IP Geolocation: Auto-Detect Language & Region
Serving content in the right language and format for each visitor is one of the highest-impact things you can do for user experience. IP geolocation gives you a reliable starting point for locale detection โ before the user even interacts with your site.
Why IP-Based Locale Detection?
Browser Accept-Language headers are useful but not always reliable. A user in Japan might have their browser set to English. IP geolocation adds a second signal โ their physical location โ which helps you make smarter defaults:
- Show content in the local language
- Display dates, numbers, and currencies in regional formats
- Surface region-specific promotions or legal notices
- Pre-select the correct country in forms
Country-to-Locale Mapping
const countryLocaleMap: Record<string, string> = {
US: "en-US",
GB: "en-GB",
DE: "de-DE",
FR: "fr-FR",
ES: "es-ES",
JP: "ja-JP",
KR: "ko-KR",
BR: "pt-BR",
CN: "zh-CN",
IN: "hi-IN",
SA: "ar-SA",
IT: "it-IT",
NL: "nl-NL",
RU: "ru-RU",
TR: "tr-TR",
};
function getLocale(countryCode: string): string {
return countryLocaleMap[countryCode] || "en-US";
}Detect Locale from IP
async function detectVisitorLocale(): Promise<string> {
const res = await fetch("https://geo.kamero.ai/api/geo");
const { country } = await res.json();
return getLocale(country);
}
// Usage
const locale = await detectVisitorLocale();
// "ja-JP" for a visitor in JapanCombine with Accept-Language Header
function getBestLocale(
acceptLanguage: string | null,
geoCountry: string
): string {
// Priority: explicit browser preference > geo
if (acceptLanguage) {
const preferred = acceptLanguage.split(",")[0]
.split(";")[0].trim();
// Check if it's a full locale (e.g., "fr-FR")
if (preferred.includes("-")) return preferred;
}
// Fall back to geo-based locale
return getLocale(geoCountry);
}This gives you the best of both worlds: respect explicit user preferences, but use geolocation as a smart fallback.
Next.js Middleware for Auto-Locale
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
const SUPPORTED_LOCALES = ["en", "de", "fr", "es", "ja"];
export function middleware(request: NextRequest) {
const country = request.geo?.country || "US";
const countryToLang: Record<string, string> = {
DE: "de", FR: "fr", ES: "es",
MX: "es", JP: "ja", AT: "de", CH: "de",
};
const detectedLang = countryToLang[country] || "en";
const pathname = request.nextUrl.pathname;
// Skip if already has locale prefix
const hasLocale = SUPPORTED_LOCALES.some(
(l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`
);
if (hasLocale) return NextResponse.next();
// Redirect to localized path
const url = request.nextUrl.clone();
url.pathname = `/${detectedLang}${pathname}`;
return NextResponse.redirect(url);
}Format Dates and Numbers by Region
function formatForLocale(locale: string) {
const now = new Date();
return {
date: new Intl.DateTimeFormat(locale, {
dateStyle: "long",
}).format(now),
number: new Intl.NumberFormat(locale).format(1234567.89),
currency: new Intl.NumberFormat(locale, {
style: "currency",
currency: getCurrencyForLocale(locale),
}).format(99.99),
};
}
// "en-US" โ { date: "February 9, 2026",
// number: "1,234,567.89",
// currency: "$99.99" }
// "de-DE" โ { date: "9. Februar 2026",
// number: "1.234.567,89",
// currency: "99,99 โฌ" }React Component with Geo-Locale
"use client";
import { useState, useEffect } from "react";
export function LocalizedContent() {
const [locale, setLocale] = useState("en-US");
useEffect(() => {
fetch("https://geo.kamero.ai/api/geo")
.then(r => r.json())
.then(({ country }) => setLocale(getLocale(country)))
.catch(() => {}); // Keep default
}, []);
const messages: Record<string, { welcome: string }> = {
"en-US": { welcome: "Welcome" },
"es-ES": { welcome: "Bienvenido" },
"fr-FR": { welcome: "Bienvenue" },
"de-DE": { welcome: "Willkommen" },
"ja-JP": { welcome: "ใใใใ" },
};
const msg = messages[locale] || messages["en-US"];
return (
<div lang={locale}>
<h1>{msg.welcome}</h1>
<p>{new Date().toLocaleDateString(locale)}</p>
</div>
);
}Best Practices
- Always let users override the detected locale with a language switcher
- Store the user's preference in a cookie or localStorage
- Use geo as a default, not a lock โ some users travel or use VPNs
- Combine
Accept-Language+ geo for the most accurate detection - Cache the geo lookup to avoid repeated API calls on navigation
- Test with users from different regions to validate your mappings
Get Started
Detect your visitors' country instantly with a free API โ no key required.
View Documentation โ