loading...

Locale specific domains in Next.js with next-i18next

justincy profile image Justin ・3 min read

Google recommends three different ways for creating locale-specific URLs:

  1. Country-specific domains: example.de
  2. Subdomains: de.example.com
  3. Subdirectories: examples.com/de/

In this post, I'll show how to use country-specific domains and subdomains for language detection in Next.js with next-i18next.

A full working example of what will be shown is available here: https://github.com/justincy/nextjs-domain-locales

By default, next-i18next uses the Accept-Language header (sent by the browser) to determine which language to show initially. This choice is stored in a cookie so that if the user requests a different language, that new preference is persisted.

But when using locale-specific domains and subdomains, we don't care about the Accept-Language header. We just need to know what domain is being used so that we can lookup the associated locale. Thankfully, next-i18next allows for custom language detectors. So let's create one.

First, we need a list of the domains we want to use as well as their associated locales. You can use any domain names you want as long as they get routed to your server and you configure them in the locale map.

const domainLocaleMap = {
  'localhost': 'en',
  'nexttest.com': 'en',
  'nexttest.es': 'es',
  'nexttest.de': 'de',
  'it.nexttest.international': 'it',
  'ua.nexttest.international': 'ua',
};

You'll need to modify your hosts file to get those domains working locally.

Now we need to write the code that uses the domainLocaleMap. For this we'll be writing a custom i18next-http-middleware language detector. We don't need to cache the language (because it's always determined by the URL) so we just need to specify name and lookup. The code will run both in the browser and on the server so we handle both cases.

const domainDetector = {
  // We use the name to refer to it later when we want to tell i18next when to use it.
  name: 'domain',
  lookup(req, res, options) {
    // You need to either set the value of DEFAULT_LOCALE before this or hard-code the value here.
    let locale = DEFAULT_LOCALE;
    // In the browser, get the hostname from window.location.
    if (typeof window !== 'undefined' ) {
      locale = domainLocaleMap[window.location.hostname];
    } 
    // On the server, get the hostname from the request headers.
    // We use the host header which is available on IncomingMessage.
    // https://nodejs.org/api/http.html#http_class_http_incomingmessage
    // But the host header may include the port so first we take that off, if it exists.
    else {
      const hostname = req.headers.host?.split(':')[0];
      locale = domainLocaleMap[hostname];
    }
    return locale;
  }
};

Now all that's left is to register the custom detector.

// https://github.com/isaachinman/next-i18next#options
module.exports = new NextI18Next({
  defaultLanguage: DEFAULT_LOCALE,
  otherLanguages: supportedLangs,
  localePath: path.resolve('./public/static/locales'),
  // Register our custom language detector
  customDetectors: [domainDetector],
  // Tell i18next to _only_ use our custom language detector.
  // https://github.com/i18next/i18next-http-middleware#detector-options
  detection: {
    order: ['domain']
  }
});

That's it! A full working example with Next.js is available here: https://github.com/justincy/nextjs-domain-locales

Hat tip to @lucasfeliciano for providing most of the details about how this can be done.

Discussion

pic
Editor guide
Collapse
fabarea profile image
Fabien Udriot

Thanks I was inspired by your post.