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

Open
amannn opened this issue Nov 9, 2023 · 11 comments
Open

Fully serializable config #611

amannn opened this issue Nov 9, 2023 · 11 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 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);
}

… or alternatively share them via a component:

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

… or hook:

function Markup() {
  const t = useTranslations('Markup');
  const defaultValues = useDefaultTranslationValues();
  return t('markup', defaultValues);
}

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.

onError

TBD, maybe registring a global handler would do the job?

getMessageFallback

Maybe an ICU message like MISSING: {namespace}.{key}? I'm not sure how many users really use this feature and if it's worth introducing much complexity here.

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.

Another alternative could be to export a general config from i18n.ts that is shared across the server and client and another one that is request-specific. I'm not sure however if this works in practice since i18n.ts can use server-only APIs, which would break the client-side build.

@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 wold 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! 👏

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