@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.
Files changed (3) hide show
  1. package/build/index.cjs +379 -249
  2. package/build/index.mjs +382 -252
  3. 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.13",
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(path6) {
177
- result.push(path6);
178
- path6.skip();
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(path6) {
206
- if (path6.node.source.value !== moduleName) {
206
+ ImportDeclaration(path7) {
207
+ if (path7.node.source.value !== moduleName) {
207
208
  return;
208
209
  }
209
- for (const specifier of path6.node.specifiers) {
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
- path6.stop();
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(path6) {
224
- usedNames.add(path6.node.name);
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(path6) {
241
+ Program(path7) {
241
242
  const importSpecifier2 = t2.importSpecifier(
242
243
  t2.identifier(localName),
243
244
  t2.identifier(exportedName)
244
245
  );
245
- const existingImport = path6.get("body").find(
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(path6);
256
- path6.node.body.splice(lastImportIndex + 1, 0, importDeclaration2);
256
+ const lastImportIndex = findLastImportIndex(path7);
257
+ path7.node.body.splice(lastImportIndex + 1, 0, importDeclaration2);
257
258
  }
258
- path6.stop();
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(path6) {
275
- if (path6.node.value.value === directiveValue) {
275
+ Directive(path7) {
276
+ if (path7.node.value.value === directiveValue) {
276
277
  hasDirective = true;
277
- path6.stop();
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(path6) {
328
- if (path6.node !== nodePath.node) {
329
- nestedElements.push(path6.node);
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: (path6) => {
353
- if (getJsxElementName(path6)?.toLowerCase() === "html") {
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
- [path6.node],
388
+ [path7.node],
388
389
  false
389
390
  );
390
- path6.replaceWith(provider);
391
- path6.skip();
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: (path6) => {
441
- if (!hasJsxScopeAttribute(path6)) return;
442
- path6.skip();
443
- jsxScopes.push(path6);
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(path6) {
452
- if (getJsxElementName(path6) === "LingoProvider") {
452
+ JSXElement(path7) {
453
+ if (getJsxElementName(path7) === "LingoProvider") {
453
454
  return;
454
455
  }
455
- const hasNonEmptyTextSiblings = path6.getAllPrevSiblings().concat(path6.getAllNextSiblings()).some(
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 = path6.get("children").some(
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(path6);
466
- path6.skip();
466
+ result.push(path7);
467
+ path7.skip();
467
468
  }
468
469
  }
469
470
  });
470
471
  return result;
471
472
  }
472
- function hasJsxScopeAttribute(path6) {
473
- return !!getJsxScopeAttribute(path6);
473
+ function hasJsxScopeAttribute(path7) {
474
+ return !!getJsxScopeAttribute(path7);
474
475
  }
475
- function getJsxScopeAttribute(path6) {
476
- const attribute = path6.node.openingElement.attributes.find(
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(path6) {
509
- if (!hasJsxAttributeScopeAttribute(path6)) return;
510
- const localizableAttributes = getJsxAttributeScopeAttribute(path6);
509
+ JSXElement(path7) {
510
+ if (!hasJsxAttributeScopeAttribute(path7)) return;
511
+ const localizableAttributes = getJsxAttributeScopeAttribute(path7);
511
512
  if (!localizableAttributes) return;
512
- result.push([path6, localizableAttributes]);
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(path6) {
532
- const openingElement = path6.node.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([path6, localizableAttrs]);
554
+ result.push([path7, localizableAttrs]);
554
555
  }
555
556
  }
556
557
  });
557
558
  return result;
558
559
  }
559
- function hasJsxAttributeScopeAttribute(path6) {
560
- return !!getJsxAttributeScopeAttribute(path6);
560
+ function hasJsxAttributeScopeAttribute(path7) {
561
+ return !!getJsxAttributeScopeAttribute(path7);
561
562
  }
562
- function getJsxAttributeScopeAttribute(path6) {
563
- const attribute = path6.node.openingElement.attributes.find(
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 path5 from "path";
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/groq.ts
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
- const rc = getRc();
1161
- const result = _5.get(rc, "llm.groqApiKey");
1162
- return result;
1181
+ return getKeyFromRc("llm.groqApiKey");
1163
1182
  }
1164
1183
  function getGroqKeyFromEnv() {
1165
- const ephemeralEnv = {};
1166
- dotenv.config({ processEnv: ephemeralEnv });
1167
- return ephemeralEnv.GROQ_API_KEY;
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
- if (provider !== "groq") {
1300
- throw new Error(
1301
- `\u26A0\uFE0F Provider ${provider} is not supported. Only "groq" provider is supported at the moment.`
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
- console.log(
1305
- `\u2728 Using model "${model}" from "${provider}" to translate from "${sourceLocale}" to "${targetLocale}"`
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: "user",
1319
- content: obj2xml(shotsTuple[0])
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: "assistant",
1323
- content: obj2xml(shotsTuple[1])
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
- console.log("Response", response.text);
1333
- let responseText = response.text;
1334
- responseText = responseText.substring(
1335
- responseText.indexOf("<"),
1336
- responseText.lastIndexOf(">") + 1
1337
- );
1338
- return xml2obj(responseText);
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 GROQ API key.
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 _failMissingGroqKeyCi() {
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 GROQ API key to translate missing strings, but GROQ_API_KEY is not set in the environment.
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 GROQ_API_KEY=<your-api-key>
1357
- • Project-wide / CI: add GROQ_API_KEY=<your-api-key> to your pipeline environment variables
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 GROQ API key, get one for free at https://groq.com
1361
- 2. If you want to use a different LLM, raise an issue in our open-source repo: https://lingo.dev/go/gh
1362
- 3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
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
- static _failGroqFailureLocal(targetLocale, errorMessage) {
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 Groq API key to translate your application.
1375
-
1376
- It looks like you set Groq API key but it is not valid. Please check your API key and try again.
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 llm.groqApiKey <your-api-key>
1382
- 2. Project-wide: Add GROQ_API_KEY=<your-api-key> to .env file in every project that uses Lingo.dev Localization Compiler
1383
- 3. Session-wide: Run export GROQ_API_KEY=<your-api-key> in your terminal before running the compiler to set the API key for the current session
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 GROQ API key, get one for free at https://groq.com
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 Groq but it failed.
1491
+ ⚠️ Lingo.dev Compiler tried to translate your application to "${targetLocale}" locale via ${details.name} but it failed.
1397
1492
 
1398
- Error details from Groq API: ${errorMessage}
1493
+ Error details from ${details.name} API: ${errorMessage}
1399
1494
 
1400
- This error comes from Groq API, please check their documentation for more details: https://console.groq.com/docs/errors
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 GROQ_API_KEY environment variable?
1404
- 2. Did you reach any limits of your Groq account?
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(path6) {
1639
- if (path6.node.source.value !== params.moduleName) return;
1733
+ ImportDeclaration(path7) {
1734
+ if (path7.node.source.value !== params.moduleName) return;
1640
1735
  const importNames = /* @__PURE__ */ new Map();
1641
- const specifiers = path6.node.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(path6, importNames, result, params.functionName);
1746
+ collectCallExpressions(path7, importNames, result, params.functionName);
1652
1747
  }
1653
1748
  });
1654
1749
  return result;
1655
1750
  }
1656
- function collectCallExpressions(path6, importNames, result, functionName) {
1657
- const program = path6.findParent(
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(path6) {
1768
- if (path6.node.source.value !== "react") return;
1769
- for (const specifier of path6.node.specifiers) {
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
- path6.stop();
1867
+ path7.stop();
1773
1868
  }
1774
1869
  }
1775
1870
  }
1776
1871
  });
1777
1872
  traverse7(ast, {
1778
- JSXFragment(path6) {
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
- path6.node.children,
1885
+ path7.node.children,
1791
1886
  false
1792
1887
  );
1793
- path6.replaceWith(fragmentElement);
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: (path6) => {
1805
- if (getJsxElementName(path6)?.toLowerCase() === "html") {
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
- path6.node.openingElement.name = t15.jsxIdentifier(
1907
+ path7.node.openingElement.name = t15.jsxIdentifier(
1813
1908
  lingoHtmlComponentImport.importedName
1814
1909
  );
1815
- if (path6.node.closingElement) {
1816
- path6.node.closingElement.name = t15.jsxIdentifier(
1910
+ if (path7.node.closingElement) {
1911
+ path7.node.closingElement.name = t15.jsxIdentifier(
1817
1912
  lingoHtmlComponentImport.importedName
1818
1913
  );
1819
1914
  }
1820
- path6.skip();
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(path6) {
1888
- if (path6.parent === nodePath.node) {
1889
- const content = extractJsxContent(path6, false);
1890
- const name = getJsxElementName(path6);
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
- path6.skip();
1987
+ path7.skip();
1893
1988
  }
1894
1989
  },
1895
- JSXText(path6) {
1896
- chunks.push(path6.node.value);
1990
+ JSXText(path7) {
1991
+ chunks.push(path7.node.value);
1897
1992
  },
1898
- JSXExpressionContainer(path6) {
1899
- if (path6.parent !== nodePath.node) {
1993
+ JSXExpressionContainer(path7) {
1994
+ if (path7.parent !== nodePath.node) {
1900
1995
  return;
1901
1996
  }
1902
- const expr = path6.node.expression;
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(path6)) {
2043
+ } else if (isWhitespace(path7)) {
1949
2044
  chunks.push(WHITESPACE_PLACEHOLDER);
1950
- } else if (isExpression2(path6)) {
2045
+ } else if (isExpression2(path7)) {
1951
2046
  chunks.push("<expression/>");
1952
2047
  }
1953
- path6.skip();
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(path6) {
2105
- path6.skip();
2199
+ JSXOpeningElement(path7) {
2200
+ path7.skip();
2106
2201
  },
2107
- JSXExpressionContainer(path6) {
2108
- if (t18.isIdentifier(path6.node.expression)) {
2109
- variables.add(path6.node.expression.name);
2110
- } else if (t18.isMemberExpression(path6.node.expression)) {
2111
- let current = path6.node.expression;
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
- path6.skip();
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(path6) {
2145
- path6.skip();
2239
+ JSXOpeningElement(path7) {
2240
+ path7.skip();
2146
2241
  },
2147
- JSXExpressionContainer(path6) {
2148
- if (t19.isCallExpression(path6.node.expression)) {
2242
+ JSXExpressionContainer(path7) {
2243
+ if (t19.isCallExpression(path7.node.expression)) {
2149
2244
  let key = "";
2150
- if (t19.isIdentifier(path6.node.expression.callee)) {
2151
- key = `${path6.node.expression.callee.name}`;
2152
- } else if (t19.isMemberExpression(path6.node.expression.callee)) {
2153
- let firstCallee = path6.node.expression.callee;
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, path6.node.expression]);
2269
+ functions.set(key, [...existing, path7.node.expression]);
2175
2270
  fnCounter++;
2176
2271
  }
2177
- path6.skip();
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(path6) {
2192
- path6.skip();
2286
+ JSXOpeningElement(path7) {
2287
+ path7.skip();
2193
2288
  },
2194
- JSXExpressionContainer(path6) {
2195
- const expr = path6.node.expression;
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
- path6.skip();
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(path6) {
2325
- const openingElement = path6.node.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 path4 from "path";
2434
+ import path5 from "path";
2340
2435
  import * as t23 from "@babel/types";
2341
2436
  var clientDictionaryLoaderMutation = createCodeMutation((payload) => {
2342
- const lingoDir = path4.resolve(
2437
+ const lingoDir = path5.resolve(
2343
2438
  process.cwd(),
2344
2439
  payload.params.sourceRoot,
2345
2440
  payload.params.lingoDir
2346
2441
  );
2347
- const currentDir = path4.dirname(
2348
- path4.resolve(process.cwd(), payload.params.sourceRoot, payload.fileKey)
2442
+ const currentDir = path5.dirname(
2443
+ path5.resolve(process.cwd(), payload.params.sourceRoot, payload.fileKey)
2349
2444
  );
2350
- const relativeLingoPath = path4.relative(currentDir, lingoDir);
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
- validateGroqKeyDetails();
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: path5.relative(path5.resolve(process.cwd(), params.sourceRoot), id).split(path5.sep).join("/")
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 validateGroqKeyDetails() {
2508
- const groq = {
2509
- fromEnv: getGroqKeyFromEnv(),
2510
- fromRc: getGroqKeyFromRc()
2511
- };
2512
- if (!groq.fromEnv && !groq.fromRc) {
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
- 💡 You're using Lingo.dev Localization Compiler in your project, which requires a GROQ API key to work.
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
- 👉 You can set the API key in one of the following ways:
2518
- 1. User-wide: Run npx lingo.dev@latest config set llm.groqApiKey <your-api-key>
2519
- 2. Project-wide: Add GROQ_API_KEY=<your-api-key> to .env file in every project that uses Lingo.dev Localization Compiler
2520
- 3. Session-wide: Run export GROQ_API_KEY=<your-api-key> in your terminal before running the compiler to set the API key for the current session
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 don't yet have a GROQ API key, get one for free at https://groq.com
2524
- 2. If you want to use a different LLM, raise an issue in our open-source repo: https://lingo.dev/go/gh
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 (groq.fromEnv && groq.fromRc) {
2531
- console.log(
2532
- dedent3`
2533
- 🔑 GROQ API key detected in both environment variables and your user-wide configuration.
2534
-
2535
- 👉 The compiler will use the key from the environment because it has higher priority.
2536
-
2537
- To update the user-wide key run: npx lingo.dev@latest config set llm.groqApiKey <your-api-key>
2538
- To remove it run: npx lingo.dev@latest config unset llm.groqApiKey
2539
- To remove the env variable from the current session run: unset GROQ_API_KEY
2540
- `
2541
- );
2542
- } else if (groq.fromEnv && !groq.fromRc) {
2543
- console.log(
2544
- dedent3`
2545
- 🔑 GROQ API key loaded from environment variables.
2546
-
2547
- You can also save the key user-wide with: npx lingo.dev@latest config set llm.groqApiKey <your-api-key>
2548
- • Or remove the env variable from the current session with: unset GROQ_API_KEY
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 {