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

useTranslations support for async rsc #406

Closed
ddenizakpinar opened this issue Jul 15, 2023 · 11 comments
Closed

useTranslations support for async rsc #406

ddenizakpinar opened this issue Jul 15, 2023 · 11 comments
Labels
enhancement New feature or request unconfirmed Needs triage.

Comments

@ddenizakpinar
Copy link

ddenizakpinar commented Jul 15, 2023

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

Currently useTranslations works fine with server components but when I convert the component to async it throws
Error: Expected a suspended thenable. This is a bug in React. Please file an issue..
Will there be a support for this situation?

Describe the solution you'd like

It should work as it is working on sync rsc

@ddenizakpinar ddenizakpinar added enhancement New feature or request unconfirmed Needs triage. labels Jul 15, 2023
@StevenLangbroek
Copy link

Hey @ddenizakpinar! This issue is closed as "completed", but I'm still getting this error with latest next + next-intl...

@bastienrobert
Copy link

Removing async from the component fixed it for me.

ref. vercel/next.js#51477 (comment)
ref. vercel/next.js#51477 (comment)

@rentalhost
Copy link

@ddenizakpinar can you please reopen this issue? I need use async anyway, and I am hitting this issue on 3.0.0-rc.2.

@ddenizakpinar ddenizakpinar reopened this Oct 11, 2023
@ddenizakpinar
Copy link
Author

@rentalhost done

@amannn
Copy link
Owner

amannn commented Oct 12, 2023

I'll go into a bit more detail since this seems to be a popular question.

The Server Components integration of next-intl asynchronously resolves a config, e.g. to load messages.

To do this, we currently use the use hook, which is primarily known for resolving promises in Client Components but also works in Server Components. The one limitation that comes with this is that the component can not be async.

To work around this, we suggest splitting your component into an async and non-async part:

Error: `useTranslations` is not callable within an async component. To resolve this,
you can split your component into two, leaving the async code in the first one and
moving the usage of `useTranslations` to the second one.

Example:

async function Profile() {
  const user = await getUser();
  return <ProfileContent user={user} />;
}

function ProfileContent({user}) {
  // Call `useTranslations` here and use the `user` prop
  return ...;
}

Many users have suggested an awaitable version of useTranslations with an API like await useTranslations(), await useTranslationsAsync(), await useTranslations().toPromise(), etc. While this would technically be possible, the ESLint config of React warns against using hooks in async components. Furthermore, built-in hooks from React like useId don't work in Server Components.

In my opinion, one of the biggest advantages of how the next-intl Server Components integration is implemented is that we use an identical API for Server as well as Client Components. With this, components that simply receive data and render translated content in return qualify as shared components that can render either in a Server or Client Component, depending on where they are imported from. In contrast, an async-only API doesn't support this and would force users to a place where components can only run in one environment depending on how they're authored. This is limiting and prevents certain architectural options.

There's an argument that an additional API could be added specifically for async components, while still defaulting to useTranslations in components. In my opinion, it's currently a bit too early to add something like this since it's unclear if async might be supported in Client Components in the future:

Ideally we would prefer to support async/await everywhere.

(from the first class promises RFC)

Even if there's a decision that async/await will not come to Client Components, it's questionable if the benefit of adding a distinct API for this use case warrants the increase in API surface of the library for what seems to be an edge case (see below).

On a related note, using use instead of await currently also has a minor performance advantage (although this might disappear in the future):

use actually has less overhead compared to async/await because it can unwrap the resolved value without waiting for the microtask queue to flush.

(from the first class promises RFC)

I hope this provides some background. From my experience, typically async Server Components are used mostly at the top of route handlers, followed by non-async components that render the data and potentially pass some of it to client components. In practice, there are occasionally cases where a call to useTranslations directly at the top-level is desirable, but more common than that, I find that the handling of translations is typically relevant for components that receive data. Based on this, I think the current restriction is practically not that limiting.

A note on getTranslator:

Some users have discovered that const t = await getTranslator(locale) can be called in async Server Components. This works indeed and is expected to continue to work. However, the API is specifically made for non-React environments like Route Handlers, therefore the t.rich function returns a markup string (e.g. '<strong>Hello</strong>') in contrast to the ReactNode return type of t.rich from useTranslations. You might want to keep this in mind in case you're considering using this API. (this is no longer the case as of next-intl@3.0.0-rc.6)

The general recommendation is to split components and stick to useTranslations.

See also #276

@ngalioth
Copy link

This issue was discussed at length here: #276

Does the mentioned workaround in the error message work for you?

I think it's about the fact that the use() api is only available in experimental and canary channels.I am using

react 18.2.0
next-intl 3.0.0-rc.4
next 13.5.4
After manually enable the experiemental flag ppr:true in next.config.mjs, useTransitions() in async rsc works

@ngalioth
Copy link

I just find out that useFormat() still dosen't work

@amannn
Copy link
Owner

amannn commented Oct 14, 2023

After manually enable the experiemental flag ppr:true in next.config.mjs, useTransitions() in async rsc works

Really? I couldn't reproduce this with the latest next version.

@ngalioth
Copy link

ngalioth commented Oct 14, 2023

After manually enable the experiemental flag ppr:true in next.config.mjs, useTransitions() in async rsc works

Really? I couldn't reproduce this with the latest next version.

Sorry, it's my fault. I double examined the code and confirmed I was wrong.
Meanwhile, i did some digging. The problem comes from the use() call in an async RSC. It's not implemented yet and should be considered as an error. Comments can be found here.Link to the comment.
The problems has been there for nearly a year and I don't think there's any progress since it's not natural to call an async function inside a sync function. So maybe we can export an async version for useTranslations say useTranslationsAsync without using the use() api as a workaround. I believe this won't cost too much effort.

@amannn
Copy link
Owner

amannn commented Oct 18, 2023

I've expanded my comment above to explain the current architectural direction in more detail, hope this helps!

@amannn
Copy link
Owner

amannn commented Oct 19, 2023

After writing the detailed comment above I gave the topic of the function signature of t.rich some more thought. I think previously this was somewhat confusing, since t.rich would either operate on React elements or strings depending on if you receive the function from useTranslations or getTranslator.

Based on this, the latest 3.0 release candidate next-intl@3.0.0-rc.6 now includes a new t.markup function that removes this ambiguity. By doing this, (await getTranslator(locale)).rich will now return a ReactNode and can be used for edge cases of server-only components. See also the release notes.

The general recommendation is still to split components, as this will naturally result in the creation of shared components that work in either environment.

I think based on this, this issue can be closed. Hope this helps!

@amannn amannn closed this as completed Oct 19, 2023
tomivm added a commit to cboard-org/cboard-ai-builder that referenced this issue Mar 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request unconfirmed Needs triage.
Projects
None yet
Development

No branches or pull requests

6 participants