-
Notifications
You must be signed in to change notification settings - Fork 37
Support non-literal translation keys for i18n #713
Comments
This is a feature we have been talking about adding for a long time. I don't think there is any downside in theory, its just a bit more complex than it seems so adding it will take some non trivial effort. |
Gotcha. Was the API you all had in mind similar to the one shown above?
Makes sense. If someone wanted to open a PR, what are the key hurdles they'd need to overcome in implementation? |
There is some communication between |
I think the constraint of key literals still makes sense for I'm imagining something like this: import {useTranslations} from "fusion-plugin-i18n-react";
function App(props) {
const [translate] = useTranslations("time.weekdays.*");
return <div>{translate(`time.weekdays.${props.id}`)}</div>
} Any translations that match the pattern passed to The key hurdle in the implementation is that all translation keys are not known at build time. With key string literals, it is trivial to mark specific translation keys as required. With patterns, these will have to be executed at runtime against the list of discovered translations (probably at server startup?). I think that modifying the babel plugin and/or core i18n library to do this shouldn't be super difficult, but it does involve a bit more complexity. |
Out of curiosity – what is the benefit of using a hook for this? Does it need to do things based on the component lifecycle? What will the implementation of Our team is approaching hooks with a healthy dose of skepticism (i.e. they are useful in some cases, but classes are still preferable in other cases). Since hooks aren't supported class components, it would be great to have a non-hooks solution too. I'm also trying to imagine how this would work when a component needs to translate a lot of different strings: function App(props) {
const [translate] = useTranslations("weekdays.*", "time_slots.*", "date_header", "date_subtitle", /* etc... */);
return (
<div>
<h2>{translate('date_header')}</h2>
<h4>{translate('date_subtitle')}</h4>
<p>{translate(`weekdays.${props.day}`)} {translate(`time_slots.${props.slot}`)}
</div>
); Seems like you either need to redundantly pass strings to |
There's not an immediate benefit, it's more about being future-proof. When React fully supports Suspense, a Hook/Component/HOC API will enable a much simpler loading mechanism based on Suspense, giving us the ability to have even more granular translation loading and potentially eliminating the requirement for build steps entirely.
I'm actually inclined to say mixing function App(props) {
const [translate] = useTranslations(["weekdays.*", "time_slots.*"]);
return (
<div>
<h2><Translate id="date_header" /></h2>
<h4><Translate id="date_subtitle" /></h4>
<p>{translate(`weekdays.${props.day}`)} {translate(`time_slots.${props.slot}`)}
</div>
); I don't think that this is that bad. In my opinion, it sort of makes sense that That said, there is another alternative that we haven't yet discussed: // Super static analysis
function App(props) {
const [translate] = useTranslations();
return (
<div>
<h2>{translate("date_header")}</h2>
<h4>{translate("date_subtitle")}</h4>
<p>{translate(`weekdays.${props.day}`)} {translate(`time_slots.${props.slot}`)}
</div>
); This is by far the simplest API, but involves more advanced static analysis, additional caveats, and potential footguns. Taking the exact above example, it is statically possible to determine the following translation key patterns, based on the (template) string literals passed to
This involves static analysis somewhat similar to how "dynamic" This is a pretty elegant solution but does involve a large potential footgun is the following scenario: function App(props) {
const [translate] = useTranslations();
return <div>{translate(props.foo)}</div>;
} There's nothing here to statically infer about the nature of the translation key. I think there are two reasonable ways of handling this:
I'm not sure I like either option. I think the first option would be sensible with either some lint rule or an explicit option passed to So either this: function App(props) {
const [translate] = useTranslations();
// eslint-disable-next-line fusion-plugin-all-translations
return <div>{translate(props.foo)}</div>;
} or function App(props) {
const [translate] = useTranslations({includeAll: true});
return <div>{translate(props.foo)}</div>;
} It is probably worth noting that we'd necessarily have an additional constraint disallowing usage of the function App(props) {
const [translate] = useTranslations();
// Once bound, translate can only be used in call expressions.
const renamed = translate; // Error, no rebinding allowed
const result = someHelper(translate); // Error, no passing as an argument
return <div>{renamed("foo")}</div>;
} This ensure the static analysis isn't defeated. Of course, hooks are not strictly required. But I would definitely have concerns regarding a comment-based API. I think if there's a scenario where removing a comment ends up breaking your app, this is would be quite counterintuitive and confusing. So for that reason, I'm generally opposed to comment-based APIs, as comments should not, in theory, affect code at runtime at all. So barring some comment annotation, there needs to be a way to indicate certain translations should be preloaded. This could have many different APIs, but perhaps the simplest being: import { preloadKeys } from "fusion-plugin-i18n";
preloadKeys(["foo.*", "bar.*"]);
function App({ foo, bar }) {
return (
<div>
<Translate id={`foo.${props.foo}`} />
<Translate id={`bar.${props.bar}`} />
</div>
);
} I think something like this would also be acceptable, but I think less preferable to the hook-based approaches suggested above. |
Appreciate the thorough reply. Agree that hooks can provide a better devexp compared to HOCs/render props in many cases. There's still significant disagreement among engineers I've talked to about if and when they should be used, so just wanted to raise a flag before we rush into a hooks-only API. I actually quite like the Regarding the alternate |
Just wanted to add support to this request for I detailed our team's issue here. Basically, a simple use case like Looking forward to a potential solution |
Type of issue
Feature request
Problem
fusion-plugin-i18n currently only works with string literals:
This is because there is a custom babel plugin that extracts these strings at build time.
This limitation, however, leads to some very verbose and bloated logic in some cases. For example, when pulling translations for days of the week our old approach:
now turns into the following with Fusion
The custom babel transform is awesome and seems necessary for efficient code splitting, but I'm hoping there's a strategy which gives us the best of both worlds.
Potential Solution
One solution that comes to mind is a comment that acts as a directive to the i18n babel transform.
The transform would parse the comment and match it against keys found in the translation files. A asterisk wildcard match is probably sufficient, but in theory this could also be a regex if people wanted more flexibility.
This solution seems to offer the benefits of code splitting, without runtime overhead and code bloat.
Thoughts?
The text was updated successfully, but these errors were encountered: