@lingo.dev/_compiler 0.1.13 → 0.2.1
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/build/index.cjs +379 -249
- package/build/index.mjs +382 -252
- package/package.json +2 -1
package/build/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { createUnplugin } from "unplugin";
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "@lingo.dev/_compiler",
|
|
7
|
-
version: "0.1
|
|
7
|
+
version: "0.2.1",
|
|
8
8
|
description: "Lingo.dev Compiler",
|
|
9
9
|
private: false,
|
|
10
10
|
publishConfig: {
|
|
@@ -40,6 +40,7 @@ var package_default = {
|
|
|
40
40
|
typescript: "^5.4.5"
|
|
41
41
|
},
|
|
42
42
|
dependencies: {
|
|
43
|
+
"@ai-sdk/google": "^1.2.19",
|
|
43
44
|
"@ai-sdk/groq": "^1.2.3",
|
|
44
45
|
"@babel/generator": "^7.26.5",
|
|
45
46
|
"@babel/parser": "^7.26.7",
|
|
@@ -173,9 +174,9 @@ function extractAttributeValue(attribute) {
|
|
|
173
174
|
function getJsxRoots(node) {
|
|
174
175
|
const result = [];
|
|
175
176
|
traverse(node, {
|
|
176
|
-
JSXElement(
|
|
177
|
-
result.push(
|
|
178
|
-
|
|
177
|
+
JSXElement(path7) {
|
|
178
|
+
result.push(path7);
|
|
179
|
+
path7.skip();
|
|
179
180
|
}
|
|
180
181
|
});
|
|
181
182
|
return result;
|
|
@@ -202,14 +203,14 @@ function getOrCreateImport(ast, params) {
|
|
|
202
203
|
function findExistingImport(ast, exportedName, moduleName) {
|
|
203
204
|
let result = null;
|
|
204
205
|
traverse(ast, {
|
|
205
|
-
ImportDeclaration(
|
|
206
|
-
if (
|
|
206
|
+
ImportDeclaration(path7) {
|
|
207
|
+
if (path7.node.source.value !== moduleName) {
|
|
207
208
|
return;
|
|
208
209
|
}
|
|
209
|
-
for (const specifier of
|
|
210
|
+
for (const specifier of path7.node.specifiers) {
|
|
210
211
|
if (t2.isImportSpecifier(specifier) && (t2.isIdentifier(specifier.imported) && specifier.imported.name === exportedName || specifier.importKind === "value" && t2.isIdentifier(specifier.local) && specifier.local.name === exportedName)) {
|
|
211
212
|
result = specifier.local.name;
|
|
212
|
-
|
|
213
|
+
path7.stop();
|
|
213
214
|
return;
|
|
214
215
|
}
|
|
215
216
|
}
|
|
@@ -220,8 +221,8 @@ function findExistingImport(ast, exportedName, moduleName) {
|
|
|
220
221
|
function generateUniqueImportName(ast, baseName) {
|
|
221
222
|
const usedNames = /* @__PURE__ */ new Set();
|
|
222
223
|
traverse(ast, {
|
|
223
|
-
Identifier(
|
|
224
|
-
usedNames.add(
|
|
224
|
+
Identifier(path7) {
|
|
225
|
+
usedNames.add(path7.node.name);
|
|
225
226
|
}
|
|
226
227
|
});
|
|
227
228
|
if (!usedNames.has(baseName)) {
|
|
@@ -237,12 +238,12 @@ function generateUniqueImportName(ast, baseName) {
|
|
|
237
238
|
}
|
|
238
239
|
function createImportDeclaration(ast, localName, exportedName, moduleName) {
|
|
239
240
|
traverse(ast, {
|
|
240
|
-
Program(
|
|
241
|
+
Program(path7) {
|
|
241
242
|
const importSpecifier2 = t2.importSpecifier(
|
|
242
243
|
t2.identifier(localName),
|
|
243
244
|
t2.identifier(exportedName)
|
|
244
245
|
);
|
|
245
|
-
const existingImport =
|
|
246
|
+
const existingImport = path7.get("body").find(
|
|
246
247
|
(nodePath) => t2.isImportDeclaration(nodePath.node) && nodePath.node.source.value === moduleName
|
|
247
248
|
);
|
|
248
249
|
if (existingImport && t2.isImportDeclaration(existingImport.node)) {
|
|
@@ -252,10 +253,10 @@ function createImportDeclaration(ast, localName, exportedName, moduleName) {
|
|
|
252
253
|
[importSpecifier2],
|
|
253
254
|
t2.stringLiteral(moduleName)
|
|
254
255
|
);
|
|
255
|
-
const lastImportIndex = findLastImportIndex(
|
|
256
|
-
|
|
256
|
+
const lastImportIndex = findLastImportIndex(path7);
|
|
257
|
+
path7.node.body.splice(lastImportIndex + 1, 0, importDeclaration2);
|
|
257
258
|
}
|
|
258
|
-
|
|
259
|
+
path7.stop();
|
|
259
260
|
}
|
|
260
261
|
});
|
|
261
262
|
}
|
|
@@ -271,10 +272,10 @@ function findLastImportIndex(programPath) {
|
|
|
271
272
|
function _hasFileDirective(ast, directiveValue) {
|
|
272
273
|
let hasDirective = false;
|
|
273
274
|
traverse(ast, {
|
|
274
|
-
Directive(
|
|
275
|
-
if (
|
|
275
|
+
Directive(path7) {
|
|
276
|
+
if (path7.node.value.value === directiveValue) {
|
|
276
277
|
hasDirective = true;
|
|
277
|
-
|
|
278
|
+
path7.stop();
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
281
|
});
|
|
@@ -324,9 +325,9 @@ function getJsxElementName(nodePath) {
|
|
|
324
325
|
function getNestedJsxElements(nodePath) {
|
|
325
326
|
const nestedElements = [];
|
|
326
327
|
nodePath.traverse({
|
|
327
|
-
JSXElement(
|
|
328
|
-
if (
|
|
329
|
-
nestedElements.push(
|
|
328
|
+
JSXElement(path7) {
|
|
329
|
+
if (path7.node !== nodePath.node) {
|
|
330
|
+
nestedElements.push(path7.node);
|
|
330
331
|
}
|
|
331
332
|
}
|
|
332
333
|
});
|
|
@@ -349,8 +350,8 @@ var LCP_DICTIONARY_FILE_NAME = "dictionary.js";
|
|
|
349
350
|
// src/jsx-provider.ts
|
|
350
351
|
var jsxProviderMutation = createCodeMutation((payload) => {
|
|
351
352
|
traverse2(payload.ast, {
|
|
352
|
-
JSXElement: (
|
|
353
|
-
if (getJsxElementName(
|
|
353
|
+
JSXElement: (path7) => {
|
|
354
|
+
if (getJsxElementName(path7)?.toLowerCase() === "html") {
|
|
354
355
|
const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
|
|
355
356
|
if (mode === "client") {
|
|
356
357
|
return;
|
|
@@ -384,11 +385,11 @@ var jsxProviderMutation = createCodeMutation((payload) => {
|
|
|
384
385
|
t4.jsxClosingElement(
|
|
385
386
|
t4.jsxIdentifier(lingoProviderImport.importedName)
|
|
386
387
|
),
|
|
387
|
-
[
|
|
388
|
+
[path7.node],
|
|
388
389
|
false
|
|
389
390
|
);
|
|
390
|
-
|
|
391
|
-
|
|
391
|
+
path7.replaceWith(provider);
|
|
392
|
+
path7.skip();
|
|
392
393
|
}
|
|
393
394
|
}
|
|
394
395
|
});
|
|
@@ -437,10 +438,10 @@ import traverse4 from "@babel/traverse";
|
|
|
437
438
|
function collectJsxScopes(ast) {
|
|
438
439
|
const jsxScopes = [];
|
|
439
440
|
traverse4(ast, {
|
|
440
|
-
JSXElement: (
|
|
441
|
-
if (!hasJsxScopeAttribute(
|
|
442
|
-
|
|
443
|
-
jsxScopes.push(
|
|
441
|
+
JSXElement: (path7) => {
|
|
442
|
+
if (!hasJsxScopeAttribute(path7)) return;
|
|
443
|
+
path7.skip();
|
|
444
|
+
jsxScopes.push(path7);
|
|
444
445
|
}
|
|
445
446
|
});
|
|
446
447
|
return jsxScopes;
|
|
@@ -448,32 +449,32 @@ function collectJsxScopes(ast) {
|
|
|
448
449
|
function getJsxScopes(node) {
|
|
449
450
|
const result = [];
|
|
450
451
|
traverse4(node, {
|
|
451
|
-
JSXElement(
|
|
452
|
-
if (getJsxElementName(
|
|
452
|
+
JSXElement(path7) {
|
|
453
|
+
if (getJsxElementName(path7) === "LingoProvider") {
|
|
453
454
|
return;
|
|
454
455
|
}
|
|
455
|
-
const hasNonEmptyTextSiblings =
|
|
456
|
+
const hasNonEmptyTextSiblings = path7.getAllPrevSiblings().concat(path7.getAllNextSiblings()).some(
|
|
456
457
|
(sibling) => t7.isJSXText(sibling.node) && sibling.node.value?.trim() !== ""
|
|
457
458
|
);
|
|
458
459
|
if (hasNonEmptyTextSiblings) {
|
|
459
460
|
return;
|
|
460
461
|
}
|
|
461
|
-
const hasNonEmptyTextChild =
|
|
462
|
+
const hasNonEmptyTextChild = path7.get("children").some(
|
|
462
463
|
(child) => t7.isJSXText(child.node) && child.node.value?.trim() !== ""
|
|
463
464
|
);
|
|
464
465
|
if (hasNonEmptyTextChild) {
|
|
465
|
-
result.push(
|
|
466
|
-
|
|
466
|
+
result.push(path7);
|
|
467
|
+
path7.skip();
|
|
467
468
|
}
|
|
468
469
|
}
|
|
469
470
|
});
|
|
470
471
|
return result;
|
|
471
472
|
}
|
|
472
|
-
function hasJsxScopeAttribute(
|
|
473
|
-
return !!getJsxScopeAttribute(
|
|
473
|
+
function hasJsxScopeAttribute(path7) {
|
|
474
|
+
return !!getJsxScopeAttribute(path7);
|
|
474
475
|
}
|
|
475
|
-
function getJsxScopeAttribute(
|
|
476
|
-
const attribute =
|
|
476
|
+
function getJsxScopeAttribute(path7) {
|
|
477
|
+
const attribute = path7.node.openingElement.attributes.find(
|
|
477
478
|
(attr) => attr.type === "JSXAttribute" && attr.name.name === "data-jsx-scope"
|
|
478
479
|
);
|
|
479
480
|
return attribute && t7.isJSXAttribute(attribute) && t7.isStringLiteral(attribute.value) ? attribute.value.value : void 0;
|
|
@@ -505,11 +506,11 @@ import traverse5 from "@babel/traverse";
|
|
|
505
506
|
function collectJsxAttributeScopes(node) {
|
|
506
507
|
const result = [];
|
|
507
508
|
traverse5(node, {
|
|
508
|
-
JSXElement(
|
|
509
|
-
if (!hasJsxAttributeScopeAttribute(
|
|
510
|
-
const localizableAttributes = getJsxAttributeScopeAttribute(
|
|
509
|
+
JSXElement(path7) {
|
|
510
|
+
if (!hasJsxAttributeScopeAttribute(path7)) return;
|
|
511
|
+
const localizableAttributes = getJsxAttributeScopeAttribute(path7);
|
|
511
512
|
if (!localizableAttributes) return;
|
|
512
|
-
result.push([
|
|
513
|
+
result.push([path7, localizableAttributes]);
|
|
513
514
|
}
|
|
514
515
|
});
|
|
515
516
|
return result;
|
|
@@ -528,8 +529,8 @@ function getJsxAttributeScopes(node) {
|
|
|
528
529
|
"subtitle"
|
|
529
530
|
];
|
|
530
531
|
traverse5(node, {
|
|
531
|
-
JSXElement(
|
|
532
|
-
const openingElement =
|
|
532
|
+
JSXElement(path7) {
|
|
533
|
+
const openingElement = path7.node.openingElement;
|
|
533
534
|
const elementName = openingElement.name;
|
|
534
535
|
if (!t9.isJSXIdentifier(elementName) || !elementName.name) {
|
|
535
536
|
return;
|
|
@@ -550,17 +551,17 @@ function getJsxAttributeScopes(node) {
|
|
|
550
551
|
}
|
|
551
552
|
).map((attr) => attr.name.name);
|
|
552
553
|
if (localizableAttrs.length > 0) {
|
|
553
|
-
result.push([
|
|
554
|
+
result.push([path7, localizableAttrs]);
|
|
554
555
|
}
|
|
555
556
|
}
|
|
556
557
|
});
|
|
557
558
|
return result;
|
|
558
559
|
}
|
|
559
|
-
function hasJsxAttributeScopeAttribute(
|
|
560
|
-
return !!getJsxAttributeScopeAttribute(
|
|
560
|
+
function hasJsxAttributeScopeAttribute(path7) {
|
|
561
|
+
return !!getJsxAttributeScopeAttribute(path7);
|
|
561
562
|
}
|
|
562
|
-
function getJsxAttributeScopeAttribute(
|
|
563
|
-
const attribute =
|
|
563
|
+
function getJsxAttributeScopeAttribute(path7) {
|
|
564
|
+
const attribute = path7.node.openingElement.attributes.find(
|
|
564
565
|
(attr) => attr.type === "JSXAttribute" && attr.name.name === "data-jsx-attribute-scope"
|
|
565
566
|
);
|
|
566
567
|
if (!attribute || !t9.isJSXAttribute(attribute)) {
|
|
@@ -603,7 +604,7 @@ var jsxAttributeFlagMutation = createCodeMutation(
|
|
|
603
604
|
var jsx_attribute_flag_default = jsxAttributeFlagMutation;
|
|
604
605
|
|
|
605
606
|
// src/index.ts
|
|
606
|
-
import
|
|
607
|
+
import path6 from "path";
|
|
607
608
|
|
|
608
609
|
// src/utils/module-params.ts
|
|
609
610
|
function parseParametrizedModuleId(rawId) {
|
|
@@ -919,6 +920,7 @@ var LCPCache = class {
|
|
|
919
920
|
|
|
920
921
|
// src/lib/lcp/api/index.ts
|
|
921
922
|
import { createGroq } from "@ai-sdk/groq";
|
|
923
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
922
924
|
import { generateText } from "ai";
|
|
923
925
|
import _6 from "lodash";
|
|
924
926
|
|
|
@@ -1150,21 +1152,45 @@ function getRc() {
|
|
|
1150
1152
|
return data;
|
|
1151
1153
|
}
|
|
1152
1154
|
|
|
1153
|
-
// src/utils/
|
|
1155
|
+
// src/utils/llm-api-key.ts
|
|
1154
1156
|
import _5 from "lodash";
|
|
1155
1157
|
import * as dotenv from "dotenv";
|
|
1158
|
+
import path4 from "path";
|
|
1159
|
+
function getKeyFromEnv(envVarName) {
|
|
1160
|
+
if (process.env[envVarName]) {
|
|
1161
|
+
return process.env[envVarName];
|
|
1162
|
+
}
|
|
1163
|
+
const result = dotenv.config({
|
|
1164
|
+
path: [
|
|
1165
|
+
path4.resolve(process.cwd(), ".env"),
|
|
1166
|
+
path4.resolve(process.cwd(), ".env.local"),
|
|
1167
|
+
path4.resolve(process.cwd(), ".env.development")
|
|
1168
|
+
]
|
|
1169
|
+
});
|
|
1170
|
+
return result?.parsed?.[envVarName];
|
|
1171
|
+
}
|
|
1172
|
+
function getKeyFromRc(rcPath) {
|
|
1173
|
+
const rc = getRc();
|
|
1174
|
+
const result = _5.get(rc, rcPath);
|
|
1175
|
+
return typeof result === "string" ? result : void 0;
|
|
1176
|
+
}
|
|
1156
1177
|
function getGroqKey() {
|
|
1157
1178
|
return getGroqKeyFromEnv() || getGroqKeyFromRc();
|
|
1158
1179
|
}
|
|
1159
1180
|
function getGroqKeyFromRc() {
|
|
1160
|
-
|
|
1161
|
-
const result = _5.get(rc, "llm.groqApiKey");
|
|
1162
|
-
return result;
|
|
1181
|
+
return getKeyFromRc("llm.groqApiKey");
|
|
1163
1182
|
}
|
|
1164
1183
|
function getGroqKeyFromEnv() {
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1184
|
+
return getKeyFromEnv("GROQ_API_KEY");
|
|
1185
|
+
}
|
|
1186
|
+
function getGoogleKey() {
|
|
1187
|
+
return getGoogleKeyFromEnv() || getGoogleKeyFromRc();
|
|
1188
|
+
}
|
|
1189
|
+
function getGoogleKeyFromRc() {
|
|
1190
|
+
return getKeyFromRc("llm.googleApiKey");
|
|
1191
|
+
}
|
|
1192
|
+
function getGoogleKeyFromEnv() {
|
|
1193
|
+
return getKeyFromEnv("GOOGLE_API_KEY");
|
|
1168
1194
|
}
|
|
1169
1195
|
|
|
1170
1196
|
// src/lib/lcp/api/index.ts
|
|
@@ -1176,6 +1202,24 @@ function isRunningInCIOrDocker() {
|
|
|
1176
1202
|
return Boolean(process.env.CI) || fs4.existsSync("/.dockerenv");
|
|
1177
1203
|
}
|
|
1178
1204
|
|
|
1205
|
+
// src/lib/lcp/api/provider-details.ts
|
|
1206
|
+
var providerDetails = {
|
|
1207
|
+
groq: {
|
|
1208
|
+
name: "Groq",
|
|
1209
|
+
apiKeyEnvVar: "GROQ_API_KEY",
|
|
1210
|
+
apiKeyConfigKey: "llm.groqApiKey",
|
|
1211
|
+
getKeyLink: "https://groq.com",
|
|
1212
|
+
docsLink: "https://console.groq.com/docs/errors"
|
|
1213
|
+
},
|
|
1214
|
+
google: {
|
|
1215
|
+
name: "Google",
|
|
1216
|
+
apiKeyEnvVar: "GOOGLE_API_KEY",
|
|
1217
|
+
apiKeyConfigKey: "llm.googleApiKey",
|
|
1218
|
+
getKeyLink: "https://ai.google.dev/",
|
|
1219
|
+
docsLink: "https://ai.google.dev/gemini-api/docs/troubleshooting"
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1179
1223
|
// src/lib/lcp/api/index.ts
|
|
1180
1224
|
var LCPAPI = class {
|
|
1181
1225
|
static async translate(models, sourceDictionary, sourceLocale, targetLocale) {
|
|
@@ -1260,148 +1304,199 @@ var LCPAPI = class {
|
|
|
1260
1304
|
return dictionary;
|
|
1261
1305
|
}
|
|
1262
1306
|
static async _translateChunk(models, sourceDictionary, sourceLocale, targetLocale) {
|
|
1263
|
-
try {
|
|
1264
|
-
return await this._translateChunkGroq(
|
|
1265
|
-
models,
|
|
1266
|
-
sourceDictionary,
|
|
1267
|
-
sourceLocale,
|
|
1268
|
-
targetLocale
|
|
1269
|
-
);
|
|
1270
|
-
} catch (error) {
|
|
1271
|
-
this._failGroqFailureLocal(
|
|
1272
|
-
targetLocale,
|
|
1273
|
-
error instanceof Error ? error.message : "Unknown error"
|
|
1274
|
-
);
|
|
1275
|
-
}
|
|
1276
|
-
if (isRunningInCIOrDocker()) {
|
|
1277
|
-
const groqFromEnv = getGroqKeyFromEnv();
|
|
1278
|
-
if (!groqFromEnv) {
|
|
1279
|
-
this._failMissingGroqKeyCi();
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
throw new Error(
|
|
1283
|
-
"\u26A0\uFE0F No API key found. Please set GROQ_API_KEY environment variable. If you dont have one go to https://groq.com/"
|
|
1284
|
-
);
|
|
1285
|
-
}
|
|
1286
|
-
static async _translateChunkGroq(models, sourceDictionary, sourceLocale, targetLocale) {
|
|
1287
|
-
const groq = createGroq({ apiKey: getGroqKey() });
|
|
1288
|
-
console.log(`Created Groq client for ${targetLocale}`);
|
|
1289
1307
|
const { provider, model } = getLocaleModel(
|
|
1290
1308
|
models,
|
|
1291
1309
|
sourceLocale,
|
|
1292
1310
|
targetLocale
|
|
1293
1311
|
);
|
|
1294
|
-
if (!model) {
|
|
1312
|
+
if (!provider || !model) {
|
|
1295
1313
|
throw new Error(
|
|
1296
|
-
`\u26A0\uFE0F Locale ${targetLocale} is not configured. Add model for this locale to your config.`
|
|
1314
|
+
`\u26A0\uFE0F Locale "${targetLocale}" is not configured. Add provider and model for this locale to your config, e.g., "groq:llama3-8b-8192".`
|
|
1297
1315
|
);
|
|
1298
1316
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1317
|
+
try {
|
|
1318
|
+
const aiModel = this._createAiModel(provider, model, targetLocale);
|
|
1319
|
+
console.log(
|
|
1320
|
+
`\u2728 Using model "${model}" from "${provider}" to translate from "${sourceLocale}" to "${targetLocale}"`
|
|
1302
1321
|
);
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
);
|
|
1307
|
-
const groqModel = groq(model);
|
|
1308
|
-
console.log(`Created model ${model}`);
|
|
1309
|
-
const response = await generateText({
|
|
1310
|
-
model: groqModel,
|
|
1311
|
-
messages: [
|
|
1312
|
-
{
|
|
1313
|
-
role: "system",
|
|
1314
|
-
content: prompt_default({ sourceLocale, targetLocale })
|
|
1315
|
-
},
|
|
1316
|
-
...shots_default.flatMap((shotsTuple) => [
|
|
1322
|
+
const response = await generateText({
|
|
1323
|
+
model: aiModel,
|
|
1324
|
+
messages: [
|
|
1317
1325
|
{
|
|
1318
|
-
role: "
|
|
1319
|
-
content:
|
|
1326
|
+
role: "system",
|
|
1327
|
+
content: prompt_default({ sourceLocale, targetLocale })
|
|
1320
1328
|
},
|
|
1329
|
+
...shots_default.flatMap((shotsTuple) => [
|
|
1330
|
+
{
|
|
1331
|
+
role: "user",
|
|
1332
|
+
content: obj2xml(shotsTuple[0])
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
role: "assistant",
|
|
1336
|
+
content: obj2xml(shotsTuple[1])
|
|
1337
|
+
}
|
|
1338
|
+
]),
|
|
1321
1339
|
{
|
|
1322
|
-
role: "
|
|
1323
|
-
content: obj2xml(
|
|
1340
|
+
role: "user",
|
|
1341
|
+
content: obj2xml(sourceDictionary)
|
|
1342
|
+
}
|
|
1343
|
+
]
|
|
1344
|
+
});
|
|
1345
|
+
console.log("Response text received for", targetLocale);
|
|
1346
|
+
let responseText = response.text;
|
|
1347
|
+
responseText = responseText.substring(
|
|
1348
|
+
responseText.indexOf("<"),
|
|
1349
|
+
responseText.lastIndexOf(">") + 1
|
|
1350
|
+
);
|
|
1351
|
+
return xml2obj(responseText);
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
this._failLLMFailureLocal(
|
|
1354
|
+
provider,
|
|
1355
|
+
targetLocale,
|
|
1356
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1357
|
+
);
|
|
1358
|
+
throw error;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Instantiates an AI model based on provider and model ID.
|
|
1363
|
+
* Includes CI/CD API key checks.
|
|
1364
|
+
* @param providerId The ID of the AI provider (e.g., "groq", "google").
|
|
1365
|
+
* @param modelId The ID of the specific model (e.g., "llama3-8b-8192", "gemini-2.0-flash").
|
|
1366
|
+
* @param targetLocale The target locale being translated to (for logging/error messages).
|
|
1367
|
+
* @returns An instantiated AI LanguageModel.
|
|
1368
|
+
* @throws Error if the provider is not supported or API key is missing in CI/CD.
|
|
1369
|
+
*/
|
|
1370
|
+
static _createAiModel(providerId, modelId, targetLocale) {
|
|
1371
|
+
switch (providerId) {
|
|
1372
|
+
case "groq":
|
|
1373
|
+
if (isRunningInCIOrDocker()) {
|
|
1374
|
+
const groqFromEnv = getGroqKeyFromEnv();
|
|
1375
|
+
if (!groqFromEnv) {
|
|
1376
|
+
this._failMissingLLMKeyCi(providerId);
|
|
1324
1377
|
}
|
|
1325
|
-
]),
|
|
1326
|
-
{
|
|
1327
|
-
role: "user",
|
|
1328
|
-
content: obj2xml(sourceDictionary)
|
|
1329
1378
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1379
|
+
const groqKey = getGroqKey();
|
|
1380
|
+
if (!groqKey) {
|
|
1381
|
+
throw new Error(
|
|
1382
|
+
"\u26A0\uFE0F GROQ API key not found. Please set GROQ_API_KEY environment variable or configure it user-wide."
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
console.log(
|
|
1386
|
+
`Creating Groq client for ${targetLocale} using model ${modelId}`
|
|
1387
|
+
);
|
|
1388
|
+
return createGroq({ apiKey: groqKey })(modelId);
|
|
1389
|
+
case "google":
|
|
1390
|
+
if (isRunningInCIOrDocker()) {
|
|
1391
|
+
const googleFromEnv = getGoogleKeyFromEnv();
|
|
1392
|
+
if (!googleFromEnv) {
|
|
1393
|
+
this._failMissingLLMKeyCi(providerId);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
const googleKey = getGoogleKey();
|
|
1397
|
+
if (!googleKey) {
|
|
1398
|
+
throw new Error(
|
|
1399
|
+
"\u26A0\uFE0F Google API key not found. Please set GOOGLE_API_KEY environment variable or configure it user-wide."
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
console.log(
|
|
1403
|
+
`Creating Google Generative AI client for ${targetLocale} using model ${modelId}`
|
|
1404
|
+
);
|
|
1405
|
+
return createGoogleGenerativeAI({ apiKey: googleKey })(modelId);
|
|
1406
|
+
default:
|
|
1407
|
+
throw new Error(
|
|
1408
|
+
`\u26A0\uFE0F Provider "${providerId}" for locale "${targetLocale}" is not supported. Only "groq" and "google" providers are supported at the moment.`
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1339
1411
|
}
|
|
1340
1412
|
/**
|
|
1341
1413
|
* Show an actionable error message and exit the process when the compiler
|
|
1342
|
-
* is running in CI/CD without a
|
|
1414
|
+
* is running in CI/CD without a required LLM API key.
|
|
1343
1415
|
* The message explains why this situation is unusual and how to fix it.
|
|
1416
|
+
* @param providerId The ID of the LLM provider whose key is missing.
|
|
1344
1417
|
*/
|
|
1345
|
-
static
|
|
1418
|
+
static _failMissingLLMKeyCi(providerId) {
|
|
1419
|
+
let details = providerDetails[providerId];
|
|
1420
|
+
if (!details) {
|
|
1421
|
+
console.error(
|
|
1422
|
+
`Internal Error: Missing details for provider "${providerId}" when reporting missing key in CI/CD. You might be using an unsupported provider.`
|
|
1423
|
+
);
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1346
1426
|
console.log(
|
|
1347
1427
|
dedent2`
|
|
1348
1428
|
\n
|
|
1349
1429
|
💡 You're using Lingo.dev Localization Compiler, and it detected unlocalized components in your app.
|
|
1350
1430
|
|
|
1351
|
-
The compiler needs a
|
|
1431
|
+
The compiler needs a ${details.name} API key to translate missing strings, but ${details.apiKeyEnvVar} is not set in the environment.
|
|
1352
1432
|
|
|
1353
1433
|
This is unexpected: typically you run a full build locally, commit the generated translation files, and push them to CI/CD.
|
|
1354
1434
|
|
|
1355
1435
|
However, If you want CI/CD to translate the new strings, provide the key with:
|
|
1356
|
-
• Session-wide: export
|
|
1357
|
-
• Project-wide / CI: add
|
|
1436
|
+
• Session-wide: export ${details.apiKeyEnvVar}=<your-api-key>
|
|
1437
|
+
• Project-wide / CI: add ${details.apiKeyEnvVar}=<your-api-key> to your pipeline environment variables
|
|
1358
1438
|
|
|
1359
1439
|
⭐️ Also:
|
|
1360
|
-
1. If you don't yet have a
|
|
1361
|
-
2. If you want to use a different LLM,
|
|
1362
|
-
3. If you
|
|
1440
|
+
1. If you don't yet have a ${details.name} API key, get one for free at ${details.getKeyLink}
|
|
1441
|
+
2. If you want to use a different LLM, update your configuration. Refer to documentation for help: https://docs.lingo.dev/
|
|
1442
|
+
3. If the model you want to use isn't supported yet, raise an issue in our open-source repo: https://lingo.dev/go/gh
|
|
1363
1443
|
|
|
1364
1444
|
✨
|
|
1365
1445
|
`
|
|
1366
1446
|
);
|
|
1367
1447
|
process.exit(1);
|
|
1368
1448
|
}
|
|
1369
|
-
|
|
1449
|
+
/**
|
|
1450
|
+
* Show an actionable error message and exit the process when an LLM API call
|
|
1451
|
+
* fails during local compilation.
|
|
1452
|
+
* @param providerId The ID of the LLM provider that failed.
|
|
1453
|
+
* @param targetLocale The target locale being translated to.
|
|
1454
|
+
* @param errorMessage The error message received from the API.
|
|
1455
|
+
*/
|
|
1456
|
+
static _failLLMFailureLocal(providerId, targetLocale, errorMessage) {
|
|
1457
|
+
const details = providerDetails[providerId];
|
|
1458
|
+
if (!details) {
|
|
1459
|
+
console.error(
|
|
1460
|
+
`Internal Error: Missing details for provider "${providerId}" when reporting local failure.`
|
|
1461
|
+
);
|
|
1462
|
+
console.error(`Original Error: ${errorMessage}`);
|
|
1463
|
+
process.exit(1);
|
|
1464
|
+
}
|
|
1370
1465
|
const isInvalidApiKey = errorMessage.match("Invalid API Key");
|
|
1371
1466
|
if (isInvalidApiKey) {
|
|
1372
1467
|
console.log(dedent2`
|
|
1373
1468
|
\n
|
|
1374
|
-
⚠️ Lingo.dev Compiler requires a valid
|
|
1375
|
-
|
|
1376
|
-
It looks like you set
|
|
1469
|
+
⚠️ Lingo.dev Compiler requires a valid ${details.name} API key to translate your application.
|
|
1470
|
+
|
|
1471
|
+
It looks like you set ${details.name} API key but it is not valid. Please check your API key and try again.
|
|
1472
|
+
|
|
1473
|
+
Error details from ${details.name} API: ${errorMessage}
|
|
1377
1474
|
|
|
1378
|
-
Error details from Groq API: ${errorMessage}
|
|
1379
|
-
|
|
1380
1475
|
👉 You can set the API key in one of the following ways:
|
|
1381
|
-
1. User-wide: Run npx lingo.dev@latest config set
|
|
1382
|
-
2. Project-wide: Add
|
|
1383
|
-
3
|
|
1384
|
-
|
|
1476
|
+
1. User-wide: Run npx lingo.dev@latest config set ${details.apiKeyConfigKey} <your-api-key>
|
|
1477
|
+
2. Project-wide: Add ${details.apiKeyEnvVar}=<your-api-key> to .env file in every project that uses Lingo.dev Localization Compiler
|
|
1478
|
+
3 Session-wide: Run export ${details.apiKeyEnvVar}=<your-api-key> in your terminal before running the compiler to set the API key for the current session
|
|
1479
|
+
|
|
1385
1480
|
⭐️ Also:
|
|
1386
|
-
1. If you don't yet have a
|
|
1481
|
+
1. If you don't yet have a ${details.name} API key, get one for free at ${details.getKeyLink}
|
|
1387
1482
|
2. If you want to use a different LLM, raise an issue in our open-source repo: https://lingo.dev/go/gh
|
|
1388
1483
|
3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
|
|
1389
|
-
|
|
1484
|
+
|
|
1390
1485
|
✨
|
|
1391
1486
|
`);
|
|
1392
1487
|
} else {
|
|
1393
1488
|
console.log(
|
|
1394
1489
|
dedent2`
|
|
1395
1490
|
\n
|
|
1396
|
-
⚠️ Lingo.dev Compiler tried to translate your application to "${targetLocale}" locale via
|
|
1491
|
+
⚠️ Lingo.dev Compiler tried to translate your application to "${targetLocale}" locale via ${details.name} but it failed.
|
|
1397
1492
|
|
|
1398
|
-
Error details from
|
|
1493
|
+
Error details from ${details.name} API: ${errorMessage}
|
|
1399
1494
|
|
|
1400
|
-
This error comes from
|
|
1495
|
+
This error comes from the ${details.name} API, please check their documentation for more details: ${details.docsLink}
|
|
1401
1496
|
|
|
1402
1497
|
⭐️ Also:
|
|
1403
|
-
1. Did you set
|
|
1404
|
-
2. Did you reach any limits of your
|
|
1498
|
+
1. Did you set ${details.apiKeyEnvVar} environment variable correctly?
|
|
1499
|
+
2. Did you reach any limits of your ${details.name} account?
|
|
1405
1500
|
3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
|
|
1406
1501
|
|
|
1407
1502
|
✨
|
|
@@ -1635,10 +1730,10 @@ import traverse6 from "@babel/traverse";
|
|
|
1635
1730
|
function findInvokations(ast, params) {
|
|
1636
1731
|
const result = [];
|
|
1637
1732
|
traverse6(ast, {
|
|
1638
|
-
ImportDeclaration(
|
|
1639
|
-
if (
|
|
1733
|
+
ImportDeclaration(path7) {
|
|
1734
|
+
if (path7.node.source.value !== params.moduleName) return;
|
|
1640
1735
|
const importNames = /* @__PURE__ */ new Map();
|
|
1641
|
-
const specifiers =
|
|
1736
|
+
const specifiers = path7.node.specifiers;
|
|
1642
1737
|
specifiers.forEach((specifier) => {
|
|
1643
1738
|
if (t11.isImportSpecifier(specifier) && t11.isIdentifier(specifier.imported) && specifier.imported.name === params.functionName) {
|
|
1644
1739
|
importNames.set(specifier.local.name, true);
|
|
@@ -1648,13 +1743,13 @@ function findInvokations(ast, params) {
|
|
|
1648
1743
|
importNames.set(specifier.local.name, "namespace");
|
|
1649
1744
|
}
|
|
1650
1745
|
});
|
|
1651
|
-
collectCallExpressions(
|
|
1746
|
+
collectCallExpressions(path7, importNames, result, params.functionName);
|
|
1652
1747
|
}
|
|
1653
1748
|
});
|
|
1654
1749
|
return result;
|
|
1655
1750
|
}
|
|
1656
|
-
function collectCallExpressions(
|
|
1657
|
-
const program =
|
|
1751
|
+
function collectCallExpressions(path7, importNames, result, functionName) {
|
|
1752
|
+
const program = path7.findParent(
|
|
1658
1753
|
(p) => p.isProgram()
|
|
1659
1754
|
);
|
|
1660
1755
|
if (!program) return;
|
|
@@ -1764,18 +1859,18 @@ function jsxFragmentMutation(payload) {
|
|
|
1764
1859
|
let foundFragments = false;
|
|
1765
1860
|
let fragmentImportName = null;
|
|
1766
1861
|
traverse7(ast, {
|
|
1767
|
-
ImportDeclaration(
|
|
1768
|
-
if (
|
|
1769
|
-
for (const specifier of
|
|
1862
|
+
ImportDeclaration(path7) {
|
|
1863
|
+
if (path7.node.source.value !== "react") return;
|
|
1864
|
+
for (const specifier of path7.node.specifiers) {
|
|
1770
1865
|
if (t14.isImportSpecifier(specifier) && t14.isIdentifier(specifier.imported) && specifier.imported.name === "Fragment") {
|
|
1771
1866
|
fragmentImportName = specifier.local.name;
|
|
1772
|
-
|
|
1867
|
+
path7.stop();
|
|
1773
1868
|
}
|
|
1774
1869
|
}
|
|
1775
1870
|
}
|
|
1776
1871
|
});
|
|
1777
1872
|
traverse7(ast, {
|
|
1778
|
-
JSXFragment(
|
|
1873
|
+
JSXFragment(path7) {
|
|
1779
1874
|
foundFragments = true;
|
|
1780
1875
|
if (!fragmentImportName) {
|
|
1781
1876
|
const result = getOrCreateImport(ast, {
|
|
@@ -1787,10 +1882,10 @@ function jsxFragmentMutation(payload) {
|
|
|
1787
1882
|
const fragmentElement = t14.jsxElement(
|
|
1788
1883
|
t14.jsxOpeningElement(t14.jsxIdentifier(fragmentImportName), [], false),
|
|
1789
1884
|
t14.jsxClosingElement(t14.jsxIdentifier(fragmentImportName)),
|
|
1790
|
-
|
|
1885
|
+
path7.node.children,
|
|
1791
1886
|
false
|
|
1792
1887
|
);
|
|
1793
|
-
|
|
1888
|
+
path7.replaceWith(fragmentElement);
|
|
1794
1889
|
}
|
|
1795
1890
|
});
|
|
1796
1891
|
return payload;
|
|
@@ -1801,23 +1896,23 @@ import traverse8 from "@babel/traverse";
|
|
|
1801
1896
|
import * as t15 from "@babel/types";
|
|
1802
1897
|
var jsxHtmlLangMutation = createCodeMutation((payload) => {
|
|
1803
1898
|
traverse8(payload.ast, {
|
|
1804
|
-
JSXElement: (
|
|
1805
|
-
if (getJsxElementName(
|
|
1899
|
+
JSXElement: (path7) => {
|
|
1900
|
+
if (getJsxElementName(path7)?.toLowerCase() === "html") {
|
|
1806
1901
|
const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
|
|
1807
1902
|
const packagePath = mode === "client" ? "lingo.dev/react/client" /* ReactClient */ : "lingo.dev/react/rsc" /* ReactRSC */;
|
|
1808
1903
|
const lingoHtmlComponentImport = getOrCreateImport(payload.ast, {
|
|
1809
1904
|
moduleName: packagePath,
|
|
1810
1905
|
exportedName: "LingoHtmlComponent"
|
|
1811
1906
|
});
|
|
1812
|
-
|
|
1907
|
+
path7.node.openingElement.name = t15.jsxIdentifier(
|
|
1813
1908
|
lingoHtmlComponentImport.importedName
|
|
1814
1909
|
);
|
|
1815
|
-
if (
|
|
1816
|
-
|
|
1910
|
+
if (path7.node.closingElement) {
|
|
1911
|
+
path7.node.closingElement.name = t15.jsxIdentifier(
|
|
1817
1912
|
lingoHtmlComponentImport.importedName
|
|
1818
1913
|
);
|
|
1819
1914
|
}
|
|
1820
|
-
|
|
1915
|
+
path7.skip();
|
|
1821
1916
|
}
|
|
1822
1917
|
}
|
|
1823
1918
|
});
|
|
@@ -1884,22 +1979,22 @@ var WHITESPACE_PLACEHOLDER = "[lingo-whitespace-placeholder]";
|
|
|
1884
1979
|
function extractJsxContent(nodePath, replaceWhitespacePlaceholders = true) {
|
|
1885
1980
|
const chunks = [];
|
|
1886
1981
|
nodePath.traverse({
|
|
1887
|
-
JSXElement(
|
|
1888
|
-
if (
|
|
1889
|
-
const content = extractJsxContent(
|
|
1890
|
-
const name = getJsxElementName(
|
|
1982
|
+
JSXElement(path7) {
|
|
1983
|
+
if (path7.parent === nodePath.node) {
|
|
1984
|
+
const content = extractJsxContent(path7, false);
|
|
1985
|
+
const name = getJsxElementName(path7);
|
|
1891
1986
|
chunks.push(`<element:${name}>${content}</element:${name}>`);
|
|
1892
|
-
|
|
1987
|
+
path7.skip();
|
|
1893
1988
|
}
|
|
1894
1989
|
},
|
|
1895
|
-
JSXText(
|
|
1896
|
-
chunks.push(
|
|
1990
|
+
JSXText(path7) {
|
|
1991
|
+
chunks.push(path7.node.value);
|
|
1897
1992
|
},
|
|
1898
|
-
JSXExpressionContainer(
|
|
1899
|
-
if (
|
|
1993
|
+
JSXExpressionContainer(path7) {
|
|
1994
|
+
if (path7.parent !== nodePath.node) {
|
|
1900
1995
|
return;
|
|
1901
1996
|
}
|
|
1902
|
-
const expr =
|
|
1997
|
+
const expr = path7.node.expression;
|
|
1903
1998
|
if (t16.isCallExpression(expr)) {
|
|
1904
1999
|
let key = "";
|
|
1905
2000
|
if (t16.isIdentifier(expr.callee)) {
|
|
@@ -1945,12 +2040,12 @@ function extractJsxContent(nodePath, replaceWhitespacePlaceholders = true) {
|
|
|
1945
2040
|
parts.unshift(current.name);
|
|
1946
2041
|
chunks.push(`{${parts.join(".").replaceAll(".[", "[")}}`);
|
|
1947
2042
|
}
|
|
1948
|
-
} else if (isWhitespace(
|
|
2043
|
+
} else if (isWhitespace(path7)) {
|
|
1949
2044
|
chunks.push(WHITESPACE_PLACEHOLDER);
|
|
1950
|
-
} else if (isExpression2(
|
|
2045
|
+
} else if (isExpression2(path7)) {
|
|
1951
2046
|
chunks.push("<expression/>");
|
|
1952
2047
|
}
|
|
1953
|
-
|
|
2048
|
+
path7.skip();
|
|
1954
2049
|
}
|
|
1955
2050
|
});
|
|
1956
2051
|
const result = chunks.join("");
|
|
@@ -2101,14 +2196,14 @@ import * as t18 from "@babel/types";
|
|
|
2101
2196
|
var getJsxVariables = (nodePath) => {
|
|
2102
2197
|
const variables = /* @__PURE__ */ new Set();
|
|
2103
2198
|
nodePath.traverse({
|
|
2104
|
-
JSXOpeningElement(
|
|
2105
|
-
|
|
2199
|
+
JSXOpeningElement(path7) {
|
|
2200
|
+
path7.skip();
|
|
2106
2201
|
},
|
|
2107
|
-
JSXExpressionContainer(
|
|
2108
|
-
if (t18.isIdentifier(
|
|
2109
|
-
variables.add(
|
|
2110
|
-
} else if (t18.isMemberExpression(
|
|
2111
|
-
let current =
|
|
2202
|
+
JSXExpressionContainer(path7) {
|
|
2203
|
+
if (t18.isIdentifier(path7.node.expression)) {
|
|
2204
|
+
variables.add(path7.node.expression.name);
|
|
2205
|
+
} else if (t18.isMemberExpression(path7.node.expression)) {
|
|
2206
|
+
let current = path7.node.expression;
|
|
2112
2207
|
const parts = [];
|
|
2113
2208
|
while (t18.isMemberExpression(current)) {
|
|
2114
2209
|
if (t18.isIdentifier(current.property)) {
|
|
@@ -2125,7 +2220,7 @@ var getJsxVariables = (nodePath) => {
|
|
|
2125
2220
|
variables.add(parts.join(".").replaceAll(".[", "["));
|
|
2126
2221
|
}
|
|
2127
2222
|
}
|
|
2128
|
-
|
|
2223
|
+
path7.skip();
|
|
2129
2224
|
}
|
|
2130
2225
|
});
|
|
2131
2226
|
const properties = Array.from(variables).map(
|
|
@@ -2141,16 +2236,16 @@ var getJsxFunctions = (nodePath) => {
|
|
|
2141
2236
|
const functions = /* @__PURE__ */ new Map();
|
|
2142
2237
|
let fnCounter = 0;
|
|
2143
2238
|
nodePath.traverse({
|
|
2144
|
-
JSXOpeningElement(
|
|
2145
|
-
|
|
2239
|
+
JSXOpeningElement(path7) {
|
|
2240
|
+
path7.skip();
|
|
2146
2241
|
},
|
|
2147
|
-
JSXExpressionContainer(
|
|
2148
|
-
if (t19.isCallExpression(
|
|
2242
|
+
JSXExpressionContainer(path7) {
|
|
2243
|
+
if (t19.isCallExpression(path7.node.expression)) {
|
|
2149
2244
|
let key = "";
|
|
2150
|
-
if (t19.isIdentifier(
|
|
2151
|
-
key = `${
|
|
2152
|
-
} else if (t19.isMemberExpression(
|
|
2153
|
-
let firstCallee =
|
|
2245
|
+
if (t19.isIdentifier(path7.node.expression.callee)) {
|
|
2246
|
+
key = `${path7.node.expression.callee.name}`;
|
|
2247
|
+
} else if (t19.isMemberExpression(path7.node.expression.callee)) {
|
|
2248
|
+
let firstCallee = path7.node.expression.callee;
|
|
2154
2249
|
while (t19.isMemberExpression(firstCallee) && t19.isCallExpression(firstCallee.object)) {
|
|
2155
2250
|
firstCallee = firstCallee.object.callee;
|
|
2156
2251
|
}
|
|
@@ -2171,10 +2266,10 @@ var getJsxFunctions = (nodePath) => {
|
|
|
2171
2266
|
key = parts.join(".");
|
|
2172
2267
|
}
|
|
2173
2268
|
const existing = functions.get(key) ?? [];
|
|
2174
|
-
functions.set(key, [...existing,
|
|
2269
|
+
functions.set(key, [...existing, path7.node.expression]);
|
|
2175
2270
|
fnCounter++;
|
|
2176
2271
|
}
|
|
2177
|
-
|
|
2272
|
+
path7.skip();
|
|
2178
2273
|
}
|
|
2179
2274
|
});
|
|
2180
2275
|
const properties = Array.from(functions.entries()).map(
|
|
@@ -2188,15 +2283,15 @@ import * as t20 from "@babel/types";
|
|
|
2188
2283
|
var getJsxExpressions = (nodePath) => {
|
|
2189
2284
|
const expressions = [];
|
|
2190
2285
|
nodePath.traverse({
|
|
2191
|
-
JSXOpeningElement(
|
|
2192
|
-
|
|
2286
|
+
JSXOpeningElement(path7) {
|
|
2287
|
+
path7.skip();
|
|
2193
2288
|
},
|
|
2194
|
-
JSXExpressionContainer(
|
|
2195
|
-
const expr =
|
|
2289
|
+
JSXExpressionContainer(path7) {
|
|
2290
|
+
const expr = path7.node.expression;
|
|
2196
2291
|
if (!t20.isJSXEmptyExpression(expr) && !t20.isIdentifier(expr) && !t20.isMemberExpression(expr) && !t20.isCallExpression(expr) && !(t20.isStringLiteral(expr) && expr.value === " ")) {
|
|
2197
2292
|
expressions.push(expr);
|
|
2198
2293
|
}
|
|
2199
|
-
|
|
2294
|
+
path7.skip();
|
|
2200
2295
|
}
|
|
2201
2296
|
});
|
|
2202
2297
|
return t20.arrayExpression(expressions);
|
|
@@ -2321,8 +2416,8 @@ var jsxRemoveAttributesMutation = createCodeMutation(
|
|
|
2321
2416
|
"data-jsx-attribute-scope"
|
|
2322
2417
|
];
|
|
2323
2418
|
traverse9(payload.ast, {
|
|
2324
|
-
JSXElement(
|
|
2325
|
-
const openingElement =
|
|
2419
|
+
JSXElement(path7) {
|
|
2420
|
+
const openingElement = path7.node.openingElement;
|
|
2326
2421
|
openingElement.attributes = openingElement.attributes.filter((attr) => {
|
|
2327
2422
|
const removeAttr = t22.isJSXAttribute(attr) && t22.isJSXIdentifier(attr.name) && ATTRIBUTES_TO_REMOVE.includes(attr.name.name);
|
|
2328
2423
|
return !removeAttr;
|
|
@@ -2336,18 +2431,18 @@ var jsxRemoveAttributesMutation = createCodeMutation(
|
|
|
2336
2431
|
);
|
|
2337
2432
|
|
|
2338
2433
|
// src/client-dictionary-loader.ts
|
|
2339
|
-
import
|
|
2434
|
+
import path5 from "path";
|
|
2340
2435
|
import * as t23 from "@babel/types";
|
|
2341
2436
|
var clientDictionaryLoaderMutation = createCodeMutation((payload) => {
|
|
2342
|
-
const lingoDir =
|
|
2437
|
+
const lingoDir = path5.resolve(
|
|
2343
2438
|
process.cwd(),
|
|
2344
2439
|
payload.params.sourceRoot,
|
|
2345
2440
|
payload.params.lingoDir
|
|
2346
2441
|
);
|
|
2347
|
-
const currentDir =
|
|
2348
|
-
|
|
2442
|
+
const currentDir = path5.dirname(
|
|
2443
|
+
path5.resolve(process.cwd(), payload.params.sourceRoot, payload.fileKey)
|
|
2349
2444
|
);
|
|
2350
|
-
const relativeLingoPath =
|
|
2445
|
+
const relativeLingoPath = path5.relative(currentDir, lingoDir);
|
|
2351
2446
|
const invokations = findInvokations(payload.ast, {
|
|
2352
2447
|
moduleName: "lingo.dev/react/client" /* ReactClient */,
|
|
2353
2448
|
functionName: "loadDictionary"
|
|
@@ -2384,13 +2479,23 @@ var clientDictionaryLoaderMutation = createCodeMutation((payload) => {
|
|
|
2384
2479
|
});
|
|
2385
2480
|
|
|
2386
2481
|
// src/index.ts
|
|
2482
|
+
var keyCheckers = {
|
|
2483
|
+
groq: {
|
|
2484
|
+
checkEnv: getGroqKeyFromEnv,
|
|
2485
|
+
checkRc: getGroqKeyFromRc
|
|
2486
|
+
},
|
|
2487
|
+
google: {
|
|
2488
|
+
checkEnv: getGoogleKeyFromEnv,
|
|
2489
|
+
checkRc: getGoogleKeyFromRc
|
|
2490
|
+
}
|
|
2491
|
+
};
|
|
2387
2492
|
var unplugin = createUnplugin(
|
|
2388
2493
|
(_params, _meta) => {
|
|
2389
2494
|
console.log("\u2139\uFE0F Starting Lingo.dev compiler...");
|
|
2495
|
+
const params = _11.defaults(_params, defaultParams);
|
|
2390
2496
|
if (!isRunningInCIOrDocker()) {
|
|
2391
|
-
|
|
2497
|
+
validateLLMKeyDetails(params.models);
|
|
2392
2498
|
}
|
|
2393
|
-
const params = _11.defaults(_params, defaultParams);
|
|
2394
2499
|
const invalidLocales = getInvalidLocales(
|
|
2395
2500
|
params.models,
|
|
2396
2501
|
params.sourceLocale,
|
|
@@ -2405,7 +2510,7 @@ var unplugin = createUnplugin(
|
|
|
2405
2510
|
1. Refer to documentation for help: https://docs.lingo.dev/
|
|
2406
2511
|
2. If you want to use a different LLM, raise an issue in our open-source repo: https://lingo.dev/go/gh
|
|
2407
2512
|
3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
|
|
2408
|
-
|
|
2513
|
+
|
|
2409
2514
|
✨
|
|
2410
2515
|
`);
|
|
2411
2516
|
process.exit(1);
|
|
@@ -2447,7 +2552,7 @@ var unplugin = createUnplugin(
|
|
|
2447
2552
|
const result = _11.chain({
|
|
2448
2553
|
code,
|
|
2449
2554
|
params,
|
|
2450
|
-
fileKey:
|
|
2555
|
+
fileKey: path6.relative(path6.resolve(process.cwd(), params.sourceRoot), id).split(path6.sep).join("/")
|
|
2451
2556
|
// Always normalize for consistent dictionaries
|
|
2452
2557
|
}).thru(createPayload).thru(
|
|
2453
2558
|
composeMutations(
|
|
@@ -2504,56 +2609,81 @@ var src_default = {
|
|
|
2504
2609
|
return config2;
|
|
2505
2610
|
}
|
|
2506
2611
|
};
|
|
2507
|
-
function
|
|
2508
|
-
const
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2612
|
+
function validateLLMKeyDetails(models) {
|
|
2613
|
+
const configuredProviders = _11.chain(Object.values(models)).map((modelString) => modelString.split(":")[0]).filter(Boolean).uniq().filter(
|
|
2614
|
+
(providerId) => providerDetails.hasOwnProperty(providerId) && keyCheckers.hasOwnProperty(providerId)
|
|
2615
|
+
).value();
|
|
2616
|
+
if (configuredProviders.length === 0) {
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const keyStatuses = {};
|
|
2620
|
+
const missingProviders = [];
|
|
2621
|
+
const foundProviders = [];
|
|
2622
|
+
for (const providerId of configuredProviders) {
|
|
2623
|
+
const details = providerDetails[providerId];
|
|
2624
|
+
const checkers = keyCheckers[providerId];
|
|
2625
|
+
if (!details || !checkers) continue;
|
|
2626
|
+
const foundInEnv = !!checkers.checkEnv();
|
|
2627
|
+
const foundInRc = !!checkers.checkRc();
|
|
2628
|
+
keyStatuses[providerId] = { foundInEnv, foundInRc, details };
|
|
2629
|
+
if (!foundInEnv && !foundInRc) {
|
|
2630
|
+
missingProviders.push(providerId);
|
|
2631
|
+
} else {
|
|
2632
|
+
foundProviders.push(providerId);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
if (missingProviders.length > 0) {
|
|
2513
2636
|
console.log(dedent3`
|
|
2514
2637
|
\n
|
|
2515
|
-
💡
|
|
2638
|
+
💡 Lingo.dev Localization Compiler is configured to use the following LLM provider(s): ${configuredProviders.join(", ")}.
|
|
2639
|
+
|
|
2640
|
+
The compiler requires API keys for these providers to work, but the following keys are missing:
|
|
2641
|
+
`);
|
|
2642
|
+
for (const providerId of missingProviders) {
|
|
2643
|
+
const status = keyStatuses[providerId];
|
|
2644
|
+
if (!status) continue;
|
|
2645
|
+
console.log(dedent3`
|
|
2646
|
+
⚠️ ${status.details.name} API key is missing. Set ${status.details.apiKeyEnvVar} environment variable.
|
|
2516
2647
|
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2648
|
+
👉 You can set the API key in one of the following ways:
|
|
2649
|
+
1. User-wide: Run npx lingo.dev@latest config set ${status.details.apiKeyConfigKey || "<config-key-not-available>"} <your-api-key>
|
|
2650
|
+
2. Project-wide: Add ${status.details.apiKeyEnvVar}=<your-api-key> to .env file in every project that uses Lingo.dev Localization Compiler
|
|
2651
|
+
3. Session-wide: Run export ${status.details.apiKeyEnvVar}=<your-api-key> in your terminal before running the compiler to set the API key for the current session
|
|
2521
2652
|
|
|
2653
|
+
⭐️ If you don't yet have a ${status.details.name} API key, get one for free at ${status.details.getKeyLink}
|
|
2654
|
+
`);
|
|
2655
|
+
}
|
|
2656
|
+
console.log(dedent3`
|
|
2657
|
+
\n
|
|
2522
2658
|
⭐️ Also:
|
|
2523
|
-
1. If you
|
|
2524
|
-
2. If you want to use
|
|
2659
|
+
1. If you want to use a different LLM, update your configuration. Refer to documentation for help: https://docs.lingo.dev/
|
|
2660
|
+
2. If the model/provider you want to use isn't supported yet, raise an issue in our open-source repo: https://lingo.dev/go/gh
|
|
2525
2661
|
3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
|
|
2526
2662
|
|
|
2527
2663
|
✨
|
|
2528
2664
|
`);
|
|
2529
2665
|
process.exit(1);
|
|
2530
|
-
} else if (
|
|
2531
|
-
console.log(
|
|
2532
|
-
|
|
2533
|
-
🔑
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
);
|
|
2551
|
-
} else if (!groq.fromEnv && groq.fromRc) {
|
|
2552
|
-
console.log(
|
|
2553
|
-
dedent3`
|
|
2554
|
-
🔑 GROQ API key loaded from your user-wide configuration.
|
|
2555
|
-
`
|
|
2556
|
-
);
|
|
2666
|
+
} else if (foundProviders.length > 0) {
|
|
2667
|
+
console.log(dedent3`
|
|
2668
|
+
\n
|
|
2669
|
+
🔑 LLM API keys detected for configured providers: ${foundProviders.join(", ")}.
|
|
2670
|
+
`);
|
|
2671
|
+
for (const providerId of foundProviders) {
|
|
2672
|
+
const status = keyStatuses[providerId];
|
|
2673
|
+
if (!status) continue;
|
|
2674
|
+
let sourceMessage = "";
|
|
2675
|
+
if (status.foundInEnv && status.foundInRc) {
|
|
2676
|
+
sourceMessage = `from both environment variables (${status.details.apiKeyEnvVar}) and your user-wide configuration. The key from the environment will be used because it has higher priority.`;
|
|
2677
|
+
} else if (status.foundInEnv) {
|
|
2678
|
+
sourceMessage = `from environment variables (${status.details.apiKeyEnvVar}).`;
|
|
2679
|
+
} else if (status.foundInRc) {
|
|
2680
|
+
sourceMessage = `from your user-wide configuration${status.details.apiKeyConfigKey ? ` (${status.details.apiKeyConfigKey})` : ""}.`;
|
|
2681
|
+
}
|
|
2682
|
+
console.log(dedent3`
|
|
2683
|
+
• ${status.details.name} API key loaded ${sourceMessage}
|
|
2684
|
+
`);
|
|
2685
|
+
}
|
|
2686
|
+
console.log("\u2728");
|
|
2557
2687
|
}
|
|
2558
2688
|
}
|
|
2559
2689
|
export {
|