Given that react-localization does not have date and number format and is heavily dependent on one developer we decided to switch to react-intl because it seems safer in the long run.
https://github.com/stefalda/react-localization/graphs/contributors
Our previous code looked like this:
localizationService.ts
import LocalizedStrings from 'react-localization';
import svSE from './languages/sv-SE';
import enUS from './languages/en-US';
import arSA from './languages/ar-SA';
export default new LocalizedStrings({
svSE,
enUS,
arSA
});
ILanguageStrings.ts
export interface ILanguageStrings {
appName: string
narration: string
language: string
}
en-US.ts
import { ILanguageStrings } from '../ILanguageStrings';
const language: ILanguageStrings = {
appName: "Our App",
narration: "Narration",
language: "Language"
}
export default language;
Localization could then be imported and ILanguageStrings was visible via IntelliSense in Visual Studio and validated by TypeScript.
import localization from '../services/localizationService';

However using FormattedMessage from react-intl id is either string | number | undefined. We still use the language files so how can we make sure id is in ILanguageStrings without breaking the original type definitions from react-intl?

I tried with TypeScript Declaration Merging and Merging Interfaces but I could only add new members there and not change the id property. A "valid" string was not seen as correct either.
react-app-env.d.ts:
import * as reactIntl from 'react-intl';
declare module 'react-intl' {
export interface MessageDescriptor {
id?: ILanguageStrings;
idTest: ILanguageStrings
}
}

https://github.com/microsoft/TypeScript/issues/10859
https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
I had the same problem before when using react-intl with typescript. My solution is simply to create a wrapper component that provides the appropriate type for the id. The id type should be the keyof the language config object that has the most support.
Assuming the content of the file ./languages/en-US has something like this
{
"AUTH.GENERAL.FORGOT_BUTTON": "Forgot Password",
"AUTH.LOGIN.TITLE": "Login Account",
"AUTH.FORGOT.TITLE": "Forgotten Password?",
"AUTH.REGISTER.TITLE": "Sign Up",
"AUTH.VALIDATION.INVALID": "{name} is not valid",
"AUTH.VALIDATION.REQUIRED": "{name} is required",
"AUTH.VALIDATION.NOT_FOUND": "The requested {name} is not found",
"AUTH.VALIDATION.INVALID_LOGIN": "The login detail is incorrect",
"AUTH.VALIDATION.REQUIRED_FIELD": "Required field",
"AUTH.VALIDATION.INVALID_FIELD": "Field is not valid",
"MENU.DASHBOARD": "Dashboard",
"MENU.PRODUCT": "Product",
"TOPBAR.GREETING": "Hi,",
...
}
I18nProvider.tsx
import React from "react";
import { IntlProvider } from "react-intl";
import svSE from './languages/sv-SE';
import enUS from './languages/en-US';
import arSA from './languages/ar-SA';
// In this example, english has the most support, so it has all the keys
export type IntlMessageID = keyof typeof enUS;
export default function I18nProvider({ children }) {
return (
<IntlProvider locale="en" messages={enMessages}>
{children}
</IntlProvider>
);
}
FormattedMessage.tsx
import React from "react";
import { FormattedMessage as ReactFormattedMessage } from "react-intl";
import { IntlMessageID } from "./I18nProvider";
type FormattedMessageProps = {
id?: IntlMessageID;
defaultMessage?: string;
values?: Record<string, React.ReactNode>;
children?: () => React.ReactNode;
};
export default function FormattedMessage(props: FormattedMessageProps) {
return <ReactFormattedMessage {...props} />;
}
import React from "react";
import I18nProvider from "./I18nProvider";
import FormattedMessage from "./FormattedMessage";
export default function App() {
return (
<I18nProvider>
<div className="App">
<FormattedMessage id="..." />
</div>
</I18nProvider>
);
}
Here is the result

In the demo below, you can trigger IntelliSense in the editor by pressing Ctrl + Space
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With