diff --git a/.github/workflows/main-suite.yml b/.github/workflows/main-suite.yml index 210c0ca3f..c9bb7461f 100644 --- a/.github/workflows/main-suite.yml +++ b/.github/workflows/main-suite.yml @@ -60,6 +60,9 @@ jobs: - name: Linting & Types run: yarn lint:all + - name: Test Public Typings + run: yarn test:tsd + - name: Check Prettier Formatting run: yarn prettier:check diff --git a/jest.config.js b/jest.config.js index e5f5fad7b..47b92d751 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,6 +15,7 @@ module.exports = { collectCoverageFrom: [ "**/*.{ts,tsx}", "!**/*.d.ts", + "!**/*.test-d.{ts,tsx}", "!**/node_modules/**", "!**/build/**", ], diff --git a/package.json b/package.json index c397bb915..fcbc3ea7a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test": "cross-env TZ=UTC NODE_ICU_DATA=node_modules/full-icu JEST_JUNIT_OUTPUT=results/unit.xml jest -c jest.config.js --no-cache", "test:integration": "cross-env TZ=UTC NODE_ICU_DATA=node_modules/full-icu JEST_JUNIT_OUTPUT=results/integration.xml jest -c jest.config.integration.js --no-cache", "test:e2e": "lerna run test:e2e", + "test:tsd": "lerna run test:tsd", "test:all": "yarn test && yarn test:integration && yarn test:e2e", "watch": "cross-env TZ=UTC NODE_ICU_DATA=node_modules/full-icu jest -c jest.config.js --watch", "watch:integration": "cross-env TZ=UTC NODE_ICU_DATA=node_modules/full-icu jest -c jest.config.integration.js --watch", diff --git a/packages/macro/global.d.ts b/packages/macro/global.d.ts index 1ddbaf06c..08eadba66 100644 --- a/packages/macro/global.d.ts +++ b/packages/macro/global.d.ts @@ -1,3 +1,5 @@ +// read more about this file here +// https://github.com/lingui/js-lingui/issues/936 // @ts-ignore declare module "@lingui/macro" { import type { MessageDescriptor, I18n } from "@lingui/core" diff --git a/packages/macro/index.d.ts b/packages/macro/index.d.ts index 26595451d..805e960f2 100644 --- a/packages/macro/index.d.ts +++ b/packages/macro/index.d.ts @@ -1,16 +1,35 @@ -import type { ReactElement, ComponentType, ReactNode } from "react" -import type { MessageDescriptor, I18n } from "@lingui/core" +import type { ReactElement, ReactNode, VFC, FC } from "react" +import type { I18n, MessageDescriptor } from "@lingui/core" import type { TransRenderProps } from "@lingui/react" -export type UnderscoreDigit = { [digit: string]: T } -export type ChoiceOptions = { +export type ChoiceOptions = { + /** Offset of value when calculating plural forms */ offset?: number - zero?: T - one?: T - few?: T - many?: T - other?: T -} & UnderscoreDigit + zero?: string + one?: string + two?: string + few?: string + many?: string + + /** Catch-all option */ + other?: string + /** Exact match form, corresponds to =N rule */ + [digit: `${number}`]: string +} + +type MacroMessageDescriptor = ( + | { + id: string + message?: string + } + | { + id?: string + message: string + } +) & { + comment?: string + context?: string +} /** * Translates a message descriptor @@ -36,7 +55,7 @@ export type ChoiceOptions = { * * @param descriptor The message descriptor to translate */ -export function t(descriptor: MessageDescriptor): string +export function t(descriptor: MacroMessageDescriptor): string /** * Translates a template string using the global I18n instance @@ -79,7 +98,7 @@ export function t( */ export function t(i18n: I18n): { (literals: TemplateStringsArray, ...placeholders: any[]): string - (descriptor: MessageDescriptor): string + (descriptor: MacroMessageDescriptor): string } /** @@ -124,6 +143,12 @@ export function selectOrdinal( options: ChoiceOptions ): string +type SelectOptions = { + /** Catch-all option */ + other: string + [matches: string]: string +} + /** * Selects a translation based on a value * @@ -144,7 +169,7 @@ export function selectOrdinal( * @param value The key of choices to use * @param choices */ -export function select(value: string, choices: ChoiceOptions): string +export function select(value: string, choices: SelectOptions): string /** * Define a message for later use @@ -163,46 +188,109 @@ export function select(value: string, choices: ChoiceOptions): string * * @param descriptor The message descriptor */ -export function defineMessage(descriptor: MessageDescriptor): MessageDescriptor +export function defineMessage( + descriptor: MacroMessageDescriptor +): MessageDescriptor -export type TransProps = { +type CommonProps = { id?: string comment?: string - values?: Record context?: string - children?: React.ReactNode - component?: React.ComponentType render?: (props: TransRenderProps) => ReactElement | null i18n?: I18n } -export type ChoiceProps = { - value?: string | number -} & TransProps & - ChoiceOptions +type TransProps = { + children: ReactNode +} & CommonProps + +type PluralChoiceProps = { + value: string | number + /** Offset of value when calculating plural forms */ + offset?: number + zero?: ReactNode + one?: ReactNode + two?: ReactNode + few?: ReactNode + many?: ReactNode + + /** Catch-all option */ + other: ReactNode + /** Exact match form, corresponds to =N rule */ + [digit: `_${number}`]: ReactNode +} & CommonProps + +type SelectChoiceProps = { + value: string + /** Catch-all option */ + other: ReactNode + [option: `_${string}`]: ReactNode +} & CommonProps /** - * The types should be changed after this PR is merged - * https://github.com/Microsoft/TypeScript/pull/26797 - * - * then we should be able to specify that key of values is same type as value. - * We would be able to remove separate type Values = {...} definition - * eg. - * type SelectProps = { - * value?: Values - * [key: Values]: string - * } + * Trans is the basic macro for static messages, + * messages with variables, but also for messages with inline markup * + * @example + * ``` + * Hello {username}. Read the docs. + * ``` + * @example + * ``` + * Hello {username}. + * ``` */ -type Values = { [key: string]: string } +export const Trans: FC -export type SelectProps = { - value: string - other: ReactNode -} & TransProps & - Values +/** + * Props of Plural macro are transformed into plural format. + * + * @example + * ``` + * import { Plural } from "@lingui/macro" + * + * + * // ↓ ↓ ↓ ↓ ↓ ↓ + * import { Trans } from "@lingui/react" + * + * ``` + */ +export const Plural: VFC +/** + * Props of SelectOrdinal macro are transformed into selectOrdinal format. + * + * @example + * ``` + * // count == 1 -> 1st + * // count == 2 -> 2nd + * // count == 3 -> 3rd + * // count == 4 -> 4th + * + * ``` + */ +export const SelectOrdinal: VFC -export const Trans: ComponentType -export const Plural: ComponentType -export const Select: ComponentType -export const SelectOrdinal: ComponentType +/** + * Props of Select macro are transformed into select format + * + * @example + * ``` + * // gender == "female" -> Her book + * // gender == "male" -> His book + * // gender == "non-binary" -> Their book + * + * + Message + +) + +// @ts-expect-error: `value` could be string only +m = + +// @ts-expect-error: `value` required +m = + +// @ts-expect-error: exact cases should be prefixed with underscore +m = ...} + other={...} + /> +) diff --git a/packages/macro/package.json b/packages/macro/package.json index e9556c8a3..a94e70878 100644 --- a/packages/macro/package.json +++ b/packages/macro/package.json @@ -3,10 +3,14 @@ "version": "3.17.1", "description": "Macro for generating messages in ICU MessageFormat syntax", "main": "./build/index.js", + "types": "./index.d.ts", "author": { "name": "Tomáš Ehrlich", "email": "tomas.ehrlich@gmail.com" }, + "scripts": { + "test:tsd": "tsd" + }, "license": "MIT", "keywords": [ "babel-plugin-macros" @@ -24,7 +28,8 @@ "files": [ "LICENSE", "README.md", - "build/" + "build/", + "index.d.ts" ], "dependencies": { "@babel/runtime": "^7.20.13", @@ -38,6 +43,12 @@ "babel-plugin-macros": "2 || 3" }, "devDependencies": { - "@types/babel-plugin-macros": "^2.8.5" + "@types/babel-plugin-macros": "^2.8.5", + "tsd": "^0.25.0" + }, + "tsd": { + "compilerOptions": { + "strict": false + } } } diff --git a/packages/macro/test/jsx-select.ts b/packages/macro/test/jsx-select.ts index 533ee0cee..51fed07fa 100644 --- a/packages/macro/test/jsx-select.ts +++ b/packages/macro/test/jsx-select.ts @@ -41,5 +41,34 @@ const cases: TestCase[] = [ }} />; `, }, + { + name: "Select should support JSX elements in cases", + input: ` + import { Select } from '@lingui/macro'; +