@vocoder/cli 0.14.0 → 0.15.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.
@@ -3094,14 +3094,14 @@ var require_lib = __commonJS({
3094
3094
  super.checkParams(node, false, true);
3095
3095
  this.scope.exit();
3096
3096
  }
3097
- forwardNoArrowParamsConversionAt(node, parse4) {
3097
+ forwardNoArrowParamsConversionAt(node, parse5) {
3098
3098
  let result;
3099
3099
  if (this.state.noArrowParamsConversionAt.includes(this.offsetToSourcePos(node.start))) {
3100
3100
  this.state.noArrowParamsConversionAt.push(this.state.start);
3101
- result = parse4();
3101
+ result = parse5();
3102
3102
  this.state.noArrowParamsConversionAt.pop();
3103
3103
  } else {
3104
- result = parse4();
3104
+ result = parse5();
3105
3105
  }
3106
3106
  return result;
3107
3107
  }
@@ -14496,7 +14496,7 @@ var require_lib = __commonJS({
14496
14496
  return result;
14497
14497
  }
14498
14498
  };
14499
- function parse3(input, options) {
14499
+ function parse4(input, options) {
14500
14500
  var _options;
14501
14501
  if (((_options = options) == null ? void 0 : _options.sourceType) === "unambiguous") {
14502
14502
  options = Object.assign({}, options);
@@ -14583,7 +14583,7 @@ var require_lib = __commonJS({
14583
14583
  }
14584
14584
  return cls;
14585
14585
  }
14586
- exports.parse = parse3;
14586
+ exports.parse = parse4;
14587
14587
  exports.parseExpression = parseExpression;
14588
14588
  exports.tokTypes = tokTypes;
14589
14589
  }
@@ -14632,7 +14632,7 @@ var require_ms = __commonJS({
14632
14632
  options = options || {};
14633
14633
  var type = typeof val;
14634
14634
  if (type === "string" && val.length > 0) {
14635
- return parse3(val);
14635
+ return parse4(val);
14636
14636
  } else if (type === "number" && isFinite(val)) {
14637
14637
  return options.long ? fmtLong(val) : fmtShort(val);
14638
14638
  }
@@ -14640,7 +14640,7 @@ var require_ms = __commonJS({
14640
14640
  "val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
14641
14641
  );
14642
14642
  };
14643
- function parse3(str) {
14643
+ function parse4(str) {
14644
14644
  str = String(str);
14645
14645
  if (str.length > 100) {
14646
14646
  return;
@@ -28425,9 +28425,9 @@ var require_traverse = __commonJS({
28425
28425
  Object.defineProperty(exports, "__esModule", {
28426
28426
  value: true
28427
28427
  });
28428
- exports.default = traverse3;
28428
+ exports.default = traverse4;
28429
28429
  var _index = require_definitions();
28430
- function traverse3(node, handlers, state) {
28430
+ function traverse4(node, handlers, state) {
28431
28431
  if (typeof handlers === "function") {
28432
28432
  handlers = {
28433
28433
  enter: handlers
@@ -32148,11 +32148,11 @@ var require_trace_mapping_umd = __commonJS({
32148
32148
  function buildNullArray() {
32149
32149
  return { __proto__: null };
32150
32150
  }
32151
- function parse3(map) {
32151
+ function parse4(map) {
32152
32152
  return typeof map === "string" ? JSON.parse(map) : map;
32153
32153
  }
32154
32154
  var FlattenMap = function(map, mapUrl) {
32155
- const parsed = parse3(map);
32155
+ const parsed = parse4(map);
32156
32156
  if (!("sections" in parsed)) {
32157
32157
  return new TraceMap(parsed, mapUrl);
32158
32158
  }
@@ -32216,7 +32216,7 @@ var require_trace_mapping_umd = __commonJS({
32216
32216
  }
32217
32217
  }
32218
32218
  function addSection(input, mapUrl, mappings, sources, sourcesContent, names, ignoreList, lineOffset, columnOffset, stopLine, stopColumn) {
32219
- const parsed = parse3(input);
32219
+ const parsed = parse4(input);
32220
32220
  if ("sections" in parsed) return recurse(...arguments);
32221
32221
  const map = new TraceMap(parsed, mapUrl);
32222
32222
  const sourcesOffset = sources.length;
@@ -32266,7 +32266,7 @@ var require_trace_mapping_umd = __commonJS({
32266
32266
  constructor(map, mapUrl) {
32267
32267
  const isString = typeof map === "string";
32268
32268
  if (!isString && map._decodedMemo) return map;
32269
- const parsed = parse3(map);
32269
+ const parsed = parse4(map);
32270
32270
  const { version, file, names, sourceRoot, sources, sourcesContent } = parsed;
32271
32271
  this.version = version;
32272
32272
  this.file = file;
@@ -40637,7 +40637,7 @@ var require_parse = __commonJS({
40637
40637
  isStatement,
40638
40638
  isStringLiteral,
40639
40639
  removePropertiesDeep,
40640
- traverse: traverse3
40640
+ traverse: traverse4
40641
40641
  } = _t;
40642
40642
  var PATTERN = /^[_$A-Z0-9]+$/;
40643
40643
  function parseAndBuildMetadata(formatter, code, opts) {
@@ -40665,7 +40665,7 @@ var require_parse = __commonJS({
40665
40665
  placeholderPattern,
40666
40666
  syntacticPlaceholders
40667
40667
  };
40668
- traverse3(ast, placeholderVisitorHandler, state);
40668
+ traverse4(ast, placeholderVisitorHandler, state);
40669
40669
  return Object.assign({
40670
40670
  ast
40671
40671
  }, state.syntactic.placeholders.length ? state.syntactic : state.legacy);
@@ -43309,7 +43309,7 @@ var require_lib8 = __commonJS({
43309
43309
  removeProperties,
43310
43310
  traverseFast
43311
43311
  } = _t;
43312
- function traverse3(parent, opts = {}, scope, state, parentPath, visitSelf) {
43312
+ function traverse4(parent, opts = {}, scope, state, parentPath, visitSelf) {
43313
43313
  if (!parent) return;
43314
43314
  if (!opts.noScope && !scope) {
43315
43315
  if (parent.type !== "Program" && parent.type !== "File") {
@@ -43325,25 +43325,25 @@ var require_lib8 = __commonJS({
43325
43325
  visitors.explode(opts);
43326
43326
  (0, _traverseNode.traverseNode)(parent, opts, scope, state, parentPath, void 0, visitSelf);
43327
43327
  }
43328
- var _default = exports.default = traverse3;
43329
- traverse3.visitors = visitors;
43330
- traverse3.verify = visitors.verify;
43331
- traverse3.explode = visitors.explode;
43332
- traverse3.cheap = function(node, enter) {
43328
+ var _default = exports.default = traverse4;
43329
+ traverse4.visitors = visitors;
43330
+ traverse4.verify = visitors.verify;
43331
+ traverse4.explode = visitors.explode;
43332
+ traverse4.cheap = function(node, enter) {
43333
43333
  traverseFast(node, enter);
43334
43334
  return;
43335
43335
  };
43336
- traverse3.node = function(node, opts, scope, state, path2, skipKeys) {
43336
+ traverse4.node = function(node, opts, scope, state, path2, skipKeys) {
43337
43337
  (0, _traverseNode.traverseNode)(node, opts, scope, state, path2, skipKeys);
43338
43338
  };
43339
- traverse3.clearNode = function(node, opts) {
43339
+ traverse4.clearNode = function(node, opts) {
43340
43340
  removeProperties(node, opts);
43341
43341
  };
43342
- traverse3.removeProperties = function(tree, opts) {
43343
- traverseFast(tree, traverse3.clearNode, opts);
43342
+ traverse4.removeProperties = function(tree, opts) {
43343
+ traverseFast(tree, traverse4.clearNode, opts);
43344
43344
  return tree;
43345
43345
  };
43346
- traverse3.hasType = function(tree, type, denylistTypes) {
43346
+ traverse4.hasType = function(tree, type, denylistTypes) {
43347
43347
  if (denylistTypes != null && denylistTypes.includes(tree.type)) return false;
43348
43348
  if (tree.type === type) return true;
43349
43349
  return traverseFast(tree, function(node) {
@@ -43355,7 +43355,7 @@ var require_lib8 = __commonJS({
43355
43355
  }
43356
43356
  });
43357
43357
  };
43358
- traverse3.cache = cache;
43358
+ traverse4.cache = cache;
43359
43359
  }
43360
43360
  });
43361
43361
 
@@ -43570,9 +43570,9 @@ var require_brace_expansion = __commonJS({
43570
43570
 
43571
43571
  // src/utils/api.ts
43572
43572
  function computeStringsHash(input) {
43573
- const { createHash } = __require("crypto");
43574
- const sorted = [...input.texts].sort();
43575
- return createHash("sha256").update(JSON.stringify({ strings: sorted, appIndustry: input.appIndustry ?? null })).digest("hex");
43573
+ const { createHash: createHash2 } = __require("crypto");
43574
+ const sorted = [...input.keys].sort();
43575
+ return createHash2("sha256").update(JSON.stringify({ strings: sorted, appIndustry: input.appIndustry ?? null })).digest("hex");
43576
43576
  }
43577
43577
  function isLimitErrorResponse(value) {
43578
43578
  if (!value || typeof value !== "object") {
@@ -43677,7 +43677,6 @@ var VocoderAPI = class {
43677
43677
  return {
43678
43678
  projectName: data.projectName,
43679
43679
  organizationName: data.organizationName,
43680
- shortCode: data.shortCode,
43681
43680
  sourceLocale: data.sourceLocale,
43682
43681
  targetLocales: data.targetLocales,
43683
43682
  targetBranches: data.targetBranches ?? ["main"],
@@ -43717,7 +43716,8 @@ var VocoderAPI = class {
43717
43716
  }));
43718
43717
  }
43719
43718
  return entries.map((entry, index) => ({
43720
- key: entry.key || this.stableTextKey(`${entry.text}:${index}`),
43719
+ // Fall back to stableTextKey only when text is present — id-only entries always have a key.
43720
+ key: entry.key || (entry.text ? this.stableTextKey(`${entry.text}:${index}`) : `_idonly_${index}`),
43721
43721
  text: entry.text,
43722
43722
  ...entry.context ? { context: entry.context } : {},
43723
43723
  ...entry.formality ? { formality: entry.formality } : {},
@@ -43725,9 +43725,9 @@ var VocoderAPI = class {
43725
43725
  }));
43726
43726
  }
43727
43727
  async submitTranslation(branch, entries, targetLocales, options, repoIdentity) {
43728
- const stringEntries = this.normalizeStringEntries(entries);
43729
- const strings = stringEntries.map((entry) => entry.text);
43730
- const stringsHash = computeStringsHash({ texts: strings, appIndustry: options?.appIndustry ?? null });
43728
+ const allEntries = this.normalizeStringEntries(entries);
43729
+ const stringEntries = allEntries.filter((e) => e.text != null);
43730
+ const stringsHash = computeStringsHash({ keys: allEntries.map((e) => e.key), appIndustry: options?.appIndustry ?? null });
43731
43731
  return this.request(
43732
43732
  "/api/cli/sync",
43733
43733
  {
@@ -43952,9 +43952,9 @@ var VocoderAPI = class {
43952
43952
  });
43953
43953
  }
43954
43954
  }
43955
- // ── Workspaces ────────────────────────────────────────────────────────────────
43956
- async listWorkspaces(userToken, params) {
43957
- const url = new URL(`${this.apiUrl}/api/cli/workspaces`);
43955
+ // ── Organizations ─────────────────────────────────────────────────────────────
43956
+ async listOrganizations(userToken, params) {
43957
+ const url = new URL(`${this.apiUrl}/api/cli/organizations`);
43958
43958
  if (params?.repo) url.searchParams.set("repo", params.repo);
43959
43959
  const response = await fetch(url.toString(), {
43960
43960
  headers: { Authorization: `Bearer ${userToken}` }
@@ -43964,7 +43964,7 @@ var VocoderAPI = class {
43964
43964
  throw new VocoderAPIError({
43965
43965
  message: extractErrorMessage(
43966
43966
  payload,
43967
- `Failed to list workspaces (${response.status})`
43967
+ `Failed to list organizations (${response.status})`
43968
43968
  ),
43969
43969
  status: response.status,
43970
43970
  payload
@@ -43973,7 +43973,7 @@ var VocoderAPI = class {
43973
43973
  return payload;
43974
43974
  }
43975
43975
  async listApps(userToken, organizationId) {
43976
- const url = new URL(`${this.apiUrl}/api/cli/projects`);
43976
+ const url = new URL(`${this.apiUrl}/api/cli/apps`);
43977
43977
  url.searchParams.set("organizationId", organizationId);
43978
43978
  const response = await fetch(url.toString(), {
43979
43979
  headers: { Authorization: `Bearer ${userToken}` }
@@ -43983,14 +43983,14 @@ var VocoderAPI = class {
43983
43983
  throw new VocoderAPIError({
43984
43984
  message: extractErrorMessage(
43985
43985
  payload,
43986
- `Failed to list projects (${response.status})`
43986
+ `Failed to list apps (${response.status})`
43987
43987
  ),
43988
43988
  status: response.status,
43989
43989
  payload
43990
43990
  });
43991
43991
  }
43992
43992
  const result = payload;
43993
- return result.projects;
43993
+ return result.apps;
43994
43994
  }
43995
43995
  async regenerateProjectApiKey(userToken, projectId) {
43996
43996
  const response = await fetch(
@@ -44143,15 +44143,16 @@ var VocoderAPI = class {
44143
44143
  * @throws {VocoderAPIError} status 422 for invalid/unsupported locale code
44144
44144
  * @throws {VocoderAPIError} status 403 with limitError.limitType "target_locales" when plan limit reached
44145
44145
  */
44146
- async addLocale(locale, repoCanonical) {
44146
+ async addLocale(locale, repoCanonical, appId) {
44147
44147
  return this.request(
44148
- "/api/cli/project/locales",
44148
+ "/api/cli/app/locales",
44149
44149
  {
44150
44150
  method: "POST",
44151
44151
  headers: { "Content-Type": "application/json" },
44152
44152
  body: JSON.stringify({
44153
44153
  locale,
44154
- ...repoCanonical ? { repoCanonical } : {}
44154
+ ...repoCanonical ? { repoCanonical } : {},
44155
+ ...appId ? { appId } : {}
44155
44156
  })
44156
44157
  },
44157
44158
  "Failed to add locale"
@@ -44164,15 +44165,16 @@ var VocoderAPI = class {
44164
44165
  *
44165
44166
  * @throws {VocoderAPIError} on auth or server errors
44166
44167
  */
44167
- async removeLocale(locale, repoCanonical) {
44168
+ async removeLocale(locale, repoCanonical, appId) {
44168
44169
  return this.request(
44169
- "/api/cli/project/locales",
44170
+ "/api/cli/app/locales",
44170
44171
  {
44171
44172
  method: "DELETE",
44172
44173
  headers: { "Content-Type": "application/json" },
44173
44174
  body: JSON.stringify({
44174
44175
  locale,
44175
- ...repoCanonical ? { repoCanonical } : {}
44176
+ ...repoCanonical ? { repoCanonical } : {},
44177
+ ...appId ? { appId } : {}
44176
44178
  })
44177
44179
  },
44178
44180
  "Failed to remove locale"
@@ -44217,7 +44219,7 @@ var VocoderAPI = class {
44217
44219
  }
44218
44220
  // ── Project creation ──────────────────────────────────────────────────────────
44219
44221
  async createProject(userToken, params) {
44220
- const response = await fetch(`${this.apiUrl}/api/cli/projects`, {
44222
+ const response = await fetch(`${this.apiUrl}/api/cli/apps`, {
44221
44223
  method: "POST",
44222
44224
  headers: {
44223
44225
  "Content-Type": "application/json",
@@ -44255,16 +44257,17 @@ var VocoderAPI = class {
44255
44257
  })
44256
44258
  });
44257
44259
  if (!response.ok) {
44258
- return { exactMatch: null, existingApps: [], hasWholeRepoApp: false };
44260
+ return { exactMatch: null, existingApps: [], hasWholeRepoApp: false, organizationContext: null };
44259
44261
  }
44260
44262
  const data = await response.json();
44261
44263
  return {
44262
44264
  exactMatch: data.exactMatch ?? null,
44263
44265
  existingApps: data.existingApps ?? [],
44264
- hasWholeRepoApp: data.hasWholeRepoApp ?? false
44266
+ hasWholeRepoApp: data.hasWholeRepoApp ?? false,
44267
+ organizationContext: data.organizationContext ?? null
44265
44268
  };
44266
44269
  } catch {
44267
- return { exactMatch: null, existingApps: [], hasWholeRepoApp: false };
44270
+ return { exactMatch: null, existingApps: [], hasWholeRepoApp: false, organizationContext: null };
44268
44271
  }
44269
44272
  }
44270
44273
  /**
@@ -44272,7 +44275,7 @@ var VocoderAPI = class {
44272
44275
  * Does not check plan limits — no new project is created.
44273
44276
  */
44274
44277
  async createApp(userToken, params) {
44275
- const response = await fetch(`${this.apiUrl}/api/cli/project/apps`, {
44278
+ const response = await fetch(`${this.apiUrl}/api/cli/apps`, {
44276
44279
  method: "POST",
44277
44280
  headers: {
44278
44281
  "Content-Type": "application/json",
@@ -44703,8 +44706,7 @@ function extractFromObject(obj) {
44703
44706
  }
44704
44707
 
44705
44708
  // ../extractor/src/index.ts
44706
- var import_parser2 = __toESM(require_lib());
44707
- var import_traverse2 = __toESM(require_lib8());
44709
+ import { createHash } from "crypto";
44708
44710
  import { readFileSync as readFileSync4 } from "fs";
44709
44711
  import { relative as pathRelative } from "path";
44710
44712
 
@@ -51078,9 +51080,16 @@ var glob = Object.assign(glob_, {
51078
51080
  });
51079
51081
  glob.glob = glob;
51080
51082
 
51081
- // ../extractor/src/hash.ts
51082
- function generateMessageHash(text, context) {
51083
- const input = context ? `${text}${context}` : text;
51083
+ // ../extractor/src/parse/react.ts
51084
+ var import_parser3 = __toESM(require_lib());
51085
+ var import_traverse3 = __toESM(require_lib8());
51086
+
51087
+ // ../core/src/hash.ts
51088
+ function generateMessageHash(text, context, formality) {
51089
+ let input = context ? `${text}${context}` : text;
51090
+ if (formality === "formal" || formality === "informal") {
51091
+ input += `${formality}`;
51092
+ }
51084
51093
  let h = 2166136261 >>> 0;
51085
51094
  for (let i = 0; i < input.length; i++) {
51086
51095
  h = Math.imul(h ^ input.charCodeAt(i), 16777619) >>> 0;
@@ -51088,10 +51097,10 @@ function generateMessageHash(text, context) {
51088
51097
  return h.toString(36).padStart(7, "0");
51089
51098
  }
51090
51099
 
51091
- // ../extractor/src/index.ts
51092
- var traverse2 = import_traverse2.default.default || import_traverse2.default;
51100
+ // ../extractor/src/shared/icu-builders.ts
51093
51101
  var PLURAL_CLDR = /* @__PURE__ */ new Set(["zero", "one", "two", "few", "many"]);
51094
51102
  var ALL_CLDR = /* @__PURE__ */ new Set(["zero", "one", "two", "few", "many", "other"]);
51103
+ var DEFAULT_ORDINAL_ICU = "{count, selectordinal, other {#}}";
51095
51104
  function buildPluralICU(props, ordinal = false) {
51096
51105
  const type = ordinal ? "selectordinal" : "plural";
51097
51106
  const exactParts = [];
@@ -51123,6 +51132,83 @@ function buildSelectICU(props) {
51123
51132
  if (!hasOther) cases.push("other {other}");
51124
51133
  return `{value, select, ${cases.join(" ")}}`;
51125
51134
  }
51135
+
51136
+ // ../extractor/src/shared/roles.ts
51137
+ function propNameToUiRole(propName) {
51138
+ switch (propName) {
51139
+ case "placeholder":
51140
+ return "input_placeholder";
51141
+ case "aria-label":
51142
+ case "aria-description":
51143
+ case "label":
51144
+ return "input_label";
51145
+ case "alt":
51146
+ return "image_alt";
51147
+ case "title":
51148
+ return "tooltip";
51149
+ default:
51150
+ return "unknown";
51151
+ }
51152
+ }
51153
+ function elementNameToUiRole(name) {
51154
+ if (!name) return "unknown";
51155
+ switch (name.toLowerCase()) {
51156
+ case "button":
51157
+ return "button_label";
51158
+ case "h1":
51159
+ case "h2":
51160
+ case "h3":
51161
+ case "h4":
51162
+ case "h5":
51163
+ case "h6":
51164
+ return "heading";
51165
+ case "label":
51166
+ return "input_label";
51167
+ case "th":
51168
+ return "table_header";
51169
+ case "option":
51170
+ return "option_label";
51171
+ case "title":
51172
+ return "page_title";
51173
+ case "p":
51174
+ case "li":
51175
+ case "dd":
51176
+ return "body_text";
51177
+ default: {
51178
+ const lower = name.toLowerCase();
51179
+ if (/button|btn|submit|cta/.test(lower)) return "button_label";
51180
+ if (/heading|headline/.test(lower)) return "heading";
51181
+ if (/label/.test(lower)) return "input_label";
51182
+ if (/tooltip|hint|popover/.test(lower)) return "tooltip";
51183
+ if (/badge|chip|tag|pill/.test(lower)) return "badge";
51184
+ if (/toast|snackbar|notification/.test(lower)) return "toast";
51185
+ if (/navitem|menuitem/.test(lower)) return "nav_item";
51186
+ return "unknown";
51187
+ }
51188
+ }
51189
+ }
51190
+ function detectUiRole(path2) {
51191
+ const parent = path2.parent;
51192
+ if (!parent) return "unknown";
51193
+ if (parent.type === "JSXExpressionContainer") {
51194
+ const attrNode = path2.parentPath?.parent;
51195
+ if (attrNode?.type === "JSXAttribute") {
51196
+ const propName = attrNode.name?.type === "JSXNamespacedName" ? `${attrNode.name.namespace.name}-${attrNode.name.name.name}` : attrNode.name?.name ?? "";
51197
+ return propNameToUiRole(propName);
51198
+ }
51199
+ }
51200
+ if (parent.type === "JSXElement") {
51201
+ const opening = parent.openingElement;
51202
+ const tagName = opening?.name?.type === "JSXMemberExpression" ? "unknown" : opening?.name?.name ?? "";
51203
+ return elementNameToUiRole(tagName);
51204
+ }
51205
+ return "unknown";
51206
+ }
51207
+
51208
+ // ../extractor/src/shared/transform.ts
51209
+ var import_parser2 = __toESM(require_lib());
51210
+ var import_traverse2 = __toESM(require_lib8());
51211
+ var traverse2 = import_traverse2.default.default || import_traverse2.default;
51126
51212
  function extractTextContentFromNodes(children, ctx) {
51127
51213
  let text = "";
51128
51214
  for (const child of children) {
@@ -51186,140 +51272,109 @@ function extractTextContentFromNodes(children, ctx) {
51186
51272
  }
51187
51273
  return text;
51188
51274
  }
51189
- var StringExtractor = class {
51190
- async extractFromProject(pattern, projectRoot = process.cwd(), excludePattern) {
51191
- const includePatterns = Array.isArray(pattern) ? pattern : [pattern];
51192
- const defaultIgnore = [
51193
- "**/node_modules/**",
51194
- "**/.next/**",
51195
- "**/dist/**",
51196
- "**/build/**"
51197
- ];
51198
- const ignorePatterns = excludePattern ? [
51199
- ...defaultIgnore,
51200
- ...Array.isArray(excludePattern) ? excludePattern : [excludePattern]
51201
- ] : defaultIgnore;
51202
- const allFiles = /* @__PURE__ */ new Set();
51203
- for (const includePattern of includePatterns) {
51204
- const files = await glob(includePattern, {
51205
- cwd: projectRoot,
51206
- absolute: true,
51207
- ignore: ignorePatterns
51208
- });
51209
- for (const file of files) allFiles.add(file);
51210
- }
51211
- const allStrings = [];
51212
- const sortedFiles = Array.from(allFiles).sort();
51213
- for (const file of sortedFiles) {
51214
- try {
51215
- const code = readFileSync4(file, "utf-8");
51216
- const relPath = pathRelative(projectRoot, file).split("\\").join("/");
51217
- const strings = _extractFromContent(relPath, code);
51218
- allStrings.push(...strings);
51219
- } catch (error) {
51220
- console.warn(`Warning: Failed to extract from ${file}:`, error);
51275
+
51276
+ // ../extractor/src/parse/react.ts
51277
+ var traverse3 = import_traverse3.default.default || import_traverse3.default;
51278
+ function extractTemplateText(node) {
51279
+ let text = "";
51280
+ for (let i = 0; i < node.quasis.length; i++) {
51281
+ const quasi = node.quasis[i];
51282
+ text += quasi.value.raw;
51283
+ if (i < node.expressions.length) {
51284
+ const expr = node.expressions[i];
51285
+ if (expr.type === "Identifier") {
51286
+ text += `{${expr.name}}`;
51287
+ } else {
51288
+ text += "{value}";
51221
51289
  }
51222
51290
  }
51223
- return deduplicateStrings(allStrings);
51224
51291
  }
51225
- };
51226
- function propNameToUiRole(propName) {
51227
- switch (propName) {
51228
- case "placeholder":
51229
- return "input_placeholder";
51230
- case "aria-label":
51231
- case "aria-description":
51232
- case "label":
51233
- return "input_label";
51234
- case "alt":
51235
- return "image_alt";
51236
- case "title":
51237
- return "tooltip";
51238
- default:
51239
- return "unknown";
51292
+ return text;
51293
+ }
51294
+ function getStringAttribute(attributes, name) {
51295
+ const attr = attributes.find(
51296
+ (a) => a.type === "JSXAttribute" && a.name.name === name
51297
+ );
51298
+ if (!attr || !attr.value) return void 0;
51299
+ if (attr.value.type === "StringLiteral") {
51300
+ return attr.value.value;
51301
+ }
51302
+ if (attr.value.type === "JSXExpressionContainer") {
51303
+ const expr = attr.value.expression;
51304
+ if (expr.type === "TemplateLiteral") return extractTemplateText(expr);
51305
+ if (expr.type === "StringLiteral") return expr.value;
51240
51306
  }
51307
+ return void 0;
51241
51308
  }
51242
- function elementNameToUiRole(name) {
51243
- if (!name) return "unknown";
51244
- switch (name.toLowerCase()) {
51245
- case "button":
51246
- return "button_label";
51247
- case "h1":
51248
- case "h2":
51249
- case "h3":
51250
- case "h4":
51251
- case "h5":
51252
- case "h6":
51253
- return "heading";
51254
- case "label":
51255
- return "input_label";
51256
- case "th":
51257
- return "table_header";
51258
- case "option":
51259
- return "option_label";
51260
- case "title":
51261
- return "page_title";
51262
- case "p":
51263
- case "li":
51264
- case "dd":
51265
- return "body_text";
51266
- // Custom component name heuristics
51267
- default: {
51268
- const lower = name.toLowerCase();
51269
- if (/button|btn|submit|cta/.test(lower)) return "button_label";
51270
- if (/heading|headline/.test(lower)) return "heading";
51271
- if (/label/.test(lower)) return "input_label";
51272
- if (/tooltip|hint|popover/.test(lower)) return "tooltip";
51273
- if (/badge|chip|tag|pill/.test(lower)) return "badge";
51274
- if (/toast|snackbar|notification/.test(lower)) return "toast";
51275
- if (/navitem|menuitem/.test(lower)) return "nav_item";
51276
- return "unknown";
51309
+ function extractPluralSelectICU(attributes) {
51310
+ const pluralProps = {};
51311
+ const selectProps = {};
51312
+ let otherValue;
51313
+ let hasPlural = false;
51314
+ let hasSelect = false;
51315
+ let isOrdinal = false;
51316
+ let hasGender = false;
51317
+ for (const attr of attributes) {
51318
+ if (attr.type !== "JSXAttribute") continue;
51319
+ const name = attr.name.name;
51320
+ if (name === "ordinal") {
51321
+ isOrdinal = true;
51322
+ continue;
51323
+ }
51324
+ if (name === "gender") {
51325
+ hasGender = true;
51326
+ continue;
51327
+ }
51328
+ const value = attr.value?.type === "StringLiteral" ? attr.value.value : null;
51329
+ if (!value) continue;
51330
+ if (PLURAL_CLDR.has(name) || /^_\d+$/.test(name)) {
51331
+ pluralProps[name] = value;
51332
+ hasPlural = true;
51333
+ } else if (name === "other") {
51334
+ otherValue = value;
51335
+ } else if (/^_[a-zA-Z]/.test(name)) {
51336
+ selectProps[name] = value;
51337
+ hasSelect = true;
51277
51338
  }
51278
51339
  }
51279
- }
51280
- function detectUiRole(path2) {
51281
- const parent = path2.parent;
51282
- if (!parent) return "unknown";
51283
- if (parent.type === "JSXExpressionContainer") {
51284
- const attrNode = path2.parentPath?.parent;
51285
- if (attrNode?.type === "JSXAttribute") {
51286
- const propName = attrNode.name?.type === "JSXNamespacedName" ? `${attrNode.name.namespace.name}-${attrNode.name.name.name}` : attrNode.name?.name ?? "";
51287
- return propNameToUiRole(propName);
51340
+ if (isOrdinal) {
51341
+ const ordinalICU = DEFAULT_ORDINAL_ICU;
51342
+ if (hasGender) {
51343
+ return `{gender, select, masculine {${ordinalICU}} feminine {${ordinalICU}} other {${ordinalICU}}}`;
51288
51344
  }
51345
+ return ordinalICU;
51289
51346
  }
51290
- if (parent.type === "JSXElement") {
51291
- const opening = parent.openingElement;
51292
- const tagName = opening?.name?.type === "JSXMemberExpression" ? "unknown" : opening?.name?.name ?? "";
51293
- return elementNameToUiRole(tagName);
51347
+ if (!hasPlural && !hasSelect) return null;
51348
+ if (hasPlural) {
51349
+ if (otherValue !== void 0) pluralProps.other = otherValue;
51350
+ return buildPluralICU(pluralProps, false);
51294
51351
  }
51295
- return "unknown";
51352
+ if (hasSelect) {
51353
+ if (otherValue !== void 0) selectProps.other = otherValue;
51354
+ return buildSelectICU(selectProps);
51355
+ }
51356
+ return null;
51296
51357
  }
51297
- function _extractFromContent(filePath, content) {
51358
+ function extractFromContent(filePath, content) {
51298
51359
  const strings = [];
51299
51360
  try {
51300
- const ast = (0, import_parser2.parse)(content, {
51361
+ const ast = (0, import_parser3.parse)(content, {
51301
51362
  sourceType: "module",
51302
51363
  plugins: ["jsx", "typescript"]
51303
51364
  });
51304
51365
  const vocoderImports = /* @__PURE__ */ new Map();
51305
51366
  const tFunctionNames = /* @__PURE__ */ new Set();
51306
- traverse2(ast, {
51367
+ traverse3(ast, {
51307
51368
  ImportDeclaration: (path2) => {
51308
- const source = path2.node.source.value;
51309
- if (source === "@vocoder/react") {
51310
- path2.node.specifiers.forEach((spec) => {
51311
- if (spec.type === "ImportSpecifier") {
51312
- const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
51313
- const local = spec.local.name;
51314
- if (imported === "T") {
51315
- vocoderImports.set(local, "T");
51316
- }
51317
- if (imported === "t") {
51318
- tFunctionNames.add(local);
51319
- }
51320
- }
51321
- });
51322
- }
51369
+ if (path2.node.source.value !== "@vocoder/react") return;
51370
+ path2.node.specifiers.forEach((spec) => {
51371
+ if (spec.type === "ImportSpecifier") {
51372
+ const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
51373
+ const local = spec.local.name;
51374
+ if (imported === "T") vocoderImports.set(local, "T");
51375
+ if (imported === "t") tFunctionNames.add(local);
51376
+ }
51377
+ });
51323
51378
  },
51324
51379
  VariableDeclarator: (path2) => {
51325
51380
  const init = path2.node.init;
@@ -51365,7 +51420,7 @@ function _extractFromContent(filePath, content) {
51365
51420
  });
51366
51421
  }
51367
51422
  const line = path2.node.loc?.start.line || 0;
51368
- const key = explicitKey && explicitKey.length > 0 ? explicitKey : generateMessageHash(text.trim(), context);
51423
+ const key = explicitKey && explicitKey.length > 0 ? explicitKey + (formality === "formal" || formality === "informal" ? `${formality}` : "") : generateMessageHash(text.trim(), context, formality);
51369
51424
  const uiRole = detectUiRole(path2);
51370
51425
  strings.push({
51371
51426
  key,
@@ -51404,19 +51459,21 @@ function _extractFromContent(filePath, content) {
51404
51459
  if (extractCtx.bail) return;
51405
51460
  }
51406
51461
  }
51407
- if (!text || text.trim().length === 0) return;
51408
51462
  const id = getStringAttribute(opening.attributes, "id");
51409
51463
  const context = getStringAttribute(opening.attributes, "context");
51410
51464
  const formality = getStringAttribute(
51411
51465
  opening.attributes,
51412
51466
  "formality"
51413
51467
  );
51468
+ const trimmedId = id?.trim() || void 0;
51469
+ const trimmedText = text?.trim() || void 0;
51470
+ if (!trimmedText && !trimmedId) return;
51414
51471
  const line = path2.node.loc?.start.line || 0;
51415
- const key = id && id.trim().length > 0 ? id.trim() : generateMessageHash(text.trim(), context);
51472
+ const key = trimmedId ? trimmedId + (formality === "formal" || formality === "informal" ? `${formality}` : "") : generateMessageHash(trimmedText, context, formality);
51416
51473
  const uiRole = detectUiRole(path2);
51417
51474
  strings.push({
51418
51475
  key,
51419
- text: text.trim(),
51476
+ text: trimmedText ?? null,
51420
51477
  file: filePath,
51421
51478
  line,
51422
51479
  context,
@@ -51432,90 +51489,49 @@ function _extractFromContent(filePath, content) {
51432
51489
  }
51433
51490
  return strings;
51434
51491
  }
51435
- function extractPluralSelectICU(attributes) {
51436
- const pluralProps = {};
51437
- const selectProps = {};
51438
- let otherValue;
51439
- let hasPlural = false;
51440
- let hasSelect = false;
51441
- let isOrdinal = false;
51442
- let hasGender = false;
51443
- for (const attr of attributes) {
51444
- if (attr.type !== "JSXAttribute") continue;
51445
- const name = attr.name.name;
51446
- if (name === "ordinal") {
51447
- isOrdinal = true;
51448
- continue;
51449
- }
51450
- if (name === "gender") {
51451
- hasGender = true;
51452
- continue;
51453
- }
51454
- const value = attr.value?.type === "StringLiteral" ? attr.value.value : null;
51455
- if (!value) continue;
51456
- if (PLURAL_CLDR.has(name) || /^_\d+$/.test(name)) {
51457
- pluralProps[name] = value;
51458
- hasPlural = true;
51459
- } else if (name === "other") {
51460
- otherValue = value;
51461
- } else if (/^_[a-zA-Z]/.test(name)) {
51462
- selectProps[name] = value;
51463
- hasSelect = true;
51464
- }
51465
- }
51466
- if (isOrdinal) {
51467
- const ordinalICU = "{count, selectordinal, other {#}}";
51468
- if (hasGender) {
51469
- return `{gender, select, masculine {${ordinalICU}} feminine {${ordinalICU}} other {${ordinalICU}}}`;
51470
- }
51471
- return ordinalICU;
51472
- }
51473
- if (!hasPlural && !hasSelect) return null;
51474
- if (hasPlural) {
51475
- if (otherValue !== void 0) pluralProps.other = otherValue;
51476
- return buildPluralICU(pluralProps, false);
51477
- }
51478
- if (hasSelect) {
51479
- if (otherValue !== void 0) selectProps.other = otherValue;
51480
- return buildSelectICU(selectProps);
51481
- }
51482
- return null;
51483
- }
51484
- function extractTemplateText(node) {
51485
- let text = "";
51486
- for (let i = 0; i < node.quasis.length; i++) {
51487
- const quasi = node.quasis[i];
51488
- text += quasi.value.raw;
51489
- if (i < node.expressions.length) {
51490
- const expr = node.expressions[i];
51491
- if (expr.type === "Identifier") {
51492
- text += `{${expr.name}}`;
51493
- } else {
51494
- text += "{value}";
51495
- }
51496
- }
51497
- }
51498
- return text;
51492
+
51493
+ // ../extractor/src/index.ts
51494
+ function computeFingerprint(appShortCode, sourceKeys) {
51495
+ const sorted = [...sourceKeys].sort();
51496
+ return createHash("sha256").update(`${appShortCode}:${sorted.join("\0")}`).digest("hex").slice(0, 12);
51499
51497
  }
51500
- function getStringAttribute(attributes, name) {
51501
- const attr = attributes.find(
51502
- (a) => a.type === "JSXAttribute" && a.name.name === name
51503
- );
51504
- if (!attr || !attr.value) return void 0;
51505
- if (attr.value.type === "StringLiteral") {
51506
- return attr.value.value;
51507
- }
51508
- if (attr.value.type === "JSXExpressionContainer") {
51509
- const expr = attr.value.expression;
51510
- if (expr.type === "TemplateLiteral") {
51511
- return extractTemplateText(expr);
51498
+ var StringExtractor = class {
51499
+ async extractFromProject(pattern, projectRoot = process.cwd(), excludePattern) {
51500
+ const includePatterns = Array.isArray(pattern) ? pattern : [pattern];
51501
+ const defaultIgnore = [
51502
+ "**/node_modules/**",
51503
+ "**/.next/**",
51504
+ "**/dist/**",
51505
+ "**/build/**"
51506
+ ];
51507
+ const ignorePatterns = excludePattern ? [
51508
+ ...defaultIgnore,
51509
+ ...Array.isArray(excludePattern) ? excludePattern : [excludePattern]
51510
+ ] : defaultIgnore;
51511
+ const allFiles = /* @__PURE__ */ new Set();
51512
+ for (const includePattern of includePatterns) {
51513
+ const files = await glob(includePattern, {
51514
+ cwd: projectRoot,
51515
+ absolute: true,
51516
+ ignore: ignorePatterns
51517
+ });
51518
+ for (const file of files) allFiles.add(file);
51512
51519
  }
51513
- if (expr.type === "StringLiteral") {
51514
- return expr.value;
51520
+ const allStrings = [];
51521
+ const sortedFiles = Array.from(allFiles).sort();
51522
+ for (const file of sortedFiles) {
51523
+ try {
51524
+ const code = readFileSync4(file, "utf-8");
51525
+ const relPath = pathRelative(projectRoot, file).split("\\").join("/");
51526
+ const strings = extractFromContent(relPath, code);
51527
+ allStrings.push(...strings);
51528
+ } catch (error) {
51529
+ console.warn(`Warning: Failed to extract from ${file}:`, error);
51530
+ }
51515
51531
  }
51532
+ return deduplicateStrings(allStrings);
51516
51533
  }
51517
- return void 0;
51518
- }
51534
+ };
51519
51535
  function deduplicateStrings(strings) {
51520
51536
  const seen = /* @__PURE__ */ new Set();
51521
51537
  const unique = [];
@@ -51540,6 +51556,7 @@ export {
51540
51556
  clearAuthData,
51541
51557
  getSetupSnippets,
51542
51558
  loadVocoderConfig,
51559
+ computeFingerprint,
51543
51560
  StringExtractor
51544
51561
  };
51545
- //# sourceMappingURL=chunk-T4BLNDJ3.mjs.map
51562
+ //# sourceMappingURL=chunk-62KCB6C6.mjs.map