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

Enable usage in backend code #47

Closed
stefnnn opened this issue Aug 4, 2021 · 8 comments · Fixed by #137
Closed

Enable usage in backend code #47

stefnnn opened this issue Aug 4, 2021 · 8 comments · Fixed by #137
Labels
enhancement New feature or request

Comments

@stefnnn
Copy link

stefnnn commented Aug 4, 2021

Is your feature request related to a problem? Please describe.
I would like to use next-intl in backend code, specifically I'd like to send an email based on the users language. There is no react rendering and hence no context with the translations available. If I understand correctly, then #40 still involved react rendering (although to a pdf).

Describe the solution you'd like
Ideally I'd like to setup some intl context statically in the backend and then in an api route load messages based on the current users language and have a translation function available.

Describe alternatives you've considered
Probably using https://formatjs.io/docs/intl could work, but I'm afraid there's quite a bit to rebuild, since e.g. the message lookup functionality is implemented in the useTranslations hook.

Additional context
Thanks for working on this library :-)

@stefnnn stefnnn added the enhancement New feature or request label Aug 4, 2021
@amannn
Copy link
Owner

amannn commented Aug 4, 2021

That's an interesting request. I think this could be doable, I just had a look through the code base to get an overview of the required parameters for all the functions. Some parameters are passed directly to the functions generated by the two hooks while others are read from context and are then put in the hook closure.

In detail these are:

useTranslations

  • t(key, values, formats) with these variables from the closure:
    • messages
    • Static namespace
    • onError
    • getMessageFallback
    • locale
    • global formats
    • timeZone
    • (caching implemented with React APIs)

useIntl

  • formatDateTime(value, formatOrOptions) with these variables from the closure:

    • timeZone
    • locale
    • onError
    • global formats
  • formatNumber(value, formatOrOptions) with these variables from the closure:

    • locale
    • global formats
    • onError
  • formatRelativeTime(date, now) with these variables from the closure:

    • global now
    • locale
    • onError

So to enable this, I guess we could offer a factory function which receives the variables that need to be available in the closure and that generates the necessary functions in return.

E.g.:

const t = createTranslator({namespace: 'foo', locale: 'en', messages: {'bar': 'Bar'}});
t('bar');

const intl = createIntl({locale: 'en', timeZone: 'Europe/Vienna'});
intl.formatNumber(1000);

Would something like this work for your use case?

Ideally we'd reuse these factory functions in the existing hooks then to avoid code duplication. The translation function also has some caching that is implemented with useRef – that should continue to work. Also there shouldn't be any regressions in terms of bundle size or performance, so this is something that needs to be investigated during the implementation. The factory functions should be split the same ways as the currently existing hooks, so tree shaking can continue to work.

If this works, the new lower level APIs could even be provided as a standalone library, potentially for other frameworks to use.

@stefnnn
Copy link
Author

stefnnn commented Aug 4, 2021

Thanks @amannn for the swift reply 😀. This looks pretty much like what I would need, except that I think createTranslator would also need messages, right? This sounds like quite a refactoring, so there might be simpler way to use translations with the same json format in the backend. Bundle size and performance would be of lesser concern in the backend. Maybe there's a more straightforward path using formatjs?

@amannn
Copy link
Owner

amannn commented Aug 4, 2021

I think createTranslator would also need messages, right?

True, good point! I've added it above.

Yep, it's indeed a bit of a refactoring. I'd definitely be open to a contribution here if you're interested, or if you need it for a commercial project and have a strong need, you can hire me to take care of the refactoring (I think it would be around 4h of work) 😉 .

Maybe there's a more straightforward path using formatjs?

You can of course also use functions from formatjs directly, but beware that there might be some tricky parts (see e.g. #45).

@stefnnn
Copy link
Author

stefnnn commented Aug 4, 2021

Right now I neither have the time to contribute nor the resources to fund you. I will look for lightweight workarounds, as currently this is really only used in one spot of our codebase. If there's more interest here in this issue or our own needs grow, I may be able to contribute at a later time. Thanks again for your time to analyze!

@amannn
Copy link
Owner

amannn commented Aug 5, 2021

Ok, sure – sounds good! Let's leave this issue open for now then.

@amannn amannn mentioned this issue Jan 19, 2022
@amannn
Copy link
Owner

amannn commented Oct 4, 2022

David Brands from Marvia reached out to me to discuss the feature. He brought up the point that it's questionable how t.rich would be supported for usage in a non-React Node.js context.

I think the options are:

  1. Not support it at all.
  2. Return a ReactNode and have the consumer call renderToString on the result.
  3. Call renderToString internally.
  4. Use a separate entry point like import {createTranslator} from 'next-intl/server'; where a separate API is used for t.rich, that works with strings. E.g. t.rich('richText', {b: (children) => '<b>${children}</b>'}) (the returned string would use backticks in real code).

(2) and (3) are both not so ideal in my opinion, as (2) is a bit cumbersome to handle and (3) would require an import from the server bundle of React (need to be careful with tree shaking, or use a different entry point). I was in fact wondering if the base implementation resulting from this could be useful to other frameworks as well and therefore avoiding a React dependency for this part could be quite helpful. (2) would actually be the default handling, but it could lead to errors where React components are accidentally stringified, so I'd consider throwing an error here.

So I think either (1) or (4) might be the way to go here.

@dbrxnds
Copy link

dbrxnds commented Oct 4, 2022

Thank you @amannn for the detailed comment.

As we do have immediate need for this feature, option 4 would be our preference. We currently do not necessarily need to render ReactNode's but instead, as shown in your example, regular HTML tags such as <b>. So that would be fine for us.

I was in fact wondering if the base implementation resulting from this could be useful to other frameworks as well and therefore avoiding a React dependency for this part could be quite helpful.

I also figured that this would likely turn out to be more of a "framework agnostic" approach, but I think that would actually be great.

@amannn
Copy link
Owner

amannn commented Oct 5, 2022

I think that's the right way to go, yes. After all providing the rich text formatters is really bound to the view layer and as we're leaving the React space here, the string interpolation might be the right choice to keep it simple.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants