-
Notifications
You must be signed in to change notification settings - Fork 152
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
[FF] Setup React intl #9449
[FF] Setup React intl #9449
Conversation
Could you leave some info on these dependencies and why they were chosen (vs possible alternatives)? Like what's with this compile step? |
@@ -149,7 +150,7 @@ export const NavBar: React.FC = track( | |||
flex={1} | |||
size="small" | |||
> | |||
Sign Up | |||
<FormattedMessage id="navbar.signup" /> |
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.
Is there type support (like do these ids autocomplete)?
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.
if this doesnt exist, having a hook or a global func could totally solve this with TS.
const t = useTranslations()
return (
<Text>
t("navbar.signup")
</Text>
)
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.
https://formatjs.io/docs/react-intl/api/
https://formatjs.io/docs/react-intl/components#why-components
It seems like this lib does a lot and tries to be a do-all solution for text formatting. Which 🤷
I'm not sure why the hook wouldn't "Provide TypeScript type definitions" though?
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.
m indeed. we can at least do what we do with palette tokens, and wrap their func in our func, and add types based on our json translation files 🤔
src/v2/compiled-lang/en-us.json
Outdated
@@ -0,0 +1,98 @@ | |||
{ | |||
"navbar.artists": [ |
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.
whats the diff of the lang
and compiled-lang
files? do we need both?
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.
The ones in lang
are the ones added by us.
In case we also adopt a third-party translation tool, these are the ones that are synced between our project and the tool.
The ones in compiled-lang
are the ones that are actually consumed by the application.
More on this can be found here:
src/v2/Apps/lang/en-us.json
Outdated
@@ -0,0 +1,17 @@ | |||
{ |
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.
should we merge this file with the other one outside Apps/
?
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.
I think this should live in v2/System/i18n/lang
(or we should discuss further per this comment)
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.
I moved this to v2/System/i18n/lang
for now, but I can change this in case we decide we really want to have multiple locale files
@@ -149,7 +150,7 @@ export const NavBar: React.FC = track( | |||
flex={1} | |||
size="small" | |||
> | |||
Sign Up | |||
<FormattedMessage id="navbar.signup" /> |
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.
i still feel very strongly that this should not look like <FormattedMessage id="navbar.signup" />
, and instead should look like t("navbar.signup")
or something very very minimal.
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.
Yeah, this is kinda confusing. There's a hook that can be used which seems nicer.
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.
Agree with @pvinis! Being locked into an react component for this can be limiting here since we're often using logic in plain js -- ` foo ? "hello" : "world". That kind of thing.
But that said, i could be convinced that a react component like this is indeed sufficient. Worth a bit of discussion.
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.
I left this comment on the wrong parent thread:
https://formatjs.io/docs/react-intl/api/
https://formatjs.io/docs/react-intl/components#why-components
I'm wondering about (and scared of) what their date formatting does.
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.
An implementation like this doesn't seem to be this straightforward with react-intl
, but it's the natural way to go in react-i18next
.
There is this other PR here with an attempt of using react-i18next
: #9797
added some comments, none of them blocking, mostly suggestions and questions. |
src/v2/Apps/Components/AppShell.tsx
Outdated
@@ -18,6 +18,8 @@ import { AppToasts } from "./AppToasts" | |||
import { useNavBarHeight } from "v2/Components/NavBar/useNavBarHeight" | |||
import { useProductionEnvironmentWarning } from "v2/Utils/Hooks/useProductionEnvironmentWarning" | |||
import { useAuthValidation } from "v2/Utils/Hooks/useAuthValidation" | |||
import { IntlProvider } from "react-intl" | |||
import locale_en_us from "../../compiled-lang/en-us.json" |
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.
Follow-up: I'm curious what including all of the different languages would amount to file-size-wise. In the future we should probably code-split via import()
and react-loadable
as we do with our other split components as seen here.
src/v2/System/SystemContext.tsx
Outdated
<SystemContext.Provider value={providerValues}> | ||
{children} | ||
</SystemContext.Provider> | ||
</IntlProvider> |
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 can be removed in favor of the Boot suggestion up above
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.
Although we will be using system context for triggering updates to the language, but that's separate from this.
const { setLanguage } = useSystemContext()
...
<Select values={languages} onChange={lang => setLanguage(lang) />
That kind of thing.
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.
I made the changes to create an InternationalizationProvider
and I'm importing it in Boot
, but it's still not really clear to me how can I remove this from SystemContext
without affecting the tests... 🤔
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.
It might impact the tests, but in general all of our react context providers live in boot since that's the root of our application. For tests, if we need access to certain providers, we can wrap the test in <MockBoot>
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 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 started to happen after removing IntlProvider
from SystemContext
because /terms
does not rely on Boot
. As we can see in the .jade
files, we use some stich components, which replicate parts of the components using on static pages like these.
In order to make this work, we should also add the IntlProvider
to the components in the stitch_components directory (components that can't benefit from Boot
)
src/v2/lang/en-us.json
Outdated
@@ -0,0 +1,50 @@ | |||
{ |
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.
Wondering what other's think, but should we keep all per-language translations in one single file? I can imagine it getting messy, but at the same time I can also imagine it being functionally easier to use add to and revise, all things considered.
Another option would be to follow the pattern that we've got setup for tests and which everyone is familiar with and add folder-level __i18n__
. So like:
├── Bar.tsx
├── Foo.tsx
├── __i18n__
│ ├── Bar.i18n.json
│ └── Foo.i18n.json
└── __tests__
├── Bar.test.tsx
└── Foo.test.tsx
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.
PR is looking awesome @lidimayra 💯
Can we add a testing example to this? I'm still uncertain about how that side of things will work, if we were to add intel and then run our existing tests.
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.
Also echoing @dzucconi, I too am curious about alternative libs and so on. I know React-intl has been around the longest and has a lot users, but what else is out there? We might be able to get away with something simpler that doesn't require a compiler, json
files and so on.
Totally naive implementation without understanding any of the underlying gotchas:
const content = {
en_us: {
nav: {
about: "About"
...
}
}
}
const useContent = () => {
const { lang } = useSystemContext()
return {
content: content[lang]
}
}
const MyComponent = () => {
const { content } = useContent()
return (
<>
<Text>{content.nav.about}</Text>
</>
)
}
We'd get instant typescript support with no effort, and everything is pretty straightforward.
Again, I'm not an i18n expert, but I do wonder what's strictly needed for the scope of our site.
@damassi Right, this is exactly what I was wondering. Seems like total overkill for our purposes but maybe I'm missing something? |
Further, we could bundle split out that |
@damassi we'd be able to chose the bundle at runtime though? I suppose you could tie this to routing (e.g. |
Yup, we would. The thing with loadable components (which matches how our route file imports work) is that it's only loaded on demand. For example: const initLang = (lang) => {
const content = {
en_us: () => loadable(
() => import(/* webpackChunkName: "i18n-en_us" */ "./lang/en_us"),
),
es_ty: () => loadable(
() => import(/* webpackChunkName: "i18n-es_ty" */ "./lang/es_ty"),
)
}
return {
load: content[lang]
}
}
const content = await initLang('en_us').load() Roughly, something like this would do it, and we could toggle langs similarly. |
Another thing I woke up thinking about that I can't quite shake is this supposed need for multiple language files, which will be hard to keep in sync (maybe thats what formatjs does? validate and scan all lang content files and evaluate subtle differences?). But that got me thinking of other possible structures if we were to go with a lightweight internal i18n lib: const content = {
nav: {
home: {
en_us: "Home",
es_es: "Casa",
ab_cd: "AAfuenf"
}
}
} So in this case, there's only one single master graph for everything which eliminates any potential for drift, and if we need to add a new translation we just update the leaves of each word branch with a new language key / translation. We could then use a js proxy so that we wouldn't have to manually access the current language each time:
then proxies to
Trying to think about how to achieve what we need to achieve with the least amount of moving parts. Having to manually maintain all of our old data fixtures through lib upgrades and so on triggers a bit of code ptsd in me... That's why if we can do simple, we should. Our site isn't so big, all said. |
Wanted to /cc @jonallured on this. Thoughts on i18n from past experience? |
👋 lots of good stuff to ponder in this PR thanks for getting us going on this @lidimayra!! ❤️ My only experience with i18n is in the context of a Rails app and a third party translation service. We worked with them to settle on a file format for the translations and then make a rake task to import them into the project. I don't remember all the details but I'll just note that whatever we do here should probably connect with how we'll work with a third party. I'd hate to see us make tech choices without taking into consideration the larger loop of working with a service ya know? |
Thank you so much for for the comments and the careful review! 🙌❤️ Something that might answer some questions that were raised by @damassi , @dzucconi and @jonallured : At some point, we want to adopt one of these services (it would be useful for the product design team, and Erin has been talking about this on #hack9-localizations channel). A bit more on this is also available on this notion page. Besides this, react-intl also provides everything we need to handle pluralization, date formatting, numbers, etc. And it's a widely used lib, with great support from the community. |
3b4cc33
to
f9746ca
Compare
0dd40f6
to
4d099a4
Compare
Adopt react-intl for internationalization See: https://formatjs.io/docs/react-intl/
Add formatsj cli package so we can use it to extract message from files Ref: https://formatjs.io/docs/getting-started/message-extraction/ Example of command to generate en-us.json: ``` yarn i18n:extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file \ src/v2/System/i18n/lang/en-us.json --id-interpolation-pattern \ '[sha512:contenthash:base64:6]' ```
Ref: https://formatjs.io/docs/getting-started/message-distribution Example: ``` yarn i18n:compile src/v2/System/i18n/lang/en-us.json --ast \ --out-file src/v2/__generated_i18n__/en-us.json ```
This setting was leading our json files to be misinterpreted by the transpiler (build would fail trying to read json files as js files)
So we can have access to InternationalizationProvider inside tests
4d099a4
to
a790af9
Compare
There is another PR with a react-i18next implementation. |
👋 Heads up! A major change is incoming in Force, where we flatten out the |
Closing this PR in favor of #9797 |
This is part of the result of an internationalization project that was worked on during the hackathon (hack9).
Changes in this PR consist of a very basic structure to enable localization on Force.
Once we move forward with the initial setup, we could split subsequent work - internationalizing other strings contained in the project - maybe we could mob on it on another Future Friday (?)
Slack channel: #hack9-localizations
Notion page
We still have some more changes that were excluded from this PR but can be found here like: