@lingui/babel-plugin-extract-messages 4.0.0-next.3 → 4.0.0-next.4

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.
@@ -1,12 +1,8 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = _default;
7
3
  function collectMessage(path, props, ctx) {
8
- // prevent from adding undefined msgid
9
- if (props.id === undefined) return;
4
+ if (props.id === void 0)
5
+ return;
10
6
  const node = path.node;
11
7
  const line = node.loc ? node.loc.start.line : null;
12
8
  const column = node.loc ? node.loc.start.column : null;
@@ -23,48 +19,61 @@ function getTextFromExpression(t, exp, hub, emitErrorOnVariable = true) {
23
19
  return exp.value;
24
20
  }
25
21
  if (t.isBinaryExpression(exp)) {
26
- return getTextFromExpression(t, exp.left, hub, emitErrorOnVariable) + getTextFromExpression(t, exp.right, hub, emitErrorOnVariable);
22
+ return getTextFromExpression(
23
+ t,
24
+ exp.left,
25
+ hub,
26
+ emitErrorOnVariable
27
+ ) + getTextFromExpression(
28
+ t,
29
+ exp.right,
30
+ hub,
31
+ emitErrorOnVariable
32
+ );
27
33
  }
28
34
  if (t.isTemplateLiteral(exp)) {
29
- var _exp$quasis$, _exp$quasis$$value;
30
- if ((exp === null || exp === void 0 ? void 0 : exp.quasis.length) > 1) {
31
- console.warn(hub.buildError(exp, "Could not extract from template literal with expressions.", SyntaxError).message);
35
+ if (exp?.quasis.length > 1) {
36
+ console.warn(
37
+ hub.buildError(
38
+ exp,
39
+ "Could not extract from template literal with expressions.",
40
+ SyntaxError
41
+ ).message
42
+ );
32
43
  return "";
33
44
  }
34
- return (_exp$quasis$ = exp.quasis[0]) === null || _exp$quasis$ === void 0 ? void 0 : (_exp$quasis$$value = _exp$quasis$.value) === null || _exp$quasis$$value === void 0 ? void 0 : _exp$quasis$$value.cooked;
45
+ return exp.quasis[0]?.value?.cooked;
35
46
  }
36
47
  if (emitErrorOnVariable) {
37
- console.warn(hub.buildError(exp, "Only strings or template literals could be extracted.", SyntaxError).message);
48
+ console.warn(
49
+ hub.buildError(
50
+ exp,
51
+ "Only strings or template literals could be extracted.",
52
+ SyntaxError
53
+ ).message
54
+ );
38
55
  }
39
56
  }
40
57
  function extractFromObjectExpression(t, exp, hub, keys) {
41
58
  const props = {};
42
- exp.properties.forEach(({
43
- key,
44
- value
45
- }, i) => {
59
+ exp.properties.forEach(({ key, value }, i) => {
46
60
  const name = key.name;
47
- if (!keys.includes(name)) return;
61
+ if (!keys.includes(name))
62
+ return;
48
63
  props[name] = getTextFromExpression(t, value, hub);
49
64
  });
50
65
  return props;
51
66
  }
52
- function _default({
53
- types: t
54
- }) {
67
+ function index({ types: t }) {
55
68
  let localTransComponentName;
56
69
  function isTransComponent(node) {
57
70
  return t.isJSXElement(node) && t.isJSXIdentifier(node.openingElement.name, {
58
71
  name: localTransComponentName
59
72
  });
60
73
  }
61
- const isI18nMethod = node => t.isMemberExpression(node) && t.isIdentifier(node.object, {
62
- name: "i18n"
63
- }) && t.isIdentifier(node.property, {
64
- name: "_"
65
- });
74
+ const isI18nMethod = (node) => t.isMemberExpression(node) && t.isIdentifier(node.object, { name: "i18n" }) && t.isIdentifier(node.property, { name: "_" });
66
75
  function hasI18nComment(node) {
67
- return node.leadingComments && node.leadingComments.some(comm => comm.value.match(/^\s*i18n/));
76
+ return node.leadingComments && node.leadingComments.some((comm) => comm.value.match(/^\s*i18n/));
68
77
  }
69
78
  return {
70
79
  visitor: {
@@ -72,29 +81,24 @@ function _default({
72
81
  // it might be different when the import is aliased:
73
82
  // import { Trans as T } from '@lingui/react';
74
83
  ImportDeclaration(path) {
75
- const {
76
- node
77
- } = path;
84
+ const { node } = path;
78
85
  const moduleName = node.source.value;
79
- if (!["@lingui/react", "@lingui/core"].includes(moduleName)) return;
86
+ if (!["@lingui/react", "@lingui/core"].includes(moduleName))
87
+ return;
80
88
  const importDeclarations = {};
81
89
  if (moduleName === "@lingui/react") {
82
- node.specifiers.forEach(specifier => {
90
+ node.specifiers.forEach((specifier) => {
83
91
  specifier = specifier;
84
92
  importDeclarations[specifier.imported.name] = specifier.local.name;
85
93
  });
86
-
87
- // Trans import might be missing if there's just Plural or similar macro.
88
- // If there's no alias, consider it was imported as Trans.
89
94
  localTransComponentName = importDeclarations["Trans"] || "Trans";
90
95
  }
91
96
  },
92
97
  // Extract translation from <Trans /> component.
93
98
  JSXElement(path, ctx) {
94
- const {
95
- node
96
- } = path;
97
- if (!localTransComponentName || !isTransComponent(node)) return;
99
+ const { node } = path;
100
+ if (!localTransComponentName || !isTransComponent(node))
101
+ return;
98
102
  const attrs = node.openingElement.attributes || [];
99
103
  const props = attrs.reduce((acc, item) => {
100
104
  const key = item.name.name;
@@ -108,27 +112,31 @@ function _default({
108
112
  return acc;
109
113
  }, {});
110
114
  if (!props.id) {
111
- // <Trans id={message} /> is valid, don't raise warning
112
- const idProp = attrs.filter(item => item.name.name === "id")[0];
113
- if (idProp === undefined || t.isLiteral(props.id)) {
114
- console.warn(path.buildCodeFrameError("Missing message ID, skipping.").message);
115
+ const idProp = attrs.filter((item) => item.name.name === "id")[0];
116
+ if (idProp === void 0 || t.isLiteral(props.id)) {
117
+ console.warn(
118
+ path.buildCodeFrameError("Missing message ID, skipping.").message
119
+ );
115
120
  }
116
121
  return;
117
122
  }
118
123
  collectMessage(path, props, ctx);
119
124
  },
120
125
  CallExpression(path, ctx) {
121
- const hasComment = [path.node, path.parent].some(node => hasI18nComment(node));
126
+ const hasComment = [path.node, path.parent].some(
127
+ (node) => hasI18nComment(node)
128
+ );
122
129
  const firstArgument = path.node.arguments[0];
123
-
124
- // support `i18n._` calls written by users in form i18n._(id, variables, descriptor)
125
- // without explicit annotation with comment
126
- // calls generated by macro has a form i18n._(/*i18n*/ {descriptor}) and
127
- // processed by ObjectExpression visitor
128
- const isNonMacroI18n = isI18nMethod(path.node.callee) && !(firstArgument !== null && firstArgument !== void 0 && firstArgument.leadingComments);
129
- if (!hasComment && !isNonMacroI18n) return;
130
+ const isNonMacroI18n = isI18nMethod(path.node.callee) && !firstArgument?.leadingComments;
131
+ if (!hasComment && !isNonMacroI18n)
132
+ return;
130
133
  let props = {
131
- id: getTextFromExpression(t, firstArgument, ctx.file.hub, false)
134
+ id: getTextFromExpression(
135
+ t,
136
+ firstArgument,
137
+ ctx.file.hub,
138
+ false
139
+ )
132
140
  };
133
141
  if (!props.id) {
134
142
  return;
@@ -137,7 +145,11 @@ function _default({
137
145
  if (t.isObjectExpression(msgDescArg)) {
138
146
  props = {
139
147
  ...props,
140
- ...extractFromObjectExpression(t, msgDescArg, ctx.file.hub, ["message", "comment", "context"])
148
+ ...extractFromObjectExpression(t, msgDescArg, ctx.file.hub, [
149
+ "message",
150
+ "comment",
151
+ "context"
152
+ ])
141
153
  };
142
154
  }
143
155
  collectMessage(path, props, ctx);
@@ -150,7 +162,9 @@ function _default({
150
162
  id: path.node.value
151
163
  };
152
164
  if (!props.id) {
153
- console.warn(path.buildCodeFrameError("Empty StringLiteral, skipping.").message);
165
+ console.warn(
166
+ path.buildCodeFrameError("Empty StringLiteral, skipping.").message
167
+ );
154
168
  return;
155
169
  }
156
170
  collectMessage(path, props, ctx);
@@ -160,13 +174,22 @@ function _default({
160
174
  if (!hasI18nComment(path.node)) {
161
175
  return;
162
176
  }
163
- const props = extractFromObjectExpression(t, path.node, ctx.file.hub, ["id", "message", "comment", "context"]);
177
+ const props = extractFromObjectExpression(t, path.node, ctx.file.hub, [
178
+ "id",
179
+ "message",
180
+ "comment",
181
+ "context"
182
+ ]);
164
183
  if (!props.id) {
165
- console.warn(path.buildCodeFrameError("Missing message ID, skipping.").message);
184
+ console.warn(
185
+ path.buildCodeFrameError("Missing message ID, skipping.").message
186
+ );
166
187
  return;
167
188
  }
168
189
  collectMessage(path, props, ctx);
169
190
  }
170
191
  }
171
192
  };
172
- }
193
+ }
194
+
195
+ module.exports = index;
@@ -0,0 +1,20 @@
1
+ import * as BabelTypesNamespace from '@babel/types';
2
+ import { PluginObj } from '@babel/core';
3
+
4
+ type BabelTypes = typeof BabelTypesNamespace;
5
+ type ExtractedMessage = {
6
+ id: string;
7
+ message?: string;
8
+ context?: string;
9
+ origin?: Origin;
10
+ comment?: string;
11
+ };
12
+ type ExtractPluginOpts = {
13
+ onMessageExtracted(msg: ExtractedMessage): void;
14
+ };
15
+ type Origin = [filename: string, line: number, column?: number];
16
+ declare function export_default({ types: t }: {
17
+ types: BabelTypes;
18
+ }): PluginObj;
19
+
20
+ export { ExtractPluginOpts, ExtractedMessage, Origin, export_default as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,193 @@
1
+ function collectMessage(path, props, ctx) {
2
+ if (props.id === void 0)
3
+ return;
4
+ const node = path.node;
5
+ const line = node.loc ? node.loc.start.line : null;
6
+ const column = node.loc ? node.loc.start.column : null;
7
+ ctx.opts.onMessageExtracted({
8
+ id: props.id,
9
+ message: props.message,
10
+ context: props.context,
11
+ comment: props.comment,
12
+ origin: [ctx.file.opts.filename, line, column]
13
+ });
14
+ }
15
+ function getTextFromExpression(t, exp, hub, emitErrorOnVariable = true) {
16
+ if (t.isStringLiteral(exp)) {
17
+ return exp.value;
18
+ }
19
+ if (t.isBinaryExpression(exp)) {
20
+ return getTextFromExpression(
21
+ t,
22
+ exp.left,
23
+ hub,
24
+ emitErrorOnVariable
25
+ ) + getTextFromExpression(
26
+ t,
27
+ exp.right,
28
+ hub,
29
+ emitErrorOnVariable
30
+ );
31
+ }
32
+ if (t.isTemplateLiteral(exp)) {
33
+ if (exp?.quasis.length > 1) {
34
+ console.warn(
35
+ hub.buildError(
36
+ exp,
37
+ "Could not extract from template literal with expressions.",
38
+ SyntaxError
39
+ ).message
40
+ );
41
+ return "";
42
+ }
43
+ return exp.quasis[0]?.value?.cooked;
44
+ }
45
+ if (emitErrorOnVariable) {
46
+ console.warn(
47
+ hub.buildError(
48
+ exp,
49
+ "Only strings or template literals could be extracted.",
50
+ SyntaxError
51
+ ).message
52
+ );
53
+ }
54
+ }
55
+ function extractFromObjectExpression(t, exp, hub, keys) {
56
+ const props = {};
57
+ exp.properties.forEach(({ key, value }, i) => {
58
+ const name = key.name;
59
+ if (!keys.includes(name))
60
+ return;
61
+ props[name] = getTextFromExpression(t, value, hub);
62
+ });
63
+ return props;
64
+ }
65
+ function index({ types: t }) {
66
+ let localTransComponentName;
67
+ function isTransComponent(node) {
68
+ return t.isJSXElement(node) && t.isJSXIdentifier(node.openingElement.name, {
69
+ name: localTransComponentName
70
+ });
71
+ }
72
+ const isI18nMethod = (node) => t.isMemberExpression(node) && t.isIdentifier(node.object, { name: "i18n" }) && t.isIdentifier(node.property, { name: "_" });
73
+ function hasI18nComment(node) {
74
+ return node.leadingComments && node.leadingComments.some((comm) => comm.value.match(/^\s*i18n/));
75
+ }
76
+ return {
77
+ visitor: {
78
+ // Get the local name of Trans component. Usually it's just `Trans`, but
79
+ // it might be different when the import is aliased:
80
+ // import { Trans as T } from '@lingui/react';
81
+ ImportDeclaration(path) {
82
+ const { node } = path;
83
+ const moduleName = node.source.value;
84
+ if (!["@lingui/react", "@lingui/core"].includes(moduleName))
85
+ return;
86
+ const importDeclarations = {};
87
+ if (moduleName === "@lingui/react") {
88
+ node.specifiers.forEach((specifier) => {
89
+ specifier = specifier;
90
+ importDeclarations[specifier.imported.name] = specifier.local.name;
91
+ });
92
+ localTransComponentName = importDeclarations["Trans"] || "Trans";
93
+ }
94
+ },
95
+ // Extract translation from <Trans /> component.
96
+ JSXElement(path, ctx) {
97
+ const { node } = path;
98
+ if (!localTransComponentName || !isTransComponent(node))
99
+ return;
100
+ const attrs = node.openingElement.attributes || [];
101
+ const props = attrs.reduce((acc, item) => {
102
+ const key = item.name.name;
103
+ if (key === "id" || key === "message" || key === "comment" || key === "context") {
104
+ if (t.isStringLiteral(item.value)) {
105
+ acc[key] = item.value.value;
106
+ } else if (t.isJSXExpressionContainer(item.value) && t.isStringLiteral(item.value.expression)) {
107
+ acc[key] = item.value.expression.value;
108
+ }
109
+ }
110
+ return acc;
111
+ }, {});
112
+ if (!props.id) {
113
+ const idProp = attrs.filter((item) => item.name.name === "id")[0];
114
+ if (idProp === void 0 || t.isLiteral(props.id)) {
115
+ console.warn(
116
+ path.buildCodeFrameError("Missing message ID, skipping.").message
117
+ );
118
+ }
119
+ return;
120
+ }
121
+ collectMessage(path, props, ctx);
122
+ },
123
+ CallExpression(path, ctx) {
124
+ const hasComment = [path.node, path.parent].some(
125
+ (node) => hasI18nComment(node)
126
+ );
127
+ const firstArgument = path.node.arguments[0];
128
+ const isNonMacroI18n = isI18nMethod(path.node.callee) && !firstArgument?.leadingComments;
129
+ if (!hasComment && !isNonMacroI18n)
130
+ return;
131
+ let props = {
132
+ id: getTextFromExpression(
133
+ t,
134
+ firstArgument,
135
+ ctx.file.hub,
136
+ false
137
+ )
138
+ };
139
+ if (!props.id) {
140
+ return;
141
+ }
142
+ const msgDescArg = path.node.arguments[2];
143
+ if (t.isObjectExpression(msgDescArg)) {
144
+ props = {
145
+ ...props,
146
+ ...extractFromObjectExpression(t, msgDescArg, ctx.file.hub, [
147
+ "message",
148
+ "comment",
149
+ "context"
150
+ ])
151
+ };
152
+ }
153
+ collectMessage(path, props, ctx);
154
+ },
155
+ StringLiteral(path, ctx) {
156
+ if (!hasI18nComment(path.node)) {
157
+ return;
158
+ }
159
+ const props = {
160
+ id: path.node.value
161
+ };
162
+ if (!props.id) {
163
+ console.warn(
164
+ path.buildCodeFrameError("Empty StringLiteral, skipping.").message
165
+ );
166
+ return;
167
+ }
168
+ collectMessage(path, props, ctx);
169
+ },
170
+ // Extract message descriptors
171
+ ObjectExpression(path, ctx) {
172
+ if (!hasI18nComment(path.node)) {
173
+ return;
174
+ }
175
+ const props = extractFromObjectExpression(t, path.node, ctx.file.hub, [
176
+ "id",
177
+ "message",
178
+ "comment",
179
+ "context"
180
+ ]);
181
+ if (!props.id) {
182
+ console.warn(
183
+ path.buildCodeFrameError("Missing message ID, skipping.").message
184
+ );
185
+ return;
186
+ }
187
+ collectMessage(path, props, ctx);
188
+ }
189
+ }
190
+ };
191
+ }
192
+
193
+ export { index as default };
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "@lingui/babel-plugin-extract-messages",
3
- "version": "4.0.0-next.3",
3
+ "version": "4.0.0-next.4",
4
4
  "description": "Babel plugin for collecting messages from source code for internationalization",
5
- "main": "./build/index.js",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
6
8
  "author": {
7
9
  "name": "Tomáš Ehrlich",
8
10
  "email": "tomas.ehrlich@gmail.com"
@@ -17,10 +19,14 @@
17
19
  "i9n",
18
20
  "translation"
19
21
  ],
22
+ "scripts": {
23
+ "build": "rimraf ./dist && unbuild",
24
+ "stub": "unbuild --stub"
25
+ },
20
26
  "files": [
21
27
  "LICENSE",
22
28
  "README.md",
23
- "build/"
29
+ "dist/"
24
30
  ],
25
31
  "repository": {
26
32
  "type": "git",
@@ -32,14 +38,12 @@
32
38
  "engines": {
33
39
  "node": ">=16.0.0"
34
40
  },
35
- "dependencies": {
36
- "@babel/runtime": "^7.20.13"
37
- },
38
41
  "devDependencies": {
39
42
  "@babel/core": "^7.21.0",
40
43
  "@babel/traverse": "7.20.12",
41
44
  "@babel/types": "^7.20.7",
42
- "@lingui/jest-mocks": "^3.0.3"
45
+ "@lingui/jest-mocks": "^3.0.3",
46
+ "unbuild": "^1.1.2"
43
47
  },
44
- "gitHead": "29cad1bdcc781994ac4d496e5c4937795423d405"
48
+ "gitHead": "3b999e35d26e67dec7cf0591f794be782e11022c"
45
49
  }
package/build/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2017-2022 Tomáš Ehrlich, (c) 2022-2023 Crowdin.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.