{"action":"created","issue":{"url":"https://api.github.com/repos/WordPress/gutenberg/issues/9846","repository_url":"https://api.github.com/repos/WordPress/gutenberg","labels_url":"https://api.github.com/repos/WordPress/gutenberg/issues/9846/labels{/name}","comments_url":"https://api.github.com/repos/WordPress/gutenberg/issues/9846/comments","events_url":"https://api.github.com/repos/WordPress/gutenberg/issues/9846/events","html_url":"https://github.com/WordPress/gutenberg/issues/9846","id":359631989,"node_id":"MDU6SXNzdWUzNTk2MzE5ODk=","number":9846,"title":"i18n: Implement a solution for interpolation of React elements in strings","user":{"login":"aduth","id":1779930,"node_id":"MDQ6VXNlcjE3Nzk5MzA=","avatar_url":"https://avatars1.githubusercontent.com/u/1779930?v=4","gravatar_id":"","url":"https://api.github.com/users/aduth","html_url":"https://github.com/aduth","followers_url":"https://api.github.com/users/aduth/followers","following_url":"https://api.github.com/users/aduth/following{/other_user}","gists_url":"https://api.github.com/users/aduth/gists{/gist_id}","starred_url":"https://api.github.com/users/aduth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aduth/subscriptions","organizations_url":"https://api.github.com/users/aduth/orgs","repos_url":"https://api.github.com/users/aduth/repos","events_url":"https://api.github.com/users/aduth/events{/privacy}","received_events_url":"https://api.github.com/users/aduth/received_events","type":"User","site_admin":false},"labels":[{"id":596965480,"node_id":"MDU6TGFiZWw1OTY5NjU0ODA=","url":"https://api.github.com/repos/WordPress/gutenberg/labels/Internationalization%20(i18n)","name":"Internationalization (i18n)","color":"655104","default":false},{"id":531680824,"node_id":"MDU6TGFiZWw1MzE2ODA4MjQ=","url":"https://api.github.com/repos/WordPress/gutenberg/labels/[Type]%20Task","name":"[Type] Task","color":"fef2c0","default":false}],"state":"open","locked":false,"assignee":{"login":"nerrad","id":1429108,"node_id":"MDQ6VXNlcjE0MjkxMDg=","avatar_url":"https://avatars1.githubusercontent.com/u/1429108?v=4","gravatar_id":"","url":"https://api.github.com/users/nerrad","html_url":"https://github.com/nerrad","followers_url":"https://api.github.com/users/nerrad/followers","following_url":"https://api.github.com/users/nerrad/following{/other_user}","gists_url":"https://api.github.com/users/nerrad/gists{/gist_id}","starred_url":"https://api.github.com/users/nerrad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nerrad/subscriptions","organizations_url":"https://api.github.com/users/nerrad/orgs","repos_url":"https://api.github.com/users/nerrad/repos","events_url":"https://api.github.com/users/nerrad/events{/privacy}","received_events_url":"https://api.github.com/users/nerrad/received_events","type":"User","site_admin":false},"assignees":[{"login":"nerrad","id":1429108,"node_id":"MDQ6VXNlcjE0MjkxMDg=","avatar_url":"https://avatars1.githubusercontent.com/u/1429108?v=4","gravatar_id":"","url":"https://api.github.com/users/nerrad","html_url":"https://github.com/nerrad","followers_url":"https://api.github.com/users/nerrad/followers","following_url":"https://api.github.com/users/nerrad/following{/other_user}","gists_url":"https://api.github.com/users/nerrad/gists{/gist_id}","starred_url":"https://api.github.com/users/nerrad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nerrad/subscriptions","organizations_url":"https://api.github.com/users/nerrad/orgs","repos_url":"https://api.github.com/users/nerrad/repos","events_url":"https://api.github.com/users/nerrad/events{/privacy}","received_events_url":"https://api.github.com/users/nerrad/received_events","type":"User","site_admin":false}],"milestone":null,"comments":12,"created_at":"2018-09-12T20:05:29Z","updated_at":"2019-06-16T21:38:04Z","closed_at":null,"author_association":"MEMBER","body":"**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](https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml), which is discouraged. This can sometimes lead to developers implementing workarounds using concatenation of partial localized strings (e.g. https://github.com/WordPress/gutenberg/pull/5767#discussion_r217068976), which is not advisable as it cannot be translated accurately in many languages. This is also noted in the [i18n for WordPress Developers](https://codex.wordpress.org/I18n_for_WordPress_Developers) guide:\r\n\r\n>Use format strings instead of string concatenation—`sprintf(__('Replace %1$s with %2$s'), $a, $b);` is always better than `__('Replace ').$a.__(' with ').$b;` .\r\n\r\nhttps://codex.wordpress.org/I18n_for_WordPress_Developers#Best_Practices\r\n\r\n**Task:** Find a way to allow for React elements to be inserted within localized strings safely.\r\n\r\n**Prior Art:**\r\n\r\n- https://github.com/martinandert/react-interpolate-component\r\n- https://github.com/Automattic/interpolate-components (Used by Calypso / WordPress.com)\r\n- https://react.i18next.com/components/interpolate\r\n- https://react.i18next.com/components/trans-component\r\n\r\n**Additional Considerations:**\r\n\r\n- We still need to be able to extract the string which would be surfaced to translators, presumably including [placeholders](https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification) where a [`JSXExpressionContainer`](https://babeljs.io/docs/en/next/babel-types.html#jsxexpressioncontainer) occurs\r\n- There should be a reasonably-ergonomic offering for those developers who choose not to use Babel tooling\r\n\r\n**Brainstorming:**\r\n\r\nIt would be nice if a developer could simply wrap their string and elements in a component which performs the localization:\r\n\r\n```jsx\r\n<Interpolate>\r\n\tCheck out this link to my <a href={ url }>website</a>.\r\n</Interpolate>\r\n```\r\n\r\nThere are, of course, complications:\r\n\r\n- How do we extract the string?\r\n   - 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).\r\n- Pluralization / Context\r\n- Nested custom component elements\r\n   - For example, what if the website link was a `<WebsiteLink />` element?\r\n- Encouraging best practices to avoid extracted strings being indecipherable \r\n\r\nIt 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:\r\n\r\n```jsx\r\n<Interpolate>\r\n\t{ ( getPlaceholder ) => (\r\n\t\t<Fragment>Check out this link to my <a href={ getPlaceholder( '%s' ) }>website</a>.</Fragment>\r\n\t) }\r\n</Interpolate>\r\n```\r\n\r\nAside: 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.\r\n\r\nTo 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](https://github.com/WordPress/gutenberg/issues/9846#issuecomment-422405570))):\r\n\r\n```jsx\r\n\r\n<Interpolate count={ 2 }>\r\n\t<Interpolate.PluralForm>\r\n\t\tCheck out this link to my <a href={ url }>website</a>.\r\n\t</Interpolate.PluralForm>\r\n\t<Interpolate.PluralForm>\r\n\t\tCheck out this link to my <a href={ url }>websites</a>.\r\n\t</Interpolate.PluralForm>\r\n</Interpolate>\r\n```\r\n\r\ncc @mcsf @jorgefilipecosta @swissspidy "},"comment":{"url":"https://api.github.com/repos/WordPress/gutenberg/issues/comments/502487831","html_url":"https://github.com/WordPress/gutenberg/issues/9846#issuecomment-502487831","issue_url":"https://api.github.com/repos/WordPress/gutenberg/issues/9846","id":502487831,"node_id":"MDEyOklzc3VlQ29tbWVudDUwMjQ4NzgzMQ==","user":{"login":"nerrad","id":1429108,"node_id":"MDQ6VXNlcjE0MjkxMDg=","avatar_url":"https://avatars1.githubusercontent.com/u/1429108?v=4","gravatar_id":"","url":"https://api.github.com/users/nerrad","html_url":"https://github.com/nerrad","followers_url":"https://api.github.com/users/nerrad/followers","following_url":"https://api.github.com/users/nerrad/following{/other_user}","gists_url":"https://api.github.com/users/nerrad/gists{/gist_id}","starred_url":"https://api.github.com/users/nerrad/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nerrad/subscriptions","organizations_url":"https://api.github.com/users/nerrad/orgs","repos_url":"https://api.github.com/users/nerrad/repos","events_url":"https://api.github.com/users/nerrad/events{/privacy}","received_events_url":"https://api.github.com/users/nerrad/received_events","type":"User","site_admin":false},"created_at":"2019-06-16T21:38:04Z","updated_at":"2019-06-16T21:38:04Z","author_association":"CONTRIBUTOR","body":"I've been thinking/experimenting on this a bit over the weekend and I have some more thoughts to throw up here.\r\n\r\nSo one thing I'm thinking of playing with is introducing a new tokens to represent open/close type of elements.  So for example, open would be `%1$o` and close would be `%1$c`.  In a string it might something like this (note `1` here means that the first argument is used for both opening and closing tags):\r\n\r\n```js\r\neprintf( __( 'Check out this link to my %1$owebsite%1$c' ), 'a'  );\r\n```\r\nBehind the scenes, the code would take care of creating the element attached to the token (in this case 'a') and adding the content between the `o` and `c` for that argument number as the child on the element.  So the children array for the above might end up with something like this:\r\n\r\n```js\r\nconst children = [\r\n    'Check out this link to my ',\r\n    wp.createElement( 'a', {}, 'website' )\r\n];\r\n```\r\n\r\nSome benefits:\r\n\r\n- String extraction will still work\r\n- It allows for keeping translation of content inside the element in context.\r\n- It's a somewhat familiar api for developers to use (albeit there's still some syntax issues that would need communicated).\r\n\r\nThe main question I have here though is how would translators handle the introduction of what on the surface seems unfamiliar tokens.  @swissspidy any thoughts on that?"}}
{"actor":{"display_login":"nerrad"}}