QuiiBz / next-international

Type-safe internationalization (i18n) for Next.js
https://next-international.vercel.app
MIT License
1.25k stars 59 forks source link

[Suggestion][Feature] Internationalisation based on sub-domains #241

Closed gustaveWPM closed 10 months ago

gustaveWPM commented 10 months ago

First of all: it's a suggestion that would involve writing some pretty hacky stuff, and nothing about it sounds simple (if we want to have a perfect UX, i.e without any full reload).


(EDIT: At the time I wrote these lines, I was too perfectionistic and I thought of scenarios that may not really correspond to the real world, in which, yes, the UX would be bad, but for scenarios that don't practically exist. Check the comments under this issue for more details.)


This idea came to me after reading a comment by @radumkc :arrow_right: https://github.com/QuiiBz/next-international/issues/188#issuecomment-1737185904

Even if I don't think this internationalisation strategy is a good one, the reality of the web is that it's being adopted by a lot of people. ( Here are the details of the reasons why I don't like this standard: https://github.com/QuiiBz/next-international/issues/188#issuecomment-1759678467 )


Upon skimming through this article, I realized that implementing this solution involved adding unsafe/undecidable features to the middleware: https://medium.com/@jfbaraky/using-subdomains-as-paths-on-next-js-e5aab5c28c28

However, I believe that this solution presented in this Medium post is insufficient/inadequate.

I'm afraid that redirecting to such a "Deep" change in the URL will break the laziness of the virtual DOM and force a complete reload of the page. (I haven't really seen any native support in Next.js for managing sub-domains, probably for this reason.)

In the case of a redirect to an entirely statically generated page, this shouldn't be too frustrating. But in the opposite case, i.e. a redirect to a fully rendered client-side page, I'm afraid that it would be a bit disappointing.

For example, in the case of a Dashboard protected by authentication and fully loaded on the client side, this would mean that a change of language would mean reloading the page, and therefore restarting all the fetches required to display the data within this Dashboard.

I think this would mean introducing a notion of "Hybrid" strategy, where we would change the language based on sub-folders for some cases, and on the sub-domains for others. Admittedly, it's confusing, but it would avoid the problem of full reload mentioned above.

Some pages would therefore have to be routed as follows: mywebsite.com/en/dashboard; mywebsite.com/en/dashboard; etc. And some others should be routed as fr.mywebsite.com/foo/bar; en.mywebsite.com/foo/bar; etc.

:information_source: (I won't go into the case of Multi-TLD/different domain names ending with .fr; .com; .es; etc here, but I think the "trick" would be more or less the same. However, this would imply undecidable cases like: "mywebsite.fr/en/dashboard"? Doesn't make any sense. In practice, such a strategy involves a large budget, as it means buying all the domain names with the desired ending. I think this is one of the least common cases?)

I don't know if what I'm saying is perfectly fair here, it's just a rough draft to open the discussion. From my point of view, it would be interesting to ask Next if they're already working on this problem to avoid writing "Hybrid" and hacky support so that it's not necessary in the end.

I feel a bit uncomfortable making such a suggestion, given that it really does involve puzzles that seem pretty infernal and horrible to test and maintain. But I still think it's a good thing to start talking about it.

:warning: From my perspective, the wise choice to do would be to wait for native subdomains and multi-TLD support on the Next.js side.

QuiiBz commented 10 months ago

I definitely don't want to support subdomains instead of (or along with) URL segment to determine the locale. It would introduce another complex layer of logic, for a behaviour that doesn't seem to be very needed: I don't think we've had anyone requesting this feature in the past.

If anyone is interested in this feature, please add a ๐Ÿ‘ to the first comment.

gustaveWPM commented 10 months ago

I don't think we've had anyone requesting this feature in the past.

Indeed: we didn't. Except in the comment from radumkc I mentioned, who talked about it in a rather implicit and anecdotal way. (Unless I have misread him.)

Since the details involving such implementation are not as easy to resolve than just a Jigsaw puzzle, because there is currently no Next.js standard, I preferred to think about it on my own and draft an issue that tries to outline it exhaustively with my rationale.

I definitely don't want to support subdomains instead of (or along with) URL segment to determine the locale.

I argued more about the cons than about the pros in my initial message, implicitly leading to this outcome on my side too. I'm not sure if that's also what led you to this conclusion, in which case I'm truly sorry, and don't forget that I might be wrong. (But I'm afraid that I'm right, so...)

kristofferso commented 10 months ago

I'm not sure I understood all your concerns @gustaveWPM, but I would be interested in this feature. In my case I have two lanuages and two separate domains (domain.no and domain.se for example). I then deploy the site to Vercel, configure the two domains and store them in two environment variables. My middleware.ts looks like this:

import { createI18nMiddleware } from 'next-international/middleware';
import { NextRequest } from 'next/server';

const HOST_NO = process.env['NEXT_PUBLIC_HOST_NO'];
const HOST_SE = process.env['NEXT_PUBLIC_HOST_SE'];

const I18nMiddleware = createI18nMiddleware({
  locales: ['no', 'se'],
  defaultLocale: 'no',
  urlMappingStrategy: 'rewrite',
  resolveLocaleFromRequest: (req) => {
    const host = req.headers.get('host');

    if (HOST_NO && host?.includes(HOST_NO)) {
      return 'no';
    }
    if (HOST_SE && host?.includes(HOST_SE)) {
      return 'se';
    }
    return 'no';
  },
});

export function middleware(request: NextRequest) {
  const nextLocaleCookie = request.cookies.get('Next-Locale');
  const host = request.headers.get('host');

  // Remove Next-Locale cookie if it's not matching the current host
  if (nextLocaleCookie && host && !host.includes(nextLocaleCookie.value)) {
    request.cookies.delete('Next-Locale');
  }

  return I18nMiddleware(request);
}

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)'],
};

This way I get two translated sites deployed on their own separate domain. The users can navigate between them but rarely will unless the happen to arrive at the wrong domain, so I can't see the concern with reloading all data over again would be a frequent case. Or am I misunderstanding something?

gustaveWPM commented 10 months ago

I'm not sure I understood all your concerns @gustaveWPM, but I would be interested in this feature. In my case I have two lanuages and two separate domains (domain.no and domain.se for example). I then deploy the site to Vercel, configure the two domains and store them in two environment variables. My middleware.ts looks like this:

import { createI18nMiddleware } from 'next-international/middleware';
import { NextRequest } from 'next/server';

const HOST_NO = process.env['NEXT_PUBLIC_HOST_NO'];
const HOST_SE = process.env['NEXT_PUBLIC_HOST_SE'];

const I18nMiddleware = createI18nMiddleware({
  locales: ['no', 'se'],
  defaultLocale: 'no',
  urlMappingStrategy: 'rewrite',
  resolveLocaleFromRequest: (req) => {
    const host = req.headers.get('host');

    if (HOST_NO && host?.includes(HOST_NO)) {
      return 'no';
    }
    if (HOST_SE && host?.includes(HOST_SE)) {
      return 'se';
    }
    return 'no';
  },
});

export function middleware(request: NextRequest) {
  const nextLocaleCookie = request.cookies.get('Next-Locale');
  const host = request.headers.get('host');

  // Remove Next-Locale cookie if it's not matching the current host
  if (nextLocaleCookie && host && !host.includes(nextLocaleCookie.value)) {
    request.cookies.delete('Next-Locale');
  }

  return I18nMiddleware(request);
}

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)'],
};

This way I get two translated sites deployed on their own separate domain. The users can navigate between them but rarely will unless the happen to arrive at the wrong domain, so I can't see the concern with reloading all data over again would be a frequent case. Or am I misunderstanding something?

I think transitioning from one domain to another one might not impact the UX too much in the current case because the pages are probably statically generated (SSG)?

But I think that in a scenario where the page is rendered on the client side, with several fetches that might take some time, changing the language would trigger all these fetches again.

I'm afraid that changing the URL so "Deeply" might break the React's Virtual DOM laziness: so everything would be reloaded, remounted, and consequently, fetched again. In terms of UX, this doesn't seem optimal to me for this scenario. :/

kristofferso commented 10 months ago

I see. Thank you for clarifying that. So in a site where users very rarely change the language back and forth (is that really a normal use case, except for us developers doing testing? ๐Ÿ˜„), do you see any harm in implementing it this way?

Most of my pages will be statically generated, with a client component here and there.

gustaveWPM commented 10 months ago

I see. Thank you for clarifying that. So in a site where users very rarely change the language back and forth (is that really a normal use case, except for us developers doing testing? ๐Ÿ˜„), do you see any harm in implementing it this way?

Most of my pages will be statically generated, with a client component here and there.

I guess it is more a question for @QuiiBz ? Perhaps it might be possible to offer limited and early support, without going all the way to create something completely perfect in terms of UX?

I struggle a bit with anything that isn't "Perfect", but maybe we could set aside all my perfectionist nuances for now. Idk.

Btw:

is that really a normal use case, except for us developers doing testing?

It could be considered as a "normal use case", but only if the website includes a language switcher component. But even in this scenario, in practice, I admit it would be rather unusual for a user to want to change the language of a website very often. So, perhaps some occasional full reloads wouldn't be so stressful in practice, while waiting for Next to possibly provide a slicker support of multi-TLD and subdomains.

Thank you for helping me clarify the reality of the use cases and needs related to this issue. In my opinion, inducing full reloads seemed really bothersome to me, but I think we can put it into perspective (and in any case, trying to avoid them would be too chaotic, so perhaps it would be much smarter to focus on user needs rather than the desire to produce something completely perfect in every circumstance).

The more I think about it, the more I find your opinion interesting (and realistic). Thank you a lot!

QuiiBz commented 10 months ago

So in a site where users very rarely change the language back and forth (is that really a normal use case, except for us developers doing testing? ๐Ÿ˜„), do you see any harm in implementing it this way?

"Normal" users don't often (if not never because we immediately resolve their preferred language) change their language, so that's probably not a big UX issue to refresh the entire page (because the domain name is updated).

I'm not personally seing a lot of websites using a domain/subdomain per locale nowadays, but almost always in the URL / in a cookie, so that's why I'm categorising this feature as niche.

QuiiBz commented 10 months ago

Closing as this seems a bit out-of-scope in the current state of next-international - it can "easily" be implemented manually when required. Thanks for the proposition though!