lingo.dev 0.79.4 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.mjs CHANGED
@@ -158,7 +158,8 @@ function createAuthenticator(params) {
158
158
  return null;
159
159
  }
160
160
  return {
161
- email: payload.email
161
+ email: payload.email,
162
+ id: payload.id
162
163
  };
163
164
  }
164
165
  return null;
@@ -988,7 +989,6 @@ var show_default = new Command5().command("show").description("Prints out the cu
988
989
 
989
990
  // src/cli/cmd/i18n.ts
990
991
  import { bucketTypeSchema, localeCodeSchema, resolveOverriddenLocale as resolveOverriddenLocale3 } from "@lingo.dev/_spec";
991
- import { LingoDotDevEngine } from "@lingo.dev/_sdk";
992
992
  import { Command as Command6 } from "interactive-commander";
993
993
  import Z4 from "zod";
994
994
  import _20 from "lodash";
@@ -2742,7 +2742,8 @@ function variableExtractLoader(params) {
2742
2742
  return createLoader({
2743
2743
  pull: async (locale, input2) => {
2744
2744
  const result = {};
2745
- for (const [key, value] of Object.entries(input2)) {
2745
+ const inputValues = _16.omitBy(input2, _16.isEmpty);
2746
+ for (const [key, value] of Object.entries(inputValues)) {
2746
2747
  const matches = value.match(specifierPattern) || [];
2747
2748
  result[key] = result[key] || {
2748
2749
  value,
@@ -2993,12 +2994,13 @@ function createInjectLocaleLoader(injectLocaleKeys) {
2993
2994
  if (!injectLocaleKeys) {
2994
2995
  return data;
2995
2996
  }
2997
+ const mergedData = _18.merge({}, originalInput, data);
2996
2998
  injectLocaleKeys.forEach((key) => {
2997
- if (_18.get(originalInput, key) === originalLocale) {
2998
- _18.set(data, key, locale);
2999
+ if (_18.get(mergedData, key) === originalLocale) {
3000
+ _18.set(mergedData, key, locale);
2999
3001
  }
3000
3002
  });
3001
- return data;
3003
+ return mergedData;
3002
3004
  }
3003
3005
  });
3004
3006
  }
@@ -3056,8 +3058,8 @@ function createBucketLoader(bucketType, bucketPathPattern, options) {
3056
3058
  createPoLoader(),
3057
3059
  createFlatLoader(),
3058
3060
  createSyncLoader(),
3059
- createUnlocalizableLoader(options.isCacheRestore, options.returnUnlocalizedKeys),
3060
- createVariableLoader({ type: "python" })
3061
+ createVariableLoader({ type: "python" }),
3062
+ createUnlocalizableLoader(options.isCacheRestore, options.returnUnlocalizedKeys)
3061
3063
  );
3062
3064
  case "properties":
3063
3065
  return composeLoaders(
@@ -3089,8 +3091,8 @@ function createBucketLoader(bucketType, bucketPathPattern, options) {
3089
3091
  createXcodeXcstringsLoader(options.defaultLocale),
3090
3092
  createFlatLoader(),
3091
3093
  createSyncLoader(),
3092
- createUnlocalizableLoader(options.isCacheRestore, options.returnUnlocalizedKeys),
3093
- createVariableLoader({ type: "ieee" })
3094
+ createVariableLoader({ type: "ieee" }),
3095
+ createUnlocalizableLoader(options.isCacheRestore, options.returnUnlocalizedKeys)
3094
3096
  );
3095
3097
  case "yaml":
3096
3098
  return composeLoaders(
@@ -3319,6 +3321,152 @@ function _tryParseJSON(line) {
3319
3321
  }
3320
3322
  }
3321
3323
 
3324
+ // src/cli/processor/lingo.ts
3325
+ import { LingoDotDevEngine } from "@lingo.dev/_sdk";
3326
+ function createLingoLocalizer(params) {
3327
+ return async (input2, onProgress) => {
3328
+ const lingo = new LingoDotDevEngine({
3329
+ apiKey: params.apiKey,
3330
+ apiUrl: params.apiUrl
3331
+ });
3332
+ const result = await lingo.localizeObject(
3333
+ input2.processableData,
3334
+ {
3335
+ sourceLocale: input2.sourceLocale,
3336
+ targetLocale: input2.targetLocale,
3337
+ reference: {
3338
+ [input2.sourceLocale]: input2.sourceData,
3339
+ [input2.targetLocale]: input2.targetData
3340
+ }
3341
+ },
3342
+ onProgress
3343
+ );
3344
+ return result;
3345
+ };
3346
+ }
3347
+
3348
+ // src/cli/processor/openai.ts
3349
+ import { generateText } from "ai";
3350
+ function createBasicTranslator(model, systemPrompt) {
3351
+ return async (input2, onProgress) => {
3352
+ if (!process.env.OPENAI_API_KEY) {
3353
+ throw new Error("OPENAI_API_KEY is not set");
3354
+ }
3355
+ const response = await generateText({
3356
+ model,
3357
+ messages: [
3358
+ {
3359
+ role: "system",
3360
+ content: JSON.stringify({
3361
+ role: "system",
3362
+ content: systemPrompt.replaceAll("{source}", input2.sourceLocale).replaceAll("{target}", input2.targetLocale)
3363
+ })
3364
+ },
3365
+ {
3366
+ role: "user",
3367
+ content: JSON.stringify({
3368
+ sourceLocale: "en",
3369
+ targetLocale: "es",
3370
+ data: {
3371
+ message: "Hello, world!"
3372
+ }
3373
+ })
3374
+ },
3375
+ {
3376
+ role: "assistant",
3377
+ content: JSON.stringify({
3378
+ sourceLocale: "en",
3379
+ targetLocale: "es",
3380
+ data: {
3381
+ message: "Hola, mundo!"
3382
+ }
3383
+ })
3384
+ },
3385
+ {
3386
+ role: "user",
3387
+ content: JSON.stringify({
3388
+ sourceLocale: "en",
3389
+ targetLocale: "es",
3390
+ data: input2.processableData
3391
+ })
3392
+ }
3393
+ ]
3394
+ });
3395
+ const result = JSON.parse(response.text);
3396
+ return result;
3397
+ };
3398
+ }
3399
+
3400
+ // src/cli/processor/index.ts
3401
+ import { createOpenAI } from "@ai-sdk/openai";
3402
+ import { createAnthropic } from "@ai-sdk/anthropic";
3403
+ function createProcessor(provider, params) {
3404
+ if (!provider || provider.id === "lingo") {
3405
+ const result = createLingoLocalizer(params);
3406
+ return result;
3407
+ } else {
3408
+ const model = getPureModelProvider(provider);
3409
+ const result = createBasicTranslator(model, provider.prompt);
3410
+ return result;
3411
+ }
3412
+ }
3413
+ function getPureModelProvider(provider) {
3414
+ switch (provider?.id) {
3415
+ case "openai":
3416
+ if (!process.env.OPENAI_API_KEY) {
3417
+ throw new Error("OPENAI_API_KEY is not set.");
3418
+ }
3419
+ return createOpenAI({
3420
+ apiKey: process.env.OPENAI_API_KEY,
3421
+ baseURL: provider.baseUrl
3422
+ })(provider.model);
3423
+ case "anthropic":
3424
+ if (!process.env.ANTHROPIC_API_KEY) {
3425
+ throw new Error("ANTHROPIC_API_KEY is not set.");
3426
+ }
3427
+ return createAnthropic({
3428
+ apiKey: process.env.ANTHROPIC_API_KEY
3429
+ })(provider.model);
3430
+ default:
3431
+ throw new Error(`Unsupported provider: ${provider?.id}`);
3432
+ }
3433
+ }
3434
+
3435
+ // src/cli/utils/exp-backoff.ts
3436
+ function withExponentialBackoff(fn, maxAttempts = 3, baseDelay = 1e3) {
3437
+ return async (...args) => {
3438
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
3439
+ try {
3440
+ return await fn(...args);
3441
+ } catch (error) {
3442
+ if (attempt === maxAttempts - 1) throw error;
3443
+ const delay = baseDelay * Math.pow(2, attempt);
3444
+ await new Promise((resolve) => setTimeout(resolve, delay));
3445
+ }
3446
+ }
3447
+ throw new Error("Unreachable code");
3448
+ };
3449
+ }
3450
+
3451
+ // src/cli/utils/observability.ts
3452
+ import { PostHog } from "posthog-node";
3453
+ async function trackEvent(distinctId, event, properties) {
3454
+ if (process.env.DO_NOT_TRACK) {
3455
+ return;
3456
+ }
3457
+ const posthog = new PostHog("phc_eR0iSoQufBxNY36k0f0T15UvHJdTfHlh8rJcxsfhfXk", {
3458
+ host: "https://eu.i.posthog.com",
3459
+ flushAt: 1,
3460
+ flushInterval: 0
3461
+ });
3462
+ await posthog.capture({
3463
+ distinctId,
3464
+ event,
3465
+ properties
3466
+ });
3467
+ await posthog.shutdown();
3468
+ }
3469
+
3322
3470
  // src/cli/cmd/i18n.ts
3323
3471
  var i18n_default = new Command6().command("i18n").description("Run Localization engine").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(
3324
3472
  "--key <key>",
@@ -3343,6 +3491,7 @@ var i18n_default = new Command6().command("i18n").description("Run Localization
3343
3491
  ]);
3344
3492
  }
3345
3493
  let hasErrors = false;
3494
+ let authId = null;
3346
3495
  try {
3347
3496
  ora.start("Loading configuration...");
3348
3497
  const i18nConfig = getConfig();
@@ -3353,7 +3502,12 @@ var i18n_default = new Command6().command("i18n").description("Run Localization
3353
3502
  ora.succeed("Localization configuration is valid");
3354
3503
  ora.start("Connecting to Lingo.dev Localization Engine...");
3355
3504
  const auth = await validateAuth(settings);
3505
+ authId = auth.id;
3356
3506
  ora.succeed(`Authenticated as ${auth.email}`);
3507
+ trackEvent(authId, "cmd.i18n.start", {
3508
+ i18nConfig,
3509
+ flags
3510
+ });
3357
3511
  let buckets = getBuckets(i18nConfig);
3358
3512
  if (flags.bucket?.length) {
3359
3513
  buckets = buckets.filter((bucket) => flags.bucket.includes(bucket.type));
@@ -3537,11 +3691,12 @@ var i18n_default = new Command6().command("i18n").description("Run Localization
3537
3691
  bucketOra.start(
3538
3692
  `[${sourceLocale} -> ${targetLocale}] [${Object.keys(processableData).length} entries] (0%) AI localization in progress...`
3539
3693
  );
3540
- const localizationEngine = createLocalizationEngineConnection({
3694
+ let processPayload = createProcessor(i18nConfig.provider, {
3541
3695
  apiKey: settings.auth.apiKey,
3542
3696
  apiUrl: settings.auth.apiUrl
3543
3697
  });
3544
- const processedTargetData = await localizationEngine.process(
3698
+ processPayload = withExponentialBackoff(processPayload, 3, 1e3);
3699
+ const processedTargetData = await processPayload(
3545
3700
  {
3546
3701
  sourceLocale,
3547
3702
  sourceData,
@@ -3615,11 +3770,19 @@ var i18n_default = new Command6().command("i18n").description("Run Localization
3615
3770
  if (flags.verbose) {
3616
3771
  ora.info("Cache file deleted.");
3617
3772
  }
3773
+ trackEvent(auth.id, "cmd.i18n.success", {
3774
+ i18nConfig,
3775
+ flags
3776
+ });
3618
3777
  } else {
3619
3778
  ora.warn("Localization completed with errors.");
3620
3779
  }
3621
3780
  } catch (error) {
3622
3781
  ora.fail(error.message);
3782
+ trackEvent(authId || "unknown", "cmd.i18n.error", {
3783
+ flags,
3784
+ error
3785
+ });
3623
3786
  process.exit(1);
3624
3787
  }
3625
3788
  });
@@ -3629,43 +3792,6 @@ function calculateDataDelta(args) {
3629
3792
  const result = _20.chain(args.sourceData).pickBy((value, key) => newKeys.includes(key) || updatedKeys.includes(key)).value();
3630
3793
  return result;
3631
3794
  }
3632
- async function retryWithExponentialBackoff(operation, maxAttempts, baseDelay = 1e3) {
3633
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
3634
- try {
3635
- return await operation();
3636
- } catch (error) {
3637
- if (attempt === maxAttempts - 1) throw error;
3638
- const delay = baseDelay * Math.pow(2, attempt);
3639
- await new Promise((resolve) => setTimeout(resolve, delay));
3640
- }
3641
- }
3642
- throw new Error("Unreachable code");
3643
- }
3644
- function createLocalizationEngineConnection(params) {
3645
- const engine = new LingoDotDevEngine({
3646
- apiKey: params.apiKey,
3647
- apiUrl: params.apiUrl
3648
- });
3649
- return {
3650
- process: async (args, onProgress) => {
3651
- return retryWithExponentialBackoff(
3652
- () => engine.localizeObject(
3653
- args.processableData,
3654
- {
3655
- sourceLocale: args.sourceLocale,
3656
- targetLocale: args.targetLocale,
3657
- reference: {
3658
- [args.sourceLocale]: args.sourceData,
3659
- [args.targetLocale]: args.targetData
3660
- }
3661
- },
3662
- onProgress
3663
- ),
3664
- params.maxRetries ?? 3
3665
- );
3666
- }
3667
- };
3668
- }
3669
3795
  function parseFlags(options) {
3670
3796
  return Z4.object({
3671
3797
  apiKey: Z4.string().optional(),
@@ -4619,7 +4745,7 @@ var ci_default = new Command10().command("ci").description("Run Lingo.dev CI/CD
4619
4745
  // package.json
4620
4746
  var package_default = {
4621
4747
  name: "lingo.dev",
4622
- version: "0.79.4",
4748
+ version: "0.80.0",
4623
4749
  description: "Lingo.dev CLI",
4624
4750
  private: false,
4625
4751
  publishConfig: {
@@ -4676,6 +4802,8 @@ var package_default = {
4676
4802
  author: "",
4677
4803
  license: "Apache-2.0",
4678
4804
  dependencies: {
4805
+ "@ai-sdk/anthropic": "^1.2.6",
4806
+ "@ai-sdk/openai": "^1.3.7",
4679
4807
  "@datocms/cma-client-node": "^3.4.0",
4680
4808
  "@gitbeaker/rest": "^39.34.3",
4681
4809
  "@inquirer/prompts": "^7.2.3",
@@ -4683,6 +4811,7 @@ var package_default = {
4683
4811
  "@lingo.dev/_spec": "workspace:*",
4684
4812
  "@modelcontextprotocol/sdk": "^1.5.0",
4685
4813
  "@paralleldrive/cuid2": "^2.2.2",
4814
+ ai: "^4.3.2",
4686
4815
  bitbucket: "^2.12.0",
4687
4816
  chalk: "^5.4.1",
4688
4817
  cors: "^2.8.5",
@@ -4719,6 +4848,7 @@ var package_default = {
4719
4848
  "p-limit": "^6.2.0",
4720
4849
  "php-array-reader": "^2.1.2",
4721
4850
  plist: "^3.1.0",
4851
+ "posthog-node": "^4.11.2",
4722
4852
  prettier: "^3.4.2",
4723
4853
  "properties-parser": "^0.6.0",
4724
4854
  slugify: "^1.6.6",