solid-translate 1.4.1 → 1.4.3
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 +20 -0
- package/dist/chunk-P6RTZPHZ.js +141 -0
- package/dist/cli.js +179 -19
- package/dist/index.d.ts +7 -0
- package/dist/{translate-M737VQHG.js → translate-SWLCBQ5Y.js} +5 -1
- package/dist/vite.d.ts +7 -0
- package/dist/vite.js +253 -37
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2BKJUY37.js +0 -82
package/dist/vite.js
CHANGED
|
@@ -14617,9 +14617,40 @@ function hashContent(content) {
|
|
|
14617
14617
|
|
|
14618
14618
|
// src/translate.ts
|
|
14619
14619
|
import { z } from "zod";
|
|
14620
|
-
async function
|
|
14621
|
-
const {
|
|
14622
|
-
return
|
|
14620
|
+
async function loadGenerateText() {
|
|
14621
|
+
const { generateText } = await import("ai");
|
|
14622
|
+
return generateText;
|
|
14623
|
+
}
|
|
14624
|
+
function extractJsonObject(text) {
|
|
14625
|
+
const start = text.indexOf("{");
|
|
14626
|
+
const end = text.lastIndexOf("}");
|
|
14627
|
+
if (start === -1 || end <= start) {
|
|
14628
|
+
throw new Error("model response contained no JSON object");
|
|
14629
|
+
}
|
|
14630
|
+
const parsed = JSON.parse(text.slice(start, end + 1));
|
|
14631
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
14632
|
+
throw new Error("model response was not a JSON object");
|
|
14633
|
+
}
|
|
14634
|
+
return parsed;
|
|
14635
|
+
}
|
|
14636
|
+
function collectBatchTranslations(parsed, requestedKeys) {
|
|
14637
|
+
let dict = parsed;
|
|
14638
|
+
const inner = parsed["translations"];
|
|
14639
|
+
if (typeof inner === "object" && inner !== null && !Array.isArray(inner) && // Only unwrap when the envelope key is not itself a requested key
|
|
14640
|
+
!requestedKeys.includes("translations")) {
|
|
14641
|
+
dict = inner;
|
|
14642
|
+
}
|
|
14643
|
+
const translations = {};
|
|
14644
|
+
const missing = [];
|
|
14645
|
+
for (const key of requestedKeys) {
|
|
14646
|
+
const value = dict[key];
|
|
14647
|
+
if (typeof value === "string" && value.length > 0) {
|
|
14648
|
+
translations[key] = value;
|
|
14649
|
+
} else {
|
|
14650
|
+
missing.push(key);
|
|
14651
|
+
}
|
|
14652
|
+
}
|
|
14653
|
+
return { translations, missing };
|
|
14623
14654
|
}
|
|
14624
14655
|
async function translateBatch(model, entries, targetLocale, sourceLocale, systemPrompt, contexts) {
|
|
14625
14656
|
const keys = Object.keys(entries);
|
|
@@ -14646,26 +14677,48 @@ async function translateBatch(model, entries, targetLocale, sourceLocale, system
|
|
|
14646
14677
|
].join("\n");
|
|
14647
14678
|
}
|
|
14648
14679
|
}
|
|
14649
|
-
const
|
|
14650
|
-
const
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14680
|
+
const generateText = await loadGenerateText();
|
|
14681
|
+
const basePrompt = [
|
|
14682
|
+
`Translate each value in this JSON object from "${sourceLocale}" to "${targetLocale}".`,
|
|
14683
|
+
`Respond with ONLY a JSON object \u2014 no prose, no code fences \u2014 containing the exact same keys and the translated values.`,
|
|
14684
|
+
contextSection,
|
|
14685
|
+
JSON.stringify(entries, null, 2)
|
|
14686
|
+
].join("\n");
|
|
14687
|
+
const attempt = async (prompt) => {
|
|
14688
|
+
const { text } = await generateText({
|
|
14689
|
+
model,
|
|
14690
|
+
system: systemPrompt || defaultSystem,
|
|
14691
|
+
prompt
|
|
14692
|
+
});
|
|
14693
|
+
return collectBatchTranslations(extractJsonObject(text), keys);
|
|
14694
|
+
};
|
|
14695
|
+
let { translations, missing } = await attempt(basePrompt);
|
|
14696
|
+
if (missing.length > 0) {
|
|
14697
|
+
const retryEntries = {};
|
|
14698
|
+
for (const key of missing) retryEntries[key] = entries[key];
|
|
14699
|
+
const retry = await attempt(
|
|
14700
|
+
[
|
|
14701
|
+
`Translate each value in this JSON object from "${sourceLocale}" to "${targetLocale}".`,
|
|
14702
|
+
`Respond with ONLY a JSON object \u2014 no prose, no code fences \u2014 containing the exact same keys and the translated values.`,
|
|
14703
|
+
contextSection,
|
|
14704
|
+
JSON.stringify(retryEntries, null, 2)
|
|
14705
|
+
].join("\n")
|
|
14706
|
+
);
|
|
14707
|
+
translations = { ...translations, ...retry.translations };
|
|
14708
|
+
missing = retry.missing;
|
|
14709
|
+
}
|
|
14710
|
+
if (missing.length > 0) {
|
|
14711
|
+
const sample = missing.slice(0, 3).join('", "');
|
|
14712
|
+
throw new Error(
|
|
14713
|
+
`model returned no translation for ${missing.length} of ${keys.length} keys (e.g. "${sample}")`
|
|
14714
|
+
);
|
|
14715
|
+
}
|
|
14716
|
+
return translations;
|
|
14664
14717
|
}
|
|
14665
14718
|
|
|
14666
14719
|
// src/extract.ts
|
|
14667
14720
|
var import_parser = __toESM(require_lib(), 1);
|
|
14668
|
-
function extractStringsFromSource(code, filePath, warnings) {
|
|
14721
|
+
function extractStringsFromSource(code, filePath, warnings, options) {
|
|
14669
14722
|
const results = [];
|
|
14670
14723
|
const seen = /* @__PURE__ */ new Set();
|
|
14671
14724
|
const warn = (line, message) => {
|
|
@@ -14687,26 +14740,158 @@ function extractStringsFromSource(code, filePath, warnings) {
|
|
|
14687
14740
|
seen.add(entry.key);
|
|
14688
14741
|
results.push(entry);
|
|
14689
14742
|
};
|
|
14743
|
+
const bindings = collectModuleBindings(ast);
|
|
14744
|
+
const shadowStack = [];
|
|
14745
|
+
const resolveMarker = (localName) => {
|
|
14746
|
+
for (let i = shadowStack.length - 1; i >= 0; i--) {
|
|
14747
|
+
if (shadowStack[i].has(localName)) return null;
|
|
14748
|
+
}
|
|
14749
|
+
const imported = bindings.imports.get(localName);
|
|
14750
|
+
if (imported) {
|
|
14751
|
+
if (!isAcceptedImportSource(imported.source, options?.importSources)) {
|
|
14752
|
+
return null;
|
|
14753
|
+
}
|
|
14754
|
+
return MARKER_NAMES.has(imported.imported) ? imported.imported : null;
|
|
14755
|
+
}
|
|
14756
|
+
if (bindings.moduleLocals.has(localName)) return null;
|
|
14757
|
+
return MARKER_NAMES.has(localName) ? localName : null;
|
|
14758
|
+
};
|
|
14690
14759
|
const visit = (node) => {
|
|
14760
|
+
const scopeBindings = collectScopeBindings(node);
|
|
14761
|
+
if (scopeBindings) shadowStack.push(scopeBindings);
|
|
14691
14762
|
if (node.type === "JSXElement") {
|
|
14692
14763
|
const name = jsxName(node);
|
|
14693
|
-
|
|
14764
|
+
const marker = name ? resolveMarker(name) : null;
|
|
14765
|
+
if (marker === "T") {
|
|
14694
14766
|
const entry = processT(node, filePath, warn);
|
|
14695
14767
|
if (entry) push(entry);
|
|
14696
|
-
} else if (
|
|
14768
|
+
} else if (marker === "Plural") {
|
|
14697
14769
|
for (const entry of processPlural(node, filePath, warn)) {
|
|
14698
14770
|
push(entry);
|
|
14699
14771
|
}
|
|
14700
14772
|
}
|
|
14701
|
-
} else if (node.type === "CallExpression" && node.callee?.type === "Identifier" && node.callee.name === "msg") {
|
|
14773
|
+
} else if (node.type === "CallExpression" && node.callee?.type === "Identifier" && resolveMarker(node.callee.name) === "msg") {
|
|
14702
14774
|
const entry = processMsg(node, filePath, warn);
|
|
14703
14775
|
if (entry) push(entry);
|
|
14704
14776
|
}
|
|
14705
14777
|
walkChildren(node, visit);
|
|
14778
|
+
if (scopeBindings) shadowStack.pop();
|
|
14706
14779
|
};
|
|
14707
14780
|
visit(ast);
|
|
14708
14781
|
return results;
|
|
14709
14782
|
}
|
|
14783
|
+
var MARKER_NAMES = /* @__PURE__ */ new Set([
|
|
14784
|
+
"msg",
|
|
14785
|
+
"T",
|
|
14786
|
+
"Var",
|
|
14787
|
+
"Num",
|
|
14788
|
+
"Currency",
|
|
14789
|
+
"DateTime",
|
|
14790
|
+
"Plural"
|
|
14791
|
+
]);
|
|
14792
|
+
var DEFAULT_IMPORT_SOURCE_RE = /(^|\/)(solid-translate|i18n)(\.[cm]?[jt]sx?)?$/;
|
|
14793
|
+
function isAcceptedImportSource(source, importSources) {
|
|
14794
|
+
if (source === "solid-translate") return true;
|
|
14795
|
+
if (importSources) return importSources.includes(source);
|
|
14796
|
+
return DEFAULT_IMPORT_SOURCE_RE.test(source);
|
|
14797
|
+
}
|
|
14798
|
+
function collectModuleBindings(ast) {
|
|
14799
|
+
const imports = /* @__PURE__ */ new Map();
|
|
14800
|
+
const moduleLocals = /* @__PURE__ */ new Set();
|
|
14801
|
+
const body = ast.program?.body ?? [];
|
|
14802
|
+
for (const stmt of body) {
|
|
14803
|
+
if (stmt.type === "ImportDeclaration") {
|
|
14804
|
+
const source = String(stmt.source?.value ?? "");
|
|
14805
|
+
for (const spec of stmt.specifiers ?? []) {
|
|
14806
|
+
const local = spec.local?.name;
|
|
14807
|
+
if (typeof local !== "string") continue;
|
|
14808
|
+
if (spec.type === "ImportSpecifier") {
|
|
14809
|
+
const imported = spec.imported?.type === "Identifier" ? spec.imported.name : String(spec.imported?.value ?? "");
|
|
14810
|
+
imports.set(local, { imported, source });
|
|
14811
|
+
} else {
|
|
14812
|
+
imports.set(local, { imported: "*", source });
|
|
14813
|
+
}
|
|
14814
|
+
}
|
|
14815
|
+
} else {
|
|
14816
|
+
collectDeclaredNames(stmt, moduleLocals);
|
|
14817
|
+
}
|
|
14818
|
+
}
|
|
14819
|
+
return { imports, moduleLocals };
|
|
14820
|
+
}
|
|
14821
|
+
function collectDeclaredNames(stmt, into) {
|
|
14822
|
+
if (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") {
|
|
14823
|
+
if (stmt.declaration) collectDeclaredNames(stmt.declaration, into);
|
|
14824
|
+
return;
|
|
14825
|
+
}
|
|
14826
|
+
if (stmt.type === "VariableDeclaration") {
|
|
14827
|
+
for (const decl of stmt.declarations ?? []) {
|
|
14828
|
+
if (decl.id) collectPatternNames(decl.id, into);
|
|
14829
|
+
}
|
|
14830
|
+
return;
|
|
14831
|
+
}
|
|
14832
|
+
if ((stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration" || stmt.type === "TSEnumDeclaration") && stmt.id?.type === "Identifier") {
|
|
14833
|
+
addMarkerName(stmt.id.name, into);
|
|
14834
|
+
}
|
|
14835
|
+
}
|
|
14836
|
+
function collectPatternNames(pattern, into) {
|
|
14837
|
+
switch (pattern.type) {
|
|
14838
|
+
case "Identifier":
|
|
14839
|
+
addMarkerName(pattern.name, into);
|
|
14840
|
+
break;
|
|
14841
|
+
case "AssignmentPattern":
|
|
14842
|
+
collectPatternNames(pattern.left, into);
|
|
14843
|
+
break;
|
|
14844
|
+
case "RestElement":
|
|
14845
|
+
collectPatternNames(pattern.argument, into);
|
|
14846
|
+
break;
|
|
14847
|
+
case "ObjectPattern":
|
|
14848
|
+
for (const prop of pattern.properties ?? []) {
|
|
14849
|
+
if (prop.type === "ObjectProperty") {
|
|
14850
|
+
collectPatternNames(prop.value, into);
|
|
14851
|
+
} else if (prop.type === "RestElement") {
|
|
14852
|
+
collectPatternNames(prop.argument, into);
|
|
14853
|
+
}
|
|
14854
|
+
}
|
|
14855
|
+
break;
|
|
14856
|
+
case "ArrayPattern":
|
|
14857
|
+
for (const el of pattern.elements ?? []) {
|
|
14858
|
+
if (el) collectPatternNames(el, into);
|
|
14859
|
+
}
|
|
14860
|
+
break;
|
|
14861
|
+
}
|
|
14862
|
+
}
|
|
14863
|
+
function addMarkerName(name, into) {
|
|
14864
|
+
if (typeof name === "string" && MARKER_NAMES.has(name)) into.add(name);
|
|
14865
|
+
}
|
|
14866
|
+
var FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
14867
|
+
"ArrowFunctionExpression",
|
|
14868
|
+
"FunctionExpression",
|
|
14869
|
+
"FunctionDeclaration",
|
|
14870
|
+
"ObjectMethod",
|
|
14871
|
+
"ClassMethod",
|
|
14872
|
+
"ClassPrivateMethod"
|
|
14873
|
+
]);
|
|
14874
|
+
function collectScopeBindings(node) {
|
|
14875
|
+
const bound = /* @__PURE__ */ new Set();
|
|
14876
|
+
if (FUNCTION_TYPES.has(node.type)) {
|
|
14877
|
+
if (node.id?.type === "Identifier") addMarkerName(node.id.name, bound);
|
|
14878
|
+
for (const param of node.params ?? []) {
|
|
14879
|
+
collectPatternNames(param, bound);
|
|
14880
|
+
}
|
|
14881
|
+
} else if (node.type === "CatchClause" && node.param) {
|
|
14882
|
+
collectPatternNames(node.param, bound);
|
|
14883
|
+
} else if (node.type === "BlockStatement") {
|
|
14884
|
+
for (const stmt of node.body ?? []) {
|
|
14885
|
+
collectDeclaredNames(stmt, bound);
|
|
14886
|
+
}
|
|
14887
|
+
} else if (node.type === "ForStatement" || node.type === "ForOfStatement" || node.type === "ForInStatement") {
|
|
14888
|
+
const init = node.init ?? node.left;
|
|
14889
|
+
if (init?.type === "VariableDeclaration") {
|
|
14890
|
+
collectDeclaredNames(init, bound);
|
|
14891
|
+
}
|
|
14892
|
+
}
|
|
14893
|
+
return bound.size > 0 ? bound : null;
|
|
14894
|
+
}
|
|
14710
14895
|
function walkChildren(node, visit) {
|
|
14711
14896
|
for (const key of Object.keys(node)) {
|
|
14712
14897
|
if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") {
|
|
@@ -15040,7 +15225,16 @@ async function syncLocaleFiles(options) {
|
|
|
15040
15225
|
delete lock.keys[key];
|
|
15041
15226
|
}
|
|
15042
15227
|
const changedCount = Object.keys(changedKeys).length;
|
|
15043
|
-
|
|
15228
|
+
const missingByLocale = {};
|
|
15229
|
+
for (const targetLocale of targetLocales) {
|
|
15230
|
+
const existing = readTargetFile(join(localesDir, `${targetLocale}.json`));
|
|
15231
|
+
const missing = Object.keys(sourceDict).filter(
|
|
15232
|
+
(key) => !(key in changedKeys) && !(key in existing)
|
|
15233
|
+
);
|
|
15234
|
+
if (missing.length > 0) missingByLocale[targetLocale] = missing;
|
|
15235
|
+
}
|
|
15236
|
+
const missingLocaleCount = Object.keys(missingByLocale).length;
|
|
15237
|
+
if (changedCount === 0 && deletedKeys.length === 0 && missingLocaleCount === 0) {
|
|
15044
15238
|
log("No changes detected in locale files.");
|
|
15045
15239
|
return {
|
|
15046
15240
|
status: "no-changes",
|
|
@@ -15049,7 +15243,7 @@ async function syncLocaleFiles(options) {
|
|
|
15049
15243
|
failures: []
|
|
15050
15244
|
};
|
|
15051
15245
|
}
|
|
15052
|
-
if (changedCount === 0) {
|
|
15246
|
+
if (changedCount === 0 && missingLocaleCount === 0) {
|
|
15053
15247
|
for (const targetLocale of targetLocales) {
|
|
15054
15248
|
const targetFilePath = join(localesDir, `${targetLocale}.json`);
|
|
15055
15249
|
const existing = readTargetFile(targetFilePath);
|
|
@@ -15062,27 +15256,43 @@ async function syncLocaleFiles(options) {
|
|
|
15062
15256
|
);
|
|
15063
15257
|
return { status: "synced", translatedKeys: [], deletedKeys, failures: [] };
|
|
15064
15258
|
}
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
for (const key of Object.keys(changedKeys)) {
|
|
15070
|
-
const ctx = pendingEntries[key]?.context;
|
|
15071
|
-
if (ctx) changedContexts[key] = ctx;
|
|
15259
|
+
if (changedCount > 0) {
|
|
15260
|
+
log(
|
|
15261
|
+
`Translating ${changedCount} key${changedCount > 1 ? "s" : ""} to ${targetLocales.length} locale${targetLocales.length > 1 ? "s" : ""}...`
|
|
15262
|
+
);
|
|
15072
15263
|
}
|
|
15264
|
+
if (missingLocaleCount > 0) {
|
|
15265
|
+
const healTotal = Object.values(missingByLocale).reduce(
|
|
15266
|
+
(sum, keys) => sum + keys.length,
|
|
15267
|
+
0
|
|
15268
|
+
);
|
|
15269
|
+
log(
|
|
15270
|
+
`Healing ${healTotal} key${healTotal > 1 ? "s" : ""} missing from ${missingLocaleCount} locale file${missingLocaleCount > 1 ? "s" : ""}...`
|
|
15271
|
+
);
|
|
15272
|
+
}
|
|
15273
|
+
const contextFor = (key) => pendingEntries[key]?.context ?? lock.keys[key]?.context;
|
|
15073
15274
|
const failures = [];
|
|
15074
15275
|
const failedKeys = /* @__PURE__ */ new Set();
|
|
15075
15276
|
for (const targetLocale of targetLocales) {
|
|
15076
15277
|
const targetFilePath = join(localesDir, `${targetLocale}.json`);
|
|
15077
15278
|
const existing = readTargetFile(targetFilePath);
|
|
15078
|
-
const
|
|
15279
|
+
const localeEntries = { ...changedKeys };
|
|
15280
|
+
for (const key of missingByLocale[targetLocale] ?? []) {
|
|
15281
|
+
localeEntries[key] = sourceDict[key];
|
|
15282
|
+
}
|
|
15283
|
+
const localeContexts = {};
|
|
15284
|
+
for (const key of Object.keys(localeEntries)) {
|
|
15285
|
+
const ctx = contextFor(key);
|
|
15286
|
+
if (ctx) localeContexts[key] = ctx;
|
|
15287
|
+
}
|
|
15288
|
+
const entries = Object.entries(localeEntries);
|
|
15079
15289
|
for (let i = 0; i < entries.length; i += batchSize) {
|
|
15080
15290
|
const batch = Object.fromEntries(entries.slice(i, i + batchSize));
|
|
15081
15291
|
try {
|
|
15082
15292
|
const translated = await translate(
|
|
15083
15293
|
batch,
|
|
15084
15294
|
targetLocale,
|
|
15085
|
-
|
|
15295
|
+
localeContexts
|
|
15086
15296
|
);
|
|
15087
15297
|
Object.assign(existing, translated);
|
|
15088
15298
|
} catch (err) {
|
|
@@ -15152,7 +15362,8 @@ function solidTranslate(config) {
|
|
|
15152
15362
|
batchSize = 50,
|
|
15153
15363
|
translate = true,
|
|
15154
15364
|
autoExtract = false,
|
|
15155
|
-
include = ["src/**/*.tsx", "src/**/*.ts", "src/**/*.jsx"]
|
|
15365
|
+
include = ["src/**/*.tsx", "src/**/*.ts", "src/**/*.jsx"],
|
|
15366
|
+
extractImportSources
|
|
15156
15367
|
} = config;
|
|
15157
15368
|
let root;
|
|
15158
15369
|
let resolvedLocalesDir;
|
|
@@ -15180,7 +15391,11 @@ function solidTranslate(config) {
|
|
|
15180
15391
|
);
|
|
15181
15392
|
let contexts = {};
|
|
15182
15393
|
if (autoExtract) {
|
|
15183
|
-
const extracted = await autoExtractStrings(
|
|
15394
|
+
const extracted = await autoExtractStrings(
|
|
15395
|
+
root,
|
|
15396
|
+
include,
|
|
15397
|
+
extractImportSources
|
|
15398
|
+
);
|
|
15184
15399
|
contexts = extracted.contexts;
|
|
15185
15400
|
let existingSource = {};
|
|
15186
15401
|
if (existsSync2(sourceFilePath)) {
|
|
@@ -15343,7 +15558,7 @@ function solidTranslate(config) {
|
|
|
15343
15558
|
};
|
|
15344
15559
|
}
|
|
15345
15560
|
var vite_default = solidTranslate;
|
|
15346
|
-
async function autoExtractStrings(root, patterns) {
|
|
15561
|
+
async function autoExtractStrings(root, patterns, importSources) {
|
|
15347
15562
|
const strings = {};
|
|
15348
15563
|
const contexts = {};
|
|
15349
15564
|
const warnings = [];
|
|
@@ -15356,7 +15571,8 @@ async function autoExtractStrings(root, patterns) {
|
|
|
15356
15571
|
const extracted = extractStringsFromSource(
|
|
15357
15572
|
code,
|
|
15358
15573
|
relative(root, file),
|
|
15359
|
-
warnings
|
|
15574
|
+
warnings,
|
|
15575
|
+
{ importSources }
|
|
15360
15576
|
);
|
|
15361
15577
|
for (const entry of extracted) {
|
|
15362
15578
|
strings[entry.key] = entry.source;
|