Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A first-class way to internationalize a statically exported Next.js app #5509

Closed
stevenpetryk opened this issue Oct 23, 2018 · 14 comments
Closed

Comments

@stevenpetryk
Copy link

stevenpetryk commented Oct 23, 2018

Feature request

I felt that the best way to frame this discussion was as a feature request, even if I wind up discovering that this capability already exists.

A very common way to structure an internationalized static site is through replication. That is, you have routes like:

  • example.com/:path
  • example.com/fr/:path
  • example.com/de/:path

and these all lead to the same route, but with a different language. With a static site, we can accomplish this using the following site structure:

/
  index.html
  ... the default language

  fr/ 
    index.html
    ... a copy of the whole site, in french
  de/
    index.html
    ... a copy of the whole site, in german
  ...

Is your feature request related to a problem? Please describe.

This is somewhat related to #2072 #4998 #4990, all of which talk about how nice it would be to be able to prefix all routes, all _next references, just everything. In other words, the ability to namespace. A lot of people also desire this so that their zones be "mounted" at different base paths.

This would enable a (somewhat hacky) workflow of:

  1. For each language, export the site, placing it into a folder named /:lang (this is a little bit different from the above structure in that the default language would also be in a folder)
  2. Deploy that whole folder to a static host
  3. Use redirect rules to route non-prefixed routes to the default language folder (/about => /en-US/about(.html))

Describe the solution you'd like

I suppose this may be another use case for a basePath type of feature, but since I18n is so common, it would be fantastic if this were a first-class feature of NextJS. Specifically, it would be incredible if we could only build the server once as we export different translations for a given site.

Similarly, it would be great if commons chunks could be shared between translated version of the site to keep final slug size down.

Describe alternatives you've considered

An alternative, in my case at least, is to just deploy my site to different subdomains for each language—but I am not able to use this approach (at Intercom, we're switching a Rails app over to NextJS and we want to minimize the SEO shock).

Also, it is possible to read the Accept-Language header and route to different version of the site based on that, but this is a bad practice according to Google. There should be different routes for different languages.

@sergiodxa
Copy link
Contributor

I think, since you want this only for static export it could perfectly be a Next.js plugin which could prefix all your pages with the locale based on an array of locales.

Something like:

const withLocalePrefix = require("next-with-locale-prefix");
const config = {};
module.exports = withLocalePrefix(["en", "es", "fr", "pt", "de"])(config);

It could detect if you have custom routes to export and prefix the locales and pass ?locale in the querystring, then in getInitialProps you should detect it to set the correct language to render in your page.

@stevenpetryk
Copy link
Author

stevenpetryk commented Oct 23, 2018

@sergiodxa thanks for getting back to me! I'm relieved that this could be implemented as a plugin.

Could you clarify what you mean be "detect if you have custom routes to export"? Because in this case, I'm looking to prefix all routes with each these language codes (whether custom or not). In general, I'm not sure what a plugin is capable of doing—is there anywhere that can guide me on that (what plugins can modify, and how to write them)? It seems like plugins are basically config transformers. If not, I can certainly look at examples and piece it together :)

@stevenpetryk
Copy link
Author

I understand now how this could be implemented with exportPathMap. I will publish my findings here once I get it sorted out.

@stevenpetryk
Copy link
Author

stevenpetryk commented Oct 23, 2018

Alright! Here's a summary of what I did for future adventurers:

First, in your next config, define your own exportPathMap:

const LANGUAGES = ['de', 'fr', 'es'] // or whatever

module.exports = {
  exportPathMap(defaultPathMap) {
    const pathMap = {};

    Object.entries(defaultPathMap).forEach(([key, value]) => {
      pathMap[key] = value;

      LANGUAGES.forEach(language => {
        pathMap[`/${language}${key}`] = { ...value, query: { language } };
      });
    });

    return pathMap;
  },
}

For more clarity about what this does, note that defaultPathMap looks a little like this:

{
  '/': { page: '/' },
  '/about': { page: '/about' },
  '/index': { page: '/index' },
  '/404': { page: '/_error' },
}

This takes each key in this object and injects new objects with different query strings:

{
  '/': { page: '/' },
  '/de': { page: '/', query: { language: 'de' } },
  '/fr': { page: '/', query: { language: 'fr' } },
  '/about': { page: '/about' },
  '/de/about': { page: '/about', query: { language: 'de' } },
  '/de/about': { page: '/about', query: { language: 'fr' } },
  // ...
}

which will output the static site as expected. However, we still have to actually accept the language query param. In my case, I'm using next-apollo, and each query needs to know the location, so React's Context API was a great fit.

Here is my root component:

export default withData(
  withRouter((props: any) => (
    <LanguageContext.Provider value={props.router.query.language || 'en-US'}>
      ...
    </LanguageContext.Provider>
  )),
);

In all Query components, I use a LanguageContext.Consumer to extract the locale value. I refactored this away into a LocalizedQuery that will automatically utilize the context.

@danechitoaie
Copy link

danechitoaie commented Mar 19, 2019

since you want this only for static export

Does this work for normal dynamic sites? What can you do in that case?

@elbotho
Copy link

elbotho commented May 30, 2019

@stevenpetryk thank you very much! Is you complete on GitHub by any chance?

@iliyanlishkov
Copy link

iliyanlishkov commented Feb 7, 2020

@stevenpetryk Please send the code and folders structure! Thank you!

@tkumpanenko
Copy link

@stevenpetryk Can you share folder structure and LanguageContext.Provider

@stevenpetryk
Copy link
Author

Hi folks, I cannot! I don't have the time.

@foxundermoon
Copy link

the SSG how to pass query.
getStaticProps can not receive query.

@giftedunicorn
Copy link

Hi @stevenpetryk, I'm using next 9.4.4. but the defaultPathMap is an empty object in exportPathMap function. Do you have same issue?

@stevenpetryk
Copy link
Author

@ednew2018 nope, the code I posted up there isn't how we're doing things these days. But I'd guess that if your defaultPathMap is empty, it means you have no static pages.

These days, I think there are better ways to solve this problem (getStaticPaths?).

In general I wouldn't follow my own advice anymore—it's worth opening a new issue/posting on SO/Slack to see what the best way is these days.

@giftedunicorn
Copy link

@stevenpetryk The defaultPathMap works if I run next build && next export.

@balazsorban44
Copy link
Member

This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@vercel vercel locked as resolved and limited conversation to collaborators Jan 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants