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

Usage outside of React components #1386

Closed
paolostyle opened this issue Jul 31, 2019 · 3 comments · Fixed by #1387
Closed

Usage outside of React components #1386

paolostyle opened this issue Jul 31, 2019 · 3 comments · Fixed by #1387

Comments

@paolostyle
Copy link

As requested: #1280 (comment)

I would want to use internationalization features outside of React components, e.g. in Redux-related files. Currently (in v2) it's quite difficult to do and I'm forced to do some awkward stuff to achieve what I want.

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

  1. Sometimes I need to use localized messages in places that are not actually React components.
    For example, in my app there's an "Export to XLSX" option which exports data (array of objects) presented in a table to a file. Because the library that does that is quite heavy, I'm lazy loading module that contains the exporting function and I'm only passing that array of objects to that function on button click. Now I would want to translate headers that will end up in that XLSX file based on object keys. I can either translate them in a component that contains that button (which IMO doesn't make much sense...) or make some custom function that uses .json files with messages and pass current locale to the exporting function. It kind of works but I really don't like neither solution.
  2. Sometimes I have to dynamically display some strings based on data localized in Redux store. The app I'm working on is a bit complicated, but generally let's say I have a graph (the one with nodes and edges, displayed as SVG). Now that graph is based on some data (obviously) and there should be a way to change the color of nodes based on selected parameter, so I want to do that using a custom ComboBox component.
    Without going into details, a parameter can have either a completely custom label (that is stored as an understandable text in my Redux store) or be from a set of limited options which are stored as an object in the Redux store, but labels are based on object keys and should be different based on currently selected language. Now I want to have both kinds of parameters in that ComboBox. Now here it gets awkward, because for some objects the label has to be translated and in some it doesn't. Ideally I'd like to just translate it in a selector that extracts these data, but because I can't I'm ending up with something like this:
[{
  value: 'preMadeParam',
  label: 'options.pre_made_param',
  needsTranslation: true
}, {
  value: 'customOptions.12',
  label: 'My awesome option',
  needsTranslation: false
}]

and then, because of the nature of that ComboBox, I have to use injectIntl HOC and do something like this (I'm using a custom HOC so normally instead of tr() it would be injectIntl.formatMessage(...) - I don't even remember because it's absurdly verbose...):

<ComboBox itemToString={item => item.needsTranslation ? tr(item.label) : item.label} />

It's not awful, but it could be way simpler if I could just do the tr() part in the selector.

Describe the solution you'd like
Well ideally I'd like to do something like

import { formatMessage } from 'react-intl';

function someFunctionOutsideOfComponent() {
  console.log(formatMessage('my_message_id'));
}

and that would console.log translated message based on currently selected locale. That would be quite difficult, though, so I guess something like:

import { createIntlInstance, IntlProvider } from 'react-intl';
// offtopic: I actually still don't know what's the best way to lazy load messages based on locale
import enMessages from '../translations/en.json';

export const intlInstance = createIntlInstance('en-US', enMessages);

ReactDOM.render(<IntlProvider instance={intlInstance}><App /></IntlProvider>);

// in some other file
import { intlInstance } from './file';

function someFunctionOutsideOfComponent() {
  console.log(intlInstance.formatMessage('my_message_id'));
}

would work too? I didn't put much thought into that to be fair, but in other i18n libraries I used in other frameworks it worked more or less like that.

Describe alternatives you've considered
Currently to do that I can either:

a) if the end goal is to display it in a component, just pass translation keys around and they eventually make into a component or if it's not, do translations in the component and then pass them around
b) make a custom function that uses extracted .json files and translates stuff based on that,
c) do some black magic and somehow extract Intl object from IntlProvider and use that (there were some quite old issues in this repo that described how to do that and apparently it worked)

To be fair I don't think any of this is good enough. I usually just use the first option but it constantly feels like I'm making awkward workarounds. I used i18n plugins in Vue and in Aurelia before and I wasn't forced to pass everything as keys. So it would be great if there was a way to do the stuff I explained. Or perhaps it is possible, but I'm just dumb and can't find it in docs?

@longlho
Copy link
Member

longlho commented Jul 31, 2019

The 2nd option u mentioned is how react-intl works internally so exposing it shouldn't be too hard.

longlho added a commit that referenced this issue Aug 2, 2019
Also introduces `createIntl` and `RawIntlProvider`
fixes #1386 
fixes #1376 

## Creating intl without using Provider

We've added a new API called `createIntl` that allows you to create an `IntlShape` object without using `Provider`. This allows you to format things outside of React lifecycle while reusing the same `intl` object. For example:

```tsx
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'

// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()

const intl = createIntl({
  locale: 'fr-FR',
  messages: {}
}, cache)

// Call imperatively
intl.formatNumber(20)

// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
```

This is especially beneficial in SSR where you can reuse the same `intl` object across requests.
@samajammin
Copy link

I'm also looking to accomplish what @paolostyle described, i.e.

import { formatMessage } from 'react-intl';

function someFunctionOutsideOfComponent() {
  console.log(formatMessage('my_message_id'));
}

Is this now possible? Thanks in advance!

@longlho
Copy link
Member

longlho commented Nov 26, 2020

Yes u can use https://formatjs.io/docs/react-intl/api#createintl to create an intl object, then pass that into <RawIntlProvider /> to use it within React.

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

Successfully merging a pull request may close this issue.

3 participants