@vocoder/cli 0.8.0 → 0.10.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.
@@ -43584,7 +43584,8 @@ function detectLocalEcosystem(cwd = process.cwd()) {
43584
43584
  hasExtractor: false,
43585
43585
  hasConfig: false,
43586
43586
  hasUiPackage: false,
43587
- sourceLocale: null
43587
+ sourceLocale: null,
43588
+ isTypeScript: existsSync(join(cwd, "tsconfig.json"))
43588
43589
  };
43589
43590
  }
43590
43591
  const allDeps = {
@@ -43594,6 +43595,7 @@ function detectLocalEcosystem(cwd = process.cwd()) {
43594
43595
  const hasUnplugin = "@vocoder/plugin" in allDeps;
43595
43596
  const hasExtractor = "@vocoder/extractor" in allDeps;
43596
43597
  const hasConfig = "@vocoder/config" in allDeps;
43598
+ const isTypeScript = existsSync(join(cwd, "tsconfig.json")) || "typescript" in allDeps;
43597
43599
  const { ecosystem, framework, uiPackage } = detectFromDeps(allDeps, cwd);
43598
43600
  const hasUiPackage = uiPackage !== null && uiPackage in allDeps;
43599
43601
  return {
@@ -43605,7 +43607,8 @@ function detectLocalEcosystem(cwd = process.cwd()) {
43605
43607
  hasExtractor,
43606
43608
  hasConfig,
43607
43609
  hasUiPackage,
43608
- sourceLocale: null
43610
+ sourceLocale: null,
43611
+ isTypeScript
43609
43612
  };
43610
43613
  }
43611
43614
  function detectPackageManager(cwd) {
@@ -43906,6 +43909,12 @@ function extractFromObject(obj) {
43906
43909
  if (key === "localesPath" && prop.value.type === "StringLiteral") {
43907
43910
  config.localesPath = prop.value.value;
43908
43911
  }
43912
+ if (key === "appIndustry" && prop.value.type === "StringLiteral") {
43913
+ config.appIndustry = prop.value.value;
43914
+ }
43915
+ if (key === "formality" && prop.value.type === "StringLiteral") {
43916
+ config.formality = prop.value.value;
43917
+ }
43909
43918
  }
43910
43919
  return config;
43911
43920
  }
@@ -50390,242 +50399,335 @@ var StringExtractor = class {
50390
50399
  const sortedFiles = Array.from(allFiles).sort();
50391
50400
  for (const file of sortedFiles) {
50392
50401
  try {
50393
- const strings = await this.extractFromFile(file, projectRoot);
50402
+ const code = readFileSync3(file, "utf-8");
50403
+ const relPath = pathRelative(projectRoot, file).split("\\").join("/");
50404
+ const strings = _extractFromContent(relPath, code);
50394
50405
  allStrings.push(...strings);
50395
50406
  } catch (error) {
50396
50407
  console.warn(`Warning: Failed to extract from ${file}:`, error);
50397
50408
  }
50398
50409
  }
50399
- return this.deduplicateStrings(allStrings);
50410
+ return deduplicateStrings(allStrings);
50400
50411
  }
50401
- async extractFromFile(filePath, projectRoot) {
50402
- const code = readFileSync3(filePath, "utf-8");
50403
- const strings = [];
50404
- const relativeFilePath = pathRelative(projectRoot, filePath).split("\\").join("/");
50405
- try {
50406
- const ast = (0, import_parser2.parse)(code, {
50407
- sourceType: "module",
50408
- plugins: ["jsx", "typescript"]
50409
- });
50410
- const vocoderImports = /* @__PURE__ */ new Map();
50411
- const tFunctionNames = /* @__PURE__ */ new Set();
50412
- traverse2(ast, {
50413
- ImportDeclaration: (path2) => {
50414
- const source = path2.node.source.value;
50415
- if (source === "@vocoder/react") {
50416
- path2.node.specifiers.forEach((spec) => {
50417
- if (spec.type === "ImportSpecifier") {
50418
- const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
50419
- const local = spec.local.name;
50420
- if (imported === "T") {
50421
- vocoderImports.set(local, "T");
50422
- }
50423
- if (imported === "t") {
50424
- tFunctionNames.add(local);
50425
- }
50412
+ };
50413
+ function propNameToUiRole(propName) {
50414
+ switch (propName) {
50415
+ case "placeholder":
50416
+ return "input_placeholder";
50417
+ case "aria-label":
50418
+ case "aria-description":
50419
+ case "label":
50420
+ return "input_label";
50421
+ case "alt":
50422
+ return "image_alt";
50423
+ case "title":
50424
+ return "tooltip";
50425
+ default:
50426
+ return "unknown";
50427
+ }
50428
+ }
50429
+ function elementNameToUiRole(name) {
50430
+ if (!name) return "unknown";
50431
+ switch (name.toLowerCase()) {
50432
+ case "button":
50433
+ return "button_label";
50434
+ case "h1":
50435
+ case "h2":
50436
+ case "h3":
50437
+ case "h4":
50438
+ case "h5":
50439
+ case "h6":
50440
+ return "heading";
50441
+ case "label":
50442
+ return "input_label";
50443
+ case "th":
50444
+ return "table_header";
50445
+ case "option":
50446
+ return "option_label";
50447
+ case "title":
50448
+ return "page_title";
50449
+ case "p":
50450
+ case "li":
50451
+ case "dd":
50452
+ return "body_text";
50453
+ // Custom component name heuristics
50454
+ default: {
50455
+ const lower = name.toLowerCase();
50456
+ if (/button|btn|submit|cta/.test(lower)) return "button_label";
50457
+ if (/heading|headline/.test(lower)) return "heading";
50458
+ if (/label/.test(lower)) return "input_label";
50459
+ if (/tooltip|hint|popover/.test(lower)) return "tooltip";
50460
+ if (/badge|chip|tag|pill/.test(lower)) return "badge";
50461
+ if (/toast|snackbar|notification/.test(lower)) return "toast";
50462
+ if (/navitem|menuitem/.test(lower)) return "nav_item";
50463
+ return "unknown";
50464
+ }
50465
+ }
50466
+ }
50467
+ function detectUiRole(path2) {
50468
+ const parent = path2.parent;
50469
+ if (!parent) return "unknown";
50470
+ if (parent.type === "JSXExpressionContainer") {
50471
+ const attrNode = path2.parentPath?.parent;
50472
+ if (attrNode?.type === "JSXAttribute") {
50473
+ const propName = attrNode.name?.type === "JSXNamespacedName" ? `${attrNode.name.namespace.name}-${attrNode.name.name.name}` : attrNode.name?.name ?? "";
50474
+ return propNameToUiRole(propName);
50475
+ }
50476
+ }
50477
+ if (parent.type === "JSXElement") {
50478
+ const opening = parent.openingElement;
50479
+ const tagName = opening?.name?.type === "JSXMemberExpression" ? "unknown" : opening?.name?.name ?? "";
50480
+ return elementNameToUiRole(tagName);
50481
+ }
50482
+ return "unknown";
50483
+ }
50484
+ var FEATURE_PATTERNS = [
50485
+ [/\/(checkout|cart|basket)/, "checkout"],
50486
+ [/\/(auth|login|signup|sign-up|register|forgot|reset-password|verify)/, "auth"],
50487
+ [/\/(onboarding|welcome|setup)/, "onboarding"],
50488
+ [/\/(dashboard|overview|home)/, "dashboard"],
50489
+ [/\/(settings|preferences|profile|account)/, "settings"],
50490
+ [/\/(search|results|explore)/, "search"],
50491
+ [/\/(product|item|detail|pdp)/, "product"],
50492
+ [/\/(pricing|plans|billing|upgrade)/, "pricing"],
50493
+ [/\/(admin|manage)/, "admin"],
50494
+ [/\/(help|support|faq|docs)/, "support"]
50495
+ ];
50496
+ function inferFeatureArea(filePath) {
50497
+ const normalized = filePath.replace(/\\/g, "/").toLowerCase();
50498
+ for (const [pattern, area] of FEATURE_PATTERNS) {
50499
+ if (pattern.test(normalized)) return area;
50500
+ }
50501
+ return void 0;
50502
+ }
50503
+ function _extractFromContent(filePath, content) {
50504
+ const strings = [];
50505
+ try {
50506
+ const ast = (0, import_parser2.parse)(content, {
50507
+ sourceType: "module",
50508
+ plugins: ["jsx", "typescript"]
50509
+ });
50510
+ const vocoderImports = /* @__PURE__ */ new Map();
50511
+ const tFunctionNames = /* @__PURE__ */ new Set();
50512
+ traverse2(ast, {
50513
+ ImportDeclaration: (path2) => {
50514
+ const source = path2.node.source.value;
50515
+ if (source === "@vocoder/react") {
50516
+ path2.node.specifiers.forEach((spec) => {
50517
+ if (spec.type === "ImportSpecifier") {
50518
+ const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
50519
+ const local = spec.local.name;
50520
+ if (imported === "T") {
50521
+ vocoderImports.set(local, "T");
50426
50522
  }
50427
- });
50428
- }
50429
- },
50430
- VariableDeclarator: (path2) => {
50431
- const init = path2.node.init;
50432
- if (init && init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "useVocoder" && path2.node.id.type === "ObjectPattern") {
50433
- path2.node.id.properties.forEach((prop) => {
50434
- if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "t") {
50435
- const localName = prop.value.type === "Identifier" ? prop.value.name : "t";
50436
- tFunctionNames.add(localName);
50523
+ if (imported === "t") {
50524
+ tFunctionNames.add(local);
50437
50525
  }
50438
- });
50439
- }
50440
- },
50441
- CallExpression: (path2) => {
50442
- const callee = path2.node.callee;
50443
- const isTFunction = callee.type === "Identifier" && tFunctionNames.has(callee.name);
50444
- if (!isTFunction) return;
50445
- const firstArg = path2.node.arguments[0];
50446
- if (!firstArg) return;
50447
- let text = null;
50448
- if (firstArg.type === "StringLiteral") {
50449
- text = firstArg.value;
50450
- } else if (firstArg.type === "TemplateLiteral") {
50451
- text = this.extractTemplateText(firstArg);
50452
- }
50453
- if (!text || text.trim().length === 0) return;
50454
- const optionsArg = path2.node.arguments[2];
50455
- let context;
50456
- let formality;
50457
- let explicitKey;
50458
- if (optionsArg && optionsArg.type === "ObjectExpression") {
50459
- optionsArg.properties.forEach((prop) => {
50460
- if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
50461
- if (prop.key.name === "context" && prop.value.type === "StringLiteral") {
50462
- context = prop.value.value;
50463
- }
50464
- if (prop.key.name === "formality" && prop.value.type === "StringLiteral") {
50465
- formality = prop.value.value;
50466
- }
50467
- if (prop.key.name === "id" && prop.value.type === "StringLiteral") {
50468
- explicitKey = prop.value.value.trim();
50469
- }
50526
+ }
50527
+ });
50528
+ }
50529
+ },
50530
+ VariableDeclarator: (path2) => {
50531
+ const init = path2.node.init;
50532
+ if (init && init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "useVocoder" && path2.node.id.type === "ObjectPattern") {
50533
+ path2.node.id.properties.forEach((prop) => {
50534
+ if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "t") {
50535
+ const localName = prop.value.type === "Identifier" ? prop.value.name : "t";
50536
+ tFunctionNames.add(localName);
50537
+ }
50538
+ });
50539
+ }
50540
+ },
50541
+ CallExpression: (path2) => {
50542
+ const callee = path2.node.callee;
50543
+ const isTFunction = callee.type === "Identifier" && tFunctionNames.has(callee.name);
50544
+ if (!isTFunction) return;
50545
+ const firstArg = path2.node.arguments[0];
50546
+ if (!firstArg) return;
50547
+ let text = null;
50548
+ if (firstArg.type === "StringLiteral") {
50549
+ text = firstArg.value;
50550
+ } else if (firstArg.type === "TemplateLiteral") {
50551
+ text = extractTemplateText(firstArg);
50552
+ }
50553
+ if (!text || text.trim().length === 0) return;
50554
+ const optionsArg = path2.node.arguments[2];
50555
+ let context;
50556
+ let formality;
50557
+ let explicitKey;
50558
+ if (optionsArg && optionsArg.type === "ObjectExpression") {
50559
+ optionsArg.properties.forEach((prop) => {
50560
+ if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
50561
+ if (prop.key.name === "context" && prop.value.type === "StringLiteral") {
50562
+ context = prop.value.value;
50470
50563
  }
50471
- });
50472
- }
50473
- const line = path2.node.loc?.start.line || 0;
50474
- const key = explicitKey && explicitKey.length > 0 ? explicitKey : generateMessageHash(text.trim(), context);
50475
- strings.push({
50476
- key,
50477
- text: text.trim(),
50478
- file: filePath,
50479
- line,
50480
- context,
50481
- formality
50564
+ if (prop.key.name === "formality" && prop.value.type === "StringLiteral") {
50565
+ formality = prop.value.value;
50566
+ }
50567
+ if (prop.key.name === "id" && prop.value.type === "StringLiteral") {
50568
+ explicitKey = prop.value.value.trim();
50569
+ }
50570
+ }
50482
50571
  });
50483
- },
50484
- JSXElement: (path2) => {
50485
- const opening = path2.node.openingElement;
50486
- const tagName = opening.name.type === "JSXIdentifier" ? opening.name.name : null;
50487
- if (!tagName) return;
50488
- const isTranslationComponent = vocoderImports.has(tagName);
50489
- if (!isTranslationComponent) return;
50490
- const msgAttribute = this.getStringAttribute(opening.attributes, "message");
50491
- let text = null;
50492
- if (msgAttribute) {
50493
- text = msgAttribute;
50572
+ }
50573
+ const line = path2.node.loc?.start.line || 0;
50574
+ const key = explicitKey && explicitKey.length > 0 ? explicitKey : generateMessageHash(text.trim(), context);
50575
+ const uiRole = detectUiRole(path2);
50576
+ const featureArea = inferFeatureArea(filePath);
50577
+ strings.push({
50578
+ key,
50579
+ text: text.trim(),
50580
+ file: filePath,
50581
+ line,
50582
+ context,
50583
+ formality,
50584
+ uiRole: uiRole !== "unknown" ? uiRole : void 0,
50585
+ featureArea
50586
+ });
50587
+ },
50588
+ JSXElement: (path2) => {
50589
+ const opening = path2.node.openingElement;
50590
+ const tagName = opening.name.type === "JSXIdentifier" ? opening.name.name : null;
50591
+ if (!tagName) return;
50592
+ const isTranslationComponent = vocoderImports.has(tagName);
50593
+ if (!isTranslationComponent) return;
50594
+ const msgAttribute = getStringAttribute(opening.attributes, "message");
50595
+ let text = null;
50596
+ if (msgAttribute) {
50597
+ text = msgAttribute;
50598
+ } else {
50599
+ const pluralSelectICU = extractPluralSelectICU(opening.attributes);
50600
+ if (pluralSelectICU) {
50601
+ text = pluralSelectICU;
50494
50602
  } else {
50495
- const pluralSelectICU = this.extractPluralSelectICU(
50496
- opening.attributes
50497
- );
50498
- if (pluralSelectICU) {
50499
- text = pluralSelectICU;
50500
- } else {
50501
- text = extractTextContentFromNodes(path2.node.children, { count: 0 });
50502
- }
50603
+ text = extractTextContentFromNodes(path2.node.children, { count: 0 });
50503
50604
  }
50504
- if (!text || text.trim().length === 0) return;
50505
- const id = this.getStringAttribute(opening.attributes, "id");
50506
- const context = this.getStringAttribute(
50507
- opening.attributes,
50508
- "context"
50509
- );
50510
- const formality = this.getStringAttribute(
50511
- opening.attributes,
50512
- "formality"
50513
- );
50514
- const line = path2.node.loc?.start.line || 0;
50515
- const key = id && id.trim().length > 0 ? id.trim() : generateMessageHash(text.trim(), context);
50516
- strings.push({
50517
- key,
50518
- text: text.trim(),
50519
- file: filePath,
50520
- line,
50521
- context,
50522
- formality
50523
- });
50524
50605
  }
50525
- });
50526
- } catch (error) {
50527
- throw new Error(
50528
- `Failed to parse ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
50529
- );
50530
- }
50531
- return strings;
50532
- }
50533
- extractPluralSelectICU(attributes) {
50534
- const pluralProps = {};
50535
- const selectProps = {};
50536
- let otherValue;
50537
- let hasPlural = false;
50538
- let hasSelect = false;
50539
- let isOrdinal = false;
50540
- let hasGender = false;
50541
- for (const attr of attributes) {
50542
- if (attr.type !== "JSXAttribute") continue;
50543
- const name = attr.name.name;
50544
- if (name === "ordinal") {
50545
- isOrdinal = true;
50546
- continue;
50547
- }
50548
- if (name === "gender") {
50549
- hasGender = true;
50550
- continue;
50551
- }
50552
- const value = attr.value?.type === "StringLiteral" ? attr.value.value : null;
50553
- if (!value) continue;
50554
- if (PLURAL_CLDR.has(name) || /^_\d+$/.test(name)) {
50555
- pluralProps[name] = value;
50556
- hasPlural = true;
50557
- } else if (name === "other") {
50558
- otherValue = value;
50559
- } else if (/^_[a-zA-Z]/.test(name)) {
50560
- selectProps[name] = value;
50561
- hasSelect = true;
50606
+ if (!text || text.trim().length === 0) return;
50607
+ const id = getStringAttribute(opening.attributes, "id");
50608
+ const context = getStringAttribute(opening.attributes, "context");
50609
+ const formality = getStringAttribute(
50610
+ opening.attributes,
50611
+ "formality"
50612
+ );
50613
+ const line = path2.node.loc?.start.line || 0;
50614
+ const key = id && id.trim().length > 0 ? id.trim() : generateMessageHash(text.trim(), context);
50615
+ const uiRole = detectUiRole(path2);
50616
+ const featureArea = inferFeatureArea(filePath);
50617
+ strings.push({
50618
+ key,
50619
+ text: text.trim(),
50620
+ file: filePath,
50621
+ line,
50622
+ context,
50623
+ formality,
50624
+ uiRole: uiRole !== "unknown" ? uiRole : void 0,
50625
+ featureArea
50626
+ });
50562
50627
  }
50628
+ });
50629
+ } catch (error) {
50630
+ throw new Error(
50631
+ `Failed to parse ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
50632
+ );
50633
+ }
50634
+ return strings;
50635
+ }
50636
+ function extractPluralSelectICU(attributes) {
50637
+ const pluralProps = {};
50638
+ const selectProps = {};
50639
+ let otherValue;
50640
+ let hasPlural = false;
50641
+ let hasSelect = false;
50642
+ let isOrdinal = false;
50643
+ let hasGender = false;
50644
+ for (const attr of attributes) {
50645
+ if (attr.type !== "JSXAttribute") continue;
50646
+ const name = attr.name.name;
50647
+ if (name === "ordinal") {
50648
+ isOrdinal = true;
50649
+ continue;
50563
50650
  }
50564
- if (isOrdinal) {
50565
- const ordinalICU = "{count, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}";
50566
- if (hasGender) {
50567
- return `{gender, select, masculine {${ordinalICU}} feminine {${ordinalICU}} other {${ordinalICU}}}`;
50568
- }
50569
- return ordinalICU;
50651
+ if (name === "gender") {
50652
+ hasGender = true;
50653
+ continue;
50570
50654
  }
50571
- if (!hasPlural && !hasSelect) return null;
50572
- if (hasPlural) {
50573
- if (otherValue !== void 0) pluralProps.other = otherValue;
50574
- return buildPluralICU(pluralProps, false);
50655
+ const value = attr.value?.type === "StringLiteral" ? attr.value.value : null;
50656
+ if (!value) continue;
50657
+ if (PLURAL_CLDR.has(name) || /^_\d+$/.test(name)) {
50658
+ pluralProps[name] = value;
50659
+ hasPlural = true;
50660
+ } else if (name === "other") {
50661
+ otherValue = value;
50662
+ } else if (/^_[a-zA-Z]/.test(name)) {
50663
+ selectProps[name] = value;
50664
+ hasSelect = true;
50575
50665
  }
50576
- if (hasSelect) {
50577
- if (otherValue !== void 0) selectProps.other = otherValue;
50578
- return buildSelectICU(selectProps);
50666
+ }
50667
+ if (isOrdinal) {
50668
+ const ordinalICU = "{count, selectordinal, other {#}}";
50669
+ if (hasGender) {
50670
+ return `{gender, select, masculine {${ordinalICU}} feminine {${ordinalICU}} other {${ordinalICU}}}`;
50579
50671
  }
50580
- return null;
50672
+ return ordinalICU;
50581
50673
  }
50582
- extractTemplateText(node) {
50583
- let text = "";
50584
- for (let i = 0; i < node.quasis.length; i++) {
50585
- const quasi = node.quasis[i];
50586
- text += quasi.value.raw;
50587
- if (i < node.expressions.length) {
50588
- const expr = node.expressions[i];
50589
- if (expr.type === "Identifier") {
50590
- text += `{${expr.name}}`;
50591
- } else {
50592
- text += "{value}";
50593
- }
50674
+ if (!hasPlural && !hasSelect) return null;
50675
+ if (hasPlural) {
50676
+ if (otherValue !== void 0) pluralProps.other = otherValue;
50677
+ return buildPluralICU(pluralProps, false);
50678
+ }
50679
+ if (hasSelect) {
50680
+ if (otherValue !== void 0) selectProps.other = otherValue;
50681
+ return buildSelectICU(selectProps);
50682
+ }
50683
+ return null;
50684
+ }
50685
+ function extractTemplateText(node) {
50686
+ let text = "";
50687
+ for (let i = 0; i < node.quasis.length; i++) {
50688
+ const quasi = node.quasis[i];
50689
+ text += quasi.value.raw;
50690
+ if (i < node.expressions.length) {
50691
+ const expr = node.expressions[i];
50692
+ if (expr.type === "Identifier") {
50693
+ text += `{${expr.name}}`;
50694
+ } else {
50695
+ text += "{value}";
50594
50696
  }
50595
50697
  }
50596
- return text;
50597
50698
  }
50598
- getStringAttribute(attributes, name) {
50599
- const attr = attributes.find(
50600
- (a) => a.type === "JSXAttribute" && a.name.name === name
50601
- );
50602
- if (!attr || !attr.value) return void 0;
50603
- if (attr.value.type === "StringLiteral") {
50604
- return attr.value.value;
50699
+ return text;
50700
+ }
50701
+ function getStringAttribute(attributes, name) {
50702
+ const attr = attributes.find(
50703
+ (a) => a.type === "JSXAttribute" && a.name.name === name
50704
+ );
50705
+ if (!attr || !attr.value) return void 0;
50706
+ if (attr.value.type === "StringLiteral") {
50707
+ return attr.value.value;
50708
+ }
50709
+ if (attr.value.type === "JSXExpressionContainer") {
50710
+ const expr = attr.value.expression;
50711
+ if (expr.type === "TemplateLiteral") {
50712
+ return extractTemplateText(expr);
50605
50713
  }
50606
- if (attr.value.type === "JSXExpressionContainer") {
50607
- const expr = attr.value.expression;
50608
- if (expr.type === "TemplateLiteral") {
50609
- return this.extractTemplateText(expr);
50610
- }
50611
- if (expr.type === "StringLiteral") {
50612
- return expr.value;
50613
- }
50714
+ if (expr.type === "StringLiteral") {
50715
+ return expr.value;
50614
50716
  }
50615
- return void 0;
50616
50717
  }
50617
- deduplicateStrings(strings) {
50618
- const seen = /* @__PURE__ */ new Set();
50619
- const unique = [];
50620
- for (const str of strings) {
50621
- if (!seen.has(str.key)) {
50622
- seen.add(str.key);
50623
- unique.push(str);
50624
- }
50718
+ return void 0;
50719
+ }
50720
+ function deduplicateStrings(strings) {
50721
+ const seen = /* @__PURE__ */ new Set();
50722
+ const unique = [];
50723
+ for (const str of strings) {
50724
+ if (!seen.has(str.key)) {
50725
+ seen.add(str.key);
50726
+ unique.push(str);
50625
50727
  }
50626
- return unique;
50627
50728
  }
50628
- };
50729
+ return unique;
50730
+ }
50629
50731
 
50630
50732
  export {
50631
50733
  detectLocalEcosystem,
@@ -50635,4 +50737,4 @@ export {
50635
50737
  loadVocoderConfig,
50636
50738
  StringExtractor
50637
50739
  };
50638
- //# sourceMappingURL=chunk-7LFRSUPU.mjs.map
50740
+ //# sourceMappingURL=chunk-73U4VZYP.mjs.map