简体   繁体   中英

Make id in FormattedMessage from react-intl inherit from a custom TypeScript interface to enable VS IntelliSense and type checking

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 string | number | undefined 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} />;
}

Usage

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

在此处输入图片说明

Live Demo

In the demo below, you can trigger IntelliSense in the editor by pressing Ctrl + Space

编辑 React-Int - 消息 ID 类型

My first thought was same as @NearHuscarl

However, I found that the document has a section about this:

https://formatjs.io/docs/react-intl#typing-message-ids-and-locale

You can use global declaration to augment the type of message id

declare global {
  namespace FormatjsIntl {
    interface Message {
      ids: keyof typeof messages
    }
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM