Skip to content

i18n: Implement a solution for interpolation of React elements in strings #9846

Closed

Description

Problem: Most any sprintf implementation, including that of @wordpress/i18n, is expected to return a string value. In a React application, this can be problematic if the string contains HTML markup, as the HTML will be escaped, at least without using dangerouslySetInnerHTML, which is discouraged. This can sometimes lead to developers implementing workarounds using concatenation of partial localized strings (e.g. #5767 (comment)), which is not advisable as it cannot be translated accurately in many languages. This is also noted in the i18n for WordPress Developers guide:

Use format strings instead of string concatenation—sprintf(__('Replace %1$s with %2$s'), $a, $b); is always better than __('Replace ').$a.__(' with ').$b; .

https://codex.wordpress.org/I18n_for_WordPress_Developers#Best_Practices

Task: Find a way to allow for React elements to be inserted within localized strings safely.

Prior Art:

Additional Considerations:

  • We still need to be able to extract the string which would be surfaced to translators, presumably including placeholders where a JSXExpressionContainer occurs
  • There should be a reasonably-ergonomic offering for those developers who choose not to use Babel tooling

Brainstorming:

It would be nice if a developer could simply wrap their string and elements in a component which performs the localization:

<Interpolate>
	Check out this link to my <a href={ url }>website</a>.
</Interpolate>

There are, of course, complications:

  • How do we extract the string?
    • Presumably it would take the form Check out this link to my <a href="%s">website</a>. We'd need to find a way to associate the JSX expression to the placeholder (and vice-versa, inject the variable value where the placeholder occurs when rendering into the UI).
  • Pluralization / Context
  • Nested custom component elements
    • For example, what if the website link was a <WebsiteLink /> element?
  • Encouraging best practices to avoid extracted strings being indecipherable

It might be simpler to associate placeholders via a known limited subset of expression values allowed. This could also be used for linting enforcement of best-practices:

<Interpolate>
	{ ( getPlaceholder ) => (
		<Fragment>Check out this link to my <a href={ getPlaceholder( '%s' ) }>website</a>.</Fragment>
	) }
</Interpolate>

Aside: It is be feasible for a custom Babel plugin to transform an element like as written in the first snippet (which has arguably nicer semantics) to this latter format.

To address pluralization, perhaps having a special child component type mapping to the index of the plural form corresponding to the count (an advantage here is supporting >2 plural forms (correction below)):

<Interpolate count={ 2 }>
	<Interpolate.PluralForm>
		Check out this link to my <a href={ url }>website</a>.
	</Interpolate.PluralForm>
	<Interpolate.PluralForm>
		Check out this link to my <a href={ url }>websites</a>.
	</Interpolate.PluralForm>
</Interpolate>

cc @mcsf @jorgefilipecosta @swissspidy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

Internationalization (i18n)Issues or PRs related to internationalization efforts[Type] TaskIssues or PRs that have been broken down into an individual action to take

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions