solid-translate 1.4.1 → 1.4.2

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/README.md CHANGED
@@ -350,9 +350,29 @@ solidTranslate({
350
350
  batchSize: 50, // Keys per API call (default: 50)
351
351
  autoExtract: true, // Auto-extract <T> and msg() strings (default: false)
352
352
  include: ["src/**/*.tsx"], // Files to scan for extraction
353
+ extractImportSources: ["@/i18n"], // Extra module specifiers whose msg/<T> imports count as markers (optional)
353
354
  })
354
355
  ```
355
356
 
357
+ ### What extraction considers a marker
358
+
359
+ Extraction only honors `msg()` calls and `<T>`/`<Plural>` elements whose
360
+ identifier actually refers to solid-translate:
361
+
362
+ - Imported bindings must come from `"solid-translate"` or an accepted
363
+ re-export wrapper. By default any specifier whose final path segment is
364
+ `solid-translate` or `i18n` (e.g. `@/i18n`, `../lib/i18n`) is accepted;
365
+ set `extractImportSources` (plugin config) / `"extractImportSources"`
366
+ (CLI config) to an explicit list to override the `i18n` heuristic
367
+ (`"solid-translate"` itself is always accepted).
368
+ - Aliased imports work: `import { msg as m } from "solid-translate"`
369
+ extracts `m("...")`.
370
+ - Locally bound identifiers are never extracted — a callback parameter
371
+ named `msg`, a local `const msg = ...`, or a local component named `T`
372
+ will not pollute the catalog or emit warnings.
373
+ - Identifiers with no binding at all are still treated as markers by name,
374
+ so snippet-style sources keep working.
375
+
356
376
  ## CLI
357
377
 
358
378
  For translating locale files, JSON, Markdown, and MDX outside of the Vite build.
package/dist/cli.js CHANGED
@@ -14599,7 +14599,7 @@ function hashContent(content) {
14599
14599
 
14600
14600
  // src/extract.ts
14601
14601
  var import_parser = __toESM(require_lib(), 1);
14602
- function extractStringsFromSource(code, filePath, warnings) {
14602
+ function extractStringsFromSource(code, filePath, warnings, options) {
14603
14603
  const results = [];
14604
14604
  const seen = /* @__PURE__ */ new Set();
14605
14605
  const warn = (line, message) => {
@@ -14621,26 +14621,158 @@ function extractStringsFromSource(code, filePath, warnings) {
14621
14621
  seen.add(entry.key);
14622
14622
  results.push(entry);
14623
14623
  };
14624
+ const bindings = collectModuleBindings(ast);
14625
+ const shadowStack = [];
14626
+ const resolveMarker = (localName) => {
14627
+ for (let i = shadowStack.length - 1; i >= 0; i--) {
14628
+ if (shadowStack[i].has(localName)) return null;
14629
+ }
14630
+ const imported = bindings.imports.get(localName);
14631
+ if (imported) {
14632
+ if (!isAcceptedImportSource(imported.source, options?.importSources)) {
14633
+ return null;
14634
+ }
14635
+ return MARKER_NAMES.has(imported.imported) ? imported.imported : null;
14636
+ }
14637
+ if (bindings.moduleLocals.has(localName)) return null;
14638
+ return MARKER_NAMES.has(localName) ? localName : null;
14639
+ };
14624
14640
  const visit = (node) => {
14641
+ const scopeBindings = collectScopeBindings(node);
14642
+ if (scopeBindings) shadowStack.push(scopeBindings);
14625
14643
  if (node.type === "JSXElement") {
14626
14644
  const name = jsxName(node);
14627
- if (name === "T") {
14645
+ const marker = name ? resolveMarker(name) : null;
14646
+ if (marker === "T") {
14628
14647
  const entry = processT(node, filePath, warn);
14629
14648
  if (entry) push(entry);
14630
- } else if (name === "Plural") {
14649
+ } else if (marker === "Plural") {
14631
14650
  for (const entry of processPlural(node, filePath, warn)) {
14632
14651
  push(entry);
14633
14652
  }
14634
14653
  }
14635
- } else if (node.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "msg") {
14654
+ } else if (node.type === "CallExpression" && node.callee?.type === "Identifier" && resolveMarker(node.callee.name) === "msg") {
14636
14655
  const entry = processMsg(node, filePath, warn);
14637
14656
  if (entry) push(entry);
14638
14657
  }
14639
14658
  walkChildren(node, visit);
14659
+ if (scopeBindings) shadowStack.pop();
14640
14660
  };
14641
14661
  visit(ast);
14642
14662
  return results;
14643
14663
  }
14664
+ var MARKER_NAMES = /* @__PURE__ */ new Set([
14665
+ "msg",
14666
+ "T",
14667
+ "Var",
14668
+ "Num",
14669
+ "Currency",
14670
+ "DateTime",
14671
+ "Plural"
14672
+ ]);
14673
+ var DEFAULT_IMPORT_SOURCE_RE = /(^|\/)(solid-translate|i18n)(\.[cm]?[jt]sx?)?$/;
14674
+ function isAcceptedImportSource(source, importSources) {
14675
+ if (source === "solid-translate") return true;
14676
+ if (importSources) return importSources.includes(source);
14677
+ return DEFAULT_IMPORT_SOURCE_RE.test(source);
14678
+ }
14679
+ function collectModuleBindings(ast) {
14680
+ const imports = /* @__PURE__ */ new Map();
14681
+ const moduleLocals = /* @__PURE__ */ new Set();
14682
+ const body = ast.program?.body ?? [];
14683
+ for (const stmt of body) {
14684
+ if (stmt.type === "ImportDeclaration") {
14685
+ const source = String(stmt.source?.value ?? "");
14686
+ for (const spec of stmt.specifiers ?? []) {
14687
+ const local = spec.local?.name;
14688
+ if (typeof local !== "string") continue;
14689
+ if (spec.type === "ImportSpecifier") {
14690
+ const imported = spec.imported?.type === "Identifier" ? spec.imported.name : String(spec.imported?.value ?? "");
14691
+ imports.set(local, { imported, source });
14692
+ } else {
14693
+ imports.set(local, { imported: "*", source });
14694
+ }
14695
+ }
14696
+ } else {
14697
+ collectDeclaredNames(stmt, moduleLocals);
14698
+ }
14699
+ }
14700
+ return { imports, moduleLocals };
14701
+ }
14702
+ function collectDeclaredNames(stmt, into) {
14703
+ if (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") {
14704
+ if (stmt.declaration) collectDeclaredNames(stmt.declaration, into);
14705
+ return;
14706
+ }
14707
+ if (stmt.type === "VariableDeclaration") {
14708
+ for (const decl of stmt.declarations ?? []) {
14709
+ if (decl.id) collectPatternNames(decl.id, into);
14710
+ }
14711
+ return;
14712
+ }
14713
+ if ((stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration" || stmt.type === "TSEnumDeclaration") && stmt.id?.type === "Identifier") {
14714
+ addMarkerName(stmt.id.name, into);
14715
+ }
14716
+ }
14717
+ function collectPatternNames(pattern, into) {
14718
+ switch (pattern.type) {
14719
+ case "Identifier":
14720
+ addMarkerName(pattern.name, into);
14721
+ break;
14722
+ case "AssignmentPattern":
14723
+ collectPatternNames(pattern.left, into);
14724
+ break;
14725
+ case "RestElement":
14726
+ collectPatternNames(pattern.argument, into);
14727
+ break;
14728
+ case "ObjectPattern":
14729
+ for (const prop of pattern.properties ?? []) {
14730
+ if (prop.type === "ObjectProperty") {
14731
+ collectPatternNames(prop.value, into);
14732
+ } else if (prop.type === "RestElement") {
14733
+ collectPatternNames(prop.argument, into);
14734
+ }
14735
+ }
14736
+ break;
14737
+ case "ArrayPattern":
14738
+ for (const el of pattern.elements ?? []) {
14739
+ if (el) collectPatternNames(el, into);
14740
+ }
14741
+ break;
14742
+ }
14743
+ }
14744
+ function addMarkerName(name, into) {
14745
+ if (typeof name === "string" && MARKER_NAMES.has(name)) into.add(name);
14746
+ }
14747
+ var FUNCTION_TYPES = /* @__PURE__ */ new Set([
14748
+ "ArrowFunctionExpression",
14749
+ "FunctionExpression",
14750
+ "FunctionDeclaration",
14751
+ "ObjectMethod",
14752
+ "ClassMethod",
14753
+ "ClassPrivateMethod"
14754
+ ]);
14755
+ function collectScopeBindings(node) {
14756
+ const bound = /* @__PURE__ */ new Set();
14757
+ if (FUNCTION_TYPES.has(node.type)) {
14758
+ if (node.id?.type === "Identifier") addMarkerName(node.id.name, bound);
14759
+ for (const param of node.params ?? []) {
14760
+ collectPatternNames(param, bound);
14761
+ }
14762
+ } else if (node.type === "CatchClause" && node.param) {
14763
+ collectPatternNames(node.param, bound);
14764
+ } else if (node.type === "BlockStatement") {
14765
+ for (const stmt of node.body ?? []) {
14766
+ collectDeclaredNames(stmt, bound);
14767
+ }
14768
+ } else if (node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement") {
14769
+ const init = node.init ?? node.left;
14770
+ if (init?.type === "VariableDeclaration") {
14771
+ collectDeclaredNames(init, bound);
14772
+ }
14773
+ }
14774
+ return bound.size > 0 ? bound : null;
14775
+ }
14644
14776
  function walkChildren(node, visit) {
14645
14777
  for (const key of Object.keys(node)) {
14646
14778
  if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") {
@@ -15229,7 +15361,8 @@ async function runExtract() {
15229
15361
  const extracted = extractStringsFromSource(
15230
15362
  code,
15231
15363
  relative(root, file),
15232
- warnings
15364
+ warnings,
15365
+ { importSources: config.extractImportSources }
15233
15366
  );
15234
15367
  for (const entry of extracted) {
15235
15368
  strings[entry.key] = entry.source;
@@ -15286,7 +15419,9 @@ async function runCheck(jsonOutput) {
15286
15419
  const code = readFileSync2(file, "utf-8");
15287
15420
  const entries = extractStringsFromSource(
15288
15421
  code,
15289
- relative(root, file)
15422
+ relative(root, file),
15423
+ void 0,
15424
+ { importSources: config.extractImportSources }
15290
15425
  );
15291
15426
  for (const entry of entries) {
15292
15427
  extracted[entry.key] = entry.source;
package/dist/index.d.ts CHANGED
@@ -36,6 +36,13 @@ interface SolidTranslatePluginConfig {
36
36
  * Default: `["src/**\/*.tsx", "src/**\/*.ts", "src/**\/*.jsx"]`
37
37
  */
38
38
  include?: string[];
39
+ /**
40
+ * Module specifiers accepted as sources of the extraction markers
41
+ * (`msg`, `<T>`, `<Plural>`, ...). `"solid-translate"` is always
42
+ * accepted. When omitted, any specifier whose final path segment is
43
+ * `solid-translate` or `i18n` (e.g. `"@/i18n"`) is accepted.
44
+ */
45
+ extractImportSources?: string[];
39
46
  }
40
47
  /** A flat dictionary mapping keys to translated strings */
41
48
  type TranslationDictionary = Record<string, string>;
package/dist/vite.d.ts CHANGED
@@ -36,6 +36,13 @@ interface SolidTranslatePluginConfig {
36
36
  * Default: `["src/**\/*.tsx", "src/**\/*.ts", "src/**\/*.jsx"]`
37
37
  */
38
38
  include?: string[];
39
+ /**
40
+ * Module specifiers accepted as sources of the extraction markers
41
+ * (`msg`, `<T>`, `<Plural>`, ...). `"solid-translate"` is always
42
+ * accepted. When omitted, any specifier whose final path segment is
43
+ * `solid-translate` or `i18n` (e.g. `"@/i18n"`) is accepted.
44
+ */
45
+ extractImportSources?: string[];
39
46
  }
40
47
 
41
48
  /**
package/dist/vite.js CHANGED
@@ -14665,7 +14665,7 @@ async function translateBatch(model, entries, targetLocale, sourceLocale, system
14665
14665
 
14666
14666
  // src/extract.ts
14667
14667
  var import_parser = __toESM(require_lib(), 1);
14668
- function extractStringsFromSource(code, filePath, warnings) {
14668
+ function extractStringsFromSource(code, filePath, warnings, options) {
14669
14669
  const results = [];
14670
14670
  const seen = /* @__PURE__ */ new Set();
14671
14671
  const warn = (line, message) => {
@@ -14687,26 +14687,158 @@ function extractStringsFromSource(code, filePath, warnings) {
14687
14687
  seen.add(entry.key);
14688
14688
  results.push(entry);
14689
14689
  };
14690
+ const bindings = collectModuleBindings(ast);
14691
+ const shadowStack = [];
14692
+ const resolveMarker = (localName) => {
14693
+ for (let i = shadowStack.length - 1; i >= 0; i--) {
14694
+ if (shadowStack[i].has(localName)) return null;
14695
+ }
14696
+ const imported = bindings.imports.get(localName);
14697
+ if (imported) {
14698
+ if (!isAcceptedImportSource(imported.source, options?.importSources)) {
14699
+ return null;
14700
+ }
14701
+ return MARKER_NAMES.has(imported.imported) ? imported.imported : null;
14702
+ }
14703
+ if (bindings.moduleLocals.has(localName)) return null;
14704
+ return MARKER_NAMES.has(localName) ? localName : null;
14705
+ };
14690
14706
  const visit = (node) => {
14707
+ const scopeBindings = collectScopeBindings(node);
14708
+ if (scopeBindings) shadowStack.push(scopeBindings);
14691
14709
  if (node.type === "JSXElement") {
14692
14710
  const name = jsxName(node);
14693
- if (name === "T") {
14711
+ const marker = name ? resolveMarker(name) : null;
14712
+ if (marker === "T") {
14694
14713
  const entry = processT(node, filePath, warn);
14695
14714
  if (entry) push(entry);
14696
- } else if (name === "Plural") {
14715
+ } else if (marker === "Plural") {
14697
14716
  for (const entry of processPlural(node, filePath, warn)) {
14698
14717
  push(entry);
14699
14718
  }
14700
14719
  }
14701
- } else if (node.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "msg") {
14720
+ } else if (node.type === "CallExpression" && node.callee?.type === "Identifier" && resolveMarker(node.callee.name) === "msg") {
14702
14721
  const entry = processMsg(node, filePath, warn);
14703
14722
  if (entry) push(entry);
14704
14723
  }
14705
14724
  walkChildren(node, visit);
14725
+ if (scopeBindings) shadowStack.pop();
14706
14726
  };
14707
14727
  visit(ast);
14708
14728
  return results;
14709
14729
  }
14730
+ var MARKER_NAMES = /* @__PURE__ */ new Set([
14731
+ "msg",
14732
+ "T",
14733
+ "Var",
14734
+ "Num",
14735
+ "Currency",
14736
+ "DateTime",
14737
+ "Plural"
14738
+ ]);
14739
+ var DEFAULT_IMPORT_SOURCE_RE = /(^|\/)(solid-translate|i18n)(\.[cm]?[jt]sx?)?$/;
14740
+ function isAcceptedImportSource(source, importSources) {
14741
+ if (source === "solid-translate") return true;
14742
+ if (importSources) return importSources.includes(source);
14743
+ return DEFAULT_IMPORT_SOURCE_RE.test(source);
14744
+ }
14745
+ function collectModuleBindings(ast) {
14746
+ const imports = /* @__PURE__ */ new Map();
14747
+ const moduleLocals = /* @__PURE__ */ new Set();
14748
+ const body = ast.program?.body ?? [];
14749
+ for (const stmt of body) {
14750
+ if (stmt.type === "ImportDeclaration") {
14751
+ const source = String(stmt.source?.value ?? "");
14752
+ for (const spec of stmt.specifiers ?? []) {
14753
+ const local = spec.local?.name;
14754
+ if (typeof local !== "string") continue;
14755
+ if (spec.type === "ImportSpecifier") {
14756
+ const imported = spec.imported?.type === "Identifier" ? spec.imported.name : String(spec.imported?.value ?? "");
14757
+ imports.set(local, { imported, source });
14758
+ } else {
14759
+ imports.set(local, { imported: "*", source });
14760
+ }
14761
+ }
14762
+ } else {
14763
+ collectDeclaredNames(stmt, moduleLocals);
14764
+ }
14765
+ }
14766
+ return { imports, moduleLocals };
14767
+ }
14768
+ function collectDeclaredNames(stmt, into) {
14769
+ if (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") {
14770
+ if (stmt.declaration) collectDeclaredNames(stmt.declaration, into);
14771
+ return;
14772
+ }
14773
+ if (stmt.type === "VariableDeclaration") {
14774
+ for (const decl of stmt.declarations ?? []) {
14775
+ if (decl.id) collectPatternNames(decl.id, into);
14776
+ }
14777
+ return;
14778
+ }
14779
+ if ((stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration" || stmt.type === "TSEnumDeclaration") && stmt.id?.type === "Identifier") {
14780
+ addMarkerName(stmt.id.name, into);
14781
+ }
14782
+ }
14783
+ function collectPatternNames(pattern, into) {
14784
+ switch (pattern.type) {
14785
+ case "Identifier":
14786
+ addMarkerName(pattern.name, into);
14787
+ break;
14788
+ case "AssignmentPattern":
14789
+ collectPatternNames(pattern.left, into);
14790
+ break;
14791
+ case "RestElement":
14792
+ collectPatternNames(pattern.argument, into);
14793
+ break;
14794
+ case "ObjectPattern":
14795
+ for (const prop of pattern.properties ?? []) {
14796
+ if (prop.type === "ObjectProperty") {
14797
+ collectPatternNames(prop.value, into);
14798
+ } else if (prop.type === "RestElement") {
14799
+ collectPatternNames(prop.argument, into);
14800
+ }
14801
+ }
14802
+ break;
14803
+ case "ArrayPattern":
14804
+ for (const el of pattern.elements ?? []) {
14805
+ if (el) collectPatternNames(el, into);
14806
+ }
14807
+ break;
14808
+ }
14809
+ }
14810
+ function addMarkerName(name, into) {
14811
+ if (typeof name === "string" && MARKER_NAMES.has(name)) into.add(name);
14812
+ }
14813
+ var FUNCTION_TYPES = /* @__PURE__ */ new Set([
14814
+ "ArrowFunctionExpression",
14815
+ "FunctionExpression",
14816
+ "FunctionDeclaration",
14817
+ "ObjectMethod",
14818
+ "ClassMethod",
14819
+ "ClassPrivateMethod"
14820
+ ]);
14821
+ function collectScopeBindings(node) {
14822
+ const bound = /* @__PURE__ */ new Set();
14823
+ if (FUNCTION_TYPES.has(node.type)) {
14824
+ if (node.id?.type === "Identifier") addMarkerName(node.id.name, bound);
14825
+ for (const param of node.params ?? []) {
14826
+ collectPatternNames(param, bound);
14827
+ }
14828
+ } else if (node.type === "CatchClause" && node.param) {
14829
+ collectPatternNames(node.param, bound);
14830
+ } else if (node.type === "BlockStatement") {
14831
+ for (const stmt of node.body ?? []) {
14832
+ collectDeclaredNames(stmt, bound);
14833
+ }
14834
+ } else if (node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement") {
14835
+ const init = node.init ?? node.left;
14836
+ if (init?.type === "VariableDeclaration") {
14837
+ collectDeclaredNames(init, bound);
14838
+ }
14839
+ }
14840
+ return bound.size > 0 ? bound : null;
14841
+ }
14710
14842
  function walkChildren(node, visit) {
14711
14843
  for (const key of Object.keys(node)) {
14712
14844
  if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") {
@@ -15152,7 +15284,8 @@ function solidTranslate(config) {
15152
15284
  batchSize = 50,
15153
15285
  translate = true,
15154
15286
  autoExtract = false,
15155
- include = ["src/**/*.tsx", "src/**/*.ts", "src/**/*.jsx"]
15287
+ include = ["src/**/*.tsx", "src/**/*.ts", "src/**/*.jsx"],
15288
+ extractImportSources
15156
15289
  } = config;
15157
15290
  let root;
15158
15291
  let resolvedLocalesDir;
@@ -15180,7 +15313,11 @@ function solidTranslate(config) {
15180
15313
  );
15181
15314
  let contexts = {};
15182
15315
  if (autoExtract) {
15183
- const extracted = await autoExtractStrings(root, include);
15316
+ const extracted = await autoExtractStrings(
15317
+ root,
15318
+ include,
15319
+ extractImportSources
15320
+ );
15184
15321
  contexts = extracted.contexts;
15185
15322
  let existingSource = {};
15186
15323
  if (existsSync2(sourceFilePath)) {
@@ -15343,7 +15480,7 @@ function solidTranslate(config) {
15343
15480
  };
15344
15481
  }
15345
15482
  var vite_default = solidTranslate;
15346
- async function autoExtractStrings(root, patterns) {
15483
+ async function autoExtractStrings(root, patterns, importSources) {
15347
15484
  const strings = {};
15348
15485
  const contexts = {};
15349
15486
  const warnings = [];
@@ -15356,7 +15493,8 @@ async function autoExtractStrings(root, patterns) {
15356
15493
  const extracted = extractStringsFromSource(
15357
15494
  code,
15358
15495
  relative(root, file),
15359
- warnings
15496
+ warnings,
15497
+ { importSources }
15360
15498
  );
15361
15499
  for (const entry of extracted) {
15362
15500
  strings[entry.key] = entry.source;