lingo.dev 0.100.1 → 0.102.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
@@ -31,7 +31,8 @@ function getSettings(explicitApiKey) {
31
31
  anthropicApiKey: env.ANTHROPIC_API_KEY || systemFile.llm?.anthropicApiKey,
32
32
  groqApiKey: env.GROQ_API_KEY || systemFile.llm?.groqApiKey,
33
33
  googleApiKey: env.GOOGLE_API_KEY || systemFile.llm?.googleApiKey,
34
- openrouterApiKey: env.OPENROUTER_API_KEY || systemFile.llm?.openrouterApiKey
34
+ openrouterApiKey: env.OPENROUTER_API_KEY || systemFile.llm?.openrouterApiKey,
35
+ mistralApiKey: env.MISTRAL_API_KEY || systemFile.llm?.mistralApiKey
35
36
  }
36
37
  };
37
38
  }
@@ -61,7 +62,8 @@ var SettingsSchema = Z.object({
61
62
  anthropicApiKey: Z.string().optional(),
62
63
  groqApiKey: Z.string().optional(),
63
64
  googleApiKey: Z.string().optional(),
64
- openrouterApiKey: Z.string().optional()
65
+ openrouterApiKey: Z.string().optional(),
66
+ mistralApiKey: Z.string().optional()
65
67
  })
66
68
  });
67
69
  var SETTINGS_KEYS = flattenZodObject(
@@ -86,7 +88,8 @@ function _loadEnv() {
86
88
  ANTHROPIC_API_KEY: Z.string().optional(),
87
89
  GROQ_API_KEY: Z.string().optional(),
88
90
  GOOGLE_API_KEY: Z.string().optional(),
89
- OPENROUTER_API_KEY: Z.string().optional()
91
+ OPENROUTER_API_KEY: Z.string().optional(),
92
+ MISTRAL_API_KEY: Z.string().optional()
90
93
  }).passthrough().parse(process.env);
91
94
  }
92
95
  function _loadSystemFile() {
@@ -104,7 +107,8 @@ function _loadSystemFile() {
104
107
  anthropicApiKey: Z.string().optional(),
105
108
  groqApiKey: Z.string().optional(),
106
109
  googleApiKey: Z.string().optional(),
107
- openrouterApiKey: Z.string().optional()
110
+ openrouterApiKey: Z.string().optional(),
111
+ mistralApiKey: Z.string().optional()
108
112
  }).optional()
109
113
  }).passthrough().parse(data);
110
114
  }
@@ -173,6 +177,12 @@ function _envVarsInfo() {
173
177
  `\u2139\uFE0F Using OPENROUTER_API_KEY env var instead of key from user config`
174
178
  );
175
179
  }
180
+ if (env.MISTRAL_API_KEY && systemFile.llm?.mistralApiKey) {
181
+ console.info(
182
+ "\x1B[36m%s\x1B[0m",
183
+ `\u2139\uFE0F Using MISTRAL_API_KEY env var instead of key from user config`
184
+ );
185
+ }
176
186
  if (env.LINGODOTDEV_API_URL) {
177
187
  console.info(
178
188
  "\x1B[36m%s\x1B[0m",
@@ -217,9 +227,12 @@ var CLIError = class extends Error {
217
227
  // src/cli/utils/cloudflare-status.ts
218
228
  async function checkCloudflareStatus() {
219
229
  try {
220
- const response = await fetch("https://www.cloudflarestatus.com/api/v2/status.json", {
221
- signal: AbortSignal.timeout(5e3)
222
- });
230
+ const response = await fetch(
231
+ "https://www.cloudflarestatus.com/api/v2/status.json",
232
+ {
233
+ signal: AbortSignal.timeout(5e3)
234
+ }
235
+ );
223
236
  if (response.ok) {
224
237
  return await response.json();
225
238
  }
@@ -378,7 +391,9 @@ async function renderBanner() {
378
391
  }
379
392
  async function renderHero() {
380
393
  console.log(
381
- `\u26A1\uFE0F ${chalk.hex(colors.green)("Lingo.dev")} - open-source, AI-powered i18n CLI for web & mobile localization.`
394
+ `\u26A1\uFE0F ${chalk.hex(colors.green)(
395
+ "Lingo.dev"
396
+ )} - open-source, AI-powered i18n CLI for web & mobile localization.`
382
397
  );
383
398
  console.log("");
384
399
  const label1 = "\u2B50 GitHub Repo:";
@@ -386,13 +401,19 @@ async function renderHero() {
386
401
  const label3 = "\u{1F4AC} 24/7 Support:";
387
402
  const maxLabelWidth = 17;
388
403
  console.log(
389
- `${chalk.hex(colors.blue)(label1.padEnd(maxLabelWidth))} ${chalk.hex(colors.blue)("https://lingo.dev/go/gh")}`
404
+ `${chalk.hex(colors.blue)(label1.padEnd(maxLabelWidth))} ${chalk.hex(
405
+ colors.blue
406
+ )("https://lingo.dev/go/gh")}`
390
407
  );
391
408
  console.log(
392
- `${chalk.hex(colors.blue)(label2.padEnd(maxLabelWidth + 1))} ${chalk.hex(colors.blue)("https://lingo.dev/go/docs")}`
409
+ `${chalk.hex(colors.blue)(label2.padEnd(maxLabelWidth + 1))} ${chalk.hex(
410
+ colors.blue
411
+ )("https://lingo.dev/go/docs")}`
393
412
  );
394
413
  console.log(
395
- `${chalk.hex(colors.blue)(label3.padEnd(maxLabelWidth + 1))} ${chalk.hex(colors.blue)("hi@lingo.dev")}`
414
+ `${chalk.hex(colors.blue)(label3.padEnd(maxLabelWidth + 1))} ${chalk.hex(
415
+ colors.blue
416
+ )("hi@lingo.dev")}`
396
417
  );
397
418
  }
398
419
  async function waitForUserPrompt(message) {
@@ -541,7 +562,11 @@ function _getConfigFilePath() {
541
562
  }
542
563
 
543
564
  // src/cli/cmd/init.ts
544
- import { defaultConfig, resolveLocaleCode as resolveLocaleCode2, bucketTypes } from "@lingo.dev/_spec";
565
+ import {
566
+ defaultConfig,
567
+ resolveLocaleCode as resolveLocaleCode2,
568
+ bucketTypes
569
+ } from "@lingo.dev/_spec";
545
570
  import fs6 from "fs";
546
571
  import path7 from "path";
547
572
  import _3 from "lodash";
@@ -583,13 +608,17 @@ function findLocaleFilesWithExtension(ext2) {
583
608
  ignore: ["node_modules/**", "package*.json", "i18n.json", "lingo.json"]
584
609
  });
585
610
  const localeFilePattern = new RegExp(`/([a-z]{2}(-[A-Z]{2})?)${ext2}$`);
586
- const localeDirectoryPattern = new RegExp(`/([a-z]{2}(-[A-Z]{2})?)/[^/]+${ext2}$`);
611
+ const localeDirectoryPattern = new RegExp(
612
+ `/([a-z]{2}(-[A-Z]{2})?)/[^/]+${ext2}$`
613
+ );
587
614
  const potentialLocaleFiles = files.filter(
588
615
  (file) => localeFilePattern.test(file) || localeDirectoryPattern.test(file)
589
616
  );
590
617
  const potantialLocaleFilesAndPatterns = potentialLocaleFiles.map((file) => {
591
618
  const matchPotentialLocales = Array.from(
592
- file.matchAll(new RegExp(`/([a-z]{2}(-[A-Z]{2})?|[^/]+)(?=/|${ext2})`, "g"))
619
+ file.matchAll(
620
+ new RegExp(`/([a-z]{2}(-[A-Z]{2})?|[^/]+)(?=/|${ext2})`, "g")
621
+ )
593
622
  );
594
623
  const potantialLocales = matchPotentialLocales.map((match2) => match2[1]);
595
624
  return { file, potantialLocales };
@@ -603,10 +632,12 @@ function findLocaleFilesWithExtension(ext2) {
603
632
  }
604
633
  return { file, locale: null };
605
634
  }).filter(({ locale }) => locale !== null);
606
- const localeFilesAndPatterns = potantialLocaleFilesAndPatterns.map(({ file, locale }) => {
607
- const pattern = file.replaceAll(new RegExp(`/${locale}${ext2}`, "g"), `/[locale]${ext2}`).replaceAll(new RegExp(`/${locale}/`, "g"), `/[locale]/`).replaceAll(new RegExp(`/${locale}/`, "g"), `/[locale]/`);
608
- return { pattern, file };
609
- });
635
+ const localeFilesAndPatterns = potantialLocaleFilesAndPatterns.map(
636
+ ({ file, locale }) => {
637
+ const pattern = file.replaceAll(new RegExp(`/${locale}${ext2}`, "g"), `/[locale]${ext2}`).replaceAll(new RegExp(`/${locale}/`, "g"), `/[locale]/`).replaceAll(new RegExp(`/${locale}/`, "g"), `/[locale]/`);
638
+ return { pattern, file };
639
+ }
640
+ );
610
641
  const grouppedFilesAndPatterns = _2.groupBy(localeFilesAndPatterns, "pattern");
611
642
  const patterns = Object.keys(grouppedFilesAndPatterns);
612
643
  const defaultPatterns = [`i18n/[locale]${ext2}`];
@@ -764,7 +795,9 @@ async function initCICD(spinner) {
764
795
  message: "Would you like to use Lingo.dev in your CI/CD?"
765
796
  });
766
797
  if (!init) {
767
- spinner.warn("CI/CD not initialized. To set it up later, see docs: https://lingo.dev/ci");
798
+ spinner.warn(
799
+ "CI/CD not initialized. To set it up later, see docs: https://lingo.dev/ci"
800
+ );
768
801
  return;
769
802
  }
770
803
  const selectedPlatforms = await checkbox({
@@ -895,7 +928,9 @@ var throwHelpError = (option, value) => {
895
928
  Do you need support for ${value} ${option}? Type "help" and we will.`
896
929
  );
897
930
  };
898
- var init_default = new InteractiveCommand().command("init").description("Initialize Lingo.dev project").helpOption("-h, --help", "Show help").addOption(new InteractiveOption("-f --force", "Overwrite existing config").prompt(void 0).default(false)).addOption(
931
+ var init_default = new InteractiveCommand().command("init").description("Initialize Lingo.dev project").helpOption("-h, --help", "Show help").addOption(
932
+ new InteractiveOption("-f --force", "Overwrite existing config").prompt(void 0).default(false)
933
+ ).addOption(
899
934
  new InteractiveOption("-s --source <locale>", "Source locale").argParser((value) => {
900
935
  try {
901
936
  resolveLocaleCode2(value);
@@ -924,7 +959,10 @@ var init_default = new InteractiveCommand().command("init").description("Initial
924
959
  return value;
925
960
  }).default("json")
926
961
  ).addOption(
927
- new InteractiveOption("-p, --paths [path...]", "List of paths for the bucket").argParser((value) => {
962
+ new InteractiveOption(
963
+ "-p, --paths [path...]",
964
+ "List of paths for the bucket"
965
+ ).argParser((value) => {
928
966
  if (!value || value.length === 0) return [];
929
967
  const values = value.includes(",") ? value.split(",") : value.split(" ");
930
968
  for (const p of values) {
@@ -985,7 +1023,9 @@ var init_default = new InteractiveCommand().command("init").description("Initial
985
1023
  }
986
1024
  if (selectedPatterns.length === 0) {
987
1025
  const useDefault = await confirm2({
988
- message: `Use (and create) default path ${defaultPatterns.join(", ")}?`
1026
+ message: `Use (and create) default path ${defaultPatterns.join(
1027
+ ", "
1028
+ )}?`
989
1029
  });
990
1030
  if (useDefault) {
991
1031
  ensurePatterns(defaultPatterns, options.source);
@@ -1009,7 +1049,9 @@ var init_default = new InteractiveCommand().command("init").description("Initial
1009
1049
  spinner.succeed("Lingo.dev project initialized");
1010
1050
  if (isInteractive) {
1011
1051
  await initCICD(spinner);
1012
- const openDocs = await confirm2({ message: "Would you like to see our docs?" });
1052
+ const openDocs = await confirm2({
1053
+ message: "Would you like to see our docs?"
1054
+ });
1013
1055
  if (openDocs) {
1014
1056
  openUrl("/go/docs");
1015
1057
  }
@@ -1040,7 +1082,9 @@ var init_default = new InteractiveCommand().command("init").description("Initial
1040
1082
  }
1041
1083
  }
1042
1084
  } else {
1043
- Ora4().warn("You are not logged in. Run `npx lingo.dev@latest auth --login` to login.");
1085
+ Ora4().warn(
1086
+ "You are not logged in. Run `npx lingo.dev@latest auth --login` to login."
1087
+ );
1044
1088
  }
1045
1089
  } else {
1046
1090
  Ora4().succeed(`Authenticated as ${auth.email}`);
@@ -1229,7 +1273,13 @@ function resolveBucketItem(bucketItem) {
1229
1273
 
1230
1274
  // src/cli/cmd/show/files.ts
1231
1275
  import { resolveOverriddenLocale as resolveOverriddenLocale2 } from "@lingo.dev/_spec";
1232
- var files_default = new Command6().command("files").description("Print out the list of files managed by Lingo.dev").option("--source", "Only show source files, files containing the original translations").option("--target", "Only show target files, files containing translated content").helpOption("-h, --help", "Show help").action(async (type) => {
1276
+ var files_default = new Command6().command("files").description("Print out the list of files managed by Lingo.dev").option(
1277
+ "--source",
1278
+ "Only show source files, files containing the original translations"
1279
+ ).option(
1280
+ "--target",
1281
+ "Only show target files, files containing translated content"
1282
+ ).helpOption("-h, --help", "Show help").action(async (type) => {
1233
1283
  const ora = Ora6();
1234
1284
  try {
1235
1285
  try {
@@ -1243,12 +1293,26 @@ var files_default = new Command6().command("files").description("Print out the l
1243
1293
  const buckets = getBuckets(i18nConfig);
1244
1294
  for (const bucket of buckets) {
1245
1295
  for (const bucketConfig of bucket.paths) {
1246
- const sourceLocale = resolveOverriddenLocale2(i18nConfig.locale.source, bucketConfig.delimiter);
1247
- const sourcePath = bucketConfig.pathPattern.replace(/\[locale\]/g, sourceLocale);
1248
- const targetPaths = i18nConfig.locale.targets.map((_targetLocale) => {
1249
- const targetLocale = resolveOverriddenLocale2(_targetLocale, bucketConfig.delimiter);
1250
- return bucketConfig.pathPattern.replace(/\[locale\]/g, targetLocale);
1251
- });
1296
+ const sourceLocale = resolveOverriddenLocale2(
1297
+ i18nConfig.locale.source,
1298
+ bucketConfig.delimiter
1299
+ );
1300
+ const sourcePath = bucketConfig.pathPattern.replace(
1301
+ /\[locale\]/g,
1302
+ sourceLocale
1303
+ );
1304
+ const targetPaths = i18nConfig.locale.targets.map(
1305
+ (_targetLocale) => {
1306
+ const targetLocale = resolveOverriddenLocale2(
1307
+ _targetLocale,
1308
+ bucketConfig.delimiter
1309
+ );
1310
+ return bucketConfig.pathPattern.replace(
1311
+ /\[locale\]/g,
1312
+ targetLocale
1313
+ );
1314
+ }
1315
+ );
1252
1316
  const result = [];
1253
1317
  if (!type.source && !type.target) {
1254
1318
  result.push(sourcePath, ...targetPaths);
@@ -1328,7 +1392,9 @@ Available keys:
1328
1392
  console.error(
1329
1393
  dedent2`
1330
1394
  ${chalk3.red("\u2716")} Unknown configuration key: ${chalk3.bold(key)}
1331
- Run ${chalk3.dim("lingo.dev config unset --help")} to see available keys.
1395
+ Run ${chalk3.dim(
1396
+ "lingo.dev config unset --help"
1397
+ )} to see available keys.
1332
1398
  `
1333
1399
  );
1334
1400
  process.exitCode = 1;
@@ -1402,7 +1468,7 @@ import {
1402
1468
  } from "@lingo.dev/_spec";
1403
1469
  import { Command as Command12 } from "interactive-commander";
1404
1470
  import Z3 from "zod";
1405
- import _29 from "lodash";
1471
+ import _30 from "lodash";
1406
1472
  import Ora7 from "ora";
1407
1473
 
1408
1474
  // src/cli/loaders/_utils.ts
@@ -1627,13 +1693,17 @@ function createTextFileLoader(pathPattern) {
1627
1693
  const trimmedResult = result.trim();
1628
1694
  return trimmedResult;
1629
1695
  },
1630
- async push(locale, data, _33, originalLocale) {
1696
+ async push(locale, data, _34, originalLocale) {
1631
1697
  const draftPath = pathPattern.replaceAll("[locale]", locale);
1632
1698
  const finalPath = path10.resolve(draftPath);
1633
1699
  const dirPath = path10.dirname(finalPath);
1634
1700
  await fs8.mkdir(dirPath, { recursive: true });
1635
1701
  const trimmedPayload = data.trim();
1636
- const trailingNewLine = await getTrailingNewLine(pathPattern, locale, originalLocale);
1702
+ const trailingNewLine = await getTrailingNewLine(
1703
+ pathPattern,
1704
+ locale,
1705
+ originalLocale
1706
+ );
1637
1707
  let finalPayload = trimmedPayload + trailingNewLine;
1638
1708
  await fs8.writeFile(finalPath, finalPayload, {
1639
1709
  encoding: "utf-8",
@@ -1727,15 +1797,22 @@ import _10 from "lodash";
1727
1797
  function createFlutterLoader() {
1728
1798
  return createLoader({
1729
1799
  async pull(locale, input2) {
1730
- const result = _10.pickBy(input2, (value, key) => !key.startsWith("@"));
1800
+ const result = _10.pickBy(input2, (value, key) => !_isMetadataKey(key));
1731
1801
  return result;
1732
1802
  },
1733
1803
  async push(locale, data, originalInput) {
1734
- const result = _10.merge({}, originalInput, { "@@locale": locale }, data);
1804
+ const metadata = _10.pickBy(
1805
+ originalInput,
1806
+ (value, key) => _isMetadataKey(key)
1807
+ );
1808
+ const result = _10.merge({}, metadata, { "@@locale": locale }, data);
1735
1809
  return result;
1736
1810
  }
1737
1811
  });
1738
1812
  }
1813
+ function _isMetadataKey(key) {
1814
+ return key.startsWith("@");
1815
+ }
1739
1816
 
1740
1817
  // src/cli/loaders/android.ts
1741
1818
  import { parseStringPromise, Builder } from "xml2js";
@@ -1930,7 +2007,9 @@ function createCsvLoader() {
1930
2007
  function _createCsvLoader() {
1931
2008
  return createLoader({
1932
2009
  async pull(locale, input2) {
1933
- const keyColumnName = detectKeyColumnName(input2.split("\n").find((l) => l.length));
2010
+ const keyColumnName = detectKeyColumnName(
2011
+ input2.split("\n").find((l) => l.length)
2012
+ );
1934
2013
  const inputParsed = parse(input2, {
1935
2014
  columns: true,
1936
2015
  skip_empty_lines: true,
@@ -1958,7 +2037,9 @@ function _createCsvLoader() {
1958
2037
  ...row,
1959
2038
  [locale]: items[row[keyColumnName]] || row[locale] || ""
1960
2039
  }));
1961
- const existingKeys = new Set(inputParsed.map((row) => row[keyColumnName]));
2040
+ const existingKeys = new Set(
2041
+ inputParsed.map((row) => row[keyColumnName])
2042
+ );
1962
2043
  Object.entries(items).forEach(([key, value]) => {
1963
2044
  if (!existingKeys.has(key)) {
1964
2045
  const newRow = {
@@ -2054,15 +2135,23 @@ function createHtmlLoader() {
2054
2135
  result[getPath(element, attr)] = value;
2055
2136
  }
2056
2137
  });
2057
- Array.from(element.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()).forEach(processNode);
2138
+ Array.from(element.childNodes).filter(
2139
+ (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
2140
+ ).forEach(processNode);
2058
2141
  }
2059
2142
  };
2060
- Array.from(document.head.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()).forEach(processNode);
2061
- Array.from(document.body.childNodes).filter((n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()).forEach(processNode);
2143
+ Array.from(document.head.childNodes).filter(
2144
+ (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
2145
+ ).forEach(processNode);
2146
+ Array.from(document.body.childNodes).filter(
2147
+ (n) => n.nodeType === 1 || n.nodeType === 3 && n.textContent?.trim()
2148
+ ).forEach(processNode);
2062
2149
  return result;
2063
2150
  },
2064
2151
  async push(locale, data, originalInput) {
2065
- const dom = new JSDOM(originalInput ?? "<!DOCTYPE html><html><head></head><body></body></html>");
2152
+ const dom = new JSDOM(
2153
+ originalInput ?? "<!DOCTYPE html><html><head></head><body></body></html>"
2154
+ );
2066
2155
  const document = dom.window.document;
2067
2156
  const htmlElement = document.documentElement;
2068
2157
  htmlElement.setAttribute("lang", locale);
@@ -2136,14 +2225,21 @@ function createMarkdownLoader() {
2136
2225
  ...Object.fromEntries(
2137
2226
  sections.map((section, index) => [`${MD_SECTION_PREFIX}${index}`, section]).filter(([, section]) => Boolean(section))
2138
2227
  ),
2139
- ...Object.fromEntries(Object.entries(frontmatter).map(([key, value]) => [`${FM_ATTR_PREFIX}${key}`, value]))
2228
+ ...Object.fromEntries(
2229
+ Object.entries(frontmatter).map(([key, value]) => [
2230
+ `${FM_ATTR_PREFIX}${key}`,
2231
+ value
2232
+ ])
2233
+ )
2140
2234
  };
2141
2235
  },
2142
2236
  async push(locale, data) {
2143
2237
  const frontmatter = Object.fromEntries(
2144
2238
  Object.entries(data).filter(([key]) => key.startsWith(FM_ATTR_PREFIX)).map(([key, value]) => [key.replace(FM_ATTR_PREFIX, ""), value])
2145
2239
  );
2146
- let content = Object.entries(data).filter(([key]) => key.startsWith(MD_SECTION_PREFIX)).sort(([a], [b]) => Number(a.split("-").pop()) - Number(b.split("-").pop())).map(([, value]) => value?.trim() ?? "").filter(Boolean).join("\n\n");
2240
+ let content = Object.entries(data).filter(([key]) => key.startsWith(MD_SECTION_PREFIX)).sort(
2241
+ ([a], [b]) => Number(a.split("-").pop()) - Number(b.split("-").pop())
2242
+ ).map(([, value]) => value?.trim() ?? "").filter(Boolean).join("\n\n");
2147
2243
  if (Object.keys(frontmatter).length > 0) {
2148
2244
  content = `
2149
2245
  ${content}`;
@@ -2176,7 +2272,7 @@ function createPropertiesLoader() {
2176
2272
  return result;
2177
2273
  },
2178
2274
  async push(locale, payload) {
2179
- const result = Object.entries(payload).filter(([_33, value]) => value != null).map(([key, value]) => `${key}=${value}`).join("\n");
2275
+ const result = Object.entries(payload).filter(([_34, value]) => value != null).map(([key, value]) => `${key}=${value}`).join("\n");
2180
2276
  return result;
2181
2277
  }
2182
2278
  });
@@ -2268,7 +2364,9 @@ function createXcodeXcstringsLoader(defaultLocale) {
2268
2364
  async pull(locale, input2, initCtx) {
2269
2365
  const resultData = {};
2270
2366
  const isSourceLanguage = locale === defaultLocale;
2271
- for (const [translationKey, _translationEntity] of Object.entries(input2.strings)) {
2367
+ for (const [translationKey, _translationEntity] of Object.entries(
2368
+ input2.strings
2369
+ )) {
2272
2370
  const rootTranslationEntity = _translationEntity;
2273
2371
  if (rootTranslationEntity.shouldTranslate === false) {
2274
2372
  continue;
@@ -2297,7 +2395,10 @@ function createXcodeXcstringsLoader(defaultLocale) {
2297
2395
  async push(locale, payload, originalInput) {
2298
2396
  const langDataToMerge = {};
2299
2397
  langDataToMerge.strings = {};
2300
- const input2 = _12.cloneDeep(originalInput) || { sourceLanguage: locale, strings: {} };
2398
+ const input2 = _12.cloneDeep(originalInput) || {
2399
+ sourceLanguage: locale,
2400
+ strings: {}
2401
+ };
2301
2402
  for (const [key, value] of Object.entries(payload)) {
2302
2403
  if (value === null || value === void 0) {
2303
2404
  continue;
@@ -2343,11 +2444,22 @@ function createXcodeXcstringsLoader(defaultLocale) {
2343
2444
  }
2344
2445
  }
2345
2446
  }
2346
- const result = _12.merge({}, originalInput, langDataToMerge);
2447
+ const originalInputWithoutLocale = originalInput ? _removeLocale(originalInput, locale) : {};
2448
+ const result = _12.merge({}, originalInputWithoutLocale, langDataToMerge);
2347
2449
  return result;
2348
2450
  }
2349
2451
  });
2350
2452
  }
2453
+ function _removeLocale(input2, locale) {
2454
+ const { sourceLanguage, strings } = input2;
2455
+ const newStrings = _12.cloneDeep(strings);
2456
+ for (const [key, value] of Object.entries(newStrings)) {
2457
+ if (value.localizations?.[locale]) {
2458
+ delete value.localizations[locale];
2459
+ }
2460
+ }
2461
+ return { sourceLanguage, strings: newStrings };
2462
+ }
2351
2463
 
2352
2464
  // src/cli/loaders/prettier.ts
2353
2465
  import path11 from "path";
@@ -2425,36 +2537,29 @@ async function formatDataWithPrettier(data, filePath, options) {
2425
2537
  import _13 from "lodash";
2426
2538
  import _isUrl from "is-url";
2427
2539
  import { isValid, parseISO } from "date-fns";
2428
- function createUnlocalizableLoader(isCacheRestore = false, returnUnlocalizedKeys = false) {
2429
- const rules = {
2430
- isEmpty: (v) => _13.isEmpty(v),
2431
- isNumber: (v) => typeof v === "number" || /^[0-9]+$/.test(v),
2432
- isBoolean: (v) => _13.isBoolean(v),
2433
- isIsoDate: (v) => _13.isString(v) && _isIsoDate(v),
2434
- isSystemId: (v) => _13.isString(v) && _isSystemId(v),
2435
- isUrl: (v) => _13.isString(v) && _isUrl(v)
2436
- };
2540
+ function createUnlocalizableLoader(returnUnlocalizedKeys = false) {
2437
2541
  return createLoader({
2438
2542
  async pull(locale, input2) {
2439
- const passthroughKeys = Object.entries(input2).filter(([key, value]) => {
2440
- for (const [ruleName, rule] of Object.entries(rules)) {
2441
- if (rule(value)) {
2442
- return true;
2443
- }
2444
- }
2445
- return false;
2446
- }).map(([key, _33]) => key);
2447
- const result = _13.omitBy(input2, (_33, key) => passthroughKeys.includes(key));
2543
+ const unlocalizableKeys = _getUnlocalizableKeys(input2);
2544
+ const result = _13.omitBy(
2545
+ input2,
2546
+ (_34, key) => unlocalizableKeys.includes(key)
2547
+ );
2448
2548
  if (returnUnlocalizedKeys) {
2449
- result.unlocalizable = _13.omitBy(input2, (_33, key) => !passthroughKeys.includes(key));
2549
+ result.unlocalizable = _13.omitBy(
2550
+ input2,
2551
+ (_34, key) => !unlocalizableKeys.includes(key)
2552
+ );
2450
2553
  }
2451
2554
  return result;
2452
2555
  },
2453
2556
  async push(locale, data, originalInput) {
2454
- if (isCacheRestore) {
2455
- return _13.merge({}, data);
2456
- }
2457
- const result = _13.merge({}, originalInput, data);
2557
+ const unlocalizableKeys = _getUnlocalizableKeys(originalInput);
2558
+ const result = _13.merge(
2559
+ {},
2560
+ data,
2561
+ _13.omitBy(originalInput, (_34, key) => !unlocalizableKeys.includes(key))
2562
+ );
2458
2563
  return result;
2459
2564
  }
2460
2565
  });
@@ -2465,6 +2570,27 @@ function _isSystemId(v) {
2465
2570
  function _isIsoDate(v) {
2466
2571
  return isValid(parseISO(v));
2467
2572
  }
2573
+ function _getUnlocalizableKeys(input2) {
2574
+ const rules = {
2575
+ isEmpty: (v) => _13.isEmpty(v),
2576
+ isNumber: (v) => typeof v === "number" || /^[0-9]+$/.test(v),
2577
+ isBoolean: (v) => _13.isBoolean(v),
2578
+ isIsoDate: (v) => _13.isString(v) && _isIsoDate(v),
2579
+ isSystemId: (v) => _13.isString(v) && _isSystemId(v),
2580
+ isUrl: (v) => _13.isString(v) && _isUrl(v)
2581
+ };
2582
+ if (!input2) {
2583
+ return [];
2584
+ }
2585
+ return Object.entries(input2).filter(([key, value]) => {
2586
+ for (const [ruleName, rule] of Object.entries(rules)) {
2587
+ if (rule(value)) {
2588
+ return true;
2589
+ }
2590
+ }
2591
+ return false;
2592
+ }).map(([key, _34]) => key);
2593
+ }
2468
2594
 
2469
2595
  // src/cli/loaders/po/index.ts
2470
2596
  import _14 from "lodash";
@@ -2736,16 +2862,24 @@ function createDatoFilterLoader() {
2736
2862
  for (const [modelId, modelInfo] of _15.entries(result)) {
2737
2863
  for (const record of modelInfo.records) {
2738
2864
  for (const [fieldId, fieldValue] of _15.entries(record)) {
2739
- const fieldInfo = modelInfo.fields.find((field) => field.api_key === fieldId);
2865
+ const fieldInfo = modelInfo.fields.find(
2866
+ (field) => field.api_key === fieldId
2867
+ );
2740
2868
  if (fieldInfo) {
2741
2869
  const sourceFieldValue = _15.get(fieldValue, [originalLocale]);
2742
- const targetFieldValue = _15.get(data, [modelId, record.id, fieldId]);
2870
+ const targetFieldValue = _15.get(data, [
2871
+ modelId,
2872
+ record.id,
2873
+ fieldId
2874
+ ]);
2743
2875
  if (targetFieldValue) {
2744
2876
  _15.set(record, [fieldId, locale], targetFieldValue);
2745
2877
  } else {
2746
2878
  _15.set(record, [fieldId, locale], sourceFieldValue);
2747
2879
  }
2748
- _15.chain(fieldValue).keys().reject((loc) => loc === locale || loc === originalLocale).filter((loc) => _15.isEmpty(_15.get(fieldValue, [loc]))).forEach((loc) => _15.set(record, [fieldId, loc], sourceFieldValue)).value();
2880
+ _15.chain(fieldValue).keys().reject((loc) => loc === locale || loc === originalLocale).filter((loc) => _15.isEmpty(_15.get(fieldValue, [loc]))).forEach(
2881
+ (loc) => _15.set(record, [fieldId, loc], sourceFieldValue)
2882
+ ).value();
2749
2883
  }
2750
2884
  }
2751
2885
  }
@@ -2763,7 +2897,9 @@ import _16 from "lodash";
2763
2897
  import { buildClient } from "@datocms/cma-client-node";
2764
2898
  function createDatoClient(params) {
2765
2899
  if (!params.apiKey) {
2766
- throw new Error("Missing required environment variable: DATO_API_TOKEN. Please set this variable and try again.");
2900
+ throw new Error(
2901
+ "Missing required environment variable: DATO_API_TOKEN. Please set this variable and try again."
2902
+ );
2767
2903
  }
2768
2904
  const dato = buildClient({
2769
2905
  apiToken: params.apiKey,
@@ -2810,11 +2946,16 @@ function createDatoClient(params) {
2810
2946
  findModels: async () => {
2811
2947
  try {
2812
2948
  const models = await dato.itemTypes.list();
2813
- const modelsWithoutBlocks = models.filter((model) => !model.modular_block);
2949
+ const modelsWithoutBlocks = models.filter(
2950
+ (model) => !model.modular_block
2951
+ );
2814
2952
  return modelsWithoutBlocks;
2815
2953
  } catch (_error) {
2816
2954
  throw new Error(
2817
- [`Failed to find models in DatoCMS.`, `Error: ${JSON.stringify(_error, null, 2)}`].join("\n\n")
2955
+ [
2956
+ `Failed to find models in DatoCMS.`,
2957
+ `Error: ${JSON.stringify(_error, null, 2)}`
2958
+ ].join("\n\n")
2818
2959
  );
2819
2960
  }
2820
2961
  },
@@ -2845,7 +2986,9 @@ function createDatoClient(params) {
2845
2986
  only_valid: "true",
2846
2987
  ids: !records.length ? void 0 : records.join(",")
2847
2988
  }
2848
- }).catch((error) => Promise.reject(error?.response?.body?.data?.[0] || error));
2989
+ }).catch(
2990
+ (error) => Promise.reject(error?.response?.body?.data?.[0] || error)
2991
+ );
2849
2992
  },
2850
2993
  findRecordsForModel: async (modelId, records) => {
2851
2994
  try {
@@ -2857,7 +3000,9 @@ function createDatoClient(params) {
2857
3000
  only_valid: "true",
2858
3001
  ids: !records?.length ? void 0 : records.join(",")
2859
3002
  }
2860
- }).catch((error) => Promise.reject(error?.response?.body?.data?.[0] || error));
3003
+ }).catch(
3004
+ (error) => Promise.reject(error?.response?.body?.data?.[0] || error)
3005
+ );
2861
3006
  return result;
2862
3007
  } catch (_error) {
2863
3008
  throw new Error(
@@ -2871,7 +3016,9 @@ function createDatoClient(params) {
2871
3016
  },
2872
3017
  updateRecord: async (id, payload) => {
2873
3018
  try {
2874
- await dato.items.update(id, payload).catch((error) => Promise.reject(error?.response?.body?.data?.[0] || error));
3019
+ await dato.items.update(id, payload).catch(
3020
+ (error) => Promise.reject(error?.response?.body?.data?.[0] || error)
3021
+ );
2875
3022
  } catch (_error) {
2876
3023
  if (_error?.attributes?.details?.message) {
2877
3024
  throw new Error(
@@ -2894,7 +3041,9 @@ function createDatoClient(params) {
2894
3041
  },
2895
3042
  enableFieldLocalization: async (args) => {
2896
3043
  try {
2897
- await dato.fields.update(`${args.modelId}::${args.fieldId}`, { localized: true }).catch((error) => Promise.reject(error?.response?.body?.data?.[0] || error));
3044
+ await dato.fields.update(`${args.modelId}::${args.fieldId}`, { localized: true }).catch(
3045
+ (error) => Promise.reject(error?.response?.body?.data?.[0] || error)
3046
+ );
2898
3047
  } catch (_error) {
2899
3048
  if (_error?.attributes?.code === "NOT_FOUND") {
2900
3049
  throw new Error(
@@ -2906,7 +3055,10 @@ function createDatoClient(params) {
2906
3055
  }
2907
3056
  if (_error?.attributes?.details?.message) {
2908
3057
  throw new Error(
2909
- [`${_error.attributes.details.message}`, `Error: ${JSON.stringify(_error, null, 2)}`].join("\n\n")
3058
+ [
3059
+ `${_error.attributes.details.message}`,
3060
+ `Error: ${JSON.stringify(_error, null, 2)}`
3061
+ ].join("\n\n")
2910
3062
  );
2911
3063
  }
2912
3064
  throw new Error(
@@ -2958,9 +3110,16 @@ function createDatoApiLoader(config, onConfigUpdate) {
2958
3110
  result.models[modelId] = { fields: [], records: [] };
2959
3111
  const fieldInfos = await getFieldDetails(dato, fields);
2960
3112
  const fieldChoices = createFieldChoices(fieldInfos);
2961
- const selectedFields = await promptFieldSelection(modelName, fieldChoices);
3113
+ const selectedFields = await promptFieldSelection(
3114
+ modelName,
3115
+ fieldChoices
3116
+ );
2962
3117
  for (const fieldInfo of fieldInfos) {
2963
- const isLocalized = await updateFieldLocalization(dato, fieldInfo, selectedFields.includes(fieldInfo.id));
3118
+ const isLocalized = await updateFieldLocalization(
3119
+ dato,
3120
+ fieldInfo,
3121
+ selectedFields.includes(fieldInfo.id)
3122
+ );
2964
3123
  if (isLocalized) {
2965
3124
  result.models[modelId].fields.push(fieldInfo);
2966
3125
  updatedConfig.models[modelId].fields = _17.uniq([
@@ -2970,9 +3129,18 @@ function createDatoApiLoader(config, onConfigUpdate) {
2970
3129
  }
2971
3130
  }
2972
3131
  const records = await dato.findRecordsForModel(modelId);
2973
- const recordChoices = createRecordChoices(records, config.models[modelId]?.records || [], project);
2974
- const selectedRecords = await promptRecordSelection(modelName, recordChoices);
2975
- result.models[modelId].records = records.filter((record) => selectedRecords.includes(record.id));
3132
+ const recordChoices = createRecordChoices(
3133
+ records,
3134
+ config.models[modelId]?.records || [],
3135
+ project
3136
+ );
3137
+ const selectedRecords = await promptRecordSelection(
3138
+ modelName,
3139
+ recordChoices
3140
+ );
3141
+ result.models[modelId].records = records.filter(
3142
+ (record) => selectedRecords.includes(record.id)
3143
+ );
2976
3144
  updatedConfig.models[modelId].records = selectedRecords;
2977
3145
  }
2978
3146
  }
@@ -3000,7 +3168,9 @@ function createDatoApiLoader(config, onConfigUpdate) {
3000
3168
  for (const modelId of _17.keys(data)) {
3001
3169
  for (let i = 0; i < data[modelId].records.length; i++) {
3002
3170
  const record = data[modelId].records[i];
3003
- console.log(`Updating record ${i + 1}/${data[modelId].records.length} for model ${modelId}...`);
3171
+ console.log(
3172
+ `Updating record ${i + 1}/${data[modelId].records.length} for model ${modelId}...`
3173
+ );
3004
3174
  await dato.updateRecord(record.id, record);
3005
3175
  }
3006
3176
  }
@@ -3039,7 +3209,9 @@ async function promptFieldSelection(modelName, choices) {
3039
3209
  }
3040
3210
  async function updateFieldLocalization(dato, fieldInfo, shouldBeLocalized) {
3041
3211
  if (shouldBeLocalized !== fieldInfo.localized) {
3042
- console.log(`${shouldBeLocalized ? "Enabling" : "Disabling"} localization for ${fieldInfo.label}...`);
3212
+ console.log(
3213
+ `${shouldBeLocalized ? "Enabling" : "Disabling"} localization for ${fieldInfo.label}...`
3214
+ );
3043
3215
  await dato.updateField(fieldInfo.id, { localized: shouldBeLocalized });
3044
3216
  }
3045
3217
  return shouldBeLocalized;
@@ -3112,9 +3284,21 @@ function createDatoExtractLoader() {
3112
3284
  for (const [virtualRecordId, record] of _18.entries(modelInfo)) {
3113
3285
  for (const [fieldName, fieldValue] of _18.entries(record)) {
3114
3286
  const [, recordId] = virtualRecordId.split("_");
3115
- const originalFieldValue = _18.get(originalInput, [modelId, recordId, fieldName]);
3116
- const rawValue = createRawDatoValue(fieldValue, originalFieldValue, true);
3117
- _18.set(result, [modelId, recordId, fieldName], rawValue || originalFieldValue);
3287
+ const originalFieldValue = _18.get(originalInput, [
3288
+ modelId,
3289
+ recordId,
3290
+ fieldName
3291
+ ]);
3292
+ const rawValue = createRawDatoValue(
3293
+ fieldValue,
3294
+ originalFieldValue,
3295
+ true
3296
+ );
3297
+ _18.set(
3298
+ result,
3299
+ [modelId, recordId, fieldName],
3300
+ rawValue || originalFieldValue
3301
+ );
3118
3302
  }
3119
3303
  }
3120
3304
  }
@@ -3184,7 +3368,11 @@ function createRawDatoValue(parsedDatoValue, originalRawDatoValue, isClean = fal
3184
3368
  case "single_block":
3185
3369
  return deserializeBlock(parsedDatoValue, originalRawDatoValue, isClean);
3186
3370
  case "rich_text":
3187
- return deserializeBlockList(parsedDatoValue, originalRawDatoValue, isClean);
3371
+ return deserializeBlockList(
3372
+ parsedDatoValue,
3373
+ originalRawDatoValue,
3374
+ isClean
3375
+ );
3188
3376
  case "json":
3189
3377
  return JSON.stringify(parsedDatoValue, null, 2);
3190
3378
  case "video":
@@ -3201,7 +3389,11 @@ function serializeStructuredText(rawStructuredText) {
3201
3389
  return serializeStructuredTextNode(rawStructuredText);
3202
3390
  function serializeStructuredTextNode(node, path17 = [], acc = {}) {
3203
3391
  if ("document" in node) {
3204
- return serializeStructuredTextNode(node.document, [...path17, "document"], acc);
3392
+ return serializeStructuredTextNode(
3393
+ node.document,
3394
+ [...path17, "document"],
3395
+ acc
3396
+ );
3205
3397
  }
3206
3398
  if (!_18.isNil(node.value)) {
3207
3399
  acc[[...path17, "value"].join(".")] = node.value;
@@ -3210,7 +3402,11 @@ function serializeStructuredText(rawStructuredText) {
3210
3402
  }
3211
3403
  if (node.children) {
3212
3404
  for (let i = 0; i < node.children.length; i++) {
3213
- serializeStructuredTextNode(node.children[i], [...path17, i.toString()], acc);
3405
+ serializeStructuredTextNode(
3406
+ node.children[i],
3407
+ [...path17, i.toString()],
3408
+ acc
3409
+ );
3214
3410
  }
3215
3411
  }
3216
3412
  return acc;
@@ -3253,7 +3449,11 @@ function deserializeVideo(parsedVideo, originalRawVideo) {
3253
3449
  function deserializeBlock(payload, rawNode, isClean = false) {
3254
3450
  const result = _18.cloneDeep(rawNode);
3255
3451
  for (const [attributeName, attributeValue] of _18.entries(rawNode.attributes)) {
3256
- const rawValue = createRawDatoValue(payload[attributeName], attributeValue, isClean);
3452
+ const rawValue = createRawDatoValue(
3453
+ payload[attributeName],
3454
+ attributeValue,
3455
+ isClean
3456
+ );
3257
3457
  _18.set(result, ["attributes", attributeName], rawValue);
3258
3458
  }
3259
3459
  if (isClean) {
@@ -3265,13 +3465,19 @@ function deserializeSeo(parsedSeo, originalRawSeo) {
3265
3465
  return _18.chain(parsedSeo).pick(["title", "description"]).defaults(originalRawSeo).value();
3266
3466
  }
3267
3467
  function deserializeBlockList(parsedBlockList, originalRawBlockList, isClean = false) {
3268
- return _18.chain(parsedBlockList).map((block, i) => deserializeBlock(block, originalRawBlockList[i], isClean)).value();
3468
+ return _18.chain(parsedBlockList).map(
3469
+ (block, i) => deserializeBlock(block, originalRawBlockList[i], isClean)
3470
+ ).value();
3269
3471
  }
3270
3472
  function deserializeStructuredText(parsedStructuredText, originalRawStructuredText) {
3271
3473
  const result = _18.cloneDeep(originalRawStructuredText);
3272
3474
  for (const [path17, value] of _18.entries(parsedStructuredText)) {
3273
3475
  const realPath = _18.chain(path17.split(".")).flatMap((s) => !_18.isNaN(_18.toNumber(s)) ? ["children", s] : s).value();
3274
- const deserializedValue = createRawDatoValue(value, _18.get(originalRawStructuredText, realPath), true);
3476
+ const deserializedValue = createRawDatoValue(
3477
+ value,
3478
+ _18.get(originalRawStructuredText, realPath),
3479
+ true
3480
+ );
3275
3481
  _18.set(result, realPath, deserializedValue);
3276
3482
  }
3277
3483
  return result;
@@ -3284,13 +3490,21 @@ function _isJson(rawDatoValue) {
3284
3490
  }
3285
3491
  }
3286
3492
  function _isFile(rawDatoValue) {
3287
- return _18.isObject(rawDatoValue) && ["alt", "title", "custom_data", "focal_point", "upload_id"].every((key) => _18.has(rawDatoValue, key));
3288
- }
3289
- function _isVideo(rawDatoValue) {
3290
- return _18.isObject(rawDatoValue) && ["url", "title", "width", "height", "provider", "provider_uid", "thumbnail_url"].every(
3493
+ return _18.isObject(rawDatoValue) && ["alt", "title", "custom_data", "focal_point", "upload_id"].every(
3291
3494
  (key) => _18.has(rawDatoValue, key)
3292
3495
  );
3293
3496
  }
3497
+ function _isVideo(rawDatoValue) {
3498
+ return _18.isObject(rawDatoValue) && [
3499
+ "url",
3500
+ "title",
3501
+ "width",
3502
+ "height",
3503
+ "provider",
3504
+ "provider_uid",
3505
+ "thumbnail_url"
3506
+ ].every((key) => _18.has(rawDatoValue, key));
3507
+ }
3294
3508
 
3295
3509
  // src/cli/loaders/dato/index.ts
3296
3510
  function createDatoLoader(configFilePath) {
@@ -3300,13 +3514,20 @@ function createDatoLoader(configFilePath) {
3300
3514
  return composeLoaders(
3301
3515
  createDatoApiLoader(
3302
3516
  datoConfig,
3303
- (updatedConfig) => fs9.writeFileSync(configFilePath, JSON5.stringify(updatedConfig, null, 2))
3517
+ (updatedConfig) => fs9.writeFileSync(
3518
+ configFilePath,
3519
+ JSON5.stringify(updatedConfig, null, 2)
3520
+ )
3304
3521
  ),
3305
3522
  createDatoFilterLoader(),
3306
3523
  createDatoExtractLoader()
3307
3524
  );
3308
3525
  } catch (error) {
3309
- throw new Error([`Failed to parse DatoCMS config file.`, `Error: ${error.message}`].join("\n\n"));
3526
+ throw new Error(
3527
+ [`Failed to parse DatoCMS config file.`, `Error: ${error.message}`].join(
3528
+ "\n\n"
3529
+ )
3530
+ );
3310
3531
  }
3311
3532
  }
3312
3533
 
@@ -3455,7 +3676,9 @@ function formatPlutilStyle(jsonData, existingJson) {
3455
3676
  }
3456
3677
  if (Array.isArray(data)) {
3457
3678
  if (data.length === 0) return "[]";
3458
- const items2 = data.map((item) => `${nextIndent}${format(item, level + 1)}`);
3679
+ const items2 = data.map(
3680
+ (item) => `${nextIndent}${format(item, level + 1)}`
3681
+ );
3459
3682
  return `[
3460
3683
  ${items2.join(",\n")}
3461
3684
  ${currentIndent}]`;
@@ -3475,7 +3698,10 @@ ${currentIndent}}`;
3475
3698
  });
3476
3699
  const items = sortedKeys.map((key) => {
3477
3700
  const value = data[key];
3478
- return `${nextIndent}${JSON.stringify(key)} : ${format(value, level + 1)}`;
3701
+ return `${nextIndent}${JSON.stringify(key)} : ${format(
3702
+ value,
3703
+ level + 1
3704
+ )}`;
3479
3705
  });
3480
3706
  return `{
3481
3707
  ${items.join(",\n")}
@@ -3548,11 +3774,23 @@ function toPhpArray(data, shortSyntax = true, indentLevel = 1) {
3548
3774
  const arrayEnd = shortSyntax ? "]" : ")";
3549
3775
  if (Array.isArray(data)) {
3550
3776
  return `${arrayStart}
3551
- ${data.map((value) => `${indent(indentLevel)}${toPhpArray(value, shortSyntax, indentLevel + 1)}`).join(",\n")}
3777
+ ${data.map(
3778
+ (value) => `${indent(indentLevel)}${toPhpArray(
3779
+ value,
3780
+ shortSyntax,
3781
+ indentLevel + 1
3782
+ )}`
3783
+ ).join(",\n")}
3552
3784
  ${indent(indentLevel - 1)}${arrayEnd}`;
3553
3785
  }
3554
3786
  const output = `${arrayStart}
3555
- ${Object.entries(data).map(([key, value]) => `${indent(indentLevel)}'${key}' => ${toPhpArray(value, shortSyntax, indentLevel + 1)}`).join(",\n")}
3787
+ ${Object.entries(data).map(
3788
+ ([key, value]) => `${indent(indentLevel)}'${key}' => ${toPhpArray(
3789
+ value,
3790
+ shortSyntax,
3791
+ indentLevel + 1
3792
+ )}`
3793
+ ).join(",\n")}
3556
3794
  ${indent(indentLevel - 1)}${arrayEnd}`;
3557
3795
  return output;
3558
3796
  }
@@ -3578,7 +3816,11 @@ function createVueJsonLoader() {
3578
3816
  }
3579
3817
  parsed.i18n[locale] = data;
3580
3818
  return `${parsed.before}<i18n>
3581
- ${JSON.stringify(parsed.i18n, null, 2)}
3819
+ ${JSON.stringify(
3820
+ parsed.i18n,
3821
+ null,
3822
+ 2
3823
+ )}
3582
3824
  </i18n>${parsed.after}`;
3583
3825
  }
3584
3826
  });
@@ -3844,42 +4086,48 @@ function createInjectLocaleLoader(injectLocaleKeys) {
3844
4086
  if (!injectLocaleKeys) {
3845
4087
  return data;
3846
4088
  }
3847
- const omitKeys = injectLocaleKeys.filter((key) => {
3848
- return _22.get(data, key) === locale;
3849
- });
4089
+ const omitKeys = _getKeysWithLocales(data, injectLocaleKeys, locale);
3850
4090
  const result = _22.omit(data, omitKeys);
3851
4091
  return result;
3852
4092
  },
3853
4093
  async push(locale, data, originalInput, originalLocale) {
3854
- if (!injectLocaleKeys) {
4094
+ if (!injectLocaleKeys || !originalInput) {
3855
4095
  return data;
3856
4096
  }
3857
- const mergedData = _22.merge({}, originalInput, data);
3858
- injectLocaleKeys.forEach((key) => {
3859
- if (_22.get(mergedData, key) === originalLocale) {
3860
- _22.set(mergedData, key, locale);
3861
- }
4097
+ const localeKeys = _getKeysWithLocales(
4098
+ originalInput,
4099
+ injectLocaleKeys,
4100
+ originalLocale
4101
+ );
4102
+ localeKeys.forEach((key) => {
4103
+ _22.set(data, key, locale);
3862
4104
  });
3863
- return mergedData;
4105
+ return data;
3864
4106
  }
3865
4107
  });
3866
4108
  }
4109
+ function _getKeysWithLocales(data, injectLocaleKeys, locale) {
4110
+ return injectLocaleKeys.filter((key) => {
4111
+ return _22.get(data, key) === locale;
4112
+ });
4113
+ }
3867
4114
 
3868
4115
  // src/cli/loaders/locked-keys.ts
3869
4116
  import _23 from "lodash";
3870
- function createLockedKeysLoader(lockedKeys, isCacheRestore = false) {
4117
+ function createLockedKeysLoader(lockedKeys) {
3871
4118
  return createLoader({
3872
- pull: async (locale, data) => _23.chain(data).pickBy((value, key) => !lockedKeys.some((lockedKey) => key.startsWith(lockedKey))).value(),
4119
+ pull: async (locale, data) => {
4120
+ return _23.pickBy(data, (value, key) => !_isLockedKey(key, lockedKeys));
4121
+ },
3873
4122
  push: async (locale, data, originalInput) => {
3874
- const lockedSubObject = _23.chain(originalInput).pickBy((value, key) => lockedKeys.some((lockedKey) => key.startsWith(lockedKey))).value();
3875
- if (isCacheRestore) {
3876
- return _23.merge({}, data, lockedSubObject);
3877
- } else {
3878
- return _23.merge({}, originalInput, data, lockedSubObject);
3879
- }
4123
+ const lockedSubObject = _23.chain(originalInput).pickBy((value, key) => _isLockedKey(key, lockedKeys)).value();
4124
+ return _23.merge({}, data, lockedSubObject);
3880
4125
  }
3881
4126
  });
3882
4127
  }
4128
+ function _isLockedKey(key, lockedKeys) {
4129
+ return lockedKeys.some((lockedKey) => key.startsWith(lockedKey));
4130
+ }
3883
4131
 
3884
4132
  // src/cli/loaders/mdx2/frontmatter-split.ts
3885
4133
  import matter2 from "gray-matter";
@@ -4109,9 +4357,14 @@ function createMdxLockedPatternsLoader(defaultPatterns) {
4109
4357
  if (!pullInput) {
4110
4358
  return data;
4111
4359
  }
4112
- const { lockedPlaceholders } = extractLockedPatterns(pullInput, patterns);
4360
+ const { lockedPlaceholders } = extractLockedPatterns(
4361
+ pullInput,
4362
+ patterns
4363
+ );
4113
4364
  let result = data;
4114
- for (const [placeholder, original] of Object.entries(lockedPlaceholders)) {
4365
+ for (const [placeholder, original] of Object.entries(
4366
+ lockedPlaceholders
4367
+ )) {
4115
4368
  result = result.replaceAll(placeholder, original);
4116
4369
  }
4117
4370
  return result;
@@ -4205,7 +4458,10 @@ function parseEjsForTranslation(input2) {
4205
4458
  if (trimmedContent) {
4206
4459
  const key = `text_${counter++}`;
4207
4460
  translatable[key] = trimmedContent;
4208
- template += textPart.content.replace(trimmedContent, `__LINGO_PLACEHOLDER_${key}__`);
4461
+ template += textPart.content.replace(
4462
+ trimmedContent,
4463
+ `__LINGO_PLACEHOLDER_${key}__`
4464
+ );
4209
4465
  } else {
4210
4466
  template += textPart.content;
4211
4467
  }
@@ -4235,7 +4491,9 @@ function createEjsLoader() {
4235
4491
  const parseResult = parseEjsForTranslation(input2);
4236
4492
  return parseResult.translatable;
4237
4493
  } catch (error) {
4238
- console.warn("Warning: Could not parse EJS template, treating as plain text");
4494
+ console.warn(
4495
+ "Warning: Could not parse EJS template, treating as plain text"
4496
+ );
4239
4497
  return { content: input2.trim() };
4240
4498
  }
4241
4499
  },
@@ -4246,15 +4504,51 @@ function createEjsLoader() {
4246
4504
  try {
4247
4505
  const parseResult = parseEjsForTranslation(originalInput);
4248
4506
  const mergedTranslatable = { ...parseResult.translatable, ...data };
4249
- return reconstructEjsWithTranslation(parseResult.content, mergedTranslatable);
4507
+ return reconstructEjsWithTranslation(
4508
+ parseResult.content,
4509
+ mergedTranslatable
4510
+ );
4250
4511
  } catch (error) {
4251
- console.warn("Warning: Could not reconstruct EJS template, returning translated data");
4512
+ console.warn(
4513
+ "Warning: Could not reconstruct EJS template, returning translated data"
4514
+ );
4252
4515
  return Object.values(data).join("\n");
4253
4516
  }
4254
4517
  }
4255
4518
  });
4256
4519
  }
4257
4520
 
4521
+ // src/cli/loaders/ensure-key-order.ts
4522
+ import _27 from "lodash";
4523
+ function createEnsureKeyOrderLoader() {
4524
+ return createLoader({
4525
+ pull: async (_locale, input2) => {
4526
+ return input2;
4527
+ },
4528
+ push: async (_locale, data, originalInput) => {
4529
+ if (!originalInput || !data) {
4530
+ return data;
4531
+ }
4532
+ return reorderKeys(data, originalInput);
4533
+ }
4534
+ });
4535
+ }
4536
+ function reorderKeys(data, originalInput) {
4537
+ if (!_27.isObject(data) || _27.isArray(data) || _27.isDate(data)) {
4538
+ return data;
4539
+ }
4540
+ const orderedData = {};
4541
+ const originalKeys = Object.keys(originalInput);
4542
+ const dataKeys = new Set(Object.keys(data));
4543
+ for (const key of originalKeys) {
4544
+ if (dataKeys.has(key)) {
4545
+ orderedData[key] = reorderKeys(data[key], originalInput[key]);
4546
+ dataKeys.delete(key);
4547
+ }
4548
+ }
4549
+ return orderedData;
4550
+ }
4551
+
4258
4552
  // src/cli/loaders/index.ts
4259
4553
  function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys, lockedPatterns, ignoredKeys) {
4260
4554
  switch (bucketType) {
@@ -4264,23 +4558,19 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4264
4558
  return composeLoaders(
4265
4559
  createTextFileLoader(bucketPathPattern),
4266
4560
  createAndroidLoader(),
4561
+ createEnsureKeyOrderLoader(),
4267
4562
  createFlatLoader(),
4268
4563
  createSyncLoader(),
4269
- createUnlocalizableLoader(
4270
- options.isCacheRestore,
4271
- options.returnUnlocalizedKeys
4272
- )
4564
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4273
4565
  );
4274
4566
  case "csv":
4275
4567
  return composeLoaders(
4276
4568
  createTextFileLoader(bucketPathPattern),
4277
4569
  createCsvLoader(),
4570
+ createEnsureKeyOrderLoader(),
4278
4571
  createFlatLoader(),
4279
4572
  createSyncLoader(),
4280
- createUnlocalizableLoader(
4281
- options.isCacheRestore,
4282
- options.returnUnlocalizedKeys
4283
- )
4573
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4284
4574
  );
4285
4575
  case "html":
4286
4576
  return composeLoaders(
@@ -4288,34 +4578,26 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4288
4578
  createPrettierLoader({ parser: "html", bucketPathPattern }),
4289
4579
  createHtmlLoader(),
4290
4580
  createSyncLoader(),
4291
- createUnlocalizableLoader(
4292
- options.isCacheRestore,
4293
- options.returnUnlocalizedKeys
4294
- )
4581
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4295
4582
  );
4296
4583
  case "ejs":
4297
4584
  return composeLoaders(
4298
4585
  createTextFileLoader(bucketPathPattern),
4299
4586
  createEjsLoader(),
4300
4587
  createSyncLoader(),
4301
- createUnlocalizableLoader(
4302
- options.isCacheRestore,
4303
- options.returnUnlocalizedKeys
4304
- )
4588
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4305
4589
  );
4306
4590
  case "json":
4307
4591
  return composeLoaders(
4308
4592
  createTextFileLoader(bucketPathPattern),
4309
4593
  createPrettierLoader({ parser: "json", bucketPathPattern }),
4310
4594
  createJsonLoader(),
4595
+ createEnsureKeyOrderLoader(),
4311
4596
  createInjectLocaleLoader(options.injectLocale),
4312
4597
  createFlatLoader(),
4313
- createLockedKeysLoader(lockedKeys || [], options.isCacheRestore),
4598
+ createLockedKeysLoader(lockedKeys || []),
4314
4599
  createSyncLoader(),
4315
- createUnlocalizableLoader(
4316
- options.isCacheRestore,
4317
- options.returnUnlocalizedKeys
4318
- )
4600
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4319
4601
  );
4320
4602
  case "markdown":
4321
4603
  return composeLoaders(
@@ -4323,10 +4605,7 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4323
4605
  createPrettierLoader({ parser: "markdown", bucketPathPattern }),
4324
4606
  createMarkdownLoader(),
4325
4607
  createSyncLoader(),
4326
- createUnlocalizableLoader(
4327
- options.isCacheRestore,
4328
- options.returnUnlocalizedKeys
4329
- )
4608
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4330
4609
  );
4331
4610
  case "mdx":
4332
4611
  return composeLoaders(
@@ -4341,55 +4620,43 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4341
4620
  createMdxSectionsSplit2Loader(),
4342
4621
  createLocalizableMdxDocumentLoader(),
4343
4622
  createFlatLoader(),
4344
- createLockedKeysLoader(lockedKeys || [], options.isCacheRestore),
4623
+ createEnsureKeyOrderLoader(),
4624
+ createLockedKeysLoader(lockedKeys || []),
4345
4625
  createSyncLoader(),
4346
- createUnlocalizableLoader(
4347
- options.isCacheRestore,
4348
- options.returnUnlocalizedKeys
4349
- )
4626
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4350
4627
  );
4351
4628
  case "po":
4352
4629
  return composeLoaders(
4353
4630
  createTextFileLoader(bucketPathPattern),
4354
4631
  createPoLoader(),
4355
4632
  createFlatLoader(),
4633
+ createEnsureKeyOrderLoader(),
4356
4634
  createSyncLoader(),
4357
4635
  createVariableLoader({ type: "python" }),
4358
- createUnlocalizableLoader(
4359
- options.isCacheRestore,
4360
- options.returnUnlocalizedKeys
4361
- )
4636
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4362
4637
  );
4363
4638
  case "properties":
4364
4639
  return composeLoaders(
4365
4640
  createTextFileLoader(bucketPathPattern),
4366
4641
  createPropertiesLoader(),
4367
4642
  createSyncLoader(),
4368
- createUnlocalizableLoader(
4369
- options.isCacheRestore,
4370
- options.returnUnlocalizedKeys
4371
- )
4643
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4372
4644
  );
4373
4645
  case "xcode-strings":
4374
4646
  return composeLoaders(
4375
4647
  createTextFileLoader(bucketPathPattern),
4376
4648
  createXcodeStringsLoader(),
4377
4649
  createSyncLoader(),
4378
- createUnlocalizableLoader(
4379
- options.isCacheRestore,
4380
- options.returnUnlocalizedKeys
4381
- )
4650
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4382
4651
  );
4383
4652
  case "xcode-stringsdict":
4384
4653
  return composeLoaders(
4385
4654
  createTextFileLoader(bucketPathPattern),
4386
4655
  createXcodeStringsdictLoader(),
4387
4656
  createFlatLoader(),
4657
+ createEnsureKeyOrderLoader(),
4388
4658
  createSyncLoader(),
4389
- createUnlocalizableLoader(
4390
- options.isCacheRestore,
4391
- options.returnUnlocalizedKeys
4392
- )
4659
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4393
4660
  );
4394
4661
  case "xcode-xcstrings":
4395
4662
  return composeLoaders(
@@ -4398,12 +4665,10 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4398
4665
  createJsonLoader(),
4399
4666
  createXcodeXcstringsLoader(options.defaultLocale),
4400
4667
  createFlatLoader(),
4668
+ createEnsureKeyOrderLoader(),
4401
4669
  createSyncLoader(),
4402
4670
  createVariableLoader({ type: "ieee" }),
4403
- createUnlocalizableLoader(
4404
- options.isCacheRestore,
4405
- options.returnUnlocalizedKeys
4406
- )
4671
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4407
4672
  );
4408
4673
  case "yaml":
4409
4674
  return composeLoaders(
@@ -4411,12 +4676,10 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4411
4676
  createPrettierLoader({ parser: "yaml", bucketPathPattern }),
4412
4677
  createYamlLoader(),
4413
4678
  createFlatLoader(),
4414
- createLockedKeysLoader(lockedKeys || [], options.isCacheRestore),
4679
+ createEnsureKeyOrderLoader(),
4680
+ createLockedKeysLoader(lockedKeys || []),
4415
4681
  createSyncLoader(),
4416
- createUnlocalizableLoader(
4417
- options.isCacheRestore,
4418
- options.returnUnlocalizedKeys
4419
- )
4682
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4420
4683
  );
4421
4684
  case "yaml-root-key":
4422
4685
  return composeLoaders(
@@ -4425,76 +4688,60 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4425
4688
  createYamlLoader(),
4426
4689
  createRootKeyLoader(true),
4427
4690
  createFlatLoader(),
4691
+ createEnsureKeyOrderLoader(),
4428
4692
  createSyncLoader(),
4429
- createUnlocalizableLoader(
4430
- options.isCacheRestore,
4431
- options.returnUnlocalizedKeys
4432
- )
4693
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4433
4694
  );
4434
4695
  case "flutter":
4435
4696
  return composeLoaders(
4436
4697
  createTextFileLoader(bucketPathPattern),
4437
4698
  createPrettierLoader({ parser: "json", bucketPathPattern }),
4438
4699
  createJsonLoader(),
4700
+ createEnsureKeyOrderLoader(),
4439
4701
  createFlutterLoader(),
4440
4702
  createFlatLoader(),
4441
4703
  createSyncLoader(),
4442
- createUnlocalizableLoader(
4443
- options.isCacheRestore,
4444
- options.returnUnlocalizedKeys
4445
- )
4704
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4446
4705
  );
4447
4706
  case "xliff":
4448
4707
  return composeLoaders(
4449
4708
  createTextFileLoader(bucketPathPattern),
4450
4709
  createXliffLoader(),
4451
4710
  createFlatLoader(),
4711
+ createEnsureKeyOrderLoader(),
4452
4712
  createSyncLoader(),
4453
- createUnlocalizableLoader(
4454
- options.isCacheRestore,
4455
- options.returnUnlocalizedKeys
4456
- )
4713
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4457
4714
  );
4458
4715
  case "xml":
4459
4716
  return composeLoaders(
4460
4717
  createTextFileLoader(bucketPathPattern),
4461
4718
  createXmlLoader(),
4462
4719
  createFlatLoader(),
4720
+ createEnsureKeyOrderLoader(),
4463
4721
  createSyncLoader(),
4464
- createUnlocalizableLoader(
4465
- options.isCacheRestore,
4466
- options.returnUnlocalizedKeys
4467
- )
4722
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4468
4723
  );
4469
4724
  case "srt":
4470
4725
  return composeLoaders(
4471
4726
  createTextFileLoader(bucketPathPattern),
4472
4727
  createSrtLoader(),
4473
4728
  createSyncLoader(),
4474
- createUnlocalizableLoader(
4475
- options.isCacheRestore,
4476
- options.returnUnlocalizedKeys
4477
- )
4729
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4478
4730
  );
4479
4731
  case "dato":
4480
4732
  return composeLoaders(
4481
4733
  createDatoLoader(bucketPathPattern),
4482
4734
  createSyncLoader(),
4483
4735
  createFlatLoader(),
4484
- createUnlocalizableLoader(
4485
- options.isCacheRestore,
4486
- options.returnUnlocalizedKeys
4487
- )
4736
+ createEnsureKeyOrderLoader(),
4737
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4488
4738
  );
4489
4739
  case "vtt":
4490
4740
  return composeLoaders(
4491
4741
  createTextFileLoader(bucketPathPattern),
4492
4742
  createVttLoader(),
4493
4743
  createSyncLoader(),
4494
- createUnlocalizableLoader(
4495
- options.isCacheRestore,
4496
- options.returnUnlocalizedKeys
4497
- )
4744
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4498
4745
  );
4499
4746
  case "php":
4500
4747
  return composeLoaders(
@@ -4502,10 +4749,8 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4502
4749
  createPhpLoader(),
4503
4750
  createSyncLoader(),
4504
4751
  createFlatLoader(),
4505
- createUnlocalizableLoader(
4506
- options.isCacheRestore,
4507
- options.returnUnlocalizedKeys
4508
- )
4752
+ createEnsureKeyOrderLoader(),
4753
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4509
4754
  );
4510
4755
  case "vue-json":
4511
4756
  return composeLoaders(
@@ -4513,10 +4758,8 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4513
4758
  createVueJsonLoader(),
4514
4759
  createSyncLoader(),
4515
4760
  createFlatLoader(),
4516
- createUnlocalizableLoader(
4517
- options.isCacheRestore,
4518
- options.returnUnlocalizedKeys
4519
- )
4761
+ createEnsureKeyOrderLoader(),
4762
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4520
4763
  );
4521
4764
  case "typescript":
4522
4765
  return composeLoaders(
@@ -4524,13 +4767,11 @@ function createBucketLoader(bucketType, bucketPathPattern, options, lockedKeys,
4524
4767
  createPrettierLoader({ parser: "typescript", bucketPathPattern }),
4525
4768
  createTypescriptLoader(),
4526
4769
  createFlatLoader(),
4770
+ createEnsureKeyOrderLoader(),
4527
4771
  createSyncLoader(),
4528
- createLockedKeysLoader(lockedKeys || [], options.isCacheRestore),
4772
+ createLockedKeysLoader(lockedKeys || []),
4529
4773
  createIgnoredKeysLoader(ignoredKeys || []),
4530
- createUnlocalizableLoader(
4531
- options.isCacheRestore,
4532
- options.returnUnlocalizedKeys
4533
- )
4774
+ createUnlocalizableLoader(options.returnUnlocalizedKeys)
4534
4775
  );
4535
4776
  }
4536
4777
  }
@@ -4574,7 +4815,7 @@ function createLingoLocalizer(params) {
4574
4815
 
4575
4816
  // src/cli/processor/basic.ts
4576
4817
  import { generateText } from "ai";
4577
- import _27 from "lodash";
4818
+ import _28 from "lodash";
4578
4819
  function createBasicTranslator(model, systemPrompt) {
4579
4820
  return async (input2, onProgress) => {
4580
4821
  const chunks = extractPayloadChunks(input2.processableData);
@@ -4588,7 +4829,7 @@ function createBasicTranslator(model, systemPrompt) {
4588
4829
  subResults.push(result2);
4589
4830
  onProgress(i / chunks.length * 100, chunk, result2);
4590
4831
  }
4591
- const result = _27.merge({}, ...subResults);
4832
+ const result = _28.merge({}, ...subResults);
4592
4833
  return result;
4593
4834
  };
4594
4835
  async function doJob(input2) {
@@ -4679,6 +4920,7 @@ import { createOpenAI } from "@ai-sdk/openai";
4679
4920
  import { createAnthropic } from "@ai-sdk/anthropic";
4680
4921
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
4681
4922
  import { createOpenRouter } from "@openrouter/ai-sdk-provider";
4923
+ import { createMistral } from "@ai-sdk/mistral";
4682
4924
  import { createOllama } from "ollama-ai-provider";
4683
4925
  function createProcessor(provider, params) {
4684
4926
  if (!provider) {
@@ -4696,7 +4938,11 @@ function getPureModelProvider(provider) {
4696
4938
 
4697
4939
  To fix this issue:
4698
4940
  1. ${envVar ? `Set ${chalk5.dim(envVar)} in your environment variables` : "Set the environment variable for your provider (if required)"}, or
4699
- 2. Remove the ${chalk5.italic("provider")} node from your i18n.json configuration to switch to ${chalk5.hex(colors.green)("Lingo.dev")}
4941
+ 2. Remove the ${chalk5.italic(
4942
+ "provider"
4943
+ )} node from your i18n.json configuration to switch to ${chalk5.hex(
4944
+ colors.green
4945
+ )("Lingo.dev")}
4700
4946
 
4701
4947
  ${chalk5.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
4702
4948
  `;
@@ -4705,7 +4951,11 @@ function getPureModelProvider(provider) {
4705
4951
 
4706
4952
  To fix this issue:
4707
4953
  1. Switch to one of the supported providers, or
4708
- 2. Remove the ${chalk5.italic("provider")} node from your i18n.json configuration to switch to ${chalk5.hex(colors.green)("Lingo.dev")}
4954
+ 2. Remove the ${chalk5.italic(
4955
+ "provider"
4956
+ )} node from your i18n.json configuration to switch to ${chalk5.hex(
4957
+ colors.green
4958
+ )("Lingo.dev")}
4709
4959
 
4710
4960
  ${chalk5.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
4711
4961
  `;
@@ -4755,6 +5005,17 @@ function getPureModelProvider(provider) {
4755
5005
  case "ollama": {
4756
5006
  return createOllama()(provider.model);
4757
5007
  }
5008
+ case "mistral": {
5009
+ if (!process.env.MISTRAL_API_KEY) {
5010
+ throw new Error(
5011
+ createMissingKeyErrorMessage("Mistral", "MISTRAL_API_KEY")
5012
+ );
5013
+ }
5014
+ return createMistral({
5015
+ apiKey: process.env.MISTRAL_API_KEY,
5016
+ baseURL: provider.baseUrl
5017
+ })(provider.model);
5018
+ }
4758
5019
  default: {
4759
5020
  throw new Error(createUnsupportedProviderErrorMessage(provider?.id));
4760
5021
  }
@@ -4815,7 +5076,7 @@ async function trackEvent(distinctId, event, properties) {
4815
5076
  }
4816
5077
 
4817
5078
  // src/cli/utils/delta.ts
4818
- import _28 from "lodash";
5079
+ import _29 from "lodash";
4819
5080
  import z from "zod";
4820
5081
 
4821
5082
  // src/cli/utils/fs.ts
@@ -4864,11 +5125,17 @@ function createDeltaProcessor(fileKey) {
4864
5125
  return checkIfFileExists(lockfilePath);
4865
5126
  },
4866
5127
  async calculateDelta(params) {
4867
- let added = _28.difference(Object.keys(params.sourceData), Object.keys(params.targetData));
4868
- let removed = _28.difference(Object.keys(params.targetData), Object.keys(params.sourceData));
4869
- const updated = _28.filter(Object.keys(params.sourceData), (key) => {
4870
- return md5(params.sourceData[key]) !== params.checksums[key] && params.checksums[key];
4871
- });
5128
+ let added = _29.difference(
5129
+ Object.keys(params.sourceData),
5130
+ Object.keys(params.targetData)
5131
+ );
5132
+ let removed = _29.difference(
5133
+ Object.keys(params.targetData),
5134
+ Object.keys(params.sourceData)
5135
+ );
5136
+ const updated = Object.keys(params.sourceData).filter(
5137
+ (key) => md5(params.sourceData[key]) !== params.checksums[key] && params.checksums[key]
5138
+ );
4872
5139
  const renamed = [];
4873
5140
  for (const addedKey of added) {
4874
5141
  const addedHash = md5(params.sourceData[addedKey]);
@@ -4879,9 +5146,18 @@ function createDeltaProcessor(fileKey) {
4879
5146
  }
4880
5147
  }
4881
5148
  }
4882
- added = added.filter((key) => !renamed.some(([oldKey, newKey]) => newKey === key));
4883
- removed = removed.filter((key) => !renamed.some(([oldKey, newKey]) => oldKey === key));
4884
- const hasChanges = [added.length > 0, removed.length > 0, updated.length > 0, renamed.length > 0].some((v) => v);
5149
+ added = added.filter(
5150
+ (key) => !renamed.some(([oldKey, newKey]) => newKey === key)
5151
+ );
5152
+ removed = removed.filter(
5153
+ (key) => !renamed.some(([oldKey, newKey]) => oldKey === key)
5154
+ );
5155
+ const hasChanges = [
5156
+ added.length > 0,
5157
+ removed.length > 0,
5158
+ updated.length > 0,
5159
+ renamed.length > 0
5160
+ ].some((v) => v);
4885
5161
  return {
4886
5162
  added,
4887
5163
  removed,
@@ -4915,7 +5191,7 @@ function createDeltaProcessor(fileKey) {
4915
5191
  await this.saveLock(lockfileData);
4916
5192
  },
4917
5193
  async createChecksums(sourceData) {
4918
- const checksums = _28.mapValues(sourceData, (value) => md5(value));
5194
+ const checksums = _29.mapValues(sourceData, (value) => md5(value));
4919
5195
  return checksums;
4920
5196
  }
4921
5197
  };
@@ -5039,7 +5315,6 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5039
5315
  bucket.type,
5040
5316
  bucketPath.pathPattern,
5041
5317
  {
5042
- isCacheRestore: false,
5043
5318
  defaultLocale: sourceLocale,
5044
5319
  injectLocale: bucket.injectLocale
5045
5320
  },
@@ -5074,7 +5349,6 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5074
5349
  bucket.type,
5075
5350
  bucketPath.pathPattern,
5076
5351
  {
5077
- isCacheRestore: false,
5078
5352
  defaultLocale: sourceLocale,
5079
5353
  returnUnlocalizedKeys: true,
5080
5354
  injectLocale: bucket.injectLocale
@@ -5087,7 +5361,7 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5087
5361
  const deltaProcessor = createDeltaProcessor(bucketPath.pathPattern);
5088
5362
  const sourceChecksums = await deltaProcessor.createChecksums(sourceData);
5089
5363
  const savedChecksums = await deltaProcessor.loadChecksums();
5090
- const updatedSourceData = _29.pickBy(
5364
+ const updatedSourceData = _30.pickBy(
5091
5365
  sourceData,
5092
5366
  (value, key) => sourceChecksums[key] !== savedChecksums[key]
5093
5367
  );
@@ -5101,15 +5375,15 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5101
5375
  bucketPath.delimiter
5102
5376
  );
5103
5377
  const { unlocalizable: targetUnlocalizable, ...targetData } = await bucketLoader.pull(targetLocale);
5104
- const missingKeys = _29.difference(
5378
+ const missingKeys = _30.difference(
5105
5379
  Object.keys(sourceData),
5106
5380
  Object.keys(targetData)
5107
5381
  );
5108
- const extraKeys = _29.difference(
5382
+ const extraKeys = _30.difference(
5109
5383
  Object.keys(targetData),
5110
5384
  Object.keys(sourceData)
5111
5385
  );
5112
- const unlocalizableDataDiff = !_29.isEqual(
5386
+ const unlocalizableDataDiff = !_30.isEqual(
5113
5387
  sourceUnlocalizable,
5114
5388
  targetUnlocalizable
5115
5389
  );
@@ -5160,7 +5434,6 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5160
5434
  bucket.type,
5161
5435
  bucketPath.pathPattern,
5162
5436
  {
5163
- isCacheRestore: false,
5164
5437
  defaultLocale: sourceLocale,
5165
5438
  injectLocale: bucket.injectLocale
5166
5439
  },
@@ -5191,13 +5464,13 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5191
5464
  targetData,
5192
5465
  checksums: checksums2
5193
5466
  });
5194
- let processableData = _29.chain(sourceData).entries().filter(
5467
+ let processableData = _30.chain(sourceData).entries().filter(
5195
5468
  ([key, value]) => delta.added.includes(key) || delta.updated.includes(key) || !!flags.force
5196
5469
  ).fromPairs().value();
5197
5470
  if (flags.key) {
5198
- processableData = _29.pickBy(
5471
+ processableData = _30.pickBy(
5199
5472
  processableData,
5200
- (_33, key) => key === flags.key
5473
+ (_34, key) => key === flags.key
5201
5474
  );
5202
5475
  }
5203
5476
  if (flags.verbose) {
@@ -5230,13 +5503,13 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5230
5503
  if (flags.verbose) {
5231
5504
  bucketOra.info(JSON.stringify(processedTargetData, null, 2));
5232
5505
  }
5233
- let finalTargetData = _29.merge(
5506
+ let finalTargetData = _30.merge(
5234
5507
  {},
5235
5508
  sourceData,
5236
5509
  targetData,
5237
5510
  processedTargetData
5238
5511
  );
5239
- finalTargetData = _29.chain(finalTargetData).entries().map(([key, value]) => {
5512
+ finalTargetData = _30.chain(finalTargetData).entries().map(([key, value]) => {
5240
5513
  const renaming = delta.renamed.find(
5241
5514
  ([oldKey, newKey]) => oldKey === key
5242
5515
  );
@@ -5260,7 +5533,7 @@ var i18n_default = new Command12().command("i18n").description("Run Localization
5260
5533
  `Applying changes to ${bucketPath} (${targetLocale})`
5261
5534
  );
5262
5535
  }
5263
- const finalDiffSize = _29.chain(finalTargetData).omitBy((value, key) => value === targetData[key]).size().value();
5536
+ const finalDiffSize = _30.chain(finalTargetData).omitBy((value, key) => value === targetData[key]).size().value();
5264
5537
  await bucketLoader.push(targetLocale, finalTargetData);
5265
5538
  if (finalDiffSize > 0 || flags.force) {
5266
5539
  bucketOra.succeed(
@@ -5387,7 +5660,9 @@ async function reviewChanges(args) {
5387
5660
  if (currentStr === proposedStr && !args.force) {
5388
5661
  console.log(
5389
5662
  `
5390
- ${chalk6.blue(args.pathPattern)} (${chalk6.yellow(args.targetLocale)}): ${chalk6.gray("No changes to review")}`
5663
+ ${chalk6.blue(args.pathPattern)} (${chalk6.yellow(
5664
+ args.targetLocale
5665
+ )}): ${chalk6.gray("No changes to review")}`
5391
5666
  );
5392
5667
  return args.proposedData;
5393
5668
  }
@@ -5408,7 +5683,9 @@ ${chalk6.blue(args.pathPattern)} (${chalk6.yellow(args.targetLocale)}): ${chalk6
5408
5683
  }).join("\n");
5409
5684
  console.log(
5410
5685
  `
5411
- Reviewing changes for ${chalk6.blue(args.pathPattern)} (${chalk6.yellow(args.targetLocale)}):`
5686
+ Reviewing changes for ${chalk6.blue(args.pathPattern)} (${chalk6.yellow(
5687
+ args.targetLocale
5688
+ )}):`
5412
5689
  );
5413
5690
  console.log(coloredDiff);
5414
5691
  const { action } = await inquirer2.prompt([
@@ -5431,7 +5708,7 @@ Reviewing changes for ${chalk6.blue(args.pathPattern)} (${chalk6.yellow(args.tar
5431
5708
  return args.currentData;
5432
5709
  }
5433
5710
  const customData = { ...args.currentData };
5434
- const changes = _29.reduce(
5711
+ const changes = _30.reduce(
5435
5712
  args.proposedData,
5436
5713
  (result, value, key) => {
5437
5714
  if (args.currentData[key] !== value) {
@@ -5504,7 +5781,7 @@ import path14 from "path";
5504
5781
  import Z4 from "zod";
5505
5782
  import YAML5 from "yaml";
5506
5783
  import { MD5 as MD52 } from "object-hash";
5507
- import _30 from "lodash";
5784
+ import _31 from "lodash";
5508
5785
  function createLockfileHelper() {
5509
5786
  return {
5510
5787
  isLockfileExists: () => {
@@ -5514,23 +5791,33 @@ function createLockfileHelper() {
5514
5791
  registerSourceData: (pathPattern, sourceData) => {
5515
5792
  const lockfile = _loadLockfile();
5516
5793
  const sectionKey = MD52(pathPattern);
5517
- const sectionChecksums = _30.mapValues(sourceData, (value) => MD52(value));
5794
+ const sectionChecksums = _31.mapValues(sourceData, (value) => MD52(value));
5518
5795
  lockfile.checksums[sectionKey] = sectionChecksums;
5519
5796
  _saveLockfile(lockfile);
5520
5797
  },
5521
5798
  registerPartialSourceData: (pathPattern, partialSourceData) => {
5522
5799
  const lockfile = _loadLockfile();
5523
5800
  const sectionKey = MD52(pathPattern);
5524
- const sectionChecksums = _30.mapValues(partialSourceData, (value) => MD52(value));
5525
- lockfile.checksums[sectionKey] = _30.merge({}, lockfile.checksums[sectionKey] ?? {}, sectionChecksums);
5801
+ const sectionChecksums = _31.mapValues(
5802
+ partialSourceData,
5803
+ (value) => MD52(value)
5804
+ );
5805
+ lockfile.checksums[sectionKey] = _31.merge(
5806
+ {},
5807
+ lockfile.checksums[sectionKey] ?? {},
5808
+ sectionChecksums
5809
+ );
5526
5810
  _saveLockfile(lockfile);
5527
5811
  },
5528
5812
  extractUpdatedData: (pathPattern, sourceData) => {
5529
5813
  const lockfile = _loadLockfile();
5530
5814
  const sectionKey = MD52(pathPattern);
5531
- const currentChecksums = _30.mapValues(sourceData, (value) => MD52(value));
5815
+ const currentChecksums = _31.mapValues(sourceData, (value) => MD52(value));
5532
5816
  const savedChecksums = lockfile.checksums[sectionKey] || {};
5533
- const updatedData = _30.pickBy(sourceData, (value, key) => savedChecksums[key] !== currentChecksums[key]);
5817
+ const updatedData = _31.pickBy(
5818
+ sourceData,
5819
+ (value, key) => savedChecksums[key] !== currentChecksums[key]
5820
+ );
5534
5821
  return updatedData;
5535
5822
  }
5536
5823
  };
@@ -5574,20 +5861,31 @@ var lockfile_default = new Command13().command("lockfile").description("Create a
5574
5861
  const ora = Ora8();
5575
5862
  const lockfileHelper = createLockfileHelper();
5576
5863
  if (lockfileHelper.isLockfileExists() && !flags.force) {
5577
- ora.warn(`Lockfile won't be created because it already exists. Use --force to overwrite.`);
5864
+ ora.warn(
5865
+ `Lockfile won't be created because it already exists. Use --force to overwrite.`
5866
+ );
5578
5867
  } else {
5579
5868
  const i18nConfig = getConfig();
5580
5869
  const buckets = getBuckets(i18nConfig);
5581
5870
  for (const bucket of buckets) {
5582
5871
  for (const bucketConfig of bucket.paths) {
5583
- const sourceLocale = resolveOverriddenLocale4(i18nConfig.locale.source, bucketConfig.delimiter);
5584
- const bucketLoader = createBucketLoader(bucket.type, bucketConfig.pathPattern, {
5585
- isCacheRestore: false,
5586
- defaultLocale: sourceLocale
5587
- });
5872
+ const sourceLocale = resolveOverriddenLocale4(
5873
+ i18nConfig.locale.source,
5874
+ bucketConfig.delimiter
5875
+ );
5876
+ const bucketLoader = createBucketLoader(
5877
+ bucket.type,
5878
+ bucketConfig.pathPattern,
5879
+ {
5880
+ defaultLocale: sourceLocale
5881
+ }
5882
+ );
5588
5883
  bucketLoader.setDefaultLocale(sourceLocale);
5589
5884
  const sourceData = await bucketLoader.pull(sourceLocale);
5590
- lockfileHelper.registerSourceData(bucketConfig.pathPattern, sourceData);
5885
+ lockfileHelper.registerSourceData(
5886
+ bucketConfig.pathPattern,
5887
+ sourceData
5888
+ );
5591
5889
  }
5592
5890
  }
5593
5891
  ora.succeed("Lockfile created");
@@ -5600,9 +5898,11 @@ var flagsSchema = Z5.object({
5600
5898
  // src/cli/cmd/cleanup.ts
5601
5899
  import { resolveOverriddenLocale as resolveOverriddenLocale5 } from "@lingo.dev/_spec";
5602
5900
  import { Command as Command14 } from "interactive-commander";
5603
- import _31 from "lodash";
5901
+ import _32 from "lodash";
5604
5902
  import Ora9 from "ora";
5605
- var cleanup_default = new Command14().command("cleanup").description("Remove keys from target files that do not exist in the source file").helpOption("-h, --help", "Show help").option("--locale <locale>", "Specific locale to cleanup").option("--bucket <bucket>", "Specific bucket to cleanup").option("--dry-run", "Show what would be removed without making changes").option(
5903
+ var cleanup_default = new Command14().command("cleanup").description(
5904
+ "Remove keys from target files that do not exist in the source file"
5905
+ ).helpOption("-h, --help", "Show help").option("--locale <locale>", "Specific locale to cleanup").option("--bucket <bucket>", "Specific bucket to cleanup").option("--dry-run", "Show what would be removed without making changes").option(
5606
5906
  "--verbose",
5607
5907
  "Show detailed output including:\n - List of keys that would be removed.\n - Processing steps."
5608
5908
  ).action(async function(options) {
@@ -5615,44 +5915,69 @@ var cleanup_default = new Command14().command("cleanup").description("Remove key
5615
5915
  ora.succeed("Configuration loaded");
5616
5916
  let buckets = getBuckets(i18nConfig);
5617
5917
  if (options.bucket) {
5618
- buckets = buckets.filter((bucket) => bucket.type === options.bucket);
5918
+ buckets = buckets.filter(
5919
+ (bucket) => bucket.type === options.bucket
5920
+ );
5619
5921
  }
5620
5922
  const targetLocales = options.locale ? [options.locale] : i18nConfig.locale.targets;
5621
5923
  for (const bucket of buckets) {
5622
5924
  console.log();
5623
5925
  ora.info(`Processing bucket: ${bucket.type}`);
5624
5926
  for (const bucketConfig of bucket.paths) {
5625
- const sourceLocale = resolveOverriddenLocale5(i18nConfig.locale.source, bucketConfig.delimiter);
5626
- const bucketOra = Ora9({ indent: 2 }).info(`Processing path: ${bucketConfig.pathPattern}`);
5627
- const bucketLoader = createBucketLoader(bucket.type, bucketConfig.pathPattern, {
5628
- isCacheRestore: false,
5629
- defaultLocale: sourceLocale
5630
- });
5927
+ const sourceLocale = resolveOverriddenLocale5(
5928
+ i18nConfig.locale.source,
5929
+ bucketConfig.delimiter
5930
+ );
5931
+ const bucketOra = Ora9({ indent: 2 }).info(
5932
+ `Processing path: ${bucketConfig.pathPattern}`
5933
+ );
5934
+ const bucketLoader = createBucketLoader(
5935
+ bucket.type,
5936
+ bucketConfig.pathPattern,
5937
+ {
5938
+ defaultLocale: sourceLocale
5939
+ }
5940
+ );
5631
5941
  bucketLoader.setDefaultLocale(sourceLocale);
5632
5942
  const sourceData = await bucketLoader.pull(sourceLocale);
5633
5943
  const sourceKeys = Object.keys(sourceData);
5634
5944
  for (const _targetLocale of targetLocales) {
5635
- const targetLocale = resolveOverriddenLocale5(_targetLocale, bucketConfig.delimiter);
5945
+ const targetLocale = resolveOverriddenLocale5(
5946
+ _targetLocale,
5947
+ bucketConfig.delimiter
5948
+ );
5636
5949
  try {
5637
5950
  const targetData = await bucketLoader.pull(targetLocale);
5638
5951
  const targetKeys = Object.keys(targetData);
5639
- const keysToRemove = _31.difference(targetKeys, sourceKeys);
5952
+ const keysToRemove = _32.difference(targetKeys, sourceKeys);
5640
5953
  if (keysToRemove.length === 0) {
5641
5954
  bucketOra.succeed(`[${targetLocale}] No keys to remove`);
5642
5955
  continue;
5643
5956
  }
5644
5957
  if (options.verbose) {
5645
- bucketOra.info(`[${targetLocale}] Keys to remove: ${JSON.stringify(keysToRemove, null, 2)}`);
5958
+ bucketOra.info(
5959
+ `[${targetLocale}] Keys to remove: ${JSON.stringify(
5960
+ keysToRemove,
5961
+ null,
5962
+ 2
5963
+ )}`
5964
+ );
5646
5965
  }
5647
5966
  if (!options.dryRun) {
5648
- const cleanedData = _31.pick(targetData, sourceKeys);
5967
+ const cleanedData = _32.pick(targetData, sourceKeys);
5649
5968
  await bucketLoader.push(targetLocale, cleanedData);
5650
- bucketOra.succeed(`[${targetLocale}] Removed ${keysToRemove.length} keys`);
5969
+ bucketOra.succeed(
5970
+ `[${targetLocale}] Removed ${keysToRemove.length} keys`
5971
+ );
5651
5972
  } else {
5652
- bucketOra.succeed(`[${targetLocale}] Would remove ${keysToRemove.length} keys (dry run)`);
5973
+ bucketOra.succeed(
5974
+ `[${targetLocale}] Would remove ${keysToRemove.length} keys (dry run)`
5975
+ );
5653
5976
  }
5654
5977
  } catch (error) {
5655
- bucketOra.fail(`[${targetLocale}] Failed to cleanup: ${error.message}`);
5978
+ bucketOra.fail(
5979
+ `[${targetLocale}] Failed to cleanup: ${error.message}`
5980
+ );
5656
5981
  results.push({
5657
5982
  step: `Cleanup ${bucket.type}/${bucketConfig} for ${targetLocale}`,
5658
5983
  status: "Failed",
@@ -5700,7 +6025,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5700
6025
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5701
6026
  import Z6 from "zod";
5702
6027
  import { ReplexicaEngine } from "@lingo.dev/_sdk";
5703
- var mcp_default = new Command15().command("mcp").description("Use Lingo.dev model context provider with your AI agent").helpOption("-h, --help", "Show help").action(async (_33, program) => {
6028
+ var mcp_default = new Command15().command("mcp").description("Use Lingo.dev model context provider with your AI agent").helpOption("-h, --help", "Show help").action(async (_34, program) => {
5704
6029
  const apiKey = program.args[0];
5705
6030
  const settings = getSettings(apiKey);
5706
6031
  if (!settings.auth.apiKey) {
@@ -5802,7 +6127,9 @@ function createLingoDotDevLocalizer(explicitApiKey) {
5802
6127
  if (!auth) {
5803
6128
  throw new Error(
5804
6129
  dedent5`
5805
- You're trying to use ${chalk8.hex(colors.green)("Lingo.dev")} provider, however, you are not authenticated.
6130
+ You're trying to use ${chalk8.hex(colors.green)(
6131
+ "Lingo.dev"
6132
+ )} provider, however, you are not authenticated.
5806
6133
 
5807
6134
  To fix this issue:
5808
6135
  1. Run ${chalk8.dim("lingo.dev login")} to authenticate, or
@@ -5854,6 +6181,7 @@ import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
5854
6181
  import { createGoogleGenerativeAI as createGoogleGenerativeAI2 } from "@ai-sdk/google";
5855
6182
  import { createOpenAI as createOpenAI2 } from "@ai-sdk/openai";
5856
6183
  import { createOpenRouter as createOpenRouter2 } from "@openrouter/ai-sdk-provider";
6184
+ import { createMistral as createMistral2 } from "@ai-sdk/mistral";
5857
6185
  import chalk9 from "chalk";
5858
6186
  import dedent6 from "dedent";
5859
6187
  import { generateText as generateText2 } from "ai";
@@ -5868,7 +6196,11 @@ function createExplicitLocalizer(provider) {
5868
6196
 
5869
6197
  To fix this issue:
5870
6198
  1. Switch to one of the supported providers, or
5871
- 2. Remove the ${chalk9.italic("provider")} node from your i18n.json configuration to switch to ${chalk9.hex(colors.green)("Lingo.dev")}
6199
+ 2. Remove the ${chalk9.italic(
6200
+ "provider"
6201
+ )} node from your i18n.json configuration to switch to ${chalk9.hex(
6202
+ colors.green
6203
+ )("Lingo.dev")}
5872
6204
 
5873
6205
  ${chalk9.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
5874
6206
  `
@@ -5912,6 +6244,14 @@ function createExplicitLocalizer(provider) {
5912
6244
  prompt: provider.prompt,
5913
6245
  skipAuth: true
5914
6246
  });
6247
+ case "mistral":
6248
+ return createAiSdkLocalizer({
6249
+ factory: (params) => createMistral2(params).languageModel(provider.model),
6250
+ id: provider.id,
6251
+ prompt: provider.prompt,
6252
+ apiKeyName: "MISTRAL_API_KEY",
6253
+ baseUrl: provider.baseUrl
6254
+ });
5915
6255
  }
5916
6256
  }
5917
6257
  function createAiSdkLocalizer(params) {
@@ -5920,11 +6260,19 @@ function createAiSdkLocalizer(params) {
5920
6260
  if (!skipAuth && !apiKey || !params.apiKeyName) {
5921
6261
  throw new Error(
5922
6262
  dedent6`
5923
- You're trying to use raw ${chalk9.dim(params.id)} API for translation. ${params.apiKeyName ? `However, ${chalk9.dim(params.apiKeyName)} environment variable is not set.` : "However, that provider is unavailable."}
6263
+ You're trying to use raw ${chalk9.dim(params.id)} API for translation. ${params.apiKeyName ? `However, ${chalk9.dim(
6264
+ params.apiKeyName
6265
+ )} environment variable is not set.` : "However, that provider is unavailable."}
5924
6266
 
5925
6267
  To fix this issue:
5926
- 1. ${params.apiKeyName ? `Set ${chalk9.dim(params.apiKeyName)} in your environment variables` : "Set the environment variable for your provider (if required)"}, or
5927
- 2. Remove the ${chalk9.italic("provider")} node from your i18n.json configuration to switch to ${chalk9.hex(colors.green)("Lingo.dev")}
6268
+ 1. ${params.apiKeyName ? `Set ${chalk9.dim(
6269
+ params.apiKeyName
6270
+ )} in your environment variables` : "Set the environment variable for your provider (if required)"}, or
6271
+ 2. Remove the ${chalk9.italic(
6272
+ "provider"
6273
+ )} node from your i18n.json configuration to switch to ${chalk9.hex(
6274
+ colors.green
6275
+ )("Lingo.dev")}
5928
6276
 
5929
6277
  ${chalk9.hex(colors.blue)("Docs: https://lingo.dev/go/docs")}
5930
6278
  `
@@ -6064,10 +6412,14 @@ async function setup(input2) {
6064
6412
  const authStatus = await ctx.localizer.checkAuth();
6065
6413
  if (!authStatus.authenticated) {
6066
6414
  throw new Error(
6067
- `Failed to authenticate with ${chalk10.hex(colors.yellow)(ctx.localizer.id)} provider. Please check your API key and try again.`
6415
+ `Failed to authenticate with ${chalk10.hex(colors.yellow)(
6416
+ ctx.localizer.id
6417
+ )} provider. Please check your API key and try again.`
6068
6418
  );
6069
6419
  }
6070
- task.title = `Authenticated as ${chalk10.hex(colors.yellow)(authStatus.username)}`;
6420
+ task.title = `Authenticated as ${chalk10.hex(colors.yellow)(
6421
+ authStatus.username
6422
+ )}`;
6071
6423
  }
6072
6424
  },
6073
6425
  {
@@ -6128,14 +6480,22 @@ async function plan(input2) {
6128
6480
  title: "Locating content buckets",
6129
6481
  task: async (ctx, task) => {
6130
6482
  const bucketCount = buckets.length;
6131
- const bucketFilter = input2.flags.bucket ? ` ${chalk11.dim(`(filtered by: ${chalk11.hex(colors.yellow)(input2.flags.bucket.join(", "))})`)}` : "";
6132
- task.title = `Found ${chalk11.hex(colors.yellow)(bucketCount.toString())} bucket(s)${bucketFilter}`;
6483
+ const bucketFilter = input2.flags.bucket ? ` ${chalk11.dim(
6484
+ `(filtered by: ${chalk11.hex(colors.yellow)(
6485
+ input2.flags.bucket.join(", ")
6486
+ )})`
6487
+ )}` : "";
6488
+ task.title = `Found ${chalk11.hex(colors.yellow)(
6489
+ bucketCount.toString()
6490
+ )} bucket(s)${bucketFilter}`;
6133
6491
  }
6134
6492
  },
6135
6493
  {
6136
6494
  title: "Detecting locales",
6137
6495
  task: async (ctx, task) => {
6138
- task.title = `Found ${chalk11.hex(colors.yellow)(_targetLocales.length.toString())} target locale(s)`;
6496
+ task.title = `Found ${chalk11.hex(colors.yellow)(
6497
+ _targetLocales.length.toString()
6498
+ )} target locale(s)`;
6139
6499
  }
6140
6500
  },
6141
6501
  {
@@ -6154,8 +6514,14 @@ async function plan(input2) {
6154
6514
  patterns.push(bucketPath.pathPattern);
6155
6515
  }
6156
6516
  }
6157
- const fileFilter = input2.flags.file ? ` ${chalk11.dim(`(filtered by: ${chalk11.hex(colors.yellow)(input2.flags.file.join(", "))})`)}` : "";
6158
- task.title = `Found ${chalk11.hex(colors.yellow)(patterns.length.toString())} path pattern(s)${fileFilter}`;
6517
+ const fileFilter = input2.flags.file ? ` ${chalk11.dim(
6518
+ `(filtered by: ${chalk11.hex(colors.yellow)(
6519
+ input2.flags.file.join(", ")
6520
+ )})`
6521
+ )}` : "";
6522
+ task.title = `Found ${chalk11.hex(colors.yellow)(
6523
+ patterns.length.toString()
6524
+ )} path pattern(s)${fileFilter}`;
6159
6525
  }
6160
6526
  },
6161
6527
  {
@@ -6193,7 +6559,9 @@ async function plan(input2) {
6193
6559
  }
6194
6560
  }
6195
6561
  }
6196
- task.title = `Prepared ${chalk11.hex(colors.green)(ctx.tasks.length.toString())} translation task(s)`;
6562
+ task.title = `Prepared ${chalk11.hex(colors.green)(
6563
+ ctx.tasks.length.toString()
6564
+ )} translation task(s)`;
6197
6565
  }
6198
6566
  }
6199
6567
  ],
@@ -6207,7 +6575,7 @@ async function plan(input2) {
6207
6575
  import chalk12 from "chalk";
6208
6576
  import { Listr as Listr3 } from "listr2";
6209
6577
  import pLimit from "p-limit";
6210
- import _32 from "lodash";
6578
+ import _33 from "lodash";
6211
6579
 
6212
6580
  // ../../node_modules/.pnpm/@isaacs+balanced-match@4.0.1/node_modules/@isaacs/balanced-match/dist/esm/index.js
6213
6581
  var balanced = (a, b, str) => {
@@ -6898,7 +7266,7 @@ var AST = class _AST {
6898
7266
  if (!this.type) {
6899
7267
  const noEmpty = this.isStart() && this.isEnd();
6900
7268
  const src = this.#parts.map((p) => {
6901
- const [re, _33, hasMagic, uflag] = typeof p === "string" ? _AST.#parseGlob(p, this.#hasMagic, noEmpty) : p.toRegExpSource(allowDot);
7269
+ const [re, _34, hasMagic, uflag] = typeof p === "string" ? _AST.#parseGlob(p, this.#hasMagic, noEmpty) : p.toRegExpSource(allowDot);
6902
7270
  this.#hasMagic = this.#hasMagic || hasMagic;
6903
7271
  this.#uflag = this.#uflag || uflag;
6904
7272
  return re;
@@ -6971,7 +7339,7 @@ var AST = class _AST {
6971
7339
  if (typeof p === "string") {
6972
7340
  throw new Error("string type in extglob ast??");
6973
7341
  }
6974
- const [re, _33, _hasMagic, uflag] = p.toRegExpSource(dot);
7342
+ const [re, _34, _hasMagic, uflag] = p.toRegExpSource(dot);
6975
7343
  this.#uflag = this.#uflag || uflag;
6976
7344
  return re;
6977
7345
  }).filter((p) => !(this.isStart() && this.isEnd()) || !!p).join("|");
@@ -7216,7 +7584,7 @@ var Minimatch = class {
7216
7584
  }
7217
7585
  return false;
7218
7586
  }
7219
- debug(..._33) {
7587
+ debug(..._34) {
7220
7588
  }
7221
7589
  make() {
7222
7590
  const pattern = this.pattern;
@@ -7238,7 +7606,7 @@ var Minimatch = class {
7238
7606
  const rawGlobParts = this.globSet.map((s) => this.slashSplit(s));
7239
7607
  this.globParts = this.preprocess(rawGlobParts);
7240
7608
  this.debug(this.pattern, this.globParts);
7241
- let set = this.globParts.map((s, _33, __) => {
7609
+ let set = this.globParts.map((s, _34, __) => {
7242
7610
  if (this.isWindows && this.windowsNoMagicRoot) {
7243
7611
  const isUNC = s[0] === "" && s[1] === "" && (s[2] === "?" || !globMagic.test(s[2])) && !globMagic.test(s[3]);
7244
7612
  const isDrive = /^[a-z]:/i.test(s[0]);
@@ -7761,11 +8129,15 @@ async function execute(input2) {
7761
8129
  {
7762
8130
  title: "Initializing localization engine",
7763
8131
  task: async (ctx, task) => {
7764
- task.title = `Localization engine ${chalk12.hex(colors.green)("ready")} (${ctx.localizer.id})`;
8132
+ task.title = `Localization engine ${chalk12.hex(colors.green)(
8133
+ "ready"
8134
+ )} (${ctx.localizer.id})`;
7765
8135
  }
7766
8136
  },
7767
8137
  {
7768
- title: `Processing localization tasks ${chalk12.dim(`(tasks: ${input2.tasks.length}, concurrency: ${effectiveConcurrency})`)}`,
8138
+ title: `Processing localization tasks ${chalk12.dim(
8139
+ `(tasks: ${input2.tasks.length}, concurrency: ${effectiveConcurrency})`
8140
+ )}`,
7769
8141
  task: (ctx, task) => {
7770
8142
  if (input2.tasks.length < 1) {
7771
8143
  task.title = `Skipping, nothing to localize.`;
@@ -7778,7 +8150,7 @@ async function execute(input2) {
7778
8150
  const workerTasks = [];
7779
8151
  for (let i = 0; i < workersCount; i++) {
7780
8152
  const assignedTasks = ctx.tasks.filter(
7781
- (_33, idx) => idx % workersCount === i
8153
+ (_34, idx) => idx % workersCount === i
7782
8154
  );
7783
8155
  workerTasks.push(
7784
8156
  createWorkerTask({
@@ -7814,11 +8186,11 @@ function createWorkerStatusMessage(args) {
7814
8186
  "[locale]",
7815
8187
  args.assignedTask.targetLocale
7816
8188
  );
7817
- return `[${chalk12.hex(colors.yellow)(`${args.percentage}%`)}] Processing: ${chalk12.dim(
7818
- displayPath
7819
- )} (${chalk12.hex(colors.yellow)(args.assignedTask.sourceLocale)} -> ${chalk12.hex(
7820
- colors.yellow
7821
- )(args.assignedTask.targetLocale)})`;
8189
+ return `[${chalk12.hex(colors.yellow)(
8190
+ `${args.percentage}%`
8191
+ )}] Processing: ${chalk12.dim(displayPath)} (${chalk12.hex(colors.yellow)(
8192
+ args.assignedTask.sourceLocale
8193
+ )} -> ${chalk12.hex(colors.yellow)(args.assignedTask.targetLocale)})`;
7822
8194
  }
7823
8195
  function createExecutionProgressMessage(ctx) {
7824
8196
  const succeededTasksCount = countTasks(
@@ -7833,7 +8205,9 @@ function createExecutionProgressMessage(ctx) {
7833
8205
  ctx,
7834
8206
  (_t, result) => result.status === "skipped"
7835
8207
  );
7836
- return `Processed ${chalk12.green(succeededTasksCount)}/${ctx.tasks.length}, Failed ${chalk12.red(failedTasksCount)}, Skipped ${chalk12.dim(skippedTasksCount)}`;
8208
+ return `Processed ${chalk12.green(succeededTasksCount)}/${ctx.tasks.length}, Failed ${chalk12.red(failedTasksCount)}, Skipped ${chalk12.dim(
8209
+ skippedTasksCount
8210
+ )}`;
7837
8211
  }
7838
8212
  function createLoaderForTask(assignedTask) {
7839
8213
  const bucketLoader = createBucketLoader(
@@ -7841,7 +8215,6 @@ function createLoaderForTask(assignedTask) {
7841
8215
  assignedTask.bucketPathPattern,
7842
8216
  {
7843
8217
  defaultLocale: assignedTask.sourceLocale,
7844
- isCacheRestore: false,
7845
8218
  injectLocale: assignedTask.injectLocale
7846
8219
  },
7847
8220
  assignedTask.lockedKeys,
@@ -7877,7 +8250,7 @@ function createWorkerTask(args) {
7877
8250
  targetData,
7878
8251
  checksums
7879
8252
  });
7880
- const processableData = _32.chain(sourceData).entries().filter(
8253
+ const processableData = _33.chain(sourceData).entries().filter(
7881
8254
  ([key, value]) => delta.added.includes(key) || delta.updated.includes(key) || !!args.ctx.flags.force
7882
8255
  ).filter(
7883
8256
  ([key]) => !assignedTask.onlyKeys.length || assignedTask.onlyKeys?.some(
@@ -7895,33 +8268,47 @@ function createWorkerTask(args) {
7895
8268
  targetData,
7896
8269
  processableData
7897
8270
  },
7898
- (progress) => {
8271
+ async (progress, _sourceChunk, processedChunk) => {
8272
+ await args.ioLimiter(async () => {
8273
+ await bucketLoader.pull(assignedTask.sourceLocale);
8274
+ const latestTargetData = await bucketLoader.pull(
8275
+ assignedTask.targetLocale
8276
+ );
8277
+ const _partialData = _33.merge(
8278
+ {},
8279
+ latestTargetData,
8280
+ processedChunk
8281
+ );
8282
+ const finalChunkTargetData = processRenamedKeys(
8283
+ delta,
8284
+ _partialData
8285
+ );
8286
+ await bucketLoader.push(
8287
+ assignedTask.targetLocale,
8288
+ finalChunkTargetData
8289
+ );
8290
+ });
7899
8291
  subTask.title = createWorkerStatusMessage({
7900
8292
  assignedTask,
7901
8293
  percentage: progress
7902
8294
  });
7903
8295
  }
7904
8296
  );
7905
- let finalTargetData = _32.merge(
8297
+ const finalTargetData = _33.merge(
7906
8298
  {},
7907
8299
  sourceData,
7908
8300
  targetData,
7909
8301
  processedTargetData
7910
8302
  );
7911
- finalTargetData = _32.chain(finalTargetData).entries().map(([key, value]) => {
7912
- const renaming = delta.renamed.find(
7913
- ([oldKey]) => oldKey === key
7914
- );
7915
- if (!renaming) {
7916
- return [key, value];
7917
- }
7918
- return [renaming[1], value];
7919
- }).fromPairs().value();
8303
+ const finalRenamedTargetData = processRenamedKeys(
8304
+ delta,
8305
+ finalTargetData
8306
+ );
7920
8307
  await args.ioLimiter(async () => {
7921
8308
  await bucketLoader.pull(assignedTask.sourceLocale);
7922
8309
  await bucketLoader.push(
7923
8310
  assignedTask.targetLocale,
7924
- finalTargetData
8311
+ finalRenamedTargetData
7925
8312
  );
7926
8313
  const checksums2 = await deltaProcessor.createChecksums(sourceData);
7927
8314
  await deltaProcessor.saveChecksums(checksums2);
@@ -7945,6 +8332,144 @@ function countTasks(ctx, predicate) {
7945
8332
  ([task, result]) => predicate(task, result)
7946
8333
  ).length;
7947
8334
  }
8335
+ function processRenamedKeys(delta, targetData) {
8336
+ return _33.chain(targetData).entries().map(([key, value]) => {
8337
+ const renaming = delta.renamed.find(([oldKey]) => oldKey === key);
8338
+ if (!renaming) {
8339
+ return [key, value];
8340
+ }
8341
+ return [renaming[1], value];
8342
+ }).fromPairs().value();
8343
+ }
8344
+
8345
+ // src/cli/cmd/run/watch.ts
8346
+ import * as chokidar from "chokidar";
8347
+ import chalk13 from "chalk";
8348
+ async function watch2(ctx) {
8349
+ const debounceDelay = ctx.flags.debounce || 5e3;
8350
+ console.log(chalk13.hex(colors.orange)("[Watch Mode]"));
8351
+ console.log(
8352
+ `\u{1F440} Watching for changes... (Press ${chalk13.yellow("Ctrl+C")} to stop)`
8353
+ );
8354
+ console.log(chalk13.dim(` Debounce delay: ${debounceDelay}ms`));
8355
+ console.log("");
8356
+ const state = {
8357
+ isRunning: false,
8358
+ pendingChanges: /* @__PURE__ */ new Set()
8359
+ };
8360
+ const watchPatterns = await getWatchPatterns(ctx);
8361
+ if (watchPatterns.length === 0) {
8362
+ console.log(chalk13.yellow("\u26A0\uFE0F No source files found to watch"));
8363
+ return;
8364
+ }
8365
+ console.log(chalk13.dim(`Watching ${watchPatterns.length} file pattern(s):`));
8366
+ watchPatterns.forEach((pattern) => {
8367
+ console.log(chalk13.dim(` \u2022 ${pattern}`));
8368
+ });
8369
+ console.log("");
8370
+ const watcher = chokidar.watch(watchPatterns, {
8371
+ ignoreInitial: true,
8372
+ persistent: true,
8373
+ awaitWriteFinish: {
8374
+ stabilityThreshold: 500,
8375
+ pollInterval: 100
8376
+ }
8377
+ });
8378
+ watcher.on("change", (path17) => {
8379
+ handleFileChange(path17, state, ctx);
8380
+ });
8381
+ watcher.on("add", (path17) => {
8382
+ handleFileChange(path17, state, ctx);
8383
+ });
8384
+ watcher.on("unlink", (path17) => {
8385
+ handleFileChange(path17, state, ctx);
8386
+ });
8387
+ watcher.on("error", (error) => {
8388
+ console.error(
8389
+ chalk13.red(
8390
+ `Watch error: ${error instanceof Error ? error.message : String(error)}`
8391
+ )
8392
+ );
8393
+ });
8394
+ process.on("SIGINT", () => {
8395
+ console.log(chalk13.yellow("\n\n\u{1F6D1} Stopping watch mode..."));
8396
+ watcher.close();
8397
+ process.exit(0);
8398
+ });
8399
+ await new Promise(() => {
8400
+ });
8401
+ }
8402
+ async function getWatchPatterns(ctx) {
8403
+ if (!ctx.config) return [];
8404
+ const buckets = getBuckets(ctx.config);
8405
+ const patterns = [];
8406
+ for (const bucket of buckets) {
8407
+ if (ctx.flags.bucket && !ctx.flags.bucket.includes(bucket.type)) {
8408
+ continue;
8409
+ }
8410
+ for (const bucketPath of bucket.paths) {
8411
+ if (ctx.flags.file) {
8412
+ if (!ctx.flags.file.some((f) => bucketPath.pathPattern.includes(f))) {
8413
+ continue;
8414
+ }
8415
+ }
8416
+ const sourceLocale = ctx.flags.sourceLocale || ctx.config.locale.source;
8417
+ const sourcePattern = bucketPath.pathPattern.replace(
8418
+ "[locale]",
8419
+ sourceLocale
8420
+ );
8421
+ patterns.push(sourcePattern);
8422
+ }
8423
+ }
8424
+ return patterns;
8425
+ }
8426
+ function handleFileChange(filePath, state, ctx) {
8427
+ const debounceDelay = ctx.flags.debounce || 5e3;
8428
+ state.pendingChanges.add(filePath);
8429
+ console.log(chalk13.dim(`\u{1F4DD} File changed: ${filePath}`));
8430
+ if (state.debounceTimer) {
8431
+ clearTimeout(state.debounceTimer);
8432
+ }
8433
+ state.debounceTimer = setTimeout(async () => {
8434
+ if (state.isRunning) {
8435
+ console.log(
8436
+ chalk13.yellow("\u23F3 Translation already in progress, skipping...")
8437
+ );
8438
+ return;
8439
+ }
8440
+ await triggerRetranslation(state, ctx);
8441
+ }, debounceDelay);
8442
+ }
8443
+ async function triggerRetranslation(state, ctx) {
8444
+ if (state.isRunning) return;
8445
+ state.isRunning = true;
8446
+ try {
8447
+ const changedFiles = Array.from(state.pendingChanges);
8448
+ state.pendingChanges.clear();
8449
+ console.log(chalk13.hex(colors.green)("\n\u{1F504} Triggering retranslation..."));
8450
+ console.log(chalk13.dim(`Changed files: ${changedFiles.join(", ")}`));
8451
+ console.log("");
8452
+ const runCtx = {
8453
+ ...ctx,
8454
+ tasks: [],
8455
+ results: /* @__PURE__ */ new Map()
8456
+ };
8457
+ await plan(runCtx);
8458
+ if (runCtx.tasks.length === 0) {
8459
+ console.log(chalk13.dim("\u2728 No translation tasks needed"));
8460
+ } else {
8461
+ await execute(runCtx);
8462
+ await renderSummary(runCtx.results);
8463
+ }
8464
+ console.log(chalk13.hex(colors.green)("\u2705 Retranslation completed"));
8465
+ console.log(chalk13.dim("\u{1F440} Continuing to watch for changes...\n"));
8466
+ } catch (error) {
8467
+ console.error(chalk13.red(`\u274C Retranslation failed: ${error.message}`));
8468
+ console.log(chalk13.dim("\u{1F440} Continuing to watch for changes...\n"));
8469
+ } finally {
8470
+ state.isRunning = false;
8471
+ }
8472
+ }
7948
8473
 
7949
8474
  // src/cli/cmd/run/_types.ts
7950
8475
  import {
@@ -7964,7 +8489,10 @@ var flagsSchema2 = z2.object({
7964
8489
  concurrency: z2.number().positive().default(10),
7965
8490
  debug: z2.boolean().default(false),
7966
8491
  sourceLocale: z2.string().optional(),
7967
- targetLocale: z2.array(z2.string()).optional()
8492
+ targetLocale: z2.array(z2.string()).optional(),
8493
+ watch: z2.boolean().default(false),
8494
+ debounce: z2.number().positive().default(5e3)
8495
+ // 5 seconds default
7968
8496
  });
7969
8497
 
7970
8498
  // src/cli/cmd/run/_utils.ts
@@ -8015,6 +8543,13 @@ var run_default = new Command16().command("run").description("Run Lingo.dev loca
8015
8543
  "--concurrency <concurrency>",
8016
8544
  "Number of concurrent tasks to run",
8017
8545
  (val) => parseInt(val)
8546
+ ).option(
8547
+ "--watch",
8548
+ "Watch source files for changes and automatically retranslate"
8549
+ ).option(
8550
+ "--debounce <milliseconds>",
8551
+ "Debounce delay in milliseconds for watch mode (default: 5000ms)",
8552
+ (val) => parseInt(val)
8018
8553
  ).action(async (args) => {
8019
8554
  let authId = null;
8020
8555
  try {
@@ -8044,6 +8579,9 @@ var run_default = new Command16().command("run").description("Run Lingo.dev loca
8044
8579
  await renderSpacer();
8045
8580
  await renderSummary(ctx.results);
8046
8581
  await renderSpacer();
8582
+ if (ctx.flags.watch) {
8583
+ await watch2(ctx);
8584
+ }
8047
8585
  trackEvent(authId, "cmd.run.success", {
8048
8586
  config: ctx.config,
8049
8587
  flags: ctx.flags
@@ -8075,7 +8613,9 @@ var InBranchFlow = class extends IntegrationFlow {
8075
8613
  execSync(`git add .`, { stdio: "inherit" });
8076
8614
  execSync(`git status --porcelain`, { stdio: "inherit" });
8077
8615
  execSync(
8078
- `git commit -m ${escapeShellArg(this.platformKit.config.commitMessage)} --no-verify`,
8616
+ `git commit -m ${escapeShellArg(
8617
+ this.platformKit.config.commitMessage
8618
+ )} --no-verify`,
8079
8619
  {
8080
8620
  stdio: "inherit"
8081
8621
  }
@@ -8198,7 +8738,9 @@ var PullRequestFlow = class extends InBranchFlow {
8198
8738
  this.ora.start("Checking if PR already exists");
8199
8739
  const pullRequestNumber = await this.ensureFreshPr(this.i18nBranchName);
8200
8740
  this.ora.succeed(
8201
- `Pull request ready: ${this.platformKit.buildPullRequestUrl(pullRequestNumber)}`
8741
+ `Pull request ready: ${this.platformKit.buildPullRequestUrl(
8742
+ pullRequestNumber
8743
+ )}`
8202
8744
  );
8203
8745
  }
8204
8746
  calculatePrBranchName() {
@@ -8368,11 +8910,17 @@ var PlatformKit = class {
8368
8910
  get config() {
8369
8911
  const env = Z7.object({
8370
8912
  LINGODOTDEV_API_KEY: Z7.string(),
8371
- LINGODOTDEV_PULL_REQUEST: Z7.preprocess((val) => val === "true" || val === true, Z7.boolean()),
8913
+ LINGODOTDEV_PULL_REQUEST: Z7.preprocess(
8914
+ (val) => val === "true" || val === true,
8915
+ Z7.boolean()
8916
+ ),
8372
8917
  LINGODOTDEV_COMMIT_MESSAGE: Z7.string().optional(),
8373
8918
  LINGODOTDEV_PULL_REQUEST_TITLE: Z7.string().optional(),
8374
8919
  LINGODOTDEV_WORKING_DIRECTORY: Z7.string().optional(),
8375
- LINGODOTDEV_PROCESS_OWN_COMMITS: Z7.preprocess((val) => val === "true" || val === true, Z7.boolean()).optional()
8920
+ LINGODOTDEV_PROCESS_OWN_COMMITS: Z7.preprocess(
8921
+ (val) => val === "true" || val === true,
8922
+ Z7.boolean()
8923
+ ).optional()
8376
8924
  }).parse(process.env);
8377
8925
  return {
8378
8926
  replexicaApiKey: env.LINGODOTDEV_API_KEY,
@@ -8767,16 +9315,34 @@ function parseBooleanArg(val) {
8767
9315
  }
8768
9316
 
8769
9317
  // src/cli/cmd/status.ts
8770
- import { bucketTypeSchema as bucketTypeSchema4, localeCodeSchema as localeCodeSchema3, resolveOverriddenLocale as resolveOverriddenLocale7 } from "@lingo.dev/_spec";
9318
+ import {
9319
+ bucketTypeSchema as bucketTypeSchema4,
9320
+ localeCodeSchema as localeCodeSchema3,
9321
+ resolveOverriddenLocale as resolveOverriddenLocale7
9322
+ } from "@lingo.dev/_spec";
8771
9323
  import { Command as Command18 } from "interactive-commander";
8772
9324
  import Z11 from "zod";
8773
9325
  import Ora10 from "ora";
8774
- import chalk13 from "chalk";
9326
+ import chalk14 from "chalk";
8775
9327
  import Table from "cli-table3";
8776
- var status_default = new Command18().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(
9328
+ var status_default = new Command18().command("status").description("Show the status of the localization process").helpOption("-h, --help", "Show help").option(
9329
+ "--locale <locale>",
9330
+ "Locale to process",
9331
+ (val, prev) => prev ? [...prev, val] : [val]
9332
+ ).option(
9333
+ "--bucket <bucket>",
9334
+ "Bucket to process",
9335
+ (val, prev) => prev ? [...prev, val] : [val]
9336
+ ).option(
8777
9337
  "--file [files...]",
8778
9338
  "File to process. Process only a specific path, may contain asterisk * to match multiple files."
8779
- ).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) {
9339
+ ).option(
9340
+ "--force",
9341
+ "Ignore lockfile and process all keys, useful for estimating full re-translation"
9342
+ ).option("--verbose", "Show detailed output including key-level word counts").option(
9343
+ "--api-key <api-key>",
9344
+ "Explicitly set the API key to use, override the default API key from settings"
9345
+ ).action(async function(options) {
8780
9346
  const ora = Ora10();
8781
9347
  const flags = parseFlags2(options);
8782
9348
  let authId = null;
@@ -8808,16 +9374,22 @@ var status_default = new Command18().command("status").description("Show the sta
8808
9374
  });
8809
9375
  let buckets = getBuckets(i18nConfig);
8810
9376
  if (flags.bucket?.length) {
8811
- buckets = buckets.filter((bucket) => flags.bucket.includes(bucket.type));
9377
+ buckets = buckets.filter(
9378
+ (bucket) => flags.bucket.includes(bucket.type)
9379
+ );
8812
9380
  }
8813
9381
  ora.succeed("Buckets retrieved");
8814
9382
  if (flags.file?.length) {
8815
9383
  buckets = buckets.map((bucket) => {
8816
- const paths = bucket.paths.filter((path17) => flags.file.find((file) => path17.pathPattern?.match(file)));
9384
+ const paths = bucket.paths.filter(
9385
+ (path17) => flags.file.find((file) => path17.pathPattern?.match(file))
9386
+ );
8817
9387
  return { ...bucket, paths };
8818
9388
  }).filter((bucket) => bucket.paths.length > 0);
8819
9389
  if (buckets.length === 0) {
8820
- ora.fail("No buckets found. All buckets were filtered out by --file option.");
9390
+ ora.fail(
9391
+ "No buckets found. All buckets were filtered out by --file option."
9392
+ );
8821
9393
  process.exit(1);
8822
9394
  } else {
8823
9395
  ora.info(`\x1B[36mProcessing only filtered buckets:\x1B[0m`);
@@ -8850,13 +9422,17 @@ var status_default = new Command18().command("status").description("Show the sta
8850
9422
  console.log();
8851
9423
  ora.info(`Analyzing bucket: ${bucket.type}`);
8852
9424
  for (const bucketPath of bucket.paths) {
8853
- const bucketOra = Ora10({ indent: 2 }).info(`Analyzing path: ${bucketPath.pathPattern}`);
8854
- const sourceLocale = resolveOverriddenLocale7(i18nConfig.locale.source, bucketPath.delimiter);
9425
+ const bucketOra = Ora10({ indent: 2 }).info(
9426
+ `Analyzing path: ${bucketPath.pathPattern}`
9427
+ );
9428
+ const sourceLocale = resolveOverriddenLocale7(
9429
+ i18nConfig.locale.source,
9430
+ bucketPath.delimiter
9431
+ );
8855
9432
  const bucketLoader = createBucketLoader(
8856
9433
  bucket.type,
8857
9434
  bucketPath.pathPattern,
8858
9435
  {
8859
- isCacheRestore: false,
8860
9436
  defaultLocale: sourceLocale,
8861
9437
  injectLocale: bucket.injectLocale
8862
9438
  },
@@ -8895,8 +9471,13 @@ var status_default = new Command18().command("status").description("Show the sta
8895
9471
  }
8896
9472
  fileStats[filePath].wordCount = sourceWordCount;
8897
9473
  for (const _targetLocale of targetLocales) {
8898
- const targetLocale = resolveOverriddenLocale7(_targetLocale, bucketPath.delimiter);
8899
- bucketOra.start(`[${sourceLocale} -> ${targetLocale}] Analyzing translation status...`);
9474
+ const targetLocale = resolveOverriddenLocale7(
9475
+ _targetLocale,
9476
+ bucketPath.delimiter
9477
+ );
9478
+ bucketOra.start(
9479
+ `[${sourceLocale} -> ${targetLocale}] Analyzing translation status...`
9480
+ );
8900
9481
  let targetData = {};
8901
9482
  let fileExists = true;
8902
9483
  try {
@@ -8912,13 +9493,20 @@ var status_default = new Command18().command("status").description("Show the sta
8912
9493
  fileStats[filePath].languageStats[_targetLocale].words = sourceWordCount;
8913
9494
  languageStats[_targetLocale].missing += sourceKeys.length;
8914
9495
  languageStats[_targetLocale].words += sourceWordCount;
8915
- totalWordCount.set(_targetLocale, (totalWordCount.get(_targetLocale) || 0) + sourceWordCount);
9496
+ totalWordCount.set(
9497
+ _targetLocale,
9498
+ (totalWordCount.get(_targetLocale) || 0) + sourceWordCount
9499
+ );
8916
9500
  bucketOra.succeed(
8917
- `[${sourceLocale} -> ${targetLocale}] ${chalk13.red(`0% complete`)} (0/${sourceKeys.length} keys) - file not found`
9501
+ `[${sourceLocale} -> ${targetLocale}] ${chalk14.red(
9502
+ `0% complete`
9503
+ )} (0/${sourceKeys.length} keys) - file not found`
8918
9504
  );
8919
9505
  continue;
8920
9506
  }
8921
- const deltaProcessor = createDeltaProcessor(bucketPath.pathPattern);
9507
+ const deltaProcessor = createDeltaProcessor(
9508
+ bucketPath.pathPattern
9509
+ );
8922
9510
  const checksums = await deltaProcessor.loadChecksums();
8923
9511
  const delta = await deltaProcessor.calculateDelta({
8924
9512
  sourceData,
@@ -8927,7 +9515,9 @@ var status_default = new Command18().command("status").description("Show the sta
8927
9515
  });
8928
9516
  const missingKeys = delta.added;
8929
9517
  const updatedKeys = delta.updated;
8930
- const completeKeys = sourceKeys.filter((key) => !missingKeys.includes(key) && !updatedKeys.includes(key));
9518
+ const completeKeys = sourceKeys.filter(
9519
+ (key) => !missingKeys.includes(key) && !updatedKeys.includes(key)
9520
+ );
8931
9521
  let wordsToTranslate = 0;
8932
9522
  const keysToProcess = flags.force ? sourceKeys : [...missingKeys, ...updatedKeys];
8933
9523
  for (const key of keysToProcess) {
@@ -8945,25 +9535,39 @@ var status_default = new Command18().command("status").description("Show the sta
8945
9535
  languageStats[_targetLocale].updated += updatedKeys.length;
8946
9536
  languageStats[_targetLocale].complete += completeKeys.length;
8947
9537
  languageStats[_targetLocale].words += wordsToTranslate;
8948
- totalWordCount.set(_targetLocale, (totalWordCount.get(_targetLocale) || 0) + wordsToTranslate);
9538
+ totalWordCount.set(
9539
+ _targetLocale,
9540
+ (totalWordCount.get(_targetLocale) || 0) + wordsToTranslate
9541
+ );
8949
9542
  const totalKeysInFile = sourceKeys.length;
8950
9543
  const completionPercent = (completeKeys.length / totalKeysInFile * 100).toFixed(1);
8951
9544
  if (missingKeys.length === 0 && updatedKeys.length === 0) {
8952
9545
  bucketOra.succeed(
8953
- `[${sourceLocale} -> ${targetLocale}] ${chalk13.green(`100% complete`)} (${completeKeys.length}/${totalKeysInFile} keys)`
9546
+ `[${sourceLocale} -> ${targetLocale}] ${chalk14.green(
9547
+ `100% complete`
9548
+ )} (${completeKeys.length}/${totalKeysInFile} keys)`
8954
9549
  );
8955
9550
  } else {
8956
- const message = `[${sourceLocale} -> ${targetLocale}] ${parseFloat(completionPercent) > 50 ? chalk13.yellow(`${completionPercent}% complete`) : chalk13.red(`${completionPercent}% complete`)} (${completeKeys.length}/${totalKeysInFile} keys)`;
9551
+ const message = `[${sourceLocale} -> ${targetLocale}] ${parseFloat(completionPercent) > 50 ? chalk14.yellow(`${completionPercent}% complete`) : chalk14.red(`${completionPercent}% complete`)} (${completeKeys.length}/${totalKeysInFile} keys)`;
8957
9552
  bucketOra.succeed(message);
8958
9553
  if (flags.verbose) {
8959
9554
  if (missingKeys.length > 0) {
8960
- console.log(` ${chalk13.red(`Missing:`)} ${missingKeys.length} keys, ~${wordsToTranslate} words`);
8961
9555
  console.log(
8962
- ` ${chalk13.dim(`Example missing: ${missingKeys.slice(0, 2).join(", ")}${missingKeys.length > 2 ? "..." : ""}`)}`
9556
+ ` ${chalk14.red(`Missing:`)} ${missingKeys.length} keys, ~${wordsToTranslate} words`
9557
+ );
9558
+ console.log(
9559
+ ` ${chalk14.red(`Missing:`)} ${missingKeys.length} keys, ~${wordsToTranslate} words`
9560
+ );
9561
+ console.log(
9562
+ ` ${chalk14.dim(
9563
+ `Example missing: ${missingKeys.slice(0, 2).join(", ")}${missingKeys.length > 2 ? "..." : ""}`
9564
+ )}`
8963
9565
  );
8964
9566
  }
8965
9567
  if (updatedKeys.length > 0) {
8966
- console.log(` ${chalk13.yellow(`Updated:`)} ${updatedKeys.length} keys that changed in source`);
9568
+ console.log(
9569
+ ` ${chalk14.yellow(`Updated:`)} ${updatedKeys.length} keys that changed in source`
9570
+ );
8967
9571
  }
8968
9572
  }
8969
9573
  }
@@ -8973,24 +9577,41 @@ var status_default = new Command18().command("status").description("Show the sta
8973
9577
  ora.fail(`Failed to analyze bucket ${bucket.type}: ${error.message}`);
8974
9578
  }
8975
9579
  }
8976
- const totalKeysNeedingTranslation = Object.values(languageStats).reduce((sum, stats) => {
8977
- return sum + stats.missing + stats.updated;
8978
- }, 0);
9580
+ const totalKeysNeedingTranslation = Object.values(languageStats).reduce(
9581
+ (sum, stats) => {
9582
+ return sum + stats.missing + stats.updated;
9583
+ },
9584
+ 0
9585
+ );
8979
9586
  const totalCompletedKeys = totalSourceKeyCount - totalKeysNeedingTranslation / targetLocales.length;
8980
9587
  console.log();
8981
- ora.succeed(chalk13.green(`Localization status completed.`));
8982
- console.log(chalk13.bold.cyan(`
9588
+ ora.succeed(chalk14.green(`Localization status completed.`));
9589
+ console.log(chalk14.bold.cyan(`
8983
9590
  \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`));
8984
- console.log(chalk13.bold.cyan(`\u2551 LOCALIZATION STATUS REPORT \u2551`));
8985
- console.log(chalk13.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`));
8986
- console.log(chalk13.bold(`
9591
+ console.log(chalk14.bold.cyan(`\u2551 LOCALIZATION STATUS REPORT \u2551`));
9592
+ 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`));
9593
+ console.log(chalk14.bold(`
8987
9594
  \u{1F4DD} SOURCE CONTENT:`));
8988
- console.log(`\u2022 Source language: ${chalk13.green(i18nConfig.locale.source)}`);
8989
- console.log(`\u2022 Source keys: ${chalk13.yellow(totalSourceKeyCount.toString())} keys across all files`);
8990
- console.log(chalk13.bold(`
9595
+ console.log(
9596
+ `\u2022 Source language: ${chalk14.green(i18nConfig.locale.source)}`
9597
+ );
9598
+ console.log(
9599
+ `\u2022 Source keys: ${chalk14.yellow(
9600
+ totalSourceKeyCount.toString()
9601
+ )} keys across all files`
9602
+ );
9603
+ console.log(chalk14.bold(`
8991
9604
  \u{1F310} LANGUAGE BY LANGUAGE BREAKDOWN:`));
8992
9605
  const table = new Table({
8993
- head: ["Language", "Status", "Complete", "Missing", "Updated", "Total Keys", "Words to Translate"],
9606
+ head: [
9607
+ "Language",
9608
+ "Status",
9609
+ "Complete",
9610
+ "Missing",
9611
+ "Updated",
9612
+ "Total Keys",
9613
+ "Words to Translate"
9614
+ ],
8994
9615
  style: {
8995
9616
  head: ["white"],
8996
9617
  // White color for headers
@@ -9009,19 +9630,19 @@ var status_default = new Command18().command("status").description("Show the sta
9009
9630
  let statusColor;
9010
9631
  if (stats.missing === totalSourceKeyCount) {
9011
9632
  statusText = "\u{1F534} Not started";
9012
- statusColor = chalk13.red;
9633
+ statusColor = chalk14.red;
9013
9634
  } else if (stats.missing === 0 && stats.updated === 0) {
9014
9635
  statusText = "\u2705 Complete";
9015
- statusColor = chalk13.green;
9636
+ statusColor = chalk14.green;
9016
9637
  } else if (parseFloat(percentComplete) > 80) {
9017
9638
  statusText = "\u{1F7E1} Almost done";
9018
- statusColor = chalk13.yellow;
9639
+ statusColor = chalk14.yellow;
9019
9640
  } else if (parseFloat(percentComplete) > 0) {
9020
9641
  statusText = "\u{1F7E0} In progress";
9021
- statusColor = chalk13.yellow;
9642
+ statusColor = chalk14.yellow;
9022
9643
  } else {
9023
9644
  statusText = "\u{1F534} Not started";
9024
- statusColor = chalk13.red;
9645
+ statusColor = chalk14.red;
9025
9646
  }
9026
9647
  const words = totalWordCount.get(locale) || 0;
9027
9648
  totalWordsToTranslate += words;
@@ -9029,35 +9650,43 @@ var status_default = new Command18().command("status").description("Show the sta
9029
9650
  locale,
9030
9651
  statusColor(statusText),
9031
9652
  `${stats.complete}/${totalSourceKeyCount} (${percentComplete}%)`,
9032
- stats.missing > 0 ? chalk13.red(stats.missing.toString()) : "0",
9033
- stats.updated > 0 ? chalk13.yellow(stats.updated.toString()) : "0",
9034
- totalNeeded > 0 ? chalk13.magenta(totalNeeded.toString()) : "0",
9653
+ stats.missing > 0 ? chalk14.red(stats.missing.toString()) : "0",
9654
+ stats.updated > 0 ? chalk14.yellow(stats.updated.toString()) : "0",
9655
+ totalNeeded > 0 ? chalk14.magenta(totalNeeded.toString()) : "0",
9035
9656
  words > 0 ? `~${words.toLocaleString()}` : "0"
9036
9657
  ]);
9037
9658
  }
9038
9659
  console.log(table.toString());
9039
- console.log(chalk13.bold(`
9660
+ console.log(chalk14.bold(`
9040
9661
  \u{1F4CA} USAGE ESTIMATE:`));
9041
9662
  console.log(
9042
- `\u2022 WORDS TO BE CONSUMED: ~${chalk13.yellow.bold(totalWordsToTranslate.toLocaleString())} words across all languages`
9663
+ `\u2022 WORDS TO BE CONSUMED: ~${chalk14.yellow.bold(
9664
+ totalWordsToTranslate.toLocaleString()
9665
+ )} words across all languages`
9666
+ );
9667
+ console.log(
9668
+ ` (Words are counted from source language for keys that need translation in target languages)`
9043
9669
  );
9044
- console.log(` (Words are counted from source language for keys that need translation in target languages)`);
9045
9670
  if (targetLocales.length > 1) {
9046
9671
  console.log(`\u2022 Per-language breakdown:`);
9047
9672
  for (const locale of targetLocales) {
9048
9673
  const words = totalWordCount.get(locale) || 0;
9049
9674
  const percent = (words / totalWordsToTranslate * 100).toFixed(1);
9050
- console.log(` - ${locale}: ~${words.toLocaleString()} words (${percent}% of total)`);
9675
+ console.log(
9676
+ ` - ${locale}: ~${words.toLocaleString()} words (${percent}% of total)`
9677
+ );
9051
9678
  }
9052
9679
  }
9053
9680
  if (flags.confirm && Object.keys(fileStats).length > 0) {
9054
- console.log(chalk13.bold(`
9681
+ console.log(chalk14.bold(`
9055
9682
  \u{1F4D1} BREAKDOWN BY FILE:`));
9056
9683
  Object.entries(fileStats).sort((a, b) => b[1].wordCount - a[1].wordCount).forEach(([path17, stats]) => {
9057
9684
  if (stats.sourceKeys === 0) return;
9058
- console.log(chalk13.bold(`
9685
+ console.log(chalk14.bold(`
9059
9686
  \u2022 ${path17}:`));
9060
- console.log(` ${stats.sourceKeys} source keys, ~${stats.wordCount.toLocaleString()} source words`);
9687
+ console.log(
9688
+ ` ${stats.sourceKeys} source keys, ~${stats.wordCount.toLocaleString()} source words`
9689
+ );
9061
9690
  const fileTable = new Table({
9062
9691
  head: ["Language", "Status", "Details"],
9063
9692
  style: {
@@ -9073,19 +9702,21 @@ var status_default = new Command18().command("status").description("Show the sta
9073
9702
  const total = stats.sourceKeys;
9074
9703
  const completion = (complete / total * 100).toFixed(1);
9075
9704
  let status = "\u2705 Complete";
9076
- let statusColor = chalk13.green;
9705
+ let statusColor = chalk14.green;
9077
9706
  if (langStats.missing === total) {
9078
9707
  status = "\u274C Not started";
9079
- statusColor = chalk13.red;
9708
+ statusColor = chalk14.red;
9080
9709
  } else if (langStats.missing > 0 || langStats.updated > 0) {
9081
9710
  status = `\u26A0\uFE0F ${completion}% complete`;
9082
- statusColor = chalk13.yellow;
9711
+ statusColor = chalk14.yellow;
9083
9712
  }
9084
9713
  let details = "";
9085
9714
  if (langStats.missing > 0 || langStats.updated > 0) {
9086
9715
  const parts = [];
9087
- if (langStats.missing > 0) parts.push(`${langStats.missing} missing`);
9088
- if (langStats.updated > 0) parts.push(`${langStats.updated} changed`);
9716
+ if (langStats.missing > 0)
9717
+ parts.push(`${langStats.missing} missing`);
9718
+ if (langStats.updated > 0)
9719
+ parts.push(`${langStats.updated} changed`);
9089
9720
  details = `${parts.join(", ")}, ~${langStats.words} words`;
9090
9721
  } else {
9091
9722
  details = "All keys translated";
@@ -9098,22 +9729,26 @@ var status_default = new Command18().command("status").description("Show the sta
9098
9729
  const completeLanguages = targetLocales.filter(
9099
9730
  (locale) => languageStats[locale].missing === 0 && languageStats[locale].updated === 0
9100
9731
  );
9101
- const missingLanguages = targetLocales.filter((locale) => languageStats[locale].complete === 0);
9102
- console.log(chalk13.bold.green(`
9732
+ const missingLanguages = targetLocales.filter(
9733
+ (locale) => languageStats[locale].complete === 0
9734
+ );
9735
+ console.log(chalk14.bold.green(`
9103
9736
  \u{1F4A1} OPTIMIZATION TIPS:`));
9104
9737
  if (missingLanguages.length > 0) {
9105
9738
  console.log(
9106
- `\u2022 ${chalk13.yellow(missingLanguages.join(", "))} ${missingLanguages.length === 1 ? "has" : "have"} no translations yet`
9739
+ `\u2022 ${chalk14.yellow(missingLanguages.join(", "))} ${missingLanguages.length === 1 ? "has" : "have"} no translations yet`
9107
9740
  );
9108
9741
  }
9109
9742
  if (completeLanguages.length > 0) {
9110
9743
  console.log(
9111
- `\u2022 ${chalk13.green(completeLanguages.join(", "))} ${completeLanguages.length === 1 ? "is" : "are"} completely translated`
9744
+ `\u2022 ${chalk14.green(completeLanguages.join(", "))} ${completeLanguages.length === 1 ? "is" : "are"} completely translated`
9112
9745
  );
9113
9746
  }
9114
9747
  if (targetLocales.length > 1) {
9115
9748
  console.log(`\u2022 Translating one language at a time reduces complexity`);
9116
- console.log(`\u2022 Try 'lingo.dev@latest i18n --locale ${targetLocales[0]}' to process just one language`);
9749
+ console.log(
9750
+ `\u2022 Try 'lingo.dev@latest i18n --locale ${targetLocales[0]}' to process just one language`
9751
+ );
9117
9752
  }
9118
9753
  trackEvent(authId || "status", "cmd.status.success", {
9119
9754
  i18nConfig,
@@ -9175,7 +9810,9 @@ function validateParams2(i18nConfig, flags) {
9175
9810
  message: `One or more specified locales do not exist in i18n.json locale.targets. Please add them to the list and try again.`,
9176
9811
  docUrl: "localeTargetNotFound"
9177
9812
  });
9178
- } else if (flags.bucket?.some((bucket) => !i18nConfig.buckets[bucket])) {
9813
+ } else if (flags.bucket?.some(
9814
+ (bucket) => !i18nConfig.buckets[bucket]
9815
+ )) {
9179
9816
  throw new CLIError({
9180
9817
  message: `One or more specified buckets do not exist in i18n.json. Please add them to the list and try again.`,
9181
9818
  docUrl: "bucketNotFound"
@@ -9187,7 +9824,7 @@ function validateParams2(i18nConfig, flags) {
9187
9824
  import { Command as Command19 } from "interactive-commander";
9188
9825
  import * as cp from "node:child_process";
9189
9826
  import figlet2 from "figlet";
9190
- import chalk14 from "chalk";
9827
+ import chalk15 from "chalk";
9191
9828
  import { vice as vice2 } from "gradient-string";
9192
9829
  var colors2 = {
9193
9830
  orange: "#ff6600",
@@ -9201,7 +9838,7 @@ var may_the_fourth_default = new Command19().command("may-the-fourth").descripti
9201
9838
  await renderClear2();
9202
9839
  await renderBanner2();
9203
9840
  await renderSpacer2();
9204
- console.log(chalk14.hex(colors2.yellow)("Loading the Star Wars movie..."));
9841
+ console.log(chalk15.hex(colors2.yellow)("Loading the Star Wars movie..."));
9205
9842
  await renderSpacer2();
9206
9843
  await new Promise((resolve, reject) => {
9207
9844
  const ssh = cp.spawn("ssh", ["starwarstel.net"], {
@@ -9220,10 +9857,12 @@ var may_the_fourth_default = new Command19().command("may-the-fourth").descripti
9220
9857
  });
9221
9858
  await renderSpacer2();
9222
9859
  console.log(
9223
- `${chalk14.hex(colors2.green)("We hope you enjoyed it! :)")} ${chalk14.hex(colors2.blue)("May the Fourth be with you! \u{1F680}")}`
9860
+ `${chalk15.hex(colors2.green)("We hope you enjoyed it! :)")} ${chalk15.hex(
9861
+ colors2.blue
9862
+ )("May the Fourth be with you! \u{1F680}")}`
9224
9863
  );
9225
9864
  await renderSpacer2();
9226
- console.log(chalk14.dim(`---`));
9865
+ console.log(chalk15.dim(`---`));
9227
9866
  await renderSpacer2();
9228
9867
  await renderHero2();
9229
9868
  });
@@ -9246,19 +9885,21 @@ async function renderBanner2() {
9246
9885
  }
9247
9886
  async function renderHero2() {
9248
9887
  console.log(
9249
- `\u26A1\uFE0F ${chalk14.hex(colors2.green)("Lingo.dev")} - open-source, AI-powered i18n CLI for web & mobile localization.`
9888
+ `\u26A1\uFE0F ${chalk15.hex(colors2.green)(
9889
+ "Lingo.dev"
9890
+ )} - open-source, AI-powered i18n CLI for web & mobile localization.`
9250
9891
  );
9251
9892
  console.log(" ");
9252
9893
  console.log(
9253
- chalk14.hex(colors2.blue)("\u2B50 GitHub Repo: https://lingo.dev/go/gh")
9894
+ chalk15.hex(colors2.blue)("\u2B50 GitHub Repo: https://lingo.dev/go/gh")
9254
9895
  );
9255
- console.log(chalk14.hex(colors2.blue)("\u{1F4AC} 24/7 Support: hi@lingo.dev"));
9896
+ console.log(chalk15.hex(colors2.blue)("\u{1F4AC} 24/7 Support: hi@lingo.dev"));
9256
9897
  }
9257
9898
 
9258
9899
  // package.json
9259
9900
  var package_default = {
9260
9901
  name: "lingo.dev",
9261
- version: "0.100.1",
9902
+ version: "0.102.0",
9262
9903
  description: "Lingo.dev CLI",
9263
9904
  private: false,
9264
9905
  publishConfig: {
@@ -9372,6 +10013,7 @@ var package_default = {
9372
10013
  dependencies: {
9373
10014
  "@ai-sdk/anthropic": "^1.2.11",
9374
10015
  "@ai-sdk/google": "^1.2.19",
10016
+ "@ai-sdk/mistral": "^1.2.8",
9375
10017
  "@ai-sdk/openai": "^1.3.22",
9376
10018
  "@babel/generator": "^7.27.1",
9377
10019
  "@babel/parser": "^7.27.1",
@@ -9392,6 +10034,7 @@ var package_default = {
9392
10034
  ai: "^4.3.15",
9393
10035
  bitbucket: "^2.12.0",
9394
10036
  chalk: "^5.4.1",
10037
+ chokidar: "^4.0.3",
9395
10038
  "cli-progress": "^3.12.0",
9396
10039
  "cli-table3": "^0.6.5",
9397
10040
  cors: "^2.8.5",
@@ -9460,6 +10103,7 @@ var package_default = {
9460
10103
  },
9461
10104
  devDependencies: {
9462
10105
  "@types/babel__generator": "^7.27.0",
10106
+ "@types/chokidar": "^2.1.7",
9463
10107
  "@types/cli-progress": "^3.11.6",
9464
10108
  "@types/cors": "^2.8.17",
9465
10109
  "@types/diff": "^7.0.0",
@@ -9488,6 +10132,167 @@ var package_default = {
9488
10132
  packageManager: "pnpm@9.12.3"
9489
10133
  };
9490
10134
 
10135
+ // src/cli/cmd/purge.ts
10136
+ import { Command as Command20 } from "interactive-commander";
10137
+ import Ora11 from "ora";
10138
+ import { resolveOverriddenLocale as resolveOverriddenLocale8 } from "@lingo.dev/_spec";
10139
+ import { confirm as confirm3 } from "@inquirer/prompts";
10140
+ var purge_default = new Command20().command("purge").description(
10141
+ "Remove translations for given --bucket, --file, --key, --locale"
10142
+ ).helpOption("-h, --help", "Show help").option(
10143
+ "--bucket <bucket>",
10144
+ "Bucket to process",
10145
+ (val, prev) => prev ? [...prev, val] : [val]
10146
+ ).option(
10147
+ "--file [files...]",
10148
+ "File(s) to process. Only process files that match the given glob pattern(s)."
10149
+ ).option(
10150
+ "--key <key>",
10151
+ "Key to remove. Remove all translation keys matching the given glob pattern."
10152
+ ).option(
10153
+ "--locale <locale>",
10154
+ "Locale to process",
10155
+ (val, prev) => prev ? [...prev, val] : [val]
10156
+ ).option(
10157
+ "--yes-really",
10158
+ "Skip interactive confirmation and delete without asking."
10159
+ ).action(async function(options) {
10160
+ const ora = Ora11();
10161
+ try {
10162
+ ora.start("Loading configuration...");
10163
+ const i18nConfig = getConfig();
10164
+ if (!i18nConfig) {
10165
+ throw new Error("i18n.json not found. Please run `lingo.dev init`.");
10166
+ }
10167
+ ora.succeed("Configuration loaded");
10168
+ let buckets = getBuckets(i18nConfig);
10169
+ if (options.bucket && options.bucket.length) {
10170
+ buckets = buckets.filter(
10171
+ (bucket) => options.bucket.includes(bucket.type)
10172
+ );
10173
+ }
10174
+ if (options.file && options.file.length) {
10175
+ buckets = buckets.map((bucket) => {
10176
+ const paths = bucket.paths.filter(
10177
+ (bucketPath) => options.file?.some((f) => bucketPath.pathPattern.includes(f))
10178
+ );
10179
+ return { ...bucket, paths };
10180
+ }).filter((bucket) => bucket.paths.length > 0);
10181
+ if (buckets.length === 0) {
10182
+ ora.fail("All files were filtered out by --file option.");
10183
+ process.exit(1);
10184
+ }
10185
+ }
10186
+ const sourceLocale = i18nConfig.locale.source;
10187
+ const targetLocales = options.locale && options.locale.length ? options.locale : i18nConfig.locale.targets;
10188
+ let removedAny = false;
10189
+ for (const bucket of buckets) {
10190
+ console.log();
10191
+ ora.info(`Processing bucket: ${bucket.type}`);
10192
+ for (const bucketPath of bucket.paths) {
10193
+ for (const _targetLocale of targetLocales) {
10194
+ const targetLocale = resolveOverriddenLocale8(
10195
+ _targetLocale,
10196
+ bucketPath.delimiter
10197
+ );
10198
+ const bucketOra = Ora11({ indent: 2 }).start(
10199
+ `Processing path: ${bucketPath.pathPattern} [${targetLocale}]`
10200
+ );
10201
+ try {
10202
+ const bucketLoader = createBucketLoader(
10203
+ bucket.type,
10204
+ bucketPath.pathPattern,
10205
+ {
10206
+ defaultLocale: sourceLocale,
10207
+ injectLocale: bucket.injectLocale
10208
+ },
10209
+ bucket.lockedKeys,
10210
+ bucket.lockedPatterns,
10211
+ bucket.ignoredKeys
10212
+ );
10213
+ await bucketLoader.init();
10214
+ bucketLoader.setDefaultLocale(sourceLocale);
10215
+ await bucketLoader.pull(sourceLocale);
10216
+ let targetData = await bucketLoader.pull(targetLocale);
10217
+ if (!targetData || Object.keys(targetData).length === 0) {
10218
+ bucketOra.info(
10219
+ `No translations found for ${bucketPath.pathPattern} [${targetLocale}]`
10220
+ );
10221
+ continue;
10222
+ }
10223
+ let newData = { ...targetData };
10224
+ let keysToRemove = [];
10225
+ if (options.key) {
10226
+ keysToRemove = Object.keys(newData).filter(
10227
+ (k) => minimatch(k, options.key)
10228
+ );
10229
+ } else {
10230
+ keysToRemove = Object.keys(newData);
10231
+ }
10232
+ if (keysToRemove.length > 0) {
10233
+ if (options.key) {
10234
+ bucketOra.info(
10235
+ `About to delete ${keysToRemove.length} key(s) matching '${options.key}' from ${bucketPath.pathPattern} [${targetLocale}]:
10236
+ ${keysToRemove.slice(0, 10).join(", ")}${keysToRemove.length > 10 ? ", ..." : ""}`
10237
+ );
10238
+ } else {
10239
+ bucketOra.info(
10240
+ `About to delete all (${keysToRemove.length}) keys from ${bucketPath.pathPattern} [${targetLocale}]`
10241
+ );
10242
+ }
10243
+ if (!options.yesReally) {
10244
+ bucketOra.warn(
10245
+ "This is a destructive operation. If you are sure, type 'y' to continue. (Use --yes-really to skip this check.)"
10246
+ );
10247
+ const confirmed = await confirm3({
10248
+ message: `Delete these keys from ${bucketPath.pathPattern} [${targetLocale}]?`,
10249
+ default: false
10250
+ });
10251
+ if (!confirmed) {
10252
+ bucketOra.info("Skipped by user.");
10253
+ continue;
10254
+ }
10255
+ }
10256
+ for (const key of keysToRemove) {
10257
+ delete newData[key];
10258
+ }
10259
+ removedAny = true;
10260
+ await bucketLoader.push(targetLocale, newData);
10261
+ if (options.key) {
10262
+ bucketOra.succeed(
10263
+ `Removed ${keysToRemove.length} key(s) matching '${options.key}' from ${bucketPath.pathPattern} [${targetLocale}]`
10264
+ );
10265
+ } else {
10266
+ bucketOra.succeed(
10267
+ `Removed all keys (${keysToRemove.length}) from ${bucketPath.pathPattern} [${targetLocale}]`
10268
+ );
10269
+ }
10270
+ } else if (options.key) {
10271
+ bucketOra.info(
10272
+ `No keys matching '${options.key}' found in ${bucketPath.pathPattern} [${targetLocale}]`
10273
+ );
10274
+ } else {
10275
+ bucketOra.info("No keys to remove.");
10276
+ }
10277
+ } catch (error) {
10278
+ const err = error;
10279
+ bucketOra.fail(`Failed: ${err.message}`);
10280
+ }
10281
+ }
10282
+ }
10283
+ }
10284
+ if (!removedAny) {
10285
+ ora.info("No keys were removed.");
10286
+ } else {
10287
+ ora.succeed("Purge completed.");
10288
+ }
10289
+ } catch (error) {
10290
+ const err = error;
10291
+ ora.fail(err.message);
10292
+ process.exit(1);
10293
+ }
10294
+ });
10295
+
9491
10296
  // src/cli/index.ts
9492
10297
  dotenv.config();
9493
10298
  var cli_default = new InteractiveCommand2().name("lingo.dev").description("Lingo.dev CLI").helpOption("-h, --help", "Show help").addHelpText(
@@ -9505,7 +10310,7 @@ ${vice3(
9505
10310
 
9506
10311
  Star the the repo :) https://github.com/LingoDotDev/lingo.dev
9507
10312
  `
9508
- ).version(`v${package_default.version}`, "-v, --version", "Show version").addCommand(init_default).interactive("-y, --no-interactive", "Disable interactive mode").addCommand(i18n_default).addCommand(auth_default).addCommand(login_default).addCommand(logout_default).addCommand(show_default).addCommand(config_default2).addCommand(lockfile_default).addCommand(cleanup_default).addCommand(mcp_default).addCommand(ci_default).addCommand(status_default).addCommand(may_the_fourth_default, { hidden: true }).addCommand(run_default, { hidden: true }).exitOverride((err) => {
10313
+ ).version(`v${package_default.version}`, "-v, --version", "Show version").addCommand(init_default).interactive("-y, --no-interactive", "Disable interactive mode").addCommand(i18n_default).addCommand(auth_default).addCommand(login_default).addCommand(logout_default).addCommand(show_default).addCommand(config_default2).addCommand(lockfile_default).addCommand(cleanup_default).addCommand(mcp_default).addCommand(ci_default).addCommand(status_default).addCommand(may_the_fourth_default, { hidden: true }).addCommand(run_default).addCommand(purge_default).exitOverride((err) => {
9509
10314
  if (err.code === "commander.helpDisplayed" || err.code === "commander.version" || err.code === "commander.help") {
9510
10315
  process.exit(0);
9511
10316
  }