Closed tricoder42 closed 5 years ago
@tricoder42 I felt a bit bad hijacking the thread too, but I didn't know where to take the conversation. This is good.
Reason 1: Using Trans component shifts responsibility from parent to Trans component itself. As a developer, you don't have to worry about skipped updates.
Well you shouldn't be skipping changes to the HOC passed translation props anyways, so this shouldn't pose an issue.
Second reason are inline components. Using Trans component you can go wild a do things like this:
You could theoretically return a html styled string from the function served by the translation (and then parse it) (instead of just strings, have something like:
{t["numberOfUsers"](users.length)}
...
{
en: {
numberOfUsers: (n) => `<b>${n}</b> people`
},
de: {
numberOfUsers: (n) => `personen: <b>${n}</b>`
}
}
). This would have the advantage of moving flexibility into the individual translations, and minimizing API surface area.
You get only one message. This is obvious, but other libs (i18next, formatjs) translate content of inline components separately which is unfortunate for translators.
What do you mean by this?
Any component is <0>valid0> here, even <1>custom1> ones.
But the translators have no idea what the tags are?
In future I'm planning further optimization, when message is rendered to React components tree and when props changes (like values, params) the message isn't parsed from scratch, but React handle update itself. Right now, message itself is cached, but inline components are rendered on each update.
could be solved by a memoized function
/ Message
<Trans>Hello World</Trans>
// becomes {'Hello World': 'Hello World'} for English
I like the way the contents automatically become the key
The method of using
<Trans></Trans>
everywhere encourages having big translation blocks. I'm not sure whether its best principles to keep them small or not, but I'd imagine so
Well you shouldn't be skipping changes to the HOC passed translation props anyways, so this shouldn't pose an issue.
The component skipping update might be high above the HOC. It might not even be aware of some i18n going on down in tree. This is common bug in formatjs
and I guess other libs too: You switch language, but not all translations are updated. So they recommend to reload the page, to avoid any problems, but it's unnecessary.
You could theoretically return a html styled string from the function served by the translation (and then parse it)
I'm working on sth similar. lingui-cli
will have a compile
method, which takes ICU message format and returns a function. In the app, you only pass a formatting function with all data in clojure, so you don't use messageformat parser in browser (slow, heavy). Then you can load compiled catalogs, save a few kb
and gain perf.
You get only one message. This is obvious, but other libs (i18next, formatjs) translate content of inline components separately which is unfortunate for translators.
What do you mean by this?
This is how does in react-intl
(taken from example in docs: https://github.com/yahoo/react-intl/wiki/Components#string-formatting-components):
<FormattedMessage
id='app.greeting'
description='Greeting to welcome the user to the app'
defaultMessage='Hello, {name}!'
values={{
name: <b>Eric</b>
}}
/>
They replace all inline components with variables and pass React component as a variable. So, either the Eric
isn't translated (not problem in this usecase), or you can pass another FormattedMessage
, but then you'll get two messages, instead of one:
<FormattedMessage
id='app.greeting'
defaultMessage='Read {more}'
values={{
more: <a href="/more"><FormattedMessage defaultMessage="more" /></a>
}}
/>
Any component is <0>valid0> here, even <1>custom1> ones.
But the translators have no idea what the tags are?
That's my concert too! I was thinking about <0:strong>
or <1:a>
, so the translator knows a bit what tag is there, but props are still kept away. Everything after :
is just a comment, the important is index of component. What do you think?
The method of using
everywhere encourages having big translation blocks. I'm not sure whether its best principles to keep them small or not, but I'd imagine so
This depends. I'm not a translator, but I think the smallest unit should be a paragraph. That's what holds the context. Some SaaS use sentence as the smallest unit. I'm not against it, but I guess that if you translate a paragraph, you only rarely do 1:1, sentence by sentence translation. In UI translatios it usually doesn't matter.
Definitely I don't what to translate Read <a>more</a>
as two separate words :) Never break a sentence into words.
The component skipping update might be high above the HOC. It might not even be aware of some i18n going on down in tree. This is common bug in formatjs and I guess other libs too: You switch language, but not all translations are updated. So they recommend to reload the page, to avoid any problems, but it's unnecessary.
I suppose this could be the case, but it seems you have written bad shouldComponentUpdate
methods if you are stopping rerender propagation for the entire subtree when the change in props does need to propagate.
They replace all inline components with variables and pass React component as a variable. So, either the Eric isn't translated (not problem in this usecase), or you can pass another FormattedMessage, but then you'll get two messages, instead of one:
Ah I see. Yes this is an eye sore and I don't want this kind of crap in my codebase.
That's my concert too! I was thinking about <0:strong> or <1:a>, so the translator knows a bit what tag is there, but props are still kept away. Everything after : is just a comment, the important is index of component. What do you think?
Well thats committing each translation to using the same HTML elements. It also seems like its adding ugly complexity. Occasionally you do want to "yield" and nest another component or translation within another - think popover on hover on a word in a paragraph - but that word could be located anywhere - this is why intl libraries are so damn complex.
At this point I may just bite the bullet and use a complex intl library
Also wouldn't the below code from your readme generate a new translation key for each value of name passed to it? - or are you doing this before runtime and stringifying the Trans contents?
// Variables
<Trans>Hello, my name is {name}</Trans>
I threw together a little proof of concept
https://github.com/DeedMob/react-local-translations
Will add dynamic language asap
I suppose this could be the case, but it seems you have written bad shouldComponentUpdate methods if you are stopping rerender propagation for the entire subtree when the change in props does need to propagate.
The problem is that language/messages are passed in context and only the last component receives it from HOC as props. Updates in context needs to be handled carefuly, as described here https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076#.mohyrubc7
Well thats committing each translation to using the same HTML elements. It also seems like its adding ugly complexity. Occasionally you do want to "yield" and nest another component or translation within another - think popover on hover on a word in a paragraph - but that word could be located anywhere - this is why intl libraries are so damn complex.
Yes, sometimes it makes sense and it's possible:
<Trans>
Click <Popover
header={i18n.t`Payment options`}
body={
<Trans>Please select payment options</Trans>
}>here</Popover> to set payment options.
</Trans>
This will result in three messages:
Each of them make sense on it's own. You can decouple messages from UI.
However, I never thought about a use case, where you use different components in different languages. Something like <0>Hello</0>
in English, but <1>Hola</1>
in Spanish, where 0
and 1
are different components. Technically it's possible with current solution, it's just not implemented yet.
Also wouldn't the below code from your readme generate a new translation key for each value of name passed to it? - or are you doing this before runtime and stringifying the Trans contents?
No, just one. You need to use babel-plugin-lingui-transform-react
or babel-plugin-lingui-transform-js
to make it happen. However, lingui-i18n
is based on template strings which are ES6 feature and lingui-react
is based on JSX, so you need babel anyway (in most cases).
The problem is that language/messages are passed in context and only the last component receives it from HOC as props. Updates in context needs to be handled carefuly, as described here https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076#.mohyrubc7
You're totally right. Thanks for telling me this, I haven't used React's contexts.
Yeah everyone will have babel anyways.
I have updated my mini localized components library to reflect this change and I'm quite happy with it. Demo @ https://deedmob.github.io/react-local-translations/example/index.html Of course I'm not sure how well it will perform, but it suits my needs quite well
@davidfurlong That's a good stuff! I'm a bit busy this week with other projects, but I'm going to push custom formats this weekend. I'll take a look at local catalogs after that.
Reg. user-provided versus autogenerated IDs: I'm wondering if by adding support for "description" prop you would end up with the best of both worlds. If you autogenerate IDs as a hash of description + default message, you will be able to avoid clashes between short default messages that mean different things in different contexts.
Also I wonder if it would make sense to create an "Argument" HOC that will have also have a description and which could be used to wrap child components - it would pass on its child but will add the description that will help translators understand what the child component is.
Actually what would be even more fun is using the jsdoc comments as descriptions for messages and message arguments.
Hello Ivan 👋
Actually what would be even more fun is using the jsdoc comments as descriptions for messages and message arguments.
Good point, I'm planning to add comments for translators, something like this:
const Label = () =>
// i18n: Heading at registration page
<h1><Trans>Registration</Trans></h1>
which would be extracted as:
{
"Registration": {
"description": "Heading at registration page",
"translation": ""
}
}
It could be probably possible to add description
prop directly to <Trans>
component, but it JS it would become more verbose: i18n.t('Registration', { description: "Heading at registration page" })
. I'm open to any ideas and PRs here!
Also I wonder if it would make sense to create an "Argument" HOC that will have also have a description and which could be used to wrap child components - it would pass on its child but will add the description that will help translators understand what the child component is.
Could you please provide a short example of this?
Thank you!
Hey Tomáš, yes, the comments approach sounds good, better than a prop. And as for arguments, if you have
<Trans>Abc<MyComponent /></Trans>
then as far as I understand, currently the translation file will just have a number for <MyComponent />
, and ideally you'd want to let the translators know what <MyComponent />
is, so I was thinking of ways to achieve that. Here too, a comment would be nicer than a wrapper component with a prop, but JSX doesn't support comments, so I can't think of some good approach off the top of my head...
I see. Maybe we could use description for it:
// i18n: <Link> - clickable element
<Trans>Link to <Link>documentation</Link></Trans>
and extracted description might be:
{
"Link to <0>documentation</0>": {
"description": "<0> - clickable element",
"translation": ""
}
}
What do you think?
Hmm... I wonder if this is general enough. For example there could be multiple child components with the same name but different props or different children. Maybe this would work, looks a bit ugly but it's the usual way to add comments in JSX:
<Trans>Link to {/* i18n: Clickable element */}<Link>documentation</Link></Trans>
Reason 1: Using
Trans
component shifts responsibility from parent toTrans
component itself. As a developer, you don't have to worry about skipped updates.Second reason are inline components. Using
Trans
component you can go wild a do things like this:<Trans> Any component is <strong className="green">valid</strong> here, even <Link>custom</Link> ones. </Trans>
Example above will result in single entry in your message catalog:
Any component is <0>valid</0> here, even <1>custom</1> ones.
This has several advantages:
- Translator doesn't care about html tags
- Props of inline components doesn't affect message
- You get only one message. This is obvious, but other libs (
i18next
,formatjs
) translate content of inline components separately which is unfortunate for translators.
According to i18next docs this doesn't seem to be up-to-date. In their example sandbox complex message extraction results into one piece.
@cJayyy Yeah, sorry, this issues was opened almost 2 years ago. Meanwhile, react-i18next
implemented similar concept with few exceptions. The main one is that they process JSX children at runtime - because of that you have to use double curly braces for variables. Unfortunately, object as a child
isn't a valid JSX so some linters might not like it.
I'm closing this issue, as it was mostly discussion and it's clearly outdated.
Just to make it clear, there're two i18n APIs:
lingui-i18n
, for vanilla javascript (i18n.t'Hello World'
). This works without React and it's a base for future integrations.lingui-react
, for React components (<Trans>Hello World</Trans>
) which under the hood useslingui-i18n
, so it's possible to use it for translation of attributes (<Trans title={i18n.t'Title'}>Hello World</Trans>
)Components vs. functions
React is all about rendering data. When data changes, React renders updates. Given example below:
when language or message catalog changes, the
span
(or probably parent component) is responsible for update, whileTrans
take care of it itself. When parent component is optimized usingshouldComponentUpdate
, theTrans
component will update the translated message even when parent skips update.Reason 1: Using
Trans
component shifts responsibility from parent toTrans
component itself. As a developer, you don't have to worry about skipped updates.Second reason are inline components. Using
Trans
component you can go wild a do things like this:Example above will result in single entry in your message catalog:
Any component is <0>valid</0> here, even <1>custom</1> ones.
This has several advantages:
i18next
,formatjs
) translate content of inline components separately which is unfortunate for translators.Reason 2: First-class support for inline components. As a developer, you can use JSX as you're used to. As a translator, you see the full message.
In future I'm planning further optimization, when message is rendered to React components tree and when props changes (like values, params) the message isn't parsed from scratch, but React handle update itself. Right now, message itself is cached, but inline components are rendered on each update.
Key vs. source language
Both approaches have pros & cons. I never settled to either solution. I'm trying to support both approaches:
I'm using second approach in examples because it's easier to start with.