@intlify/unplugin-vue-i18n 11.0.3 → 11.1.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/lib/index.cjs CHANGED
@@ -16,7 +16,6 @@ const pc = require('picocolors');
16
16
  const eslintUitls = require('@eslint-community/eslint-utils');
17
17
  const vueI18nExtensions = require('@intlify/vue-i18n-extensions');
18
18
  const scopeManager = require('@typescript-eslint/scope-manager');
19
- const typescriptEstree = require('@typescript-eslint/typescript-estree');
20
19
 
21
20
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
22
21
 
@@ -50,6 +49,11 @@ function resolveOptions(options) {
50
49
  const optimizeTranslationDirective = shared.isString(options.optimizeTranslationDirective) || shared.isArray(options.optimizeTranslationDirective) ? options.optimizeTranslationDirective : !!options.optimizeTranslationDirective;
51
50
  const translationIdentifiers = /* @__PURE__ */ new Map();
52
51
  const transformI18nBlock = typeof options.transformI18nBlock === "function" ? options.transformI18nBlock : null;
52
+ const treeShaking = options.treeShaking ? typeof options.treeShaking === "object" ? {
53
+ safelist: options.treeShaking.safelist || [],
54
+ dynamicKeyStrategy: options.treeShaking.dynamicKeyStrategy || "keep-all",
55
+ scanPatterns: options.treeShaking.scanPatterns
56
+ } : { safelist: [], dynamicKeyStrategy: "keep-all" } : null;
53
57
  return {
54
58
  include: normalizeGlobOption(options.include || []),
55
59
  exclude: normalizeGlobOption(options.exclude),
@@ -68,7 +72,8 @@ function resolveOptions(options) {
68
72
  escapeHtml,
69
73
  optimizeTranslationDirective,
70
74
  translationIdentifiers,
71
- transformI18nBlock
75
+ transformI18nBlock,
76
+ treeShaking
72
77
  };
73
78
  }
74
79
 
@@ -180,7 +185,7 @@ const RE_VIRTUAL_PREFIXED_INTLIFY_BUNDLE_IMPORT_ID = new RegExp(
180
185
  const RE_RESOURCE_FORMAT = /\.(json5?|ya?ml|[c|m]?[j|t]s)$/;
181
186
  const RE_SFC_I18N_CUSTOM_BLOCK = /\?vue&type=i18n/;
182
187
  const RE_SFC_I18N_WEBPACK_CUSTOM_BLOCK = /blockType=i18n/;
183
- const debug$2 = createDebug__default(resolveNamespace("resource"));
188
+ const debug$3 = createDebug__default(resolveNamespace("resource"));
184
189
  function resourcePlugin({
185
190
  onlyLocales,
186
191
  include,
@@ -198,7 +203,7 @@ function resourcePlugin({
198
203
  allowDynamic,
199
204
  escapeHtml,
200
205
  transformI18nBlock
201
- }, meta) {
206
+ }, meta, collector) {
202
207
  let viteModule = null;
203
208
  async function getViteModule() {
204
209
  if (viteModule != null) {
@@ -226,15 +231,15 @@ function resourcePlugin({
226
231
  return _filter;
227
232
  }
228
233
  if (meta.framework == "webpack") {
229
- debug$2("Using filter for webpack");
234
+ debug$3("Using filter for webpack");
230
235
  _filter = pluginutils.createFilter(...resolveIncludeExclude());
231
236
  } else {
232
237
  const viteModule2 = await getViteModule();
233
238
  if (viteModule2.rolldownVersion) {
234
- debug$2("Using filter for rolldown-vite");
239
+ debug$3("Using filter for rolldown-vite");
235
240
  _filter = pluginutils.createFilter(...resolveIncludeExclude());
236
241
  } else {
237
- debug$2("Using filter for rollup-vite");
242
+ debug$3("Using filter for rollup-vite");
238
243
  _filter = pluginutils.createFilter(...resolveIncludeExcludeForLegacy());
239
244
  }
240
245
  }
@@ -246,7 +251,7 @@ function resourcePlugin({
246
251
  let isProduction = false;
247
252
  let sourceMap = false;
248
253
  const vueI18nAliasName = module;
249
- debug$2(`vue-i18n alias name: ${vueI18nAliasName}`);
254
+ debug$3(`vue-i18n alias name: ${vueI18nAliasName}`);
250
255
  let vuePlugin = null;
251
256
  const getSfcParser = () => {
252
257
  return vuePlugin ? getVueCompiler(vuePlugin).parse : compilerSfc.parse;
@@ -272,7 +277,7 @@ function resourcePlugin({
272
277
  __VUE_I18N_PROD_DEVTOOLS__: false
273
278
  }
274
279
  };
275
- debug$2("define Config:", defineConfig);
280
+ debug$3("define Config:", defineConfig);
276
281
  const aliasConfig = {
277
282
  resolve: {
278
283
  alias: {
@@ -283,7 +288,7 @@ function resourcePlugin({
283
288
  }
284
289
  }
285
290
  };
286
- debug$2("alias Config:", aliasConfig);
291
+ debug$3("alias Config:", aliasConfig);
287
292
  return shared.assign(defineConfig, aliasConfig);
288
293
  },
289
294
  async configResolved(config) {
@@ -293,9 +298,9 @@ function resourcePlugin({
293
298
  }
294
299
  isProduction = config.isProduction;
295
300
  sourceMap = config.command === "build" ? !!config.build.sourcemap : false;
296
- debug$2(`configResolved: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
301
+ debug$3(`configResolved: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
297
302
  const isRolldownVite = !!(await getViteModule()).rolldownVersion;
298
- debug$2(`Using ${isRolldownVite ? "rolldown-vite" : "vite"} for the build`);
303
+ debug$3(`Using ${isRolldownVite ? "rolldown-vite" : "vite"} for the build`);
299
304
  if (!isRolldownVite) {
300
305
  const jsonPlugin = getVitePlugin(config, "vite:json");
301
306
  if (jsonPlugin) {
@@ -309,7 +314,7 @@ function resourcePlugin({
309
314
  if (query.vue) {
310
315
  return;
311
316
  }
312
- debug$2("org json plugin");
317
+ debug$3("org json plugin");
313
318
  return orgTransform.apply(this, [code, id]);
314
319
  }
315
320
  jsonPlugin.transform = overrideJson;
@@ -334,7 +339,7 @@ function resourcePlugin({
334
339
  webpack(compiler) {
335
340
  isProduction = compiler.options.mode !== "development";
336
341
  sourceMap = !!compiler.options.devtool;
337
- debug$2(`webpack: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
342
+ debug$3(`webpack: isProduction = ${isProduction}, sourceMap = ${sourceMap}`);
338
343
  compiler.options.resolve = normalizeConfigResolveAlias(
339
344
  compiler.options.resolve,
340
345
  meta.framework
@@ -343,7 +348,7 @@ function resourcePlugin({
343
348
  ssr: ssrBuild,
344
349
  runtimeOnly
345
350
  });
346
- debug$2(
351
+ debug$3(
347
352
  `set ${vueI18nAliasName}: ${getVueI18nAliasPath({
348
353
  ssr: ssrBuild,
349
354
  runtimeOnly
@@ -356,15 +361,15 @@ function resourcePlugin({
356
361
  __INTLIFY_PROD_DEVTOOLS__: "false"
357
362
  })
358
363
  );
359
- debug$2(`set __VUE_I18N_LEGACY_API__ is '${compositionOnly}'`);
360
- debug$2(`set __VUE_I18N_FULL_INSTALL__ is '${fullInstall}'`);
364
+ debug$3(`set __VUE_I18N_LEGACY_API__ is '${compositionOnly}'`);
365
+ debug$3(`set __VUE_I18N_FULL_INSTALL__ is '${fullInstall}'`);
361
366
  const filter = pluginutils.createFilter(...resolveIncludeExclude());
362
367
  if (compiler.options.module) {
363
368
  compiler.options.module.rules.push({
364
369
  test: RE_RESOURCE_FORMAT,
365
370
  type: "javascript/auto",
366
371
  include(resource) {
367
- debug$2("webpack resource include", resource);
372
+ debug$3("webpack resource include", resource);
368
373
  return filter(resource);
369
374
  }
370
375
  });
@@ -383,7 +388,7 @@ function resourcePlugin({
383
388
  id: RE_VIRTUAL_PREFIXED_INTLIFY_BUNDLE_IMPORT_ID
384
389
  },
385
390
  async handler(id) {
386
- debug$2("load", id);
391
+ debug$3("load", id);
387
392
  if (INTLIFY_BUNDLE_IMPORT_ID === getVirtualId(id, meta.framework) && include) {
388
393
  let resourcePaths = [];
389
394
  for (const inc of include) {
@@ -393,7 +398,8 @@ function resourcePlugin({
393
398
  const code = await generateBundleResources(resourcePaths, isProduction, {
394
399
  forceStringify,
395
400
  strictMessage,
396
- escapeHtml
401
+ escapeHtml,
402
+ usedKeyFilter: collector ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
397
403
  });
398
404
  return {
399
405
  moduleType: "js",
@@ -410,7 +416,7 @@ function resourcePlugin({
410
416
  return;
411
417
  }
412
418
  const { filename, query } = parseVueRequest(id);
413
- debug$2("transform", id, JSON.stringify(query), filename);
419
+ debug$3("transform", id, JSON.stringify(query), filename);
414
420
  let langInfo = defaultSFCLang;
415
421
  let inSourceMap;
416
422
  if (!query.vue) {
@@ -429,13 +435,14 @@ function resourcePlugin({
429
435
  escapeHtml,
430
436
  jit: true,
431
437
  onlyLocales,
432
- forceStringify
438
+ forceStringify,
439
+ usedKeyFilter: collector ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
433
440
  }
434
441
  );
435
- debug$2("parseOptions", parseOptions);
442
+ debug$3("parseOptions", parseOptions);
436
443
  const { code: generatedCode, map } = await generate(code, parseOptions);
437
- debug$2("generated code", generatedCode);
438
- debug$2("sourcemap", map, sourceMap);
444
+ debug$3("generated code", generatedCode);
445
+ debug$3("sourcemap", map, sourceMap);
439
446
  if (code === generatedCode) return;
440
447
  return {
441
448
  moduleType: "js",
@@ -450,8 +457,9 @@ function resourcePlugin({
450
457
  } else if (defaultSFCLang) {
451
458
  langInfo = defaultSFCLang;
452
459
  }
453
- debug$2("langInfo", langInfo);
460
+ debug$3("langInfo", langInfo);
454
461
  const generate = /\.?json5?/.test(langInfo) ? bundleUtils.generateJSON : bundleUtils.generateYAML;
462
+ const isGlobalBlock = globalSFCScope || !!query.global;
455
463
  const parseOptions = getOptions(
456
464
  filename,
457
465
  isProduction,
@@ -464,10 +472,11 @@ function resourcePlugin({
464
472
  strictMessage,
465
473
  escapeHtml,
466
474
  onlyLocales,
467
- forceStringify
475
+ forceStringify,
476
+ usedKeyFilter: collector && isGlobalBlock ? (keyPath) => collector.shouldKeepKey(keyPath) : void 0
468
477
  }
469
478
  );
470
- debug$2("parseOptions", parseOptions);
479
+ debug$3("parseOptions", parseOptions);
471
480
  let source = await getCode(
472
481
  code,
473
482
  filename,
@@ -485,8 +494,8 @@ function resourcePlugin({
485
494
  }
486
495
  }
487
496
  const { code: generatedCode, map } = generate(source, parseOptions);
488
- debug$2("generated code", generatedCode);
489
- debug$2("sourcemap", map, sourceMap);
497
+ debug$3("generated code", generatedCode);
498
+ debug$3("sourcemap", map, sourceMap);
490
499
  if (code === generatedCode) return;
491
500
  return {
492
501
  moduleType: "js",
@@ -542,11 +551,12 @@ async function generateBundleResources(resources, isProduction, {
542
551
  strictMessage = true,
543
552
  escapeHtml = false,
544
553
  jit = true,
545
- transformI18nBlock = void 0
554
+ transformI18nBlock = void 0,
555
+ usedKeyFilter = void 0
546
556
  }) {
547
557
  const codes = [];
548
558
  for (const res of resources) {
549
- debug$2(`${res} bundle loading ...`);
559
+ debug$3(`${res} bundle loading ...`);
550
560
  if (/\.(json5?|ya?ml)$/.test(res)) {
551
561
  const { ext, name } = path.parse(res);
552
562
  const source = await getRaw(res);
@@ -558,11 +568,12 @@ async function generateBundleResources(resources, isProduction, {
558
568
  strictMessage,
559
569
  escapeHtml,
560
570
  forceStringify,
561
- transformI18nBlock
571
+ transformI18nBlock,
572
+ usedKeyFilter
562
573
  });
563
574
  parseOptions.type = "bare";
564
575
  const { code } = generate(source, parseOptions);
565
- debug$2("generated code", code);
576
+ debug$3("generated code", code);
566
577
  codes.push(`${JSON.stringify(name)}: ${code}`);
567
578
  }
568
579
  }
@@ -597,7 +608,7 @@ async function getCode(source, filename, sourceMap, query, parser, framework = "
597
608
  }
598
609
  if (framework === "webpack") {
599
610
  if (issuerPath) {
600
- debug$2(`getCode (webpack) ${index} via issuerPath`, issuerPath);
611
+ debug$3(`getCode (webpack) ${index} via issuerPath`, issuerPath);
601
612
  return await getRaw(filename);
602
613
  } else {
603
614
  const result = parser(await getRaw(filename), {
@@ -607,7 +618,7 @@ async function getCode(source, filename, sourceMap, query, parser, framework = "
607
618
  const block = result.descriptor.customBlocks[index];
608
619
  if (block) {
609
620
  const code = block.src ? await getRaw(block.src) : block.content;
610
- debug$2(`getCode (webpack) ${index} from SFC`, code);
621
+ debug$3(`getCode (webpack) ${index} from SFC`, code);
611
622
  return code;
612
623
  } else {
613
624
  return source;
@@ -631,7 +642,8 @@ function getOptions(filename, isProduction, query, sourceMap, {
631
642
  strictMessage = true,
632
643
  escapeHtml = false,
633
644
  jit = true,
634
- transformI18nBlock = null
645
+ transformI18nBlock = null,
646
+ usedKeyFilter = void 0
635
647
  }) {
636
648
  const mode = isProduction ? "production" : "development";
637
649
  const baseOptions = {
@@ -644,6 +656,7 @@ function getOptions(filename, isProduction, query, sourceMap, {
644
656
  escapeHtml,
645
657
  jit,
646
658
  onlyLocales,
659
+ usedKeyFilter,
647
660
  env: mode,
648
661
  transformI18nBlock,
649
662
  onWarn: (msg) => {
@@ -688,7 +701,12 @@ async function getRaw(path) {
688
701
  return node_fs.promises.readFile(path, { encoding: "utf-8" });
689
702
  }
690
703
 
691
- const debug$1 = createDebug__default(resolveNamespace("directive"));
704
+ const debug$2 = createDebug__default(resolveNamespace("directive"));
705
+ const tsEstree = {
706
+ parse: void 0,
707
+ simpleTraverse: void 0,
708
+ AST_NODE_TYPES: void 0
709
+ };
692
710
  function directivePlugin({
693
711
  optimizeTranslationDirective,
694
712
  translationIdentifiers
@@ -700,7 +718,13 @@ function directivePlugin({
700
718
  name: resolveNamespace("directive"),
701
719
  enforce: "pre",
702
720
  vite: {
703
- config(config) {
721
+ async config(config) {
722
+ await import('@typescript-eslint/typescript-estree').then((r) => {
723
+ tsEstree.parse = r.parse;
724
+ tsEstree.simpleTraverse = r.simpleTraverse;
725
+ tsEstree.AST_NODE_TYPES = r.AST_NODE_TYPES;
726
+ return;
727
+ });
704
728
  vuePlugin = getVitePlugin(config, "vite:vue");
705
729
  if (!checkVuePlugin(vuePlugin)) {
706
730
  return;
@@ -711,7 +735,7 @@ function directivePlugin({
711
735
  optimizeTranslationDirective,
712
736
  translationIdentifiers
713
737
  );
714
- debug$1(`vite:vue options['template']:`, vuePlugin.api.options);
738
+ debug$2(`vite:vue options['template']:`, vuePlugin.api.options);
715
739
  }
716
740
  },
717
741
  configResolved(config) {
@@ -754,7 +778,7 @@ function resolveVueOptions(vuePlugin, optimizeTranslationDirective, translationI
754
778
  const vuePluginOptions = getVuePluginOptions(vuePlugin);
755
779
  const normalizedFilename = normalizePath(path__default.relative(vuePluginOptions.root, filename));
756
780
  const resolveIdentifier2 = translationIdentifiers.get(normalizedFilename);
757
- debug$1("resolved vue-i18n Identifier", resolveIdentifier2);
781
+ debug$2("resolved vue-i18n Identifier", resolveIdentifier2);
758
782
  if (resolveIdentifier2 == null) {
759
783
  return void 0;
760
784
  }
@@ -772,12 +796,12 @@ function resolveVueOptions(vuePlugin, optimizeTranslationDirective, translationI
772
796
  }
773
797
  function analyzeIdentifiers(descriptor, { root }, translationIdentifiers) {
774
798
  const source = descriptor.scriptSetup?.content || descriptor.script?.content;
775
- debug$1("getDescriptor content", source);
799
+ debug$2("getDescriptor content", source);
776
800
  if (!source) {
777
801
  return;
778
802
  }
779
- const ast = typescriptEstree.parse(source, { range: true });
780
- typescriptEstree.simpleTraverse(ast, {
803
+ const ast = tsEstree.parse(source, { range: true });
804
+ tsEstree.simpleTraverse(ast, {
781
805
  enter(node, parent) {
782
806
  if (parent) {
783
807
  node.parent = parent;
@@ -790,11 +814,11 @@ function analyzeIdentifiers(descriptor, { root }, translationIdentifiers) {
790
814
  if (importLocalName == null) {
791
815
  return;
792
816
  }
793
- debug$1("importLocalName", importLocalName);
817
+ debug$2("importLocalName", importLocalName);
794
818
  const resolvedIdentifier = getVueI18nIdentifier(scope, importLocalName);
795
819
  if (resolvedIdentifier) {
796
820
  const normalizedFilename = normalizePath(path__default.relative(root, descriptor.filename));
797
- debug$1("set vue-i18n resolved identifier: ", resolvedIdentifier);
821
+ debug$2("set vue-i18n resolved identifier: ", resolvedIdentifier);
798
822
  translationIdentifiers.set(normalizedFilename, resolvedIdentifier);
799
823
  }
800
824
  }
@@ -842,9 +866,9 @@ function getVueI18nIdentifier(scope, local) {
842
866
  return null;
843
867
  }
844
868
  const variableIdPairs = parseVariableId(id);
845
- debug$1("variableIdPairs:", variableIdPairs);
869
+ debug$2("variableIdPairs:", variableIdPairs);
846
870
  const returnVariableIdPairs = parseReturnStatement(returnStatement);
847
- debug$1("returnVariableIdPairs:", returnVariableIdPairs);
871
+ debug$2("returnVariableIdPairs:", returnVariableIdPairs);
848
872
  return resolveIdentifier(variableIdPairs, returnVariableIdPairs);
849
873
  }
850
874
  const EMPTY_NODE_RETURN = {
@@ -857,18 +881,18 @@ function getCallExpressionAndReturnStatement(scope, local) {
857
881
  return EMPTY_NODE_RETURN;
858
882
  }
859
883
  const callExpressionRef = variable.references.find((ref) => {
860
- return ref.identifier.parent?.type === typescriptEstree.AST_NODE_TYPES.CallExpression;
884
+ return ref.identifier.parent?.type === tsEstree.AST_NODE_TYPES.CallExpression;
861
885
  });
862
886
  if (callExpressionRef == null) {
863
887
  return EMPTY_NODE_RETURN;
864
888
  }
865
889
  let returnStatement = null;
866
- if (callExpressionRef.from.type === "function" && callExpressionRef.from.block.type === typescriptEstree.AST_NODE_TYPES.FunctionExpression && // @ts-expect-error -- FIXME: type error
867
- callExpressionRef.from.block.parent.type === typescriptEstree.AST_NODE_TYPES.Property && // @ts-expect-error -- FIXME: type error
868
- callExpressionRef.from.block.parent.key.type === typescriptEstree.AST_NODE_TYPES.Identifier && // @ts-expect-error -- FIXME: type error
890
+ if (callExpressionRef.from.type === "function" && callExpressionRef.from.block.type === tsEstree.AST_NODE_TYPES.FunctionExpression && // @ts-expect-error -- FIXME: type error
891
+ callExpressionRef.from.block.parent.type === tsEstree.AST_NODE_TYPES.Property && // @ts-expect-error -- FIXME: type error
892
+ callExpressionRef.from.block.parent.key.type === tsEstree.AST_NODE_TYPES.Identifier && // @ts-expect-error -- FIXME: type error
869
893
  callExpressionRef.from.block.parent.key.name === "setup") {
870
894
  returnStatement = callExpressionRef.from.block.body.body.find((statement) => {
871
- return statement.type === typescriptEstree.AST_NODE_TYPES.ReturnStatement;
895
+ return statement.type === tsEstree.AST_NODE_TYPES.ReturnStatement;
872
896
  });
873
897
  }
874
898
  return {
@@ -878,22 +902,22 @@ function getCallExpressionAndReturnStatement(scope, local) {
878
902
  };
879
903
  }
880
904
  function getVariableDeclarationIdFrom(node) {
881
- if (node.parent?.type !== typescriptEstree.AST_NODE_TYPES.VariableDeclarator) {
905
+ if (node.parent?.type !== tsEstree.AST_NODE_TYPES.VariableDeclarator) {
882
906
  return null;
883
907
  }
884
908
  return node.parent.id;
885
909
  }
886
910
  function parseVariableId(node) {
887
- if (node.type === typescriptEstree.AST_NODE_TYPES.Identifier) {
911
+ if (node.type === tsEstree.AST_NODE_TYPES.Identifier) {
888
912
  return [{ key: node.name, value: null }];
889
913
  } else {
890
914
  const props = node.properties.filter(
891
915
  // ignore RestElement
892
- (prop) => prop.type === typescriptEstree.AST_NODE_TYPES.Property
916
+ (prop) => prop.type === tsEstree.AST_NODE_TYPES.Property
893
917
  );
894
918
  const pairs = [];
895
919
  for (const prop of props) {
896
- if (prop?.key.type === typescriptEstree.AST_NODE_TYPES.Identifier && prop?.value.type === typescriptEstree.AST_NODE_TYPES.Identifier) {
920
+ if (prop?.key.type === tsEstree.AST_NODE_TYPES.Identifier && prop?.value.type === tsEstree.AST_NODE_TYPES.Identifier) {
897
921
  pairs.push({ key: prop.key.name, value: prop.value.name });
898
922
  }
899
923
  }
@@ -905,12 +929,12 @@ function parseReturnStatement(node) {
905
929
  if (node == null || node.argument == null) {
906
930
  return pairs;
907
931
  }
908
- if (node.argument.type === typescriptEstree.AST_NODE_TYPES.ObjectExpression) {
932
+ if (node.argument.type === tsEstree.AST_NODE_TYPES.ObjectExpression) {
909
933
  for (const prop of node.argument.properties) {
910
- if (prop.type === typescriptEstree.AST_NODE_TYPES.Property) {
911
- if (prop.key.type === typescriptEstree.AST_NODE_TYPES.Identifier && prop.value.type === typescriptEstree.AST_NODE_TYPES.Identifier) {
934
+ if (prop.type === tsEstree.AST_NODE_TYPES.Property) {
935
+ if (prop.key.type === tsEstree.AST_NODE_TYPES.Identifier && prop.value.type === tsEstree.AST_NODE_TYPES.Identifier) {
912
936
  pairs.push({ key: prop.key.name, value: prop.value.name });
913
- } else if (prop.key.type === typescriptEstree.AST_NODE_TYPES.Identifier && prop.value.type === typescriptEstree.AST_NODE_TYPES.MemberExpression && prop.value.object.type === typescriptEstree.AST_NODE_TYPES.Identifier && prop.value.property.type === typescriptEstree.AST_NODE_TYPES.Identifier) {
937
+ } else if (prop.key.type === tsEstree.AST_NODE_TYPES.Identifier && prop.value.type === tsEstree.AST_NODE_TYPES.MemberExpression && prop.value.object.type === tsEstree.AST_NODE_TYPES.Identifier && prop.value.property.type === tsEstree.AST_NODE_TYPES.Identifier) {
914
938
  pairs.push({
915
939
  key: prop.key.name,
916
940
  value: `${prop.value.object.name}.${prop.value.property.name}`
@@ -919,7 +943,7 @@ function parseReturnStatement(node) {
919
943
  }
920
944
  }
921
945
  return pairs;
922
- } else if (node.argument.type === typescriptEstree.AST_NODE_TYPES.Identifier) {
946
+ } else if (node.argument.type === tsEstree.AST_NODE_TYPES.Identifier) {
923
947
  return pairs;
924
948
  } else {
925
949
  return pairs;
@@ -956,6 +980,270 @@ function resolveIdentifier(localVariables, returnVariable) {
956
980
  }
957
981
  }
958
982
 
983
+ function matchSafelistPattern(keyPath, pattern) {
984
+ const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^.]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
985
+ return new RegExp(`^${regexStr}$`).test(keyPath);
986
+ }
987
+ function createUsedKeysCollector(options) {
988
+ const usedKeys = /* @__PURE__ */ new Set();
989
+ const safelistPatterns = options.safelist || [];
990
+ const dynamicKeyStrategy = options.dynamicKeyStrategy || "keep-all";
991
+ const removedKeys = /* @__PURE__ */ new Map();
992
+ let dynamicKeysDetected = false;
993
+ return {
994
+ get usedKeys() {
995
+ return usedKeys;
996
+ },
997
+ get dynamicKeysDetected() {
998
+ return dynamicKeysDetected;
999
+ },
1000
+ set dynamicKeysDetected(val) {
1001
+ dynamicKeysDetected = val;
1002
+ },
1003
+ safelistPatterns,
1004
+ removedKeys,
1005
+ addKey(key) {
1006
+ usedKeys.add(key);
1007
+ },
1008
+ markDynamic() {
1009
+ dynamicKeysDetected = true;
1010
+ },
1011
+ shouldKeepKey(keyPath) {
1012
+ if (dynamicKeysDetected && dynamicKeyStrategy === "keep-all") {
1013
+ return true;
1014
+ }
1015
+ if (safelistPatterns.some((pattern) => matchSafelistPattern(keyPath, pattern))) {
1016
+ return true;
1017
+ }
1018
+ if (usedKeys.has(keyPath)) {
1019
+ return true;
1020
+ }
1021
+ for (const key of usedKeys) {
1022
+ if (key.startsWith(keyPath + ".")) {
1023
+ return true;
1024
+ }
1025
+ }
1026
+ return false;
1027
+ },
1028
+ reportRemoved(filename, keyPath) {
1029
+ const existing = removedKeys.get(filename);
1030
+ if (existing) {
1031
+ existing.push(keyPath);
1032
+ } else {
1033
+ removedKeys.set(filename, [keyPath]);
1034
+ }
1035
+ },
1036
+ getDiagnostics() {
1037
+ let totalRemoved = 0;
1038
+ for (const keys of removedKeys.values()) {
1039
+ totalRemoved += keys.length;
1040
+ }
1041
+ return { totalRemoved, byFile: removedKeys };
1042
+ }
1043
+ };
1044
+ }
1045
+
1046
+ const I18N_FUNCTIONS = /* @__PURE__ */ new Set(["t", "$t", "tc", "$tc", "te", "$te", "d", "$d", "n", "$n"]);
1047
+ const TEMPLATE_T_REGEX = /\$?t\s*\(\s*['"]([^'"]+)['"]/g;
1048
+ const VT_DIRECTIVE_STRING_REGEX = /v-t\s*=\s*"'([^']+)'"/g;
1049
+ const VT_DIRECTIVE_PATH_REGEX = /v-t\s*=\s*"\{[^}]*path\s*:\s*'([^']+)'/g;
1050
+ const TEMPLATE_DYNAMIC_T_REGEX = /\$?t\s*\(\s*[a-zA-Z_$]/g;
1051
+ async function extractKeysFromScript(source) {
1052
+ const keys = [];
1053
+ let hasDynamic = false;
1054
+ let tsEstree;
1055
+ try {
1056
+ tsEstree = await import('@typescript-eslint/typescript-estree');
1057
+ } catch {
1058
+ return extractKeysFromScriptRegex(source);
1059
+ }
1060
+ try {
1061
+ const ast = tsEstree.parse(source, {
1062
+ range: true,
1063
+ jsx: true,
1064
+ allowInvalidAST: true,
1065
+ suppressDeprecatedPropertyWarnings: true
1066
+ });
1067
+ tsEstree.simpleTraverse(ast, {
1068
+ enter(node) {
1069
+ if (node.type !== tsEstree.AST_NODE_TYPES.CallExpression) {
1070
+ return;
1071
+ }
1072
+ const calleeName = getCalleeName(node, tsEstree.AST_NODE_TYPES);
1073
+ if (!calleeName || !I18N_FUNCTIONS.has(calleeName)) {
1074
+ return;
1075
+ }
1076
+ const firstArg = node.arguments[0];
1077
+ if (!firstArg) {
1078
+ return;
1079
+ }
1080
+ if (firstArg.type === tsEstree.AST_NODE_TYPES.Literal && typeof firstArg.value === "string") {
1081
+ keys.push(firstArg.value);
1082
+ } else if (firstArg.type === tsEstree.AST_NODE_TYPES.TemplateLiteral && firstArg.expressions.length === 0 && firstArg.quasis.length === 1) {
1083
+ const value = firstArg.quasis[0].value.cooked;
1084
+ if (value) {
1085
+ keys.push(value);
1086
+ }
1087
+ } else {
1088
+ hasDynamic = true;
1089
+ }
1090
+ }
1091
+ });
1092
+ } catch {
1093
+ return extractKeysFromScriptRegex(source);
1094
+ }
1095
+ return { keys, hasDynamic };
1096
+ }
1097
+ function getCalleeName(node, AST_NODE_TYPES) {
1098
+ const callee = node.callee;
1099
+ if (callee.type === AST_NODE_TYPES.Identifier) {
1100
+ return callee.name;
1101
+ }
1102
+ if (callee.type === AST_NODE_TYPES.MemberExpression && callee.property.type === AST_NODE_TYPES.Identifier) {
1103
+ return callee.property.name;
1104
+ }
1105
+ return null;
1106
+ }
1107
+ function extractKeysFromScriptRegex(source) {
1108
+ const keys = [];
1109
+ let match;
1110
+ const regex = /\b(?:\$?t|tc|\$tc|te|\$te|d|\$d|n|\$n)\s*\(\s*['"]([^'"]+)['"]/g;
1111
+ while ((match = regex.exec(source)) !== null) {
1112
+ keys.push(match[1]);
1113
+ }
1114
+ return { keys, hasDynamic: false };
1115
+ }
1116
+ function extractKeysFromTemplate(templateContent) {
1117
+ const keys = [];
1118
+ let hasDynamic = false;
1119
+ let match;
1120
+ TEMPLATE_T_REGEX.lastIndex = 0;
1121
+ VT_DIRECTIVE_STRING_REGEX.lastIndex = 0;
1122
+ VT_DIRECTIVE_PATH_REGEX.lastIndex = 0;
1123
+ TEMPLATE_DYNAMIC_T_REGEX.lastIndex = 0;
1124
+ while ((match = TEMPLATE_T_REGEX.exec(templateContent)) !== null) {
1125
+ keys.push(match[1]);
1126
+ }
1127
+ while ((match = VT_DIRECTIVE_STRING_REGEX.exec(templateContent)) !== null) {
1128
+ keys.push(match[1]);
1129
+ }
1130
+ while ((match = VT_DIRECTIVE_PATH_REGEX.exec(templateContent)) !== null) {
1131
+ keys.push(match[1]);
1132
+ }
1133
+ if (TEMPLATE_DYNAMIC_T_REGEX.test(templateContent)) {
1134
+ hasDynamic = true;
1135
+ }
1136
+ return { keys, hasDynamic };
1137
+ }
1138
+ async function analyzeVueSFC(content) {
1139
+ const keys = [];
1140
+ let hasDynamic = false;
1141
+ let parse;
1142
+ try {
1143
+ const compilerSfc = await import('vue/compiler-sfc');
1144
+ parse = compilerSfc.parse;
1145
+ } catch {
1146
+ const templateResult = extractKeysFromTemplate(content);
1147
+ const scriptResult = await extractKeysFromScript(content);
1148
+ return {
1149
+ keys: [...templateResult.keys, ...scriptResult.keys],
1150
+ hasDynamic: templateResult.hasDynamic || scriptResult.hasDynamic
1151
+ };
1152
+ }
1153
+ const { descriptor } = parse(content);
1154
+ if (descriptor.template?.content) {
1155
+ const templateResult = extractKeysFromTemplate(descriptor.template.content);
1156
+ keys.push(...templateResult.keys);
1157
+ if (templateResult.hasDynamic) {
1158
+ hasDynamic = true;
1159
+ }
1160
+ }
1161
+ const scriptContent = descriptor.scriptSetup?.content || descriptor.script?.content;
1162
+ if (scriptContent) {
1163
+ const scriptResult = await extractKeysFromScript(scriptContent);
1164
+ keys.push(...scriptResult.keys);
1165
+ if (scriptResult.hasDynamic) {
1166
+ hasDynamic = true;
1167
+ }
1168
+ }
1169
+ return { keys, hasDynamic };
1170
+ }
1171
+ async function analyzeFile(content, filePath, collector) {
1172
+ let result;
1173
+ if (filePath.endsWith(".vue")) {
1174
+ result = await analyzeVueSFC(content);
1175
+ } else {
1176
+ result = await extractKeysFromScript(content);
1177
+ }
1178
+ for (const key of result.keys) {
1179
+ collector.addKey(key);
1180
+ }
1181
+ if (result.hasDynamic) {
1182
+ collector.markDynamic();
1183
+ }
1184
+ }
1185
+
1186
+ const debug$1 = createDebug__default(resolveNamespace("tree-shaking"));
1187
+ function treeShakingPlugin(resolvedOptions, collector) {
1188
+ let projectRoot = "";
1189
+ return {
1190
+ name: resolveNamespace("tree-shaking"),
1191
+ enforce: "pre",
1192
+ vite: {
1193
+ configResolved(config) {
1194
+ projectRoot = config.root;
1195
+ }
1196
+ },
1197
+ webpack(compiler) {
1198
+ projectRoot = compiler.options.context || process.cwd();
1199
+ },
1200
+ async buildStart() {
1201
+ const treeShaking = resolvedOptions.treeShaking;
1202
+ const patterns = treeShaking?.scanPatterns || [`${projectRoot}/src/**/*.{vue,ts,js,tsx,jsx}`];
1203
+ debug$1("scanning patterns:", patterns);
1204
+ const files = await fg__default(patterns, {
1205
+ ignore: ["**/node_modules/**"],
1206
+ absolute: true
1207
+ });
1208
+ debug$1(`found ${files.length} source files to scan`);
1209
+ for (const file of files) {
1210
+ try {
1211
+ const content = await node_fs.promises.readFile(file, "utf-8");
1212
+ await analyzeFile(content, file, collector);
1213
+ } catch (err) {
1214
+ debug$1(`failed to analyze file: ${file}`, err);
1215
+ }
1216
+ }
1217
+ debug$1(
1218
+ `scan complete: ${collector.usedKeys.size} used keys found, dynamic: ${collector.dynamicKeysDetected}`
1219
+ );
1220
+ if (collector.dynamicKeysDetected) {
1221
+ const strategy = treeShaking?.dynamicKeyStrategy || "keep-all";
1222
+ if (strategy === "keep-all") {
1223
+ warn(
1224
+ "Tree-shaking: dynamic key usage detected. All keys will be preserved (dynamicKeyStrategy: keep-all)."
1225
+ );
1226
+ } else {
1227
+ warn(
1228
+ "Tree-shaking: dynamic key usage detected. Unused keys will still be removed (dynamicKeyStrategy: ignore)."
1229
+ );
1230
+ }
1231
+ }
1232
+ },
1233
+ buildEnd() {
1234
+ const diagnostics = collector.getDiagnostics();
1235
+ if (diagnostics.totalRemoved > 0) {
1236
+ debug$1(`tree-shaking: removed ${diagnostics.totalRemoved} unused message keys`);
1237
+ for (const [file, keys] of diagnostics.byFile) {
1238
+ debug$1(` ${file}: removed ${keys.length} keys (${keys.join(", ")})`);
1239
+ }
1240
+ } else {
1241
+ debug$1("tree-shaking: no keys removed");
1242
+ }
1243
+ }
1244
+ };
1245
+ }
1246
+
959
1247
  const debug = createDebug__default(resolveNamespace("root"));
960
1248
  const unpluginFactory = (options = {}, meta) => {
961
1249
  debug("meta framework", meta.framework);
@@ -965,7 +1253,12 @@ const unpluginFactory = (options = {}, meta) => {
965
1253
  debug("plugin options (resolving):", options);
966
1254
  const resolvedOptions = resolveOptions(options);
967
1255
  debug("plugin options (resolved):", resolvedOptions);
968
- const plugins = [resourcePlugin(resolvedOptions, meta)];
1256
+ const collector = resolvedOptions.treeShaking ? createUsedKeysCollector(resolvedOptions.treeShaking) : null;
1257
+ const plugins = [];
1258
+ if (resolvedOptions.treeShaking && collector) {
1259
+ plugins.push(treeShakingPlugin(resolvedOptions, collector));
1260
+ }
1261
+ plugins.push(resourcePlugin(resolvedOptions, meta, collector));
969
1262
  if (resolvedOptions.optimizeTranslationDirective) {
970
1263
  if (meta.framework === "webpack") {
971
1264
  raiseError(