vue-i18n-extract-plugin 1.0.50 → 1.0.52

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.
Files changed (3) hide show
  1. package/README.md +17 -0
  2. package/lib/visitors.js +114 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -98,6 +98,18 @@ export default {
98
98
  }),
99
99
  ...
100
100
  };
101
+
102
+ // ts支持
103
+ import { defineConfig } from "vite-i18n-extract-plugin";
104
+
105
+ export default defineConfig({
106
+ rewrite: false,
107
+ translator: new YoudaoTranslator({
108
+ appId: "youdao appId",
109
+ appKey: "youdao appKey"
110
+ }),
111
+ ...
112
+ });
101
113
  ```
102
114
 
103
115
  ## Vite plugin
@@ -145,6 +157,8 @@ import enMessages from "@/locales/en.json";
145
157
 
146
158
  const i18n = createI18n({
147
159
  legacy: false,
160
+ globalInjection: true,
161
+ allowComposition: true,
148
162
  fallbackLocale: "en",
149
163
  locale: "zh",
150
164
  messages: {
@@ -156,6 +170,9 @@ const i18n = createI18n({
156
170
  // 导出一个$t方法
157
171
  export const $t = i18n.global.t.bind(i18n.global);
158
172
 
173
+ // 建议在全局也挂载一个$t方法做兜底
174
+ globalThis.$t = $t;
175
+
159
176
  export default i18n;
160
177
  ```
161
178
 
package/lib/visitors.js CHANGED
@@ -15,6 +15,84 @@ function isTFunction(node, option) {
15
15
  );
16
16
  }
17
17
 
18
+ function isVNodeCall(path, nodeName) {
19
+ const node = path.node;
20
+ if (!path.isCallExpression()) return false;
21
+
22
+ const callee = node.callee;
23
+ return (
24
+ (t.isIdentifier(callee) && callee.name === nodeName) ||
25
+ (t.isMemberExpression(callee) &&
26
+ t.isIdentifier(callee.property) &&
27
+ callee.property.name === nodeName)
28
+ );
29
+ }
30
+
31
+ function getPropKey(propNode) {
32
+ if (t.isIdentifier(propNode.key)) {
33
+ return propNode.key.name;
34
+ }
35
+ if (t.isStringLiteral(propNode.key)) {
36
+ return propNode.key.value; // .replace(/-([a-z])/g, (_, char) => char.toUpperCase());
37
+ }
38
+ return null;
39
+ }
40
+
41
+ function transformDirectiveIfNeeded(path, parentPath) {
42
+ let hasCreateVNode = false;
43
+ // 属性值情况,如 title: "xxx"
44
+ if (parentPath.isObjectProperty()) {
45
+ const propKey = getPropKey(parentPath.node);
46
+
47
+ if (!propKey) return;
48
+
49
+ const vNodeCall = path.findParent(
50
+ p =>
51
+ p.isCallExpression() &&
52
+ (t.isIdentifier(p.node.callee, { name: "_createVNode" }) ||
53
+ (t.isMemberExpression(p.node.callee) &&
54
+ t.isIdentifier(p.node.callee.property, { name: "_createVNode" })))
55
+ );
56
+
57
+ if (vNodeCall) {
58
+ hasCreateVNode = true;
59
+ const args = vNodeCall.node.arguments;
60
+ // PatchFlag = 8 (PROPS), dynamicProps = ["title"]
61
+ const patchFlagIndex = 3;
62
+ const dynamicPropsIndex = 4;
63
+
64
+ // 确保第 3 个参数(children)存在,否则补 null
65
+ if (args.length === 2) {
66
+ args.push(t.nullLiteral()); // => 第三个参数
67
+ }
68
+
69
+ // 补齐参数数组长度到第 5 个参数(index: 4)
70
+ while (args.length <= dynamicPropsIndex) {
71
+ args.push(null);
72
+ }
73
+
74
+ // 设置 patchFlag = 8
75
+ args[patchFlagIndex] = t.numericLiteral(8);
76
+
77
+ // 设置 dynamicProps = ["propKey"]
78
+ const existingDynamicProps = args[dynamicPropsIndex];
79
+ if (!existingDynamicProps || !t.isArrayExpression(existingDynamicProps)) {
80
+ args[dynamicPropsIndex] = t.arrayExpression([t.stringLiteral(propKey)]);
81
+ } else {
82
+ const existingKeys = new Set(
83
+ existingDynamicProps.elements
84
+ .filter(el => t.isStringLiteral(el))
85
+ .map(el => el.value)
86
+ );
87
+ if (!existingKeys.has(propKey)) {
88
+ existingDynamicProps.elements.push(t.stringLiteral(propKey));
89
+ }
90
+ }
91
+ }
92
+ }
93
+ return hasCreateVNode;
94
+ }
95
+
18
96
  function shouldTransform(path) {
19
97
  const parent = path.parentPath;
20
98
  return !(
@@ -52,8 +130,6 @@ function createI18nVisitor(option, i18nMap) {
52
130
  return;
53
131
  }
54
132
 
55
- // console.log("CallExpression", path.node.arguments);
56
-
57
133
  const hashed = generateId(keyText);
58
134
 
59
135
  if (i18nMap) {
@@ -104,18 +180,50 @@ function createI18nVisitor(option, i18nMap) {
104
180
  return;
105
181
  }
106
182
 
107
- // console.log("StringLiteral", value);
108
-
109
183
  const hashed = generateId(value);
110
184
 
111
185
  if (i18nMap) {
112
186
  i18nMap[hashed] = value;
113
187
  }
114
188
 
115
- const callExpression = t.callExpression(
116
- t.identifier(option.translateKey),
189
+ // 生成 _ctx.$t("hashed")
190
+ let callExpression = t.callExpression(
191
+ t.memberExpression(
192
+ t.identifier("_ctx"),
193
+ t.identifier(option.translateKey)
194
+ ),
117
195
  [t.stringLiteral(hashed)]
118
196
  );
197
+
198
+ // 判断是否createTextVNode或MemberExpression(如 Vue.createTextVNode)
199
+ if (isVNodeCall(parentPath, "_createTextVNode")) {
200
+ // 如果 createTextVNode 参数只有一个,则补充第二个参数 1
201
+ const args = parentPath.node.arguments;
202
+ if (args.length === 1) {
203
+ parentPath.node.arguments = [callExpression, t.numericLiteral(1)];
204
+ }
205
+ // 是否 vue.createElementVNode(...),且该 StringLiteral 是第三个参数
206
+ } else if (isVNodeCall(parentPath, "_createElementVNode")) {
207
+ const callExpr = parentPath.node;
208
+ const args = callExpr.arguments;
209
+
210
+ // 当前是第三个参数,且参数数量为3
211
+ const argIndex = args.findIndex(arg => arg === path.node);
212
+ if (argIndex === 2 && args.length === 3) {
213
+ args[2] = callExpression; // 替换第3个参数
214
+ args.push(t.numericLiteral(1)); // 添加第4个参数
215
+ return;
216
+ }
217
+ } else {
218
+ const hasCreateVNode = transformDirectiveIfNeeded(path, parentPath);
219
+ if (!hasCreateVNode) {
220
+ // 生成 $t("hashed")
221
+ callExpression = t.callExpression(t.identifier(option.translateKey), [
222
+ t.stringLiteral(hashed)
223
+ ]);
224
+ }
225
+ }
226
+
119
227
  // 如果是 JSX 属性值,需要包裹在 JSXExpressionContainer 中
120
228
  if (parentPath.isJSXAttribute()) {
121
229
  const jsxExpression = t.jsxExpressionContainer(callExpression);
@@ -152,8 +260,6 @@ function createI18nVisitor(option, i18nMap) {
152
260
 
153
261
  if (option.extractFromText === false) return;
154
262
 
155
- // console.log("TemplateElement", value);
156
-
157
263
  const hashed = generateId(value);
158
264
 
159
265
  if (i18nMap) {
@@ -175,8 +281,6 @@ function createI18nVisitor(option, i18nMap) {
175
281
  return;
176
282
  }
177
283
 
178
- // console.log("JSXText", path.node.value);
179
-
180
284
  const hashed = generateId(text);
181
285
 
182
286
  if (i18nMap) {
@@ -205,8 +309,6 @@ function createI18nVisitor(option, i18nMap) {
205
309
  return;
206
310
  }
207
311
 
208
- // console.log("JSXExpressionContainer", path.node.expression.expr);
209
-
210
312
  const hashed = generateId(value);
211
313
 
212
314
  if (i18nMap) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-i18n-extract-plugin",
3
- "version": "1.0.50",
3
+ "version": "1.0.52",
4
4
  "main": "lib/index.js",
5
5
  "types": "types/index.d.ts",
6
6
  "bin": {