As i18next-browser-languageDetector supports subdomain detection out of the box, it'd be nice to also support it here.
We would have two mutually exclusive options regarding routing:
localeSubpaths (already exists)localeSubdomains (does not exist)Or perhaps we'd have a single localeRouting option that would take an enum of strings, eg subpaths or subdomains.
We'd need to add logic to lng-path-corrector and lng-path-detector to handle subdomains instead of subpaths.
We'd also need to update the Link component.
I don't yet know how feasible this proposal is, as it's not possible to have a SPA across subdomains anyways. Development and testing will also be a pain.
There are two language detectors I used to use before trying out next-i18next, both of them are not working at the moment. See https://gitlab.com/kachkaev/website-frontend/blob/master/src/server.ts#L11-32
My language detectors live in server.ts – and there is no browser-dependent logic by design. The enforcedLocale detector is helpful in development (ENFORCED_LOCALE=xx yarn dev) and languageByDomain picks the right language based on the domain.
I moved the language detectors to i18n.ts like this:
import * as ICU from "i18next-icu";
import en from "i18next-icu/locale-data/en";
import ru from "i18next-icu/locale-data/ru";
import NextI18Next from "next-i18next";
const use: any[] = [];
const icu = new ICU();
icu.addLocaleData(ru);
icu.addLocaleData(en);
use.push(icu);
if (typeof window === "undefined") {
const { env } = require("./config");
const i18nextMiddleware = require("i18next-express-middleware");
const languageDetector = new i18nextMiddleware.LanguageDetector(null, {
order: ["enforcedLocale", "languageByDomain"],
});
languageDetector.addDetector({
name: "enforcedLocale",
lookup: () => env.ENFORCED_LOCALE,
cacheUserLanguage: () => {
/**/
},
});
languageDetector.addDetector({
name: "languageByDomain",
lookup: (opts) => {
const hostWithoutPort = (opts.headers.host || "").replace(/\:\d+$/, "");
return hostWithoutPort === env.HOST_RU ? "ru" : "en";
},
cacheUserLanguage: () => {
/**/
},
});
use.push(languageDetector);
}
const nextI18Next = new NextI18Next({
defaultLanguage: "en",
otherLanguages: ["ru"],
localePath: "./locales",
keySeparator: "###",
use,
});
export default nextI18Next;
However, there is no effect yet. What can be missing?
I didn't debug it but might worth try this:
const nextI18Next = new NextI18Next({
defaultLanguage: "en",
otherLanguages: ["ru"],
localePath: "./locales",
keySeparator: "###",
use,
detection: {
order: [] // <--- add here the order of your detectors, including any custom one
}
});
Thanks @lucasfeliciano! Il’ll try this tonight or tomorrow morning :)
@kachkaev Plugins in the use array are definitely being called. It's most likely a configuration error. Let me know if you don't resolve it.
This issue is about _subdomain support_, not language detection in general. If you have any other problems, please open a new issue.
@lucasfeliciano it worked, thank you! 🎉
Closing due to lack of activity/requests. Can reopen if people really want, but this probably falls outside the scope of next-i18next as different subdomains breaks SPA architecture anyways.
Indeed, seems out of the scope.
Can you elaborate on breaks SPA architecture anyways?
I didn't get it
When changing languages in the localeSubpaths approach, for example, we can maintain full SPA navigation. That is to say, a router event happens within NextJs and a new chunk/page and namespaces will be loaded, but are no new document requests or hard navigation.
That is not possible over subdomains - you are forced to basically serve two completely different webpages. You might as well have static React apps, or just forcefully set the locale and don't allow changing.
Ah yes, that is correct
But in the end was much simpler than I thought, I just had to make the custom detector work it properly and redirect the user to a new domain/subdomain when changing languages.
Since everything will reload, it will hit the detector and set the correct language.
Could be smoother but our business rules doesn't help us. :(
Yes, there are pre-existing i18next detectors out there, I just don't have any experience with them. I'm glad it ended up being simple in your case.
@lucasfeliciano I am currently working on subdomains implementation.
I can't find a way to apply it during nextjs initialization. How did you did it ? :)
Since the native detection doesn't work for subdomain I don't know how to do it.
Hey @HeadFox, here is my implementation of my detector
languageByDomainDetector.js
const getLanguageFromHostname = require('./getLanguageFromHostname')
module.exports = {
name: 'domain',
lookup(req, res, options) {
let language = 'en'
if (typeof window !== 'undefined' ) {
language = getLanguageFromHostname(window.location.hostname)
} else {
language = getLanguageFromHostname(req.get('host'))
}
return language
},
cacheUserLanguage(req, res, language, options = {}) {
//todo
},
}
The getLanguageFromHostname is a helper that I have a map of my domains which is mapped to a certain language
And when creating the next-i18next instance you pass like this:
const NextI18NextInstance = new NextI18Next({
defaultLanguage: 'en',
fallbackLng: 'en',
otherLanguages: ['nl', 'de', 'fr', 'hu', 'es'],
load: 'languageOnly',
customDetectors: [languageByDomainDetector], // <- Add the domain detector as a custom detector
detection: {
order: ['domain'], // <- Say to i18n to use the domain detector
},
})
Thank you @lucasfeliciano. I used the code you shared to write a blog post and create a full working example: https://dev.to/justincy/locale-specific-domains-for-i18n-in-next-js-with-next-i18next-54hi
Most helpful comment
Hey @HeadFox, here is my implementation of my detector
languageByDomainDetector.js
The
getLanguageFromHostnameis a helper that I have a map of my domains which is mapped to a certain languageAnd when creating the next-i18next instance you pass like this: