Skip to content

Add ability to use elements in Translate interpolation options#11204

Merged
ThieryMichel merged 2 commits intonextfrom
translate-element
Mar 27, 2026
Merged

Add ability to use elements in Translate interpolation options#11204
ThieryMichel merged 2 commits intonextfrom
translate-element

Conversation

@fzaninotto
Copy link
Copy Markdown
Member

@fzaninotto fzaninotto commented Mar 26, 2026

Problem

<Translate> supports string interpolation:

const messages = {
    custom: {
        hello_world: 'Hello, %{name}!',
    },
};

<Translate i18nKey="custom.hello_world" options={{ name: 'John' }} />
// Hello, John!

But it does not allow to use a React element in interpolatino:

const messages = {
    custom: {
        hello_world: 'Hello, %{name}!',
    },
};

<Translate i18nKey="custom.hello_world" options={{ name: <RecordRepresentation /> }} />
// Hello, John Doe!

Solution

Replace element options with placeholders, then pass the options to the translation provider, then replace the placeholders back with elements.

How To Test

http://localhost:9010/?path=/story/ra-i18n-polyglot--translate-with-react-element&args=locale:fr

Additional Checks

  • The PR targets master for a bugfix or a documentation fix, or next for a feature
  • The PR includes unit tests (if not possible, describe why)
  • The PR includes one or several stories (if not possible, describe why)
  • The documentation is up to date

Comment on lines +10 to +44
const elementMap: Record<string, ReactElement> = {};
const sanitizedOptions: Record<string, any> = {};
let placeholderIndex = 0;

if (options) {
for (const [key, value] of Object.entries(options)) {
if (isValidElement(value)) {
const placeholder = `TRANSLATION_PLACEHOLDER_${placeholderIndex++}`;
elementMap[placeholder] = value;
sanitizedOptions[key] =
`${SPLIT_MARKER}${placeholder}${SPLIT_MARKER}`;
} else {
sanitizedOptions[key] = value;
}
}
}

const translateOptions =
typeof children === 'string'
? { _: children, ...sanitizedOptions }
: sanitizedOptions;

const translatedMessage = translate(i18nKey, translateOptions);

if (!translatedMessage) {
return children;
}

// If no elements were extracted, return plain string
if (placeholderIndex === 0) {
return <>{translatedMessage}</>;
}
return children;

// Split the translated string and replace placeholders with React elements
const parts = translatedMessage.split(SPLIT_MARKER);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding useMemo around all those computations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When calling <Translate key="foo" options={{ name: <NameField /> }} />, the options prop receives a new object on every render, so useMemo will always recompute. The memo is effectively useless for the most common usage pattern.

Besides, we don't know if this computation is costly. Premature optimization?

@ThieryMichel ThieryMichel merged commit ffbc184 into next Mar 27, 2026
15 checks passed
@ThieryMichel ThieryMichel deleted the translate-element branch March 27, 2026 08:24
@ThieryMichel ThieryMichel added this to the 5.15.0 milestone Mar 27, 2026
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 this pull request may close these issues.

3 participants