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

Fully serializable config #611

Closed
amannn opened this issue Nov 9, 2023 · 13 comments
Closed

Fully serializable config #611

amannn opened this issue Nov 9, 2023 · 13 comments

Comments

@amannn
Copy link
Owner

amannn commented Nov 9, 2023

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

These three config options accept functions and can therefore not be serialized currently:

  1. defaultTranslationValues
  2. onError
  3. getMessageFallback

Due to this, we can't automatically pass them from an RSC render to the client side.

It might be worth investigating serializable alternatives, to avoid this.

Describe the solution you'd like

defaultTranslationValues

Maybe a better option could be to deprecate the option altogether and ask users to share values e.g. via a component:

function Markup() {
  const t = useTranslations('Markup');
  return <RichText>{components => t('markup', components)}</RichText>
}

function RichText({children}) {
  return children({
    b: (chunks: ReactNode) => <b>{chunks}</b>
  });
}

… or imports:

// defaultRichTextComponents.tsx

export default {
  b: (chunks: ReactNode) => <b>{chunks}</b>
}

// Markup.tsx

import defaultRichTextComponents from './defaultRichTextComponents';

function Markup() {
  const t = useTranslations('Markup');
  return t.rich('markup', defaultRichTextComponents);
}

As a nice side effect, this would allow us to statically type the arguments of a given ICU string (see #410), since there are no potential global values floating around.

Another benefit could be to remove ambiguity between values that should be used for t.rich and t.markup (see #1181).

onError

See #1285

getMessageFallback

See #1285

Alternatives:

  • An ICU message like MISSING: {namespace}.{key}?
  • Deprecate the feature completely?

Describe alternatives you've considered

NextIntlClientProvider can be added in a client-side module graph to configure the non-serializable config options, see the docs.

@amannn amannn added enhancement New feature or request unconfirmed Needs triage. labels Nov 9, 2023
@ovflowd
Copy link

ovflowd commented Nov 9, 2023

Note this doesn't handle the case where I want to pass the messages as an object from another component.

I.e.

const MyComponent = ({ translationValues }) => t.rich('translationKey', translatioNValues);

So IMHO the whole RichTranslations and MarkupTranslations should support non-functions.

@amannn
Copy link
Owner Author

amannn commented Nov 9, 2023

You mean when translationValues contains functions, right? Can you share more about the practical use case?

@ovflowd
Copy link

ovflowd commented Nov 9, 2023

You mean when translationValues contains functions, right? Can you share more about the practical use case?

Wdym by that? I should be able to pass translation values as props between any component if I wish. Shouldnt be blocked by an implementation detail of making them a function.

In other words, what if I have dynamic values and want to map them, I'd be unable due to next-intl requiring markup or rich text to be wrapped on functions...

@amannn
Copy link
Owner Author

amannn commented Nov 13, 2023

I should be able to pass translation values as props between any component if I wish.

React defines the rules for which props are serializable.

next-intl provides an API that allows for rich text formatting where any custom component can be used:

t.rich('hint', {
  help: (chunks) => <Link href="/help">{chunks}</Link>
})

Theoretically, React elements themselves are serializable, so from an API perspective the value of help here is serializable:

t.rich('hint', {
  help: <Link href="/help" />
})

However, if the element that is passed to help comes from a Server Component and t.rich executes in a Client Component, then only the markup is transferred—not the component. This is quite the point of the RSC model. Therefore, practically this API doesn't work.


Long story short, I can't think of an API that has the same flexibility as the current t.rich that would accept a fully serializable config. Staying within either a server or client module graph for rich text elements solves the problem from a different angle. Due to the way the RSC model works, you can import rich text elements even from both from Server and Client Components.

My comment on top of this thread describes examples for sharing common rich text elements. I think it's helpful to discuss this based on concrete examples to be able to find good solutions.

In case you can think of a better pattern, let me know!

@ovflowd
Copy link

ovflowd commented Nov 13, 2023

I think sharing this with you is self-explanatory.

Pretty much there's a translation key that we want to have a specific part of it to be surrounded with a "span" element (https://github.com/nodejs/nodejs.org/blob/main/i18n/locales/en.json#L84), that's it.

This was possible before, because react-intl allowed me to replace a variable, such as {something} (https://github.com/nodejs/nodejs.org/blob/c72dc046dffda7d8b056623602c80bac3dd6a8f4/i18n/locales/en.json#L19) with something else (https://github.com/nodejs/nodejs.org/blob/c72dc046dffda7d8b056623602c80bac3dd6a8f4/layouts/DocsLayout.tsx#L21)

I understand that this is unrelated to next-intl, and react-intl would face the same issues once I've used server components; Hence me indulging if it makes sense to support something else.

In the end, I just want to be able to share translation values from Component A to Component B. Right not it works because both components are client components, but I feel that maybe next-intl should support a way to transfer rich-text content independent if the component is RSC or CSC... 🤔

Not a big deal, but I feel that it'd be nice if it was supported. Either by something like:

const translationContext: RichTranslationValues = {
 graySpan: '<span className="small color-lightgray">{0}</span>'
}

Encapsulating the HTML code within a string and then eval'ing it. And the translation key be like:

"apiLts": "{fullLtsNodeVersion} API <graySpan>something</graySpan>",

I know this is just a poor example, and definitely a stretch.

@ovflowd
Copy link

ovflowd commented Nov 13, 2023

Also sorry if I'm just crying wolf here, @amannn, please feel more than free to just disregard my request!

@amannn
Copy link
Owner Author

amannn commented Nov 15, 2023

This was possible before, because react-intl allowed me to replace a variable, such as {something} (https://github.com/nodejs/nodejs.org/blob/c72dc046dffda7d8b056623602c80bac3dd6a8f4/i18n/locales/en.json#L19) with something else (https://github.com/nodejs/nodejs.org/blob/c72dc046dffda7d8b056623602c80bac3dd6a8f4/layouts/DocsLayout.tsx#L21)

That's an interesting one, because you didn't need chunks previously (since "LTS" was hardcoded):

spanLts: <span className="small color-lightgray">LTS</span>,

next-intl could most likely support passing a React Element without chunks for rich text, by directly assigning the element to a value (without a function). In fact, I think it already does, we'd only have to update the types for such a call.

There's currently an expandable section in the rich text docs about self-closing tags. This could be updated accordingly as part of this change.

In regard to rich text content with chunks: I don't see eval'ing as a good direction here, to be honest. The provided elements can not just be HTML, but any React component.

To come back to your example: I think there could also be a question here about which parts of the component should be Server or Client Components. Briefly looking into the code, I'm wondering if the rendering of the sidebar could be achieved in RSC and only individual items that need to be highlighted if they're active could be Client Components. The next-intl App Router example does just that for the navigation. If you're staying within RSC for the rendering of the items, then this problem also disappears.

Hope this helps!

@ovflowd
Copy link

ovflowd commented Nov 15, 2023

'm wondering if the rendering of the sidebar could be achieved in RSC and only individual items that need to be highlighted if they're active could be Client Components.

Hm, true, I think I can make it RSC compatible.

In regard to rich text content with chunks: I don't see eval'ing as a good direction here, to be honest. The provided elements can not just be HTML, but any React component.

Right, I'm also not a fan of this. But I believe at least for the case you mentioned above about without chunks, worth documenting it and updating the types. Can be useful for some folks :)

@amannn
Copy link
Owner Author

amannn commented Nov 15, 2023

Right, I'm also not a fan of this. But I believe at least for the case you mentioned above about without chunks, worth documenting it and updating the types. Can be useful for some folks :)

Yep, I agree!

@ovflowd
Copy link

ovflowd commented Nov 15, 2023

Aaand, found a way to make it RSC by removing the only Hook that was "client-dependent"

@amannn
Copy link
Owner Author

amannn commented Nov 15, 2023

Awesome! 👏

@amannn
Copy link
Owner Author

amannn commented Aug 26, 2024

I've added #1285 to investigate a better way to achieve global error handling.

@amannn amannn mentioned this issue Sep 4, 2024
3 tasks
amannn added a commit that referenced this issue Oct 11, 2024
`defaultTranslationValues` allow to share global values to be used in
messages across your app. The most common case are shared rich text
elements (e.g. `b: (chunks) => <b>{chunks}</b>`).

However, over time this feature has shown drawbacks:
1. We can't serialize them automatically across the RSC boundary (see
#611)
2. They get in the way of type-safe arguments (see
#410)

Due to this, the feature will be deprecated and the docs will suggest a
better alternative for common tags in rich text that doesn't have the
limitations mentioned above ([updated
docs](https://next-intl-docs-git-feat-deprecate-defaulttrans-f52ebe-next-intl.vercel.app/docs/usage/messages#rich-text-reuse-tags)).
Shared values don't get a direct replacement from `next-intl`, but
should be handled as part of your app logic (e.g. a shared module, React
Context, etc.).

**Note**: #410 can not be
implemented immediately as part of this, as long as
`defaultTranslationValues` are still available (even if deprecated).
Instead, this feature could be added as part of the next major release.

Contributes to #611
@amannn
Copy link
Owner Author

amannn commented Oct 29, 2024

defaultTranslationValues have been deprecated in next-intl 3.22 and we suggest a better alternative now. Additionally, the error handling topic is tracked in a separate issue now: #1285.

Due to this, I'll close this issue.

@amannn amannn closed this as completed Oct 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants