I would like to abstract the use of <Trans />.
e.g.
const TextButton1 = ({ children }) => (
<button>
<Trans>{children}</Trans>
</button>
);
const TextButton2 = ({ children }) => <Trans render="button">{children}</Trans>;
// used in somewhere else
<TextButton1>some message id<TextButton1>
<TextButton2>some other message id<TextButton2>
In this way, I don't need to import Trans in every single file that is using the button component. However, lingui extract cannot extract properly.
I need to do
<TextButton1>{i18nMark('some message id')}<TextButton1>
<TextButton2>{i18nMark('some other message id')}<TextButton2>
if I want the extract works. However, in this way I need to import i18nMark in every single file instead which leads to same problem that I want to avoid at first.
Any suggestion?
Hey @chochihim,
this isn't possible at the moment. @FredyC had a similar request few weeks ago, so we might revise this in the future, but first we need to unify @lingui/macro with both transform plugin. Then allow @lingui/macro to be used standalone as a plugin and finally add an option for implicit imports with aliases.
Something similar to this, although we still need to figure out the final form:
{
"plugins": [
["@lingui/macro", {
"imports": {
"Trans": ["TextButton1", "TextButton2"]
}
}]
]
}
I already tried to solve similar issue in #284 (allow Trans component transformation/extraction without explicit import) but I didn't realize that users might want to rename trans components as well.
This could be even released in minor version bump, because @lingu/macro can't be used standalone at the moment.
Unfortunately, I'm not going to focus on this in upcoming weeks. If you want to dive into this, I'm happy to help, otherwise I might take a look at it after React Conf.
@chochihim You are my kind of guy. I am also rather lazy writing these things all over the place... but I don't think there is an easy way to solve this. Also, you are mixing two things together. If you use <Trans> then you don't need to worry about i18nMark at all.
I am afraid your idea is probably impossible simply because extractor can only statically analyze the code and what you have here is some runtime code that needs to be executed first and it would not really work.
Just for your information, there is a new and hot release with macros. Personally, It won't help with this use case, but it's a little bit nicer.
Edit: Darn it, @tricoder42 was faster for a few seconds :D
I am not sure if @tricoder42's suggestion is the way to go. Imagine I create another component FancyTextButton:
const FancyTextButton = ({ children }) => (
<TextButton1>
<SomeFancyComponent />
{children}
</TextButton1>
);
// to be used as
<FancyTextButton>message id</FancyTextButton>
Does that mean I need to amend my config every time I create a new component that compose a base component? I am using styling solution like styled-components and I will create many of these components.
I have tested further and figured out way that seems working for my use case:
const TextButton = ({ children }) => {
return (
<button>
<Trans id={children} />
</button>
);
};
//
<TextButton>Some Message Id</TextButton>
However, in this way, I cannot use cli to extract. I need to manually add entry to messages.json for each locale. But the problem is running cli to extract will override it.
Is letting users manually define messages which are not to be overridden when using the cli a possible solution?
Yeah, as @FredyC, lingui extract statically analyses source code and searches for
Why can't you write: <FancyTextButton><Trans>message id</Trans></FancyTextButton>? After all, FancyTextButton manages visual style, Trans deals with internationalization.
What problem are you trying to solve? You don't want to import Trans component everywhere? Or you don't want to wrap your translatable messages? The solution I proposed solves the first problem. I never imagined that wrapping texts in <Trans> component might be a problem. After all, the whole library is build around making this as easy as possible.
We might add support for adding manual messages, once #326 is finished, but it feels like a very fragile solution. You'll loose the benefit of automatically extracted messages, automatically cleaned catalog (in case you remove message) and suddenly you'll have two sources of truth: source code and message catalog. You can also do it even now: just create an extra file and fill it with i18nMark calls. That way the extract command will collect your messages.
First of all, yes I don't want to import Trans component everywhere. The solution you proposed works. My only concern is that I need to change the aliases array every time I create a component which is cumbersome to me (Given that it is quite often to create a new component just for some style twisting by using CSS-in-JS library).
Secondly, I am thinking if it is possible to move the Trans component up the component tree. Think of a bit more complex component:
const Card = ({ title, description }) => (
<div>
<h2>{title}</h2>
<p>{description}</p>
</div>
)
What should I do if I want to translate both title and description? The API in my mind is:
const Card = ({ title, description }) => (
<div>
<h2><Trans>{title}</Trans></h2>
<p><Trans>{description}</Trans></p>
</div>
);
// usage
<Card title="title message id" description="description message id" />
In this way, consumers of Card do not need to bother the usage of translation. What do you think?
Anyway I am still learning about using this library. Please forgive If I make any mistake about the usage.
First of all, yes I don't want to import Trans component everywhere. The solution you proposed works. My only concern is that I need to change the aliases array every time I create a component which is cumbersome to me (Given that it is quite often to create a new component just for some style twisting by using CSS-in-JS library).
I understand that. I didn't understand your intentions for the first time, but now it makes sense.
Secondly, I am thinking if it is possible to move the Trans component up the component tree.
You have two options, but both require to use either Trans or I18n component:
First, pass Trans components:
const Card = ({ title, description }) => (
<div>
<h2>{title}</h2>
<p>{description}</p>
</div>
);
// usage
<Card
title={<Trans>title message id</Trans>}
description={<Trans>description message id</Trans>}
/>
Second, use I18n render prop component (this also works if you need to pass translated messages to html attributes, like href, title, etc):
const Card = ({ title, description }) => (
<div>
<h2>{title}</h2>
<p>{description}</p>
</div>
);
// usage
import { I18n } from "@lingui/react"
import { t } from "@lingui/macro"
<I18n>
{({ i18n }) => (
<Card
title={i18n._(t`title message id`)}
description={i18n._(t`description message id`)}
/>
)}
</I18n>
It may look cumbersone to wrap all messages either in Trans or i18n._, but defining messages in code is only the half of internationalization. You need to have a workflow which extracts messages to external file and loads translations.
You could bypass it by passing title or description prop directly to id prop of Trans component, but then you're on your own - you need to define message manually and keep it updated. In large projects it soon might become very error prone.
I agree with @tricoder42 that doing <FancyTextButton><Trans>message id</Trans></FancyTextButton> is more of React thinking approach of composition. You have a component that does some styling and it shouldn't necessarily care about translation. It's kinda against a single responsibility principle.
The approach with passing <Trans> directly in a prop is great, but bit more problematic if you are using some 3rd party components that are not able to accept ReactNode instead of a string. Got burned by that many times already. To keep my sanity in check I've decided that all props will use i18n form so I don't need to worry about this issue all the time.
I am curious, why is it such a huge deal to avoid extra import? It does not cost anything except longer source code by one line. With macros in place, you are even enforced to explicitly say where is that variable coming from (and that's a good thing imo).
And side note, with VSCode you can have a pretty decent auto-import feature. It's even more powerful with TypeScript based project. I don't really think about imports in my projects, because everything is auto-imported and then even auto-sorted by rules I've specified. This works really well and it's consistent across the team.
Thank you for all the advices! I agree with @tricoder42 that workflow of extracting messages is required. I guess I will stick with wrapping my translatable messages in Trans or using I18n with macro.
Closing as won't fix. I can't imagine at the moment how this could be done (probably by extending macros even further?), but if anyone has suggestion, feel free to send a PR 馃憤
Most helpful comment
@chochihim You are my kind of guy. I am also rather lazy writing these things all over the place... but I don't think there is an easy way to solve this. Also, you are mixing two things together. If you use
<Trans>then you don't need to worry abouti18nMarkat all.I am afraid your idea is probably impossible simply because extractor can only statically analyze the code and what you have here is some runtime code that needs to be executed first and it would not really work.
Just for your information, there is a new and hot release with macros. Personally, It won't help with this use case, but it's a little bit nicer.
Edit: Darn it, @tricoder42 was faster for a few seconds :D