lingo.dev 0.93.4 → 0.93.6

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/cli.mjs CHANGED
@@ -5374,7 +5374,7 @@ var mcp_default = new Command13().command("mcp").description("Use Lingo.dev mode
5374
5374
  });
5375
5375
 
5376
5376
  // src/cli/cmd/ci/index.ts
5377
- import { Command as Command14 } from "interactive-commander";
5377
+ import { Command as Command15 } from "interactive-commander";
5378
5378
  import createOra from "ora";
5379
5379
 
5380
5380
  // src/cli/cmd/ci/flows/pull-request.ts
@@ -5400,2039 +5400,1952 @@ function escapeShellArg(arg) {
5400
5400
  return `'${arg.replace(/'/g, "'\\''")}'`;
5401
5401
  }
5402
5402
 
5403
- // src/cli/cmd/ci/flows/in-branch.ts
5404
- var InBranchFlow = class extends IntegrationFlow {
5405
- async preRun() {
5406
- this.ora.start("Configuring git");
5407
- const canContinue = this.configureGit();
5408
- this.ora.succeed("Git configured");
5409
- return canContinue;
5410
- }
5411
- async run(forcePush = false) {
5412
- this.ora.start("Running Lingo.dev");
5413
- await this.runLingoDotDev();
5414
- this.ora.succeed("Done running Lingo.dev");
5415
- execSync(`rm -f i18n.cache`, { stdio: "inherit" });
5416
- this.ora.start("Checking for changes");
5417
- const hasChanges = this.checkCommitableChanges();
5418
- this.ora.succeed(hasChanges ? "Changes detected" : "No changes detected");
5419
- if (hasChanges) {
5420
- this.ora.start("Committing changes");
5421
- execSync(`git add .`, { stdio: "inherit" });
5422
- execSync(`git status --porcelain`, { stdio: "inherit" });
5423
- execSync(
5424
- `git commit -m ${escapeShellArg(this.platformKit.config.commitMessage)} --no-verify`,
5425
- {
5426
- stdio: "inherit"
5427
- }
5428
- );
5429
- this.ora.succeed("Changes committed");
5430
- this.ora.start("Pushing changes to remote");
5431
- const currentBranch = this.i18nBranchName ?? this.platformKit.platformConfig.baseBranchName;
5432
- execSync(
5433
- `git push origin ${currentBranch} ${forcePush ? "--force" : ""}`,
5434
- {
5435
- stdio: "inherit"
5436
- }
5437
- );
5438
- this.ora.succeed("Changes pushed to remote");
5439
- }
5440
- return hasChanges;
5441
- }
5442
- checkCommitableChanges() {
5443
- return execSync('git status --porcelain || echo "has_changes"', {
5444
- encoding: "utf8"
5445
- }).length > 0;
5446
- }
5447
- async runLingoDotDev() {
5448
- try {
5449
- await i18n_default.exitOverride().parseAsync(["--api-key", this.platformKit.config.replexicaApiKey], {
5450
- from: "user"
5451
- });
5452
- } catch (err) {
5453
- if (err.code === "commander.helpDisplayed") return;
5454
- throw err;
5455
- }
5456
- }
5457
- configureGit() {
5458
- const { processOwnCommits } = this.platformKit.config;
5459
- const { baseBranchName } = this.platformKit.platformConfig;
5460
- this.ora.info(`Current working directory:`);
5461
- execSync(`pwd`, { stdio: "inherit" });
5462
- execSync(`ls -la`, { stdio: "inherit" });
5463
- execSync(`git config --global safe.directory ${process.cwd()}`);
5464
- execSync(`git config user.name "${gitConfig.userName}"`);
5465
- execSync(`git config user.email "${gitConfig.userEmail}"`);
5466
- this.platformKit?.gitConfig();
5467
- execSync(`git fetch origin ${baseBranchName}`, { stdio: "inherit" });
5468
- execSync(`git checkout ${baseBranchName} --`, { stdio: "inherit" });
5469
- if (!processOwnCommits) {
5470
- const currentAuthor = `${gitConfig.userName} <${gitConfig.userEmail}>`;
5471
- const authorOfLastCommit = execSync(
5472
- `git log -1 --pretty=format:'%an <%ae>'`
5473
- ).toString();
5474
- if (authorOfLastCommit === currentAuthor) {
5475
- this.ora.warn(
5476
- `The last commit was already made by ${currentAuthor}, so this run will be skipped, as running again would have no effect. See docs: https://docs.lingo.dev/ci-action/overview`
5477
- );
5478
- return false;
5479
- }
5480
- }
5481
- const workingDir = path15.resolve(
5482
- process.cwd(),
5483
- this.platformKit.config.workingDir
5484
- );
5485
- if (workingDir !== process.cwd()) {
5486
- this.ora.info(
5487
- `Changing to working directory: ${this.platformKit.config.workingDir}`
5488
- );
5489
- process.chdir(workingDir);
5490
- }
5491
- return true;
5403
+ // src/cli/cmd/run/index.ts
5404
+ import { Command as Command14 } from "interactive-commander";
5405
+
5406
+ // src/cli/cmd/run/setup.ts
5407
+ import chalk9 from "chalk";
5408
+ import { Listr } from "listr2";
5409
+
5410
+ // src/cli/cmd/run/_const.ts
5411
+ import chalk6 from "chalk";
5412
+ import { ListrDefaultRendererLogLevels } from "listr2";
5413
+ var commonTaskRendererOptions = {
5414
+ color: {
5415
+ [ListrDefaultRendererLogLevels.COMPLETED]: (msg) => msg ? chalk6.hex(colors.green)(msg) : chalk6.hex(colors.green)("")
5416
+ },
5417
+ icon: {
5418
+ [ListrDefaultRendererLogLevels.COMPLETED]: chalk6.hex(colors.green)("\u2713")
5492
5419
  }
5493
5420
  };
5494
5421
 
5495
- // src/cli/cmd/ci/flows/pull-request.ts
5496
- var PullRequestFlow = class extends InBranchFlow {
5497
- async preRun() {
5498
- const canContinue = await super.preRun?.();
5499
- if (!canContinue) {
5500
- return false;
5501
- }
5502
- this.ora.start("Calculating automated branch name");
5503
- this.i18nBranchName = this.calculatePrBranchName();
5504
- this.ora.succeed(
5505
- `Automated branch name calculated: ${this.i18nBranchName}`
5422
+ // src/cli/localizer/lingodotdev.ts
5423
+ import dedent5 from "dedent";
5424
+ import chalk7 from "chalk";
5425
+ import { LingoDotDevEngine as LingoDotDevEngine2 } from "@lingo.dev/_sdk";
5426
+ function createLingoDotDevLocalizer(explicitApiKey) {
5427
+ const { auth } = getSettings(explicitApiKey);
5428
+ if (!auth) {
5429
+ throw new Error(
5430
+ dedent5`
5431
+ You're trying to use ${chalk7.hex(colors.green)("Lingo.dev")} provider, however, you are not authenticated.
5432
+
5433
+ To fix this issue:
5434
+ 1. Run ${chalk7.dim("lingo.dev login")} to authenticate, or
5435
+ 2. Use the ${chalk7.dim("--api-key")} flag to provide an API key.
5436
+ 3. Set ${chalk7.dim("LINGODOTDEV_API_KEY")} environment variable.
5437
+ `
5506
5438
  );
5507
- this.ora.start("Checking if branch exists");
5508
- const branchExists = await this.checkBranchExistance(this.i18nBranchName);
5509
- this.ora.succeed(branchExists ? "Branch exists" : "Branch does not exist");
5510
- if (branchExists) {
5511
- this.ora.start(`Checking out branch ${this.i18nBranchName}`);
5512
- this.checkoutI18nBranch(this.i18nBranchName);
5513
- this.ora.succeed(`Checked out branch ${this.i18nBranchName}`);
5514
- this.ora.start(
5515
- `Syncing with ${this.platformKit.platformConfig.baseBranchName}`
5439
+ }
5440
+ const engine = new LingoDotDevEngine2({
5441
+ apiKey: auth.apiKey,
5442
+ apiUrl: auth.apiUrl
5443
+ });
5444
+ return {
5445
+ id: "Lingo.dev",
5446
+ checkAuth: async () => {
5447
+ try {
5448
+ const response = await engine.whoami();
5449
+ return {
5450
+ authenticated: !!response,
5451
+ username: response?.email
5452
+ };
5453
+ } catch {
5454
+ return { authenticated: false };
5455
+ }
5456
+ },
5457
+ localize: async (input2, onProgress) => {
5458
+ if (!Object.keys(input2.processableData).length) {
5459
+ return input2;
5460
+ }
5461
+ const processedData = await engine.localizeObject(
5462
+ input2.processableData,
5463
+ {
5464
+ sourceLocale: input2.sourceLocale,
5465
+ targetLocale: input2.targetLocale,
5466
+ reference: {
5467
+ [input2.sourceLocale]: input2.sourceData,
5468
+ [input2.targetLocale]: input2.targetData
5469
+ }
5470
+ },
5471
+ onProgress
5516
5472
  );
5517
- this.syncI18nBranch();
5518
- this.ora.succeed(`Checked out and synced branch ${this.i18nBranchName}`);
5519
- } else {
5520
- this.ora.start(`Creating branch ${this.i18nBranchName}`);
5521
- this.createI18nBranch(this.i18nBranchName);
5522
- this.ora.succeed(`Created branch ${this.i18nBranchName}`);
5473
+ return processedData;
5523
5474
  }
5524
- return true;
5525
- }
5526
- async run() {
5527
- return super.run(true);
5528
- }
5529
- async postRun() {
5530
- if (!this.i18nBranchName) {
5475
+ };
5476
+ }
5477
+
5478
+ // src/cli/localizer/explicit.ts
5479
+ import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
5480
+ import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
5481
+ import chalk8 from "chalk";
5482
+ import dedent6 from "dedent";
5483
+ import { generateText as generateText2 } from "ai";
5484
+ import { jsonrepair as jsonrepair3 } from "jsonrepair";
5485
+ function createExplicitLocalizer(provider) {
5486
+ switch (provider.id) {
5487
+ default:
5531
5488
  throw new Error(
5532
- "i18nBranchName is not set. Did you forget to call preRun?"
5489
+ dedent6`
5490
+ You're trying to use unsupported provider: ${chalk8.dim(provider.id)}.
5491
+
5492
+ To fix this issue:
5493
+ 1. Switch to one of the supported providers, or
5494
+ 2. Remove the ${chalk8.italic("provider")} node from your i18n.json configuration to switch to ${chalk8.hex(colors.green)("Lingo.dev")}
5495
+
5496
+ ${chalk8.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
5497
+ `
5533
5498
  );
5534
- }
5535
- this.ora.start("Checking if PR already exists");
5536
- const pullRequestNumber = await this.ensureFreshPr(this.i18nBranchName);
5537
- this.ora.succeed(
5538
- `Pull request ready: ${this.platformKit.buildPullRequestUrl(pullRequestNumber)}`
5539
- );
5540
- }
5541
- calculatePrBranchName() {
5542
- return `lingo.dev/${this.platformKit.platformConfig.baseBranchName}`;
5543
- }
5544
- async checkBranchExistance(prBranchName) {
5545
- return this.platformKit.branchExists({
5546
- branch: prBranchName
5547
- });
5499
+ case "openai":
5500
+ return createAiSdkLocalizer({
5501
+ factory: (params) => createOpenAI2(params).languageModel(provider.model),
5502
+ id: provider.id,
5503
+ prompt: provider.prompt,
5504
+ apiKeyName: "OPENAI_API_KEY",
5505
+ baseUrl: provider.baseUrl
5506
+ });
5507
+ case "anthropic":
5508
+ return createAiSdkLocalizer({
5509
+ factory: (params) => createAnthropic2(params).languageModel(provider.model),
5510
+ id: provider.id,
5511
+ prompt: provider.prompt,
5512
+ apiKeyName: "ANTHROPIC_API_KEY",
5513
+ baseUrl: provider.baseUrl
5514
+ });
5548
5515
  }
5549
- async ensureFreshPr(i18nBranchName) {
5550
- this.ora.start(
5551
- `Checking for existing PR with head ${i18nBranchName} and base ${this.platformKit.platformConfig.baseBranchName}`
5516
+ }
5517
+ function createAiSdkLocalizer(params) {
5518
+ const apiKey = process.env[params.apiKeyName];
5519
+ if (!apiKey) {
5520
+ throw new Error(
5521
+ dedent6`
5522
+ You're trying to use raw ${chalk8.dim(params.id)} API for translation, however, ${chalk8.dim(params.apiKeyName)} environment variable is not set.
5523
+
5524
+ To fix this issue:
5525
+ 1. Set ${chalk8.dim(params.apiKeyName)} in your environment variables, or
5526
+ 2. Remove the ${chalk8.italic("provider")} node from your i18n.json configuration to switch to ${chalk8.hex(colors.green)("Lingo.dev")}
5527
+
5528
+ ${chalk8.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
5529
+ `
5552
5530
  );
5553
- let prNumber = await this.platformKit.getOpenPullRequestNumber({
5554
- branch: i18nBranchName
5555
- });
5556
- if (prNumber) {
5557
- this.ora.succeed(`Existing PR found: #${prNumber}`);
5558
- } else {
5559
- this.ora.start(`Creating new PR`);
5560
- prNumber = await this.platformKit.createPullRequest({
5561
- head: i18nBranchName,
5562
- title: this.platformKit.config.pullRequestTitle,
5563
- body: this.getPrBodyContent()
5564
- });
5565
- this.ora.succeed(`Created new PR: #${prNumber}`);
5566
- }
5567
- return prNumber;
5568
5531
  }
5569
- checkoutI18nBranch(i18nBranchName) {
5570
- execSync2(`git fetch origin ${i18nBranchName}`, { stdio: "inherit" });
5571
- execSync2(`git checkout -b ${i18nBranchName}`, {
5572
- stdio: "inherit"
5573
- });
5532
+ const model = params.factory({
5533
+ apiKey,
5534
+ baseUrl: params.baseUrl
5535
+ });
5536
+ return {
5537
+ id: params.id,
5538
+ checkAuth: async () => {
5539
+ try {
5540
+ await generateText2({
5541
+ model,
5542
+ messages: [
5543
+ { role: "system", content: "You are an echo server" },
5544
+ { role: "user", content: "OK" },
5545
+ { role: "assistant", content: "OK" },
5546
+ { role: "user", content: "OK" }
5547
+ ]
5548
+ });
5549
+ return { authenticated: true, username: "anonymous" };
5550
+ } catch (error) {
5551
+ return { authenticated: false };
5552
+ }
5553
+ },
5554
+ localize: async (input2) => {
5555
+ const systemPrompt = params.prompt.replaceAll("{source}", input2.sourceLocale).replaceAll("{target}", input2.targetLocale);
5556
+ const shots = [
5557
+ [
5558
+ {
5559
+ sourceLocale: "en",
5560
+ targetLocale: "es",
5561
+ data: {
5562
+ message: "Hello, world!"
5563
+ }
5564
+ },
5565
+ {
5566
+ sourceLocale: "en",
5567
+ targetLocale: "es",
5568
+ data: {
5569
+ message: "Hola, mundo!"
5570
+ }
5571
+ }
5572
+ ]
5573
+ ];
5574
+ const payload = {
5575
+ sourceLocale: input2.sourceLocale,
5576
+ targetLocale: input2.targetLocale,
5577
+ data: input2.processableData
5578
+ };
5579
+ const response = await generateText2({
5580
+ model,
5581
+ messages: [
5582
+ { role: "system", content: systemPrompt },
5583
+ { role: "user", content: "OK" },
5584
+ ...shots.flatMap(
5585
+ ([userShot, assistantShot]) => [
5586
+ { role: "user", content: JSON.stringify(userShot) },
5587
+ { role: "assistant", content: JSON.stringify(assistantShot) }
5588
+ ]
5589
+ ),
5590
+ { role: "user", content: JSON.stringify(payload) }
5591
+ ]
5592
+ });
5593
+ const result = JSON.parse(response.text);
5594
+ const index = result.data.indexOf("{");
5595
+ const lastIndex = result.data.lastIndexOf("}");
5596
+ const trimmed = result.data.slice(index, lastIndex + 1);
5597
+ const repaired = jsonrepair3(trimmed);
5598
+ const finalResult = JSON.parse(repaired);
5599
+ return finalResult.data;
5600
+ }
5601
+ };
5602
+ }
5603
+
5604
+ // src/cli/localizer/index.ts
5605
+ function createLocalizer(provider, apiKey) {
5606
+ if (!provider) {
5607
+ return createLingoDotDevLocalizer(apiKey);
5608
+ } else {
5609
+ return createExplicitLocalizer(provider);
5574
5610
  }
5575
- createI18nBranch(i18nBranchName) {
5576
- try {
5577
- execSync2(
5578
- `git fetch origin ${this.platformKit.platformConfig.baseBranchName}`,
5579
- { stdio: "inherit" }
5580
- );
5581
- execSync2(
5582
- `git checkout -b ${i18nBranchName} origin/${this.platformKit.platformConfig.baseBranchName}`,
5583
- {
5584
- stdio: "inherit"
5611
+ }
5612
+
5613
+ // src/cli/cmd/run/setup.ts
5614
+ async function setup(input2) {
5615
+ console.log(chalk9.hex(colors.orange)("[Setup]"));
5616
+ return new Listr(
5617
+ [
5618
+ {
5619
+ title: "Setting up the environment",
5620
+ task: async (ctx, task) => {
5621
+ task.title = `Environment setup completed`;
5585
5622
  }
5586
- );
5587
- } catch (error) {
5588
- const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
5589
- this.ora.fail(`Failed to create branch: ${errorMessage}`);
5590
- this.ora.info(`
5591
- Troubleshooting tips:
5592
- 1. Make sure you have permission to create branches
5593
- 2. Check if the branch already exists locally (try 'git branch -a')
5594
- 3. Verify connectivity to remote repository
5595
- `);
5596
- throw new Error(`Branch creation failed: ${errorMessage}`);
5623
+ },
5624
+ {
5625
+ title: "Loading i18n configuration",
5626
+ task: async (ctx, task) => {
5627
+ ctx.config = getConfig(true);
5628
+ if (!ctx.config) {
5629
+ throw new Error(
5630
+ "i18n.json not found. Please run `lingo.dev init` to initialize the project."
5631
+ );
5632
+ } else if (!ctx.config.buckets || !Object.keys(ctx.config.buckets).length) {
5633
+ throw new Error(
5634
+ "No buckets found in i18n.json. Please add at least one bucket containing i18n content."
5635
+ );
5636
+ } else if (ctx.flags.bucket?.some(
5637
+ (bucket) => !ctx.config?.buckets[bucket]
5638
+ )) {
5639
+ throw new Error(
5640
+ `One or more specified buckets do not exist in i18n.json. Please add them to the list first and try again.`
5641
+ );
5642
+ }
5643
+ task.title = `Loaded i18n configuration`;
5644
+ }
5645
+ },
5646
+ {
5647
+ title: "Selecting localization provider",
5648
+ task: async (ctx, task) => {
5649
+ ctx.localizer = createLocalizer(
5650
+ ctx.config?.provider,
5651
+ ctx.flags.apiKey
5652
+ );
5653
+ if (!ctx.localizer) {
5654
+ throw new Error(
5655
+ "Could not create localization provider. Please check your i18n.json configuration."
5656
+ );
5657
+ }
5658
+ task.title = ctx.localizer.id === "Lingo.dev" ? `Using ${chalk9.hex(colors.green)(ctx.localizer.id)} provider` : `Using raw ${chalk9.hex(colors.yellow)(ctx.localizer.id)} API`;
5659
+ }
5660
+ },
5661
+ {
5662
+ title: "Checking authentication",
5663
+ task: async (ctx, task) => {
5664
+ const authStatus = await ctx.localizer.checkAuth();
5665
+ if (!authStatus.authenticated) {
5666
+ throw new Error(
5667
+ `Failed to authenticate with ${chalk9.hex(colors.yellow)(ctx.localizer.id)} provider. Please check your API key and try again.`
5668
+ );
5669
+ }
5670
+ task.title = `Authenticated as ${chalk9.hex(colors.yellow)(authStatus.username)}`;
5671
+ }
5672
+ },
5673
+ {
5674
+ title: "Initializing localization provider",
5675
+ async task(ctx, task) {
5676
+ const isLingoDotDev = ctx.localizer.id === "Lingo.dev";
5677
+ const subTasks = isLingoDotDev ? [
5678
+ "Brand voice enabled",
5679
+ "Translation memory connected",
5680
+ "Glossary enabled",
5681
+ "Quality assurance enabled"
5682
+ ].map((title) => ({ title, task: () => {
5683
+ } })) : [
5684
+ "Skipping brand voice",
5685
+ "Skipping glossary",
5686
+ "Skipping translation memory",
5687
+ "Skipping quality assurance"
5688
+ ].map((title) => ({ title, task: () => {
5689
+ }, skip: true }));
5690
+ return task.newListr(subTasks, {
5691
+ concurrent: true,
5692
+ rendererOptions: { collapseSubtasks: false }
5693
+ });
5694
+ }
5695
+ }
5696
+ ],
5697
+ {
5698
+ rendererOptions: commonTaskRendererOptions
5597
5699
  }
5700
+ ).run(input2);
5701
+ }
5702
+
5703
+ // src/cli/cmd/run/plan.ts
5704
+ import chalk10 from "chalk";
5705
+ import { Listr as Listr2 } from "listr2";
5706
+ import { resolveOverriddenLocale as resolveOverriddenLocale6 } from "@lingo.dev/_spec";
5707
+ async function plan(input2) {
5708
+ console.log(chalk10.hex(colors.orange)("[Planning]"));
5709
+ let buckets = getBuckets(input2.config);
5710
+ if (input2.flags.bucket) {
5711
+ buckets = buckets.filter((b) => input2.flags.bucket.includes(b.type));
5598
5712
  }
5599
- syncI18nBranch() {
5600
- if (!this.i18nBranchName) {
5601
- throw new Error("i18nBranchName is not set");
5602
- }
5603
- this.ora.start(
5604
- `Fetching latest changes from ${this.platformKit.platformConfig.baseBranchName}`
5605
- );
5606
- execSync2(
5607
- `git fetch origin ${this.platformKit.platformConfig.baseBranchName}`,
5608
- { stdio: "inherit" }
5713
+ const _sourceLocale = input2.flags.sourceLocale || input2.config.locale.source;
5714
+ if (!_sourceLocale) {
5715
+ throw new Error(
5716
+ `No source locale provided. Use --source-locale to specify the source locale or add it to i18n.json (locale.source)`
5609
5717
  );
5610
- this.ora.succeed(
5611
- `Fetched latest changes from ${this.platformKit.platformConfig.baseBranchName}`
5718
+ }
5719
+ const _targetLocales = input2.flags.targetLocale || input2.config.locale.targets;
5720
+ if (!_targetLocales.length) {
5721
+ throw new Error(
5722
+ `No target locales provided. Use --target-locale to specify the target locales or add them to i18n.json (locale.targets)`
5612
5723
  );
5613
- try {
5614
- this.ora.start("Attempting to rebase branch");
5615
- execSync2(
5616
- `git rebase origin/${this.platformKit.platformConfig.baseBranchName}`,
5617
- { stdio: "inherit" }
5618
- );
5619
- this.ora.succeed("Successfully rebased branch");
5620
- } catch (error) {
5621
- this.ora.warn("Rebase failed, falling back to alternative sync method");
5622
- this.ora.start("Aborting failed rebase");
5623
- execSync2("git rebase --abort", { stdio: "inherit" });
5624
- this.ora.succeed("Aborted failed rebase");
5625
- this.ora.start(
5626
- `Resetting to ${this.platformKit.platformConfig.baseBranchName}`
5627
- );
5628
- execSync2(
5629
- `git reset --hard origin/${this.platformKit.platformConfig.baseBranchName}`,
5630
- { stdio: "inherit" }
5631
- );
5632
- this.ora.succeed(
5633
- `Reset to ${this.platformKit.platformConfig.baseBranchName}`
5634
- );
5635
- this.ora.start("Restoring target files");
5636
- const targetFiles = ["i18n.lock"];
5637
- const targetFileNames = execSync2(
5638
- `npx lingo.dev@latest show files --target ${this.platformKit.platformConfig.baseBranchName}`,
5639
- { encoding: "utf8" }
5640
- ).split("\n").filter(Boolean);
5641
- targetFiles.push(...targetFileNames);
5642
- execSync2(`git fetch origin ${this.i18nBranchName}`, { stdio: "inherit" });
5643
- for (const file of targetFiles) {
5644
- try {
5645
- execSync2(`git checkout FETCH_HEAD -- ${file}`, { stdio: "inherit" });
5646
- } catch (error2) {
5647
- this.ora.warn(`Skipping non-existent file: ${file}`);
5648
- continue;
5724
+ }
5725
+ return new Listr2(
5726
+ [
5727
+ {
5728
+ title: "Locating content buckets",
5729
+ task: async (ctx, task) => {
5730
+ const bucketCount = buckets.length;
5731
+ const bucketFilter = input2.flags.bucket ? ` ${chalk10.dim(`(filtered by: ${chalk10.hex(colors.yellow)(input2.flags.bucket.join(", "))})`)}` : "";
5732
+ task.title = `Found ${chalk10.hex(colors.yellow)(bucketCount.toString())} bucket(s)${bucketFilter}`;
5649
5733
  }
5650
- }
5651
- this.ora.succeed("Restored target files");
5652
- }
5653
- this.ora.start("Checking for changes to commit");
5654
- const hasChanges = this.checkCommitableChanges();
5655
- if (hasChanges) {
5656
- execSync2("git add .", { stdio: "inherit" });
5657
- execSync2(
5658
- `git commit -m "chore: sync with ${this.platformKit.platformConfig.baseBranchName}" --no-verify`,
5659
- {
5660
- stdio: "inherit"
5734
+ },
5735
+ {
5736
+ title: "Detecting locales",
5737
+ task: async (ctx, task) => {
5738
+ task.title = `Found ${chalk10.hex(colors.yellow)(_targetLocales.length.toString())} target locale(s)`;
5661
5739
  }
5662
- );
5663
- this.ora.succeed("Committed additional changes");
5664
- } else {
5665
- this.ora.succeed("No changes to commit");
5666
- }
5667
- }
5668
- getPrBodyContent() {
5669
- return `
5670
- Hey team,
5671
-
5672
- [**Lingo.dev**](https://lingo.dev) here with fresh translations!
5673
-
5674
- ### In this update
5675
-
5676
- - Added missing translations
5677
- - Performed brand voice, context and glossary checks
5678
- - Enhanced translations using Lingo.dev Localization Engine
5679
-
5680
- ### Next Steps
5681
-
5682
- - [ ] Review the changes
5683
- - [ ] Merge when ready
5684
- `.trim();
5685
- }
5686
- };
5687
-
5688
- // src/cli/cmd/ci/platforms/bitbucket.ts
5689
- import { execSync as execSync4 } from "child_process";
5690
- import bbLib from "bitbucket";
5691
- import Z8 from "zod";
5692
-
5693
- // src/cli/cmd/ci/platforms/_base.ts
5694
- import { execSync as execSync3 } from "child_process";
5695
- import Z7 from "zod";
5696
- var defaultMessage = "feat: update translations via @lingodotdev";
5697
- var PlatformKit = class {
5698
- gitConfig(token, repoUrl) {
5699
- if (token && repoUrl) {
5700
- execSync3(`git remote set-url origin ${repoUrl}`, {
5701
- stdio: "inherit"
5702
- });
5740
+ },
5741
+ {
5742
+ title: "Locating localizable files",
5743
+ task: async (ctx, task) => {
5744
+ const patterns = [];
5745
+ for (const bucket of buckets) {
5746
+ for (const bucketPath of bucket.paths) {
5747
+ if (input2.flags.file) {
5748
+ if (!input2.flags.file.some(
5749
+ (f) => bucketPath.pathPattern.includes(f)
5750
+ )) {
5751
+ continue;
5752
+ }
5753
+ }
5754
+ patterns.push(bucketPath.pathPattern);
5755
+ }
5756
+ }
5757
+ const fileFilter = input2.flags.file ? ` ${chalk10.dim(`(filtered by: ${chalk10.hex(colors.yellow)(input2.flags.file.join(", "))})`)}` : "";
5758
+ task.title = `Found ${chalk10.hex(colors.yellow)(patterns.length.toString())} path pattern(s)${fileFilter}`;
5759
+ }
5760
+ },
5761
+ {
5762
+ title: "Computing translation tasks",
5763
+ task: async (ctx, task) => {
5764
+ for (const bucket of buckets) {
5765
+ for (const bucketPath of bucket.paths) {
5766
+ if (input2.flags.file) {
5767
+ if (!input2.flags.file.some(
5768
+ (f) => bucketPath.pathPattern.includes(f)
5769
+ )) {
5770
+ continue;
5771
+ }
5772
+ }
5773
+ const sourceLocale = resolveOverriddenLocale6(
5774
+ _sourceLocale,
5775
+ bucketPath.delimiter
5776
+ );
5777
+ for (const _targetLocale of _targetLocales) {
5778
+ const targetLocale = resolveOverriddenLocale6(
5779
+ _targetLocale,
5780
+ bucketPath.delimiter
5781
+ );
5782
+ if (sourceLocale === targetLocale) continue;
5783
+ ctx.tasks.push({
5784
+ sourceLocale,
5785
+ targetLocale,
5786
+ bucketType: bucket.type,
5787
+ bucketPathPattern: bucketPath.pathPattern,
5788
+ injectLocale: bucket.injectLocale || [],
5789
+ lockedKeys: bucket.lockedKeys || [],
5790
+ lockedPatterns: bucket.lockedPatterns || []
5791
+ });
5792
+ }
5793
+ }
5794
+ }
5795
+ task.title = `Prepared ${chalk10.hex(colors.green)(ctx.tasks.length.toString())} translation task(s)`;
5796
+ }
5797
+ }
5798
+ ],
5799
+ {
5800
+ rendererOptions: commonTaskRendererOptions
5703
5801
  }
5704
- }
5705
- get config() {
5706
- const env = Z7.object({
5707
- LINGODOTDEV_API_KEY: Z7.string(),
5708
- LINGODOTDEV_PULL_REQUEST: Z7.preprocess((val) => val === "true" || val === true, Z7.boolean()),
5709
- LINGODOTDEV_COMMIT_MESSAGE: Z7.string().optional(),
5710
- LINGODOTDEV_PULL_REQUEST_TITLE: Z7.string().optional(),
5711
- LINGODOTDEV_WORKING_DIRECTORY: Z7.string().optional(),
5712
- LINGODOTDEV_PROCESS_OWN_COMMITS: Z7.preprocess((val) => val === "true" || val === true, Z7.boolean()).optional()
5713
- }).parse(process.env);
5714
- return {
5715
- replexicaApiKey: env.LINGODOTDEV_API_KEY,
5716
- isPullRequestMode: env.LINGODOTDEV_PULL_REQUEST,
5717
- commitMessage: env.LINGODOTDEV_COMMIT_MESSAGE || defaultMessage,
5718
- pullRequestTitle: env.LINGODOTDEV_PULL_REQUEST_TITLE || defaultMessage,
5719
- workingDir: env.LINGODOTDEV_WORKING_DIRECTORY || ".",
5720
- processOwnCommits: env.LINGODOTDEV_PROCESS_OWN_COMMITS || false
5721
- };
5722
- }
5723
- };
5802
+ ).run(input2);
5803
+ }
5724
5804
 
5725
- // src/cli/cmd/ci/platforms/bitbucket.ts
5726
- var { Bitbucket } = bbLib;
5727
- var BitbucketPlatformKit = class extends PlatformKit {
5728
- _bb;
5729
- get bb() {
5730
- if (!this._bb) {
5731
- this._bb = new Bitbucket({
5732
- auth: { token: this.platformConfig.bbToken || "" }
5733
- });
5734
- }
5735
- return this._bb;
5736
- }
5737
- async branchExists({ branch }) {
5738
- return await this.bb.repositories.getBranch({
5739
- workspace: this.platformConfig.repositoryOwner,
5740
- repo_slug: this.platformConfig.repositoryName,
5741
- name: branch
5742
- }).then((r) => r.data).then((v) => !!v).catch((r) => r.status === 404 ? false : Promise.reject(r));
5743
- }
5744
- async getOpenPullRequestNumber({ branch }) {
5745
- return await this.bb.repositories.listPullRequests({
5746
- workspace: this.platformConfig.repositoryOwner,
5747
- repo_slug: this.platformConfig.repositoryName,
5748
- state: "OPEN"
5749
- }).then(({ data: { values } }) => {
5750
- return values?.find(
5751
- ({ source, destination }) => source?.branch?.name === branch && destination?.branch?.name === this.platformConfig.baseBranchName
5752
- );
5753
- }).then((pr) => pr?.id);
5754
- }
5755
- async closePullRequest({ pullRequestNumber }) {
5756
- await this.bb.repositories.declinePullRequest({
5757
- workspace: this.platformConfig.repositoryOwner,
5758
- repo_slug: this.platformConfig.repositoryName,
5759
- pull_request_id: pullRequestNumber
5760
- });
5761
- }
5762
- async createPullRequest({
5763
- title,
5764
- body,
5765
- head
5766
- }) {
5767
- return await this.bb.repositories.createPullRequest({
5768
- workspace: this.platformConfig.repositoryOwner,
5769
- repo_slug: this.platformConfig.repositoryName,
5770
- _body: {
5771
- title,
5772
- description: body,
5773
- source: { branch: { name: head } },
5774
- destination: { branch: { name: this.platformConfig.baseBranchName } }
5775
- }
5776
- }).then(({ data }) => data.id ?? 0);
5777
- }
5778
- async commentOnPullRequest({
5779
- pullRequestNumber,
5780
- body
5781
- }) {
5782
- await this.bb.repositories.createPullRequestComment({
5783
- workspace: this.platformConfig.repositoryOwner,
5784
- repo_slug: this.platformConfig.repositoryName,
5785
- pull_request_id: pullRequestNumber,
5786
- _body: {
5787
- content: {
5788
- raw: body
5805
+ // src/cli/cmd/run/execute.ts
5806
+ import chalk11 from "chalk";
5807
+ import { Listr as Listr3 } from "listr2";
5808
+ import pLimit from "p-limit";
5809
+ import _32 from "lodash";
5810
+ var MAX_WORKER_COUNT = 10;
5811
+ async function execute(input2) {
5812
+ const effectiveConcurrency = Math.min(
5813
+ input2.flags.concurrency,
5814
+ input2.tasks.length,
5815
+ MAX_WORKER_COUNT
5816
+ );
5817
+ console.log(chalk11.hex(colors.orange)(`[Localization]`));
5818
+ return new Listr3(
5819
+ [
5820
+ {
5821
+ title: "Initializing localization engine",
5822
+ task: async (ctx, task) => {
5823
+ task.title = `Localization engine ${chalk11.hex(colors.green)("ready")} (${ctx.localizer.id})`;
5789
5824
  }
5790
- }
5791
- });
5792
- }
5793
- async gitConfig() {
5794
- execSync4("git config --unset http.${BITBUCKET_GIT_HTTP_ORIGIN}.proxy", {
5795
- stdio: "inherit"
5796
- });
5797
- execSync4(
5798
- "git config http.${BITBUCKET_GIT_HTTP_ORIGIN}.proxy http://host.docker.internal:29418/",
5825
+ },
5799
5826
  {
5800
- stdio: "inherit"
5827
+ title: `Processing localization tasks ${chalk11.dim(`(tasks: ${input2.tasks.length}, concurrency: ${effectiveConcurrency})`)}`,
5828
+ task: (ctx, task) => {
5829
+ if (input2.tasks.length < 1) {
5830
+ task.title = `Skipping, nothing to localize.`;
5831
+ task.skip();
5832
+ return;
5833
+ }
5834
+ const i18nLimiter = pLimit(effectiveConcurrency);
5835
+ const ioLimiter = pLimit(1);
5836
+ const workersCount = effectiveConcurrency;
5837
+ const workerTasks = [];
5838
+ for (let i = 0; i < workersCount; i++) {
5839
+ const assignedTasks = ctx.tasks.filter(
5840
+ (_33, idx) => idx % workersCount === i
5841
+ );
5842
+ workerTasks.push(
5843
+ createWorkerTask({
5844
+ ctx,
5845
+ assignedTasks,
5846
+ ioLimiter,
5847
+ i18nLimiter,
5848
+ onDone() {
5849
+ task.title = createExecutionProgressMessage(ctx);
5850
+ }
5851
+ })
5852
+ );
5853
+ }
5854
+ return task.newListr(workerTasks, {
5855
+ concurrent: true,
5856
+ exitOnError: false,
5857
+ rendererOptions: {
5858
+ ...commonTaskRendererOptions,
5859
+ collapseSubtasks: true
5860
+ }
5861
+ });
5862
+ }
5801
5863
  }
5802
- );
5803
- }
5804
- get platformConfig() {
5805
- const env = Z8.object({
5806
- BITBUCKET_BRANCH: Z8.string(),
5807
- BITBUCKET_REPO_FULL_NAME: Z8.string(),
5808
- BB_TOKEN: Z8.string().optional()
5809
- }).parse(process.env);
5810
- const [repositoryOwner, repositoryName] = env.BITBUCKET_REPO_FULL_NAME.split("/");
5811
- return {
5812
- baseBranchName: env.BITBUCKET_BRANCH,
5813
- repositoryOwner,
5814
- repositoryName,
5815
- bbToken: env.BB_TOKEN
5816
- };
5817
- }
5818
- buildPullRequestUrl(pullRequestNumber) {
5819
- const { repositoryOwner, repositoryName } = this.platformConfig;
5820
- return `https://bitbucket.org/${repositoryOwner}/${repositoryName}/pull-requests/${pullRequestNumber}`;
5864
+ ],
5865
+ {
5866
+ exitOnError: false,
5867
+ rendererOptions: commonTaskRendererOptions
5868
+ }
5869
+ ).run(input2);
5870
+ }
5871
+ function createWorkerStatusMessage(args) {
5872
+ const displayPath = args.assignedTask.bucketPathPattern.replace(
5873
+ "[locale]",
5874
+ args.assignedTask.targetLocale
5875
+ );
5876
+ return `[${chalk11.hex(colors.yellow)(`${args.percentage}%`)}] Processing: ${chalk11.dim(
5877
+ displayPath
5878
+ )} (${chalk11.hex(colors.yellow)(args.assignedTask.sourceLocale)} -> ${chalk11.hex(
5879
+ colors.yellow
5880
+ )(args.assignedTask.targetLocale)})`;
5881
+ }
5882
+ function createExecutionProgressMessage(ctx) {
5883
+ const succeededTasksCount = countTasks(
5884
+ ctx,
5885
+ (_t, result) => result.status === "success"
5886
+ );
5887
+ const failedTasksCount = countTasks(
5888
+ ctx,
5889
+ (_t, result) => result.status === "error"
5890
+ );
5891
+ const skippedTasksCount = countTasks(
5892
+ ctx,
5893
+ (_t, result) => result.status === "skipped"
5894
+ );
5895
+ return `Processed ${chalk11.green(succeededTasksCount)}/${ctx.tasks.length}, Failed ${chalk11.red(failedTasksCount)}, Skipped ${chalk11.dim(skippedTasksCount)}`;
5896
+ }
5897
+ function createLoaderForTask(assignedTask) {
5898
+ const bucketLoader = createBucketLoader(
5899
+ assignedTask.bucketType,
5900
+ assignedTask.bucketPathPattern,
5901
+ {
5902
+ defaultLocale: assignedTask.sourceLocale,
5903
+ isCacheRestore: false,
5904
+ injectLocale: assignedTask.injectLocale
5905
+ },
5906
+ assignedTask.lockedKeys,
5907
+ assignedTask.lockedPatterns
5908
+ );
5909
+ bucketLoader.setDefaultLocale(assignedTask.sourceLocale);
5910
+ return bucketLoader;
5911
+ }
5912
+ function createWorkerTask(args) {
5913
+ return {
5914
+ title: "Initializing...",
5915
+ task: async (_subCtx, subTask) => {
5916
+ for (const assignedTask of args.assignedTasks) {
5917
+ subTask.title = createWorkerStatusMessage({
5918
+ assignedTask,
5919
+ percentage: 0
5920
+ });
5921
+ const bucketLoader = createLoaderForTask(assignedTask);
5922
+ const deltaProcessor = createDeltaProcessor(
5923
+ assignedTask.bucketPathPattern
5924
+ );
5925
+ const taskResult = await args.i18nLimiter(async () => {
5926
+ try {
5927
+ const sourceData = await bucketLoader.pull(
5928
+ assignedTask.sourceLocale
5929
+ );
5930
+ const targetData = await bucketLoader.pull(
5931
+ assignedTask.targetLocale
5932
+ );
5933
+ const checksums = await deltaProcessor.loadChecksums();
5934
+ const delta = await deltaProcessor.calculateDelta({
5935
+ sourceData,
5936
+ targetData,
5937
+ checksums
5938
+ });
5939
+ const processableData = _32.chain(sourceData).entries().filter(
5940
+ ([key, value]) => delta.added.includes(key) || delta.updated.includes(key) || !!args.ctx.flags.force
5941
+ ).fromPairs().value();
5942
+ if (!Object.keys(processableData).length) {
5943
+ return { status: "skipped" };
5944
+ }
5945
+ const processedTargetData = await args.ctx.localizer.localize(
5946
+ {
5947
+ sourceLocale: assignedTask.sourceLocale,
5948
+ targetLocale: assignedTask.targetLocale,
5949
+ sourceData,
5950
+ targetData,
5951
+ processableData
5952
+ },
5953
+ (progress) => {
5954
+ subTask.title = createWorkerStatusMessage({
5955
+ assignedTask,
5956
+ percentage: progress
5957
+ });
5958
+ }
5959
+ );
5960
+ let finalTargetData = _32.merge(
5961
+ {},
5962
+ sourceData,
5963
+ targetData,
5964
+ processedTargetData
5965
+ );
5966
+ finalTargetData = _32.chain(finalTargetData).entries().map(([key, value]) => {
5967
+ const renaming = delta.renamed.find(
5968
+ ([oldKey]) => oldKey === key
5969
+ );
5970
+ if (!renaming) {
5971
+ return [key, value];
5972
+ }
5973
+ return [renaming[1], value];
5974
+ }).fromPairs().value();
5975
+ await args.ioLimiter(async () => {
5976
+ await bucketLoader.pull(assignedTask.sourceLocale);
5977
+ await bucketLoader.push(
5978
+ assignedTask.targetLocale,
5979
+ finalTargetData
5980
+ );
5981
+ const checksums2 = await deltaProcessor.createChecksums(sourceData);
5982
+ await deltaProcessor.saveChecksums(checksums2);
5983
+ });
5984
+ return { status: "success" };
5985
+ } catch (error) {
5986
+ return {
5987
+ status: "error",
5988
+ error
5989
+ };
5990
+ }
5991
+ });
5992
+ args.ctx.results.set(assignedTask, taskResult);
5993
+ }
5994
+ subTask.title = "Done";
5995
+ }
5996
+ };
5997
+ }
5998
+ function countTasks(ctx, predicate) {
5999
+ return Array.from(ctx.results.entries()).filter(
6000
+ ([task, result]) => predicate(task, result)
6001
+ ).length;
6002
+ }
6003
+
6004
+ // src/cli/cmd/run/_types.ts
6005
+ import {
6006
+ bucketTypeSchema as bucketTypeSchema3
6007
+ } from "@lingo.dev/_spec";
6008
+ import { z as z2 } from "zod";
6009
+ var flagsSchema2 = z2.object({
6010
+ bucket: z2.array(bucketTypeSchema3).optional(),
6011
+ key: z2.array(z2.string()).optional(),
6012
+ file: z2.array(z2.string()).optional(),
6013
+ apiKey: z2.string().optional(),
6014
+ force: z2.boolean().optional(),
6015
+ frozen: z2.boolean().optional(),
6016
+ verbose: z2.boolean().optional(),
6017
+ strict: z2.boolean().optional(),
6018
+ interactive: z2.boolean().default(false),
6019
+ concurrency: z2.number().positive().default(10),
6020
+ debug: z2.boolean().default(false),
6021
+ sourceLocale: z2.string().optional(),
6022
+ targetLocale: z2.array(z2.string()).optional()
6023
+ });
6024
+
6025
+ // src/cli/cmd/run/_render.ts
6026
+ import chalk12 from "chalk";
6027
+ import figlet from "figlet";
6028
+ import { vice } from "gradient-string";
6029
+ import readline2 from "readline";
6030
+ async function renderClear() {
6031
+ console.log("\x1Bc");
6032
+ }
6033
+ async function renderSpacer() {
6034
+ console.log(" ");
6035
+ }
6036
+ async function renderBanner() {
6037
+ console.log(
6038
+ vice(
6039
+ figlet.textSync("LINGO.DEV", {
6040
+ font: "ANSI Shadow",
6041
+ horizontalLayout: "default",
6042
+ verticalLayout: "default"
6043
+ })
6044
+ )
6045
+ );
6046
+ }
6047
+ async function renderHero() {
6048
+ console.log(
6049
+ `\u26A1\uFE0F ${chalk12.hex(colors.green)("Lingo.dev")} - open-source, AI-powered i18n CLI for web & mobile localization.`
6050
+ );
6051
+ console.log("");
6052
+ const label1 = "\u2B50 GitHub Repo:";
6053
+ const label2 = "\u{1F4DA} Docs:";
6054
+ const label3 = "\u{1F4AC} 24/7 Support:";
6055
+ const maxLabelWidth = 17;
6056
+ console.log(
6057
+ `${chalk12.hex(colors.blue)(label1.padEnd(maxLabelWidth))} ${chalk12.hex(colors.blue)("https://lingo.dev/go/gh")}`
6058
+ );
6059
+ console.log(
6060
+ `${chalk12.hex(colors.blue)(label2.padEnd(maxLabelWidth + 1))} ${chalk12.hex(colors.blue)("https://lingo.dev/go/docs")}`
6061
+ );
6062
+ console.log(
6063
+ `${chalk12.hex(colors.blue)(label3.padEnd(maxLabelWidth + 1))} ${chalk12.hex(colors.blue)("hi@lingo.dev")}`
6064
+ );
6065
+ }
6066
+ async function pauseIfDebug(debug) {
6067
+ if (debug) {
6068
+ await waitForUserPrompt("Press Enter to continue...");
5821
6069
  }
5822
- };
6070
+ }
6071
+ async function waitForUserPrompt(message) {
6072
+ const rl = readline2.createInterface({
6073
+ input: process.stdin,
6074
+ output: process.stdout
6075
+ });
6076
+ return new Promise((resolve) => {
6077
+ rl.question(chalk12.dim(`[${message}]
6078
+ `), () => {
6079
+ rl.close();
6080
+ resolve();
6081
+ });
6082
+ });
6083
+ }
6084
+ async function renderSummary(ctx) {
6085
+ console.log(chalk12.hex(colors.green)("[Done]"));
6086
+ const skippedTasksCount = Array.from(ctx.results.values()).filter(
6087
+ (r) => r.status === "skipped"
6088
+ ).length;
6089
+ console.log(`\u2022 ${chalk12.hex(colors.yellow)(skippedTasksCount)} from cache`);
6090
+ const succeededTasksCount = Array.from(ctx.results.values()).filter(
6091
+ (r) => r.status === "success"
6092
+ ).length;
6093
+ console.log(`\u2022 ${chalk12.hex(colors.yellow)(succeededTasksCount)} processed`);
6094
+ const failedTasksCount = Array.from(ctx.results.values()).filter(
6095
+ (r) => r.status === "error"
6096
+ ).length;
6097
+ console.log(`\u2022 ${chalk12.hex(colors.yellow)(failedTasksCount)} failed`);
6098
+ }
5823
6099
 
5824
- // src/cli/cmd/ci/platforms/github.ts
5825
- import { Octokit } from "octokit";
5826
- import Z9 from "zod";
5827
- var GitHubPlatformKit = class extends PlatformKit {
5828
- _octokit;
5829
- get octokit() {
5830
- if (!this._octokit) {
5831
- this._octokit = new Octokit({ auth: this.platformConfig.ghToken });
6100
+ // src/cli/cmd/run/index.ts
6101
+ import chalk13 from "chalk";
6102
+ var run_default = new Command14().command("run").description("Run Lingo.dev localization engine").helpOption("-h, --help", "Show help").option(
6103
+ "--source-locale <source-locale>",
6104
+ "Locale to use as source locale. Defaults to i18n.json locale.source",
6105
+ (val, prev) => {
6106
+ if (!val) return prev;
6107
+ if (!process.argv.includes("--target-locale")) {
6108
+ console.error(
6109
+ `
6110
+ \u274C ${chalk13.red("Error")}: --source-locale must be used together with --target-locale
6111
+ `
6112
+ );
6113
+ process.exit(1);
5832
6114
  }
5833
- return this._octokit;
5834
- }
5835
- async branchExists({ branch }) {
5836
- return await this.octokit.rest.repos.getBranch({
5837
- branch,
5838
- owner: this.platformConfig.repositoryOwner,
5839
- repo: this.platformConfig.repositoryName
5840
- }).then((r) => r.data).then((v) => !!v).catch((r) => r.status === 404 ? false : Promise.reject(r));
6115
+ return val;
5841
6116
  }
5842
- async getOpenPullRequestNumber({ branch }) {
5843
- return await this.octokit.rest.pulls.list({
5844
- head: `${this.platformConfig.repositoryOwner}:${branch}`,
5845
- owner: this.platformConfig.repositoryOwner,
5846
- repo: this.platformConfig.repositoryName,
5847
- base: this.platformConfig.baseBranchName,
5848
- state: "open"
5849
- }).then(({ data }) => data[0]).then((pr) => pr?.number);
5850
- }
5851
- async closePullRequest({ pullRequestNumber }) {
5852
- await this.octokit.rest.pulls.update({
5853
- pull_number: pullRequestNumber,
5854
- owner: this.platformConfig.repositoryOwner,
5855
- repo: this.platformConfig.repositoryName,
5856
- state: "closed"
5857
- });
5858
- }
5859
- async createPullRequest({
5860
- head,
5861
- title,
5862
- body
5863
- }) {
5864
- return await this.octokit.rest.pulls.create({
5865
- head,
5866
- title,
5867
- body,
5868
- owner: this.platformConfig.repositoryOwner,
5869
- repo: this.platformConfig.repositoryName,
5870
- base: this.platformConfig.baseBranchName
5871
- }).then(({ data }) => data.number);
5872
- }
5873
- async commentOnPullRequest({
5874
- pullRequestNumber,
5875
- body
5876
- }) {
5877
- await this.octokit.rest.issues.createComment({
5878
- issue_number: pullRequestNumber,
5879
- body,
5880
- owner: this.platformConfig.repositoryOwner,
5881
- repo: this.platformConfig.repositoryName
5882
- });
5883
- }
5884
- async gitConfig() {
5885
- const { ghToken, repositoryOwner, repositoryName } = this.platformConfig;
5886
- const { processOwnCommits } = this.config;
5887
- if (ghToken && processOwnCommits) {
5888
- console.log(
5889
- "Using provided GH_TOKEN. This will trigger your CI/CD pipeline to run again."
6117
+ ).option(
6118
+ "--target-locale <target-locale>",
6119
+ "Locale to use as target locale. Defaults to i18n.json locale.targets",
6120
+ (val, prev) => {
6121
+ if (!val) return prev;
6122
+ if (!process.argv.includes("--source-locale")) {
6123
+ console.error(
6124
+ `
6125
+ \u274C ${chalk13.red("Error")}: --target-locale must be used together with --source-locale
6126
+ `
5890
6127
  );
5891
- const url = `https://${ghToken}@github.com/${repositoryOwner}/${repositoryName}.git`;
5892
- super.gitConfig(ghToken, url);
6128
+ process.exit(1);
5893
6129
  }
6130
+ return prev ? [...prev, val] : [val];
5894
6131
  }
5895
- get platformConfig() {
5896
- const env = Z9.object({
5897
- GITHUB_REPOSITORY: Z9.string(),
5898
- GITHUB_REPOSITORY_OWNER: Z9.string(),
5899
- GITHUB_REF_NAME: Z9.string(),
5900
- GITHUB_HEAD_REF: Z9.string(),
5901
- GH_TOKEN: Z9.string().optional()
5902
- }).parse(process.env);
5903
- const baseBranchName = !env.GITHUB_REF_NAME.endsWith("/merge") ? env.GITHUB_REF_NAME : env.GITHUB_HEAD_REF;
5904
- return {
5905
- ghToken: env.GH_TOKEN,
5906
- baseBranchName,
5907
- repositoryOwner: env.GITHUB_REPOSITORY_OWNER,
5908
- repositoryName: env.GITHUB_REPOSITORY.split("/")[1]
6132
+ ).option(
6133
+ "--bucket <bucket>",
6134
+ "Bucket to process",
6135
+ (val, prev) => prev ? [...prev, val] : [val]
6136
+ ).option(
6137
+ "--file <file>",
6138
+ "File to process. Process only files that include this string in their path. Useful if you have a lot of files and want to focus on a specific one. Specify more files separated by commas or spaces.",
6139
+ (val, prev) => prev ? [...prev, val] : [val]
6140
+ ).option(
6141
+ "--key <key>",
6142
+ "Key to process. Process only a specific translation key, useful for updating a single entry",
6143
+ (val, prev) => prev ? [...prev, val] : [val]
6144
+ ).option(
6145
+ "--force",
6146
+ "Ignore lockfile and process all keys, useful for full re-translation"
6147
+ ).option(
6148
+ "--api-key <api-key>",
6149
+ "Explicitly set the API key to use, override the default API key from settings"
6150
+ ).option(
6151
+ "--debug",
6152
+ "Pause execution at start for debugging purposes, waits for user confirmation before proceeding"
6153
+ ).option(
6154
+ "--concurrency <concurrency>",
6155
+ "Number of concurrent tasks to run",
6156
+ (val) => parseInt(val)
6157
+ ).action(async (args) => {
6158
+ try {
6159
+ const ctx = {
6160
+ flags: flagsSchema2.parse(args),
6161
+ config: null,
6162
+ results: /* @__PURE__ */ new Map(),
6163
+ tasks: [],
6164
+ localizer: null
5909
6165
  };
6166
+ await pauseIfDebug(ctx.flags.debug);
6167
+ await renderClear();
6168
+ await renderSpacer();
6169
+ await renderBanner();
6170
+ await renderHero();
6171
+ await renderSpacer();
6172
+ await setup(ctx);
6173
+ await renderSpacer();
6174
+ await plan(ctx);
6175
+ await renderSpacer();
6176
+ await execute(ctx);
6177
+ await renderSpacer();
6178
+ await renderSummary(ctx);
6179
+ await renderSpacer();
6180
+ } catch (error) {
6181
+ process.exit(1);
5910
6182
  }
5911
- buildPullRequestUrl(pullRequestNumber) {
5912
- const { repositoryOwner, repositoryName } = this.platformConfig;
5913
- return `https://github.com/${repositoryOwner}/${repositoryName}/pull/${pullRequestNumber}`;
5914
- }
5915
- };
6183
+ });
5916
6184
 
5917
- // src/cli/cmd/ci/platforms/gitlab.ts
5918
- import { Gitlab } from "@gitbeaker/rest";
5919
- import Z10 from "zod";
5920
- var gl = new Gitlab({ token: "" });
5921
- var GitlabPlatformKit = class extends PlatformKit {
5922
- _gitlab;
5923
- constructor() {
5924
- super();
5925
- process.chdir(this.platformConfig.projectDir);
6185
+ // src/cli/cmd/ci/flows/in-branch.ts
6186
+ var InBranchFlow = class extends IntegrationFlow {
6187
+ async preRun() {
6188
+ this.ora.start("Configuring git");
6189
+ const canContinue = this.configureGit();
6190
+ this.ora.succeed("Git configured");
6191
+ return canContinue;
5926
6192
  }
5927
- get gitlab() {
5928
- if (!this._gitlab) {
5929
- this._gitlab = new Gitlab({
5930
- token: this.platformConfig.glToken || ""
5931
- });
6193
+ async run(options) {
6194
+ this.ora.start("Running Lingo.dev");
6195
+ await this.runLingoDotDev(options.parallel);
6196
+ this.ora.succeed("Done running Lingo.dev");
6197
+ execSync(`rm -f i18n.cache`, { stdio: "inherit" });
6198
+ this.ora.start("Checking for changes");
6199
+ const hasChanges = this.checkCommitableChanges();
6200
+ this.ora.succeed(hasChanges ? "Changes detected" : "No changes detected");
6201
+ if (hasChanges) {
6202
+ this.ora.start("Committing changes");
6203
+ execSync(`git add .`, { stdio: "inherit" });
6204
+ execSync(`git status --porcelain`, { stdio: "inherit" });
6205
+ execSync(
6206
+ `git commit -m ${escapeShellArg(this.platformKit.config.commitMessage)} --no-verify`,
6207
+ {
6208
+ stdio: "inherit"
6209
+ }
6210
+ );
6211
+ this.ora.succeed("Changes committed");
6212
+ this.ora.start("Pushing changes to remote");
6213
+ const currentBranch = this.i18nBranchName ?? this.platformKit.platformConfig.baseBranchName;
6214
+ execSync(
6215
+ `git push origin ${currentBranch} ${options.force ? "--force" : ""}`,
6216
+ {
6217
+ stdio: "inherit"
6218
+ }
6219
+ );
6220
+ this.ora.succeed("Changes pushed to remote");
5932
6221
  }
5933
- return this._gitlab;
6222
+ return hasChanges;
5934
6223
  }
5935
- get platformConfig() {
5936
- const env = Z10.object({
5937
- GL_TOKEN: Z10.string().optional(),
5938
- CI_COMMIT_BRANCH: Z10.string(),
5939
- CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: Z10.string().optional(),
5940
- CI_PROJECT_NAMESPACE: Z10.string(),
5941
- CI_PROJECT_NAME: Z10.string(),
5942
- CI_PROJECT_ID: Z10.string(),
5943
- CI_PROJECT_DIR: Z10.string(),
5944
- CI_REPOSITORY_URL: Z10.string()
5945
- }).parse(process.env);
5946
- const config = {
5947
- glToken: env.GL_TOKEN,
5948
- baseBranchName: env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME ?? env.CI_COMMIT_BRANCH,
5949
- repositoryOwner: env.CI_PROJECT_NAMESPACE,
5950
- repositoryName: env.CI_PROJECT_NAME,
5951
- gitlabProjectId: env.CI_PROJECT_ID,
5952
- projectDir: env.CI_PROJECT_DIR,
5953
- reporitoryUrl: env.CI_REPOSITORY_URL
5954
- };
5955
- return config;
6224
+ checkCommitableChanges() {
6225
+ return execSync('git status --porcelain || echo "has_changes"', {
6226
+ encoding: "utf8"
6227
+ }).length > 0;
5956
6228
  }
5957
- async branchExists({ branch }) {
6229
+ async runLingoDotDev(isParallel) {
5958
6230
  try {
5959
- await this.gitlab.Branches.show(
5960
- this.platformConfig.gitlabProjectId,
5961
- branch
5962
- );
5963
- return true;
5964
- } catch {
5965
- return false;
6231
+ if (!isParallel) {
6232
+ await i18n_default.exitOverride().parseAsync(["--api-key", this.platformKit.config.replexicaApiKey], {
6233
+ from: "user"
6234
+ });
6235
+ } else {
6236
+ await run_default.exitOverride().parseAsync(["--api-key", this.platformKit.config.replexicaApiKey], {
6237
+ from: "user"
6238
+ });
6239
+ }
6240
+ } catch (err) {
6241
+ if (err.code === "commander.helpDisplayed") return;
6242
+ throw err;
5966
6243
  }
5967
6244
  }
5968
- async getOpenPullRequestNumber({
5969
- branch
5970
- }) {
5971
- const mergeRequests = await this.gitlab.MergeRequests.all({
5972
- projectId: this.platformConfig.gitlabProjectId,
5973
- sourceBranch: branch,
5974
- state: "opened"
5975
- });
5976
- return mergeRequests[0]?.iid;
5977
- }
5978
- async closePullRequest({
5979
- pullRequestNumber
5980
- }) {
5981
- await this.gitlab.MergeRequests.edit(
5982
- this.platformConfig.gitlabProjectId,
5983
- pullRequestNumber,
5984
- {
5985
- stateEvent: "close"
6245
+ configureGit() {
6246
+ const { processOwnCommits } = this.platformKit.config;
6247
+ const { baseBranchName } = this.platformKit.platformConfig;
6248
+ this.ora.info(`Current working directory:`);
6249
+ execSync(`pwd`, { stdio: "inherit" });
6250
+ execSync(`ls -la`, { stdio: "inherit" });
6251
+ execSync(`git config --global safe.directory ${process.cwd()}`);
6252
+ execSync(`git config user.name "${gitConfig.userName}"`);
6253
+ execSync(`git config user.email "${gitConfig.userEmail}"`);
6254
+ this.platformKit?.gitConfig();
6255
+ execSync(`git fetch origin ${baseBranchName}`, { stdio: "inherit" });
6256
+ execSync(`git checkout ${baseBranchName} --`, { stdio: "inherit" });
6257
+ if (!processOwnCommits) {
6258
+ const currentAuthor = `${gitConfig.userName} <${gitConfig.userEmail}>`;
6259
+ const authorOfLastCommit = execSync(
6260
+ `git log -1 --pretty=format:'%an <%ae>'`
6261
+ ).toString();
6262
+ if (authorOfLastCommit === currentAuthor) {
6263
+ this.ora.warn(
6264
+ `The last commit was already made by ${currentAuthor}, so this run will be skipped, as running again would have no effect. See docs: https://docs.lingo.dev/ci-action/overview`
6265
+ );
6266
+ return false;
5986
6267
  }
6268
+ }
6269
+ const workingDir = path15.resolve(
6270
+ process.cwd(),
6271
+ this.platformKit.config.workingDir
5987
6272
  );
5988
- }
5989
- async createPullRequest({
5990
- head,
5991
- title,
5992
- body
5993
- }) {
5994
- const mr = await this.gitlab.MergeRequests.create(
5995
- this.platformConfig.gitlabProjectId,
5996
- head,
5997
- this.platformConfig.baseBranchName,
5998
- title,
5999
- {
6000
- description: body
6001
- }
6002
- );
6003
- return mr.iid;
6004
- }
6005
- async commentOnPullRequest({
6006
- pullRequestNumber,
6007
- body
6008
- }) {
6009
- await this.gitlab.MergeRequestNotes.create(
6010
- this.platformConfig.gitlabProjectId,
6011
- pullRequestNumber,
6012
- body
6013
- );
6014
- }
6015
- gitConfig() {
6016
- const glToken = this.platformConfig.glToken;
6017
- const url = `https://oauth2:${glToken}@gitlab.com/${this.platformConfig.repositoryOwner}/${this.platformConfig.repositoryName}.git`;
6018
- super.gitConfig(glToken, url);
6019
- }
6020
- buildPullRequestUrl(pullRequestNumber) {
6021
- return `https://gitlab.com/${this.platformConfig.repositoryOwner}/${this.platformConfig.repositoryName}/-/merge_requests/${pullRequestNumber}`;
6273
+ if (workingDir !== process.cwd()) {
6274
+ this.ora.info(
6275
+ `Changing to working directory: ${this.platformKit.config.workingDir}`
6276
+ );
6277
+ process.chdir(workingDir);
6278
+ }
6279
+ return true;
6022
6280
  }
6023
6281
  };
6024
6282
 
6025
- // src/cli/cmd/ci/platforms/index.ts
6026
- var getPlatformKit = () => {
6027
- if (process.env.BITBUCKET_PIPELINE_UUID) {
6028
- return new BitbucketPlatformKit();
6283
+ // src/cli/cmd/ci/flows/pull-request.ts
6284
+ var PullRequestFlow = class extends InBranchFlow {
6285
+ async preRun() {
6286
+ const canContinue = await super.preRun?.();
6287
+ if (!canContinue) {
6288
+ return false;
6289
+ }
6290
+ this.ora.start("Calculating automated branch name");
6291
+ this.i18nBranchName = this.calculatePrBranchName();
6292
+ this.ora.succeed(
6293
+ `Automated branch name calculated: ${this.i18nBranchName}`
6294
+ );
6295
+ this.ora.start("Checking if branch exists");
6296
+ const branchExists = await this.checkBranchExistance(this.i18nBranchName);
6297
+ this.ora.succeed(branchExists ? "Branch exists" : "Branch does not exist");
6298
+ if (branchExists) {
6299
+ this.ora.start(`Checking out branch ${this.i18nBranchName}`);
6300
+ this.checkoutI18nBranch(this.i18nBranchName);
6301
+ this.ora.succeed(`Checked out branch ${this.i18nBranchName}`);
6302
+ this.ora.start(
6303
+ `Syncing with ${this.platformKit.platformConfig.baseBranchName}`
6304
+ );
6305
+ this.syncI18nBranch();
6306
+ this.ora.succeed(`Checked out and synced branch ${this.i18nBranchName}`);
6307
+ } else {
6308
+ this.ora.start(`Creating branch ${this.i18nBranchName}`);
6309
+ this.createI18nBranch(this.i18nBranchName);
6310
+ this.ora.succeed(`Created branch ${this.i18nBranchName}`);
6311
+ }
6312
+ return true;
6029
6313
  }
6030
- if (process.env.GITHUB_ACTION) {
6031
- return new GitHubPlatformKit();
6314
+ async run(options) {
6315
+ return super.run({
6316
+ force: true,
6317
+ ...options
6318
+ });
6032
6319
  }
6033
- if (process.env.GITLAB_CI) {
6034
- return new GitlabPlatformKit();
6320
+ async postRun() {
6321
+ if (!this.i18nBranchName) {
6322
+ throw new Error(
6323
+ "i18nBranchName is not set. Did you forget to call preRun?"
6324
+ );
6325
+ }
6326
+ this.ora.start("Checking if PR already exists");
6327
+ const pullRequestNumber = await this.ensureFreshPr(this.i18nBranchName);
6328
+ this.ora.succeed(
6329
+ `Pull request ready: ${this.platformKit.buildPullRequestUrl(pullRequestNumber)}`
6330
+ );
6035
6331
  }
6036
- throw new Error("This platform is not supported");
6037
- };
6038
-
6039
- // src/cli/cmd/ci/index.ts
6040
- var ci_default = new Command14().command("ci").description("Run Lingo.dev CI/CD action").helpOption("-h, --help", "Show help").option("--api-key <key>", "API key").option("--pull-request [boolean]", "Create a pull request with the changes").option("--commit-message <message>", "Commit message").option("--pull-request-title <title>", "Pull request title").option("--working-directory <dir>", "Working directory").option(
6041
- "--process-own-commits [boolean]",
6042
- "Process commits made by this action"
6043
- ).action(async (options) => {
6044
- const settings = getSettings(options.apiKey);
6045
- if (!settings.auth.apiKey) {
6046
- console.error("No API key provided");
6047
- return;
6332
+ calculatePrBranchName() {
6333
+ return `lingo.dev/${this.platformKit.platformConfig.baseBranchName}`;
6048
6334
  }
6049
- const authenticator = createAuthenticator({
6050
- apiUrl: settings.auth.apiUrl,
6051
- apiKey: settings.auth.apiKey
6052
- });
6053
- const auth = await authenticator.whoami();
6054
- if (!auth) {
6055
- console.error("Not authenticated");
6056
- return;
6335
+ async checkBranchExistance(prBranchName) {
6336
+ return this.platformKit.branchExists({
6337
+ branch: prBranchName
6338
+ });
6057
6339
  }
6058
- const env = {
6059
- LINGODOTDEV_API_KEY: settings.auth.apiKey,
6060
- LINGODOTDEV_PULL_REQUEST: options.pullRequest?.toString() || "false",
6061
- ...options.commitMessage && {
6062
- LINGODOTDEV_COMMIT_MESSAGE: options.commitMessage
6063
- },
6064
- ...options.pullRequestTitle && {
6065
- LINGODOTDEV_PULL_REQUEST_TITLE: options.pullRequestTitle
6066
- },
6067
- ...options.workingDirectory && {
6068
- LINGODOTDEV_WORKING_DIRECTORY: options.workingDirectory
6069
- },
6070
- ...options.processOwnCommits && {
6071
- LINGODOTDEV_PROCESS_OWN_COMMITS: options.processOwnCommits.toString()
6340
+ async ensureFreshPr(i18nBranchName) {
6341
+ this.ora.start(
6342
+ `Checking for existing PR with head ${i18nBranchName} and base ${this.platformKit.platformConfig.baseBranchName}`
6343
+ );
6344
+ let prNumber = await this.platformKit.getOpenPullRequestNumber({
6345
+ branch: i18nBranchName
6346
+ });
6347
+ if (prNumber) {
6348
+ this.ora.succeed(`Existing PR found: #${prNumber}`);
6349
+ } else {
6350
+ this.ora.start(`Creating new PR`);
6351
+ prNumber = await this.platformKit.createPullRequest({
6352
+ head: i18nBranchName,
6353
+ title: this.platformKit.config.pullRequestTitle,
6354
+ body: this.getPrBodyContent()
6355
+ });
6356
+ this.ora.succeed(`Created new PR: #${prNumber}`);
6072
6357
  }
6073
- };
6074
- process.env = { ...process.env, ...env };
6075
- const ora = createOra();
6076
- const platformKit = getPlatformKit();
6077
- const { isPullRequestMode } = platformKit.config;
6078
- ora.info(`Pull request mode: ${isPullRequestMode ? "on" : "off"}`);
6079
- const flow = isPullRequestMode ? new PullRequestFlow(ora, platformKit) : new InBranchFlow(ora, platformKit);
6080
- const canRun = await flow.preRun?.();
6081
- if (canRun === false) {
6082
- return;
6358
+ return prNumber;
6083
6359
  }
6084
- const hasChanges = await flow.run();
6085
- if (!hasChanges) {
6086
- return;
6360
+ checkoutI18nBranch(i18nBranchName) {
6361
+ execSync2(`git fetch origin ${i18nBranchName}`, { stdio: "inherit" });
6362
+ execSync2(`git checkout -b ${i18nBranchName}`, {
6363
+ stdio: "inherit"
6364
+ });
6087
6365
  }
6088
- await flow.postRun?.();
6089
- });
6090
-
6091
- // src/cli/cmd/status.ts
6092
- import { bucketTypeSchema as bucketTypeSchema3, localeCodeSchema as localeCodeSchema2, resolveOverriddenLocale as resolveOverriddenLocale6 } from "@lingo.dev/_spec";
6093
- import { Command as Command15 } from "interactive-commander";
6094
- import Z11 from "zod";
6095
- import Ora8 from "ora";
6096
- import chalk6 from "chalk";
6097
- import Table from "cli-table3";
6098
- var status_default = new Command15().command("status").description("Show the status of the localization process").helpOption("-h, --help", "Show help").option("--locale <locale>", "Locale to process", (val, prev) => prev ? [...prev, val] : [val]).option("--bucket <bucket>", "Bucket to process", (val, prev) => prev ? [...prev, val] : [val]).option(
6099
- "--file [files...]",
6100
- "File to process. Process only a specific path, may contain asterisk * to match multiple files."
6101
- ).option("--force", "Ignore lockfile and process all keys, useful for estimating full re-translation").option("--verbose", "Show detailed output including key-level word counts").option("--api-key <api-key>", "Explicitly set the API key to use, override the default API key from settings").action(async function(options) {
6102
- const ora = Ora8();
6103
- const flags = parseFlags2(options);
6104
- let authId = null;
6105
- try {
6106
- ora.start("Loading configuration...");
6107
- const i18nConfig = getConfig();
6108
- const settings = getSettings(flags.apiKey);
6109
- ora.succeed("Configuration loaded");
6366
+ createI18nBranch(i18nBranchName) {
6110
6367
  try {
6111
- ora.start("Checking authentication status...");
6112
- const auth = await tryAuthenticate(settings);
6113
- if (auth) {
6114
- authId = auth.id;
6115
- ora.succeed(`Authenticated as ${auth.email}`);
6116
- } else {
6117
- ora.info(
6118
- "Not authenticated. Continuing without authentication. (Run `lingo.dev auth --login` to authenticate)"
6119
- );
6120
- }
6368
+ execSync2(
6369
+ `git fetch origin ${this.platformKit.platformConfig.baseBranchName}`,
6370
+ { stdio: "inherit" }
6371
+ );
6372
+ execSync2(
6373
+ `git checkout -b ${i18nBranchName} origin/${this.platformKit.platformConfig.baseBranchName}`,
6374
+ {
6375
+ stdio: "inherit"
6376
+ }
6377
+ );
6121
6378
  } catch (error) {
6122
- ora.info("Authentication failed. Continuing without authentication.");
6123
- }
6124
- ora.start("Validating localization configuration...");
6125
- validateParams2(i18nConfig, flags);
6126
- ora.succeed("Localization configuration is valid");
6127
- trackEvent(authId || "status", "cmd.status.start", {
6128
- i18nConfig,
6129
- flags
6130
- });
6131
- let buckets = getBuckets(i18nConfig);
6132
- if (flags.bucket?.length) {
6133
- buckets = buckets.filter((bucket) => flags.bucket.includes(bucket.type));
6134
- }
6135
- ora.succeed("Buckets retrieved");
6136
- if (flags.file?.length) {
6137
- buckets = buckets.map((bucket) => {
6138
- const paths = bucket.paths.filter((path16) => flags.file.find((file) => path16.pathPattern?.match(file)));
6139
- return { ...bucket, paths };
6140
- }).filter((bucket) => bucket.paths.length > 0);
6141
- if (buckets.length === 0) {
6142
- ora.fail("No buckets found. All buckets were filtered out by --file option.");
6143
- process.exit(1);
6144
- } else {
6145
- ora.info(`\x1B[36mProcessing only filtered buckets:\x1B[0m`);
6146
- buckets.map((bucket) => {
6147
- ora.info(` ${bucket.type}:`);
6148
- bucket.paths.forEach((path16) => {
6149
- ora.info(` - ${path16.pathPattern}`);
6150
- });
6151
- });
6152
- }
6379
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
6380
+ this.ora.fail(`Failed to create branch: ${errorMessage}`);
6381
+ this.ora.info(`
6382
+ Troubleshooting tips:
6383
+ 1. Make sure you have permission to create branches
6384
+ 2. Check if the branch already exists locally (try 'git branch -a')
6385
+ 3. Verify connectivity to remote repository
6386
+ `);
6387
+ throw new Error(`Branch creation failed: ${errorMessage}`);
6153
6388
  }
6154
- const targetLocales = flags.locale?.length ? flags.locale : i18nConfig.locale.targets;
6155
- let totalSourceKeyCount = 0;
6156
- let uniqueKeysToTranslate = 0;
6157
- let totalExistingTranslations = 0;
6158
- const totalWordCount = /* @__PURE__ */ new Map();
6159
- const languageStats = {};
6160
- for (const locale of targetLocales) {
6161
- languageStats[locale] = {
6162
- complete: 0,
6163
- missing: 0,
6164
- updated: 0,
6165
- words: 0
6166
- };
6167
- totalWordCount.set(locale, 0);
6389
+ }
6390
+ syncI18nBranch() {
6391
+ if (!this.i18nBranchName) {
6392
+ throw new Error("i18nBranchName is not set");
6168
6393
  }
6169
- const fileStats = {};
6170
- for (const bucket of buckets) {
6171
- try {
6172
- console.log();
6173
- ora.info(`Analyzing bucket: ${bucket.type}`);
6174
- for (const bucketPath of bucket.paths) {
6175
- const bucketOra = Ora8({ indent: 2 }).info(`Analyzing path: ${bucketPath.pathPattern}`);
6176
- const sourceLocale = resolveOverriddenLocale6(i18nConfig.locale.source, bucketPath.delimiter);
6177
- const bucketLoader = createBucketLoader(
6178
- bucket.type,
6179
- bucketPath.pathPattern,
6180
- {
6181
- isCacheRestore: false,
6182
- defaultLocale: sourceLocale,
6183
- injectLocale: bucket.injectLocale
6184
- },
6185
- bucket.lockedKeys
6186
- );
6187
- bucketLoader.setDefaultLocale(sourceLocale);
6188
- await bucketLoader.init();
6189
- const filePath = bucketPath.pathPattern;
6190
- if (!fileStats[filePath]) {
6191
- fileStats[filePath] = {
6192
- path: filePath,
6193
- sourceKeys: 0,
6194
- wordCount: 0,
6195
- languageStats: {}
6196
- };
6197
- for (const locale of targetLocales) {
6198
- fileStats[filePath].languageStats[locale] = {
6199
- complete: 0,
6200
- missing: 0,
6201
- updated: 0,
6202
- words: 0
6203
- };
6204
- }
6205
- }
6206
- const sourceData = await bucketLoader.pull(sourceLocale);
6207
- const sourceKeys = Object.keys(sourceData);
6208
- fileStats[filePath].sourceKeys = sourceKeys.length;
6209
- totalSourceKeyCount += sourceKeys.length;
6210
- let sourceWordCount = 0;
6211
- for (const key of sourceKeys) {
6212
- const value = sourceData[key];
6213
- if (typeof value === "string") {
6214
- const words = value.trim().split(/\s+/).length;
6215
- sourceWordCount += words;
6216
- }
6217
- }
6218
- fileStats[filePath].wordCount = sourceWordCount;
6219
- for (const _targetLocale of targetLocales) {
6220
- const targetLocale = resolveOverriddenLocale6(_targetLocale, bucketPath.delimiter);
6221
- bucketOra.start(`[${sourceLocale} -> ${targetLocale}] Analyzing translation status...`);
6222
- let targetData = {};
6223
- let fileExists = true;
6224
- try {
6225
- targetData = await bucketLoader.pull(targetLocale);
6226
- } catch (error) {
6227
- fileExists = false;
6228
- bucketOra.info(
6229
- `[${sourceLocale} -> ${targetLocale}] Target file not found, assuming all keys need translation.`
6230
- );
6231
- }
6232
- if (!fileExists) {
6233
- fileStats[filePath].languageStats[targetLocale].missing = sourceKeys.length;
6234
- fileStats[filePath].languageStats[targetLocale].words = sourceWordCount;
6235
- languageStats[targetLocale].missing += sourceKeys.length;
6236
- languageStats[targetLocale].words += sourceWordCount;
6237
- totalWordCount.set(targetLocale, (totalWordCount.get(targetLocale) || 0) + sourceWordCount);
6238
- bucketOra.succeed(
6239
- `[${sourceLocale} -> ${targetLocale}] ${chalk6.red(`0% complete`)} (0/${sourceKeys.length} keys) - file not found`
6240
- );
6241
- continue;
6242
- }
6243
- const deltaProcessor = createDeltaProcessor(bucketPath.pathPattern);
6244
- const checksums = await deltaProcessor.loadChecksums();
6245
- const delta = await deltaProcessor.calculateDelta({
6246
- sourceData,
6247
- targetData,
6248
- checksums
6249
- });
6250
- const missingKeys = delta.added;
6251
- const updatedKeys = delta.updated;
6252
- const completeKeys = sourceKeys.filter((key) => !missingKeys.includes(key) && !updatedKeys.includes(key));
6253
- let wordsToTranslate = 0;
6254
- const keysToProcess = flags.force ? sourceKeys : [...missingKeys, ...updatedKeys];
6255
- for (const key of keysToProcess) {
6256
- const value = sourceData[String(key)];
6257
- if (typeof value === "string") {
6258
- const words = value.trim().split(/\s+/).length;
6259
- wordsToTranslate += words;
6260
- }
6261
- }
6262
- fileStats[filePath].languageStats[targetLocale].missing = missingKeys.length;
6263
- fileStats[filePath].languageStats[targetLocale].updated = updatedKeys.length;
6264
- fileStats[filePath].languageStats[targetLocale].complete = completeKeys.length;
6265
- fileStats[filePath].languageStats[targetLocale].words = wordsToTranslate;
6266
- languageStats[targetLocale].missing += missingKeys.length;
6267
- languageStats[targetLocale].updated += updatedKeys.length;
6268
- languageStats[targetLocale].complete += completeKeys.length;
6269
- languageStats[targetLocale].words += wordsToTranslate;
6270
- totalWordCount.set(targetLocale, (totalWordCount.get(targetLocale) || 0) + wordsToTranslate);
6271
- const totalKeysInFile = sourceKeys.length;
6272
- const completionPercent = (completeKeys.length / totalKeysInFile * 100).toFixed(1);
6273
- if (missingKeys.length === 0 && updatedKeys.length === 0) {
6274
- bucketOra.succeed(
6275
- `[${sourceLocale} -> ${targetLocale}] ${chalk6.green(`100% complete`)} (${completeKeys.length}/${totalKeysInFile} keys)`
6276
- );
6277
- } else {
6278
- const message = `[${sourceLocale} -> ${targetLocale}] ${parseFloat(completionPercent) > 50 ? chalk6.yellow(`${completionPercent}% complete`) : chalk6.red(`${completionPercent}% complete`)} (${completeKeys.length}/${totalKeysInFile} keys)`;
6279
- bucketOra.succeed(message);
6280
- if (flags.verbose) {
6281
- if (missingKeys.length > 0) {
6282
- console.log(` ${chalk6.red(`Missing:`)} ${missingKeys.length} keys, ~${wordsToTranslate} words`);
6283
- console.log(
6284
- ` ${chalk6.dim(`Example missing: ${missingKeys.slice(0, 2).join(", ")}${missingKeys.length > 2 ? "..." : ""}`)}`
6285
- );
6286
- }
6287
- if (updatedKeys.length > 0) {
6288
- console.log(` ${chalk6.yellow(`Updated:`)} ${updatedKeys.length} keys that changed in source`);
6289
- }
6290
- }
6291
- }
6292
- }
6293
- }
6294
- } catch (error) {
6295
- ora.fail(`Failed to analyze bucket ${bucket.type}: ${error.message}`);
6296
- }
6297
- }
6298
- const totalKeysNeedingTranslation = Object.values(languageStats).reduce((sum, stats) => {
6299
- return sum + stats.missing + stats.updated;
6300
- }, 0);
6301
- const totalCompletedKeys = totalSourceKeyCount - totalKeysNeedingTranslation / targetLocales.length;
6302
- console.log();
6303
- ora.succeed(chalk6.green(`Localization status completed.`));
6304
- console.log(chalk6.bold.cyan(`
6305
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`));
6306
- console.log(chalk6.bold.cyan(`\u2551 LOCALIZATION STATUS REPORT \u2551`));
6307
- console.log(chalk6.bold.cyan(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`));
6308
- console.log(chalk6.bold(`
6309
- \u{1F4DD} SOURCE CONTENT:`));
6310
- console.log(`\u2022 Source language: ${chalk6.green(i18nConfig.locale.source)}`);
6311
- console.log(`\u2022 Source keys: ${chalk6.yellow(totalSourceKeyCount.toString())} keys across all files`);
6312
- console.log(chalk6.bold(`
6313
- \u{1F310} LANGUAGE BY LANGUAGE BREAKDOWN:`));
6314
- const table = new Table({
6315
- head: ["Language", "Status", "Complete", "Missing", "Updated", "Total Keys", "Words to Translate"],
6316
- style: {
6317
- head: ["white"],
6318
- // White color for headers
6319
- border: []
6320
- // No color for borders
6321
- },
6322
- colWidths: [12, 20, 18, 12, 12, 12, 15]
6323
- // Explicit column widths, making Status column wider
6324
- });
6325
- let totalWordsToTranslate = 0;
6326
- for (const locale of targetLocales) {
6327
- const stats = languageStats[locale];
6328
- const percentComplete = (stats.complete / totalSourceKeyCount * 100).toFixed(1);
6329
- const totalNeeded = stats.missing + stats.updated;
6330
- let statusText;
6331
- let statusColor;
6332
- if (stats.missing === totalSourceKeyCount) {
6333
- statusText = "\u{1F534} Not started";
6334
- statusColor = chalk6.red;
6335
- } else if (stats.missing === 0 && stats.updated === 0) {
6336
- statusText = "\u2705 Complete";
6337
- statusColor = chalk6.green;
6338
- } else if (parseFloat(percentComplete) > 80) {
6339
- statusText = "\u{1F7E1} Almost done";
6340
- statusColor = chalk6.yellow;
6341
- } else if (parseFloat(percentComplete) > 0) {
6342
- statusText = "\u{1F7E0} In progress";
6343
- statusColor = chalk6.yellow;
6344
- } else {
6345
- statusText = "\u{1F534} Not started";
6346
- statusColor = chalk6.red;
6347
- }
6348
- const words = totalWordCount.get(locale) || 0;
6349
- totalWordsToTranslate += words;
6350
- table.push([
6351
- locale,
6352
- statusColor(statusText),
6353
- `${stats.complete}/${totalSourceKeyCount} (${percentComplete}%)`,
6354
- stats.missing > 0 ? chalk6.red(stats.missing.toString()) : "0",
6355
- stats.updated > 0 ? chalk6.yellow(stats.updated.toString()) : "0",
6356
- totalNeeded > 0 ? chalk6.magenta(totalNeeded.toString()) : "0",
6357
- words > 0 ? `~${words.toLocaleString()}` : "0"
6358
- ]);
6359
- }
6360
- console.log(table.toString());
6361
- console.log(chalk6.bold(`
6362
- \u{1F4CA} USAGE ESTIMATE:`));
6363
- console.log(
6364
- `\u2022 WORDS TO BE CONSUMED: ~${chalk6.yellow.bold(totalWordsToTranslate.toLocaleString())} words across all languages`
6394
+ this.ora.start(
6395
+ `Fetching latest changes from ${this.platformKit.platformConfig.baseBranchName}`
6365
6396
  );
6366
- console.log(` (Words are counted from source language for keys that need translation in target languages)`);
6367
- if (targetLocales.length > 1) {
6368
- console.log(`\u2022 Per-language breakdown:`);
6369
- for (const locale of targetLocales) {
6370
- const words = totalWordCount.get(locale) || 0;
6371
- const percent = (words / totalWordsToTranslate * 100).toFixed(1);
6372
- console.log(` - ${locale}: ~${words.toLocaleString()} words (${percent}% of total)`);
6373
- }
6374
- }
6375
- if (flags.confirm && Object.keys(fileStats).length > 0) {
6376
- console.log(chalk6.bold(`
6377
- \u{1F4D1} BREAKDOWN BY FILE:`));
6378
- Object.entries(fileStats).sort((a, b) => b[1].wordCount - a[1].wordCount).forEach(([path16, stats]) => {
6379
- if (stats.sourceKeys === 0) return;
6380
- console.log(chalk6.bold(`
6381
- \u2022 ${path16}:`));
6382
- console.log(` ${stats.sourceKeys} source keys, ~${stats.wordCount.toLocaleString()} source words`);
6383
- const fileTable = new Table({
6384
- head: ["Language", "Status", "Details"],
6385
- style: {
6386
- head: ["white"],
6387
- border: []
6388
- },
6389
- colWidths: [12, 20, 50]
6390
- // Explicit column widths for file detail table
6391
- });
6392
- for (const locale of targetLocales) {
6393
- const langStats = stats.languageStats[locale];
6394
- const complete = langStats.complete;
6395
- const total = stats.sourceKeys;
6396
- const completion = (complete / total * 100).toFixed(1);
6397
- let status = "\u2705 Complete";
6398
- let statusColor = chalk6.green;
6399
- if (langStats.missing === total) {
6400
- status = "\u274C Not started";
6401
- statusColor = chalk6.red;
6402
- } else if (langStats.missing > 0 || langStats.updated > 0) {
6403
- status = `\u26A0\uFE0F ${completion}% complete`;
6404
- statusColor = chalk6.yellow;
6405
- }
6406
- let details = "";
6407
- if (langStats.missing > 0 || langStats.updated > 0) {
6408
- const parts = [];
6409
- if (langStats.missing > 0) parts.push(`${langStats.missing} missing`);
6410
- if (langStats.updated > 0) parts.push(`${langStats.updated} changed`);
6411
- details = `${parts.join(", ")}, ~${langStats.words} words`;
6412
- } else {
6413
- details = "All keys translated";
6414
- }
6415
- fileTable.push([locale, statusColor(status), details]);
6416
- }
6417
- console.log(fileTable.toString());
6418
- });
6419
- }
6420
- const completeLanguages = targetLocales.filter(
6421
- (locale) => languageStats[locale].missing === 0 && languageStats[locale].updated === 0
6397
+ execSync2(
6398
+ `git fetch origin ${this.platformKit.platformConfig.baseBranchName}`,
6399
+ { stdio: "inherit" }
6422
6400
  );
6423
- const missingLanguages = targetLocales.filter((locale) => languageStats[locale].complete === 0);
6424
- console.log(chalk6.bold.green(`
6425
- \u{1F4A1} OPTIMIZATION TIPS:`));
6426
- if (missingLanguages.length > 0) {
6427
- console.log(
6428
- `\u2022 ${chalk6.yellow(missingLanguages.join(", "))} ${missingLanguages.length === 1 ? "has" : "have"} no translations yet`
6401
+ this.ora.succeed(
6402
+ `Fetched latest changes from ${this.platformKit.platformConfig.baseBranchName}`
6403
+ );
6404
+ try {
6405
+ this.ora.start("Attempting to rebase branch");
6406
+ execSync2(
6407
+ `git rebase origin/${this.platformKit.platformConfig.baseBranchName}`,
6408
+ { stdio: "inherit" }
6429
6409
  );
6430
- }
6431
- if (completeLanguages.length > 0) {
6432
- console.log(
6433
- `\u2022 ${chalk6.green(completeLanguages.join(", "))} ${completeLanguages.length === 1 ? "is" : "are"} completely translated`
6410
+ this.ora.succeed("Successfully rebased branch");
6411
+ } catch (error) {
6412
+ this.ora.warn("Rebase failed, falling back to alternative sync method");
6413
+ this.ora.start("Aborting failed rebase");
6414
+ execSync2("git rebase --abort", { stdio: "inherit" });
6415
+ this.ora.succeed("Aborted failed rebase");
6416
+ this.ora.start(
6417
+ `Resetting to ${this.platformKit.platformConfig.baseBranchName}`
6434
6418
  );
6435
- }
6436
- if (targetLocales.length > 1) {
6437
- console.log(`\u2022 Translating one language at a time reduces complexity`);
6438
- console.log(`\u2022 Try 'lingo.dev@latest i18n --locale ${targetLocales[0]}' to process just one language`);
6439
- }
6440
- trackEvent(authId || "status", "cmd.status.success", {
6441
- i18nConfig,
6442
- flags,
6443
- totalSourceKeyCount,
6444
- languageStats,
6445
- totalWordsToTranslate,
6446
- authenticated: !!authId
6447
- });
6448
- } catch (error) {
6449
- ora.fail(error.message);
6450
- trackEvent(authId || "status", "cmd.status.error", {
6451
- flags,
6452
- error: error.message,
6453
- authenticated: !!authId
6454
- });
6455
- process.exit(1);
6419
+ execSync2(
6420
+ `git reset --hard origin/${this.platformKit.platformConfig.baseBranchName}`,
6421
+ { stdio: "inherit" }
6422
+ );
6423
+ this.ora.succeed(
6424
+ `Reset to ${this.platformKit.platformConfig.baseBranchName}`
6425
+ );
6426
+ this.ora.start("Restoring target files");
6427
+ const targetFiles = ["i18n.lock"];
6428
+ const targetFileNames = execSync2(
6429
+ `npx lingo.dev@latest show files --target ${this.platformKit.platformConfig.baseBranchName}`,
6430
+ { encoding: "utf8" }
6431
+ ).split("\n").filter(Boolean);
6432
+ targetFiles.push(...targetFileNames);
6433
+ execSync2(`git fetch origin ${this.i18nBranchName}`, { stdio: "inherit" });
6434
+ for (const file of targetFiles) {
6435
+ try {
6436
+ execSync2(`git checkout FETCH_HEAD -- ${file}`, { stdio: "inherit" });
6437
+ } catch (error2) {
6438
+ this.ora.warn(`Skipping non-existent file: ${file}`);
6439
+ continue;
6440
+ }
6441
+ }
6442
+ this.ora.succeed("Restored target files");
6443
+ }
6444
+ this.ora.start("Checking for changes to commit");
6445
+ const hasChanges = this.checkCommitableChanges();
6446
+ if (hasChanges) {
6447
+ execSync2("git add .", { stdio: "inherit" });
6448
+ execSync2(
6449
+ `git commit -m "chore: sync with ${this.platformKit.platformConfig.baseBranchName}" --no-verify`,
6450
+ {
6451
+ stdio: "inherit"
6452
+ }
6453
+ );
6454
+ this.ora.succeed("Committed additional changes");
6455
+ } else {
6456
+ this.ora.succeed("No changes to commit");
6457
+ }
6456
6458
  }
6457
- });
6458
- function parseFlags2(options) {
6459
- return Z11.object({
6460
- locale: Z11.array(localeCodeSchema2).optional(),
6461
- bucket: Z11.array(bucketTypeSchema3).optional(),
6462
- force: Z11.boolean().optional(),
6463
- confirm: Z11.boolean().optional(),
6464
- verbose: Z11.boolean().optional(),
6465
- file: Z11.array(Z11.string()).optional(),
6466
- apiKey: Z11.string().optional()
6467
- }).parse(options);
6468
- }
6469
- async function tryAuthenticate(settings) {
6470
- if (!settings.auth.apiKey) {
6471
- return null;
6459
+ getPrBodyContent() {
6460
+ return `
6461
+ Hey team,
6462
+
6463
+ [**Lingo.dev**](https://lingo.dev) here with fresh translations!
6464
+
6465
+ ### In this update
6466
+
6467
+ - Added missing translations
6468
+ - Performed brand voice, context and glossary checks
6469
+ - Enhanced translations using Lingo.dev Localization Engine
6470
+
6471
+ ### Next Steps
6472
+
6473
+ - [ ] Review the changes
6474
+ - [ ] Merge when ready
6475
+ `.trim();
6472
6476
  }
6473
- try {
6474
- const authenticator = createAuthenticator({
6475
- apiKey: settings.auth.apiKey,
6476
- apiUrl: settings.auth.apiUrl
6477
- });
6478
- const user = await authenticator.whoami();
6479
- return user;
6480
- } catch (error) {
6481
- return null;
6477
+ };
6478
+
6479
+ // src/cli/cmd/ci/platforms/bitbucket.ts
6480
+ import { execSync as execSync4 } from "child_process";
6481
+ import bbLib from "bitbucket";
6482
+ import Z8 from "zod";
6483
+
6484
+ // src/cli/cmd/ci/platforms/_base.ts
6485
+ import { execSync as execSync3 } from "child_process";
6486
+ import Z7 from "zod";
6487
+ var defaultMessage = "feat: update translations via @lingodotdev";
6488
+ var PlatformKit = class {
6489
+ gitConfig(token, repoUrl) {
6490
+ if (token && repoUrl) {
6491
+ execSync3(`git remote set-url origin ${repoUrl}`, {
6492
+ stdio: "inherit"
6493
+ });
6494
+ }
6482
6495
  }
6483
- }
6484
- function validateParams2(i18nConfig, flags) {
6485
- if (!i18nConfig) {
6486
- throw new CLIError({
6487
- message: "i18n.json not found. Please run `lingo.dev init` to initialize the project.",
6488
- docUrl: "i18nNotFound"
6489
- });
6490
- } else if (!i18nConfig.buckets || !Object.keys(i18nConfig.buckets).length) {
6491
- throw new CLIError({
6492
- message: "No buckets found in i18n.json. Please add at least one bucket containing i18n content.",
6493
- docUrl: "bucketNotFound"
6494
- });
6495
- } else if (flags.locale?.some((locale) => !i18nConfig.locale.targets.includes(locale))) {
6496
- throw new CLIError({
6497
- message: `One or more specified locales do not exist in i18n.json locale.targets. Please add them to the list and try again.`,
6498
- docUrl: "localeTargetNotFound"
6499
- });
6500
- } else if (flags.bucket?.some((bucket) => !i18nConfig.buckets[bucket])) {
6501
- throw new CLIError({
6502
- message: `One or more specified buckets do not exist in i18n.json. Please add them to the list and try again.`,
6503
- docUrl: "bucketNotFound"
6504
- });
6496
+ get config() {
6497
+ const env = Z7.object({
6498
+ LINGODOTDEV_API_KEY: Z7.string(),
6499
+ LINGODOTDEV_PULL_REQUEST: Z7.preprocess((val) => val === "true" || val === true, Z7.boolean()),
6500
+ LINGODOTDEV_COMMIT_MESSAGE: Z7.string().optional(),
6501
+ LINGODOTDEV_PULL_REQUEST_TITLE: Z7.string().optional(),
6502
+ LINGODOTDEV_WORKING_DIRECTORY: Z7.string().optional(),
6503
+ LINGODOTDEV_PROCESS_OWN_COMMITS: Z7.preprocess((val) => val === "true" || val === true, Z7.boolean()).optional()
6504
+ }).parse(process.env);
6505
+ return {
6506
+ replexicaApiKey: env.LINGODOTDEV_API_KEY,
6507
+ isPullRequestMode: env.LINGODOTDEV_PULL_REQUEST,
6508
+ commitMessage: env.LINGODOTDEV_COMMIT_MESSAGE || defaultMessage,
6509
+ pullRequestTitle: env.LINGODOTDEV_PULL_REQUEST_TITLE || defaultMessage,
6510
+ workingDir: env.LINGODOTDEV_WORKING_DIRECTORY || ".",
6511
+ processOwnCommits: env.LINGODOTDEV_PROCESS_OWN_COMMITS || false
6512
+ };
6505
6513
  }
6506
- }
6507
-
6508
- // src/cli/cmd/may-the-fourth.ts
6509
- import { Command as Command16 } from "interactive-commander";
6510
- import * as cp from "node:child_process";
6511
- import figlet from "figlet";
6512
- import chalk7 from "chalk";
6513
- import { vice } from "gradient-string";
6514
- var colors2 = {
6515
- orange: "#ff6600",
6516
- green: "#6ae300",
6517
- blue: "#0090ff",
6518
- yellow: "#ffcc00",
6519
- grey: "#808080",
6520
- red: "#ff0000"
6521
6514
  };
6522
- var may_the_fourth_default = new Command16().command("may-the-fourth").description("May the Fourth be with you").helpOption("-h, --help", "Show help").action(async () => {
6523
- await renderClear();
6524
- await renderBanner();
6525
- await renderSpacer();
6526
- console.log(chalk7.hex(colors2.yellow)("Loading the Star Wars movie..."));
6527
- await renderSpacer();
6528
- await new Promise((resolve, reject) => {
6529
- const ssh = cp.spawn("ssh", ["starwarstel.net"], {
6530
- stdio: "inherit"
6515
+
6516
+ // src/cli/cmd/ci/platforms/bitbucket.ts
6517
+ var { Bitbucket } = bbLib;
6518
+ var BitbucketPlatformKit = class extends PlatformKit {
6519
+ _bb;
6520
+ get bb() {
6521
+ if (!this._bb) {
6522
+ this._bb = new Bitbucket({
6523
+ auth: { token: this.platformConfig.bbToken || "" }
6524
+ });
6525
+ }
6526
+ return this._bb;
6527
+ }
6528
+ async branchExists({ branch }) {
6529
+ return await this.bb.repositories.getBranch({
6530
+ workspace: this.platformConfig.repositoryOwner,
6531
+ repo_slug: this.platformConfig.repositoryName,
6532
+ name: branch
6533
+ }).then((r) => r.data).then((v) => !!v).catch((r) => r.status === 404 ? false : Promise.reject(r));
6534
+ }
6535
+ async getOpenPullRequestNumber({ branch }) {
6536
+ return await this.bb.repositories.listPullRequests({
6537
+ workspace: this.platformConfig.repositoryOwner,
6538
+ repo_slug: this.platformConfig.repositoryName,
6539
+ state: "OPEN"
6540
+ }).then(({ data: { values } }) => {
6541
+ return values?.find(
6542
+ ({ source, destination }) => source?.branch?.name === branch && destination?.branch?.name === this.platformConfig.baseBranchName
6543
+ );
6544
+ }).then((pr) => pr?.id);
6545
+ }
6546
+ async closePullRequest({ pullRequestNumber }) {
6547
+ await this.bb.repositories.declinePullRequest({
6548
+ workspace: this.platformConfig.repositoryOwner,
6549
+ repo_slug: this.platformConfig.repositoryName,
6550
+ pull_request_id: pullRequestNumber
6531
6551
  });
6532
- ssh.on("close", (code) => {
6533
- if (code !== 0) {
6534
- console.error(`SSH process exited with code ${code}`);
6552
+ }
6553
+ async createPullRequest({
6554
+ title,
6555
+ body,
6556
+ head
6557
+ }) {
6558
+ return await this.bb.repositories.createPullRequest({
6559
+ workspace: this.platformConfig.repositoryOwner,
6560
+ repo_slug: this.platformConfig.repositoryName,
6561
+ _body: {
6562
+ title,
6563
+ description: body,
6564
+ source: { branch: { name: head } },
6565
+ destination: { branch: { name: this.platformConfig.baseBranchName } }
6566
+ }
6567
+ }).then(({ data }) => data.id ?? 0);
6568
+ }
6569
+ async commentOnPullRequest({
6570
+ pullRequestNumber,
6571
+ body
6572
+ }) {
6573
+ await this.bb.repositories.createPullRequestComment({
6574
+ workspace: this.platformConfig.repositoryOwner,
6575
+ repo_slug: this.platformConfig.repositoryName,
6576
+ pull_request_id: pullRequestNumber,
6577
+ _body: {
6578
+ content: {
6579
+ raw: body
6580
+ }
6535
6581
  }
6536
- resolve();
6537
6582
  });
6538
- ssh.on("error", (err) => {
6539
- console.error("Failed to start SSH process:", err);
6540
- reject(err);
6583
+ }
6584
+ async gitConfig() {
6585
+ execSync4("git config --unset http.${BITBUCKET_GIT_HTTP_ORIGIN}.proxy", {
6586
+ stdio: "inherit"
6541
6587
  });
6542
- });
6543
- await renderSpacer();
6544
- console.log(
6545
- `${chalk7.hex(colors2.green)("We hope you enjoyed it! :)")} ${chalk7.hex(colors2.blue)("May the Fourth be with you! \u{1F680}")}`
6546
- );
6547
- await renderSpacer();
6548
- console.log(chalk7.dim(`---`));
6549
- await renderSpacer();
6550
- await renderHero();
6551
- });
6552
- async function renderClear() {
6553
- console.log("\x1Bc");
6554
- }
6555
- async function renderSpacer() {
6556
- console.log(" ");
6557
- }
6558
- async function renderBanner() {
6559
- console.log(
6560
- vice(
6561
- figlet.textSync("LINGO.DEV", {
6562
- font: "ANSI Shadow",
6563
- horizontalLayout: "default",
6564
- verticalLayout: "default"
6565
- })
6566
- )
6567
- );
6568
- }
6569
- async function renderHero() {
6570
- console.log(
6571
- `\u26A1\uFE0F ${chalk7.hex(colors2.green)("Lingo.dev")} - open-source, AI-powered i18n CLI for web & mobile localization.`
6572
- );
6573
- console.log(" ");
6574
- console.log(
6575
- chalk7.hex(colors2.blue)("\u2B50 GitHub Repo: https://lingo.dev/go/gh")
6576
- );
6577
- console.log(chalk7.hex(colors2.blue)("\u{1F4AC} 24/7 Support: hi@lingo.dev"));
6578
- }
6588
+ execSync4(
6589
+ "git config http.${BITBUCKET_GIT_HTTP_ORIGIN}.proxy http://host.docker.internal:29418/",
6590
+ {
6591
+ stdio: "inherit"
6592
+ }
6593
+ );
6594
+ }
6595
+ get platformConfig() {
6596
+ const env = Z8.object({
6597
+ BITBUCKET_BRANCH: Z8.string(),
6598
+ BITBUCKET_REPO_FULL_NAME: Z8.string(),
6599
+ BB_TOKEN: Z8.string().optional()
6600
+ }).parse(process.env);
6601
+ const [repositoryOwner, repositoryName] = env.BITBUCKET_REPO_FULL_NAME.split("/");
6602
+ return {
6603
+ baseBranchName: env.BITBUCKET_BRANCH,
6604
+ repositoryOwner,
6605
+ repositoryName,
6606
+ bbToken: env.BB_TOKEN
6607
+ };
6608
+ }
6609
+ buildPullRequestUrl(pullRequestNumber) {
6610
+ const { repositoryOwner, repositoryName } = this.platformConfig;
6611
+ return `https://bitbucket.org/${repositoryOwner}/${repositoryName}/pull-requests/${pullRequestNumber}`;
6612
+ }
6613
+ };
6579
6614
 
6580
- // package.json
6581
- var package_default = {
6582
- name: "lingo.dev",
6583
- version: "0.93.4",
6584
- description: "Lingo.dev CLI",
6585
- private: false,
6586
- publishConfig: {
6587
- access: "public"
6588
- },
6589
- type: "module",
6590
- sideEffects: false,
6591
- exports: {
6592
- "./cli": {
6593
- types: "./build/cli.d.ts",
6594
- import: "./build/cli.mjs",
6595
- require: "./build/cli.cjs"
6596
- },
6597
- "./sdk": {
6598
- types: "./build/sdk.d.ts",
6599
- import: "./build/sdk.mjs",
6600
- require: "./build/sdk.cjs"
6601
- },
6602
- "./spec": {
6603
- types: "./build/spec.d.ts",
6604
- import: "./build/spec.mjs",
6605
- require: "./build/spec.cjs"
6606
- },
6607
- "./compiler": {
6608
- types: "./build/compiler.d.ts",
6609
- import: "./build/compiler.mjs",
6610
- require: "./build/compiler.cjs"
6611
- },
6612
- "./react": {
6613
- types: "./build/react.d.ts",
6614
- import: "./build/react.mjs",
6615
- require: "./build/react.cjs"
6616
- },
6617
- "./react-client": {
6618
- types: "./build/react/client.d.ts",
6619
- import: "./build/react/client.mjs",
6620
- require: "./build/react/client.cjs"
6621
- },
6622
- "./react/client": {
6623
- types: "./build/react/client.d.ts",
6624
- import: "./build/react/client.mjs",
6625
- require: "./build/react/client.cjs"
6626
- },
6627
- "./react-rsc": {
6628
- types: "./build/react/rsc.d.ts",
6629
- import: "./build/react/rsc.mjs",
6630
- require: "./build/react/rsc.cjs"
6631
- },
6632
- "./react/rsc": {
6633
- types: "./build/react/rsc.d.ts",
6634
- import: "./build/react/rsc.mjs",
6635
- require: "./build/react/rsc.cjs"
6636
- },
6637
- "./react-router": {
6638
- types: "./build/react/react-router.d.ts",
6639
- import: "./build/react/react-router.mjs",
6640
- require: "./build/react/react-router.cjs"
6641
- },
6642
- "./react/react-router": {
6643
- types: "./build/react/react-router.d.ts",
6644
- import: "./build/react/react-router.mjs",
6645
- require: "./build/react/react-router.cjs"
6615
+ // src/cli/cmd/ci/platforms/github.ts
6616
+ import { Octokit } from "octokit";
6617
+ import Z9 from "zod";
6618
+ var GitHubPlatformKit = class extends PlatformKit {
6619
+ _octokit;
6620
+ get octokit() {
6621
+ if (!this._octokit) {
6622
+ this._octokit = new Octokit({ auth: this.platformConfig.ghToken });
6646
6623
  }
6647
- },
6648
- typesVersions: {
6649
- "*": {
6650
- sdk: [
6651
- "./build/sdk.d.ts"
6652
- ],
6653
- cli: [
6654
- "./build/cli.d.ts"
6655
- ],
6656
- spec: [
6657
- "./build/spec.d.ts"
6658
- ],
6659
- compiler: [
6660
- "./build/compiler.d.ts"
6661
- ],
6662
- react: [
6663
- "./build/react.d.ts"
6664
- ],
6665
- "react/client": [
6666
- "./build/react/client.d.ts"
6667
- ],
6668
- "react/rsc": [
6669
- "./build/react/rsc.d.ts"
6670
- ],
6671
- "react/react-router": [
6672
- "./build/react/react-router.d.ts"
6673
- ]
6624
+ return this._octokit;
6625
+ }
6626
+ async branchExists({ branch }) {
6627
+ return await this.octokit.rest.repos.getBranch({
6628
+ branch,
6629
+ owner: this.platformConfig.repositoryOwner,
6630
+ repo: this.platformConfig.repositoryName
6631
+ }).then((r) => r.data).then((v) => !!v).catch((r) => r.status === 404 ? false : Promise.reject(r));
6632
+ }
6633
+ async getOpenPullRequestNumber({ branch }) {
6634
+ return await this.octokit.rest.pulls.list({
6635
+ head: `${this.platformConfig.repositoryOwner}:${branch}`,
6636
+ owner: this.platformConfig.repositoryOwner,
6637
+ repo: this.platformConfig.repositoryName,
6638
+ base: this.platformConfig.baseBranchName,
6639
+ state: "open"
6640
+ }).then(({ data }) => data[0]).then((pr) => pr?.number);
6641
+ }
6642
+ async closePullRequest({ pullRequestNumber }) {
6643
+ await this.octokit.rest.pulls.update({
6644
+ pull_number: pullRequestNumber,
6645
+ owner: this.platformConfig.repositoryOwner,
6646
+ repo: this.platformConfig.repositoryName,
6647
+ state: "closed"
6648
+ });
6649
+ }
6650
+ async createPullRequest({
6651
+ head,
6652
+ title,
6653
+ body
6654
+ }) {
6655
+ return await this.octokit.rest.pulls.create({
6656
+ head,
6657
+ title,
6658
+ body,
6659
+ owner: this.platformConfig.repositoryOwner,
6660
+ repo: this.platformConfig.repositoryName,
6661
+ base: this.platformConfig.baseBranchName
6662
+ }).then(({ data }) => data.number);
6663
+ }
6664
+ async commentOnPullRequest({
6665
+ pullRequestNumber,
6666
+ body
6667
+ }) {
6668
+ await this.octokit.rest.issues.createComment({
6669
+ issue_number: pullRequestNumber,
6670
+ body,
6671
+ owner: this.platformConfig.repositoryOwner,
6672
+ repo: this.platformConfig.repositoryName
6673
+ });
6674
+ }
6675
+ async gitConfig() {
6676
+ const { ghToken, repositoryOwner, repositoryName } = this.platformConfig;
6677
+ const { processOwnCommits } = this.config;
6678
+ if (ghToken && processOwnCommits) {
6679
+ console.log(
6680
+ "Using provided GH_TOKEN. This will trigger your CI/CD pipeline to run again."
6681
+ );
6682
+ const url = `https://${ghToken}@github.com/${repositoryOwner}/${repositoryName}.git`;
6683
+ super.gitConfig(ghToken, url);
6674
6684
  }
6675
- },
6676
- bin: {
6677
- "lingo.dev": "./bin/cli.mjs"
6678
- },
6679
- files: [
6680
- "bin",
6681
- "build"
6682
- ],
6683
- scripts: {
6684
- "lingo.dev": "node --inspect=9229 ./bin/cli.mjs",
6685
- dev: "tsup --watch",
6686
- build: "tsc --noEmit && tsup",
6687
- test: "vitest run",
6688
- "test:watch": "vitest",
6689
- clean: "rm -rf build"
6690
- },
6691
- keywords: [],
6692
- author: "",
6693
- license: "Apache-2.0",
6694
- dependencies: {
6695
- "@ai-sdk/anthropic": "^1.2.11",
6696
- "@ai-sdk/openai": "^1.3.22",
6697
- "@babel/generator": "^7.27.1",
6698
- "@babel/parser": "^7.27.1",
6699
- "@babel/traverse": "^7.27.4",
6700
- "@babel/types": "^7.27.1",
6701
- "@datocms/cma-client-node": "^4.0.1",
6702
- "@gitbeaker/rest": "^39.34.3",
6703
- "@inkjs/ui": "^2.0.0",
6704
- "@inquirer/prompts": "^7.4.1",
6705
- "@lingo.dev/_sdk": "workspace:*",
6706
- "@lingo.dev/_spec": "workspace:*",
6707
- "@lingo.dev/_react": "workspace:*",
6708
- "@lingo.dev/_compiler": "workspace:*",
6709
- "@modelcontextprotocol/sdk": "^1.5.0",
6710
- "@paralleldrive/cuid2": "^2.2.2",
6711
- ai: "^4.3.15",
6712
- bitbucket: "^2.12.0",
6713
- chalk: "^5.4.1",
6714
- "cli-progress": "^3.12.0",
6715
- "cli-table3": "^0.6.5",
6716
- cors: "^2.8.5",
6717
- "csv-parse": "^5.6.0",
6718
- "csv-stringify": "^6.5.2",
6719
- "date-fns": "^4.1.0",
6720
- dedent: "^1.5.3",
6721
- diff: "^7.0.0",
6722
- dotenv: "^16.4.7",
6723
- express: "^5.1.0",
6724
- "external-editor": "^3.1.0",
6725
- figlet: "^1.8.0",
6726
- flat: "^6.0.1",
6727
- "gettext-parser": "^8.0.0",
6728
- glob: "<11.0.0",
6729
- "gradient-string": "^3.0.0",
6730
- "gray-matter": "^4.0.3",
6731
- ini: "^5.0.0",
6732
- ink: "^4.2.0",
6733
- "ink-progress-bar": "^3.0.0",
6734
- "ink-spinner": "^5.0.0",
6735
- inquirer: "^12.6.0",
6736
- "interactive-commander": "^0.5.194",
6737
- "is-url": "^1.2.4",
6738
- jsdom: "^25.0.1",
6739
- json5: "^2.2.3",
6740
- jsonrepair: "^3.11.2",
6741
- listr2: "^8.3.2",
6742
- lodash: "^4.17.21",
6743
- marked: "^15.0.6",
6744
- "mdast-util-from-markdown": "^2.0.2",
6745
- "mdast-util-gfm": "^3.1.0",
6746
- "micromark-extension-gfm": "^3.0.0",
6747
- "node-machine-id": "^1.1.12",
6748
- "node-webvtt": "^1.9.4",
6749
- "object-hash": "^3.0.0",
6750
- octokit: "^4.0.2",
6751
- open: "^10.1.2",
6752
- ora: "^8.1.1",
6753
- "p-limit": "^6.2.0",
6754
- "php-array-reader": "^2.1.2",
6755
- plist: "^3.1.0",
6756
- "posthog-node": "^4.17.0",
6757
- prettier: "^3.4.2",
6758
- react: "^18.3.1",
6759
- "rehype-stringify": "^10.0.1",
6760
- "remark-disable-tokenizers": "^1.1.1",
6761
- "remark-frontmatter": "^5.0.0",
6762
- "remark-gfm": "^4.0.1",
6763
- "remark-mdx": "^3.1.0",
6764
- "remark-mdx-frontmatter": "^5.1.0",
6765
- "remark-parse": "^11.0.0",
6766
- "remark-rehype": "^11.1.2",
6767
- "remark-stringify": "^11.0.0",
6768
- "srt-parser-2": "^1.2.3",
6769
- unified: "^11.0.5",
6770
- "unist-util-visit": "^5.0.0",
6771
- vfile: "^6.0.3",
6772
- xliff: "^6.2.1",
6773
- xml2js: "^0.6.2",
6774
- xpath: "^0.0.34",
6775
- yaml: "^2.7.0",
6776
- zod: "^3.24.1"
6777
- },
6778
- devDependencies: {
6779
- "@types/babel__generator": "^7.27.0",
6780
- "@types/cli-progress": "^3.11.6",
6781
- "@types/cors": "^2.8.17",
6782
- "@types/diff": "^7.0.0",
6783
- "@types/express": "^5.0.1",
6784
- "@types/figlet": "^1.7.0",
6785
- "@types/gettext-parser": "^4.0.4",
6786
- "@types/glob": "^8.1.0",
6787
- "@types/ini": "^4.1.1",
6788
- "@types/is-url": "^1.2.32",
6789
- "@types/jsdom": "^21.1.7",
6790
- "@types/lodash": "^4.17.16",
6791
- "@types/mdast": "^4.0.4",
6792
- "@types/node": "^22.10.2",
6793
- "@types/node-gettext": "^3.0.6",
6794
- "@types/object-hash": "^3.0.6",
6795
- "@types/plist": "^3.0.5",
6796
- "@types/react": "^18.3.20",
6797
- "@types/xml2js": "^0.4.14",
6798
- tsup: "^8.3.5",
6799
- typescript: "^5.8.3",
6800
- vitest: "^3.1.2"
6801
- },
6802
- engines: {
6803
- node: ">=18"
6804
- },
6805
- packageManager: "pnpm@9.12.3"
6685
+ }
6686
+ get platformConfig() {
6687
+ const env = Z9.object({
6688
+ GITHUB_REPOSITORY: Z9.string(),
6689
+ GITHUB_REPOSITORY_OWNER: Z9.string(),
6690
+ GITHUB_REF_NAME: Z9.string(),
6691
+ GITHUB_HEAD_REF: Z9.string(),
6692
+ GH_TOKEN: Z9.string().optional()
6693
+ }).parse(process.env);
6694
+ const baseBranchName = !env.GITHUB_REF_NAME.endsWith("/merge") ? env.GITHUB_REF_NAME : env.GITHUB_HEAD_REF;
6695
+ return {
6696
+ ghToken: env.GH_TOKEN,
6697
+ baseBranchName,
6698
+ repositoryOwner: env.GITHUB_REPOSITORY_OWNER,
6699
+ repositoryName: env.GITHUB_REPOSITORY.split("/")[1]
6700
+ };
6701
+ }
6702
+ buildPullRequestUrl(pullRequestNumber) {
6703
+ const { repositoryOwner, repositoryName } = this.platformConfig;
6704
+ return `https://github.com/${repositoryOwner}/${repositoryName}/pull/${pullRequestNumber}`;
6705
+ }
6806
6706
  };
6807
6707
 
6808
- // src/cli/cmd/run/index.ts
6809
- import { Command as Command17 } from "interactive-commander";
6810
-
6811
- // src/cli/cmd/run/setup.ts
6812
- import chalk11 from "chalk";
6813
- import { Listr } from "listr2";
6814
-
6815
- // src/cli/cmd/run/_const.ts
6816
- import chalk8 from "chalk";
6817
- import { ListrDefaultRendererLogLevels } from "listr2";
6818
- var commonTaskRendererOptions = {
6819
- color: {
6820
- [ListrDefaultRendererLogLevels.COMPLETED]: (msg) => msg ? chalk8.hex(colors.green)(msg) : chalk8.hex(colors.green)("")
6821
- },
6822
- icon: {
6823
- [ListrDefaultRendererLogLevels.COMPLETED]: chalk8.hex(colors.green)("\u2713")
6708
+ // src/cli/cmd/ci/platforms/gitlab.ts
6709
+ import { Gitlab } from "@gitbeaker/rest";
6710
+ import Z10 from "zod";
6711
+ var gl = new Gitlab({ token: "" });
6712
+ var GitlabPlatformKit = class extends PlatformKit {
6713
+ _gitlab;
6714
+ constructor() {
6715
+ super();
6716
+ process.chdir(this.platformConfig.projectDir);
6717
+ }
6718
+ get gitlab() {
6719
+ if (!this._gitlab) {
6720
+ this._gitlab = new Gitlab({
6721
+ token: this.platformConfig.glToken || ""
6722
+ });
6723
+ }
6724
+ return this._gitlab;
6725
+ }
6726
+ get platformConfig() {
6727
+ const env = Z10.object({
6728
+ GL_TOKEN: Z10.string().optional(),
6729
+ CI_COMMIT_BRANCH: Z10.string(),
6730
+ CI_MERGE_REQUEST_SOURCE_BRANCH_NAME: Z10.string().optional(),
6731
+ CI_PROJECT_NAMESPACE: Z10.string(),
6732
+ CI_PROJECT_NAME: Z10.string(),
6733
+ CI_PROJECT_ID: Z10.string(),
6734
+ CI_PROJECT_DIR: Z10.string(),
6735
+ CI_REPOSITORY_URL: Z10.string()
6736
+ }).parse(process.env);
6737
+ const config = {
6738
+ glToken: env.GL_TOKEN,
6739
+ baseBranchName: env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME ?? env.CI_COMMIT_BRANCH,
6740
+ repositoryOwner: env.CI_PROJECT_NAMESPACE,
6741
+ repositoryName: env.CI_PROJECT_NAME,
6742
+ gitlabProjectId: env.CI_PROJECT_ID,
6743
+ projectDir: env.CI_PROJECT_DIR,
6744
+ reporitoryUrl: env.CI_REPOSITORY_URL
6745
+ };
6746
+ return config;
6747
+ }
6748
+ async branchExists({ branch }) {
6749
+ try {
6750
+ await this.gitlab.Branches.show(
6751
+ this.platformConfig.gitlabProjectId,
6752
+ branch
6753
+ );
6754
+ return true;
6755
+ } catch {
6756
+ return false;
6757
+ }
6758
+ }
6759
+ async getOpenPullRequestNumber({
6760
+ branch
6761
+ }) {
6762
+ const mergeRequests = await this.gitlab.MergeRequests.all({
6763
+ projectId: this.platformConfig.gitlabProjectId,
6764
+ sourceBranch: branch,
6765
+ state: "opened"
6766
+ });
6767
+ return mergeRequests[0]?.iid;
6768
+ }
6769
+ async closePullRequest({
6770
+ pullRequestNumber
6771
+ }) {
6772
+ await this.gitlab.MergeRequests.edit(
6773
+ this.platformConfig.gitlabProjectId,
6774
+ pullRequestNumber,
6775
+ {
6776
+ stateEvent: "close"
6777
+ }
6778
+ );
6779
+ }
6780
+ async createPullRequest({
6781
+ head,
6782
+ title,
6783
+ body
6784
+ }) {
6785
+ const mr = await this.gitlab.MergeRequests.create(
6786
+ this.platformConfig.gitlabProjectId,
6787
+ head,
6788
+ this.platformConfig.baseBranchName,
6789
+ title,
6790
+ {
6791
+ description: body
6792
+ }
6793
+ );
6794
+ return mr.iid;
6795
+ }
6796
+ async commentOnPullRequest({
6797
+ pullRequestNumber,
6798
+ body
6799
+ }) {
6800
+ await this.gitlab.MergeRequestNotes.create(
6801
+ this.platformConfig.gitlabProjectId,
6802
+ pullRequestNumber,
6803
+ body
6804
+ );
6805
+ }
6806
+ gitConfig() {
6807
+ const glToken = this.platformConfig.glToken;
6808
+ const url = `https://oauth2:${glToken}@gitlab.com/${this.platformConfig.repositoryOwner}/${this.platformConfig.repositoryName}.git`;
6809
+ super.gitConfig(glToken, url);
6810
+ }
6811
+ buildPullRequestUrl(pullRequestNumber) {
6812
+ return `https://gitlab.com/${this.platformConfig.repositoryOwner}/${this.platformConfig.repositoryName}/-/merge_requests/${pullRequestNumber}`;
6824
6813
  }
6825
6814
  };
6826
6815
 
6827
- // src/cli/localizer/lingodotdev.ts
6828
- import dedent5 from "dedent";
6829
- import chalk9 from "chalk";
6830
- import { LingoDotDevEngine as LingoDotDevEngine2 } from "@lingo.dev/_sdk";
6831
- function createLingoDotDevLocalizer(explicitApiKey) {
6832
- const { auth } = getSettings(explicitApiKey);
6833
- if (!auth) {
6834
- throw new Error(
6835
- dedent5`
6836
- You're trying to use ${chalk9.hex(colors.green)("Lingo.dev")} provider, however, you are not authenticated.
6816
+ // src/cli/cmd/ci/platforms/index.ts
6817
+ var getPlatformKit = () => {
6818
+ if (process.env.BITBUCKET_PIPELINE_UUID) {
6819
+ return new BitbucketPlatformKit();
6820
+ }
6821
+ if (process.env.GITHUB_ACTION) {
6822
+ return new GitHubPlatformKit();
6823
+ }
6824
+ if (process.env.GITLAB_CI) {
6825
+ return new GitlabPlatformKit();
6826
+ }
6827
+ throw new Error("This platform is not supported");
6828
+ };
6837
6829
 
6838
- To fix this issue:
6839
- 1. Run ${chalk9.dim("lingo.dev login")} to authenticate, or
6840
- 2. Use the ${chalk9.dim("--api-key")} flag to provide an API key.
6841
- 3. Set ${chalk9.dim("LINGODOTDEV_API_KEY")} environment variable.
6842
- `
6843
- );
6830
+ // src/cli/cmd/ci/index.ts
6831
+ var ci_default = new Command15().command("ci").description("Run Lingo.dev CI/CD action").helpOption("-h, --help", "Show help").option("--parallel", "Run in parallel mode", (val) => {
6832
+ if (typeof val === "boolean") return val;
6833
+ return val?.toLowerCase() === "true";
6834
+ }).option("--api-key <key>", "API key").option("--pull-request [boolean]", "Create a pull request with the changes").option("--commit-message <message>", "Commit message").option("--pull-request-title <title>", "Pull request title").option("--working-directory <dir>", "Working directory").option(
6835
+ "--process-own-commits [boolean]",
6836
+ "Process commits made by this action"
6837
+ ).action(async (options) => {
6838
+ const settings = getSettings(options.apiKey);
6839
+ console.log(options);
6840
+ if (!settings.auth.apiKey) {
6841
+ console.error("No API key provided");
6842
+ return;
6844
6843
  }
6845
- const engine = new LingoDotDevEngine2({
6846
- apiKey: auth.apiKey,
6847
- apiUrl: auth.apiUrl
6844
+ const authenticator = createAuthenticator({
6845
+ apiUrl: settings.auth.apiUrl,
6846
+ apiKey: settings.auth.apiKey
6848
6847
  });
6849
- return {
6850
- id: "Lingo.dev",
6851
- checkAuth: async () => {
6852
- try {
6853
- const response = await engine.whoami();
6854
- return {
6855
- authenticated: !!response,
6856
- username: response?.email
6857
- };
6858
- } catch {
6859
- return { authenticated: false };
6860
- }
6848
+ const auth = await authenticator.whoami();
6849
+ if (!auth) {
6850
+ console.error("Not authenticated");
6851
+ return;
6852
+ }
6853
+ const env = {
6854
+ LINGODOTDEV_API_KEY: settings.auth.apiKey,
6855
+ LINGODOTDEV_PULL_REQUEST: options.pullRequest?.toString() || "false",
6856
+ ...options.commitMessage && {
6857
+ LINGODOTDEV_COMMIT_MESSAGE: options.commitMessage
6861
6858
  },
6862
- localize: async (input2, onProgress) => {
6863
- if (!Object.keys(input2.processableData).length) {
6864
- return input2;
6865
- }
6866
- const processedData = await engine.localizeObject(
6867
- input2.processableData,
6868
- {
6869
- sourceLocale: input2.sourceLocale,
6870
- targetLocale: input2.targetLocale,
6871
- reference: {
6872
- [input2.sourceLocale]: input2.sourceData,
6873
- [input2.targetLocale]: input2.targetData
6874
- }
6875
- },
6876
- onProgress
6877
- );
6878
- return processedData;
6859
+ ...options.pullRequestTitle && {
6860
+ LINGODOTDEV_PULL_REQUEST_TITLE: options.pullRequestTitle
6861
+ },
6862
+ ...options.workingDirectory && {
6863
+ LINGODOTDEV_WORKING_DIRECTORY: options.workingDirectory
6864
+ },
6865
+ ...options.processOwnCommits && {
6866
+ LINGODOTDEV_PROCESS_OWN_COMMITS: options.processOwnCommits.toString()
6879
6867
  }
6880
6868
  };
6881
- }
6882
-
6883
- // src/cli/localizer/explicit.ts
6884
- import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
6885
- import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
6886
- import chalk10 from "chalk";
6887
- import dedent6 from "dedent";
6888
- import { generateText as generateText2 } from "ai";
6889
- import { jsonrepair as jsonrepair3 } from "jsonrepair";
6890
- function createExplicitLocalizer(provider) {
6891
- switch (provider.id) {
6892
- default:
6893
- throw new Error(
6894
- dedent6`
6895
- You're trying to use unsupported provider: ${chalk10.dim(provider.id)}.
6896
-
6897
- To fix this issue:
6898
- 1. Switch to one of the supported providers, or
6899
- 2. Remove the ${chalk10.italic("provider")} node from your i18n.json configuration to switch to ${chalk10.hex(colors.green)("Lingo.dev")}
6900
-
6901
- ${chalk10.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
6902
- `
6903
- );
6904
- case "openai":
6905
- return createAiSdkLocalizer({
6906
- factory: (params) => createOpenAI2(params).languageModel(provider.model),
6907
- id: provider.id,
6908
- prompt: provider.prompt,
6909
- apiKeyName: "OPENAI_API_KEY",
6910
- baseUrl: provider.baseUrl
6911
- });
6912
- case "anthropic":
6913
- return createAiSdkLocalizer({
6914
- factory: (params) => createAnthropic2(params).languageModel(provider.model),
6915
- id: provider.id,
6916
- prompt: provider.prompt,
6917
- apiKeyName: "ANTHROPIC_API_KEY",
6918
- baseUrl: provider.baseUrl
6919
- });
6920
- }
6921
- }
6922
- function createAiSdkLocalizer(params) {
6923
- const apiKey = process.env[params.apiKeyName];
6924
- if (!apiKey) {
6925
- throw new Error(
6926
- dedent6`
6927
- You're trying to use raw ${chalk10.dim(params.id)} API for translation, however, ${chalk10.dim(params.apiKeyName)} environment variable is not set.
6928
-
6929
- To fix this issue:
6930
- 1. Set ${chalk10.dim(params.apiKeyName)} in your environment variables, or
6931
- 2. Remove the ${chalk10.italic("provider")} node from your i18n.json configuration to switch to ${chalk10.hex(colors.green)("Lingo.dev")}
6932
-
6933
- ${chalk10.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
6934
- `
6935
- );
6869
+ process.env = { ...process.env, ...env };
6870
+ const ora = createOra();
6871
+ const platformKit = getPlatformKit();
6872
+ const { isPullRequestMode } = platformKit.config;
6873
+ ora.info(`Pull request mode: ${isPullRequestMode ? "on" : "off"}`);
6874
+ const flow = isPullRequestMode ? new PullRequestFlow(ora, platformKit) : new InBranchFlow(ora, platformKit);
6875
+ const canRun = await flow.preRun?.();
6876
+ if (canRun === false) {
6877
+ return;
6936
6878
  }
6937
- const model = params.factory({
6938
- apiKey,
6939
- baseUrl: params.baseUrl
6879
+ const hasChanges = await flow.run({
6880
+ parallel: options.parallel
6940
6881
  });
6941
- return {
6942
- id: params.id,
6943
- checkAuth: async () => {
6944
- try {
6945
- await generateText2({
6946
- model,
6947
- messages: [
6948
- { role: "system", content: "You are an echo server" },
6949
- { role: "user", content: "OK" },
6950
- { role: "assistant", content: "OK" },
6951
- { role: "user", content: "OK" }
6952
- ]
6882
+ if (!hasChanges) {
6883
+ return;
6884
+ }
6885
+ await flow.postRun?.();
6886
+ });
6887
+
6888
+ // src/cli/cmd/status.ts
6889
+ import { bucketTypeSchema as bucketTypeSchema4, localeCodeSchema as localeCodeSchema3, resolveOverriddenLocale as resolveOverriddenLocale7 } from "@lingo.dev/_spec";
6890
+ import { Command as Command16 } from "interactive-commander";
6891
+ import Z11 from "zod";
6892
+ import Ora8 from "ora";
6893
+ import chalk14 from "chalk";
6894
+ import Table from "cli-table3";
6895
+ var status_default = new Command16().command("status").description("Show the status of the localization process").helpOption("-h, --help", "Show help").option("--locale <locale>", "Locale to process", (val, prev) => prev ? [...prev, val] : [val]).option("--bucket <bucket>", "Bucket to process", (val, prev) => prev ? [...prev, val] : [val]).option(
6896
+ "--file [files...]",
6897
+ "File to process. Process only a specific path, may contain asterisk * to match multiple files."
6898
+ ).option("--force", "Ignore lockfile and process all keys, useful for estimating full re-translation").option("--verbose", "Show detailed output including key-level word counts").option("--api-key <api-key>", "Explicitly set the API key to use, override the default API key from settings").action(async function(options) {
6899
+ const ora = Ora8();
6900
+ const flags = parseFlags2(options);
6901
+ let authId = null;
6902
+ try {
6903
+ ora.start("Loading configuration...");
6904
+ const i18nConfig = getConfig();
6905
+ const settings = getSettings(flags.apiKey);
6906
+ ora.succeed("Configuration loaded");
6907
+ try {
6908
+ ora.start("Checking authentication status...");
6909
+ const auth = await tryAuthenticate(settings);
6910
+ if (auth) {
6911
+ authId = auth.id;
6912
+ ora.succeed(`Authenticated as ${auth.email}`);
6913
+ } else {
6914
+ ora.info(
6915
+ "Not authenticated. Continuing without authentication. (Run `lingo.dev auth --login` to authenticate)"
6916
+ );
6917
+ }
6918
+ } catch (error) {
6919
+ ora.info("Authentication failed. Continuing without authentication.");
6920
+ }
6921
+ ora.start("Validating localization configuration...");
6922
+ validateParams2(i18nConfig, flags);
6923
+ ora.succeed("Localization configuration is valid");
6924
+ trackEvent(authId || "status", "cmd.status.start", {
6925
+ i18nConfig,
6926
+ flags
6927
+ });
6928
+ let buckets = getBuckets(i18nConfig);
6929
+ if (flags.bucket?.length) {
6930
+ buckets = buckets.filter((bucket) => flags.bucket.includes(bucket.type));
6931
+ }
6932
+ ora.succeed("Buckets retrieved");
6933
+ if (flags.file?.length) {
6934
+ buckets = buckets.map((bucket) => {
6935
+ const paths = bucket.paths.filter((path16) => flags.file.find((file) => path16.pathPattern?.match(file)));
6936
+ return { ...bucket, paths };
6937
+ }).filter((bucket) => bucket.paths.length > 0);
6938
+ if (buckets.length === 0) {
6939
+ ora.fail("No buckets found. All buckets were filtered out by --file option.");
6940
+ process.exit(1);
6941
+ } else {
6942
+ ora.info(`\x1B[36mProcessing only filtered buckets:\x1B[0m`);
6943
+ buckets.map((bucket) => {
6944
+ ora.info(` ${bucket.type}:`);
6945
+ bucket.paths.forEach((path16) => {
6946
+ ora.info(` - ${path16.pathPattern}`);
6947
+ });
6953
6948
  });
6954
- return { authenticated: true, username: "anonymous" };
6955
- } catch (error) {
6956
- return { authenticated: false };
6957
6949
  }
6958
- },
6959
- localize: async (input2) => {
6960
- const systemPrompt = params.prompt.replaceAll("{source}", input2.sourceLocale).replaceAll("{target}", input2.targetLocale);
6961
- const shots = [
6962
- [
6963
- {
6964
- sourceLocale: "en",
6965
- targetLocale: "es",
6966
- data: {
6967
- message: "Hello, world!"
6968
- }
6969
- },
6970
- {
6971
- sourceLocale: "en",
6972
- targetLocale: "es",
6973
- data: {
6974
- message: "Hola, mundo!"
6975
- }
6976
- }
6977
- ]
6978
- ];
6979
- const payload = {
6980
- sourceLocale: input2.sourceLocale,
6981
- targetLocale: input2.targetLocale,
6982
- data: input2.processableData
6950
+ }
6951
+ const targetLocales = flags.locale?.length ? flags.locale : i18nConfig.locale.targets;
6952
+ let totalSourceKeyCount = 0;
6953
+ let uniqueKeysToTranslate = 0;
6954
+ let totalExistingTranslations = 0;
6955
+ const totalWordCount = /* @__PURE__ */ new Map();
6956
+ const languageStats = {};
6957
+ for (const locale of targetLocales) {
6958
+ languageStats[locale] = {
6959
+ complete: 0,
6960
+ missing: 0,
6961
+ updated: 0,
6962
+ words: 0
6983
6963
  };
6984
- const response = await generateText2({
6985
- model,
6986
- messages: [
6987
- { role: "system", content: systemPrompt },
6988
- { role: "user", content: "OK" },
6989
- ...shots.flatMap(
6990
- ([userShot, assistantShot]) => [
6991
- { role: "user", content: JSON.stringify(userShot) },
6992
- { role: "assistant", content: JSON.stringify(assistantShot) }
6993
- ]
6994
- ),
6995
- { role: "user", content: JSON.stringify(payload) }
6996
- ]
6997
- });
6998
- const result = JSON.parse(response.text);
6999
- const index = result.data.indexOf("{");
7000
- const lastIndex = result.data.lastIndexOf("}");
7001
- const trimmed = result.data.slice(index, lastIndex + 1);
7002
- const repaired = jsonrepair3(trimmed);
7003
- const finalResult = JSON.parse(repaired);
7004
- return finalResult.data;
6964
+ totalWordCount.set(locale, 0);
7005
6965
  }
7006
- };
7007
- }
7008
-
7009
- // src/cli/localizer/index.ts
7010
- function createLocalizer(provider) {
7011
- if (!provider) {
7012
- return createLingoDotDevLocalizer();
7013
- } else {
7014
- return createExplicitLocalizer(provider);
7015
- }
7016
- }
7017
-
7018
- // src/cli/cmd/run/setup.ts
7019
- async function setup(input2) {
7020
- console.log(chalk11.hex(colors.orange)("[Setup]"));
7021
- return new Listr(
7022
- [
7023
- {
7024
- title: "Setting up the environment",
7025
- task: async (ctx, task) => {
7026
- task.title = `Environment setup completed`;
7027
- }
7028
- },
7029
- {
7030
- title: "Loading i18n configuration",
7031
- task: async (ctx, task) => {
7032
- ctx.config = getConfig(true);
7033
- if (!ctx.config) {
7034
- throw new Error(
7035
- "i18n.json not found. Please run `lingo.dev init` to initialize the project."
7036
- );
7037
- } else if (!ctx.config.buckets || !Object.keys(ctx.config.buckets).length) {
7038
- throw new Error(
7039
- "No buckets found in i18n.json. Please add at least one bucket containing i18n content."
7040
- );
7041
- } else if (ctx.flags.locale?.some(
7042
- (locale) => !ctx.config?.locale.targets.includes(locale)
7043
- )) {
7044
- throw new Error(
7045
- `One or more specified locales do not exist in i18n.json locale.targets. Please add them to the list first and try again.`
7046
- );
7047
- } else if (ctx.flags.bucket?.some(
7048
- (bucket) => !ctx.config?.buckets[bucket]
7049
- )) {
7050
- throw new Error(
7051
- `One or more specified buckets do not exist in i18n.json. Please add them to the list first and try again.`
7052
- );
7053
- }
7054
- task.title = `Loaded i18n configuration`;
7055
- }
7056
- },
7057
- {
7058
- title: "Selecting localization provider",
7059
- task: async (ctx, task) => {
7060
- ctx.localizer = createLocalizer(ctx.config?.provider);
7061
- if (!ctx.localizer) {
7062
- throw new Error(
7063
- "Could not create localization provider. Please check your i18n.json configuration."
7064
- );
7065
- }
7066
- task.title = ctx.localizer.id === "Lingo.dev" ? `Using ${chalk11.hex(colors.green)(ctx.localizer.id)} provider` : `Using raw ${chalk11.hex(colors.yellow)(ctx.localizer.id)} API`;
7067
- }
7068
- },
7069
- {
7070
- title: "Checking authentication",
7071
- task: async (ctx, task) => {
7072
- const authStatus = await ctx.localizer.checkAuth();
7073
- if (!authStatus.authenticated) {
7074
- throw new Error(
7075
- `Failed to authenticate with ${chalk11.hex(colors.yellow)(ctx.localizer.id)} provider. Please check your API key and try again.`
7076
- );
7077
- }
7078
- task.title = `Authenticated as ${chalk11.hex(colors.yellow)(authStatus.username)}`;
7079
- }
7080
- },
7081
- {
7082
- title: "Initializing localization provider",
7083
- async task(ctx, task) {
7084
- const isLingoDotDev = ctx.localizer.id === "Lingo.dev";
7085
- const subTasks = isLingoDotDev ? [
7086
- "Brand voice enabled",
7087
- "Translation memory connected",
7088
- "Glossary enabled",
7089
- "Quality assurance enabled"
7090
- ].map((title) => ({ title, task: () => {
7091
- } })) : [
7092
- "Skipping brand voice",
7093
- "Skipping glossary",
7094
- "Skipping translation memory",
7095
- "Skipping quality assurance"
7096
- ].map((title) => ({ title, task: () => {
7097
- }, skip: true }));
7098
- return task.newListr(subTasks, {
7099
- concurrent: true,
7100
- rendererOptions: { collapseSubtasks: false }
7101
- });
7102
- }
7103
- }
7104
- ],
7105
- {
7106
- rendererOptions: commonTaskRendererOptions
7107
- }
7108
- ).run(input2);
7109
- }
7110
-
7111
- // src/cli/cmd/run/plan.ts
7112
- import chalk12 from "chalk";
7113
- import { Listr as Listr2 } from "listr2";
7114
- import { resolveOverriddenLocale as resolveOverriddenLocale7 } from "@lingo.dev/_spec";
7115
- async function plan(input2) {
7116
- console.log(chalk12.hex(colors.orange)("[Planning]"));
7117
- let buckets = getBuckets(input2.config);
7118
- if (input2.flags.bucket) {
7119
- buckets = buckets.filter((b) => input2.flags.bucket.includes(b.type));
7120
- }
7121
- let locales = input2.config.locale.targets;
7122
- if (input2.flags.locale) {
7123
- locales = locales.filter((l) => input2.flags.locale.includes(l));
7124
- }
7125
- return new Listr2(
7126
- [
7127
- {
7128
- title: "Locating content buckets",
7129
- task: async (ctx, task) => {
7130
- const bucketCount = buckets.length;
7131
- const bucketFilter = input2.flags.bucket ? ` ${chalk12.dim(`(filtered by: ${chalk12.hex(colors.yellow)(input2.flags.bucket.join(", "))})`)}` : "";
7132
- task.title = `Found ${chalk12.hex(colors.yellow)(bucketCount.toString())} bucket(s)${bucketFilter}`;
7133
- }
7134
- },
7135
- {
7136
- title: "Detecting locales",
7137
- task: async (ctx, task) => {
7138
- if (!locales.length) {
7139
- throw new Error(
7140
- `No target locales found in config. Please add locales to your i18n.json config file.`
7141
- );
6966
+ const fileStats = {};
6967
+ for (const bucket of buckets) {
6968
+ try {
6969
+ console.log();
6970
+ ora.info(`Analyzing bucket: ${bucket.type}`);
6971
+ for (const bucketPath of bucket.paths) {
6972
+ const bucketOra = Ora8({ indent: 2 }).info(`Analyzing path: ${bucketPath.pathPattern}`);
6973
+ const sourceLocale = resolveOverriddenLocale7(i18nConfig.locale.source, bucketPath.delimiter);
6974
+ const bucketLoader = createBucketLoader(
6975
+ bucket.type,
6976
+ bucketPath.pathPattern,
6977
+ {
6978
+ isCacheRestore: false,
6979
+ defaultLocale: sourceLocale,
6980
+ injectLocale: bucket.injectLocale
6981
+ },
6982
+ bucket.lockedKeys
6983
+ );
6984
+ bucketLoader.setDefaultLocale(sourceLocale);
6985
+ await bucketLoader.init();
6986
+ const filePath = bucketPath.pathPattern;
6987
+ if (!fileStats[filePath]) {
6988
+ fileStats[filePath] = {
6989
+ path: filePath,
6990
+ sourceKeys: 0,
6991
+ wordCount: 0,
6992
+ languageStats: {}
6993
+ };
6994
+ for (const locale of targetLocales) {
6995
+ fileStats[filePath].languageStats[locale] = {
6996
+ complete: 0,
6997
+ missing: 0,
6998
+ updated: 0,
6999
+ words: 0
7000
+ };
7001
+ }
7142
7002
  }
7143
- const localeFilter = input2.flags.locale ? ` ${chalk12.dim(`(filtered by: ${chalk12.hex(colors.yellow)(input2.flags.locale.join(", "))})`)}` : "";
7144
- task.title = `Found ${chalk12.hex(colors.yellow)(locales.length.toString())} target locale(s)${localeFilter}`;
7145
- }
7146
- },
7147
- {
7148
- title: "Locating localizable files",
7149
- task: async (ctx, task) => {
7150
- const patterns = [];
7151
- for (const bucket of buckets) {
7152
- for (const bucketPath of bucket.paths) {
7153
- if (input2.flags.file) {
7154
- if (!input2.flags.file.some(
7155
- (f) => bucketPath.pathPattern.includes(f)
7156
- )) {
7157
- continue;
7158
- }
7159
- }
7160
- patterns.push(bucketPath.pathPattern);
7003
+ const sourceData = await bucketLoader.pull(sourceLocale);
7004
+ const sourceKeys = Object.keys(sourceData);
7005
+ fileStats[filePath].sourceKeys = sourceKeys.length;
7006
+ totalSourceKeyCount += sourceKeys.length;
7007
+ let sourceWordCount = 0;
7008
+ for (const key of sourceKeys) {
7009
+ const value = sourceData[key];
7010
+ if (typeof value === "string") {
7011
+ const words = value.trim().split(/\s+/).length;
7012
+ sourceWordCount += words;
7161
7013
  }
7162
7014
  }
7163
- const fileFilter = input2.flags.file ? ` ${chalk12.dim(`(filtered by: ${chalk12.hex(colors.yellow)(input2.flags.file.join(", "))})`)}` : "";
7164
- task.title = `Found ${chalk12.hex(colors.yellow)(patterns.length.toString())} path pattern(s)${fileFilter}`;
7165
- }
7166
- },
7167
- {
7168
- title: "Computing translation tasks",
7169
- task: async (ctx, task) => {
7170
- for (const bucket of buckets) {
7171
- for (const bucketPath of bucket.paths) {
7172
- if (input2.flags.file) {
7173
- if (!input2.flags.file.some(
7174
- (f) => bucketPath.pathPattern.includes(f)
7175
- )) {
7176
- continue;
7177
- }
7015
+ fileStats[filePath].wordCount = sourceWordCount;
7016
+ for (const _targetLocale of targetLocales) {
7017
+ const targetLocale = resolveOverriddenLocale7(_targetLocale, bucketPath.delimiter);
7018
+ bucketOra.start(`[${sourceLocale} -> ${targetLocale}] Analyzing translation status...`);
7019
+ let targetData = {};
7020
+ let fileExists = true;
7021
+ try {
7022
+ targetData = await bucketLoader.pull(targetLocale);
7023
+ } catch (error) {
7024
+ fileExists = false;
7025
+ bucketOra.info(
7026
+ `[${sourceLocale} -> ${targetLocale}] Target file not found, assuming all keys need translation.`
7027
+ );
7028
+ }
7029
+ if (!fileExists) {
7030
+ fileStats[filePath].languageStats[targetLocale].missing = sourceKeys.length;
7031
+ fileStats[filePath].languageStats[targetLocale].words = sourceWordCount;
7032
+ languageStats[targetLocale].missing += sourceKeys.length;
7033
+ languageStats[targetLocale].words += sourceWordCount;
7034
+ totalWordCount.set(targetLocale, (totalWordCount.get(targetLocale) || 0) + sourceWordCount);
7035
+ bucketOra.succeed(
7036
+ `[${sourceLocale} -> ${targetLocale}] ${chalk14.red(`0% complete`)} (0/${sourceKeys.length} keys) - file not found`
7037
+ );
7038
+ continue;
7039
+ }
7040
+ const deltaProcessor = createDeltaProcessor(bucketPath.pathPattern);
7041
+ const checksums = await deltaProcessor.loadChecksums();
7042
+ const delta = await deltaProcessor.calculateDelta({
7043
+ sourceData,
7044
+ targetData,
7045
+ checksums
7046
+ });
7047
+ const missingKeys = delta.added;
7048
+ const updatedKeys = delta.updated;
7049
+ const completeKeys = sourceKeys.filter((key) => !missingKeys.includes(key) && !updatedKeys.includes(key));
7050
+ let wordsToTranslate = 0;
7051
+ const keysToProcess = flags.force ? sourceKeys : [...missingKeys, ...updatedKeys];
7052
+ for (const key of keysToProcess) {
7053
+ const value = sourceData[String(key)];
7054
+ if (typeof value === "string") {
7055
+ const words = value.trim().split(/\s+/).length;
7056
+ wordsToTranslate += words;
7178
7057
  }
7179
- const sourceLocale = resolveOverriddenLocale7(
7180
- ctx.config.locale.source,
7181
- bucketPath.delimiter
7058
+ }
7059
+ fileStats[filePath].languageStats[targetLocale].missing = missingKeys.length;
7060
+ fileStats[filePath].languageStats[targetLocale].updated = updatedKeys.length;
7061
+ fileStats[filePath].languageStats[targetLocale].complete = completeKeys.length;
7062
+ fileStats[filePath].languageStats[targetLocale].words = wordsToTranslate;
7063
+ languageStats[targetLocale].missing += missingKeys.length;
7064
+ languageStats[targetLocale].updated += updatedKeys.length;
7065
+ languageStats[targetLocale].complete += completeKeys.length;
7066
+ languageStats[targetLocale].words += wordsToTranslate;
7067
+ totalWordCount.set(targetLocale, (totalWordCount.get(targetLocale) || 0) + wordsToTranslate);
7068
+ const totalKeysInFile = sourceKeys.length;
7069
+ const completionPercent = (completeKeys.length / totalKeysInFile * 100).toFixed(1);
7070
+ if (missingKeys.length === 0 && updatedKeys.length === 0) {
7071
+ bucketOra.succeed(
7072
+ `[${sourceLocale} -> ${targetLocale}] ${chalk14.green(`100% complete`)} (${completeKeys.length}/${totalKeysInFile} keys)`
7182
7073
  );
7183
- for (const _targetLocale of locales) {
7184
- const targetLocale = resolveOverriddenLocale7(
7185
- _targetLocale,
7186
- bucketPath.delimiter
7187
- );
7188
- if (sourceLocale === targetLocale) continue;
7189
- ctx.tasks.push({
7190
- sourceLocale,
7191
- targetLocale,
7192
- bucketType: bucket.type,
7193
- bucketPathPattern: bucketPath.pathPattern,
7194
- injectLocale: bucket.injectLocale || [],
7195
- lockedKeys: bucket.lockedKeys || [],
7196
- lockedPatterns: bucket.lockedPatterns || []
7197
- });
7074
+ } else {
7075
+ const message = `[${sourceLocale} -> ${targetLocale}] ${parseFloat(completionPercent) > 50 ? chalk14.yellow(`${completionPercent}% complete`) : chalk14.red(`${completionPercent}% complete`)} (${completeKeys.length}/${totalKeysInFile} keys)`;
7076
+ bucketOra.succeed(message);
7077
+ if (flags.verbose) {
7078
+ if (missingKeys.length > 0) {
7079
+ console.log(` ${chalk14.red(`Missing:`)} ${missingKeys.length} keys, ~${wordsToTranslate} words`);
7080
+ console.log(
7081
+ ` ${chalk14.dim(`Example missing: ${missingKeys.slice(0, 2).join(", ")}${missingKeys.length > 2 ? "..." : ""}`)}`
7082
+ );
7083
+ }
7084
+ if (updatedKeys.length > 0) {
7085
+ console.log(` ${chalk14.yellow(`Updated:`)} ${updatedKeys.length} keys that changed in source`);
7086
+ }
7198
7087
  }
7199
7088
  }
7200
7089
  }
7201
- task.title = `Prepared ${chalk12.hex(colors.green)(ctx.tasks.length.toString())} translation task(s)`;
7202
7090
  }
7091
+ } catch (error) {
7092
+ ora.fail(`Failed to analyze bucket ${bucket.type}: ${error.message}`);
7203
7093
  }
7204
- ],
7205
- {
7206
- rendererOptions: commonTaskRendererOptions
7207
7094
  }
7208
- ).run(input2);
7209
- }
7210
-
7211
- // src/cli/cmd/run/execute.ts
7212
- import chalk13 from "chalk";
7213
- import { Listr as Listr3 } from "listr2";
7214
- import pLimit from "p-limit";
7215
- import _32 from "lodash";
7216
- var MAX_WORKER_COUNT = 10;
7217
- async function execute(input2) {
7218
- const effectiveConcurrency = Math.min(
7219
- input2.flags.concurrency,
7220
- input2.tasks.length,
7221
- MAX_WORKER_COUNT
7222
- );
7223
- console.log(chalk13.hex(colors.orange)(`[Localization]`));
7224
- return new Listr3(
7225
- [
7226
- {
7227
- title: "Initializing localization engine",
7228
- task: async (ctx, task) => {
7229
- task.title = `Localization engine ${chalk13.hex(colors.green)("ready")} (${ctx.localizer.id})`;
7230
- }
7095
+ const totalKeysNeedingTranslation = Object.values(languageStats).reduce((sum, stats) => {
7096
+ return sum + stats.missing + stats.updated;
7097
+ }, 0);
7098
+ const totalCompletedKeys = totalSourceKeyCount - totalKeysNeedingTranslation / targetLocales.length;
7099
+ console.log();
7100
+ ora.succeed(chalk14.green(`Localization status completed.`));
7101
+ console.log(chalk14.bold.cyan(`
7102
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`));
7103
+ console.log(chalk14.bold.cyan(`\u2551 LOCALIZATION STATUS REPORT \u2551`));
7104
+ console.log(chalk14.bold.cyan(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D`));
7105
+ console.log(chalk14.bold(`
7106
+ \u{1F4DD} SOURCE CONTENT:`));
7107
+ console.log(`\u2022 Source language: ${chalk14.green(i18nConfig.locale.source)}`);
7108
+ console.log(`\u2022 Source keys: ${chalk14.yellow(totalSourceKeyCount.toString())} keys across all files`);
7109
+ console.log(chalk14.bold(`
7110
+ \u{1F310} LANGUAGE BY LANGUAGE BREAKDOWN:`));
7111
+ const table = new Table({
7112
+ head: ["Language", "Status", "Complete", "Missing", "Updated", "Total Keys", "Words to Translate"],
7113
+ style: {
7114
+ head: ["white"],
7115
+ // White color for headers
7116
+ border: []
7117
+ // No color for borders
7231
7118
  },
7232
- {
7233
- title: `Processing localization tasks ${chalk13.dim(`(tasks: ${input2.tasks.length}, concurrency: ${effectiveConcurrency})`)}`,
7234
- task: (ctx, task) => {
7235
- if (input2.tasks.length < 1) {
7236
- task.title = `Skipping, nothing to localize.`;
7237
- task.skip();
7238
- return;
7119
+ colWidths: [12, 20, 18, 12, 12, 12, 15]
7120
+ // Explicit column widths, making Status column wider
7121
+ });
7122
+ let totalWordsToTranslate = 0;
7123
+ for (const locale of targetLocales) {
7124
+ const stats = languageStats[locale];
7125
+ const percentComplete = (stats.complete / totalSourceKeyCount * 100).toFixed(1);
7126
+ const totalNeeded = stats.missing + stats.updated;
7127
+ let statusText;
7128
+ let statusColor;
7129
+ if (stats.missing === totalSourceKeyCount) {
7130
+ statusText = "\u{1F534} Not started";
7131
+ statusColor = chalk14.red;
7132
+ } else if (stats.missing === 0 && stats.updated === 0) {
7133
+ statusText = "\u2705 Complete";
7134
+ statusColor = chalk14.green;
7135
+ } else if (parseFloat(percentComplete) > 80) {
7136
+ statusText = "\u{1F7E1} Almost done";
7137
+ statusColor = chalk14.yellow;
7138
+ } else if (parseFloat(percentComplete) > 0) {
7139
+ statusText = "\u{1F7E0} In progress";
7140
+ statusColor = chalk14.yellow;
7141
+ } else {
7142
+ statusText = "\u{1F534} Not started";
7143
+ statusColor = chalk14.red;
7144
+ }
7145
+ const words = totalWordCount.get(locale) || 0;
7146
+ totalWordsToTranslate += words;
7147
+ table.push([
7148
+ locale,
7149
+ statusColor(statusText),
7150
+ `${stats.complete}/${totalSourceKeyCount} (${percentComplete}%)`,
7151
+ stats.missing > 0 ? chalk14.red(stats.missing.toString()) : "0",
7152
+ stats.updated > 0 ? chalk14.yellow(stats.updated.toString()) : "0",
7153
+ totalNeeded > 0 ? chalk14.magenta(totalNeeded.toString()) : "0",
7154
+ words > 0 ? `~${words.toLocaleString()}` : "0"
7155
+ ]);
7156
+ }
7157
+ console.log(table.toString());
7158
+ console.log(chalk14.bold(`
7159
+ \u{1F4CA} USAGE ESTIMATE:`));
7160
+ console.log(
7161
+ `\u2022 WORDS TO BE CONSUMED: ~${chalk14.yellow.bold(totalWordsToTranslate.toLocaleString())} words across all languages`
7162
+ );
7163
+ console.log(` (Words are counted from source language for keys that need translation in target languages)`);
7164
+ if (targetLocales.length > 1) {
7165
+ console.log(`\u2022 Per-language breakdown:`);
7166
+ for (const locale of targetLocales) {
7167
+ const words = totalWordCount.get(locale) || 0;
7168
+ const percent = (words / totalWordsToTranslate * 100).toFixed(1);
7169
+ console.log(` - ${locale}: ~${words.toLocaleString()} words (${percent}% of total)`);
7170
+ }
7171
+ }
7172
+ if (flags.confirm && Object.keys(fileStats).length > 0) {
7173
+ console.log(chalk14.bold(`
7174
+ \u{1F4D1} BREAKDOWN BY FILE:`));
7175
+ Object.entries(fileStats).sort((a, b) => b[1].wordCount - a[1].wordCount).forEach(([path16, stats]) => {
7176
+ if (stats.sourceKeys === 0) return;
7177
+ console.log(chalk14.bold(`
7178
+ \u2022 ${path16}:`));
7179
+ console.log(` ${stats.sourceKeys} source keys, ~${stats.wordCount.toLocaleString()} source words`);
7180
+ const fileTable = new Table({
7181
+ head: ["Language", "Status", "Details"],
7182
+ style: {
7183
+ head: ["white"],
7184
+ border: []
7185
+ },
7186
+ colWidths: [12, 20, 50]
7187
+ // Explicit column widths for file detail table
7188
+ });
7189
+ for (const locale of targetLocales) {
7190
+ const langStats = stats.languageStats[locale];
7191
+ const complete = langStats.complete;
7192
+ const total = stats.sourceKeys;
7193
+ const completion = (complete / total * 100).toFixed(1);
7194
+ let status = "\u2705 Complete";
7195
+ let statusColor = chalk14.green;
7196
+ if (langStats.missing === total) {
7197
+ status = "\u274C Not started";
7198
+ statusColor = chalk14.red;
7199
+ } else if (langStats.missing > 0 || langStats.updated > 0) {
7200
+ status = `\u26A0\uFE0F ${completion}% complete`;
7201
+ statusColor = chalk14.yellow;
7239
7202
  }
7240
- const i18nLimiter = pLimit(effectiveConcurrency);
7241
- const ioLimiter = pLimit(1);
7242
- const workersCount = effectiveConcurrency;
7243
- const workerTasks = [];
7244
- for (let i = 0; i < workersCount; i++) {
7245
- const assignedTasks = ctx.tasks.filter(
7246
- (_33, idx) => idx % workersCount === i
7247
- );
7248
- workerTasks.push(
7249
- createWorkerTask({
7250
- ctx,
7251
- assignedTasks,
7252
- ioLimiter,
7253
- i18nLimiter,
7254
- onDone() {
7255
- task.title = createExecutionProgressMessage(ctx);
7256
- }
7257
- })
7258
- );
7203
+ let details = "";
7204
+ if (langStats.missing > 0 || langStats.updated > 0) {
7205
+ const parts = [];
7206
+ if (langStats.missing > 0) parts.push(`${langStats.missing} missing`);
7207
+ if (langStats.updated > 0) parts.push(`${langStats.updated} changed`);
7208
+ details = `${parts.join(", ")}, ~${langStats.words} words`;
7209
+ } else {
7210
+ details = "All keys translated";
7259
7211
  }
7260
- return task.newListr(workerTasks, {
7261
- concurrent: true,
7262
- exitOnError: false,
7263
- rendererOptions: {
7264
- ...commonTaskRendererOptions,
7265
- collapseSubtasks: true
7266
- }
7267
- });
7212
+ fileTable.push([locale, statusColor(status), details]);
7268
7213
  }
7269
- }
7270
- ],
7271
- {
7272
- exitOnError: false,
7273
- rendererOptions: commonTaskRendererOptions
7214
+ console.log(fileTable.toString());
7215
+ });
7274
7216
  }
7275
- ).run(input2);
7217
+ const completeLanguages = targetLocales.filter(
7218
+ (locale) => languageStats[locale].missing === 0 && languageStats[locale].updated === 0
7219
+ );
7220
+ const missingLanguages = targetLocales.filter((locale) => languageStats[locale].complete === 0);
7221
+ console.log(chalk14.bold.green(`
7222
+ \u{1F4A1} OPTIMIZATION TIPS:`));
7223
+ if (missingLanguages.length > 0) {
7224
+ console.log(
7225
+ `\u2022 ${chalk14.yellow(missingLanguages.join(", "))} ${missingLanguages.length === 1 ? "has" : "have"} no translations yet`
7226
+ );
7227
+ }
7228
+ if (completeLanguages.length > 0) {
7229
+ console.log(
7230
+ `\u2022 ${chalk14.green(completeLanguages.join(", "))} ${completeLanguages.length === 1 ? "is" : "are"} completely translated`
7231
+ );
7232
+ }
7233
+ if (targetLocales.length > 1) {
7234
+ console.log(`\u2022 Translating one language at a time reduces complexity`);
7235
+ console.log(`\u2022 Try 'lingo.dev@latest i18n --locale ${targetLocales[0]}' to process just one language`);
7236
+ }
7237
+ trackEvent(authId || "status", "cmd.status.success", {
7238
+ i18nConfig,
7239
+ flags,
7240
+ totalSourceKeyCount,
7241
+ languageStats,
7242
+ totalWordsToTranslate,
7243
+ authenticated: !!authId
7244
+ });
7245
+ } catch (error) {
7246
+ ora.fail(error.message);
7247
+ trackEvent(authId || "status", "cmd.status.error", {
7248
+ flags,
7249
+ error: error.message,
7250
+ authenticated: !!authId
7251
+ });
7252
+ process.exit(1);
7253
+ }
7254
+ });
7255
+ function parseFlags2(options) {
7256
+ return Z11.object({
7257
+ locale: Z11.array(localeCodeSchema3).optional(),
7258
+ bucket: Z11.array(bucketTypeSchema4).optional(),
7259
+ force: Z11.boolean().optional(),
7260
+ confirm: Z11.boolean().optional(),
7261
+ verbose: Z11.boolean().optional(),
7262
+ file: Z11.array(Z11.string()).optional(),
7263
+ apiKey: Z11.string().optional()
7264
+ }).parse(options);
7276
7265
  }
7277
- function createWorkerStatusMessage(args) {
7278
- const displayPath = args.assignedTask.bucketPathPattern.replace(
7279
- "[locale]",
7280
- args.assignedTask.targetLocale
7281
- );
7282
- return `[${chalk13.hex(colors.yellow)(`${args.percentage}%`)}] Processing: ${chalk13.dim(
7283
- displayPath
7284
- )} (${chalk13.hex(colors.yellow)(args.assignedTask.sourceLocale)} -> ${chalk13.hex(
7285
- colors.yellow
7286
- )(args.assignedTask.targetLocale)})`;
7266
+ async function tryAuthenticate(settings) {
7267
+ if (!settings.auth.apiKey) {
7268
+ return null;
7269
+ }
7270
+ try {
7271
+ const authenticator = createAuthenticator({
7272
+ apiKey: settings.auth.apiKey,
7273
+ apiUrl: settings.auth.apiUrl
7274
+ });
7275
+ const user = await authenticator.whoami();
7276
+ return user;
7277
+ } catch (error) {
7278
+ return null;
7279
+ }
7287
7280
  }
7288
- function createExecutionProgressMessage(ctx) {
7289
- const succeededTasksCount = countTasks(
7290
- ctx,
7291
- (_t, result) => result.status === "success"
7292
- );
7293
- const failedTasksCount = countTasks(
7294
- ctx,
7295
- (_t, result) => result.status === "error"
7296
- );
7297
- const skippedTasksCount = countTasks(
7298
- ctx,
7299
- (_t, result) => result.status === "skipped"
7300
- );
7301
- return `Processed ${chalk13.green(succeededTasksCount)}/${ctx.tasks.length}, Failed ${chalk13.red(failedTasksCount)}, Skipped ${chalk13.dim(skippedTasksCount)}`;
7302
- }
7303
- function createLoaderForTask(assignedTask) {
7304
- const bucketLoader = createBucketLoader(
7305
- assignedTask.bucketType,
7306
- assignedTask.bucketPathPattern,
7307
- {
7308
- defaultLocale: assignedTask.sourceLocale,
7309
- isCacheRestore: false,
7310
- injectLocale: assignedTask.injectLocale
7311
- },
7312
- assignedTask.lockedKeys,
7313
- assignedTask.lockedPatterns
7314
- );
7315
- bucketLoader.setDefaultLocale(assignedTask.sourceLocale);
7316
- return bucketLoader;
7317
- }
7318
- function createWorkerTask(args) {
7319
- return {
7320
- title: "Initializing...",
7321
- task: async (_subCtx, subTask) => {
7322
- for (const assignedTask of args.assignedTasks) {
7323
- subTask.title = createWorkerStatusMessage({
7324
- assignedTask,
7325
- percentage: 0
7326
- });
7327
- const bucketLoader = createLoaderForTask(assignedTask);
7328
- const deltaProcessor = createDeltaProcessor(
7329
- assignedTask.bucketPathPattern
7330
- );
7331
- const taskResult = await args.i18nLimiter(async () => {
7332
- try {
7333
- const sourceData = await bucketLoader.pull(
7334
- assignedTask.sourceLocale
7335
- );
7336
- const targetData = await bucketLoader.pull(
7337
- assignedTask.targetLocale
7338
- );
7339
- const checksums = await deltaProcessor.loadChecksums();
7340
- const delta = await deltaProcessor.calculateDelta({
7341
- sourceData,
7342
- targetData,
7343
- checksums
7344
- });
7345
- const processableData = _32.chain(sourceData).entries().filter(
7346
- ([key, value]) => delta.added.includes(key) || delta.updated.includes(key) || !!args.ctx.flags.force
7347
- ).fromPairs().value();
7348
- if (!Object.keys(processableData).length) {
7349
- return { status: "skipped" };
7350
- }
7351
- const processedTargetData = await args.ctx.localizer.localize(
7352
- {
7353
- sourceLocale: assignedTask.sourceLocale,
7354
- targetLocale: assignedTask.targetLocale,
7355
- sourceData,
7356
- targetData,
7357
- processableData
7358
- },
7359
- (progress) => {
7360
- subTask.title = createWorkerStatusMessage({
7361
- assignedTask,
7362
- percentage: progress
7363
- });
7364
- }
7365
- );
7366
- let finalTargetData = _32.merge(
7367
- {},
7368
- sourceData,
7369
- targetData,
7370
- processedTargetData
7371
- );
7372
- finalTargetData = _32.chain(finalTargetData).entries().map(([key, value]) => {
7373
- const renaming = delta.renamed.find(
7374
- ([oldKey]) => oldKey === key
7375
- );
7376
- if (!renaming) {
7377
- return [key, value];
7378
- }
7379
- return [renaming[1], value];
7380
- }).fromPairs().value();
7381
- await args.ioLimiter(async () => {
7382
- await bucketLoader.pull(assignedTask.sourceLocale);
7383
- await bucketLoader.push(
7384
- assignedTask.targetLocale,
7385
- finalTargetData
7386
- );
7387
- const checksums2 = await deltaProcessor.createChecksums(sourceData);
7388
- await deltaProcessor.saveChecksums(checksums2);
7389
- });
7390
- return { status: "success" };
7391
- } catch (error) {
7392
- return {
7393
- status: "error",
7394
- error
7395
- };
7396
- }
7397
- });
7398
- args.ctx.results.set(assignedTask, taskResult);
7399
- }
7400
- subTask.title = "Done";
7401
- }
7402
- };
7403
- }
7404
- function countTasks(ctx, predicate) {
7405
- return Array.from(ctx.results.entries()).filter(
7406
- ([task, result]) => predicate(task, result)
7407
- ).length;
7281
+ function validateParams2(i18nConfig, flags) {
7282
+ if (!i18nConfig) {
7283
+ throw new CLIError({
7284
+ message: "i18n.json not found. Please run `lingo.dev init` to initialize the project.",
7285
+ docUrl: "i18nNotFound"
7286
+ });
7287
+ } else if (!i18nConfig.buckets || !Object.keys(i18nConfig.buckets).length) {
7288
+ throw new CLIError({
7289
+ message: "No buckets found in i18n.json. Please add at least one bucket containing i18n content.",
7290
+ docUrl: "bucketNotFound"
7291
+ });
7292
+ } else if (flags.locale?.some((locale) => !i18nConfig.locale.targets.includes(locale))) {
7293
+ throw new CLIError({
7294
+ message: `One or more specified locales do not exist in i18n.json locale.targets. Please add them to the list and try again.`,
7295
+ docUrl: "localeTargetNotFound"
7296
+ });
7297
+ } else if (flags.bucket?.some((bucket) => !i18nConfig.buckets[bucket])) {
7298
+ throw new CLIError({
7299
+ message: `One or more specified buckets do not exist in i18n.json. Please add them to the list and try again.`,
7300
+ docUrl: "bucketNotFound"
7301
+ });
7302
+ }
7408
7303
  }
7409
7304
 
7410
- // src/cli/cmd/run/_types.ts
7411
- import {
7412
- bucketTypeSchema as bucketTypeSchema4,
7413
- localeCodeSchema as localeCodeSchema3
7414
- } from "@lingo.dev/_spec";
7415
- import { z as z2 } from "zod";
7416
- var flagsSchema2 = z2.object({
7417
- locale: z2.array(localeCodeSchema3).optional(),
7418
- bucket: z2.array(bucketTypeSchema4).optional(),
7419
- key: z2.array(z2.string()).optional(),
7420
- file: z2.array(z2.string()).optional(),
7421
- apiKey: z2.string().optional(),
7422
- force: z2.boolean().optional(),
7423
- frozen: z2.boolean().optional(),
7424
- verbose: z2.boolean().optional(),
7425
- strict: z2.boolean().optional(),
7426
- interactive: z2.boolean().default(false),
7427
- concurrency: z2.number().positive().default(10),
7428
- debug: z2.boolean().default(false)
7429
- });
7430
-
7431
- // src/cli/cmd/run/_render.ts
7432
- import chalk14 from "chalk";
7305
+ // src/cli/cmd/may-the-fourth.ts
7306
+ import { Command as Command17 } from "interactive-commander";
7307
+ import * as cp from "node:child_process";
7433
7308
  import figlet2 from "figlet";
7309
+ import chalk15 from "chalk";
7434
7310
  import { vice as vice2 } from "gradient-string";
7435
- import readline2 from "readline";
7311
+ var colors2 = {
7312
+ orange: "#ff6600",
7313
+ green: "#6ae300",
7314
+ blue: "#0090ff",
7315
+ yellow: "#ffcc00",
7316
+ grey: "#808080",
7317
+ red: "#ff0000"
7318
+ };
7319
+ var may_the_fourth_default = new Command17().command("may-the-fourth").description("May the Fourth be with you").helpOption("-h, --help", "Show help").action(async () => {
7320
+ await renderClear2();
7321
+ await renderBanner2();
7322
+ await renderSpacer2();
7323
+ console.log(chalk15.hex(colors2.yellow)("Loading the Star Wars movie..."));
7324
+ await renderSpacer2();
7325
+ await new Promise((resolve, reject) => {
7326
+ const ssh = cp.spawn("ssh", ["starwarstel.net"], {
7327
+ stdio: "inherit"
7328
+ });
7329
+ ssh.on("close", (code) => {
7330
+ if (code !== 0) {
7331
+ console.error(`SSH process exited with code ${code}`);
7332
+ }
7333
+ resolve();
7334
+ });
7335
+ ssh.on("error", (err) => {
7336
+ console.error("Failed to start SSH process:", err);
7337
+ reject(err);
7338
+ });
7339
+ });
7340
+ await renderSpacer2();
7341
+ console.log(
7342
+ `${chalk15.hex(colors2.green)("We hope you enjoyed it! :)")} ${chalk15.hex(colors2.blue)("May the Fourth be with you! \u{1F680}")}`
7343
+ );
7344
+ await renderSpacer2();
7345
+ console.log(chalk15.dim(`---`));
7346
+ await renderSpacer2();
7347
+ await renderHero2();
7348
+ });
7436
7349
  async function renderClear2() {
7437
7350
  console.log("\x1Bc");
7438
7351
  }
@@ -7452,114 +7365,242 @@ async function renderBanner2() {
7452
7365
  }
7453
7366
  async function renderHero2() {
7454
7367
  console.log(
7455
- `\u26A1\uFE0F ${chalk14.hex(colors.green)("Lingo.dev")} - open-source, AI-powered i18n CLI for web & mobile localization.`
7456
- );
7457
- console.log("");
7458
- const label1 = "\u2B50 GitHub Repo:";
7459
- const label2 = "\u{1F4DA} Docs:";
7460
- const label3 = "\u{1F4AC} 24/7 Support:";
7461
- const maxLabelWidth = 17;
7462
- console.log(
7463
- `${chalk14.hex(colors.blue)(label1.padEnd(maxLabelWidth))} ${chalk14.hex(colors.blue)("https://lingo.dev/go/gh")}`
7464
- );
7465
- console.log(
7466
- `${chalk14.hex(colors.blue)(label2.padEnd(maxLabelWidth + 1))} ${chalk14.hex(colors.blue)("https://lingo.dev/go/docs")}`
7368
+ `\u26A1\uFE0F ${chalk15.hex(colors2.green)("Lingo.dev")} - open-source, AI-powered i18n CLI for web & mobile localization.`
7467
7369
  );
7370
+ console.log(" ");
7468
7371
  console.log(
7469
- `${chalk14.hex(colors.blue)(label3.padEnd(maxLabelWidth + 1))} ${chalk14.hex(colors.blue)("hi@lingo.dev")}`
7372
+ chalk15.hex(colors2.blue)("\u2B50 GitHub Repo: https://lingo.dev/go/gh")
7470
7373
  );
7471
- }
7472
- async function pauseIfDebug(debug) {
7473
- if (debug) {
7474
- await waitForUserPrompt("Press Enter to continue...");
7475
- }
7476
- }
7477
- async function waitForUserPrompt(message) {
7478
- const rl = readline2.createInterface({
7479
- input: process.stdin,
7480
- output: process.stdout
7481
- });
7482
- return new Promise((resolve) => {
7483
- rl.question(chalk14.dim(`[${message}]
7484
- `), () => {
7485
- rl.close();
7486
- resolve();
7487
- });
7488
- });
7489
- }
7490
- async function renderSummary(ctx) {
7491
- console.log(chalk14.hex(colors.green)("[Done]"));
7492
- const skippedTasksCount = Array.from(ctx.results.values()).filter(
7493
- (r) => r.status === "skipped"
7494
- ).length;
7495
- console.log(`\u2022 ${chalk14.hex(colors.yellow)(skippedTasksCount)} from cache`);
7496
- const succeededTasksCount = Array.from(ctx.results.values()).filter(
7497
- (r) => r.status === "success"
7498
- ).length;
7499
- console.log(`\u2022 ${chalk14.hex(colors.yellow)(succeededTasksCount)} processed`);
7500
- const failedTasksCount = Array.from(ctx.results.values()).filter(
7501
- (r) => r.status === "error"
7502
- ).length;
7503
- console.log(`\u2022 ${chalk14.hex(colors.yellow)(failedTasksCount)} failed`);
7374
+ console.log(chalk15.hex(colors2.blue)("\u{1F4AC} 24/7 Support: hi@lingo.dev"));
7504
7375
  }
7505
7376
 
7506
- // src/cli/cmd/run/index.ts
7507
- var run_default = new Command17().command("run").description("Run Lingo.dev localization engine").helpOption("-h, --help", "Show help").option(
7508
- "--locale <locale>",
7509
- "Locale to process",
7510
- (val, prev) => prev ? [...prev, val] : [val]
7511
- ).option(
7512
- "--bucket <bucket>",
7513
- "Bucket to process",
7514
- (val, prev) => prev ? [...prev, val] : [val]
7515
- ).option(
7516
- "--file <file>",
7517
- "File to process. Process only files that include this string in their path. Useful if you have a lot of files and want to focus on a specific one. Specify more files separated by commas or spaces.",
7518
- (val, prev) => prev ? [...prev, val] : [val]
7519
- ).option(
7520
- "--key <key>",
7521
- "Key to process. Process only a specific translation key, useful for updating a single entry",
7522
- (val, prev) => prev ? [...prev, val] : [val]
7523
- ).option(
7524
- "--force",
7525
- "Ignore lockfile and process all keys, useful for full re-translation"
7526
- ).option(
7527
- "--api-key <api-key>",
7528
- "Explicitly set the API key to use, override the default API key from settings"
7529
- ).option(
7530
- "--debug",
7531
- "Pause execution at start for debugging purposes, waits for user confirmation before proceeding"
7532
- ).option(
7533
- "--concurrency <concurrency>",
7534
- "Number of concurrent tasks to run",
7535
- (val) => parseInt(val)
7536
- ).action(async (args) => {
7537
- try {
7538
- const ctx = {
7539
- flags: flagsSchema2.parse(args),
7540
- config: null,
7541
- results: /* @__PURE__ */ new Map(),
7542
- tasks: [],
7543
- localizer: null
7544
- };
7545
- await pauseIfDebug(ctx.flags.debug);
7546
- await renderClear2();
7547
- await renderSpacer2();
7548
- await renderBanner2();
7549
- await renderHero2();
7550
- await renderSpacer2();
7551
- await setup(ctx);
7552
- await renderSpacer2();
7553
- await plan(ctx);
7554
- await renderSpacer2();
7555
- await execute(ctx);
7556
- await renderSpacer2();
7557
- await renderSummary(ctx);
7558
- await renderSpacer2();
7559
- } catch (error) {
7560
- process.exit(1);
7561
- }
7562
- });
7377
+ // package.json
7378
+ var package_default = {
7379
+ name: "lingo.dev",
7380
+ version: "0.93.6",
7381
+ description: "Lingo.dev CLI",
7382
+ private: false,
7383
+ publishConfig: {
7384
+ access: "public"
7385
+ },
7386
+ type: "module",
7387
+ sideEffects: false,
7388
+ exports: {
7389
+ "./cli": {
7390
+ types: "./build/cli.d.ts",
7391
+ import: "./build/cli.mjs",
7392
+ require: "./build/cli.cjs"
7393
+ },
7394
+ "./sdk": {
7395
+ types: "./build/sdk.d.ts",
7396
+ import: "./build/sdk.mjs",
7397
+ require: "./build/sdk.cjs"
7398
+ },
7399
+ "./spec": {
7400
+ types: "./build/spec.d.ts",
7401
+ import: "./build/spec.mjs",
7402
+ require: "./build/spec.cjs"
7403
+ },
7404
+ "./compiler": {
7405
+ types: "./build/compiler.d.ts",
7406
+ import: "./build/compiler.mjs",
7407
+ require: "./build/compiler.cjs"
7408
+ },
7409
+ "./react": {
7410
+ types: "./build/react.d.ts",
7411
+ import: "./build/react.mjs",
7412
+ require: "./build/react.cjs"
7413
+ },
7414
+ "./react-client": {
7415
+ types: "./build/react/client.d.ts",
7416
+ import: "./build/react/client.mjs",
7417
+ require: "./build/react/client.cjs"
7418
+ },
7419
+ "./react/client": {
7420
+ types: "./build/react/client.d.ts",
7421
+ import: "./build/react/client.mjs",
7422
+ require: "./build/react/client.cjs"
7423
+ },
7424
+ "./react-rsc": {
7425
+ types: "./build/react/rsc.d.ts",
7426
+ import: "./build/react/rsc.mjs",
7427
+ require: "./build/react/rsc.cjs"
7428
+ },
7429
+ "./react/rsc": {
7430
+ types: "./build/react/rsc.d.ts",
7431
+ import: "./build/react/rsc.mjs",
7432
+ require: "./build/react/rsc.cjs"
7433
+ },
7434
+ "./react-router": {
7435
+ types: "./build/react/react-router.d.ts",
7436
+ import: "./build/react/react-router.mjs",
7437
+ require: "./build/react/react-router.cjs"
7438
+ },
7439
+ "./react/react-router": {
7440
+ types: "./build/react/react-router.d.ts",
7441
+ import: "./build/react/react-router.mjs",
7442
+ require: "./build/react/react-router.cjs"
7443
+ }
7444
+ },
7445
+ typesVersions: {
7446
+ "*": {
7447
+ sdk: [
7448
+ "./build/sdk.d.ts"
7449
+ ],
7450
+ cli: [
7451
+ "./build/cli.d.ts"
7452
+ ],
7453
+ spec: [
7454
+ "./build/spec.d.ts"
7455
+ ],
7456
+ compiler: [
7457
+ "./build/compiler.d.ts"
7458
+ ],
7459
+ react: [
7460
+ "./build/react.d.ts"
7461
+ ],
7462
+ "react/client": [
7463
+ "./build/react/client.d.ts"
7464
+ ],
7465
+ "react/rsc": [
7466
+ "./build/react/rsc.d.ts"
7467
+ ],
7468
+ "react/react-router": [
7469
+ "./build/react/react-router.d.ts"
7470
+ ]
7471
+ }
7472
+ },
7473
+ bin: {
7474
+ "lingo.dev": "./bin/cli.mjs"
7475
+ },
7476
+ files: [
7477
+ "bin",
7478
+ "build"
7479
+ ],
7480
+ scripts: {
7481
+ "lingo.dev": "node --inspect=9229 ./bin/cli.mjs",
7482
+ dev: "tsup --watch",
7483
+ build: "tsc --noEmit && tsup",
7484
+ test: "vitest run",
7485
+ "test:watch": "vitest",
7486
+ clean: "rm -rf build"
7487
+ },
7488
+ keywords: [],
7489
+ author: "",
7490
+ license: "Apache-2.0",
7491
+ dependencies: {
7492
+ "@ai-sdk/anthropic": "^1.2.11",
7493
+ "@ai-sdk/openai": "^1.3.22",
7494
+ "@babel/generator": "^7.27.1",
7495
+ "@babel/parser": "^7.27.1",
7496
+ "@babel/traverse": "^7.27.4",
7497
+ "@babel/types": "^7.27.1",
7498
+ "@datocms/cma-client-node": "^4.0.1",
7499
+ "@gitbeaker/rest": "^39.34.3",
7500
+ "@inkjs/ui": "^2.0.0",
7501
+ "@inquirer/prompts": "^7.4.1",
7502
+ "@lingo.dev/_sdk": "workspace:*",
7503
+ "@lingo.dev/_spec": "workspace:*",
7504
+ "@lingo.dev/_react": "workspace:*",
7505
+ "@lingo.dev/_compiler": "workspace:*",
7506
+ "@modelcontextprotocol/sdk": "^1.5.0",
7507
+ "@paralleldrive/cuid2": "^2.2.2",
7508
+ ai: "^4.3.15",
7509
+ bitbucket: "^2.12.0",
7510
+ chalk: "^5.4.1",
7511
+ "cli-progress": "^3.12.0",
7512
+ "cli-table3": "^0.6.5",
7513
+ cors: "^2.8.5",
7514
+ "csv-parse": "^5.6.0",
7515
+ "csv-stringify": "^6.5.2",
7516
+ "date-fns": "^4.1.0",
7517
+ dedent: "^1.5.3",
7518
+ diff: "^7.0.0",
7519
+ dotenv: "^16.4.7",
7520
+ express: "^5.1.0",
7521
+ "external-editor": "^3.1.0",
7522
+ figlet: "^1.8.0",
7523
+ flat: "^6.0.1",
7524
+ "gettext-parser": "^8.0.0",
7525
+ glob: "<11.0.0",
7526
+ "gradient-string": "^3.0.0",
7527
+ "gray-matter": "^4.0.3",
7528
+ ini: "^5.0.0",
7529
+ ink: "^4.2.0",
7530
+ "ink-progress-bar": "^3.0.0",
7531
+ "ink-spinner": "^5.0.0",
7532
+ inquirer: "^12.6.0",
7533
+ "interactive-commander": "^0.5.194",
7534
+ "is-url": "^1.2.4",
7535
+ jsdom: "^25.0.1",
7536
+ json5: "^2.2.3",
7537
+ jsonrepair: "^3.11.2",
7538
+ listr2: "^8.3.2",
7539
+ lodash: "^4.17.21",
7540
+ marked: "^15.0.6",
7541
+ "mdast-util-from-markdown": "^2.0.2",
7542
+ "mdast-util-gfm": "^3.1.0",
7543
+ "micromark-extension-gfm": "^3.0.0",
7544
+ "node-machine-id": "^1.1.12",
7545
+ "node-webvtt": "^1.9.4",
7546
+ "object-hash": "^3.0.0",
7547
+ octokit: "^4.0.2",
7548
+ open: "^10.1.2",
7549
+ ora: "^8.1.1",
7550
+ "p-limit": "^6.2.0",
7551
+ "php-array-reader": "^2.1.2",
7552
+ plist: "^3.1.0",
7553
+ "posthog-node": "^4.17.0",
7554
+ prettier: "^3.4.2",
7555
+ react: "^18.3.1",
7556
+ "rehype-stringify": "^10.0.1",
7557
+ "remark-disable-tokenizers": "^1.1.1",
7558
+ "remark-frontmatter": "^5.0.0",
7559
+ "remark-gfm": "^4.0.1",
7560
+ "remark-mdx": "^3.1.0",
7561
+ "remark-mdx-frontmatter": "^5.1.0",
7562
+ "remark-parse": "^11.0.0",
7563
+ "remark-rehype": "^11.1.2",
7564
+ "remark-stringify": "^11.0.0",
7565
+ "srt-parser-2": "^1.2.3",
7566
+ unified: "^11.0.5",
7567
+ "unist-util-visit": "^5.0.0",
7568
+ vfile: "^6.0.3",
7569
+ xliff: "^6.2.1",
7570
+ xml2js: "^0.6.2",
7571
+ xpath: "^0.0.34",
7572
+ yaml: "^2.7.0",
7573
+ zod: "^3.24.1"
7574
+ },
7575
+ devDependencies: {
7576
+ "@types/babel__generator": "^7.27.0",
7577
+ "@types/cli-progress": "^3.11.6",
7578
+ "@types/cors": "^2.8.17",
7579
+ "@types/diff": "^7.0.0",
7580
+ "@types/express": "^5.0.1",
7581
+ "@types/figlet": "^1.7.0",
7582
+ "@types/gettext-parser": "^4.0.4",
7583
+ "@types/glob": "^8.1.0",
7584
+ "@types/ini": "^4.1.1",
7585
+ "@types/is-url": "^1.2.32",
7586
+ "@types/jsdom": "^21.1.7",
7587
+ "@types/lodash": "^4.17.16",
7588
+ "@types/mdast": "^4.0.4",
7589
+ "@types/node": "^22.10.2",
7590
+ "@types/node-gettext": "^3.0.6",
7591
+ "@types/object-hash": "^3.0.6",
7592
+ "@types/plist": "^3.0.5",
7593
+ "@types/react": "^18.3.20",
7594
+ "@types/xml2js": "^0.4.14",
7595
+ tsup: "^8.3.5",
7596
+ typescript: "^5.8.3",
7597
+ vitest: "^3.1.2"
7598
+ },
7599
+ engines: {
7600
+ node: ">=18"
7601
+ },
7602
+ packageManager: "pnpm@9.12.3"
7603
+ };
7563
7604
 
7564
7605
  // src/cli/index.ts
7565
7606
  dotenv.config();