Skip to content

Commit

Permalink
refactor(macro): more accurate typings for macro + testing (#1340)
Browse files Browse the repository at this point in the history
* refactor(macro): more accurate typings for macro + testing

* Apply suggestions from code review

Co-authored-by: Martin Chrástek <chrastek12@gmail.com>
  • Loading branch information
timofei-iatsenko and Martin005 authored Feb 14, 2023
1 parent d2701ed commit f6655ec
Show file tree
Hide file tree
Showing 10 changed files with 583 additions and 98 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/main-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
collectCoverageFrom: [
"**/*.{ts,tsx}",
"!**/*.d.ts",
"!**/*.test-d.{ts,tsx}",
"!**/node_modules/**",
"!**/build/**",
],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/macro/global.d.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
172 changes: 130 additions & 42 deletions packages/macro/index.d.ts
Original file line number Diff line number Diff line change
@@ -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<T = string> = { [digit: string]: T }
export type ChoiceOptions<T = string> = {
export type ChoiceOptions = {
/** Offset of value when calculating plural forms */
offset?: number
zero?: T
one?: T
few?: T
many?: T
other?: T
} & UnderscoreDigit<T>
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
Expand All @@ -36,7 +55,7 @@ export type ChoiceOptions<T = string> = {
*
* @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
Expand Down Expand Up @@ -79,7 +98,7 @@ export function t(
*/
export function t(i18n: I18n): {
(literals: TemplateStringsArray, ...placeholders: any[]): string
(descriptor: MessageDescriptor): string
(descriptor: MacroMessageDescriptor): string
}

/**
Expand Down Expand Up @@ -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
*
Expand All @@ -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
Expand All @@ -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<string, unknown>
context?: string
children?: React.ReactNode
component?: React.ComponentType<TransRenderProps>
render?: (props: TransRenderProps) => ReactElement<any, any> | null
i18n?: I18n
}

export type ChoiceProps = {
value?: string | number
} & TransProps &
ChoiceOptions<ReactNode>
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<Values> = {
* value?: Values
* [key: Values]: string
* }
* Trans is the basic macro for static messages,
* messages with variables, but also for messages with inline markup
*
* @example
* ```
* <Trans>Hello {username}. Read the <a href="/docs">docs</a>.</Trans>
* ```
* @example
* ```
* <Trans id="custom.id">Hello {username}.</Trans>
* ```
*/
type Values = { [key: string]: string }
export const Trans: FC<TransProps>

export type SelectProps = {
value: string
other: ReactNode
} & TransProps &
Values
/**
* Props of Plural macro are transformed into plural format.
*
* @example
* ```
* import { Plural } from "@lingui/macro"
* <Plural value={numBooks} one="Book" other="Books" />
*
* // ↓ ↓ ↓ ↓ ↓ ↓
* import { Trans } from "@lingui/react"
* <Trans id="{numBooks, plural, one {Book} other {Books}}" values={{ numBooks }} />
* ```
*/
export const Plural: VFC<PluralChoiceProps>
/**
* Props of SelectOrdinal macro are transformed into selectOrdinal format.
*
* @example
* ```
* // count == 1 -> 1st
* // count == 2 -> 2nd
* // count == 3 -> 3rd
* // count == 4 -> 4th
* <SelectOrdinal
* value={count}
* one="#st"
* two="#nd"
* few="#rd"
* other="#th"
* />
* ```
*/
export const SelectOrdinal: VFC<PluralChoiceProps>

export const Trans: ComponentType<TransProps>
export const Plural: ComponentType<ChoiceProps>
export const Select: ComponentType<SelectProps>
export const SelectOrdinal: ComponentType<ChoiceProps>
/**
* Props of Select macro are transformed into select format
*
* @example
* ```
* // gender == "female" -> Her book
* // gender == "male" -> His book
* // gender == "non-binary" -> Their book
*
* <Select
* value={gender}
* _male="His book"
* _female="Her book"
* other="Their book"
* />
* ```
*/
export const Select: VFC<SelectChoiceProps>
Loading

0 comments on commit f6655ec

Please sign in to comment.