@lingui/macro 3.17.1 → 4.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/global.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ // read more about this file here
2
+ // https://github.com/lingui/js-lingui/issues/936
1
3
  // @ts-ignore
2
4
  declare module "@lingui/macro" {
3
5
  import type { MessageDescriptor, I18n } from "@lingui/core"
package/build/index.d.ts CHANGED
@@ -1,16 +1,36 @@
1
- import type { ReactElement, ComponentType, ReactNode } from "react"
2
- import type { MessageDescriptor, I18n } from "@lingui/core"
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import type { ReactElement, ReactNode, VFC, FC } from "react"
3
+ import type { I18n, MessageDescriptor } from "@lingui/core"
3
4
  import type { TransRenderProps } from "@lingui/react"
4
5
 
5
- export type UnderscoreDigit<T = string> = { [digit: string]: T }
6
- export type ChoiceOptions<T = string> = {
6
+ export type ChoiceOptions = {
7
+ /** Offset of value when calculating plural forms */
7
8
  offset?: number
8
- zero?: T
9
- one?: T
10
- few?: T
11
- many?: T
12
- other?: T
13
- } & UnderscoreDigit<T>
9
+ zero?: string
10
+ one?: string
11
+ two?: string
12
+ few?: string
13
+ many?: string
14
+
15
+ /** Catch-all option */
16
+ other?: string
17
+ /** Exact match form, corresponds to =N rule */
18
+ [digit: `${number}`]: string
19
+ }
20
+
21
+ type MacroMessageDescriptor = (
22
+ | {
23
+ id: string
24
+ message?: string
25
+ }
26
+ | {
27
+ id?: string
28
+ message: string
29
+ }
30
+ ) & {
31
+ comment?: string
32
+ context?: string
33
+ }
14
34
 
15
35
  /**
16
36
  * Translates a message descriptor
@@ -36,7 +56,7 @@ export type ChoiceOptions<T = string> = {
36
56
  *
37
57
  * @param descriptor The message descriptor to translate
38
58
  */
39
- export function t(descriptor: MessageDescriptor): string
59
+ export function t(descriptor: MacroMessageDescriptor): string
40
60
 
41
61
  /**
42
62
  * Translates a template string using the global I18n instance
@@ -79,7 +99,7 @@ export function t(
79
99
  */
80
100
  export function t(i18n: I18n): {
81
101
  (literals: TemplateStringsArray, ...placeholders: any[]): string
82
- (descriptor: MessageDescriptor): string
102
+ (descriptor: MacroMessageDescriptor): string
83
103
  }
84
104
 
85
105
  /**
@@ -124,6 +144,12 @@ export function selectOrdinal(
124
144
  options: ChoiceOptions
125
145
  ): string
126
146
 
147
+ type SelectOptions = {
148
+ /** Catch-all option */
149
+ other: string
150
+ [matches: string]: string
151
+ }
152
+
127
153
  /**
128
154
  * Selects a translation based on a value
129
155
  *
@@ -144,7 +170,7 @@ export function selectOrdinal(
144
170
  * @param value The key of choices to use
145
171
  * @param choices
146
172
  */
147
- export function select(value: string, choices: ChoiceOptions): string
173
+ export function select(value: string, choices: SelectOptions): string
148
174
 
149
175
  /**
150
176
  * Define a message for later use
@@ -163,46 +189,109 @@ export function select(value: string, choices: ChoiceOptions): string
163
189
  *
164
190
  * @param descriptor The message descriptor
165
191
  */
166
- export function defineMessage(descriptor: MessageDescriptor): MessageDescriptor
192
+ export function defineMessage(
193
+ descriptor: MacroMessageDescriptor
194
+ ): MessageDescriptor
167
195
 
168
- export type TransProps = {
196
+ type CommonProps = {
169
197
  id?: string
170
198
  comment?: string
171
- values?: Record<string, unknown>
172
199
  context?: string
173
- children?: React.ReactNode
174
- component?: React.ComponentType<TransRenderProps>
175
200
  render?: (props: TransRenderProps) => ReactElement<any, any> | null
176
201
  i18n?: I18n
177
202
  }
178
203
 
179
- export type ChoiceProps = {
180
- value?: string | number
181
- } & TransProps &
182
- ChoiceOptions<ReactNode>
204
+ type TransProps = {
205
+ children: ReactNode
206
+ } & CommonProps
207
+
208
+ type PluralChoiceProps = {
209
+ value: string | number
210
+ /** Offset of value when calculating plural forms */
211
+ offset?: number
212
+ zero?: ReactNode
213
+ one?: ReactNode
214
+ two?: ReactNode
215
+ few?: ReactNode
216
+ many?: ReactNode
217
+
218
+ /** Catch-all option */
219
+ other: ReactNode
220
+ /** Exact match form, corresponds to =N rule */
221
+ [digit: `_${number}`]: ReactNode
222
+ } & CommonProps
223
+
224
+ type SelectChoiceProps = {
225
+ value: string
226
+ /** Catch-all option */
227
+ other: ReactNode
228
+ [option: `_${string}`]: ReactNode
229
+ } & CommonProps
183
230
 
184
231
  /**
185
- * The types should be changed after this PR is merged
186
- * https://github.com/Microsoft/TypeScript/pull/26797
187
- *
188
- * then we should be able to specify that key of values is same type as value.
189
- * We would be able to remove separate type Values = {...} definition
190
- * eg.
191
- * type SelectProps<Values> = {
192
- * value?: Values
193
- * [key: Values]: string
194
- * }
232
+ * Trans is the basic macro for static messages,
233
+ * messages with variables, but also for messages with inline markup
195
234
  *
235
+ * @example
236
+ * ```
237
+ * <Trans>Hello {username}. Read the <a href="/docs">docs</a>.</Trans>
238
+ * ```
239
+ * @example
240
+ * ```
241
+ * <Trans id="custom.id">Hello {username}.</Trans>
242
+ * ```
196
243
  */
197
- type Values = { [key: string]: string }
244
+ export const Trans: FC<TransProps>
198
245
 
199
- export type SelectProps = {
200
- value: string
201
- other: ReactNode
202
- } & TransProps &
203
- Values
246
+ /**
247
+ * Props of Plural macro are transformed into plural format.
248
+ *
249
+ * @example
250
+ * ```
251
+ * import { Plural } from "@lingui/macro"
252
+ * <Plural value={numBooks} one="Book" other="Books" />
253
+ *
254
+ * // ↓ ↓ ↓ ↓ ↓ ↓
255
+ * import { Trans } from "@lingui/react"
256
+ * <Trans id="{numBooks, plural, one {Book} other {Books}}" values={{ numBooks }} />
257
+ * ```
258
+ */
259
+ export const Plural: VFC<PluralChoiceProps>
260
+ /**
261
+ * Props of SelectOrdinal macro are transformed into selectOrdinal format.
262
+ *
263
+ * @example
264
+ * ```
265
+ * // count == 1 -> 1st
266
+ * // count == 2 -> 2nd
267
+ * // count == 3 -> 3rd
268
+ * // count == 4 -> 4th
269
+ * <SelectOrdinal
270
+ * value={count}
271
+ * one="#st"
272
+ * two="#nd"
273
+ * few="#rd"
274
+ * other="#th"
275
+ * />
276
+ * ```
277
+ */
278
+ export const SelectOrdinal: VFC<PluralChoiceProps>
204
279
 
205
- export const Trans: ComponentType<TransProps>
206
- export const Plural: ComponentType<ChoiceProps>
207
- export const Select: ComponentType<SelectProps>
208
- export const SelectOrdinal: ComponentType<ChoiceProps>
280
+ /**
281
+ * Props of Select macro are transformed into select format
282
+ *
283
+ * @example
284
+ * ```
285
+ * // gender == "female" -> Her book
286
+ * // gender == "male" -> His book
287
+ * // gender == "non-binary" -> Their book
288
+ *
289
+ * <Select
290
+ * value={gender}
291
+ * _male="His book"
292
+ * _female="Her book"
293
+ * other="Their book"
294
+ * />
295
+ * ```
296
+ */
297
+ export const Select: VFC<SelectChoiceProps>
package/build/index.js CHANGED
@@ -10,74 +10,80 @@ var _macroJs = _interopRequireDefault(require("./macroJs"));
10
10
  var _macroJsx = _interopRequireDefault(require("./macroJsx"));
11
11
  var _types = require("@babel/types");
12
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
- const config = (0, _conf.getConfig)({
14
- configPath: process.env.LINGUI_CONFIG
15
- });
16
- const getSymbolSource = name => {
17
- if (Array.isArray(config.runtimeConfigModule)) {
18
- if (name === "i18n") {
19
- return config.runtimeConfigModule;
20
- } else {
21
- return ["@lingui/react", name];
22
- }
23
- } else {
24
- if (config.runtimeConfigModule[name]) {
25
- return config.runtimeConfigModule[name];
26
- } else {
27
- return ["@lingui/react", name];
28
- }
29
- }
30
- };
31
- const [i18nImportModule, i18nImportName = "i18n"] = getSymbolSource("i18n");
32
- const [TransImportModule, TransImportName = "Trans"] = getSymbolSource("Trans");
33
13
  const jsMacroTags = new Set(["defineMessage", "arg", "t", "plural", "select", "selectOrdinal"]);
34
14
  const jsxMacroTags = new Set(["Trans", "Plural", "Select", "SelectOrdinal"]);
15
+ let config;
16
+ function getConfig(_config) {
17
+ if (_config) {
18
+ config = _config;
19
+ }
20
+ if (!config) {
21
+ config = (0, _conf.getConfig)();
22
+ }
23
+ return config;
24
+ }
35
25
  function macro({
36
26
  references,
37
27
  state,
38
- babel
28
+ babel,
29
+ config
39
30
  }) {
40
- const jsxNodes = [];
41
- const jsNodes = [];
31
+ const opts = config;
32
+ const {
33
+ i18nImportModule,
34
+ i18nImportName,
35
+ TransImportModule,
36
+ TransImportName
37
+ } = getConfig(opts.linguiConfig).runtimeConfigModule;
38
+ const jsxNodes = new Set();
39
+ const jsNodes = new Set();
42
40
  let needsI18nImport = false;
41
+ let nameMap = new Map();
43
42
  Object.keys(references).forEach(tagName => {
44
43
  const nodes = references[tagName];
45
44
  if (jsMacroTags.has(tagName)) {
46
- nodes.forEach(node => {
47
- jsNodes.push(node.parentPath);
45
+ nodes.forEach(path => {
46
+ nameMap.set(tagName, path.node.name);
47
+ jsNodes.add(path.parentPath);
48
48
  });
49
49
  } else if (jsxMacroTags.has(tagName)) {
50
- nodes.forEach(node => {
50
+ // babel-plugin-macros return JSXIdentifier nodes.
51
+ // Which is for every JSX element would be presented twice (opening / close)
52
+ // Here we're taking JSXElement and dedupe it.
53
+ nodes.forEach(path => {
54
+ nameMap.set(tagName, path.node.name);
55
+
51
56
  // identifier.openingElement.jsxElement
52
- jsxNodes.push(node.parentPath.parentPath);
57
+ jsxNodes.add(path.parentPath.parentPath);
53
58
  });
54
59
  } else {
55
60
  throw nodes[0].buildCodeFrameError(`Unknown macro ${tagName}`);
56
61
  }
57
62
  });
58
- jsNodes.filter(isRootPath(jsNodes)).forEach(path => {
59
- if (alreadyVisited(path)) return;
63
+ const stripNonEssentialProps = process.env.NODE_ENV == "production" && !opts.extract;
64
+ const jsNodesArray = Array.from(jsNodes);
65
+ jsNodesArray.filter(isRootPath(jsNodesArray)).forEach(path => {
60
66
  const macro = new _macroJs.default(babel, {
61
- i18nImportName
67
+ i18nImportName,
68
+ stripNonEssentialProps,
69
+ nameMap
62
70
  });
63
71
  if (macro.replacePath(path)) needsI18nImport = true;
64
72
  });
65
- jsxNodes.filter(isRootPath(jsxNodes)).forEach(path => {
66
- if (alreadyVisited(path)) return;
67
- const macro = new _macroJsx.default(babel);
73
+ const jsxNodesArray = Array.from(jsxNodes);
74
+ jsxNodesArray.filter(isRootPath(jsxNodesArray)).forEach(path => {
75
+ const macro = new _macroJsx.default(babel, {
76
+ stripNonEssentialProps,
77
+ nameMap
78
+ });
68
79
  macro.replacePath(path);
69
80
  });
70
81
  if (needsI18nImport) {
71
82
  addImport(babel, state, i18nImportModule, i18nImportName);
72
83
  }
73
- if (jsxNodes.length) {
84
+ if (jsxNodes.size) {
74
85
  addImport(babel, state, TransImportModule, TransImportName);
75
86
  }
76
- if (process.env.LINGUI_EXTRACT === "1") {
77
- return {
78
- keepImports: true
79
- };
80
- }
81
87
  }
82
88
  function addImport(babel, state, module, importName) {
83
89
  const {
@@ -98,6 +104,14 @@ function addImport(babel, state, module, importName) {
98
104
  state.file.path.node.body.unshift(t.importDeclaration([t.importSpecifier(tIdentifier, tIdentifier)], t.stringLiteral(module)));
99
105
  }
100
106
  }
107
+
108
+ /**
109
+ * Filtering nested macro calls
110
+ *
111
+ * <Macro>
112
+ * <Macro /> <-- this would be filtered out
113
+ * </Macro>
114
+ */
101
115
  function isRootPath(allPath) {
102
116
  return node => function traverse(path) {
103
117
  if (!path.parentPath) {
@@ -107,15 +121,7 @@ function isRootPath(allPath) {
107
121
  }
108
122
  }(node);
109
123
  }
110
- const alreadyVisitedCache = new WeakSet();
111
- const alreadyVisited = path => {
112
- if (alreadyVisitedCache.has(path)) {
113
- return true;
114
- } else {
115
- alreadyVisitedCache.add(path);
116
- return false;
117
- }
118
- };
124
+ ;
119
125
  [...jsMacroTags, ...jsxMacroTags].forEach(name => {
120
126
  Object.defineProperty(module.exports, name, {
121
127
  get() {
@@ -123,5 +129,7 @@ const alreadyVisited = path => {
123
129
  }
124
130
  });
125
131
  });
126
- var _default = (0, _babelPluginMacros.createMacro)(macro);
132
+ var _default = (0, _babelPluginMacros.createMacro)(macro, {
133
+ configName: "lingui"
134
+ });
127
135
  exports.default = _default;