-
Notifications
You must be signed in to change notification settings - Fork 12.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
I18n: Migrate to I18next #55845
I18n: Migrate to I18next #55845
Conversation
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/35174 |
7fa249d
to
686bae1
Compare
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/35589 |
357e429
to
a9e5687
Compare
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/35605 |
export const t = (id: string, defaultMessage: string) => { | ||
initI18n(); | ||
return i18next.t(id, defaultMessage); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will add a lint rule to ensure that people import these components when translating grafana-ui
385fff5
to
ba15db8
Compare
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/35670 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all looks really good, nice job in switching all those translated places from lingui to i18next!
I've got one question though, it seems like we are importing things from various different places, like:
import { Trans } from 'react-i18next';
import { t } from 'app/core/internationalization';
import { t } from 'i18next';
import { useTranslation } from 'react-i18next';
and then const { t } = useTranslation();
import { t, Trans } from '../../../utils/i18n';
when in grafana-ui
Is there a reason why we can't use the util from grafana-ui
or a similar one everywhere?
@@ -0,0 +1,17 @@ | |||
const fs = require('fs/promises'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a typo on this file name psuedo.js
@@ -353,7 +359,7 @@ const getEmptyListStyles = stylesFactory((theme: GrafanaTheme2) => { | |||
a, | |||
span { | |||
font-size: 13px; | |||
} | |||
}\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this intended? 🤔
yup - good catch @JoaoSilvaGrafana. There's still a bit of tidy-up work i need to do (i switched where i was importing half way through updating all the lingui stuff), but I was wondering the exact same thing as you - can we centralize all the i18n setup and imports into grafana/ui? I would probably aim to move all of My one thought/concern is whether this would create some awkward version dependency things for us. @jackw do you have any thoughts on this? |
loadLocalePromise, | ||
|
||
// Preload selected app plugins | ||
await preloadPlugins(config.pluginsToPreload), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to use await for this promise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no! good catch!
@mckn all three of these questions (and my answers) are tightly related. The main thing I'm optimising for here are the many other developers (at grafana and in the community!) who get subjected to this when developing UIs to support translation. The harder we make it to support translation, the worse of a job they will do and the more inconsistent i18n would be in Grafana. The goal is to make translating grafana as simple as possible, using workflows as close to their current one, so more of Grafana is translated in a higher quality. We should use tooling to automate the busy work to let developers focus on the real stuff.
The justification for this is tricky. Because we statically extract the strings (with both lingui and i18next), introducing a wrapper component complicates this. i18next-parser is less strict than lingui's and it only looks at the name of the function/component, but still the name of the props are important. However, as Joao mentioned in his comment, the imports in this PR are inconsistent and need to be standardised. I think what I'm going to do is to create wrapper components in grafana core, the same as grafana-ui, and use lint rules to enforce that the correct imports are used. I'm extremely hesitant to put too much of the actual i18n framework in grafana-ui - I'm cautious to create weird version dependencies issues between grafana and grafana-ui, and plugins (somehow).
I agree. Having english phrases in multiple places is a no-go. The goal I have in mind (which would probably do in the next step to prepare for integrating with crowdin) would be that the english messages.json file would be empty, and the english phrases would only be in the source components. I think this is the easiest model for developers - phrases are where they expect them (they're in-situ, just as if there's no translation) and they don't need to manually add keys and messages into another file. What's your thoughts on that - i think it resolves that concern directly?
For context - key/phrase reuse is discouraged. The public contributing docs mention this very passively in passing (needs to be fixed!), but from previous conversations we established the rule of thumb to not reuse a phrase. At the moment, because messages are statically extracted from the call site, the call site is the source of truth for translation keys. We don't need to typecheck this because it's impossible for it to be wrong. Most of the time when you write a component, the key won't exist yet, so if we were typechecking this the default experience would be type errors, which I think isn't ideal. I think statically extracting phrases from the source code is the best approach. It's easier for developers and removes busy work from the process of creating new UI that supports i18n. It also ensures translations are most correct and error free, in regard to interpolation. When we use static extraction, it makes sure the extracted phrase in all locale's messages catalogues have the correct variable name and component order: <Trans i18nKey="page.title">Hello {{username}}</Trans>
// extracted to messages.json
{
"page.title": "Hello {{username}}",
} If we were added these phrases into the JSON manually, we would need to make sure that the variable name pass in the values object matches the phrase in all the message catalogues. Remember, these are all a bunch of JSON files, so there's no great way to typecheck/validate the contents of all the phrases: <Trans i18nKey="page.title" values={{username}} />
// manually update messages.json
{
"page.title": "Hello {{username}}", // make sure the username variable matches the source across all locales
} This gets even trickier when doing JSX interpolation <Trans
i18nKey="page.title"
values={{ username }}
components={[<strong />, <em />]} // make sure these components are in the correct order as the phrases across all locales
/>
// manually update messages.json
{
"page.title": "<0>Hello</0> <1>{{username}}</1>", // make sure the username variable matches the source across all locales, ane
} If we don't automate this by statically extracting the phrases, I think we will have a much higher chance of incorrect and inconsistent translations. I think statically extracting message keys and strings from our source gives us the most consistency and correctness. Thoughts @mckn? |
Isn't this gonna be tricky as anything in grafana-ui is public? 🤔
Not sure this will create an awkward version dependency. Maybe I'm missing something so please expand on that if you think it's a concern. We need to make sure that there is only ever one instance running otherwise I think we'll lose the context, right? |
@jackw I would put it in grafana-ui as a "private api" (if such exists - just not export it from the root index.js?) where we would not support it and have breaking changes at any time. I can't think of any specific problems - on the surface it seems fine, but I know how we've had related headaches to this before so I'm trying to be very cautious around this! |
ba15db8
to
887c6e0
Compare
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/36107 |
c66c92f
to
cbf5ab5
Compare
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/36129 |
Hmmm, the There's a rather blunt fix/workaround to use module augmentation to use the old HTMLAttribute children type, or we would have to fall back to the alternate syntax which is a bit more problematic due to the reasons i mentioned in my above comment. The more this goes on, the more I'm feeling that we're putting too much weight on simplifying a build tooling issue by sacrificing ergonomics for all the developers. Like @academo said:
|
Yes, when I'm thinking about this I really like the approach here! Great stuff! One thing that I was thinking about regarding the utility functions in |
@mckn Requiring a provider to use Grafana-UI would be a breaking change, where the goal of translating grafana-ui is to do it in a way that is not a breaking change. I see no benefit from using a provider at the moment. |
Yeah - makes sense but at the same time you won't be able to use the localization functionality of the grafana/ui components. But that might be ok until we have made som more progress on this. |
@mckn Yup at the moment we are not supporting external consumers (gcom) to use the translations. At the moment these are only available within Grafana. I don't think this takes us down any dead ends that would make supporting this difficult in the future, and the wrappers in app/core/i18n and grafana-ui/utils/i18n would probably make this easier for us later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems pretty similar to Lingui so I'd say easy to use from a developer's perspective. Only thing I'm curious about is how are plurals done in i18next, but regardless of that I'd say we go with the workaround to get this to work with React 18 and it looks good to me otherwise 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🚀 . I have pretty much the same feedback as @JoaoSilvaGrafana also the comparison document is fair regarding the tradeoffs between using lingui over react-i18n.
cbf5ab5
to
a6908e2
Compare
a6908e2
to
6681872
Compare
Drone build failed: https://drone.grafana.net/grafana/grafana-enterprise/36514 |
6681872
to
ffe075a
Compare
What this PR does / why we need it:
The outcome from the work done on #55300 was that we want to switch to a translation library that has less dependencies on our build tooling.
This PR switches Grafana to I18next to remove our dependency on babel-macros. The tradeoff is:
<Trans>
with React 18 type definitions i18next/react-i18next#1483messages.json
message catalogues. I don't think this is worth it.Which issue(s) this PR fixes:
Part of #56303
Special notes for your reviewer:
Many files are changed in this PR to switch messages from lingui to i18next, but I've tried to make the list of commits more approachable way to review this. The main changes to scrutinise is the first "Switch from lingui from i18next" commit
TODO:
t()
template strings are using the correct i18next interpolation syntaxHow do we compile the messages .json files? 'default phrase' will remain in the source code, so do we just not need to load en-us messages? Should we not commit the en-us messages json? How will crowdin get the english messages?In later PRs we will