@vocoder/cli 0.9.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.
@@ -43909,6 +43909,12 @@ function extractFromObject(obj) {
43909
43909
  if (key === "localesPath" && prop.value.type === "StringLiteral") {
43910
43910
  config.localesPath = prop.value.value;
43911
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
+ }
43912
43918
  }
43913
43919
  return config;
43914
43920
  }
@@ -50393,242 +50399,335 @@ var StringExtractor = class {
50393
50399
  const sortedFiles = Array.from(allFiles).sort();
50394
50400
  for (const file of sortedFiles) {
50395
50401
  try {
50396
- 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);
50397
50405
  allStrings.push(...strings);
50398
50406
  } catch (error) {
50399
50407
  console.warn(`Warning: Failed to extract from ${file}:`, error);
50400
50408
  }
50401
50409
  }
50402
- return this.deduplicateStrings(allStrings);
50410
+ return deduplicateStrings(allStrings);
50403
50411
  }
50404
- async extractFromFile(filePath, projectRoot) {
50405
- const code = readFileSync3(filePath, "utf-8");
50406
- const strings = [];
50407
- const relativeFilePath = pathRelative(projectRoot, filePath).split("\\").join("/");
50408
- try {
50409
- const ast = (0, import_parser2.parse)(code, {
50410
- sourceType: "module",
50411
- plugins: ["jsx", "typescript"]
50412
- });
50413
- const vocoderImports = /* @__PURE__ */ new Map();
50414
- const tFunctionNames = /* @__PURE__ */ new Set();
50415
- traverse2(ast, {
50416
- ImportDeclaration: (path2) => {
50417
- const source = path2.node.source.value;
50418
- if (source === "@vocoder/react") {
50419
- path2.node.specifiers.forEach((spec) => {
50420
- if (spec.type === "ImportSpecifier") {
50421
- const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
50422
- const local = spec.local.name;
50423
- if (imported === "T") {
50424
- vocoderImports.set(local, "T");
50425
- }
50426
- if (imported === "t") {
50427
- tFunctionNames.add(local);
50428
- }
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");
50429
50522
  }
50430
- });
50431
- }
50432
- },
50433
- VariableDeclarator: (path2) => {
50434
- const init = path2.node.init;
50435
- if (init && init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "useVocoder" && path2.node.id.type === "ObjectPattern") {
50436
- path2.node.id.properties.forEach((prop) => {
50437
- if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "t") {
50438
- const localName = prop.value.type === "Identifier" ? prop.value.name : "t";
50439
- tFunctionNames.add(localName);
50523
+ if (imported === "t") {
50524
+ tFunctionNames.add(local);
50440
50525
  }
50441
- });
50442
- }
50443
- },
50444
- CallExpression: (path2) => {
50445
- const callee = path2.node.callee;
50446
- const isTFunction = callee.type === "Identifier" && tFunctionNames.has(callee.name);
50447
- if (!isTFunction) return;
50448
- const firstArg = path2.node.arguments[0];
50449
- if (!firstArg) return;
50450
- let text = null;
50451
- if (firstArg.type === "StringLiteral") {
50452
- text = firstArg.value;
50453
- } else if (firstArg.type === "TemplateLiteral") {
50454
- text = this.extractTemplateText(firstArg);
50455
- }
50456
- if (!text || text.trim().length === 0) return;
50457
- const optionsArg = path2.node.arguments[2];
50458
- let context;
50459
- let formality;
50460
- let explicitKey;
50461
- if (optionsArg && optionsArg.type === "ObjectExpression") {
50462
- optionsArg.properties.forEach((prop) => {
50463
- if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
50464
- if (prop.key.name === "context" && prop.value.type === "StringLiteral") {
50465
- context = prop.value.value;
50466
- }
50467
- if (prop.key.name === "formality" && prop.value.type === "StringLiteral") {
50468
- formality = prop.value.value;
50469
- }
50470
- if (prop.key.name === "id" && prop.value.type === "StringLiteral") {
50471
- explicitKey = prop.value.value.trim();
50472
- }
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;
50473
50563
  }
50474
- });
50475
- }
50476
- const line = path2.node.loc?.start.line || 0;
50477
- const key = explicitKey && explicitKey.length > 0 ? explicitKey : generateMessageHash(text.trim(), context);
50478
- strings.push({
50479
- key,
50480
- text: text.trim(),
50481
- file: filePath,
50482
- line,
50483
- context,
50484
- 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
+ }
50485
50571
  });
50486
- },
50487
- JSXElement: (path2) => {
50488
- const opening = path2.node.openingElement;
50489
- const tagName = opening.name.type === "JSXIdentifier" ? opening.name.name : null;
50490
- if (!tagName) return;
50491
- const isTranslationComponent = vocoderImports.has(tagName);
50492
- if (!isTranslationComponent) return;
50493
- const msgAttribute = this.getStringAttribute(opening.attributes, "message");
50494
- let text = null;
50495
- if (msgAttribute) {
50496
- 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;
50497
50602
  } else {
50498
- const pluralSelectICU = this.extractPluralSelectICU(
50499
- opening.attributes
50500
- );
50501
- if (pluralSelectICU) {
50502
- text = pluralSelectICU;
50503
- } else {
50504
- text = extractTextContentFromNodes(path2.node.children, { count: 0 });
50505
- }
50603
+ text = extractTextContentFromNodes(path2.node.children, { count: 0 });
50506
50604
  }
50507
- if (!text || text.trim().length === 0) return;
50508
- const id = this.getStringAttribute(opening.attributes, "id");
50509
- const context = this.getStringAttribute(
50510
- opening.attributes,
50511
- "context"
50512
- );
50513
- const formality = this.getStringAttribute(
50514
- opening.attributes,
50515
- "formality"
50516
- );
50517
- const line = path2.node.loc?.start.line || 0;
50518
- const key = id && id.trim().length > 0 ? id.trim() : generateMessageHash(text.trim(), context);
50519
- strings.push({
50520
- key,
50521
- text: text.trim(),
50522
- file: filePath,
50523
- line,
50524
- context,
50525
- formality
50526
- });
50527
50605
  }
50528
- });
50529
- } catch (error) {
50530
- throw new Error(
50531
- `Failed to parse ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
50532
- );
50533
- }
50534
- return strings;
50535
- }
50536
- extractPluralSelectICU(attributes) {
50537
- const pluralProps = {};
50538
- const selectProps = {};
50539
- let otherValue;
50540
- let hasPlural = false;
50541
- let hasSelect = false;
50542
- let isOrdinal = false;
50543
- let hasGender = false;
50544
- for (const attr of attributes) {
50545
- if (attr.type !== "JSXAttribute") continue;
50546
- const name = attr.name.name;
50547
- if (name === "ordinal") {
50548
- isOrdinal = true;
50549
- continue;
50550
- }
50551
- if (name === "gender") {
50552
- hasGender = true;
50553
- continue;
50554
- }
50555
- const value = attr.value?.type === "StringLiteral" ? attr.value.value : null;
50556
- if (!value) continue;
50557
- if (PLURAL_CLDR.has(name) || /^_\d+$/.test(name)) {
50558
- pluralProps[name] = value;
50559
- hasPlural = true;
50560
- } else if (name === "other") {
50561
- otherValue = value;
50562
- } else if (/^_[a-zA-Z]/.test(name)) {
50563
- selectProps[name] = value;
50564
- 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
+ });
50565
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;
50566
50650
  }
50567
- if (isOrdinal) {
50568
- const ordinalICU = "{count, selectordinal, other {#}}";
50569
- if (hasGender) {
50570
- return `{gender, select, masculine {${ordinalICU}} feminine {${ordinalICU}} other {${ordinalICU}}}`;
50571
- }
50572
- return ordinalICU;
50651
+ if (name === "gender") {
50652
+ hasGender = true;
50653
+ continue;
50573
50654
  }
50574
- if (!hasPlural && !hasSelect) return null;
50575
- if (hasPlural) {
50576
- if (otherValue !== void 0) pluralProps.other = otherValue;
50577
- 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;
50578
50665
  }
50579
- if (hasSelect) {
50580
- if (otherValue !== void 0) selectProps.other = otherValue;
50581
- 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}}}`;
50582
50671
  }
50583
- return null;
50672
+ return ordinalICU;
50584
50673
  }
50585
- extractTemplateText(node) {
50586
- let text = "";
50587
- for (let i = 0; i < node.quasis.length; i++) {
50588
- const quasi = node.quasis[i];
50589
- text += quasi.value.raw;
50590
- if (i < node.expressions.length) {
50591
- const expr = node.expressions[i];
50592
- if (expr.type === "Identifier") {
50593
- text += `{${expr.name}}`;
50594
- } else {
50595
- text += "{value}";
50596
- }
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}";
50597
50696
  }
50598
50697
  }
50599
- return text;
50600
50698
  }
50601
- getStringAttribute(attributes, name) {
50602
- const attr = attributes.find(
50603
- (a) => a.type === "JSXAttribute" && a.name.name === name
50604
- );
50605
- if (!attr || !attr.value) return void 0;
50606
- if (attr.value.type === "StringLiteral") {
50607
- 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);
50608
50713
  }
50609
- if (attr.value.type === "JSXExpressionContainer") {
50610
- const expr = attr.value.expression;
50611
- if (expr.type === "TemplateLiteral") {
50612
- return this.extractTemplateText(expr);
50613
- }
50614
- if (expr.type === "StringLiteral") {
50615
- return expr.value;
50616
- }
50714
+ if (expr.type === "StringLiteral") {
50715
+ return expr.value;
50617
50716
  }
50618
- return void 0;
50619
50717
  }
50620
- deduplicateStrings(strings) {
50621
- const seen = /* @__PURE__ */ new Set();
50622
- const unique = [];
50623
- for (const str of strings) {
50624
- if (!seen.has(str.key)) {
50625
- seen.add(str.key);
50626
- unique.push(str);
50627
- }
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);
50628
50727
  }
50629
- return unique;
50630
50728
  }
50631
- };
50729
+ return unique;
50730
+ }
50632
50731
 
50633
50732
  export {
50634
50733
  detectLocalEcosystem,
@@ -50638,4 +50737,4 @@ export {
50638
50737
  loadVocoderConfig,
50639
50738
  StringExtractor
50640
50739
  };
50641
- //# sourceMappingURL=chunk-IZN5HVYD.mjs.map
50740
+ //# sourceMappingURL=chunk-73U4VZYP.mjs.map