17
loading...
This website collects cookies to deliver better user experience
function App() {
return (
<div>
Gonna pass a prop through components
<ChildFirstLevel myProp="A prop to pass" />
</div>
);
}
function ChildFirstLevel({ myProp }) {
return <ChildSecondLevel myProp={myProp} />;
}
function ChildSecondLevel({ myProp }) {
return <ChildThirdLevel myProp={myProp} />;
}
function ChildThirdLevel({ myProp }) {
// Some process with myProp
// It's the only component that needs the props
return <p>This component uses myProp</p>;
}
import { createContext, useContext } from "react";
const MyContext = createContext();
function App() {
return (
<MyContext.Provider value="A prop to pass">
<div>
Gonna pass a value with react context
<ChildFirstLevel />
</div>
</MyContext.Provider>
);
}
function ChildFirstLevel() {
return <ChildSecondLevel />;
}
function ChildSecondLevel() {
return <ChildThirdLevel />;
}
function ChildThirdLevel() {
const myProp = useContext(MyContext);
// Some process with myProp
// It's the only component that needs the props
return <p>This component uses myProp</p>;
}
This is just an example that would probably not exist in real application
For more information the React documentation is awesome.
navigator.language
for example.import { createContext, useContext, useMemo } from "react";
const I18nContext = createContext();
const useI18nContext = () => useContext(I18nContext);
function I18nProvider({ children, locale }) {
const value = useMemo(
() => ({
locale,
}),
[locale]
);
return (
<I18nContext.Provider value={value}>
{children}
</I18nContext.Provider>
);
}
Note: I have memoized the value, because in the project some components could be memoized because having performance problem (costly/long render)
const MESSAGES = {
en: {
title: 'This is a title for the application',
body: 'You need a body content?'
},
fr: {
title: 'Ceci est le titre de l\'application',
body: 'Besoin de contenu pour le body?'
}
};
In applications, these translations can be separated in file for specific language for example: messages-fr.properties
, messages-en.properties
, ... Then you can build an object from these files ;)
// The messages are passed to the Provider
function I18nProvider({ children, locale, messages }) {
// The user needs to only pass the messageKey
const getMessage = useCallback((messageKey) => {
return messages[locale][messageKey];
}, [locale, messages]);
const value = useMemo(() => ({
locale,
getMessage,
}), [locale, getMessage]);
return (
<I18nContext.Provider value={value}>
{children}
</I18nContext.Provider>
);
}
defaultLocale
to fallback to with locale and/or a defaultMessage
. The Provider becomes:// Pass an optional defaultLocale to the Provider
function I18nProvider({
children,
locale,
defaultLocale,
messages,
}) {
// Fallback to the `defaultMessage`, if there is no
// defaultMessage fallback to the `defaultLocale`
const getMessage = useCallback(
({ messageKey, defaultMessage }) => {
return (
messages[locale]?.[messageKey] ??
defaultMessage ??
messages[defaultLocale][messageKey]
);
},
[locale, messages, defaultLocale]
);
const value = useMemo(
() => ({
locale,
getMessage,
}),
[locale, getMessage]
);
return (
<I18nContext.Provider value={value}>
{children}
</I18nContext.Provider>
);
}
getMessage
with useI18nContext
const { getMessage } = useI18nContext();
const title = getMessage({ messageKey: 'title' });
I18nMessage
that has messageKey
and defaultMessage
function I18nMessage({ messageKey, defaultMessage }) {
const { getMessage } = useI18nContext();
return getMessage({ messageKey, defaultMessage });
}
// Use
<I18nMessage messageKey="title" />
withI18n
that injects getMessage
to our component
function withI18n(WrappedComponent) {
const Component = (props) => {
const { getMessage } = useI18nContext();
return (
<WrappedComponent
{...props}
getMessage={getMessage}
/>
);
};
Component.displayName = "I18n" + WrappedComponent.name;
return Component;
}
function Title({ getMessage }) {
const title = getMessage({ messageKey: "title" });
return <h1>title</h1>;
}
const I18nConnectedTitle = withI18n(Title);
// Watch out the month is 0-based
const date = new Date(2021, 5, 23);
// In en-US should be displayed
"6/23/2021"
// In fr-FR should be displayed
"23/06/2021"
// In en-IN should be displayed
"23/6/2021"
Note: The time is also not formatted the same way in function of the locale
Intl.DateTimeFormat
API which is accessible on all browsers. Note: Intl relays on the Common Locale Data Repository
I18nProvider
becomes:function I18nProvider({
children,
locale,
defaultLocale,
messages,
}) {
const getMessage = useCallback(
({ messageKey, defaultMessage }) => {
return (
messages[locale]?.[messageKey] ??
defaultMessage ??
messages[defaultLocale][messageKey]
);
},
[locale, messages, defaultLocale]
);
const getFormattedDate = useCallback(
(date, options = {}) =>
Intl.DateTimeFormat(locale, options).format(date),
[locale]
);
const value = useMemo(
() => ({
locale,
getMessage,
getFormattedDate,
}),
[
locale,
getMessage,
getFormattedDate,
]
);
return (
<I18nContext.Provider value={value}>
{children}
</I18nContext.Provider>
);
}
Note: I will not describe the usage, because it's exactly the same as translations but you can see the final implementation in the codesandbox in conclusion.
const number = 123456.789;
// In en-US should be displayed
"123,456.789"
// In fr-FR should be displayed
"123 456,789"
// In en-IN should be displayed
"1,23,456.789"
Intl.NumberFormat
which works on all browsers.Intl.NumberFormat
, you can see that there is a tone of options available in second parameter, so in our implementation (like with date formatting) we will pass an options object.I18nProvider
becomes then:function I18nProvider({
children,
locale,
defaultLocale,
messages,
}) {
const getMessage = useCallback(
({ messageKey, defaultMessage }) => {
return (
messages[locale]?.[messageKey] ??
defaultMessage ??
messages[defaultLocale][messageKey]
);
},
[locale, messages, defaultLocale]
);
const getFormattedDate = useCallback(
(date, options = {}) =>
Intl.DateTimeFormat(locale, options).format(date),
[locale]
);
const getFormattedNumber = useCallback(
(number, options = {}) =>
Intl.NumberFormat(locale, options).format(number),
[locale]
);
const value = useMemo(
() => ({
locale,
getMessage,
getFormattedDate,
getFormattedNumber,
}),
[
locale,
getMessage,
getFormattedDate,
getFormattedNumber,
]
);
return (
<I18nContext.Provider value={value}>
{children}
</I18nContext.Provider>
);
}
Note: When working with currency, it can be boring to always pass the right option so we could make a utility function name getFormattedCurrency
which only take the currency name in second parameter:
const getFormattedCurrency = useCallback(
(number, currency) =>
Intl.NumberFormat(locale, {
style: "currency",
currency,
}).format(number),
[locale]
);
locale
, message translations
to the provider and then put utility methods in the context to get a message translated and formatted date, number or currency.Intl
for formatted date and number which relays on the CLDR.