I18n message formatting with React elements (using wrm-react-i18n)

Mission

To pass in a React element and format the message based on its content; e.g. we want these to render with different plural/singular text depending on what the value of X is, with the value of X being bolded:

  • where X == 0, it would read as: “0 Examples!!!”
  • where X == 1, it would read as: “1 Example”
  • where X > 1, it would read as: “2 Examples”

Problem

@atlassian/wrm-react-i18n internally works by swapping out any React elements with placeholders and then delegates to wrm/format which formats those place holders in to the message. @atlassian/wrm-react-i18n takes the formatted message, and then replaces the placeholders with the provided React elements.

It’s exactly these placeholders that prevents wrm/format from being able to pick the correct choice because it doesn’t know the true values.

A bad solution

This should not be used, because:

  • We forever have a risk that there’s an XSS or it’s one day introduced by accident
    • Even if in some specific scenario the chance is ranked as low, we do not want linters and security scanners causing fatigue
  • It introduces a new bug type where the HTML is not as intended due to having gone through the translation process

Putting the HTML into the formatted string:

some.i18n.key={0,choice,0#<b>0</b> Examples!!!!!|1#<b>1</b> Example|1#<b>{0,number}</b> Examples}

Then allowing using dangerouslySetInnerHTML (assuming this is a modern codebase where React is used):

<p dangerouslySetInnerHTML={{__html: I18n.getText('some.i18n.key', numberOfExamples)}}/>

A better solution

→ Pass the value in again without being wrapped in any React elements.

We’d make the I18n string:

some.i18n.key={1,choice,0#{0,number} Examples!!!!!|1#{0,number} Example|1<{0,number} Examples} 

Then use it like:

import {I18n} from '@atlassian/wrm-react-i18n';

I18n.getText(
          'some.i18n.key',
          <b>{numberOfExamples}</b>,
          numberOfExamples,
)