Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Idea: Internationalization support #202

Closed
sergiodxa opened this issue Jun 18, 2021 · 20 comments
Closed

Idea: Internationalization support #202

sergiodxa opened this issue Jun 18, 2021 · 20 comments

Comments

@sergiodxa
Copy link
Member

I have been playing with i18next to add i18n to a Remix app, I was able to make it work but there are a few things which are missing or Remix could help solve better.

  • Loading localized strings (it could be way better)
  • Route meta (title, description, etc.) (missing)
  • URLs (add /:locale prefix automatically or change the whole URL based on the language) (missing)

Loading localized strings

The approach I used to load the localized strings for React i18next was to detect the language the user wants based on the request and then read a JSON from disk, to detect the language I used the following approach:

  • If the query has ?lng=:locale I use that
  • If there's a cookie i18next I use that
  • If there's an accept-language header I use that
  • I default to English

Once I got the locale from query/cookie/header/default I use the package accept-language-parser to set an array of supported languages and check if the user expected language is there, and default again to English if it's not. This means if the browser accept-language comes with es and en and I only support English it will default to English.

Then in entry.server I run this function to get the language and read the JSON from disk, the JSON is on public/locales/:locale.json.

Client-side, in entry.client I initialize i18next using the i18next-browser-languagedetector plugins which will use the same approach to get the user preferred language and the plugin i18next-http-backend to load the locale from /locales/:locale.json (this is why I put it on the public folder), once i18next has initialized I run the ReactDOM.hydrate function.

I need to wait for i18next to finish so the app will render with the correct language and not using the default while it finish to initialize, detect the language and load the locales.

If Remix have a built-in way to do i18n I would be able to remove a lot of code here, I would expect Remix to see what locales JSON files I have in a folder at the build time and then use a similar approach to what I did to know what locale to load. So a localized Remix app would have this folder structure:

/app
/locales
  en.json
  es.json
  ...
remix.config.js

Another option, would be to allow routes to define how to get the locales based on the request, this will be great to avoid loading locales I don't really need but it could mean I have to see how to ask only for those loaders and not just load everything and duplicate all the messages.

export let locales: LocalesFunction = async ({ request }) => {
  // somehow get the locales here
  // I could do whatever I want here like checking the query, a cookie, a header, the session or querying the DB if I stored the user preferred language
  // and finally I return a JSON with the locales
}

Then Remix could call this along the loader, the result (of both approaches) could be passed to the meta function and to the component of the route (in a context), inside that component we could use something like this:

export let meta: MetaFunction = ({ locales }) => {
  return { title: locales.title };
}

export default function View() {
  let t = useTranslations() // API inspired by react-i18next
  return (
    <>
      <h1>{t("title")}</h1>
      {/* this Trans is also from react-i18next, is how to add components in the middle of a translation */}
      <p><Trans message="copy" components={ strong: <strong /> } /></p>
    </>
  )
}

Route meta

The title and meta:description of a route should be localized too, with the approach I'm using I can't do it because I only have access to the translations in the view, I would have to load the translations again in every loader to pass them to the meta function.

If Remix automatically loads the locales it could inject it to the route meta function, this could work in a per page or global (all locales in a single file) approach.

URLs

Another things that Remix could support is to add a prefix to all the URLs of the app based on the language, so the app/routes folder could be like this:

/app
  /routes
    index.tsx
    about.tsx
    something.tsx
    another.tsx

And Remix could generate the routes:

/en
/en/about
/en/something
/en/another

/es
/es/about
/es/something
/es/another

And basically add the locale before every route, then use that to detect the locale of the user, if the user access without that locale (e.g. /about) it could use the default language (defined in remix.config.js) and we could handle a redirect to the locale specific or not.

Another thing Remix could allow is to enable translate the pathnames, so /about becomes /sobre-mi in Spanish.

@sergiodxa sergiodxa changed the title Internationalization support Idea: Internationalization support Jun 18, 2021
@tchak
Copy link

tchak commented Jun 22, 2021

I just wanted to mention that I really hope that no matter what is built into remix will not be tightly linked to i18next. I have a preference for react-intl (mainly because of ICU Message syntax) and I hope it will be a viable option with remix. I agree that the main benefit of some support in remix would be to load translations alongside other required assets for a page.

@sergiodxa
Copy link
Member Author

Just in case, I don’t want Remix to bundle i18next specifically, I used it but if it can work with any other lib would be great, or if has a built-in lib so there’s no need to add anything else.

@willin
Copy link
Contributor

willin commented Nov 26, 2021

Internationalized Routing just works. ref

@sergiodxa
Copy link
Member Author

sergiodxa commented Nov 26, 2021

Internationalized Routing just works. ref

@willin That’s Next, this is about Remix

@willin
Copy link
Contributor

willin commented Nov 29, 2021

just a ref to look into

@BenoitAverty
Copy link

Hey !
I'm linking my own experiment here : https://github.com/BenoitAverty/remix-i18n-test

I've done this experiment with these requirements in mind :

  • Be able to load translations at runtime depending on env var, customer id, ...
  • Be able to load translations from anywhere (static json file, database, any translation provider)
  • Avoid loading the same translations twice in a request
  • Avoid fetching the same translations twice from the brower

I think this is important to consider if remix provides something built-in.

Language detection and routing should be customizable too I think.

@olavoasantos
Copy link

I was looking into this as well. I was having a hard time translating my meta tags as they got translated back into the fallback language upon hydration. Turns out the meta function runs both in the server and in the browser and there was a mismatch on the active language. I ended up finding a hack (not too proud of it but it works haha).

Digging into the Remix's source code, turns out the route's meta function runs in a component scope so you're able to make use of hooks. If you have your I18n provider above the Document component you can do something like:

export const meta: MetaFunction = () => {
  const { t } = useTranslation();

  return {
    title: t('core:meta.main.title'),
    description: t('core:meta.main.description'),
  };
};

@sergiodxa
Copy link
Member Author

@olavoasantos what I do is to get the title inside the loader, and return it as data, then read the data inside the meta function

@olavoasantos
Copy link

Hum... That's less bad than my hack I think haha
But you lose dynamic language switching, right? You'd need to refresh the page since the tags are coming from the server?

@sergiodxa-silverback
Copy link

@olavoasantos if you make the language switch submit a form (it should) then all loaders are going to be reloaded after the submit automatically so you don't lose the benefit.

@olavoasantos
Copy link

Makes sense. Thanks for the help!

@seia-soto
Copy link

I hope this idea to go into reality. I do love remix and am ready for Korean translation!

@andrelandgraf
Copy link
Contributor

andrelandgraf commented Dec 18, 2021

I just want to add a few things that I think are very important to consider if a :locale prefix is added to Remix:

Link, useNavigate, and redirect should support the /:locale prefix out of the box

If Remix implements an out-of-the-box localization solution, the Link component and useNavigate hook should add the :locale prefix to the To path property! Similarly, any server-side redirect should also add the :locale prefix to the URL.

Default language should not include an :locale prefix

The default / fallback language should not receive a :locale path prefix to avoid redirects.

Illustration:

In the remix.config.json (or somewhere else), we define en-US as our default language. An English speaking user visits our domain ourdomain.com. Remix should not redirect the user to ourdomain.com/en-us/ but rather keep the user at ourdomain.com.

Pretty sure this is also important for SEO that there is not immediate redirect on the landing page for the google bots.

So instead of

/en
/en/about
/en/something
/en/another

/es
/es/about
/es/something
/es/another

Remix should generate

/
/about
/something
/another

/es
/es/about
/es/something
/es/another

I think Next.js does it this way as well.

@andrelandgraf
Copy link
Contributor

I would be amazing if Remix could allow routes to be marked as "English / default language only". I have always missed that feature in other frameworks. It would allow devs to translate applications gradually and to keep /admin, /documentation, and similar subroutes in a single language.

In routes/__layout/documentation.tsx:

export const i18n = false

Remix then should remove the /:locale path prefix when the user enters the subroutes and switch the document language to "en" or whatever the default language of the application is.

Not sure if there are any major downsides to this (apart from a lot of work obviously) but the feature would be really helpful, I think!

@CanRau
Copy link
Contributor

CanRau commented Dec 18, 2021

Default language should not include an :locale prefix

I for myself prefer to add the language prefix to all, including the default/fallback, languages, although I know many don't like or do it that way, I prefer this ever since reading some Google Developers content on i18n or something, stating that it's not necessary but recommended for consistency and clarity, though can't find the page right now 😳😴

At the moment I'm doing i18n manually, tho so far I've got only english content 😅 (https://www.canrau.com)

@larissa-n
Copy link

Some recent projects out there with /:locale:

Neither happens to be based on i18next (and I don't think either of them is meant for production, or at least there are obvious limitations).

Another thing Remix could allow is to enable translate the pathnames, so /about becomes /sobre-mi in Spanish.

That would be awesome!

What I haven't seen implemented in Remix yet is domain routing (like mysite.fr setting the language accordingly), but it wouldn't be far from those examples. Remix is uniquely suited for stuff like this.

@sergiodxa
Copy link
Member Author

What I haven't seen implemented in Remix yet is domain routing (like mysite.fr setting the language accordingly), but it wouldn't be far from those examples. Remix is uniquely suited for stuff like this.

I think you can use the request.url to detect the domain and get the correct locale based on that

@heymath
Copy link

heymath commented Feb 8, 2022

That would be great to have a way to create routes like routes/about.tsx and map them to a locale to change its actual path depending on the locale like:

  • en-US : path = /about
  • fr-FR : path = /a-propos

And we would have to be able to set/detect a locale from request URL,domain, headers, (or whatever else) too:

  • mydomain.fr : locale = fr-FR
  • mydomain.com : locale = en-US

I would have loved to try Remix over Next.js for a project, but the fact that routes can not be translated or associated/restricted to a locale 😭

Or maybe there is a way not simplified/config by Remix, but I did not get how to do it with custom code/config? 🤔

@CanRau
Copy link
Contributor

CanRau commented Feb 8, 2022 via email

@knpwrs
Copy link

knpwrs commented Apr 3, 2022

As someone who has used both i18next and lingui, I'd like to give a shoutout to lingui. They let you write your strings inline with React components, including pluralization, and then it extracts ICU strings for you so you don't have to know that syntax yourself.

Unfortunately, it's based on Babel Macros, so if it were to be supported in Remix it would be through something like https://github.com/nativew/esbuild-plugin-babel (though I don't know if one must use the macro, possibly worth looking into or otherwise asking).

@remix-run remix-run locked and limited conversation to collaborators Apr 19, 2022
@chaance chaance converted this issue into discussion #2877 Apr 19, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests