@lingo.dev/_compiler 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -1,2646 +1,90 @@
1
- // src/index.ts
2
- import { createUnplugin } from "unplugin";
3
-
4
- // package.json
5
- var package_default = {
6
- name: "@lingo.dev/_compiler",
7
- version: "0.3.4",
8
- description: "Lingo.dev Compiler",
9
- private: false,
10
- publishConfig: {
11
- access: "public"
12
- },
13
- sideEffects: false,
14
- type: "module",
15
- main: "build/index.cjs",
16
- types: "build/index.d.ts",
17
- module: "build/index.mjs",
18
- files: [
19
- "build"
20
- ],
21
- scripts: {
22
- dev: "tsup --watch",
23
- build: "tsc --noEmit && tsup",
24
- clean: "rm -rf build",
25
- test: "vitest --run",
26
- "test:watch": "vitest -w"
27
- },
28
- keywords: [],
29
- author: "",
30
- license: "ISC",
31
- devDependencies: {
32
- "@types/babel__generator": "^7.6.8",
33
- "@types/babel__traverse": "^7.20.6",
34
- "@types/ini": "^4.1.1",
35
- "@types/lodash": "^4.17.4",
36
- "@types/object-hash": "^3.0.6",
37
- "@types/react": "^18.3.18",
38
- next: "15.2.4",
39
- tsup: "^8.3.5",
40
- typescript: "^5.4.5"
41
- },
42
- dependencies: {
43
- "@ai-sdk/google": "^1.2.19",
44
- "@ai-sdk/groq": "^1.2.3",
45
- "@babel/generator": "^7.26.5",
46
- "@babel/parser": "^7.26.7",
47
- "@babel/traverse": "^7.27.4",
48
- "@babel/types": "^7.26.7",
49
- "@lingo.dev/_sdk": "workspace:*",
50
- "@openrouter/ai-sdk-provider": "^0.7.1",
51
- ai: "^4.2.10",
52
- dedent: "^1.6.0",
53
- dotenv: "^16.4.5",
54
- "fast-xml-parser": "^5.0.8",
55
- ini: "^5.0.0",
56
- lodash: "^4.17.21",
57
- "object-hash": "^3.0.0",
58
- "ollama-ai-provider": "^1.2.0",
59
- prettier: "^3.4.2",
60
- unplugin: "^2.1.2",
61
- vitest: "^2.1.4",
62
- zod: "^3.24.1"
63
- },
64
- packageManager: "pnpm@9.12.3"
65
- };
66
-
67
- // src/index.ts
68
- import _11 from "lodash";
69
- import dedent3 from "dedent";
70
-
71
- // src/_base.ts
72
- import generate from "@babel/generator";
73
- import * as parser from "@babel/parser";
74
- function createCodeMutation(spec) {
75
- return (payload) => {
76
- const result = spec(payload);
77
- return result;
78
- };
79
- }
80
- function createPayload(input) {
81
- const ast = parser.parse(input.code, {
82
- sourceType: "module",
83
- plugins: ["jsx", "typescript"]
84
- });
85
- return {
86
- ...input,
87
- ast
88
- };
89
- }
90
- function createOutput(payload) {
91
- const generationResult = generate(payload.ast, {}, payload.code);
92
- return {
93
- code: generationResult.code,
94
- map: generationResult.map
95
- };
96
- }
97
- function composeMutations(...mutations) {
98
- return (input) => {
99
- let result = input;
100
- for (const mutate of mutations) {
101
- const intermediateResult = mutate(result);
102
- if (!intermediateResult) {
103
- break;
104
- } else {
105
- result = intermediateResult;
106
- }
107
- }
108
- return result;
109
- };
110
- }
111
- var defaultParams = {
112
- sourceRoot: "src",
113
- lingoDir: "lingo",
114
- sourceLocale: "en",
115
- targetLocales: ["es"],
116
- rsc: false,
117
- useDirective: false,
118
- debug: false,
119
- models: {}
120
- };
121
-
122
- // src/utils/index.ts
123
- import traverse from "@babel/traverse";
124
- import * as t2 from "@babel/types";
125
-
126
- // src/utils/jsx-attribute.ts
127
- import * as t from "@babel/types";
128
- import _ from "lodash";
129
- function getJsxAttributesMap(nodePath) {
130
- const attributes = nodePath.node.openingElement.attributes;
131
- return _.reduce(
132
- attributes,
133
- (result, attr) => {
134
- if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") {
135
- return result;
136
- }
137
- const name = attr.name.name;
138
- const value = extractAttributeValue(attr);
139
- return { ...result, [name]: value };
140
- },
141
- {}
142
- );
143
- }
144
- function getJsxAttributeValue(nodePath, attributeName) {
145
- const attributes = nodePath.node.openingElement.attributes;
146
- const attribute = _.find(
147
- attributes,
148
- (attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === attributeName
149
- );
150
- if (!attribute) {
151
- return void 0;
152
- }
153
- return extractAttributeValue(attribute);
154
- }
155
- function extractAttributeValue(attribute) {
156
- if (!attribute.value) {
157
- return true;
158
- }
159
- if (attribute.value.type === "StringLiteral") {
160
- return attribute.value.value;
161
- }
162
- if (attribute.value.type === "JSXExpressionContainer") {
163
- const expression = attribute.value.expression;
164
- if (expression.type === "BooleanLiteral") {
165
- return expression.value;
166
- }
167
- if (expression.type === "NumericLiteral") {
168
- return expression.value;
169
- }
170
- if (expression.type === "StringLiteral") {
171
- return expression.value;
172
- }
173
- }
174
- return null;
175
- }
176
-
177
- // src/utils/index.ts
178
- function getJsxRoots(node) {
179
- const result = [];
180
- traverse(node, {
181
- JSXElement(path7) {
182
- result.push(path7);
183
- path7.skip();
184
- }
185
- });
186
- return result;
187
- }
188
- function getOrCreateImport(ast, params) {
189
- let importedName = params.exportedName;
190
- let existingImport = findExistingImport(
191
- ast,
192
- params.exportedName,
193
- params.moduleName
194
- );
195
- if (existingImport) {
196
- return { importedName: existingImport };
197
- }
198
- importedName = generateUniqueImportName(ast, params.exportedName);
199
- createImportDeclaration(
200
- ast,
201
- importedName,
202
- params.exportedName,
203
- params.moduleName
204
- );
205
- return { importedName };
206
- }
207
- function findExistingImport(ast, exportedName, moduleName) {
208
- let result = null;
209
- traverse(ast, {
210
- ImportDeclaration(path7) {
211
- if (!moduleName.includes(path7.node.source.value)) {
212
- return;
213
- }
214
- for (const specifier of path7.node.specifiers) {
215
- if (t2.isImportSpecifier(specifier) && (t2.isIdentifier(specifier.imported) && specifier.imported.name === exportedName || specifier.importKind === "value" && t2.isIdentifier(specifier.local) && specifier.local.name === exportedName)) {
216
- result = specifier.local.name;
217
- path7.stop();
218
- return;
219
- }
220
- }
221
- }
222
- });
223
- return result;
224
- }
225
- function generateUniqueImportName(ast, baseName) {
226
- const usedNames = /* @__PURE__ */ new Set();
227
- traverse(ast, {
228
- Identifier(path7) {
229
- usedNames.add(path7.node.name);
230
- }
231
- });
232
- if (!usedNames.has(baseName)) {
233
- return baseName;
234
- }
235
- let counter = 1;
236
- let candidateName = `${baseName}${counter}`;
237
- while (usedNames.has(candidateName)) {
238
- counter++;
239
- candidateName = `${baseName}${counter}`;
240
- }
241
- return candidateName;
242
- }
243
- function createImportDeclaration(ast, localName, exportedName, moduleName) {
244
- traverse(ast, {
245
- Program(path7) {
246
- const importSpecifier2 = t2.importSpecifier(
247
- t2.identifier(localName),
248
- t2.identifier(exportedName)
249
- );
250
- const existingImport = path7.get("body").find(
251
- (nodePath) => t2.isImportDeclaration(nodePath.node) && moduleName.includes(nodePath.node.source.value)
252
- );
253
- if (existingImport && t2.isImportDeclaration(existingImport.node)) {
254
- existingImport.node.specifiers.push(importSpecifier2);
255
- } else {
256
- const importDeclaration2 = t2.importDeclaration(
257
- [importSpecifier2],
258
- t2.stringLiteral(moduleName[0])
259
- );
260
- const lastImportIndex = findLastImportIndex(path7);
261
- path7.node.body.splice(lastImportIndex + 1, 0, importDeclaration2);
262
- }
263
- path7.stop();
264
- }
265
- });
266
- }
267
- function findLastImportIndex(programPath) {
268
- const body = programPath.node.body;
269
- for (let i = body.length - 1; i >= 0; i--) {
270
- if (t2.isImportDeclaration(body[i])) {
271
- return i;
272
- }
273
- }
274
- return -1;
275
- }
276
- function _hasFileDirective(ast, directiveValue) {
277
- let hasDirective = false;
278
- traverse(ast, {
279
- Directive(path7) {
280
- if (path7.node.value.value === directiveValue) {
281
- hasDirective = true;
282
- path7.stop();
283
- }
284
- }
285
- });
286
- return hasDirective;
287
- }
288
- function hasI18nDirective(ast) {
289
- return _hasFileDirective(ast, "use i18n");
290
- }
291
- function hasClientDirective(ast) {
292
- return _hasFileDirective(ast, "use client");
293
- }
294
- function getModuleExecutionMode(ast, rscEnabled) {
295
- if (rscEnabled) {
296
- if (hasClientDirective(ast)) {
297
- return "client";
298
- } else {
299
- return "server";
300
- }
301
- } else {
302
- return "client";
303
- }
304
- }
305
-
306
- // src/i18n-directive.ts
307
- var i18nDirectiveMutation = createCodeMutation((payload) => {
308
- if (!payload.params.useDirective || hasI18nDirective(payload.ast)) {
309
- return payload;
310
- } else {
311
- return null;
312
- }
313
- });
314
- var i18n_directive_default = i18nDirectiveMutation;
315
-
316
- // src/jsx-provider.ts
317
- import traverse2 from "@babel/traverse";
318
- import * as t4 from "@babel/types";
319
-
320
- // src/utils/jsx-element.ts
321
- import * as t3 from "@babel/types";
322
- function getJsxElementName(nodePath) {
323
- const openingElement = nodePath.node.openingElement;
324
- if (t3.isJSXIdentifier(openingElement.name)) {
325
- return openingElement.name.name;
326
- }
327
- return null;
328
- }
329
- function getNestedJsxElements(nodePath) {
330
- const nestedElements = [];
331
- nodePath.traverse({
332
- JSXElement(path7) {
333
- if (path7.node !== nodePath.node) {
334
- nestedElements.push(path7.node);
335
- }
336
- }
337
- });
338
- const arrayOfElements = nestedElements.map((element, index) => {
339
- const param = t3.identifier("children");
340
- const clonedElement = t3.cloneNode(element);
341
- clonedElement.children = [t3.jsxExpressionContainer(param)];
342
- return t3.arrowFunctionExpression(
343
- [t3.objectPattern([t3.objectProperty(param, param, false, true)])],
344
- clonedElement
345
- );
346
- });
347
- const result = t3.arrayExpression(arrayOfElements);
348
- return result;
349
- }
350
-
351
- // src/_const.ts
352
- var ModuleId = {
353
- ReactClient: ["lingo.dev/react/client", "lingo.dev/react-client"],
354
- ReactRSC: ["lingo.dev/react/rsc", "lingo.dev/react-rsc"],
355
- ReactRouter: ["lingo.dev/react/react-router", "lingo.dev/react-router"]
356
- };
357
- var LCP_DICTIONARY_FILE_NAME = "dictionary.js";
358
-
359
- // src/jsx-provider.ts
360
- var jsxProviderMutation = createCodeMutation((payload) => {
361
- traverse2(payload.ast, {
362
- JSXElement: (path7) => {
363
- if (getJsxElementName(path7)?.toLowerCase() === "html") {
364
- const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
365
- if (mode === "client") {
366
- return;
367
- }
368
- const lingoProviderImport = getOrCreateImport(payload.ast, {
369
- moduleName: ModuleId.ReactRSC,
370
- exportedName: "LingoProvider"
371
- });
372
- const loadDictionaryImport = getOrCreateImport(payload.ast, {
373
- moduleName: ModuleId.ReactRSC,
374
- exportedName: "loadDictionary"
375
- });
376
- const loadDictionaryArrow = t4.arrowFunctionExpression(
377
- [t4.identifier("locale")],
378
- t4.callExpression(t4.identifier(loadDictionaryImport.importedName), [
379
- t4.identifier("locale")
380
- ])
381
- );
382
- const providerProps = [
383
- t4.jsxAttribute(
384
- t4.jsxIdentifier("loadDictionary"),
385
- t4.jsxExpressionContainer(loadDictionaryArrow)
386
- )
387
- ];
388
- const provider = t4.jsxElement(
389
- t4.jsxOpeningElement(
390
- t4.jsxIdentifier(lingoProviderImport.importedName),
391
- providerProps,
392
- false
393
- ),
394
- t4.jsxClosingElement(
395
- t4.jsxIdentifier(lingoProviderImport.importedName)
396
- ),
397
- [path7.node],
398
- false
399
- );
400
- path7.replaceWith(provider);
401
- path7.skip();
402
- }
403
- }
404
- });
405
- return payload;
406
- });
407
- var jsx_provider_default = jsxProviderMutation;
408
-
409
- // src/jsx-root-flag.ts
410
- import * as t5 from "@babel/types";
411
- var jsxRootFlagMutation = createCodeMutation((payload) => {
412
- const jsxRoots = getJsxRoots(payload.ast);
413
- for (const jsxElementPath of jsxRoots) {
414
- jsxElementPath.node.openingElement.attributes.push(
415
- t5.jsxAttribute(t5.jsxIdentifier("data-jsx-root"), null)
416
- );
417
- }
418
- return {
419
- ...payload
420
- };
421
- });
422
- var jsx_root_flag_default = jsxRootFlagMutation;
423
-
424
- // src/jsx-scope-flag.ts
425
- import * as t8 from "@babel/types";
426
-
427
- // src/utils/ast-key.ts
428
- import * as t6 from "@babel/types";
429
- import traverse3 from "@babel/traverse";
430
- function getAstKey(nodePath) {
431
- const keyChunks = [];
432
- let current = nodePath;
433
- while (current) {
434
- keyChunks.push(current.key);
435
- current = current.parentPath;
436
- if (t6.isProgram(current?.node)) {
437
- break;
438
- }
439
- }
440
- const result = keyChunks.reverse().join("/");
441
- return result;
442
- }
443
-
444
- // src/utils/jsx-scope.ts
445
- import * as t7 from "@babel/types";
446
- import traverse4 from "@babel/traverse";
447
- function collectJsxScopes(ast) {
448
- const jsxScopes = [];
449
- traverse4(ast, {
450
- JSXElement: (path7) => {
451
- if (!hasJsxScopeAttribute(path7)) return;
452
- path7.skip();
453
- jsxScopes.push(path7);
454
- }
455
- });
456
- return jsxScopes;
457
- }
458
- function getJsxScopes(node) {
459
- const result = [];
460
- traverse4(node, {
461
- JSXElement(path7) {
462
- if (getJsxElementName(path7) === "LingoProvider") {
463
- return;
464
- }
465
- const hasNonEmptyTextSiblings = path7.getAllPrevSiblings().concat(path7.getAllNextSiblings()).some(
466
- (sibling) => t7.isJSXText(sibling.node) && sibling.node.value?.trim() !== ""
467
- );
468
- if (hasNonEmptyTextSiblings) {
469
- return;
470
- }
471
- const hasNonEmptyTextChild = path7.get("children").some(
472
- (child) => t7.isJSXText(child.node) && child.node.value?.trim() !== ""
473
- );
474
- if (hasNonEmptyTextChild) {
475
- result.push(path7);
476
- path7.skip();
477
- }
478
- }
479
- });
480
- return result;
481
- }
482
- function hasJsxScopeAttribute(path7) {
483
- return !!getJsxScopeAttribute(path7);
484
- }
485
- function getJsxScopeAttribute(path7) {
486
- const attribute = path7.node.openingElement.attributes.find(
487
- (attr) => attr.type === "JSXAttribute" && attr.name.name === "data-jsx-scope"
488
- );
489
- return attribute && t7.isJSXAttribute(attribute) && t7.isStringLiteral(attribute.value) ? attribute.value.value : void 0;
490
- }
491
-
492
- // src/jsx-scope-flag.ts
493
- var jsxScopeFlagMutation = createCodeMutation((payload) => {
494
- const jsxScopes = getJsxScopes(payload.ast);
495
- for (const jsxScope of jsxScopes) {
496
- jsxScope.node.openingElement.attributes.push(
497
- t8.jsxAttribute(
498
- t8.jsxIdentifier("data-jsx-scope"),
499
- t8.stringLiteral(getAstKey(jsxScope))
500
- )
501
- );
502
- }
503
- return {
504
- ...payload
505
- };
506
- });
507
- var jsx_scope_flag_default = jsxScopeFlagMutation;
508
-
509
- // src/jsx-attribute-flag.ts
510
- import * as t10 from "@babel/types";
511
-
512
- // src/utils/jsx-attribute-scope.ts
513
- import * as t9 from "@babel/types";
514
- import traverse5 from "@babel/traverse";
515
- function collectJsxAttributeScopes(node) {
516
- const result = [];
517
- traverse5(node, {
518
- JSXElement(path7) {
519
- if (!hasJsxAttributeScopeAttribute(path7)) return;
520
- const localizableAttributes = getJsxAttributeScopeAttribute(path7);
521
- if (!localizableAttributes) return;
522
- result.push([path7, localizableAttributes]);
523
- }
524
- });
525
- return result;
526
- }
527
- function getJsxAttributeScopes(node) {
528
- const result = [];
529
- const LOCALIZABLE_ATTRIBUTES = [
530
- "title",
531
- "aria-label",
532
- "aria-description",
533
- "alt",
534
- "label",
535
- "description",
536
- "placeholder",
537
- "content",
538
- "subtitle"
539
- ];
540
- traverse5(node, {
541
- JSXElement(path7) {
542
- const openingElement = path7.node.openingElement;
543
- const elementName = openingElement.name;
544
- if (!t9.isJSXIdentifier(elementName) || !elementName.name) {
545
- return;
546
- }
547
- const hasAttributeScope = openingElement.attributes.find(
548
- (attr) => t9.isJSXAttribute(attr) && attr.name.name === "data-jsx-attribute-scope"
549
- );
550
- if (hasAttributeScope) {
551
- return;
552
- }
553
- const localizableAttrs = openingElement.attributes.filter(
554
- (attr) => {
555
- if (!t9.isJSXAttribute(attr) || !t9.isStringLiteral(attr.value)) {
556
- return false;
557
- }
558
- const name = attr.name.name;
559
- return typeof name === "string" && LOCALIZABLE_ATTRIBUTES.includes(name);
560
- }
561
- ).map((attr) => attr.name.name);
562
- if (localizableAttrs.length > 0) {
563
- result.push([path7, localizableAttrs]);
564
- }
565
- }
566
- });
567
- return result;
568
- }
569
- function hasJsxAttributeScopeAttribute(path7) {
570
- return !!getJsxAttributeScopeAttribute(path7);
571
- }
572
- function getJsxAttributeScopeAttribute(path7) {
573
- const attribute = path7.node.openingElement.attributes.find(
574
- (attr) => attr.type === "JSXAttribute" && attr.name.name === "data-jsx-attribute-scope"
575
- );
576
- if (!attribute || !t9.isJSXAttribute(attribute)) {
577
- return void 0;
578
- }
579
- if (t9.isJSXExpressionContainer(attribute.value) && t9.isArrayExpression(attribute.value.expression)) {
580
- const arrayExpr = attribute.value.expression;
581
- return arrayExpr.elements.filter((el) => t9.isStringLiteral(el)).map((el) => el.value);
582
- }
583
- if (t9.isStringLiteral(attribute.value)) {
584
- return [attribute.value.value];
585
- }
586
- return void 0;
587
- }
588
-
589
- // src/jsx-attribute-flag.ts
590
- var jsxAttributeFlagMutation = createCodeMutation(
591
- (payload) => {
592
- const jsxScopes = getJsxAttributeScopes(payload.ast);
593
- for (const [jsxScope, attributes] of jsxScopes) {
594
- const scopeKey = getAstKey(jsxScope);
595
- jsxScope.node.openingElement.attributes.push(
596
- t10.jsxAttribute(
597
- t10.jsxIdentifier("data-jsx-attribute-scope"),
598
- t10.jsxExpressionContainer(
599
- t10.arrayExpression(
600
- attributes.map(
601
- (attr) => t10.stringLiteral(`${attr}:${scopeKey}-${attr}`)
602
- )
603
- )
604
- )
605
- )
606
- );
607
- }
608
- return {
609
- ...payload
610
- };
611
- }
612
- );
613
- var jsx_attribute_flag_default = jsxAttributeFlagMutation;
614
-
615
- // src/index.ts
616
- import path6 from "path";
617
-
618
- // src/utils/module-params.ts
619
- function parseParametrizedModuleId(rawId) {
620
- const moduleUri = new URL(rawId, "module://");
621
- return {
622
- id: moduleUri.pathname.replace(/^\//, ""),
623
- params: Object.fromEntries(moduleUri.searchParams.entries())
624
- };
625
- }
626
-
627
- // src/lib/lcp/index.ts
628
- import * as fs from "fs";
629
- import _2 from "lodash";
630
- import * as path from "path";
631
- import dedent from "dedent";
632
- var LCP_FILE_NAME = "meta.json";
633
- var LCP = class _LCP {
634
- constructor(filePath, data = {
635
- version: 0.1
636
- }) {
637
- this.filePath = filePath;
638
- this.data = data;
639
- }
640
- static ensureFile(params) {
641
- const filePath = path.resolve(
642
- process.cwd(),
643
- params.sourceRoot,
644
- params.lingoDir,
645
- LCP_FILE_NAME
646
- );
647
- if (!fs.existsSync(filePath)) {
648
- const dir = path.dirname(filePath);
649
- if (!fs.existsSync(dir)) {
650
- fs.mkdirSync(dir, { recursive: true });
651
- }
652
- fs.writeFileSync(filePath, "{}");
653
- console.log(dedent`
654
- \n
655
- ⚠️ Lingo.dev Compiler detected missing meta.json file in lingo directory.
656
- Please restart the build / watch command to regenerate all Lingo.dev Compiler files.
657
- `);
658
- try {
659
- fs.rmdirSync(path.resolve(process.cwd(), ".next"), {
660
- recursive: true
661
- });
662
- } catch (error) {
663
- }
664
- process.exit(1);
665
- }
666
- }
667
- static getInstance(params) {
668
- const filePath = path.resolve(
669
- process.cwd(),
670
- params.sourceRoot,
671
- params.lingoDir,
672
- LCP_FILE_NAME
673
- );
674
- if (fs.existsSync(filePath)) {
675
- return new _LCP(filePath, JSON.parse(fs.readFileSync(filePath, "utf8")));
676
- }
677
- return new _LCP(filePath);
678
- }
679
- // wait until LCP file stops updating
680
- // this ensures all files were transformed before loading / translating dictionaries
681
- static async ready(params) {
682
- if (params.isDev) {
683
- _LCP.ensureFile(params);
684
- }
685
- const filePath = path.resolve(
686
- process.cwd(),
687
- params.sourceRoot,
688
- params.lingoDir,
689
- LCP_FILE_NAME
690
- );
691
- if (fs.existsSync(filePath)) {
692
- const stats = fs.statSync(filePath);
693
- if (Date.now() - stats.mtimeMs > 1500) {
694
- return;
695
- }
696
- }
697
- return new Promise((resolve3) => {
698
- setTimeout(() => {
699
- _LCP.ready(params).then(resolve3);
700
- }, 750);
701
- });
702
- }
703
- resetScope(fileKey, scopeKey) {
704
- if (!_2.isObject(
705
- _2.get(this.data, ["files", fileKey])
706
- )) {
707
- _2.set(this.data, ["files", fileKey], {});
708
- }
709
- _2.set(
710
- this.data,
711
- [
712
- "files",
713
- fileKey,
714
- "scopes",
715
- scopeKey
716
- ],
717
- {}
718
- );
719
- return this;
720
- }
721
- setScopeType(fileKey, scopeKey, type) {
722
- return this._setScopeField(fileKey, scopeKey, "type", type);
723
- }
724
- setScopeContext(fileKey, scopeKey, context) {
725
- return this._setScopeField(fileKey, scopeKey, "context", context);
726
- }
727
- setScopeHash(fileKey, scopeKey, hash) {
728
- return this._setScopeField(fileKey, scopeKey, "hash", hash);
729
- }
730
- setScopeSkip(fileKey, scopeKey, skip) {
731
- return this._setScopeField(fileKey, scopeKey, "skip", skip);
732
- }
733
- setScopeOverrides(fileKey, scopeKey, overrides) {
734
- return this._setScopeField(fileKey, scopeKey, "overrides", overrides);
735
- }
736
- setScopeContent(fileKey, scopeKey, content) {
737
- return this._setScopeField(fileKey, scopeKey, "content", content);
738
- }
739
- toJSON() {
740
- const files = _2(this.data?.files).mapValues((file, fileName) => {
741
- return {
742
- ...file,
743
- scopes: _2(file?.scopes).toPairs().sortBy([0]).fromPairs().value()
744
- };
745
- }).toPairs().sortBy([0]).fromPairs().value();
746
- return { ...this.data, files };
747
- }
748
- toString() {
749
- return JSON.stringify(this.toJSON(), null, 2);
750
- }
751
- save() {
752
- const hasChanges = !fs.existsSync(this.filePath) || fs.readFileSync(this.filePath, "utf8") !== this.toString();
753
- if (hasChanges) {
754
- const dir = path.dirname(this.filePath);
755
- if (!fs.existsSync(dir)) {
756
- fs.mkdirSync(dir, { recursive: true });
757
- }
758
- fs.writeFileSync(this.filePath, this.toString());
759
- this._triggerLCPReload();
760
- }
761
- }
762
- _triggerLCPReload() {
763
- const dir = path.dirname(this.filePath);
764
- const filePath = path.resolve(dir, LCP_DICTIONARY_FILE_NAME);
765
- if (fs.existsSync(filePath)) {
766
- try {
767
- const now = Math.floor(Date.now() / 1e3);
768
- fs.utimesSync(filePath, now, now);
769
- } catch (error) {
770
- if (error?.code === "EINVAL") {
771
- console.warn(
772
- dedent`
773
- ⚠️ Lingo: Auto-reload disabled - system blocks Node.js timestamp updates.
774
- 💡 Fix: Adjust security settings to allow Node.js file modifications.
775
- ⚡ Workaround: Manually refresh browser after translation changes.
776
- 💬 Need help? Join our Discord: https://lingo.dev/go/discord.
777
- `
778
- );
779
- }
780
- }
781
- }
782
- }
783
- _setScopeField(fileKey, scopeKey, field, value) {
784
- _2.set(
785
- this.data,
786
- [
787
- "files",
788
- fileKey,
789
- "scopes",
790
- scopeKey,
791
- field
792
- ],
793
- value
794
- );
795
- return this;
796
- }
797
- };
798
-
799
- // src/lib/lcp/server.ts
800
- import _7 from "lodash";
801
-
802
- // src/lib/lcp/cache.ts
803
- import * as fs2 from "fs";
804
- import * as path2 from "path";
805
- import * as prettier from "prettier";
806
- import _3 from "lodash";
807
- var LCPCache = class {
808
- // make sure the cache file exists, otherwise imports will fail
809
- static ensureDictionaryFile(params) {
810
- const cachePath = this._getCachePath(params);
811
- if (!fs2.existsSync(cachePath)) {
812
- const dir = path2.dirname(cachePath);
813
- if (!fs2.existsSync(dir)) {
814
- fs2.mkdirSync(dir, { recursive: true });
815
- }
816
- fs2.writeFileSync(cachePath, "export default {};");
817
- }
818
- }
819
- // read cache entries for given locale, validate entry hash from LCP schema
820
- static readLocaleDictionary(locale, params) {
821
- const cache = this._read(params);
822
- const dictionary = this._extractLocaleDictionary(cache, locale, params.lcp);
823
- return dictionary;
824
- }
825
- // write cache entries for given locale to existing cache file, use hash from LCP schema
826
- static async writeLocaleDictionary(dictionary, params) {
827
- const currentCache = this._read(params);
828
- const newCache = this._mergeLocaleDictionary(
829
- currentCache,
830
- dictionary,
831
- params.lcp
832
- );
833
- await this._write(newCache, params);
834
- }
835
- // merge dictionary with current cache, sort files, entries and locales to minimize diffs
836
- static _mergeLocaleDictionary(currentCache, dictionary, lcp) {
837
- const files = _3(dictionary.files).mapValues((file, fileName) => ({
838
- ...file,
839
- entries: _3(file.entries).mapValues((entry, entryName) => {
840
- const cachedEntry = _3.get(currentCache, ["files", fileName, "entries", entryName]) ?? {};
841
- const hash = _3.get(lcp, [
842
- "files",
843
- fileName,
844
- "scopes",
845
- entryName,
846
- "hash"
847
- ]);
848
- const cachedEntryContent = cachedEntry.hash === hash ? cachedEntry.content : {};
849
- const content = _3({
850
- ...cachedEntryContent,
851
- [dictionary.locale]: entry
852
- }).toPairs().sortBy([0]).fromPairs().value();
853
- return { content, hash };
854
- }).toPairs().sortBy([0]).fromPairs().value()
855
- })).toPairs().sortBy([0]).fromPairs().value();
856
- const newCache = {
857
- version: dictionary.version,
858
- files
859
- };
860
- return newCache;
861
- }
862
- // extract dictionary from cache for given locale, validate entry hash from LCP schema
863
- static _extractLocaleDictionary(cache, locale, lcp) {
864
- const findCachedEntry = (hash) => {
865
- const cachedEntry = _3(cache.files).flatMap((file) => _3.values(file.entries)).find((entry) => entry.hash === hash);
866
- if (cachedEntry) {
867
- return cachedEntry.content[locale];
868
- }
869
- return void 0;
870
- };
871
- const files = _3(lcp.files).mapValues((file) => {
872
- return {
873
- entries: _3(file.scopes).mapValues((entry) => {
874
- return findCachedEntry(entry.hash);
875
- }).pickBy((value) => value !== void 0).value()
876
- };
877
- }).pickBy((file) => !_3.isEmpty(file.entries)).value();
878
- const dictionary = {
879
- version: cache.version,
880
- locale,
881
- files
882
- };
883
- return dictionary;
884
- }
885
- // format with prettier
886
- static async _format(cachedContent, cachePath) {
887
- try {
888
- const config2 = await prettier.resolveConfig(cachePath);
889
- const prettierOptions = {
890
- ...config2 ?? {},
891
- parser: config2?.parser ? config2.parser : "typescript"
892
- };
893
- return await prettier.format(cachedContent, prettierOptions);
894
- } catch (error) {
895
- }
896
- return cachedContent;
897
- }
898
- // write cache to file as JSON
899
- static async _write(dictionaryCache, params) {
900
- const cachePath = this._getCachePath(params);
901
- const cache = `export default ${JSON.stringify(dictionaryCache, null, 2)};`;
902
- const formattedCache = await this._format(cache, cachePath);
903
- fs2.writeFileSync(cachePath, formattedCache);
904
- }
905
- // read cache from file as JSON
906
- static _read(params) {
907
- const cachePath = this._getCachePath(params);
908
- if (!fs2.existsSync(cachePath)) {
909
- return {
910
- version: 0.1,
911
- files: {}
912
- };
913
- }
914
- const jsObjectString = fs2.readFileSync(cachePath, "utf8");
915
- const cache = jsObjectString.replace(/^export default/, "").replace(/;\s*$/, "");
916
- const obj = new Function(`return (${cache})`)();
917
- return obj;
918
- }
919
- // get cache file path
920
- static _getCachePath(params) {
921
- return path2.resolve(
922
- process.cwd(),
923
- params.sourceRoot,
924
- params.lingoDir,
925
- LCP_DICTIONARY_FILE_NAME
926
- );
927
- }
928
- };
929
-
930
- // src/lib/lcp/api/index.ts
931
- import { createGroq } from "@ai-sdk/groq";
932
- import { createGoogleGenerativeAI } from "@ai-sdk/google";
933
- import { createOpenRouter } from "@openrouter/ai-sdk-provider";
934
- import { createOllama } from "ollama-ai-provider";
935
- import { generateText } from "ai";
936
- import { LingoDotDevEngine } from "@lingo.dev/_sdk";
937
- import _6 from "lodash";
938
-
939
- // src/utils/locales.ts
940
- function getInvalidLocales(localeModels, sourceLocale, targetLocales) {
941
- return targetLocales.filter((targetLocale) => {
942
- const { provider, model } = getLocaleModel(
943
- localeModels,
944
- sourceLocale,
945
- targetLocale
946
- );
947
- return provider === void 0 || model === void 0;
948
- });
949
- }
950
- function getLocaleModel(localeModels, sourceLocale, targetLocale) {
951
- const localeKeys = [
952
- `${sourceLocale}:${targetLocale}`,
953
- `*:${targetLocale}`,
954
- `${sourceLocale}:*`,
955
- "*:*"
956
- ];
957
- const modelKey = localeKeys.find((key) => localeModels.hasOwnProperty(key));
958
- if (modelKey) {
959
- const value = localeModels[modelKey];
960
- const firstColonIndex = value?.indexOf(":");
961
- if (value && firstColonIndex !== -1 && firstColonIndex !== void 0) {
962
- const provider2 = value.substring(0, firstColonIndex);
963
- const model2 = value.substring(firstColonIndex + 1);
964
- if (provider2 && model2) {
965
- return { provider: provider2, model: model2 };
966
- }
967
- }
968
- const [provider, model] = value?.split(":") || [];
969
- if (provider && model) {
970
- return { provider, model };
971
- }
972
- }
973
- return { provider: void 0, model: void 0 };
974
- }
975
-
976
- // src/lib/lcp/api/prompt.ts
977
- var prompt_default = (args) => {
978
- return getUserSystemPrompt(args) || getBuiltInSystemPrompt(args);
979
- };
980
- function getUserSystemPrompt(args) {
981
- const userPrompt = args.prompt?.trim()?.replace("{SOURCE_LOCALE}", args.sourceLocale)?.replace("{TARGET_LOCALE}", args.targetLocale);
982
- if (userPrompt) {
983
- console.log("\u2728 Compiler is using user-defined prompt.");
984
- return userPrompt;
985
- }
986
- return void 0;
987
- }
988
- function getBuiltInSystemPrompt(args) {
989
- return `
990
- # Identity
991
-
992
- You are an advanced AI localization engine. You do state-of-the-art localization for software products.
993
- Your task is to localize pieces of data from one locale to another locale.
994
- You always consider context, cultural nuances of source and target locales, and specific localization requirements.
995
- You replicate the meaning, intent, style, tone, and purpose of the original data.
996
-
997
- ## Setup
998
-
999
- Source language (locale code): ${args.sourceLocale}
1000
- Target language (locale code): ${args.targetLocale}
1001
-
1002
- ## Guidelines
1003
-
1004
- Follow these guidelines for translation:
1005
-
1006
- 1. Analyze the source text to understand its overall context and purpose
1007
- 2. Translate the meaning and intent rather than word-for-word translation
1008
- 3. Rephrase and restructure sentences to sound natural and fluent in the target language
1009
- 4. Adapt idiomatic expressions and cultural references for the target audience
1010
- 5. Maintain the style and tone of the source text
1011
- 6. You must produce valid UTF-8 encoded output
1012
- 7. YOU MUST ONLY PRODUCE VALID XML.
1013
-
1014
- ## Special Instructions
1015
-
1016
- Do not localize any of these technical elements:
1017
- - Variables like {variable}, {variable.key}, {data[type]}
1018
- - Expressions like <expression/>
1019
- - Functions like <function:value/>, <function:getDisplayName/>
1020
- - Elements like <element:strong>, </element:strong>, <element:LuPlus>, </element:LuPlus>, <element:LuSparkles>, </element:LuSparkles>
1021
-
1022
- Remember, you are a context-aware multilingual assistant helping international companies.
1023
- Your goal is to perform state-of-the-art localization for software products and content.
1024
- `;
1025
- }
1026
-
1027
- // src/lib/lcp/api/xml2obj.ts
1028
- import { XMLParser, XMLBuilder } from "fast-xml-parser";
1029
- import _4 from "lodash";
1030
- var TAG_OBJECT = "object";
1031
- var TAG_ARRAY = "array";
1032
- var TAG_VALUE = "value";
1033
- function _toGenericNode(value, key) {
1034
- if (_4.isArray(value)) {
1035
- const children = _4.map(value, (item) => _toGenericNode(item));
1036
- return {
1037
- [TAG_ARRAY]: {
1038
- ...key ? { key } : {},
1039
- ..._groupChildren(children)
1040
- }
1041
- };
1042
- }
1043
- if (_4.isPlainObject(value)) {
1044
- const children = _4.map(
1045
- Object.entries(value),
1046
- ([k, v]) => _toGenericNode(v, k)
1047
- );
1048
- return {
1049
- [TAG_OBJECT]: {
1050
- ...key ? { key } : {},
1051
- ..._groupChildren(children)
1052
- }
1053
- };
1054
- }
1055
- return {
1056
- [TAG_VALUE]: {
1057
- ...key ? { key } : {},
1058
- "#text": value ?? ""
1059
- }
1060
- };
1061
- }
1062
- function _groupChildren(nodes) {
1063
- return _4(nodes).groupBy((node) => Object.keys(node)[0]).mapValues((arr) => _4.map(arr, (n) => n[Object.keys(n)[0]])).value();
1064
- }
1065
- function _fromGenericNode(tag, data) {
1066
- if (tag === TAG_VALUE) {
1067
- if (_4.isPlainObject(data)) {
1068
- return _4.get(data, "#text", "");
1069
- }
1070
- return data ?? "";
1071
- }
1072
- if (tag === TAG_ARRAY) {
1073
- const result = [];
1074
- _4.forEach([TAG_VALUE, TAG_OBJECT, TAG_ARRAY], (childTag) => {
1075
- const childNodes = _4.castArray(_4.get(data, childTag, []));
1076
- _4.forEach(childNodes, (child) => {
1077
- result.push(_fromGenericNode(childTag, child));
1078
- });
1079
- });
1080
- return result;
1081
- }
1082
- const obj = {};
1083
- _4.forEach([TAG_VALUE, TAG_OBJECT, TAG_ARRAY], (childTag) => {
1084
- const childNodes = _4.castArray(_4.get(data, childTag, []));
1085
- _4.forEach(childNodes, (child) => {
1086
- const key = _4.get(child, "key", "");
1087
- obj[key] = _fromGenericNode(childTag, child);
1088
- });
1089
- });
1090
- return obj;
1091
- }
1092
- function obj2xml(obj) {
1093
- const rootNode = _toGenericNode(obj)[TAG_OBJECT];
1094
- const builder = new XMLBuilder({
1095
- ignoreAttributes: false,
1096
- attributeNamePrefix: "",
1097
- format: true,
1098
- suppressEmptyNode: true
1099
- });
1100
- return builder.build({ [TAG_OBJECT]: rootNode });
1101
- }
1102
- function xml2obj(xml) {
1103
- const parser2 = new XMLParser({
1104
- ignoreAttributes: false,
1105
- attributeNamePrefix: "",
1106
- parseTagValue: true,
1107
- parseAttributeValue: false,
1108
- processEntities: true,
1109
- isArray: (name) => [TAG_VALUE, TAG_ARRAY, TAG_OBJECT].includes(name)
1110
- });
1111
- const parsed = parser2.parse(xml);
1112
- const withoutDeclaration = _4.omit(parsed, "?xml");
1113
- const rootTag = Object.keys(withoutDeclaration)[0];
1114
- const rootNode = _4.castArray(withoutDeclaration[rootTag])[0];
1115
- return _fromGenericNode(rootTag, rootNode);
1116
- }
1117
-
1118
- // src/lib/lcp/api/shots.ts
1119
- var shots_default = [
1120
- // Shot #1
1121
- [
1122
- {
1123
- version: 0.1,
1124
- locale: "en",
1125
- files: {
1126
- "demo-app/my-custom-header.tsx": {
1127
- entries: {
1128
- "1z2x3c4v": "Dashboard",
1129
- "5t6y7u8i": "Settings",
1130
- "9o0p1q2r": "Logout"
1131
- }
1132
- },
1133
- "demo-app/my-custom-footer.tsx": {
1134
- entries: {
1135
- "9k0l1m2n": "\xA9 2025 Lingo.dev. All rights reserved."
1136
- }
1137
- }
1138
- }
1139
- },
1140
- {
1141
- version: 0.1,
1142
- locale: "es",
1143
- files: {
1144
- "demo-app/my-custom-header.tsx": {
1145
- entries: {
1146
- "1z2x3c4v": "Panel de control",
1147
- "5t6y7u8i": "Configuraci\xF3n",
1148
- "9o0p1q2r": "Cerrar sesi\xF3n"
1149
- }
1150
- },
1151
- "demo-app/my-custom-footer.tsx": {
1152
- entries: {
1153
- "9k0l1m2n": "\xA9 2025 Lingo.dev. Todos los derechos reservados."
1154
- }
1155
- }
1156
- }
1157
- }
1158
- ]
1159
- // More shots here...
1160
- ];
1161
-
1162
- // src/utils/rc.ts
1163
- import os from "os";
1164
- import path3 from "path";
1165
- import fs3 from "fs";
1166
- import Ini from "ini";
1167
- function getRc() {
1168
- const settingsFile = ".lingodotdevrc";
1169
- const homedir = os.homedir();
1170
- const settingsFilePath = path3.join(homedir, settingsFile);
1171
- const content = fs3.existsSync(settingsFilePath) ? fs3.readFileSync(settingsFilePath, "utf-8") : "";
1172
- const data = Ini.parse(content);
1173
- return data;
1174
- }
1175
-
1176
- // src/utils/llm-api-key.ts
1177
- import _5 from "lodash";
1178
- import * as dotenv from "dotenv";
1179
- import path4 from "path";
1180
- function getKeyFromEnv(envVarName) {
1181
- if (process.env[envVarName]) {
1182
- return process.env[envVarName];
1183
- }
1184
- const result = dotenv.config({
1185
- path: [
1186
- path4.resolve(process.cwd(), ".env"),
1187
- path4.resolve(process.cwd(), ".env.local"),
1188
- path4.resolve(process.cwd(), ".env.development")
1189
- ]
1190
- });
1191
- return result?.parsed?.[envVarName];
1192
- }
1193
- function getKeyFromRc(rcPath) {
1194
- const rc = getRc();
1195
- const result = _5.get(rc, rcPath);
1196
- return typeof result === "string" ? result : void 0;
1197
- }
1198
- function getGroqKey() {
1199
- return getGroqKeyFromEnv() || getGroqKeyFromRc();
1200
- }
1201
- function getGroqKeyFromRc() {
1202
- return getKeyFromRc("llm.groqApiKey");
1203
- }
1204
- function getGroqKeyFromEnv() {
1205
- return getKeyFromEnv("GROQ_API_KEY");
1206
- }
1207
- function getLingoDotDevKeyFromEnv() {
1208
- return getKeyFromEnv("LINGODOTDEV_API_KEY");
1209
- }
1210
- function getLingoDotDevKeyFromRc() {
1211
- return getKeyFromRc("auth.apiKey");
1212
- }
1213
- function getLingoDotDevKey() {
1214
- return getLingoDotDevKeyFromEnv() || getLingoDotDevKeyFromRc();
1215
- }
1216
- function getGoogleKey() {
1217
- return getGoogleKeyFromEnv() || getGoogleKeyFromRc();
1218
- }
1219
- function getGoogleKeyFromRc() {
1220
- return getKeyFromRc("llm.googleApiKey");
1221
- }
1222
- function getGoogleKeyFromEnv() {
1223
- return getKeyFromEnv("GOOGLE_API_KEY");
1224
- }
1225
- function getOpenRouterKey() {
1226
- return getOpenRouterKeyFromEnv() || getOpenRouterKeyFromRc();
1227
- }
1228
- function getOpenRouterKeyFromRc() {
1229
- return getKeyFromRc("llm.openrouterApiKey");
1230
- }
1231
- function getOpenRouterKeyFromEnv() {
1232
- return getKeyFromEnv("OPENROUTER_API_KEY");
1233
- }
1234
-
1235
- // src/lib/lcp/api/index.ts
1236
- import dedent2 from "dedent";
1237
-
1238
- // src/utils/env.ts
1239
- import fs4 from "fs";
1240
- function isRunningInCIOrDocker() {
1241
- return Boolean(process.env.CI) || fs4.existsSync("/.dockerenv");
1242
- }
1243
-
1244
- // src/lib/lcp/api/provider-details.ts
1245
- var providerDetails = {
1246
- groq: {
1247
- name: "Groq",
1248
- apiKeyEnvVar: "GROQ_API_KEY",
1249
- apiKeyConfigKey: "llm.groqApiKey",
1250
- getKeyLink: "https://groq.com",
1251
- docsLink: "https://console.groq.com/docs/errors"
1252
- },
1253
- google: {
1254
- name: "Google",
1255
- apiKeyEnvVar: "GOOGLE_API_KEY",
1256
- apiKeyConfigKey: "llm.googleApiKey",
1257
- getKeyLink: "https://ai.google.dev/",
1258
- docsLink: "https://ai.google.dev/gemini-api/docs/troubleshooting"
1259
- },
1260
- openrouter: {
1261
- name: "OpenRouter",
1262
- apiKeyEnvVar: "OPENROUTER_API_KEY",
1263
- apiKeyConfigKey: "llm.openrouterApiKey",
1264
- getKeyLink: "https://openrouter.ai",
1265
- docsLink: "https://openrouter.ai/docs"
1266
- },
1267
- ollama: {
1268
- name: "Ollama",
1269
- apiKeyEnvVar: void 0,
1270
- // Ollama doesn't require an API key
1271
- apiKeyConfigKey: void 0,
1272
- // Ollama doesn't require an API key
1273
- getKeyLink: "https://ollama.com/download",
1274
- docsLink: "https://github.com/ollama/ollama/tree/main/docs"
1275
- }
1276
- };
1277
-
1278
- // src/lib/lcp/api/index.ts
1279
- var LCPAPI = class {
1280
- static async translate(models, sourceDictionary, sourceLocale, targetLocale) {
1281
- const timeLabel = `LCPAPI.translate: ${targetLocale}`;
1282
- console.time(timeLabel);
1283
- const chunks = this._chunkDictionary(sourceDictionary);
1284
- const translatedChunks = [];
1285
- for (const chunk of chunks) {
1286
- const translatedChunk = await this._translateChunk(
1287
- models,
1288
- chunk,
1289
- sourceLocale,
1290
- targetLocale
1291
- );
1292
- translatedChunks.push(translatedChunk);
1293
- }
1294
- const result = this._mergeDictionaries(translatedChunks);
1295
- console.timeEnd(timeLabel);
1296
- return result;
1297
- }
1298
- static _chunkDictionary(dictionary) {
1299
- const MAX_ENTRIES_PER_CHUNK = 100;
1300
- const { files, ...rest } = dictionary;
1301
- const chunks = [];
1302
- let currentChunk = {
1303
- ...rest,
1304
- files: {}
1305
- };
1306
- let currentEntryCount = 0;
1307
- Object.entries(files).forEach(([fileName, file]) => {
1308
- const entries = file.entries;
1309
- const entryPairs = Object.entries(entries);
1310
- let currentIndex = 0;
1311
- while (currentIndex < entryPairs.length) {
1312
- const remainingSpace = MAX_ENTRIES_PER_CHUNK - currentEntryCount;
1313
- const entriesToAdd = entryPairs.slice(
1314
- currentIndex,
1315
- currentIndex + remainingSpace
1316
- );
1317
- if (entriesToAdd.length > 0) {
1318
- currentChunk.files[fileName] = currentChunk.files[fileName] || {
1319
- entries: {}
1320
- };
1321
- currentChunk.files[fileName].entries = {
1322
- ...currentChunk.files[fileName].entries,
1323
- ...Object.fromEntries(entriesToAdd)
1324
- };
1325
- currentEntryCount += entriesToAdd.length;
1326
- }
1327
- currentIndex += entriesToAdd.length;
1328
- if (currentEntryCount >= MAX_ENTRIES_PER_CHUNK || currentIndex < entryPairs.length && currentEntryCount + (entryPairs.length - currentIndex) > MAX_ENTRIES_PER_CHUNK) {
1329
- chunks.push(currentChunk);
1330
- currentChunk = { ...rest, files: {} };
1331
- currentEntryCount = 0;
1332
- }
1333
- }
1334
- });
1335
- if (currentEntryCount > 0) {
1336
- chunks.push(currentChunk);
1337
- }
1338
- return chunks;
1339
- }
1340
- static _mergeDictionaries(dictionaries) {
1341
- const fileNames = _6.uniq(
1342
- _6.flatMap(dictionaries, (dict) => Object.keys(dict.files))
1343
- );
1344
- const files = _6(fileNames).map((fileName) => {
1345
- const entries = dictionaries.reduce((entries2, dict) => {
1346
- const file = dict.files[fileName];
1347
- if (file) {
1348
- entries2 = _6.merge(entries2, file.entries);
1349
- }
1350
- return entries2;
1351
- }, {});
1352
- return [fileName, { entries }];
1353
- }).fromPairs().value();
1354
- const dictionary = {
1355
- version: dictionaries[0].version,
1356
- locale: dictionaries[0].locale,
1357
- files
1358
- };
1359
- return dictionary;
1360
- }
1361
- static _createLingoDotDevEngine() {
1362
- if (isRunningInCIOrDocker()) {
1363
- const apiKeyFromEnv = getLingoDotDevKeyFromEnv();
1364
- if (!apiKeyFromEnv) {
1365
- this._failMissingLLMKeyCi("lingo.dev");
1366
- }
1367
- }
1368
- const apiKey = getLingoDotDevKey();
1369
- if (!apiKey) {
1370
- throw new Error(
1371
- "\u26A0\uFE0F Lingo.dev API key not found. Please set LINGODOTDEV_API_KEY environment variable or configure it user-wide."
1372
- );
1373
- }
1374
- console.log(`Creating Lingo.dev client`);
1375
- return new LingoDotDevEngine({
1376
- apiKey
1377
- });
1378
- }
1379
- static async _translateChunk(models, sourceDictionary, sourceLocale, targetLocale) {
1380
- if (models === "lingo.dev") {
1381
- try {
1382
- const lingoDotDevEngine = this._createLingoDotDevEngine();
1383
- console.log(
1384
- `\u2728 Using Lingo.dev Engine to localize from "${sourceLocale}" to "${targetLocale}"`
1385
- );
1386
- const result = await lingoDotDevEngine.localizeObject(
1387
- sourceDictionary,
1388
- {
1389
- sourceLocale,
1390
- targetLocale
1391
- }
1392
- );
1393
- return result;
1394
- } catch (error) {
1395
- this._failLLMFailureLocal(
1396
- "lingo.dev",
1397
- targetLocale,
1398
- error instanceof Error ? error.message : "Unknown error"
1399
- );
1400
- throw error;
1401
- }
1402
- } else {
1403
- const { provider, model } = getLocaleModel(
1404
- models,
1405
- sourceLocale,
1406
- targetLocale
1407
- );
1408
- if (!provider || !model) {
1409
- throw new Error(
1410
- dedent2`
1411
- 🚫 Lingo.dev Localization Engine Not Configured!
1412
-
1413
- The "models" parameter is missing or incomplete in your Lingo.dev configuration.
1414
-
1415
- 👉 To fix this, set the "models" parameter to either:
1416
- • "lingo.dev" (for the default engine)
1417
- • a map of locale-to-model, e.g. { "models": { "en:es": "openai:gpt-3.5-turbo" } }
1418
-
1419
- Example:
1420
- {
1421
- // ...
1422
- "models": "lingo.dev"
1423
- }
1424
-
1425
- For more details, see: https://lingo.dev/compiler
1426
- To get help, join our Discord: https://lingo.dev/go/discord
1427
- `
1428
- );
1429
- }
1430
- try {
1431
- const aiModel = this._createAiModel(provider, model, targetLocale);
1432
- console.log(
1433
- `\u2139\uFE0F Using raw LLM API ("${provider}":"${model}") to translate from "${sourceLocale}" to "${targetLocale}"`
1434
- );
1435
- const response = await generateText({
1436
- model: aiModel,
1437
- messages: [
1438
- {
1439
- role: "system",
1440
- content: prompt_default({ sourceLocale, targetLocale })
1441
- },
1442
- ...shots_default.flatMap((shotsTuple) => [
1443
- {
1444
- role: "user",
1445
- content: obj2xml(shotsTuple[0])
1446
- },
1447
- {
1448
- role: "assistant",
1449
- content: obj2xml(shotsTuple[1])
1450
- }
1451
- ]),
1452
- {
1453
- role: "user",
1454
- content: obj2xml(sourceDictionary)
1455
- }
1456
- ]
1457
- });
1458
- console.log("Response text received for", targetLocale);
1459
- let responseText = response.text;
1460
- responseText = responseText.substring(
1461
- responseText.indexOf("<"),
1462
- responseText.lastIndexOf(">") + 1
1463
- );
1464
- return xml2obj(responseText);
1465
- } catch (error) {
1466
- this._failLLMFailureLocal(
1467
- provider,
1468
- targetLocale,
1469
- error instanceof Error ? error.message : "Unknown error"
1470
- );
1471
- throw error;
1472
- }
1473
- }
1474
- }
1475
- /**
1476
- * Instantiates an AI model based on provider and model ID.
1477
- * Includes CI/CD API key checks.
1478
- * @param providerId The ID of the AI provider (e.g., "groq", "google").
1479
- * @param modelId The ID of the specific model (e.g., "llama3-8b-8192", "gemini-2.0-flash").
1480
- * @param targetLocale The target locale being translated to (for logging/error messages).
1481
- * @returns An instantiated AI LanguageModel.
1482
- * @throws Error if the provider is not supported or API key is missing in CI/CD.
1483
- */
1484
- static _createAiModel(providerId, modelId, targetLocale) {
1485
- switch (providerId) {
1486
- case "groq": {
1487
- if (isRunningInCIOrDocker()) {
1488
- const groqFromEnv = getGroqKeyFromEnv();
1489
- if (!groqFromEnv) {
1490
- this._failMissingLLMKeyCi(providerId);
1491
- }
1492
- }
1493
- const groqKey = getGroqKey();
1494
- if (!groqKey) {
1495
- throw new Error(
1496
- "\u26A0\uFE0F GROQ API key not found. Please set GROQ_API_KEY environment variable or configure it user-wide."
1497
- );
1498
- }
1499
- console.log(
1500
- `Creating Groq client for ${targetLocale} using model ${modelId}`
1501
- );
1502
- return createGroq({ apiKey: groqKey })(modelId);
1503
- }
1504
- case "google": {
1505
- if (isRunningInCIOrDocker()) {
1506
- const googleFromEnv = getGoogleKeyFromEnv();
1507
- if (!googleFromEnv) {
1508
- this._failMissingLLMKeyCi(providerId);
1509
- }
1510
- }
1511
- const googleKey = getGoogleKey();
1512
- if (!googleKey) {
1513
- throw new Error(
1514
- "\u26A0\uFE0F Google API key not found. Please set GOOGLE_API_KEY environment variable or configure it user-wide."
1515
- );
1516
- }
1517
- console.log(
1518
- `Creating Google Generative AI client for ${targetLocale} using model ${modelId}`
1519
- );
1520
- return createGoogleGenerativeAI({ apiKey: googleKey })(modelId);
1521
- }
1522
- case "openrouter": {
1523
- if (isRunningInCIOrDocker()) {
1524
- const openRouterFromEnv = getOpenRouterKeyFromEnv();
1525
- if (!openRouterFromEnv) {
1526
- this._failMissingLLMKeyCi(providerId);
1527
- }
1528
- }
1529
- const openRouterKey = getOpenRouterKey();
1530
- if (!openRouterKey) {
1531
- throw new Error(
1532
- "\u26A0\uFE0F OpenRouter API key not found. Please set OPENROUTER_API_KEY environment variable or configure it user-wide."
1533
- );
1534
- }
1535
- console.log(
1536
- `Creating OpenRouter client for ${targetLocale} using model ${modelId}`
1537
- );
1538
- return createOpenRouter({
1539
- apiKey: openRouterKey
1540
- })(modelId);
1541
- }
1542
- case "ollama": {
1543
- console.log(
1544
- `Creating Ollama client for ${targetLocale} using model ${modelId} at default Ollama address`
1545
- );
1546
- return createOllama()(modelId);
1547
- }
1548
- default: {
1549
- throw new Error(
1550
- `\u26A0\uFE0F Provider "${providerId}" for locale "${targetLocale}" is not supported. Only "groq" and "google" providers are supported at the moment.`
1551
- );
1552
- }
1553
- }
1554
- }
1555
- /**
1556
- * Show an actionable error message and exit the process when the compiler
1557
- * is running in CI/CD without a required LLM API key.
1558
- * The message explains why this situation is unusual and how to fix it.
1559
- * @param providerId The ID of the LLM provider whose key is missing.
1560
- */
1561
- static _failMissingLLMKeyCi(providerId) {
1562
- let details = providerDetails[providerId];
1563
- if (!details) {
1564
- console.error(
1565
- `Internal Error: Missing details for provider "${providerId}" when reporting missing key in CI/CD. You might be using an unsupported provider.`
1566
- );
1567
- process.exit(1);
1568
- }
1569
- console.log(
1570
- dedent2`
1571
- \n
1572
- 💡 You're using Lingo.dev Localization Compiler, and it detected unlocalized components in your app.
1573
-
1574
- The compiler needs a ${details.name} API key to translate missing strings, but ${details.apiKeyEnvVar} is not set in the environment.
1575
-
1576
- This is unexpected: typically you run a full build locally, commit the generated translation files, and push them to CI/CD.
1577
-
1578
- However, If you want CI/CD to translate the new strings, provide the key with:
1579
- • Session-wide: export ${details.apiKeyEnvVar}=<your-api-key>
1580
- • Project-wide / CI: add ${details.apiKeyEnvVar}=<your-api-key> to your pipeline environment variables
1581
-
1582
- ⭐️ Also:
1583
- 1. If you don't yet have a ${details.name} API key, get one for free at ${details.getKeyLink}
1584
- 2. If you want to use a different LLM, update your configuration. Refer to documentation for help: https://lingo.dev/compiler
1585
- 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
1586
-
1587
-
1588
- `
1589
- );
1590
- process.exit(1);
1591
- }
1592
- /**
1593
- * Show an actionable error message and exit the process when an LLM API call
1594
- * fails during local compilation.
1595
- * @param providerId The ID of the LLM provider that failed.
1596
- * @param targetLocale The target locale being translated to.
1597
- * @param errorMessage The error message received from the API.
1598
- */
1599
- static _failLLMFailureLocal(providerId, targetLocale, errorMessage) {
1600
- const details = providerDetails[providerId];
1601
- if (!details) {
1602
- console.error(
1603
- `Internal Error: Missing details for provider "${providerId}" when reporting local failure.`
1604
- );
1605
- console.error(`Original Error: ${errorMessage}`);
1606
- process.exit(1);
1607
- }
1608
- const isInvalidApiKey = errorMessage.match("Invalid API Key");
1609
- if (isInvalidApiKey) {
1610
- console.log(dedent2`
1611
- \n
1612
- ⚠️ Lingo.dev Compiler requires a valid ${details.name} API key to translate your application.
1613
-
1614
- It looks like you set ${details.name} API key but it is not valid. Please check your API key and try again.
1615
-
1616
- Error details from ${details.name} API: ${errorMessage}
1617
-
1618
- 👉 You can set the API key in one of the following ways:
1619
- 1. User-wide: Run npx lingo.dev@latest config set ${details.apiKeyConfigKey} <your-api-key>
1620
- 2. Project-wide: Add ${details.apiKeyEnvVar}=<your-api-key> to .env file in every project that uses Lingo.dev Localization Compiler
1621
- 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
1622
-
1623
- ⭐️ Also:
1624
- 1. If you don't yet have a ${details.name} API key, get one for free at ${details.getKeyLink}
1625
- 2. If you want to use a different LLM, raise an issue in our open-source repo: https://lingo.dev/go/gh
1626
- 3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
1627
-
1628
-
1629
- `);
1630
- } else {
1631
- console.log(
1632
- dedent2`
1633
- \n
1634
- ⚠️ Lingo.dev Compiler tried to translate your application to "${targetLocale}" locale via ${details.name} but it failed.
1635
-
1636
- Error details from ${details.name} API: ${errorMessage}
1637
-
1638
- This error comes from the ${details.name} API, please check their documentation for more details: ${details.docsLink}
1639
-
1640
- ⭐️ Also:
1641
- 1. Did you set ${details.apiKeyEnvVar ? `${details.apiKeyEnvVar}` : "the provider API key"} environment variable correctly ${!details.apiKeyEnvVar ? "(if required)" : ""}?
1642
- 2. Did you reach any limits of your ${details.name} account?
1643
- 3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
1644
-
1645
-
1646
- `
1647
- );
1648
- }
1649
- process.exit(1);
1650
- }
1651
- };
1652
-
1653
- // src/lib/lcp/server.ts
1654
- var LCPServer = class {
1655
- static dictionariesCache = null;
1656
- static inFlightPromise = null;
1657
- static async loadDictionaries(params) {
1658
- if (this.dictionariesCache) {
1659
- return this.dictionariesCache;
1660
- }
1661
- if (this.inFlightPromise) {
1662
- return this.inFlightPromise;
1663
- }
1664
- this.inFlightPromise = (async () => {
1665
- try {
1666
- const targetLocales = _7.uniq([
1667
- ...params.targetLocales,
1668
- params.sourceLocale
1669
- ]);
1670
- const dictionaries = await Promise.all(
1671
- targetLocales.map(
1672
- (targetLocale) => this.loadDictionaryForLocale({ ...params, targetLocale })
1673
- )
1674
- );
1675
- const result = _7.fromPairs(
1676
- targetLocales.map((targetLocale, index) => [
1677
- targetLocale,
1678
- dictionaries[index]
1679
- ])
1680
- );
1681
- this.dictionariesCache = result;
1682
- return result;
1683
- } finally {
1684
- this.inFlightPromise = null;
1685
- }
1686
- })();
1687
- return this.inFlightPromise;
1688
- }
1689
- static async loadDictionaryForLocale(params) {
1690
- const sourceDictionary = this._extractSourceDictionary(
1691
- params.lcp,
1692
- params.sourceLocale,
1693
- params.targetLocale
1694
- );
1695
- const cacheParams = {
1696
- lcp: params.lcp,
1697
- sourceLocale: params.sourceLocale,
1698
- lingoDir: params.lingoDir,
1699
- sourceRoot: params.sourceRoot
1700
- };
1701
- if (this._countDictionaryEntries(sourceDictionary) === 0) {
1702
- console.log(
1703
- "Source dictionary is empty, returning empty dictionary for target locale"
1704
- );
1705
- return { ...sourceDictionary, locale: params.targetLocale };
1706
- }
1707
- const cache = LCPCache.readLocaleDictionary(
1708
- params.targetLocale,
1709
- cacheParams
1710
- );
1711
- const uncachedSourceDictionary = this._getDictionaryDiff(
1712
- sourceDictionary,
1713
- cache
1714
- );
1715
- let targetDictionary;
1716
- let newTranslations;
1717
- if (this._countDictionaryEntries(uncachedSourceDictionary) === 0) {
1718
- targetDictionary = cache;
1719
- } else if (params.targetLocale === params.sourceLocale) {
1720
- console.log(
1721
- "\u2139\uFE0F Lingo.dev returns source dictionary - source and target locales are the same"
1722
- );
1723
- await LCPCache.writeLocaleDictionary(sourceDictionary, cacheParams);
1724
- return sourceDictionary;
1725
- } else {
1726
- newTranslations = await LCPAPI.translate(
1727
- params.models,
1728
- uncachedSourceDictionary,
1729
- params.sourceLocale,
1730
- params.targetLocale
1731
- );
1732
- targetDictionary = this._mergeDictionaries(newTranslations, cache);
1733
- targetDictionary = {
1734
- ...targetDictionary,
1735
- locale: params.targetLocale
1736
- };
1737
- await LCPCache.writeLocaleDictionary(targetDictionary, cacheParams);
1738
- }
1739
- const targetDictionaryWithFallback = this._mergeDictionaries(
1740
- targetDictionary,
1741
- sourceDictionary,
1742
- true
1743
- );
1744
- const result = this._addOverridesToDictionary(
1745
- targetDictionaryWithFallback,
1746
- params.lcp,
1747
- params.targetLocale
1748
- );
1749
- if (newTranslations) {
1750
- console.log(
1751
- `\u2139\uFE0F Lingo.dev dictionary for ${params.targetLocale}:
1752
- - %d entries
1753
- - %d cached
1754
- - %d uncached
1755
- - %d translated
1756
- - %d overrides`,
1757
- this._countDictionaryEntries(result),
1758
- this._countDictionaryEntries(cache),
1759
- this._countDictionaryEntries(uncachedSourceDictionary),
1760
- newTranslations ? this._countDictionaryEntries(newTranslations) : 0,
1761
- this._countDictionaryEntries(result) - this._countDictionaryEntries(targetDictionary)
1762
- );
1763
- }
1764
- return result;
1765
- }
1766
- static _extractSourceDictionary(lcp, sourceLocale, targetLocale) {
1767
- const dictionary = {
1768
- version: 0.1,
1769
- locale: sourceLocale,
1770
- files: {}
1771
- };
1772
- for (const [fileKey, fileData] of Object.entries(lcp.files || {})) {
1773
- for (const [scopeKey, scopeData] of Object.entries(
1774
- fileData.scopes || {}
1775
- )) {
1776
- if (scopeData.skip) {
1777
- continue;
1778
- }
1779
- if (this._getScopeLocaleOverride(scopeData, targetLocale)) {
1780
- continue;
1781
- }
1782
- _7.set(
1783
- dictionary,
1784
- [
1785
- "files",
1786
- fileKey,
1787
- "entries",
1788
- scopeKey
1789
- ],
1790
- scopeData.content
1791
- );
1792
- }
1793
- }
1794
- return dictionary;
1795
- }
1796
- static _addOverridesToDictionary(dictionary, lcp, targetLocale) {
1797
- for (const [fileKey, fileData] of Object.entries(lcp.files || {})) {
1798
- for (const [scopeKey, scopeData] of Object.entries(
1799
- fileData.scopes || {}
1800
- )) {
1801
- const override = this._getScopeLocaleOverride(scopeData, targetLocale);
1802
- if (!override) {
1803
- continue;
1804
- }
1805
- _7.set(
1806
- dictionary,
1807
- [
1808
- "files",
1809
- fileKey,
1810
- "entries",
1811
- scopeKey
1812
- ],
1813
- override
1814
- );
1815
- }
1816
- }
1817
- return dictionary;
1818
- }
1819
- static _getScopeLocaleOverride(scopeData, locale) {
1820
- return _7.get(scopeData.overrides, locale) ?? null;
1821
- }
1822
- static _getDictionaryDiff(sourceDictionary, targetDictionary) {
1823
- if (this._countDictionaryEntries(targetDictionary) === 0) {
1824
- return sourceDictionary;
1825
- }
1826
- const files = _7(sourceDictionary.files).mapValues((file, fileName) => ({
1827
- ...file,
1828
- entries: _7(file.entries).mapValues((entry, entryName) => {
1829
- const targetEntry = _7.get(targetDictionary.files, [
1830
- fileName,
1831
- "entries",
1832
- entryName
1833
- ]);
1834
- if (targetEntry !== void 0) {
1835
- return void 0;
1836
- }
1837
- return entry;
1838
- }).pickBy((value) => value !== void 0).value()
1839
- })).pickBy((value) => Object.keys(value.entries).length > 0).value();
1840
- const dictionary = {
1841
- version: sourceDictionary.version,
1842
- locale: sourceDictionary.locale,
1843
- files
1844
- };
1845
- return dictionary;
1846
- }
1847
- static _mergeDictionaries(sourceDictionary, targetDictionary, removeEmptyEntries = false) {
1848
- const fileNames = _7.uniq([
1849
- ...Object.keys(sourceDictionary.files),
1850
- ...Object.keys(targetDictionary.files)
1851
- ]);
1852
- const files = _7(fileNames).map((fileName) => {
1853
- const sourceFile = _7.get(sourceDictionary.files, fileName);
1854
- const targetFile = _7.get(targetDictionary.files, fileName);
1855
- const entries = removeEmptyEntries ? _7.pickBy(
1856
- sourceFile?.entries || {},
1857
- (value) => String(value || "")?.trim?.()?.length > 0
1858
- ) : sourceFile?.entries || {};
1859
- return [
1860
- fileName,
1861
- {
1862
- ...targetFile,
1863
- entries: _7.merge({}, targetFile?.entries || {}, entries)
1864
- }
1865
- ];
1866
- }).fromPairs().value();
1867
- const dictionary = {
1868
- version: sourceDictionary.version,
1869
- locale: sourceDictionary.locale,
1870
- files
1871
- };
1872
- return dictionary;
1873
- }
1874
- static _countDictionaryEntries(dict) {
1875
- return Object.values(dict.files).reduce(
1876
- (sum, file) => sum + Object.keys(file.entries).length,
1877
- 0
1878
- );
1879
- }
1880
- };
1881
-
1882
- // src/utils/invokations.ts
1883
- import * as t11 from "@babel/types";
1884
- import traverse6 from "@babel/traverse";
1885
- function findInvokations(ast, params) {
1886
- const result = [];
1887
- traverse6(ast, {
1888
- ImportDeclaration(path7) {
1889
- if (!params.moduleName.includes(path7.node.source.value)) return;
1890
- const importNames = /* @__PURE__ */ new Map();
1891
- const specifiers = path7.node.specifiers;
1892
- specifiers.forEach((specifier) => {
1893
- if (t11.isImportSpecifier(specifier) && t11.isIdentifier(specifier.imported) && specifier.imported.name === params.functionName) {
1894
- importNames.set(specifier.local.name, true);
1895
- } else if (t11.isImportDefaultSpecifier(specifier) && params.functionName === "default") {
1896
- importNames.set(specifier.local.name, true);
1897
- } else if (t11.isImportNamespaceSpecifier(specifier)) {
1898
- importNames.set(specifier.local.name, "namespace");
1899
- }
1900
- });
1901
- collectCallExpressions(path7, importNames, result, params.functionName);
1902
- }
1903
- });
1904
- return result;
1905
- }
1906
- function collectCallExpressions(path7, importNames, result, functionName) {
1907
- const program = path7.findParent(
1908
- (p) => p.isProgram()
1909
- );
1910
- if (!program) return;
1911
- program.traverse({
1912
- CallExpression(callPath) {
1913
- const callee = callPath.node.callee;
1914
- if (t11.isIdentifier(callee) && importNames.has(callee.name)) {
1915
- result.push(callPath.node);
1916
- } else if (t11.isMemberExpression(callee) && t11.isIdentifier(callee.object) && importNames.get(callee.object.name) === "namespace" && t11.isIdentifier(callee.property) && callee.property.name === functionName) {
1917
- result.push(callPath.node);
1918
- }
1919
- }
1920
- });
1921
- }
1922
-
1923
- // src/rsc-dictionary-loader.ts
1924
- import * as t13 from "@babel/types";
1925
-
1926
- // src/_utils.ts
1927
- import path5 from "path";
1928
- var getDictionaryPath = (params) => {
1929
- const absolute = path5.resolve(
1930
- params.sourceRoot,
1931
- params.lingoDir,
1932
- LCP_DICTIONARY_FILE_NAME
1933
- );
1934
- const rel = path5.relative(params.relativeFilePath, absolute);
1935
- return rel.split(path5.sep).join(path5.posix.sep);
1936
- };
1937
-
1938
- // src/utils/create-locale-import-map.ts
1939
- import * as t12 from "@babel/types";
1940
- function createLocaleImportMap(allLocales, dictionaryPath) {
1941
- return t12.objectExpression(
1942
- allLocales.map(
1943
- (locale) => t12.objectProperty(
1944
- t12.stringLiteral(locale),
1945
- t12.arrowFunctionExpression(
1946
- [],
1947
- t12.callExpression(t12.identifier("import"), [
1948
- t12.stringLiteral(`${dictionaryPath}?locale=${locale}`)
1949
- ])
1950
- )
1951
- )
1952
- )
1953
- );
1954
- }
1955
-
1956
- // src/rsc-dictionary-loader.ts
1957
- var rscDictionaryLoaderMutation = createCodeMutation((payload) => {
1958
- const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
1959
- if (mode === "client") {
1960
- return payload;
1961
- }
1962
- const invokations = findInvokations(payload.ast, {
1963
- moduleName: ModuleId.ReactRSC,
1964
- functionName: "loadDictionary"
1965
- });
1966
- const allLocales = Array.from(
1967
- /* @__PURE__ */ new Set([payload.params.sourceLocale, ...payload.params.targetLocales])
1968
- );
1969
- for (const invokation of invokations) {
1970
- const internalDictionaryLoader = getOrCreateImport(payload.ast, {
1971
- moduleName: ModuleId.ReactRSC,
1972
- exportedName: "loadDictionary_internal"
1973
- });
1974
- if (t13.isIdentifier(invokation.callee)) {
1975
- invokation.callee.name = internalDictionaryLoader.importedName;
1976
- }
1977
- const dictionaryPath = getDictionaryPath({
1978
- sourceRoot: payload.params.sourceRoot,
1979
- lingoDir: payload.params.lingoDir,
1980
- relativeFilePath: payload.relativeFilePath
1981
- });
1982
- const localeImportMap = createLocaleImportMap(allLocales, dictionaryPath);
1983
- invokation.arguments.push(localeImportMap);
1984
- }
1985
- return payload;
1986
- });
1987
-
1988
- // src/react-router-dictionary-loader.ts
1989
- import * as t14 from "@babel/types";
1990
- var reactRouterDictionaryLoaderMutation = createCodeMutation(
1991
- (payload) => {
1992
- const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
1993
- if (mode === "server") {
1994
- return payload;
1995
- }
1996
- const invokations = findInvokations(payload.ast, {
1997
- moduleName: ModuleId.ReactRouter,
1998
- functionName: "loadDictionary"
1999
- });
2000
- const allLocales = Array.from(
2001
- /* @__PURE__ */ new Set([payload.params.sourceLocale, ...payload.params.targetLocales])
2002
- );
2003
- for (const invokation of invokations) {
2004
- const internalDictionaryLoader = getOrCreateImport(payload.ast, {
2005
- moduleName: ModuleId.ReactRouter,
2006
- exportedName: "loadDictionary_internal"
2007
- });
2008
- if (t14.isIdentifier(invokation.callee)) {
2009
- invokation.callee.name = internalDictionaryLoader.importedName;
2010
- }
2011
- const dictionaryPath = getDictionaryPath({
2012
- sourceRoot: payload.params.sourceRoot,
2013
- lingoDir: payload.params.lingoDir,
2014
- relativeFilePath: payload.relativeFilePath
2015
- });
2016
- const localeImportMap = createLocaleImportMap(allLocales, dictionaryPath);
2017
- invokation.arguments.push(localeImportMap);
2018
- }
2019
- return payload;
2020
- }
2021
- );
2022
-
2023
- // src/jsx-fragment.ts
2024
- import traverse7 from "@babel/traverse";
2025
- import * as t15 from "@babel/types";
2026
- function jsxFragmentMutation(payload) {
2027
- const { ast } = payload;
2028
- let foundFragments = false;
2029
- let fragmentImportName = null;
2030
- traverse7(ast, {
2031
- ImportDeclaration(path7) {
2032
- if (path7.node.source.value !== "react") return;
2033
- for (const specifier of path7.node.specifiers) {
2034
- if (t15.isImportSpecifier(specifier) && t15.isIdentifier(specifier.imported) && specifier.imported.name === "Fragment") {
2035
- fragmentImportName = specifier.local.name;
2036
- path7.stop();
2037
- }
2038
- }
2039
- }
2040
- });
2041
- traverse7(ast, {
2042
- JSXFragment(path7) {
2043
- foundFragments = true;
2044
- if (!fragmentImportName) {
2045
- const result = getOrCreateImport(ast, {
2046
- exportedName: "Fragment",
2047
- moduleName: ["react"]
2048
- });
2049
- fragmentImportName = result.importedName;
2050
- }
2051
- const fragmentElement = t15.jsxElement(
2052
- t15.jsxOpeningElement(t15.jsxIdentifier(fragmentImportName), [], false),
2053
- t15.jsxClosingElement(t15.jsxIdentifier(fragmentImportName)),
2054
- path7.node.children,
2055
- false
2056
- );
2057
- path7.replaceWith(fragmentElement);
2058
- }
2059
- });
2060
- return payload;
2061
- }
2062
-
2063
- // src/jsx-html-lang.ts
2064
- import traverse8 from "@babel/traverse";
2065
- import * as t16 from "@babel/types";
2066
- var jsxHtmlLangMutation = createCodeMutation((payload) => {
2067
- traverse8(payload.ast, {
2068
- JSXElement: (path7) => {
2069
- if (getJsxElementName(path7)?.toLowerCase() === "html") {
2070
- const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
2071
- const packagePath = mode === "client" ? ModuleId.ReactClient : ModuleId.ReactRSC;
2072
- const lingoHtmlComponentImport = getOrCreateImport(payload.ast, {
2073
- moduleName: packagePath,
2074
- exportedName: "LingoHtmlComponent"
2075
- });
2076
- path7.node.openingElement.name = t16.jsxIdentifier(
2077
- lingoHtmlComponentImport.importedName
2078
- );
2079
- if (path7.node.closingElement) {
2080
- path7.node.closingElement.name = t16.jsxIdentifier(
2081
- lingoHtmlComponentImport.importedName
2082
- );
2083
- }
2084
- path7.skip();
2085
- }
2086
- }
2087
- });
2088
- return payload;
2089
- });
2090
-
2091
- // src/utils/hash.ts
2092
- import { MD5 } from "object-hash";
2093
- function getJsxElementHash(nodePath) {
2094
- if (!nodePath.node) {
2095
- return "";
2096
- }
2097
- const content = nodePath.node.children.map((child) => child.value).join("");
2098
- const result = MD5(content);
2099
- return result;
2100
- }
2101
- function getJsxAttributeValueHash(attributeValue) {
2102
- if (!attributeValue) {
2103
- return "";
2104
- }
2105
- const result = MD5(attributeValue);
2106
- return result;
2107
- }
2108
-
2109
- // src/jsx-attribute-scopes-export.ts
2110
- import _8 from "lodash";
2111
- function jsxAttributeScopesExportMutation(payload) {
2112
- const attributeScopes = collectJsxAttributeScopes(payload.ast);
2113
- if (_8.isEmpty(attributeScopes)) {
2114
- return payload;
2115
- }
2116
- const lcp = LCP.getInstance({
2117
- sourceRoot: payload.params.sourceRoot,
2118
- lingoDir: payload.params.lingoDir
2119
- });
2120
- for (const [scope, attributes] of attributeScopes) {
2121
- for (const attributeDefinition of attributes) {
2122
- const [attribute, scopeKey] = attributeDefinition.split(":");
2123
- lcp.resetScope(payload.relativeFilePath, scopeKey);
2124
- const attributeValue = getJsxAttributeValue(scope, attribute);
2125
- if (!attributeValue) {
2126
- continue;
2127
- }
2128
- lcp.setScopeType(payload.relativeFilePath, scopeKey, "attribute");
2129
- const hash = getJsxAttributeValueHash(String(attributeValue));
2130
- lcp.setScopeHash(payload.relativeFilePath, scopeKey, hash);
2131
- lcp.setScopeContext(payload.relativeFilePath, scopeKey, "");
2132
- lcp.setScopeSkip(payload.relativeFilePath, scopeKey, false);
2133
- lcp.setScopeOverrides(payload.relativeFilePath, scopeKey, {});
2134
- lcp.setScopeContent(
2135
- payload.relativeFilePath,
2136
- scopeKey,
2137
- String(attributeValue)
2138
- );
2139
- }
2140
- }
2141
- lcp.save();
2142
- return payload;
2143
- }
2144
-
2145
- // src/jsx-scopes-export.ts
2146
- import _10 from "lodash";
2147
-
2148
- // src/utils/jsx-content.ts
2149
- import * as t17 from "@babel/types";
2150
- import _9 from "lodash";
2151
- var WHITESPACE_PLACEHOLDER = "[lingo-whitespace-placeholder]";
2152
- function extractJsxContent(nodePath, replaceWhitespacePlaceholders = true) {
2153
- const chunks = [];
2154
- nodePath.traverse({
2155
- JSXElement(path7) {
2156
- if (path7.parent === nodePath.node) {
2157
- const content = extractJsxContent(path7, false);
2158
- const name = getJsxElementName(path7);
2159
- chunks.push(`<element:${name}>${content}</element:${name}>`);
2160
- path7.skip();
2161
- }
2162
- },
2163
- JSXText(path7) {
2164
- chunks.push(path7.node.value);
2165
- },
2166
- JSXExpressionContainer(path7) {
2167
- if (path7.parent !== nodePath.node) {
2168
- return;
2169
- }
2170
- const expr = path7.node.expression;
2171
- if (t17.isCallExpression(expr)) {
2172
- let key = "";
2173
- if (t17.isIdentifier(expr.callee)) {
2174
- key = `${expr.callee.name}`;
2175
- } else if (t17.isMemberExpression(expr.callee)) {
2176
- let firstCallee = expr.callee;
2177
- while (t17.isMemberExpression(firstCallee) && t17.isCallExpression(firstCallee.object)) {
2178
- firstCallee = firstCallee.object.callee;
2179
- }
2180
- let current = firstCallee;
2181
- const parts = [];
2182
- while (t17.isMemberExpression(current)) {
2183
- if (t17.isIdentifier(current.property)) {
2184
- parts.unshift(current.property.name);
2185
- }
2186
- current = current.object;
2187
- }
2188
- if (t17.isIdentifier(current)) {
2189
- parts.unshift(current.name);
2190
- }
2191
- if (t17.isMemberExpression(firstCallee) && t17.isNewExpression(firstCallee.object) && t17.isIdentifier(firstCallee.object.callee)) {
2192
- parts.unshift(firstCallee.object.callee.name);
2193
- }
2194
- key = parts.join(".");
2195
- }
2196
- chunks.push(`<function:${key}/>`);
2197
- } else if (t17.isIdentifier(expr)) {
2198
- chunks.push(`{${expr.name}}`);
2199
- } else if (t17.isMemberExpression(expr)) {
2200
- let current = expr;
2201
- const parts = [];
2202
- while (t17.isMemberExpression(current)) {
2203
- if (t17.isIdentifier(current.property)) {
2204
- if (current.computed) {
2205
- parts.unshift(`[${current.property.name}]`);
2206
- } else {
2207
- parts.unshift(current.property.name);
2208
- }
2209
- }
2210
- current = current.object;
2211
- }
2212
- if (t17.isIdentifier(current)) {
2213
- parts.unshift(current.name);
2214
- chunks.push(`{${parts.join(".").replaceAll(".[", "[")}}`);
2215
- }
2216
- } else if (isWhitespace(path7)) {
2217
- chunks.push(WHITESPACE_PLACEHOLDER);
2218
- } else if (isExpression2(path7)) {
2219
- chunks.push("<expression/>");
2220
- }
2221
- path7.skip();
2222
- }
2223
- });
2224
- const result = chunks.join("");
2225
- const normalized = normalizeJsxWhitespace(result);
2226
- if (replaceWhitespacePlaceholders) {
2227
- return normalized.replaceAll(WHITESPACE_PLACEHOLDER, " ");
2228
- }
2229
- return normalized;
2230
- }
2231
- var compilerProps = ["data-jsx-attribute-scope", "data-jsx-scope"];
2232
- function isExpression2(nodePath) {
2233
- const isCompilerExpression = !_9.isArray(nodePath.container) && t17.isJSXAttribute(nodePath.container) && t17.isJSXIdentifier(nodePath.container.name) && compilerProps.includes(nodePath.container.name.name);
2234
- return !isCompilerExpression && !t17.isJSXEmptyExpression(nodePath.node.expression);
2235
- }
2236
- function isWhitespace(nodePath) {
2237
- const expr = nodePath.node.expression;
2238
- return t17.isStringLiteral(expr) && expr.value === " ";
2239
- }
2240
- function normalizeJsxWhitespace(input) {
2241
- const lines = input.split("\n");
2242
- let result = "";
2243
- for (let i = 0; i < lines.length; i++) {
2244
- const line = lines[i].trim();
2245
- if (line === "") continue;
2246
- if (i > 0 && (line.startsWith("<element:") || line.startsWith("<function:") || line.startsWith("{") || line.startsWith("<expression/>"))) {
2247
- result += line;
2248
- } else {
2249
- if (result && !result.endsWith(" ")) result += " ";
2250
- result += line;
2251
- }
2252
- }
2253
- result = result.replace(/\s+/g, " ");
2254
- return result.trim();
2255
- }
2256
-
2257
- // src/jsx-scopes-export.ts
2258
- function jsxScopesExportMutation(payload) {
2259
- const scopes = collectJsxScopes(payload.ast);
2260
- if (_10.isEmpty(scopes)) {
2261
- return payload;
2262
- }
2263
- const lcp = LCP.getInstance({
2264
- sourceRoot: payload.params.sourceRoot,
2265
- lingoDir: payload.params.lingoDir
2266
- });
2267
- for (const scope of scopes) {
2268
- const scopeKey = getAstKey(scope);
2269
- lcp.resetScope(payload.relativeFilePath, scopeKey);
2270
- lcp.setScopeType(payload.relativeFilePath, scopeKey, "element");
2271
- const hash = getJsxElementHash(scope);
2272
- lcp.setScopeHash(payload.relativeFilePath, scopeKey, hash);
2273
- const context = getJsxAttributeValue(scope, "data-lingo-context");
2274
- lcp.setScopeContext(
2275
- payload.relativeFilePath,
2276
- scopeKey,
2277
- String(context || "")
2278
- );
2279
- const skip = getJsxAttributeValue(scope, "data-lingo-skip");
2280
- lcp.setScopeSkip(
2281
- payload.relativeFilePath,
2282
- scopeKey,
2283
- Boolean(skip || false)
2284
- );
2285
- const attributesMap = getJsxAttributesMap(scope);
2286
- const overrides = _10.chain(attributesMap).entries().filter(
2287
- ([attributeKey]) => attributeKey.startsWith("data-lingo-override-")
2288
- ).map(([k, v]) => [k.split("data-lingo-override-")[1], v]).filter(([k]) => !!k).filter(([, v]) => !!v).fromPairs().value();
2289
- lcp.setScopeOverrides(payload.relativeFilePath, scopeKey, overrides);
2290
- const content = extractJsxContent(scope);
2291
- lcp.setScopeContent(payload.relativeFilePath, scopeKey, content);
2292
- }
2293
- lcp.save();
2294
- return payload;
2295
- }
2296
-
2297
- // src/jsx-attribute-scope-inject.ts
2298
- import * as t18 from "@babel/types";
2299
- var lingoJsxAttributeScopeInjectMutation = createCodeMutation(
2300
- (payload) => {
2301
- const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
2302
- const jsxAttributeScopes = collectJsxAttributeScopes(payload.ast);
2303
- for (const [jsxScope, attributes] of jsxAttributeScopes) {
2304
- const packagePath = mode === "client" ? ModuleId.ReactClient : ModuleId.ReactRSC;
2305
- const lingoComponentImport = getOrCreateImport(payload.ast, {
2306
- moduleName: packagePath,
2307
- exportedName: "LingoAttributeComponent"
2308
- });
2309
- const originalJsxElementName = getJsxElementName(jsxScope);
2310
- if (!originalJsxElementName) {
2311
- continue;
2312
- }
2313
- jsxScope.node.openingElement.name = t18.jsxIdentifier(
2314
- lingoComponentImport.importedName
2315
- );
2316
- if (jsxScope.node.closingElement) {
2317
- jsxScope.node.closingElement.name = t18.jsxIdentifier(
2318
- lingoComponentImport.importedName
2319
- );
2320
- }
2321
- const as = /^[A-Z]/.test(originalJsxElementName) ? t18.jsxExpressionContainer(t18.identifier(originalJsxElementName)) : t18.stringLiteral(originalJsxElementName);
2322
- jsxScope.node.openingElement.attributes.push(
2323
- t18.jsxAttribute(t18.jsxIdentifier("$attrAs"), as)
2324
- );
2325
- jsxScope.node.openingElement.attributes.push(
2326
- t18.jsxAttribute(
2327
- t18.jsxIdentifier("$fileKey"),
2328
- t18.stringLiteral(payload.relativeFilePath)
2329
- )
2330
- );
2331
- jsxScope.node.openingElement.attributes.push(
2332
- t18.jsxAttribute(
2333
- t18.jsxIdentifier("$attributes"),
2334
- t18.jsxExpressionContainer(
2335
- t18.objectExpression(
2336
- attributes.map((attributeDefinition) => {
2337
- const [attribute, key = ""] = attributeDefinition.split(":");
2338
- return t18.objectProperty(
2339
- t18.stringLiteral(attribute),
2340
- t18.stringLiteral(key)
2341
- );
2342
- })
2343
- )
2344
- )
2345
- )
2346
- );
2347
- if (mode === "server") {
2348
- const loadDictionaryImport = getOrCreateImport(payload.ast, {
2349
- exportedName: "loadDictionary",
2350
- moduleName: ModuleId.ReactRSC
2351
- });
2352
- jsxScope.node.openingElement.attributes.push(
2353
- t18.jsxAttribute(
2354
- t18.jsxIdentifier("$loadDictionary"),
2355
- t18.jsxExpressionContainer(
2356
- t18.arrowFunctionExpression(
2357
- [t18.identifier("locale")],
2358
- t18.callExpression(
2359
- t18.identifier(loadDictionaryImport.importedName),
2360
- [t18.identifier("locale")]
2361
- )
2362
- )
2363
- )
2364
- )
2365
- );
2366
- }
2367
- }
2368
- return payload;
2369
- }
2370
- );
2371
-
2372
- // src/jsx-scope-inject.ts
2373
- import * as t22 from "@babel/types";
2374
-
2375
- // src/utils/jsx-variables.ts
2376
- import * as t19 from "@babel/types";
2377
- var getJsxVariables = (nodePath) => {
2378
- const variables = /* @__PURE__ */ new Set();
2379
- nodePath.traverse({
2380
- JSXOpeningElement(path7) {
2381
- path7.skip();
2382
- },
2383
- JSXExpressionContainer(path7) {
2384
- if (t19.isIdentifier(path7.node.expression)) {
2385
- variables.add(path7.node.expression.name);
2386
- } else if (t19.isMemberExpression(path7.node.expression)) {
2387
- let current = path7.node.expression;
2388
- const parts = [];
2389
- while (t19.isMemberExpression(current)) {
2390
- if (t19.isIdentifier(current.property)) {
2391
- if (current.computed) {
2392
- parts.unshift(`[${current.property.name}]`);
2393
- } else {
2394
- parts.unshift(current.property.name);
2395
- }
2396
- }
2397
- current = current.object;
2398
- }
2399
- if (t19.isIdentifier(current)) {
2400
- parts.unshift(current.name);
2401
- variables.add(parts.join(".").replaceAll(".[", "["));
2402
- }
2403
- }
2404
- path7.skip();
2405
- }
2406
- });
2407
- const properties = Array.from(variables).map(
2408
- (name) => t19.objectProperty(t19.stringLiteral(name), t19.identifier(name))
2409
- );
2410
- const result = t19.objectExpression(properties);
2411
- return result;
2412
- };
1
+ import {
2
+ LCPCache,
3
+ LCP_DICTIONARY_FILE_NAME,
4
+ __require,
5
+ defaultParams,
6
+ getGoogleKeyFromEnv,
7
+ getGoogleKeyFromRc,
8
+ getGroqKeyFromEnv,
9
+ getGroqKeyFromRc,
10
+ getInvalidLocales,
11
+ getLingoDotDevKeyFromEnv,
12
+ getLingoDotDevKeyFromRc,
13
+ isRunningInCIOrDocker,
14
+ loadDictionary,
15
+ providerDetails,
16
+ transformComponent
17
+ } from "./chunk-YT6JFNHK.mjs";
2413
18
 
2414
- // src/utils/jsx-functions.ts
2415
- import * as t20 from "@babel/types";
2416
- var getJsxFunctions = (nodePath) => {
2417
- const functions = /* @__PURE__ */ new Map();
2418
- let fnCounter = 0;
2419
- nodePath.traverse({
2420
- JSXOpeningElement(path7) {
2421
- path7.skip();
2422
- },
2423
- JSXExpressionContainer(path7) {
2424
- if (t20.isCallExpression(path7.node.expression)) {
2425
- let key = "";
2426
- if (t20.isIdentifier(path7.node.expression.callee)) {
2427
- key = `${path7.node.expression.callee.name}`;
2428
- } else if (t20.isMemberExpression(path7.node.expression.callee)) {
2429
- let firstCallee = path7.node.expression.callee;
2430
- while (t20.isMemberExpression(firstCallee) && t20.isCallExpression(firstCallee.object)) {
2431
- firstCallee = firstCallee.object.callee;
2432
- }
2433
- let current = firstCallee;
2434
- const parts = [];
2435
- while (t20.isMemberExpression(current)) {
2436
- if (t20.isIdentifier(current.property)) {
2437
- parts.unshift(current.property.name);
2438
- }
2439
- current = current.object;
2440
- }
2441
- if (t20.isIdentifier(current)) {
2442
- parts.unshift(current.name);
2443
- }
2444
- if (t20.isMemberExpression(firstCallee) && t20.isNewExpression(firstCallee.object) && t20.isIdentifier(firstCallee.object.callee)) {
2445
- parts.unshift(firstCallee.object.callee.name);
2446
- }
2447
- key = parts.join(".");
2448
- }
2449
- const existing = functions.get(key) ?? [];
2450
- functions.set(key, [...existing, path7.node.expression]);
2451
- fnCounter++;
2452
- }
2453
- path7.skip();
2454
- }
2455
- });
2456
- const properties = Array.from(functions.entries()).map(
2457
- ([name, callExpr]) => t20.objectProperty(t20.stringLiteral(name), t20.arrayExpression(callExpr))
2458
- );
2459
- return t20.objectExpression(properties);
2460
- };
19
+ // src/index.ts
20
+ import { createUnplugin } from "unplugin";
2461
21
 
2462
- // src/utils/jsx-expressions.ts
2463
- import * as t21 from "@babel/types";
2464
- var getJsxExpressions = (nodePath) => {
2465
- const expressions = [];
2466
- nodePath.traverse({
2467
- JSXOpeningElement(path7) {
2468
- path7.skip();
2469
- },
2470
- JSXExpressionContainer(path7) {
2471
- const expr = path7.node.expression;
2472
- if (!t21.isJSXEmptyExpression(expr) && !t21.isIdentifier(expr) && !t21.isMemberExpression(expr) && !t21.isCallExpression(expr) && !(t21.isStringLiteral(expr) && expr.value === " ")) {
2473
- expressions.push(expr);
2474
- }
2475
- path7.skip();
2476
- }
2477
- });
2478
- return t21.arrayExpression(expressions);
22
+ // package.json
23
+ var package_default = {
24
+ name: "@lingo.dev/_compiler",
25
+ version: "0.4.0",
26
+ description: "Lingo.dev Compiler",
27
+ private: false,
28
+ publishConfig: {
29
+ access: "public"
30
+ },
31
+ sideEffects: false,
32
+ type: "module",
33
+ main: "build/index.cjs",
34
+ types: "build/index.d.ts",
35
+ module: "build/index.mjs",
36
+ files: [
37
+ "build"
38
+ ],
39
+ scripts: {
40
+ dev: "tsup --watch",
41
+ build: "tsc --noEmit && tsup",
42
+ clean: "rm -rf build",
43
+ test: "vitest --run",
44
+ "test:watch": "vitest -w"
45
+ },
46
+ keywords: [],
47
+ author: "",
48
+ license: "ISC",
49
+ devDependencies: {
50
+ "@types/babel__generator": "^7.6.8",
51
+ "@types/babel__traverse": "^7.20.6",
52
+ "@types/ini": "^4.1.1",
53
+ "@types/lodash": "^4.17.4",
54
+ "@types/object-hash": "^3.0.6",
55
+ "@types/react": "^18.3.18",
56
+ next: "15.2.4",
57
+ tsup: "^8.3.5",
58
+ typescript: "^5.4.5"
59
+ },
60
+ dependencies: {
61
+ "@ai-sdk/google": "^1.2.19",
62
+ "@ai-sdk/groq": "^1.2.3",
63
+ "@babel/generator": "^7.26.5",
64
+ "@babel/parser": "^7.26.7",
65
+ "@babel/traverse": "^7.27.4",
66
+ "@babel/types": "^7.26.7",
67
+ "@lingo.dev/_sdk": "workspace:*",
68
+ "@openrouter/ai-sdk-provider": "^0.7.1",
69
+ ai: "^4.2.10",
70
+ dedent: "^1.6.0",
71
+ dotenv: "^16.4.5",
72
+ "fast-xml-parser": "^5.0.8",
73
+ ini: "^5.0.0",
74
+ lodash: "^4.17.21",
75
+ "object-hash": "^3.0.0",
76
+ "ollama-ai-provider": "^1.2.0",
77
+ prettier: "^3.4.2",
78
+ unplugin: "^2.1.2",
79
+ vitest: "^2.1.4",
80
+ zod: "^3.24.1"
81
+ },
82
+ packageManager: "pnpm@9.12.3"
2479
83
  };
2480
84
 
2481
- // src/jsx-scope-inject.ts
2482
- var lingoJsxScopeInjectMutation = createCodeMutation((payload) => {
2483
- const mode = getModuleExecutionMode(payload.ast, payload.params.rsc);
2484
- const jsxScopes = collectJsxScopes(payload.ast);
2485
- for (const jsxScope of jsxScopes) {
2486
- const skip = getJsxAttributeValue(jsxScope, "data-lingo-skip");
2487
- if (skip) {
2488
- continue;
2489
- }
2490
- const packagePath = mode === "client" ? ModuleId.ReactClient : ModuleId.ReactRSC;
2491
- const lingoComponentImport = getOrCreateImport(payload.ast, {
2492
- moduleName: packagePath,
2493
- exportedName: "LingoComponent"
2494
- });
2495
- const originalJsxElementName = getJsxElementName(jsxScope);
2496
- if (!originalJsxElementName) {
2497
- continue;
2498
- }
2499
- const originalAttributes = jsxScope.node.openingElement.attributes.slice();
2500
- const as = /^[A-Z]/.test(originalJsxElementName) ? t22.jsxExpressionContainer(t22.identifier(originalJsxElementName)) : t22.stringLiteral(originalJsxElementName);
2501
- originalAttributes.push(t22.jsxAttribute(t22.jsxIdentifier("$as"), as));
2502
- originalAttributes.push(
2503
- t22.jsxAttribute(
2504
- t22.jsxIdentifier("$fileKey"),
2505
- t22.stringLiteral(payload.relativeFilePath)
2506
- )
2507
- );
2508
- originalAttributes.push(
2509
- t22.jsxAttribute(
2510
- t22.jsxIdentifier("$entryKey"),
2511
- t22.stringLiteral(getJsxScopeAttribute(jsxScope))
2512
- )
2513
- );
2514
- const $variables = getJsxVariables(jsxScope);
2515
- if ($variables.properties.length > 0) {
2516
- originalAttributes.push(
2517
- t22.jsxAttribute(
2518
- t22.jsxIdentifier("$variables"),
2519
- t22.jsxExpressionContainer($variables)
2520
- )
2521
- );
2522
- }
2523
- const $elements = getNestedJsxElements(jsxScope);
2524
- if ($elements.elements.length > 0) {
2525
- originalAttributes.push(
2526
- t22.jsxAttribute(
2527
- t22.jsxIdentifier("$elements"),
2528
- t22.jsxExpressionContainer($elements)
2529
- )
2530
- );
2531
- }
2532
- const $functions = getJsxFunctions(jsxScope);
2533
- if ($functions.properties.length > 0) {
2534
- originalAttributes.push(
2535
- t22.jsxAttribute(
2536
- t22.jsxIdentifier("$functions"),
2537
- t22.jsxExpressionContainer($functions)
2538
- )
2539
- );
2540
- }
2541
- const $expressions = getJsxExpressions(jsxScope);
2542
- if ($expressions.elements.length > 0) {
2543
- originalAttributes.push(
2544
- t22.jsxAttribute(
2545
- t22.jsxIdentifier("$expressions"),
2546
- t22.jsxExpressionContainer($expressions)
2547
- )
2548
- );
2549
- }
2550
- if (mode === "server") {
2551
- const loadDictionaryImport = getOrCreateImport(payload.ast, {
2552
- exportedName: "loadDictionary",
2553
- moduleName: ModuleId.ReactRSC
2554
- });
2555
- originalAttributes.push(
2556
- t22.jsxAttribute(
2557
- t22.jsxIdentifier("$loadDictionary"),
2558
- t22.jsxExpressionContainer(
2559
- t22.arrowFunctionExpression(
2560
- [t22.identifier("locale")],
2561
- t22.callExpression(
2562
- t22.identifier(loadDictionaryImport.importedName),
2563
- [t22.identifier("locale")]
2564
- )
2565
- )
2566
- )
2567
- )
2568
- );
2569
- }
2570
- const newNode = t22.jsxElement(
2571
- t22.jsxOpeningElement(
2572
- t22.jsxIdentifier(lingoComponentImport.importedName),
2573
- originalAttributes,
2574
- true
2575
- // selfClosing
2576
- ),
2577
- null,
2578
- // no closing element
2579
- [],
2580
- // no children
2581
- true
2582
- // selfClosing
2583
- );
2584
- jsxScope.replaceWith(newNode);
2585
- }
2586
- return payload;
2587
- });
2588
-
2589
- // src/jsx-remove-attributes.ts
2590
- import * as t23 from "@babel/types";
2591
- import traverse9 from "@babel/traverse";
2592
- var jsxRemoveAttributesMutation = createCodeMutation(
2593
- (payload) => {
2594
- const ATTRIBUTES_TO_REMOVE = [
2595
- "data-jsx-root",
2596
- "data-jsx-scope",
2597
- "data-jsx-attribute-scope"
2598
- ];
2599
- traverse9(payload.ast, {
2600
- JSXElement(path7) {
2601
- const openingElement = path7.node.openingElement;
2602
- openingElement.attributes = openingElement.attributes.filter((attr) => {
2603
- const removeAttr = t23.isJSXAttribute(attr) && t23.isJSXIdentifier(attr.name) && ATTRIBUTES_TO_REMOVE.includes(attr.name.name);
2604
- return !removeAttr;
2605
- });
2606
- }
2607
- });
2608
- return {
2609
- ...payload
2610
- };
2611
- }
2612
- );
2613
-
2614
- // src/client-dictionary-loader.ts
2615
- import * as t24 from "@babel/types";
2616
- var clientDictionaryLoaderMutation = createCodeMutation((payload) => {
2617
- const invokations = findInvokations(payload.ast, {
2618
- moduleName: ModuleId.ReactClient,
2619
- functionName: "loadDictionary"
2620
- });
2621
- const allLocales = Array.from(
2622
- /* @__PURE__ */ new Set([payload.params.sourceLocale, ...payload.params.targetLocales])
2623
- );
2624
- for (const invokation of invokations) {
2625
- const internalDictionaryLoader = getOrCreateImport(payload.ast, {
2626
- moduleName: ModuleId.ReactClient,
2627
- exportedName: "loadDictionary_internal"
2628
- });
2629
- if (t24.isIdentifier(invokation.callee)) {
2630
- invokation.callee.name = internalDictionaryLoader.importedName;
2631
- }
2632
- const dictionaryPath = getDictionaryPath({
2633
- sourceRoot: payload.params.sourceRoot,
2634
- lingoDir: payload.params.lingoDir,
2635
- relativeFilePath: payload.relativeFilePath
2636
- });
2637
- const localeImportMap = createLocaleImportMap(allLocales, dictionaryPath);
2638
- invokation.arguments.push(localeImportMap);
2639
- }
2640
- return payload;
2641
- });
2642
-
2643
85
  // src/index.ts
86
+ import _ from "lodash";
87
+ import dedent from "dedent";
2644
88
  var keyCheckers = {
2645
89
  groq: {
2646
90
  checkEnv: getGroqKeyFromEnv,
@@ -2658,7 +102,7 @@ var keyCheckers = {
2658
102
  var unplugin = createUnplugin(
2659
103
  (_params, _meta) => {
2660
104
  console.log("\u2139\uFE0F Starting Lingo.dev compiler...");
2661
- const params = _11.defaults(_params, defaultParams);
105
+ const params = _.defaults(_params, defaultParams);
2662
106
  if (!isRunningInCIOrDocker()) {
2663
107
  if (params.models === "lingo.dev") {
2664
108
  validateLLMKeyDetails(["lingo.dev"]);
@@ -2671,15 +115,15 @@ var unplugin = createUnplugin(
2671
115
  params.targetLocales
2672
116
  );
2673
117
  if (invalidLocales.length > 0) {
2674
- console.log(dedent3`
118
+ console.log(dedent`
2675
119
  \n
2676
120
  ⚠️ Lingo.dev Localization Compiler requires LLM model setup for the following locales: ${invalidLocales.join(", ")}.
2677
-
121
+
2678
122
  ⭐️ Next steps:
2679
123
  1. Refer to documentation for help: https://lingo.dev/compiler
2680
124
  2. If you want to use a different LLM, raise an issue in our open-source repo: https://lingo.dev/go/gh
2681
125
  3. If you have questions, feature requests, or would like to contribute, join our Discord: https://lingo.dev/go/discord
2682
-
126
+
2683
127
 
2684
128
  `);
2685
129
  process.exit(1);
@@ -2695,23 +139,22 @@ var unplugin = createUnplugin(
2695
139
  name: package_default.name,
2696
140
  loadInclude: (id) => !!id.match(LCP_DICTIONARY_FILE_NAME),
2697
141
  async load(id) {
2698
- const moduleInfo = parseParametrizedModuleId(id);
2699
- const lcpParams = {
142
+ const dictionary = await loadDictionary({
143
+ resourcePath: id,
144
+ resourceQuery: "",
145
+ params: {
146
+ ...params,
147
+ models: params.models,
148
+ sourceLocale: params.sourceLocale,
149
+ targetLocales: params.targetLocales
150
+ },
2700
151
  sourceRoot: params.sourceRoot,
2701
152
  lingoDir: params.lingoDir,
2702
153
  isDev
2703
- };
2704
- await LCP.ready(lcpParams);
2705
- const lcp = LCP.getInstance(lcpParams);
2706
- const dictionaries = await LCPServer.loadDictionaries({
2707
- models: params.models,
2708
- lcp: lcp.data,
2709
- sourceLocale: params.sourceLocale,
2710
- targetLocales: params.targetLocales,
2711
- sourceRoot: params.sourceRoot,
2712
- lingoDir: params.lingoDir
2713
154
  });
2714
- const dictionary = dictionaries[moduleInfo.params.locale];
155
+ if (!dictionary) {
156
+ return null;
157
+ }
2715
158
  console.log(JSON.stringify(dictionary, null, 2));
2716
159
  return {
2717
160
  code: `export default ${JSON.stringify(dictionary, null, 2)}`
@@ -2721,36 +164,12 @@ var unplugin = createUnplugin(
2721
164
  enforce: "pre",
2722
165
  transform(code, id) {
2723
166
  try {
2724
- const result = _11.chain({
167
+ const result = transformComponent({
2725
168
  code,
2726
169
  params,
2727
- relativeFilePath: path6.relative(path6.resolve(process.cwd(), params.sourceRoot), id).split(path6.sep).join("/")
2728
- // Always normalize for consistent dictionaries
2729
- }).thru(createPayload).thru(
2730
- composeMutations(
2731
- i18n_directive_default,
2732
- jsxFragmentMutation,
2733
- jsx_attribute_flag_default,
2734
- // log here to see transformedfiles
2735
- // (input) => {
2736
- // console.log(`transform ${id}`);
2737
- // return input;
2738
- // },
2739
- jsx_provider_default,
2740
- jsxHtmlLangMutation,
2741
- jsx_root_flag_default,
2742
- jsx_scope_flag_default,
2743
- jsx_attribute_flag_default,
2744
- jsxAttributeScopesExportMutation,
2745
- jsxScopesExportMutation,
2746
- lingoJsxAttributeScopeInjectMutation,
2747
- lingoJsxScopeInjectMutation,
2748
- rscDictionaryLoaderMutation,
2749
- reactRouterDictionaryLoaderMutation,
2750
- jsxRemoveAttributesMutation,
2751
- clientDictionaryLoaderMutation
2752
- )
2753
- ).thru(createOutput).value();
170
+ resourcePath: id,
171
+ sourceRoot: params.sourceRoot
172
+ });
2754
173
  return result;
2755
174
  } catch (error) {
2756
175
  console.error("\u26A0\uFE0F Lingo.dev compiler failed to localize your app");
@@ -2762,27 +181,79 @@ var unplugin = createUnplugin(
2762
181
  }
2763
182
  );
2764
183
  var src_default = {
2765
- next: (compilerParams) => (nextConfig) => ({
2766
- ...nextConfig,
2767
- // what if we already have a webpack config?
2768
- webpack: (config2, { isServer }) => {
2769
- config2.plugins.unshift(
2770
- unplugin.webpack(
2771
- _11.merge({}, defaultParams, { rsc: true }, compilerParams)
2772
- )
184
+ next: (compilerParams) => (nextConfig = {}) => {
185
+ const mergedParams = _.merge(
186
+ {},
187
+ defaultParams,
188
+ {
189
+ rsc: true,
190
+ turbopack: {
191
+ enabled: "auto",
192
+ useLegacyTurbo: false
193
+ }
194
+ },
195
+ compilerParams
196
+ );
197
+ let turbopackEnabled;
198
+ if (mergedParams.turbopack?.enabled === "auto") {
199
+ turbopackEnabled = process.env.TURBOPACK === "1" || process.env.TURBOPACK === "true";
200
+ } else {
201
+ turbopackEnabled = mergedParams.turbopack?.enabled === true;
202
+ }
203
+ const supportLegacyTurbo = mergedParams.turbopack?.useLegacyTurbo === true;
204
+ const hasWebpackConfig = typeof nextConfig.webpack === "function";
205
+ const hasTurbopackConfig = typeof nextConfig.turbopack === "function";
206
+ if (hasWebpackConfig && turbopackEnabled) {
207
+ console.warn(
208
+ "\u26A0\uFE0F Turbopack is enabled in the Lingo.dev compiler, but you have webpack config. Lingo.dev will still apply turbopack configuration."
2773
209
  );
2774
- return config2;
2775
210
  }
2776
- }),
2777
- vite: (compilerParams) => (config2) => {
2778
- config2.plugins.unshift(
2779
- unplugin.vite(_11.merge({}, defaultParams, { rsc: false }, compilerParams))
211
+ if (hasTurbopackConfig && !turbopackEnabled) {
212
+ console.warn(
213
+ "\u26A0\uFE0F Turbopack is disabled in the Lingo.dev compiler, but you have turbopack config. Lingo.dev will not apply turbopack configuration."
214
+ );
215
+ }
216
+ const originalWebpack = nextConfig.webpack;
217
+ nextConfig.webpack = (config, options) => {
218
+ if (!turbopackEnabled) {
219
+ console.log("Applying Lingo.dev webpack configuration...");
220
+ config.plugins.unshift(unplugin.webpack(mergedParams));
221
+ }
222
+ if (typeof originalWebpack === "function") {
223
+ return originalWebpack(config, options);
224
+ }
225
+ return config;
226
+ };
227
+ if (turbopackEnabled) {
228
+ console.log("Applying Lingo.dev Turbopack configuration...");
229
+ let turbopackConfigPath = nextConfig.turbopack ??= {};
230
+ if (supportLegacyTurbo) {
231
+ turbopackConfigPath = (nextConfig.experimental ??= {}).turbo ??= {};
232
+ }
233
+ turbopackConfigPath.rules ??= {};
234
+ const rules = turbopackConfigPath.rules;
235
+ const lingoGlob = `**/*.{ts,tsx,js,jsx}`;
236
+ const lingoLoaderPath = __require.resolve("./lingo-turbopack-loader");
237
+ rules[lingoGlob] = {
238
+ loaders: [
239
+ {
240
+ loader: lingoLoaderPath,
241
+ options: mergedParams
242
+ }
243
+ ]
244
+ };
245
+ }
246
+ return nextConfig;
247
+ },
248
+ vite: (compilerParams) => (config) => {
249
+ config.plugins.unshift(
250
+ unplugin.vite(_.merge({}, defaultParams, { rsc: false }, compilerParams))
2780
251
  );
2781
- return config2;
252
+ return config;
2782
253
  }
2783
254
  };
2784
255
  function getConfiguredProviders(models) {
2785
- return _11.chain(Object.values(models)).map((modelString) => modelString.split(":")[0]).filter(Boolean).uniq().filter(
256
+ return _.chain(Object.values(models)).map((modelString) => modelString.split(":")[0]).filter(Boolean).uniq().filter(
2786
257
  (providerId) => providerDetails.hasOwnProperty(providerId) && keyCheckers.hasOwnProperty(providerId)
2787
258
  ).value();
2788
259
  }
@@ -2807,7 +278,7 @@ function validateLLMKeyDetails(configuredProviders) {
2807
278
  }
2808
279
  }
2809
280
  if (missingProviders.length > 0) {
2810
- console.log(dedent3`
281
+ console.log(dedent`
2811
282
  \n
2812
283
  💡 Lingo.dev Localization Compiler is configured to use the following LLM provider(s): ${configuredProviders.join(", ")}.
2813
284
 
@@ -2816,7 +287,7 @@ function validateLLMKeyDetails(configuredProviders) {
2816
287
  for (const providerId of missingProviders) {
2817
288
  const status = keyStatuses[providerId];
2818
289
  if (!status) continue;
2819
- console.log(dedent3`
290
+ console.log(dedent`
2820
291
  ⚠️ ${status.details.name} API key is missing. Set ${status.details.apiKeyEnvVar} environment variable.
2821
292
 
2822
293
  👉 You can set the API key in one of the following ways:
@@ -2827,7 +298,7 @@ function validateLLMKeyDetails(configuredProviders) {
2827
298
  ⭐️ If you don't yet have a ${status.details.name} API key, get one for free at ${status.details.getKeyLink}
2828
299
  `);
2829
300
  }
2830
- console.log(dedent3`
301
+ console.log(dedent`
2831
302
  \n
2832
303
  ⭐️ Also:
2833
304
  1. If you want to use a different LLM, update your configuration. Refer to documentation for help: https://lingo.dev/compiler
@@ -2838,7 +309,7 @@ function validateLLMKeyDetails(configuredProviders) {
2838
309
  `);
2839
310
  process.exit(1);
2840
311
  } else if (foundProviders.length > 0) {
2841
- console.log(dedent3`
312
+ console.log(dedent`
2842
313
  \n
2843
314
  🔑 LLM API keys detected for configured providers: ${foundProviders.join(", ")}.
2844
315
  `);
@@ -2853,7 +324,7 @@ function validateLLMKeyDetails(configuredProviders) {
2853
324
  } else if (status.foundInRc) {
2854
325
  sourceMessage = `from your user-wide configuration${status.details.apiKeyConfigKey ? ` (${status.details.apiKeyConfigKey})` : ""}.`;
2855
326
  }
2856
- console.log(dedent3`
327
+ console.log(dedent`
2857
328
  • ${status.details.name} API key loaded ${sourceMessage}
2858
329
  `);
2859
330
  }