@intlpullhq/cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +48 -0
  2. package/dist/index.js +801 -925
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -918,8 +918,8 @@ import { useState as useState6, useEffect as useEffect5 } from "react";
918
918
  import { Box as Box9, Text as Text10, useApp as useApp2 } from "ink";
919
919
  import SelectInput2 from "ink-select-input";
920
920
  import Spinner3 from "ink-spinner";
921
- import { writeFileSync, mkdirSync, existsSync } from "fs";
922
- import { join, dirname } from "path";
921
+ import { writeFileSync, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
922
+ import { join as join2, dirname as dirname2 } from "path";
923
923
 
924
924
  // src/commands/pull/api.ts
925
925
  async function fetchProjects2(apiUrl, apiKey) {
@@ -1080,16 +1080,23 @@ async function fetchTranslationsParallel(projectId, apiUrl, apiKey, options) {
1080
1080
  })
1081
1081
  );
1082
1082
  const bundle = {};
1083
+ const namespacedBundle = {};
1083
1084
  for (const result of results) {
1084
1085
  if (!bundle[result.language]) {
1085
1086
  bundle[result.language] = {};
1086
1087
  }
1087
1088
  Object.assign(bundle[result.language], result.translations);
1089
+ if (!namespacedBundle[result.language]) {
1090
+ namespacedBundle[result.language] = {};
1091
+ }
1092
+ namespacedBundle[result.language][result.namespace] = result.translations;
1088
1093
  }
1089
1094
  return {
1090
1095
  bundle,
1096
+ namespacedBundle,
1091
1097
  version: projectData.current_version || "1.0.0",
1092
- namespaceCount: namespaces.length
1098
+ namespaceCount: namespaces.length,
1099
+ namespaces
1093
1100
  };
1094
1101
  }
1095
1102
  async function fetchProjectInfo(projectId, apiUrl, apiKey) {
@@ -1190,6 +1197,51 @@ function MultiSelect({ items, selected, onToggle, onSubmit, isActive = true }) {
1190
1197
  ] });
1191
1198
  }
1192
1199
 
1200
+ // src/lib/output-resolver.ts
1201
+ import { join, dirname, extname } from "path";
1202
+ import { mkdirSync, existsSync } from "fs";
1203
+ function hasNamespacePlaceholder(pattern) {
1204
+ return pattern.includes("[namespace]") || pattern.includes("{namespace}");
1205
+ }
1206
+ function getFormatExtension(format) {
1207
+ switch (format) {
1208
+ case "yaml":
1209
+ case "yml":
1210
+ return ".yaml";
1211
+ case "ts":
1212
+ case "typescript":
1213
+ return ".ts";
1214
+ case "json":
1215
+ default:
1216
+ return ".json";
1217
+ }
1218
+ }
1219
+ function resolveOutputPath(pattern, options) {
1220
+ let resolved = pattern;
1221
+ resolved = resolved.replace(/\[locale\]/g, options.locale);
1222
+ resolved = resolved.replace(/\{locale\}/g, options.locale);
1223
+ if (options.namespace) {
1224
+ resolved = resolved.replace(/\[namespace\]/g, options.namespace);
1225
+ resolved = resolved.replace(/\{namespace\}/g, options.namespace);
1226
+ }
1227
+ if (options.format) {
1228
+ const currentExt = extname(resolved);
1229
+ const targetExt = getFormatExtension(options.format);
1230
+ if (currentExt && currentExt !== targetExt) {
1231
+ resolved = resolved.slice(0, -currentExt.length) + targetExt;
1232
+ }
1233
+ }
1234
+ return {
1235
+ path: resolved,
1236
+ dir: dirname(resolved)
1237
+ };
1238
+ }
1239
+ function ensureDir(dir) {
1240
+ if (!existsSync(dir)) {
1241
+ mkdirSync(dir, { recursive: true });
1242
+ }
1243
+ }
1244
+
1193
1245
  // src/commands/pull/components/interactive-pull.tsx
1194
1246
  import { Fragment, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1195
1247
  function InteractivePull({ initialOptions }) {
@@ -1240,43 +1292,87 @@ function InteractivePull({ initialOptions }) {
1240
1292
  const apiUrl = globalConfig.apiUrl || "https://api.intlpull.com";
1241
1293
  const apiKey = resolved?.key;
1242
1294
  setPullState((s) => ({ ...s, message: "Fetching translations..." }));
1243
- const { bundle, version } = await fetchTranslations(
1295
+ const result = await fetchTranslationsParallel(
1244
1296
  projectId,
1245
1297
  apiUrl,
1246
1298
  apiKey,
1247
1299
  {
1248
1300
  languages: state.selectedLanguages.length > 0 ? state.selectedLanguages : void 0,
1249
- platform: state.selectedPlatform
1301
+ platform: state.selectedPlatform,
1302
+ branch: state.selectedBranch,
1303
+ onProgress: (completed, total) => {
1304
+ setPullState((s) => ({
1305
+ ...s,
1306
+ message: `Fetching translations (${completed}/${total})...`
1307
+ }));
1308
+ }
1250
1309
  }
1251
1310
  );
1311
+ const { bundle, namespacedBundle, version, namespaces } = result;
1252
1312
  const locales = state.selectedLanguages.length > 0 ? state.selectedLanguages : Object.keys(bundle);
1253
1313
  if (locales.length === 0) {
1254
1314
  throw new Error("No translations found for this project.");
1255
1315
  }
1256
- const outputDir = initialOptions.output || projectConfig?.outputDir || "./messages";
1316
+ const outputPattern = initialOptions.output || projectConfig?.output || (projectConfig?.outputDir ? `${projectConfig.outputDir}/[locale].json` : "./messages/[locale].json");
1257
1317
  const format = state.selectedFormat;
1258
1318
  const ext = getFileExtension(format);
1259
- setPullState((s) => ({
1260
- ...s,
1261
- message: `Writing ${locales.length} translation files...`
1262
- }));
1319
+ const useNamespaces = hasNamespacePlaceholder(outputPattern) && namespacedBundle && namespaces.length > 0;
1320
+ if (useNamespaces) {
1321
+ setPullState((s) => ({
1322
+ ...s,
1323
+ message: `Writing ${locales.length} \xD7 ${namespaces.length} translation files...`
1324
+ }));
1325
+ } else {
1326
+ setPullState((s) => ({
1327
+ ...s,
1328
+ message: `Writing ${locales.length} translation files...`
1329
+ }));
1330
+ }
1263
1331
  const writtenFiles = [];
1264
1332
  let totalKeyCount = 0;
1265
- for (const locale of locales) {
1266
- if (!bundle[locale]) continue;
1267
- const outputPath = join(outputDir, `${locale}${ext}`);
1268
- const dir = dirname(outputPath);
1269
- if (!existsSync(dir)) {
1270
- mkdirSync(dir, { recursive: true });
1333
+ if (useNamespaces && namespacedBundle) {
1334
+ for (const locale of locales) {
1335
+ if (!namespacedBundle[locale]) continue;
1336
+ for (const namespace of namespaces) {
1337
+ const translations = namespacedBundle[locale][namespace];
1338
+ if (!translations || Object.keys(translations).length === 0) continue;
1339
+ const { path: outputPath, dir } = resolveOutputPath(outputPattern, {
1340
+ locale,
1341
+ namespace,
1342
+ format
1343
+ });
1344
+ ensureDir(dir);
1345
+ const content = formatTranslations(translations, format, locale);
1346
+ writeFileSync(outputPath, content);
1347
+ writtenFiles.push(outputPath);
1348
+ totalKeyCount += Object.keys(translations).length;
1349
+ }
1350
+ }
1351
+ } else {
1352
+ for (const locale of locales) {
1353
+ if (!bundle[locale]) continue;
1354
+ let outputPath;
1355
+ if (outputPattern.includes("[locale]") || outputPattern.includes("{locale}")) {
1356
+ const resolved2 = resolveOutputPath(outputPattern, { locale, format });
1357
+ outputPath = resolved2.path;
1358
+ ensureDir(resolved2.dir);
1359
+ } else {
1360
+ outputPath = join2(outputPattern, `${locale}${ext}`);
1361
+ const dir = dirname2(outputPath);
1362
+ if (!existsSync2(dir)) {
1363
+ mkdirSync2(dir, { recursive: true });
1364
+ }
1365
+ }
1366
+ const content = formatTranslations(bundle[locale], format, locale);
1367
+ writeFileSync(outputPath, content);
1368
+ writtenFiles.push(outputPath);
1369
+ totalKeyCount += Object.keys(bundle[locale]).length;
1271
1370
  }
1272
- const content = formatTranslations(bundle[locale], format, locale);
1273
- writeFileSync(outputPath, content);
1274
- writtenFiles.push(outputPath);
1275
- totalKeyCount += Object.keys(bundle[locale]).length;
1276
1371
  }
1372
+ const successMessage = useNamespaces ? `Successfully pulled ${locales.length} languages \xD7 ${namespaces.length} namespaces` : "Successfully pulled translations";
1277
1373
  setPullState({
1278
1374
  status: "success",
1279
- message: "Successfully pulled translations",
1375
+ message: successMessage,
1280
1376
  files: writtenFiles,
1281
1377
  details: {
1282
1378
  version,
@@ -1545,8 +1641,8 @@ function InteractivePull({ initialOptions }) {
1545
1641
  import { useState as useState7, useEffect as useEffect6 } from "react";
1546
1642
  import { Box as Box10, Text as Text11 } from "ink";
1547
1643
  import Spinner4 from "ink-spinner";
1548
- import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
1549
- import { join as join2, dirname as dirname2 } from "path";
1644
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
1645
+ import { join as join3, dirname as dirname3 } from "path";
1550
1646
 
1551
1647
  // src/lib/frameworks.ts
1552
1648
  var LIBRARIES2 = {
@@ -1559,10 +1655,13 @@ var LIBRARIES2 = {
1559
1655
  translationCall: (key, params) => params ? `t('${key}', ${params})` : `t('${key}')`,
1560
1656
  pluralSupport: true,
1561
1657
  defaultOutputDir: "./messages",
1562
- filePattern: "{locale}.json",
1658
+ // next-intl uses flat files by default, but supports namespaced via getMessage()
1659
+ // Users can configure messages/[locale]/[namespace].json if they prefer namespacing
1660
+ filePattern: "[locale].json",
1563
1661
  setupInstructions: [
1564
1662
  "npm install next-intl",
1565
- "Create messages/{locale}.json files",
1663
+ "Create messages/[locale].json files",
1664
+ "Configure src/i18n/request.ts to load from messages/",
1566
1665
  "Add NextIntlClientProvider to layout",
1567
1666
  "Configure next.config.js with createNextIntlPlugin()"
1568
1667
  ]
@@ -1576,10 +1675,13 @@ var LIBRARIES2 = {
1576
1675
  translationCall: (key, params) => params ? `t('${key}', ${params})` : `t('${key}')`,
1577
1676
  pluralSupport: true,
1578
1677
  defaultOutputDir: "./public/locales",
1579
- filePattern: "{locale}/{namespace}.json",
1678
+ // react-i18next with i18next-http-backend expects this structure
1679
+ // IMPORTANT: Must match backend.loadPath in i18n.js config
1680
+ filePattern: "[locale]/[namespace].json",
1580
1681
  setupInstructions: [
1581
- "npm install react-i18next i18next",
1582
- "Create public/locales/{locale}/{namespace}.json files",
1682
+ "npm install react-i18next i18next i18next-http-backend",
1683
+ "Create public/locales/[locale]/[namespace].json files",
1684
+ 'Configure i18n.js with backend.loadPath: "/locales/{{lng}}/{{ns}}.json"',
1583
1685
  "Initialize i18next with i18n.init()",
1584
1686
  "Wrap app with I18nextProvider"
1585
1687
  ]
@@ -1593,11 +1695,11 @@ var LIBRARIES2 = {
1593
1695
  translationCall: (key, params) => params ? `i18next.t('${key}', ${params})` : `i18next.t('${key}')`,
1594
1696
  pluralSupport: true,
1595
1697
  defaultOutputDir: "./locales",
1596
- filePattern: "{locale}/{namespace}.json",
1698
+ filePattern: "[locale]/[namespace].json",
1597
1699
  setupInstructions: [
1598
1700
  "npm install i18next",
1599
- "Create locales/{locale}/{namespace}.json files",
1600
- "Initialize i18next with i18n.init()"
1701
+ "Create locales/[locale]/[namespace].json files",
1702
+ "Initialize i18next with resources or backend loader"
1601
1703
  ]
1602
1704
  },
1603
1705
  "react-intl": {
@@ -1609,10 +1711,11 @@ var LIBRARIES2 = {
1609
1711
  translationCall: (key, params) => params ? `intl.formatMessage({ id: '${key}' }, ${params})` : `intl.formatMessage({ id: '${key}' })`,
1610
1712
  pluralSupport: true,
1611
1713
  defaultOutputDir: "./src/lang",
1612
- filePattern: "{locale}.json",
1714
+ // react-intl typically uses flat files (messages extracted by ID)
1715
+ filePattern: "[locale].json",
1613
1716
  setupInstructions: [
1614
1717
  "npm install react-intl",
1615
- "Create src/lang/{locale}.json files",
1718
+ "Create src/lang/[locale].json files",
1616
1719
  "Wrap app with IntlProvider",
1617
1720
  "Import and pass messages to IntlProvider"
1618
1721
  ]
@@ -1626,10 +1729,11 @@ var LIBRARIES2 = {
1626
1729
  translationCall: (key, params) => params ? `t('${key}', ${params})` : `t('${key}')`,
1627
1730
  pluralSupport: true,
1628
1731
  defaultOutputDir: "./src/locales",
1629
- filePattern: "{locale}.json",
1732
+ // vue-i18n supports both flat and namespaced, defaulting to flat
1733
+ filePattern: "[locale].json",
1630
1734
  setupInstructions: [
1631
1735
  "npm install vue-i18n",
1632
- "Create src/locales/{locale}.json files",
1736
+ "Create src/locales/[locale].json files",
1633
1737
  "Create and configure i18n instance",
1634
1738
  "Use app.use(i18n) in main.js"
1635
1739
  ]
@@ -1643,10 +1747,10 @@ var LIBRARIES2 = {
1643
1747
  translationCall: (key, params) => params ? `$_('${key}', ${params})` : `$_('${key}')`,
1644
1748
  pluralSupport: true,
1645
1749
  defaultOutputDir: "./src/lib/i18n",
1646
- filePattern: "{locale}.json",
1750
+ filePattern: "[locale].json",
1647
1751
  setupInstructions: [
1648
1752
  "npm install svelte-i18n",
1649
- "Create src/lib/i18n/{locale}.json files",
1753
+ "Create src/lib/i18n/[locale].json files",
1650
1754
  "Initialize with init() and register()",
1651
1755
  "Use $_ or $t stores in components"
1652
1756
  ]
@@ -1660,10 +1764,10 @@ var LIBRARIES2 = {
1660
1764
  translationCall: (key, params) => params ? `t('${key}', ${params})` : `t('${key}')`,
1661
1765
  pluralSupport: true,
1662
1766
  defaultOutputDir: "./public/locales",
1663
- filePattern: "{locale}/{namespace}.json",
1767
+ filePattern: "[locale]/[namespace].json",
1664
1768
  setupInstructions: [
1665
1769
  "npx astro add astro-i18next",
1666
- "Create public/locales/{locale}/{namespace}.json files",
1770
+ "Create public/locales/[locale]/[namespace].json files",
1667
1771
  "Configure astro-i18next in astro.config.mjs"
1668
1772
  ]
1669
1773
  }
@@ -1742,8 +1846,10 @@ Or use a project-scoped API key for automatic selection.`
1742
1846
  const branchDisplay = branch && branch !== "main" ? ` from branch '${branch}'` : "";
1743
1847
  setState((s) => ({ ...s, status: "downloading", message: `Downloading ${targetLanguages.length} languages${branchDisplay}...` }));
1744
1848
  let bundle;
1849
+ let namespacedBundle;
1745
1850
  let version;
1746
1851
  let namespaceCount = 0;
1852
+ let namespaces = [];
1747
1853
  if (useParallel) {
1748
1854
  const result = await fetchTranslationsParallel(
1749
1855
  projectId,
@@ -1762,8 +1868,10 @@ Or use a project-scoped API key for automatic selection.`
1762
1868
  }
1763
1869
  );
1764
1870
  bundle = result.bundle;
1871
+ namespacedBundle = result.namespacedBundle;
1765
1872
  version = result.version;
1766
1873
  namespaceCount = result.namespaceCount;
1874
+ namespaces = result.namespaces;
1767
1875
  const hasContent = Object.values(bundle).some(
1768
1876
  (langBundle) => Object.keys(langBundle).length > 0
1769
1877
  );
@@ -1776,6 +1884,8 @@ Or use a project-scoped API key for automatic selection.`
1776
1884
  });
1777
1885
  bundle = exportResult.bundle;
1778
1886
  version = exportResult.version;
1887
+ namespacedBundle = void 0;
1888
+ namespaces = [];
1779
1889
  }
1780
1890
  } else {
1781
1891
  const result = await fetchTranslations(projectId, apiUrl, apiKey, {
@@ -1791,23 +1901,46 @@ Or use a project-scoped API key for automatic selection.`
1791
1901
  const writtenFiles = [];
1792
1902
  let totalKeyCount = 0;
1793
1903
  const filePattern = libraryConfig?.filePattern || "{locale}.json";
1794
- const useNamespaceFolders = filePattern.includes("{namespace}");
1795
- for (const locale of targetLanguages) {
1796
- if (!bundle[locale]) continue;
1797
- let outputPath;
1798
- if (useNamespaceFolders) {
1799
- outputPath = join2(outputDir, locale, `common${ext}`);
1800
- } else {
1801
- outputPath = join2(outputDir, `${locale}${ext}`);
1904
+ const useNamespaceFiles = hasNamespacePlaceholder(filePattern) && namespacedBundle && namespaces.length > 0;
1905
+ const outputPattern = filePattern.startsWith("./") ? filePattern : join3(outputDir, filePattern);
1906
+ if (useNamespaceFiles && namespacedBundle) {
1907
+ for (const locale of targetLanguages) {
1908
+ if (!namespacedBundle[locale]) continue;
1909
+ for (const namespace of namespaces) {
1910
+ const translations = namespacedBundle[locale][namespace];
1911
+ if (!translations || Object.keys(translations).length === 0) continue;
1912
+ const { path: outputPath, dir } = resolveOutputPath(outputPattern, {
1913
+ locale,
1914
+ namespace,
1915
+ format
1916
+ });
1917
+ ensureDir(dir);
1918
+ const content = formatTranslations(translations, format, locale);
1919
+ writeFileSync2(outputPath, content);
1920
+ writtenFiles.push(outputPath);
1921
+ totalKeyCount += Object.keys(translations).length;
1922
+ }
1802
1923
  }
1803
- const dir = dirname2(outputPath);
1804
- if (!existsSync2(dir)) {
1805
- mkdirSync2(dir, { recursive: true });
1924
+ } else {
1925
+ for (const locale of targetLanguages) {
1926
+ if (!bundle[locale]) continue;
1927
+ let outputPath;
1928
+ if (filePattern.includes("{locale}") || filePattern.includes("[locale]")) {
1929
+ const { path: path4, dir } = resolveOutputPath(outputPattern, { locale, format });
1930
+ outputPath = path4;
1931
+ ensureDir(dir);
1932
+ } else {
1933
+ outputPath = join3(outputDir, `${locale}${ext}`);
1934
+ const dir = dirname3(outputPath);
1935
+ if (!existsSync3(dir)) {
1936
+ mkdirSync3(dir, { recursive: true });
1937
+ }
1938
+ }
1939
+ const content = formatTranslations(bundle[locale], format, locale);
1940
+ writeFileSync2(outputPath, content);
1941
+ writtenFiles.push(outputPath);
1942
+ totalKeyCount += Object.keys(bundle[locale]).length;
1806
1943
  }
1807
- const content = formatTranslations(bundle[locale], format, locale);
1808
- writeFileSync2(outputPath, content);
1809
- writtenFiles.push(outputPath);
1810
- totalKeyCount += Object.keys(bundle[locale]).length;
1811
1944
  }
1812
1945
  if (!projectConfig && projectId) {
1813
1946
  const newConfig = {
@@ -1816,13 +1949,14 @@ Or use a project-scoped API key for automatic selection.`
1816
1949
  library: detected.library,
1817
1950
  outputDir,
1818
1951
  sourceLanguage: projectInfo.languages[0] || "en",
1819
- namespaces: ["common"]
1952
+ namespaces: namespaces.length > 0 ? namespaces : ["common"]
1820
1953
  };
1821
1954
  saveProjectConfig(newConfig);
1822
1955
  }
1956
+ const successMessage = useNamespaceFiles ? `Downloaded ${targetLanguages.length} languages \xD7 ${namespaceCount} namespaces` : useParallel ? `Downloaded ${targetLanguages.length} languages (${namespaceCount} namespaces merged)` : `Downloaded ${targetLanguages.length} languages`;
1823
1957
  setState({
1824
1958
  status: "success",
1825
- message: useParallel ? `Downloaded ${targetLanguages.length} languages (${namespaceCount} namespaces)` : `Downloaded ${targetLanguages.length} languages`,
1959
+ message: successMessage,
1826
1960
  project: projectInfo,
1827
1961
  framework: detected,
1828
1962
  outputDir,
@@ -1925,8 +2059,8 @@ function runPull(options) {
1925
2059
  import React8 from "react";
1926
2060
  import { render as render5, Box as Box11, Text as Text12, useInput as useInput3 } from "ink";
1927
2061
  import Spinner5 from "ink-spinner";
1928
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1929
- import { join as join4, basename as basename2, extname as extname2 } from "path";
2062
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
2063
+ import { join as join5, basename as basename2, extname as extname3 } from "path";
1930
2064
 
1931
2065
  // src/lib/languages.ts
1932
2066
  var LANGUAGES = [
@@ -2097,8 +2231,8 @@ function formatLanguageDisplay(code, includeFlag = true) {
2097
2231
  }
2098
2232
 
2099
2233
  // src/lib/discovery.ts
2100
- import { existsSync as existsSync3, readdirSync, statSync, readFileSync } from "fs";
2101
- import { join as join3, basename, extname, dirname as dirname3, relative } from "path";
2234
+ import { existsSync as existsSync4, readdirSync, statSync, readFileSync } from "fs";
2235
+ import { join as join4, basename, extname as extname2, dirname as dirname4, relative } from "path";
2102
2236
  import yaml from "js-yaml";
2103
2237
  var COMMON_PATHS = [
2104
2238
  // next-intl
@@ -2133,7 +2267,7 @@ function isLikelyLanguageCode(str) {
2133
2267
  return false;
2134
2268
  }
2135
2269
  function isNamespaceFile(filename) {
2136
- const name = basename(filename, extname(filename));
2270
+ const name = basename(filename, extname2(filename));
2137
2271
  const commonNamespaces = [
2138
2272
  "common",
2139
2273
  "translation",
@@ -2183,7 +2317,7 @@ function countKeys(obj) {
2183
2317
  return count;
2184
2318
  }
2185
2319
  function parseTranslationFile(filePath) {
2186
- const ext = extname(filePath).toLowerCase();
2320
+ const ext = extname2(filePath).toLowerCase();
2187
2321
  try {
2188
2322
  if (ext === ".json") {
2189
2323
  const content = JSON.parse(readFileSync(filePath, "utf-8"));
@@ -2206,7 +2340,7 @@ function parseTranslationFile(filePath) {
2206
2340
  }
2207
2341
  function detectLanguageFromPath(filePath) {
2208
2342
  const parts = filePath.split(/[\/\\]/);
2209
- const fileName = basename(filePath, extname(filePath));
2343
+ const fileName = basename(filePath, extname2(filePath));
2210
2344
  if (isLikelyLanguageCode(fileName)) {
2211
2345
  return normalizeLanguageCode(fileName);
2212
2346
  }
@@ -2220,11 +2354,11 @@ function detectLanguageFromPath(filePath) {
2220
2354
  }
2221
2355
  function scanDirectory(dir, projectRoot, depth = 0, maxDepth = 3) {
2222
2356
  const files = [];
2223
- if (depth > maxDepth || !existsSync3(dir)) return files;
2357
+ if (depth > maxDepth || !existsSync4(dir)) return files;
2224
2358
  try {
2225
2359
  const entries = readdirSync(dir);
2226
2360
  for (const entry of entries) {
2227
- const fullPath = join3(dir, entry);
2361
+ const fullPath = join4(dir, entry);
2228
2362
  const relativePath = relative(projectRoot, fullPath).replace(/\\/g, "/");
2229
2363
  try {
2230
2364
  const stat = statSync(fullPath);
@@ -2234,7 +2368,7 @@ function scanDirectory(dir, projectRoot, depth = 0, maxDepth = 3) {
2234
2368
  }
2235
2369
  files.push(...scanDirectory(fullPath, projectRoot, depth + 1, maxDepth));
2236
2370
  } else if (stat.isFile()) {
2237
- const ext = extname(entry).toLowerCase();
2371
+ const ext = extname2(entry).toLowerCase();
2238
2372
  if (![".json", ".yaml", ".yml", ".ts", ".js"].includes(ext)) continue;
2239
2373
  if (entry.includes("config") || entry.includes("tsconfig")) continue;
2240
2374
  const parsed = parseTranslationFile(fullPath);
@@ -2242,7 +2376,7 @@ function scanDirectory(dir, projectRoot, depth = 0, maxDepth = 3) {
2242
2376
  const language = detectLanguageFromPath(fullPath);
2243
2377
  if (!language) continue;
2244
2378
  let namespace;
2245
- const parentDir = basename(dirname3(fullPath));
2379
+ const parentDir = basename(dirname4(fullPath));
2246
2380
  const fileName = basename(entry, ext);
2247
2381
  if (isLikelyLanguageCode(parentDir)) {
2248
2382
  namespace = fileName;
@@ -2282,7 +2416,7 @@ function determineStructure(files, baseDir) {
2282
2416
  return maxFilesPerLang > 1 ? "nested" : "flat";
2283
2417
  }
2284
2418
  const hasNamespaceInFilename = files.some((f) => {
2285
- const name = basename(f.path, extname(f.path));
2419
+ const name = basename(f.path, extname2(f.path));
2286
2420
  return name.includes(".") && !isLikelyLanguageCode(name);
2287
2421
  });
2288
2422
  if (hasNamespaceInFilename) return "namespace-based";
@@ -2345,13 +2479,13 @@ function discoverTranslationFiles(projectRoot = process.cwd()) {
2345
2479
  const framework = detectFramework(projectRoot);
2346
2480
  let files = [];
2347
2481
  let baseDir = "";
2348
- const configPath = join3(projectRoot, ".intlpull.json");
2349
- if (existsSync3(configPath)) {
2482
+ const configPath = join4(projectRoot, ".intlpull.json");
2483
+ if (existsSync4(configPath)) {
2350
2484
  try {
2351
2485
  const config = JSON.parse(readFileSync(configPath, "utf-8"));
2352
2486
  if (config.outputDir) {
2353
- const configuredDir = join3(projectRoot, config.outputDir);
2354
- if (existsSync3(configuredDir)) {
2487
+ const configuredDir = join4(projectRoot, config.outputDir);
2488
+ if (existsSync4(configuredDir)) {
2355
2489
  files = scanDirectory(configuredDir, projectRoot);
2356
2490
  if (files.length > 0) {
2357
2491
  baseDir = configuredDir;
@@ -2364,8 +2498,8 @@ function discoverTranslationFiles(projectRoot = process.cwd()) {
2364
2498
  }
2365
2499
  if (files.length === 0) {
2366
2500
  for (const commonPath of COMMON_PATHS) {
2367
- const dir = join3(projectRoot, commonPath);
2368
- if (existsSync3(dir)) {
2501
+ const dir = join4(projectRoot, commonPath);
2502
+ if (existsSync4(dir)) {
2369
2503
  const foundFiles = scanDirectory(dir, projectRoot);
2370
2504
  if (foundFiles.length > 0) {
2371
2505
  files = foundFiles;
@@ -2377,11 +2511,11 @@ function discoverTranslationFiles(projectRoot = process.cwd()) {
2377
2511
  }
2378
2512
  }
2379
2513
  if (files.length === 0) {
2380
- const srcDir = join3(projectRoot, "src");
2381
- if (existsSync3(srcDir)) {
2514
+ const srcDir = join4(projectRoot, "src");
2515
+ if (existsSync4(srcDir)) {
2382
2516
  files = scanDirectory(srcDir, projectRoot, 0, 4);
2383
2517
  if (files.length > 0) {
2384
- const dirs = new Set(files.map((f) => dirname3(f.path)));
2518
+ const dirs = new Set(files.map((f) => dirname4(f.path)));
2385
2519
  baseDir = dirs.size === 1 ? [...dirs][0] : srcDir;
2386
2520
  indicators.push("Found translation files in src/");
2387
2521
  }
@@ -2473,7 +2607,7 @@ function readSourceTranslationsByNamespace(projectRoot = process.cwd(), sourceLa
2473
2607
  const flattened = flattenTranslations(content);
2474
2608
  const keyCount = Object.keys(flattened).length;
2475
2609
  if (keyCount > 0) {
2476
- const fileNameWithoutExt = basename(file.path, extname(file.path));
2610
+ const fileNameWithoutExt = basename(file.path, extname2(file.path));
2477
2611
  const namespace = file.namespace || (isLikelyLanguageCode(fileNameWithoutExt) ? "common" : fileNameWithoutExt);
2478
2612
  namespaces.push({
2479
2613
  namespace,
@@ -2520,7 +2654,7 @@ function readAllTranslationsByLanguage(projectRoot = process.cwd()) {
2520
2654
  const flattened = flattenTranslations(content);
2521
2655
  const keyCount = Object.keys(flattened).length;
2522
2656
  if (keyCount > 0) {
2523
- const fileNameWithoutExt = basename(file.path, extname(file.path));
2657
+ const fileNameWithoutExt = basename(file.path, extname2(file.path));
2524
2658
  const namespace = file.namespace || (isLikelyLanguageCode(fileNameWithoutExt) ? "common" : fileNameWithoutExt);
2525
2659
  namespaces.push({
2526
2660
  namespace,
@@ -2714,8 +2848,8 @@ To create this branch, go to the IntlPull dashboard or use: npx @intlpullhq/cli
2714
2848
  status: "reading",
2715
2849
  message: `Reading ${options.file}...`
2716
2850
  }));
2717
- const filePath = join4(cwd, options.file);
2718
- if (!existsSync4(filePath)) {
2851
+ const filePath = join5(cwd, options.file);
2852
+ if (!existsSync5(filePath)) {
2719
2853
  setState((prev) => ({
2720
2854
  ...prev,
2721
2855
  status: "error",
@@ -2726,7 +2860,7 @@ To create this branch, go to the IntlPull dashboard or use: npx @intlpullhq/cli
2726
2860
  try {
2727
2861
  const content = JSON.parse(readFileSync2(filePath, "utf-8"));
2728
2862
  const keys = flattenTranslations(content);
2729
- const namespace = basename2(options.file, extname2(options.file));
2863
+ const namespace = basename2(options.file, extname3(options.file));
2730
2864
  namespaces = [{
2731
2865
  namespace,
2732
2866
  keys,
@@ -3777,9 +3911,9 @@ import { readFileSync as readFileSync3 } from "fs";
3777
3911
 
3778
3912
  // src/commands/import/utils.ts
3779
3913
  import { readdirSync as readdirSync2 } from "fs";
3780
- import { join as join5, basename as basename3, extname as extname3 } from "path";
3914
+ import { join as join6, basename as basename3, extname as extname4 } from "path";
3781
3915
  function detectLanguageFromFilename2(filename) {
3782
- const name = basename3(filename, extname3(filename));
3916
+ const name = basename3(filename, extname4(filename));
3783
3917
  const patterns = [
3784
3918
  /^([a-z]{2}(?:-[A-Z]{2})?)$/i,
3785
3919
  // en, en-US
@@ -3799,7 +3933,7 @@ function detectLanguageFromFilename2(filename) {
3799
3933
  return null;
3800
3934
  }
3801
3935
  function detectFormatFromExtension(filename) {
3802
- const ext = extname3(filename).toLowerCase();
3936
+ const ext = extname4(filename).toLowerCase();
3803
3937
  switch (ext) {
3804
3938
  case ".yaml":
3805
3939
  case ".yml":
@@ -3815,13 +3949,13 @@ function findTranslationFiles(dir, pattern) {
3815
3949
  const entries = readdirSync2(dir, { withFileTypes: true });
3816
3950
  for (const entry of entries) {
3817
3951
  if (entry.isFile()) {
3818
- const ext = extname3(entry.name).toLowerCase();
3952
+ const ext = extname4(entry.name).toLowerCase();
3819
3953
  if ([".json", ".yaml", ".yml"].includes(ext)) {
3820
3954
  if (pattern) {
3821
3955
  const regex = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, "."));
3822
3956
  if (!regex.test(entry.name)) continue;
3823
3957
  }
3824
- const filePath = join5(dir, entry.name);
3958
+ const filePath = join6(dir, entry.name);
3825
3959
  files.push({
3826
3960
  path: filePath,
3827
3961
  name: entry.name,
@@ -3830,7 +3964,7 @@ function findTranslationFiles(dir, pattern) {
3830
3964
  });
3831
3965
  }
3832
3966
  } else if (entry.isDirectory() && entry.name !== "node_modules") {
3833
- const subFiles = findTranslationFiles(join5(dir, entry.name), pattern);
3967
+ const subFiles = findTranslationFiles(join6(dir, entry.name), pattern);
3834
3968
  files.push(...subFiles);
3835
3969
  }
3836
3970
  }
@@ -6625,8 +6759,8 @@ import { render as render9, Box as Box22, Text as Text23, useApp as useApp5, use
6625
6759
  import TextInput2 from "ink-text-input";
6626
6760
  import SelectInput6 from "ink-select-input";
6627
6761
  import Spinner11 from "ink-spinner";
6628
- import { readFileSync as readFileSync5, existsSync as existsSync5, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
6629
- import { join as join6, basename as basename5, extname as extname4, relative as relative2 } from "path";
6762
+ import { readFileSync as readFileSync5, existsSync as existsSync6, statSync as statSync3, readdirSync as readdirSync3 } from "fs";
6763
+ import { join as join7, basename as basename5, extname as extname5, relative as relative2 } from "path";
6630
6764
  import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
6631
6765
  function countKeys2(content) {
6632
6766
  try {
@@ -6659,13 +6793,13 @@ function scanTranslationFiles(dir, pattern) {
6659
6793
  return;
6660
6794
  }
6661
6795
  for (const entry of entries) {
6662
- const fullPath = join6(currentDir, entry.name);
6796
+ const fullPath = join7(currentDir, entry.name);
6663
6797
  if (entry.isDirectory()) {
6664
6798
  if (!["node_modules", ".git", "dist", "build", ".next", ".cache", "coverage"].includes(entry.name)) {
6665
6799
  scan(fullPath);
6666
6800
  }
6667
6801
  } else if (entry.isFile()) {
6668
- const ext = extname4(entry.name).toLowerCase();
6802
+ const ext = extname5(entry.name).toLowerCase();
6669
6803
  if (extensions.includes(ext)) {
6670
6804
  if (pattern && !entry.name.match(new RegExp(pattern.replace(/\*/g, ".*")))) {
6671
6805
  continue;
@@ -6694,7 +6828,7 @@ function scanTranslationFiles(dir, pattern) {
6694
6828
  }
6695
6829
  }
6696
6830
  }
6697
- if (existsSync5(dir) && statSync3(dir).isDirectory()) {
6831
+ if (existsSync6(dir) && statSync3(dir).isDirectory()) {
6698
6832
  scan(dir);
6699
6833
  }
6700
6834
  return files.sort((a, b) => a.language.localeCompare(b.language));
@@ -6799,8 +6933,8 @@ Make sure your files follow naming conventions like:
6799
6933
  }
6800
6934
  if (!options.project) {
6801
6935
  try {
6802
- const pkgPath = join6(process.cwd(), "package.json");
6803
- if (existsSync5(pkgPath)) {
6936
+ const pkgPath = join7(process.cwd(), "package.json");
6937
+ if (existsSync6(pkgPath)) {
6804
6938
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
6805
6939
  if (pkg.name) {
6806
6940
  setProjectName(pkg.name.replace(/^@[^/]+\//, ""));
@@ -7149,403 +7283,36 @@ function runMigrateFiles(path4, options) {
7149
7283
  process.exit(1);
7150
7284
  }
7151
7285
  const targetPath = path4 || process.cwd();
7152
- if (!existsSync5(targetPath)) {
7286
+ if (!existsSync6(targetPath)) {
7153
7287
  console.error(`Path not found: ${targetPath}`);
7154
7288
  process.exit(1);
7155
7289
  }
7156
7290
  render9(/* @__PURE__ */ jsx25(MigrationUI, { options, path: targetPath }));
7157
7291
  }
7158
7292
 
7159
- // src/commands/compare.tsx
7160
- import { useState as useState14, useEffect as useEffect11 } from "react";
7161
- import { render as render10, Box as Box23, Text as Text24 } from "ink";
7162
- import Spinner12 from "ink-spinner";
7163
- import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
7164
- var COMPETITOR_PRICING = {
7165
- lokalise: {
7166
- name: "Lokalise",
7167
- tiers: [
7168
- { name: "Start", price: 120, keys: 2e3, users: 5, features: ["Basic features"] },
7169
- { name: "Essential", price: 450, keys: 1e4, users: 10, features: ["TM", "Glossary"] },
7170
- { name: "Pro", price: 990, keys: 3e4, users: 25, features: ["All features"] },
7171
- { name: "Enterprise", price: 2500, keys: 1e5, users: 50, features: ["SSO", "SLA"] }
7172
- ],
7173
- perKeyPricing: { price: 0.045, unit: 1 },
7174
- notes: [
7175
- "Prices are per month, billed annually",
7176
- "Overage charges apply for additional keys",
7177
- "Enterprise pricing varies"
7178
- ]
7179
- },
7180
- crowdin: {
7181
- name: "Crowdin",
7182
- tiers: [
7183
- { name: "Free", price: 0, keys: 500, users: 1, features: ["1 public project"] },
7184
- { name: "Pro", price: 50, keys: 5e3, users: 3, features: ["Private projects"] },
7185
- { name: "Team", price: 250, keys: 25e3, users: 10, features: ["TM", "Glossary"] },
7186
- { name: "Enterprise", price: 600, keys: 1e5, users: 30, features: ["SSO", "API"] }
7187
- ],
7188
- perKeyPricing: { price: 0.02, unit: 1 },
7189
- notes: [
7190
- "Prices are per month",
7191
- "Free tier for open source",
7192
- "Enterprise has custom pricing"
7193
- ]
7194
- },
7195
- phrase: {
7196
- name: "Phrase",
7197
- tiers: [
7198
- { name: "Starter", price: 75, keys: 1e3, users: 3, features: ["Basic"] },
7199
- { name: "Pro", price: 250, keys: 5e3, users: 10, features: ["TM", "API"] },
7200
- { name: "Business", price: 600, keys: 2e4, users: 25, features: ["Glossary"] },
7201
- { name: "Enterprise", price: 1500, keys: 1e5, users: 100, features: ["SSO", "SLA"] }
7202
- ],
7203
- perKeyPricing: { price: 0.05, unit: 1 },
7204
- notes: [
7205
- "Prices are per month, billed annually",
7206
- "Phrase Strings pricing",
7207
- "Separate products for TMS"
7208
- ]
7209
- }
7210
- };
7211
- var INTLPULL_PRICING = {
7212
- name: "IntlPull",
7213
- tiers: [
7214
- { name: "Free", price: 0, keys: 1e3, users: 2, features: ["In-context editing", "Chrome extension", "CLI", "OTA updates"] },
7215
- { name: "Pro", price: 19, keys: 1e4, users: 5, features: ["All Free features", "TM", "Glossary", "Branching"] },
7216
- { name: "Team", price: 49, keys: 5e4, users: 15, features: ["All Pro features", "Priority support", "Team roles"] },
7217
- { name: "Business", price: 99, keys: 2e5, users: 50, features: ["All Team features", "SSO", "SLA", "Dedicated support"] }
7218
- ],
7219
- notes: [
7220
- "No per-key overage charges",
7221
- "All plans include OTA updates",
7222
- "Developer-first tools included"
7223
- ]
7224
- };
7225
- function calculateCompetitorCost(provider, keys, users) {
7226
- const pricing = COMPETITOR_PRICING[provider];
7227
- if (!pricing) {
7228
- return { tier: "Unknown", monthly: 0, annual: 0 };
7229
- }
7230
- const suitableTier = pricing.tiers.find((t) => t.keys >= keys && t.users >= users) || pricing.tiers[pricing.tiers.length - 1];
7231
- return {
7232
- tier: suitableTier.name,
7233
- monthly: suitableTier.price,
7234
- annual: suitableTier.price * 12
7235
- };
7236
- }
7237
- function calculateIntlPullCost(keys, users) {
7238
- const suitableTier = INTLPULL_PRICING.tiers.find((t) => t.keys >= keys && t.users >= users) || INTLPULL_PRICING.tiers[INTLPULL_PRICING.tiers.length - 1];
7239
- return {
7240
- tier: suitableTier.name,
7241
- monthly: suitableTier.price,
7242
- annual: suitableTier.price * 12
7243
- };
7244
- }
7245
- function formatCurrency(amount) {
7246
- return new Intl.NumberFormat("en-US", {
7247
- style: "currency",
7248
- currency: "USD",
7249
- minimumFractionDigits: 0,
7250
- maximumFractionDigits: 0
7251
- }).format(amount);
7252
- }
7253
- function formatPercent(value) {
7254
- return `${Math.round(value)}%`;
7255
- }
7256
- function CompareDisplay({ options }) {
7257
- const [loading, setLoading] = useState14(true);
7258
- const [result, setResult] = useState14(null);
7259
- const [projectStats, setProjectStats] = useState14(null);
7260
- useEffect11(() => {
7261
- async function loadData() {
7262
- const config = getProjectConfig();
7263
- const resolved = getResolvedApiKey();
7264
- const globalConfig = getGlobalConfig();
7265
- let keys = options.keys || 1e3;
7266
- let languages = options.languages || 2;
7267
- let users = options.users || 5;
7268
- if (resolved?.key && config?.projectId) {
7269
- try {
7270
- const apiUrl = globalConfig.apiUrl || "https://api.intlpull.com";
7271
- const response = await fetch(`${apiUrl}/api/v1/projects/${config.projectId}`, {
7272
- headers: { "X-API-Key": resolved.key }
7273
- });
7274
- if (response.ok) {
7275
- const data = await response.json();
7276
- keys = data.stats?.total_keys || keys;
7277
- languages = data.languages?.length || languages;
7278
- setProjectStats({ keys, languages });
7279
- }
7280
- } catch {
7281
- }
7282
- }
7283
- const provider = options.from || "lokalise";
7284
- const competitorCost = calculateCompetitorCost(provider, keys, users);
7285
- const intlpullCost = calculateIntlPullCost(keys, users);
7286
- const monthlySavings = competitorCost.monthly - intlpullCost.monthly;
7287
- const annualSavings = competitorCost.annual - intlpullCost.annual;
7288
- const percentSavings = competitorCost.monthly > 0 ? monthlySavings / competitorCost.monthly * 100 : 0;
7289
- setResult({
7290
- competitor: {
7291
- name: COMPETITOR_PRICING[provider]?.name || provider,
7292
- tier: competitorCost.tier,
7293
- monthly: competitorCost.monthly,
7294
- annual: competitorCost.annual
7295
- },
7296
- intlpull: intlpullCost,
7297
- savings: {
7298
- monthly: monthlySavings,
7299
- annual: annualSavings,
7300
- percent: percentSavings
7301
- },
7302
- inputs: { keys, languages, users }
7303
- });
7304
- setLoading(false);
7305
- }
7306
- loadData();
7307
- }, [options]);
7308
- if (loading) {
7309
- return /* @__PURE__ */ jsxs24(Box23, { children: [
7310
- /* @__PURE__ */ jsx26(Text24, { color: "cyan", children: /* @__PURE__ */ jsx26(Spinner12, { type: "dots" }) }),
7311
- /* @__PURE__ */ jsx26(Text24, { children: " Calculating cost comparison..." })
7312
- ] });
7313
- }
7314
- if (!result) {
7315
- return /* @__PURE__ */ jsx26(Box23, { children: /* @__PURE__ */ jsx26(Text24, { color: "red", children: "Failed to calculate comparison" }) });
7316
- }
7317
- if (options.json) {
7318
- return /* @__PURE__ */ jsx26(Text24, { children: JSON.stringify(result, null, 2) });
7319
- }
7320
- return /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", paddingX: 1, children: [
7321
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, children: [
7322
- /* @__PURE__ */ jsx26(Text24, { bold: true, color: "cyan", children: "IntlPull" }),
7323
- /* @__PURE__ */ jsx26(Text24, { children: " \u2022 Cost Comparison" })
7324
- ] }),
7325
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, flexDirection: "column", children: [
7326
- /* @__PURE__ */ jsx26(Text24, { bold: true, children: "Your Usage:" }),
7327
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7328
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Keys: " }),
7329
- /* @__PURE__ */ jsx26(Text24, { children: result.inputs.keys.toLocaleString() }),
7330
- projectStats && /* @__PURE__ */ jsx26(Text24, { color: "green", children: " (from project)" })
7331
- ] }),
7332
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7333
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Languages: " }),
7334
- /* @__PURE__ */ jsx26(Text24, { children: result.inputs.languages })
7335
- ] }),
7336
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7337
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Users: " }),
7338
- /* @__PURE__ */ jsx26(Text24, { children: result.inputs.users })
7339
- ] })
7340
- ] }),
7341
- /* @__PURE__ */ jsx26(Box23, { marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsxs24(Box23, { borderStyle: "single", flexDirection: "column", paddingX: 2, paddingY: 1, children: [
7342
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, children: [
7343
- /* @__PURE__ */ jsx26(Text24, { bold: true, color: "red", children: result.competitor.name }),
7344
- /* @__PURE__ */ jsxs24(Text24, { children: [
7345
- " (",
7346
- result.competitor.tier,
7347
- " plan)"
7348
- ] })
7349
- ] }),
7350
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7351
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Monthly: " }),
7352
- /* @__PURE__ */ jsx26(Text24, { children: formatCurrency(result.competitor.monthly) })
7353
- ] }),
7354
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7355
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Annual: " }),
7356
- /* @__PURE__ */ jsx26(Text24, { children: formatCurrency(result.competitor.annual) })
7357
- ] }),
7358
- /* @__PURE__ */ jsx26(Box23, { marginY: 1, children: /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
7359
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, children: [
7360
- /* @__PURE__ */ jsx26(Text24, { bold: true, color: "green", children: "IntlPull" }),
7361
- /* @__PURE__ */ jsxs24(Text24, { children: [
7362
- " (",
7363
- result.intlpull.tier,
7364
- " plan)"
7365
- ] })
7366
- ] }),
7367
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7368
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Monthly: " }),
7369
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: formatCurrency(result.intlpull.monthly) })
7370
- ] }),
7371
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7372
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Annual: " }),
7373
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: formatCurrency(result.intlpull.annual) })
7374
- ] })
7375
- ] }) }),
7376
- result.savings.monthly > 0 && /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, flexDirection: "column", children: [
7377
- /* @__PURE__ */ jsx26(Text24, { bold: true, color: "green", children: "\u{1F4B0} Your Savings with IntlPull:" }),
7378
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7379
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Monthly: " }),
7380
- /* @__PURE__ */ jsx26(Text24, { color: "green", bold: true, children: formatCurrency(result.savings.monthly) }),
7381
- /* @__PURE__ */ jsxs24(Text24, { dimColor: true, children: [
7382
- " (",
7383
- formatPercent(result.savings.percent),
7384
- " less)"
7385
- ] })
7386
- ] }),
7387
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, children: [
7388
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Annual: " }),
7389
- /* @__PURE__ */ jsx26(Text24, { color: "green", bold: true, children: formatCurrency(result.savings.annual) })
7390
- ] })
7391
- ] }),
7392
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, flexDirection: "column", children: [
7393
- /* @__PURE__ */ jsx26(Text24, { bold: true, children: "What you get with IntlPull:" }),
7394
- /* @__PURE__ */ jsxs24(Box23, { paddingLeft: 2, flexDirection: "column", children: [
7395
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: "\u2713 In-context editing (Chrome extension)" }),
7396
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: "\u2713 CLI with smart auto-detection" }),
7397
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: "\u2713 OTA updates for mobile apps" }),
7398
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: "\u2713 Git branch integration" }),
7399
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: "\u2713 AI-powered translation" }),
7400
- /* @__PURE__ */ jsx26(Text24, { color: "green", children: "\u2713 No per-key overage charges" })
7401
- ] })
7402
- ] }),
7403
- /* @__PURE__ */ jsxs24(Box23, { marginTop: 1, children: [
7404
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Ready to switch? Run: " }),
7405
- /* @__PURE__ */ jsxs24(Text24, { color: "cyan", children: [
7406
- "npx @intlpullhq/cli migrate from ",
7407
- options.from || "lokalise"
7408
- ] })
7409
- ] })
7410
- ] });
7411
- }
7412
- function CompareAllProviders({ options }) {
7413
- const [loading, setLoading] = useState14(true);
7414
- const [results, setResults] = useState14([]);
7415
- useEffect11(() => {
7416
- const keys = options.keys || 5e3;
7417
- const users = options.users || 5;
7418
- const languages = options.languages || 3;
7419
- const providers = ["lokalise", "crowdin", "phrase"];
7420
- const comparisons = [];
7421
- for (const provider of providers) {
7422
- const competitorCost = calculateCompetitorCost(provider, keys, users);
7423
- const intlpullCost2 = calculateIntlPullCost(keys, users);
7424
- const monthlySavings = competitorCost.monthly - intlpullCost2.monthly;
7425
- const annualSavings = competitorCost.annual - intlpullCost2.annual;
7426
- const percentSavings = competitorCost.monthly > 0 ? monthlySavings / competitorCost.monthly * 100 : 0;
7427
- comparisons.push({
7428
- competitor: {
7429
- name: COMPETITOR_PRICING[provider]?.name || provider,
7430
- tier: competitorCost.tier,
7431
- monthly: competitorCost.monthly,
7432
- annual: competitorCost.annual
7433
- },
7434
- intlpull: intlpullCost2,
7435
- savings: {
7436
- monthly: monthlySavings,
7437
- annual: annualSavings,
7438
- percent: percentSavings
7439
- },
7440
- inputs: { keys, languages, users }
7441
- });
7442
- }
7443
- setResults(comparisons);
7444
- setLoading(false);
7445
- }, [options]);
7446
- if (loading) {
7447
- return /* @__PURE__ */ jsxs24(Box23, { children: [
7448
- /* @__PURE__ */ jsx26(Text24, { color: "cyan", children: /* @__PURE__ */ jsx26(Spinner12, { type: "dots" }) }),
7449
- /* @__PURE__ */ jsx26(Text24, { children: " Calculating cost comparison..." })
7450
- ] });
7451
- }
7452
- if (options.json) {
7453
- return /* @__PURE__ */ jsx26(Text24, { children: JSON.stringify(results, null, 2) });
7454
- }
7455
- const intlpullCost = results[0]?.intlpull;
7456
- return /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", paddingX: 1, children: [
7457
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, children: [
7458
- /* @__PURE__ */ jsx26(Text24, { bold: true, color: "cyan", children: "IntlPull" }),
7459
- /* @__PURE__ */ jsx26(Text24, { children: " \u2022 Cost Comparison" })
7460
- ] }),
7461
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, children: [
7462
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Comparing for: " }),
7463
- /* @__PURE__ */ jsxs24(Text24, { children: [
7464
- results[0]?.inputs.keys.toLocaleString(),
7465
- " keys, "
7466
- ] }),
7467
- /* @__PURE__ */ jsxs24(Text24, { children: [
7468
- results[0]?.inputs.users,
7469
- " users, "
7470
- ] }),
7471
- /* @__PURE__ */ jsxs24(Text24, { children: [
7472
- results[0]?.inputs.languages,
7473
- " languages"
7474
- ] })
7475
- ] }),
7476
- /* @__PURE__ */ jsx26(Box23, { marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsxs24(Box23, { borderStyle: "single", flexDirection: "column", paddingX: 2, paddingY: 1, children: [
7477
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, children: [
7478
- /* @__PURE__ */ jsx26(Box23, { width: 15, children: /* @__PURE__ */ jsx26(Text24, { bold: true, children: "Provider" }) }),
7479
- /* @__PURE__ */ jsx26(Box23, { width: 10, children: /* @__PURE__ */ jsx26(Text24, { bold: true, children: "Plan" }) }),
7480
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { bold: true, children: "Monthly" }) }),
7481
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { bold: true, children: "Annual" }) }),
7482
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { bold: true, children: "Savings" }) })
7483
- ] }),
7484
- results.map((r, i) => /* @__PURE__ */ jsxs24(Box23, { children: [
7485
- /* @__PURE__ */ jsx26(Box23, { width: 15, children: /* @__PURE__ */ jsx26(Text24, { color: "red", children: r.competitor.name }) }),
7486
- /* @__PURE__ */ jsx26(Box23, { width: 10, children: /* @__PURE__ */ jsx26(Text24, { children: r.competitor.tier }) }),
7487
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { children: formatCurrency(r.competitor.monthly) }) }),
7488
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { children: formatCurrency(r.competitor.annual) }) }),
7489
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { color: "green", children: r.savings.monthly > 0 ? formatPercent(r.savings.percent) : "-" }) })
7490
- ] }, i)),
7491
- /* @__PURE__ */ jsx26(Box23, { marginY: 1, children: /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }),
7492
- /* @__PURE__ */ jsxs24(Box23, { children: [
7493
- /* @__PURE__ */ jsx26(Box23, { width: 15, children: /* @__PURE__ */ jsx26(Text24, { color: "green", bold: true, children: "IntlPull" }) }),
7494
- /* @__PURE__ */ jsx26(Box23, { width: 10, children: /* @__PURE__ */ jsx26(Text24, { children: intlpullCost?.tier }) }),
7495
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { color: "green", bold: true, children: formatCurrency(intlpullCost?.monthly || 0) }) }),
7496
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { color: "green", bold: true, children: formatCurrency(intlpullCost?.annual || 0) }) }),
7497
- /* @__PURE__ */ jsx26(Box23, { width: 12, children: /* @__PURE__ */ jsx26(Text24, { color: "green", bold: true, children: "Best" }) })
7498
- ] })
7499
- ] }) }),
7500
- /* @__PURE__ */ jsxs24(Box23, { marginBottom: 1, children: [
7501
- /* @__PURE__ */ jsx26(Text24, { children: "\u{1F4B0} Save up to " }),
7502
- /* @__PURE__ */ jsxs24(Text24, { color: "green", bold: true, children: [
7503
- formatCurrency(Math.max(...results.map((r) => r.savings.annual))),
7504
- "/year"
7505
- ] }),
7506
- /* @__PURE__ */ jsx26(Text24, { children: " by switching to IntlPull" })
7507
- ] }),
7508
- /* @__PURE__ */ jsxs24(Box23, { marginTop: 1, children: [
7509
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Ready to switch? Run: " }),
7510
- /* @__PURE__ */ jsx26(Text24, { color: "cyan", children: "npx @intlpullhq/cli migrate from <provider>" })
7511
- ] }),
7512
- /* @__PURE__ */ jsxs24(Box23, { marginTop: 1, children: [
7513
- /* @__PURE__ */ jsx26(Text24, { dimColor: true, children: "Compare with your usage: " }),
7514
- /* @__PURE__ */ jsx26(Text24, { color: "cyan", children: "npx @intlpullhq/cli compare --keys 10000 --users 10" })
7515
- ] })
7516
- ] });
7517
- }
7518
- function runCompare(options) {
7519
- if (options.from) {
7520
- render10(/* @__PURE__ */ jsx26(CompareDisplay, { options }));
7521
- } else {
7522
- render10(/* @__PURE__ */ jsx26(CompareAllProviders, { options }));
7523
- }
7524
- }
7525
-
7526
7293
  // src/commands/check.tsx
7527
- import { useState as useState15, useEffect as useEffect12 } from "react";
7528
- import { render as render11, Box as Box25, Text as Text26 } from "ink";
7294
+ import { useState as useState14, useEffect as useEffect11 } from "react";
7295
+ import { render as render10, Box as Box24, Text as Text25 } from "ink";
7529
7296
  import { glob } from "glob";
7530
- import { readFileSync as readFileSync6 } from "fs";
7297
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
7531
7298
 
7532
7299
  // src/components/TaskList.tsx
7533
- import { Box as Box24, Text as Text25 } from "ink";
7534
- import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
7300
+ import { Box as Box23, Text as Text24 } from "ink";
7301
+ import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
7535
7302
  function getStatusIcon(status) {
7536
7303
  switch (status) {
7537
7304
  case "pending":
7538
- return /* @__PURE__ */ jsx27(Text25, { color: colors.textDim, children: icons.pending });
7305
+ return /* @__PURE__ */ jsx26(Text24, { color: colors.textDim, children: icons.pending });
7539
7306
  case "running":
7540
- return /* @__PURE__ */ jsx27(Spinner2, { type: "dots", color: colors.primary });
7307
+ return /* @__PURE__ */ jsx26(Spinner2, { type: "dots", color: colors.primary });
7541
7308
  case "success":
7542
- return /* @__PURE__ */ jsx27(Text25, { color: colors.success, children: icons.success });
7309
+ return /* @__PURE__ */ jsx26(Text24, { color: colors.success, children: icons.success });
7543
7310
  case "error":
7544
- return /* @__PURE__ */ jsx27(Text25, { color: colors.error, children: icons.error });
7311
+ return /* @__PURE__ */ jsx26(Text24, { color: colors.error, children: icons.error });
7545
7312
  case "warning":
7546
- return /* @__PURE__ */ jsx27(Text25, { color: colors.warning, children: icons.warning });
7313
+ return /* @__PURE__ */ jsx26(Text24, { color: colors.warning, children: icons.warning });
7547
7314
  case "skipped":
7548
- return /* @__PURE__ */ jsx27(Text25, { color: colors.textDim, children: icons.skipped });
7315
+ return /* @__PURE__ */ jsx26(Text24, { color: colors.textDim, children: icons.skipped });
7549
7316
  }
7550
7317
  }
7551
7318
  function getStatusColor(status) {
@@ -7565,36 +7332,45 @@ function getStatusColor(status) {
7565
7332
  }
7566
7333
  }
7567
7334
  function TaskList({ tasks, title }) {
7568
- return /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", marginY: 1, children: [
7569
- title && /* @__PURE__ */ jsx27(Box24, { marginBottom: 1, children: /* @__PURE__ */ jsx27(Text25, { bold: true, color: colors.text, children: title }) }),
7570
- tasks.map((task) => /* @__PURE__ */ jsxs25(Box24, { marginLeft: 1, children: [
7571
- /* @__PURE__ */ jsx27(Box24, { width: 3, children: getStatusIcon(task.status) }),
7572
- /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", children: [
7573
- /* @__PURE__ */ jsx27(Text25, { color: getStatusColor(task.status), children: task.label }),
7574
- task.output && task.status !== "pending" && /* @__PURE__ */ jsx27(Text25, { color: colors.textDim, dimColor: true, children: task.output })
7335
+ return /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", marginY: 1, children: [
7336
+ title && /* @__PURE__ */ jsx26(Box23, { marginBottom: 1, children: /* @__PURE__ */ jsx26(Text24, { bold: true, color: colors.text, children: title }) }),
7337
+ tasks.map((task) => /* @__PURE__ */ jsxs24(Box23, { marginLeft: 1, children: [
7338
+ /* @__PURE__ */ jsx26(Box23, { width: 3, children: getStatusIcon(task.status) }),
7339
+ /* @__PURE__ */ jsxs24(Box23, { flexDirection: "column", children: [
7340
+ /* @__PURE__ */ jsx26(Text24, { color: getStatusColor(task.status), children: task.label }),
7341
+ task.output && task.status !== "pending" && /* @__PURE__ */ jsx26(Text24, { color: colors.textDim, dimColor: true, children: task.output })
7575
7342
  ] })
7576
7343
  ] }, task.id))
7577
7344
  ] });
7578
7345
  }
7579
7346
 
7580
7347
  // src/commands/check.tsx
7581
- import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
7348
+ import { jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
7349
+ var LANG_CODE_REGEX = /^[a-z]{2}([-_][a-zA-Z]{2})?$/i;
7582
7350
  function CheckCommand({ options }) {
7583
- const [tasks, setTasks] = useState15([
7351
+ const baseTasks = [
7584
7352
  { id: "config", label: "Loading configuration", status: "pending" },
7585
7353
  { id: "scan", label: "Scanning translation files", status: "pending" },
7586
7354
  { id: "compare", label: "Comparing translations", status: "pending" },
7587
7355
  { id: "report", label: "Generating report", status: "pending" }
7588
- ]);
7589
- const [result, setResult] = useState15(null);
7590
- const [error, setError] = useState15(null);
7591
- const [done, setDone] = useState15(false);
7356
+ ];
7357
+ if (options.fix) {
7358
+ baseTasks.push({ id: "fix", label: "Fixing missing translations", status: "pending" });
7359
+ }
7360
+ if (options.output) {
7361
+ baseTasks.push({ id: "output", label: "Writing report file", status: "pending" });
7362
+ }
7363
+ const [tasks, setTasks] = useState14(baseTasks);
7364
+ const [result, setResult] = useState14(null);
7365
+ const [fixResult, setFixResult] = useState14(null);
7366
+ const [error, setError] = useState14(null);
7367
+ const [done, setDone] = useState14(false);
7592
7368
  const updateTask = (id, update) => {
7593
7369
  setTasks(
7594
7370
  (prev) => prev.map((t) => t.id === id ? { ...t, ...update } : t)
7595
7371
  );
7596
7372
  };
7597
- useEffect12(() => {
7373
+ useEffect11(() => {
7598
7374
  async function run() {
7599
7375
  try {
7600
7376
  updateTask("config", { status: "running" });
@@ -7636,10 +7412,10 @@ function CheckCommand({ options }) {
7636
7412
  const parts = file.split("/");
7637
7413
  const fileName = parts[parts.length - 1];
7638
7414
  const dirName = parts[parts.length - 2];
7639
- if (/^[a-z]{2}(-[A-Z]{2})?$/.test(dirName)) {
7415
+ if (LANG_CODE_REGEX.test(dirName)) {
7640
7416
  if (!filesByLang[dirName]) filesByLang[dirName] = [];
7641
7417
  filesByLang[dirName].push(file);
7642
- } else if (/^[a-z]{2}(-[A-Z]{2})?\.json$/.test(fileName)) {
7418
+ } else if (LANG_CODE_REGEX.test(fileName.replace(".json", ""))) {
7643
7419
  const lang = fileName.replace(".json", "");
7644
7420
  if (!filesByLang[lang]) filesByLang[lang] = [];
7645
7421
  filesByLang[lang].push(file);
@@ -7727,14 +7503,77 @@ ${parseErrors.join("\n")}`);
7727
7503
  status: missingKeys.length > 0 ? "warning" : "success",
7728
7504
  output: missingKeys.length > 0 ? `Found ${missingKeys.length} missing key(s)` : "All translations complete!"
7729
7505
  });
7730
- setResult({
7506
+ const checkResult = {
7731
7507
  sourceLanguage,
7732
7508
  targetLanguages,
7733
7509
  totalKeys,
7734
7510
  missingKeys: missingKeys.slice(0, 10),
7735
7511
  // Show first 10
7736
7512
  coveragePercentage
7737
- });
7513
+ };
7514
+ setResult(checkResult);
7515
+ if (options.fix && missingKeys.length > 0) {
7516
+ updateTask("fix", { status: "running" });
7517
+ let filesModified = 0;
7518
+ let keysAdded = 0;
7519
+ for (const targetLang of targetLanguages) {
7520
+ if (!filesByLang[targetLang]) continue;
7521
+ for (const file of filesByLang[targetLang]) {
7522
+ let content = {};
7523
+ try {
7524
+ content = JSON.parse(readFileSync6(file, "utf-8"));
7525
+ } catch {
7526
+ content = {};
7527
+ }
7528
+ const flatTarget = flattenObjectToRecord(content);
7529
+ let modified = false;
7530
+ for (const mk of missingKeys) {
7531
+ if (mk.missingIn.includes(targetLang) && !(mk.key in flatTarget)) {
7532
+ setNestedValue(content, mk.key, `[${targetLang.toUpperCase()}] ${mk.sourceValue}`);
7533
+ keysAdded++;
7534
+ modified = true;
7535
+ }
7536
+ }
7537
+ if (modified) {
7538
+ writeFileSync4(file, JSON.stringify(content, null, 2) + "\n");
7539
+ filesModified++;
7540
+ }
7541
+ }
7542
+ }
7543
+ setFixResult({ filesModified, keysAdded });
7544
+ updateTask("fix", {
7545
+ status: keysAdded > 0 ? "success" : "warning",
7546
+ output: keysAdded > 0 ? `Added ${keysAdded} key(s) to ${filesModified} file(s)` : "No keys to fix"
7547
+ });
7548
+ }
7549
+ if (options.output) {
7550
+ updateTask("output", { status: "running" });
7551
+ const fullReport = {
7552
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7553
+ sourceLanguage,
7554
+ targetLanguages,
7555
+ totalKeys,
7556
+ missingKeysCount: missingKeys.length,
7557
+ missingKeys: missingKeys.map((mk) => ({
7558
+ key: mk.key,
7559
+ sourceValue: mk.sourceValue,
7560
+ missingIn: mk.missingIn
7561
+ })),
7562
+ coveragePercentage
7563
+ };
7564
+ try {
7565
+ writeFileSync4(options.output, JSON.stringify(fullReport, null, 2) + "\n");
7566
+ updateTask("output", {
7567
+ status: "success",
7568
+ output: `Report written to ${options.output}`
7569
+ });
7570
+ } catch (writeErr) {
7571
+ updateTask("output", {
7572
+ status: "error",
7573
+ output: `Failed to write report: ${writeErr instanceof Error ? writeErr.message : "Unknown error"}`
7574
+ });
7575
+ }
7576
+ }
7738
7577
  setDone(true);
7739
7578
  } catch (err) {
7740
7579
  setError(err instanceof Error ? err.message : "Unknown error");
@@ -7743,52 +7582,73 @@ ${parseErrors.join("\n")}`);
7743
7582
  }
7744
7583
  run();
7745
7584
  }, [options]);
7746
- return /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", children: [
7747
- /* @__PURE__ */ jsx28(Header, { compact: true }),
7748
- /* @__PURE__ */ jsx28(TaskList, { tasks, title: "Checking translations" }),
7749
- error && /* @__PURE__ */ jsx28(Alert, { type: "error", title: "Error", children: error }),
7750
- done && result && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7751
- result.missingKeys.length === 0 ? /* @__PURE__ */ jsxs26(Alert, { type: "success", title: "All translations complete", children: [
7585
+ return /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", children: [
7586
+ /* @__PURE__ */ jsx27(Header, { compact: true }),
7587
+ /* @__PURE__ */ jsx27(TaskList, { tasks, title: "Checking translations" }),
7588
+ error && /* @__PURE__ */ jsx27(Alert, { type: "error", title: "Error", children: error }),
7589
+ done && result && /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", marginTop: 1, children: [
7590
+ result.missingKeys.length === 0 ? /* @__PURE__ */ jsxs25(Alert, { type: "success", title: "All translations complete", children: [
7752
7591
  result.totalKeys,
7753
7592
  " keys are translated in all ",
7754
7593
  result.targetLanguages.length,
7755
7594
  " language(s)"
7756
- ] }) : /* @__PURE__ */ jsxs26(Alert, { type: "warning", title: "Missing translations found", children: [
7595
+ ] }) : /* @__PURE__ */ jsxs25(Alert, { type: "warning", title: "Missing translations found", children: [
7757
7596
  result.missingKeys.length,
7758
7597
  " key(s) missing across target languages"
7759
7598
  ] }),
7760
- /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7761
- /* @__PURE__ */ jsx28(Text26, { bold: true, color: colors.text, children: "Coverage:" }),
7762
- Object.entries(result.coveragePercentage).map(([lang, pct]) => /* @__PURE__ */ jsxs26(Box25, { marginLeft: 2, children: [
7763
- /* @__PURE__ */ jsx28(Text26, { color: pct === 100 ? colors.success : pct >= 80 ? colors.warning : colors.error, children: pct === 100 ? icons.success : icons.warning }),
7764
- /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
7599
+ /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", marginTop: 1, children: [
7600
+ /* @__PURE__ */ jsx27(Text25, { bold: true, color: colors.text, children: "Coverage:" }),
7601
+ Object.entries(result.coveragePercentage).map(([lang, pct]) => /* @__PURE__ */ jsxs25(Box24, { marginLeft: 2, children: [
7602
+ /* @__PURE__ */ jsx27(Text25, { color: pct === 100 ? colors.success : pct >= 80 ? colors.warning : colors.error, children: pct === 100 ? icons.success : icons.warning }),
7603
+ /* @__PURE__ */ jsxs25(Text25, { color: colors.textMuted, children: [
7765
7604
  " ",
7766
7605
  lang,
7767
7606
  ": ",
7768
- /* @__PURE__ */ jsxs26(Text26, { color: pct === 100 ? colors.success : colors.text, children: [
7607
+ /* @__PURE__ */ jsxs25(Text25, { color: pct === 100 ? colors.success : colors.text, children: [
7769
7608
  pct,
7770
7609
  "%"
7771
7610
  ] })
7772
7611
  ] })
7773
7612
  ] }, lang))
7774
7613
  ] }),
7775
- result.missingKeys.length > 0 && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7776
- /* @__PURE__ */ jsx28(Text26, { bold: true, color: colors.text, children: "Missing keys (first 10):" }),
7777
- result.missingKeys.map((mk, i) => /* @__PURE__ */ jsxs26(Box25, { marginLeft: 2, flexDirection: "column", children: [
7778
- /* @__PURE__ */ jsxs26(Text26, { color: colors.error, children: [
7614
+ result.missingKeys.length > 0 && /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", marginTop: 1, children: [
7615
+ /* @__PURE__ */ jsx27(Text25, { bold: true, color: colors.text, children: "Missing keys (first 10):" }),
7616
+ result.missingKeys.map((mk, i) => /* @__PURE__ */ jsxs25(Box24, { marginLeft: 2, flexDirection: "column", children: [
7617
+ /* @__PURE__ */ jsxs25(Text25, { color: colors.error, children: [
7779
7618
  "\u2022 ",
7780
7619
  mk.key
7781
7620
  ] }),
7782
- /* @__PURE__ */ jsx28(Box25, { marginLeft: 2, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
7621
+ /* @__PURE__ */ jsx27(Box24, { marginLeft: 2, children: /* @__PURE__ */ jsxs25(Text25, { color: colors.textMuted, children: [
7783
7622
  "Missing in: ",
7784
7623
  mk.missingIn.join(", ")
7785
7624
  ] }) })
7786
7625
  ] }, i))
7787
7626
  ] }),
7788
- /* @__PURE__ */ jsx28(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
7627
+ fixResult && fixResult.keysAdded > 0 && /* @__PURE__ */ jsxs25(Box24, { flexDirection: "column", marginTop: 1, children: [
7628
+ /* @__PURE__ */ jsxs25(Text25, { bold: true, color: colors.success, children: [
7629
+ icons.success,
7630
+ " Fixed ",
7631
+ fixResult.keysAdded,
7632
+ " key(s) in ",
7633
+ fixResult.filesModified,
7634
+ " file(s)"
7635
+ ] }),
7636
+ /* @__PURE__ */ jsxs25(Box24, { marginTop: 1, children: [
7637
+ /* @__PURE__ */ jsxs25(Text25, { color: colors.warning, children: [
7638
+ icons.warning,
7639
+ " "
7640
+ ] }),
7641
+ /* @__PURE__ */ jsx27(Text25, { color: colors.textMuted, children: "Missing keys have been added with [LANG] prefix. Review and translate them." })
7642
+ ] })
7643
+ ] }),
7644
+ !options.fix && result.missingKeys.length > 0 && /* @__PURE__ */ jsx27(Box24, { marginTop: 1, children: /* @__PURE__ */ jsxs25(Text25, { color: colors.textMuted, children: [
7789
7645
  "Run ",
7790
- /* @__PURE__ */ jsx28(Text26, { color: colors.primary, children: "npx @intlpullhq/cli fix" }),
7646
+ /* @__PURE__ */ jsx27(Text25, { color: colors.primary, children: "npx @intlpullhq/cli fix" }),
7791
7647
  " to auto-generate missing translations"
7648
+ ] }) }),
7649
+ options.output && /* @__PURE__ */ jsx27(Box24, { marginTop: 1, children: /* @__PURE__ */ jsxs25(Text25, { color: colors.textMuted, children: [
7650
+ "Report saved to ",
7651
+ /* @__PURE__ */ jsx27(Text25, { color: colors.primary, children: options.output })
7792
7652
  ] }) })
7793
7653
  ] })
7794
7654
  ] });
@@ -7803,15 +7663,38 @@ function flattenObject2(obj, prefix, result) {
7803
7663
  }
7804
7664
  }
7805
7665
  }
7666
+ function flattenObjectToRecord(obj, prefix = "") {
7667
+ const result = {};
7668
+ for (const [key, value] of Object.entries(obj)) {
7669
+ const newKey = prefix ? `${prefix}.${key}` : key;
7670
+ if (typeof value === "string") {
7671
+ result[newKey] = value;
7672
+ } else if (typeof value === "object" && value !== null) {
7673
+ Object.assign(result, flattenObjectToRecord(value, newKey));
7674
+ }
7675
+ }
7676
+ return result;
7677
+ }
7678
+ function setNestedValue(obj, key, value) {
7679
+ const parts = key.split(".");
7680
+ let current = obj;
7681
+ for (let i = 0; i < parts.length - 1; i++) {
7682
+ if (!current[parts[i]] || typeof current[parts[i]] === "string") {
7683
+ current[parts[i]] = {};
7684
+ }
7685
+ current = current[parts[i]];
7686
+ }
7687
+ current[parts[parts.length - 1]] = value;
7688
+ }
7806
7689
  function runCheck(options) {
7807
- render11(/* @__PURE__ */ jsx28(CheckCommand, { options }));
7690
+ render10(/* @__PURE__ */ jsx27(CheckCommand, { options }));
7808
7691
  }
7809
7692
 
7810
7693
  // src/commands/diff.tsx
7811
- import { useState as useState16, useEffect as useEffect13 } from "react";
7812
- import { render as render12, Box as Box26, Text as Text27 } from "ink";
7694
+ import { useState as useState15, useEffect as useEffect12 } from "react";
7695
+ import { render as render11, Box as Box25, Text as Text26 } from "ink";
7813
7696
  import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
7814
- import { jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
7697
+ import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
7815
7698
  async function fetchProjects5(apiUrl, apiKey) {
7816
7699
  const response = await fetch(`${apiUrl}/api/v1/projects`, {
7817
7700
  headers: { Accept: "application/json", "X-API-Key": apiKey }
@@ -7828,21 +7711,21 @@ async function fetchProjects5(apiUrl, apiKey) {
7828
7711
  }));
7829
7712
  }
7830
7713
  function DiffCommand({ options }) {
7831
- const [tasks, setTasks] = useState16([
7714
+ const [tasks, setTasks] = useState15([
7832
7715
  { id: "config", label: "Loading configuration", status: "pending" },
7833
7716
  { id: "local", label: "Reading local files", status: "pending" },
7834
7717
  { id: "remote", label: "Fetching from IntlPull", status: "pending" },
7835
7718
  { id: "compare", label: "Comparing changes", status: "pending" }
7836
7719
  ]);
7837
- const [result, setResult] = useState16(null);
7838
- const [error, setError] = useState16(null);
7839
- const [done, setDone] = useState16(false);
7720
+ const [result, setResult] = useState15(null);
7721
+ const [error, setError] = useState15(null);
7722
+ const [done, setDone] = useState15(false);
7840
7723
  const updateTask = (id, update) => {
7841
7724
  setTasks(
7842
7725
  (prev) => prev.map((t) => t.id === id ? { ...t, ...update } : t)
7843
7726
  );
7844
7727
  };
7845
- useEffect13(() => {
7728
+ useEffect12(() => {
7846
7729
  async function run() {
7847
7730
  try {
7848
7731
  updateTask("config", { status: "running" });
@@ -7963,12 +7846,12 @@ Or use a project-scoped API key for automatic selection.`
7963
7846
  }
7964
7847
  run();
7965
7848
  }, [options.source, options.target]);
7966
- return /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
7967
- /* @__PURE__ */ jsx29(Header, { compact: true }),
7968
- /* @__PURE__ */ jsx29(TaskList, { tasks, title: "Computing diff" }),
7969
- error && /* @__PURE__ */ jsx29(Alert, { type: "error", title: "Error", children: error }),
7970
- done && result && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", marginTop: 1, children: [
7971
- result.total === 0 ? /* @__PURE__ */ jsx29(Alert, { type: "success", title: "No changes", children: "Local files match IntlPull" }) : /* @__PURE__ */ jsxs27(Alert, { type: "info", title: "Changes detected", children: [
7849
+ return /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", children: [
7850
+ /* @__PURE__ */ jsx28(Header, { compact: true }),
7851
+ /* @__PURE__ */ jsx28(TaskList, { tasks, title: "Computing diff" }),
7852
+ error && /* @__PURE__ */ jsx28(Alert, { type: "error", title: "Error", children: error }),
7853
+ done && result && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7854
+ result.total === 0 ? /* @__PURE__ */ jsx28(Alert, { type: "success", title: "No changes", children: "Local files match IntlPull" }) : /* @__PURE__ */ jsxs26(Alert, { type: "info", title: "Changes detected", children: [
7972
7855
  result.added.length,
7973
7856
  " new locally, ",
7974
7857
  result.removed.length,
@@ -7976,79 +7859,79 @@ Or use a project-scoped API key for automatic selection.`
7976
7859
  result.changed.length,
7977
7860
  " modified"
7978
7861
  ] }),
7979
- result.added.length > 0 && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", marginTop: 1, children: [
7980
- /* @__PURE__ */ jsxs27(Text27, { bold: true, color: colors.success, children: [
7862
+ result.added.length > 0 && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7863
+ /* @__PURE__ */ jsxs26(Text26, { bold: true, color: colors.success, children: [
7981
7864
  "+ New in local (",
7982
7865
  result.added.length,
7983
7866
  "):"
7984
7867
  ] }),
7985
- result.added.slice(0, 5).map((entry, i) => /* @__PURE__ */ jsxs27(Box26, { marginLeft: 2, children: [
7986
- /* @__PURE__ */ jsx29(Text27, { color: colors.success, children: "+ " }),
7987
- /* @__PURE__ */ jsx29(Text27, { color: colors.text, children: entry.key }),
7988
- /* @__PURE__ */ jsxs27(Text27, { color: colors.textDim, children: [
7868
+ result.added.slice(0, 5).map((entry, i) => /* @__PURE__ */ jsxs26(Box25, { marginLeft: 2, children: [
7869
+ /* @__PURE__ */ jsx28(Text26, { color: colors.success, children: "+ " }),
7870
+ /* @__PURE__ */ jsx28(Text26, { color: colors.text, children: entry.key }),
7871
+ /* @__PURE__ */ jsxs26(Text26, { color: colors.textDim, children: [
7989
7872
  ' = "',
7990
7873
  truncate(entry.newValue || "", 40),
7991
7874
  '"'
7992
7875
  ] })
7993
7876
  ] }, i)),
7994
- result.added.length > 5 && /* @__PURE__ */ jsx29(Box26, { marginLeft: 2, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textMuted, children: [
7877
+ result.added.length > 5 && /* @__PURE__ */ jsx28(Box25, { marginLeft: 2, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
7995
7878
  "... and ",
7996
7879
  result.added.length - 5,
7997
7880
  " more"
7998
7881
  ] }) })
7999
7882
  ] }),
8000
- result.removed.length > 0 && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", marginTop: 1, children: [
8001
- /* @__PURE__ */ jsxs27(Text27, { bold: true, color: colors.error, children: [
7883
+ result.removed.length > 0 && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7884
+ /* @__PURE__ */ jsxs26(Text26, { bold: true, color: colors.error, children: [
8002
7885
  "- Missing locally (",
8003
7886
  result.removed.length,
8004
7887
  "):"
8005
7888
  ] }),
8006
- result.removed.slice(0, 5).map((entry, i) => /* @__PURE__ */ jsxs27(Box26, { marginLeft: 2, children: [
8007
- /* @__PURE__ */ jsx29(Text27, { color: colors.error, children: "- " }),
8008
- /* @__PURE__ */ jsx29(Text27, { color: colors.text, children: entry.key })
7889
+ result.removed.slice(0, 5).map((entry, i) => /* @__PURE__ */ jsxs26(Box25, { marginLeft: 2, children: [
7890
+ /* @__PURE__ */ jsx28(Text26, { color: colors.error, children: "- " }),
7891
+ /* @__PURE__ */ jsx28(Text26, { color: colors.text, children: entry.key })
8009
7892
  ] }, i)),
8010
- result.removed.length > 5 && /* @__PURE__ */ jsx29(Box26, { marginLeft: 2, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textMuted, children: [
7893
+ result.removed.length > 5 && /* @__PURE__ */ jsx28(Box25, { marginLeft: 2, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
8011
7894
  "... and ",
8012
7895
  result.removed.length - 5,
8013
7896
  " more"
8014
7897
  ] }) })
8015
7898
  ] }),
8016
- result.changed.length > 0 && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", marginTop: 1, children: [
8017
- /* @__PURE__ */ jsxs27(Text27, { bold: true, color: colors.warning, children: [
7899
+ result.changed.length > 0 && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7900
+ /* @__PURE__ */ jsxs26(Text26, { bold: true, color: colors.warning, children: [
8018
7901
  "~ Modified (",
8019
7902
  result.changed.length,
8020
7903
  "):"
8021
7904
  ] }),
8022
- result.changed.slice(0, 5).map((entry, i) => /* @__PURE__ */ jsxs27(Box26, { marginLeft: 2, flexDirection: "column", children: [
8023
- /* @__PURE__ */ jsxs27(Box26, { children: [
8024
- /* @__PURE__ */ jsx29(Text27, { color: colors.warning, children: "~ " }),
8025
- /* @__PURE__ */ jsx29(Text27, { color: colors.text, children: entry.key })
7905
+ result.changed.slice(0, 5).map((entry, i) => /* @__PURE__ */ jsxs26(Box25, { marginLeft: 2, flexDirection: "column", children: [
7906
+ /* @__PURE__ */ jsxs26(Box25, { children: [
7907
+ /* @__PURE__ */ jsx28(Text26, { color: colors.warning, children: "~ " }),
7908
+ /* @__PURE__ */ jsx28(Text26, { color: colors.text, children: entry.key })
8026
7909
  ] }),
8027
- /* @__PURE__ */ jsx29(Box26, { marginLeft: 4, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textDim, children: [
7910
+ /* @__PURE__ */ jsx28(Box25, { marginLeft: 4, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textDim, children: [
8028
7911
  'remote: "',
8029
7912
  truncate(entry.oldValue || "", 30),
8030
7913
  '"'
8031
7914
  ] }) }),
8032
- /* @__PURE__ */ jsx29(Box26, { marginLeft: 4, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textDim, children: [
7915
+ /* @__PURE__ */ jsx28(Box25, { marginLeft: 4, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textDim, children: [
8033
7916
  'local: "',
8034
7917
  truncate(entry.newValue || "", 30),
8035
7918
  '"'
8036
7919
  ] }) })
8037
7920
  ] }, i)),
8038
- result.changed.length > 5 && /* @__PURE__ */ jsx29(Box26, { marginLeft: 2, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textMuted, children: [
7921
+ result.changed.length > 5 && /* @__PURE__ */ jsx28(Box25, { marginLeft: 2, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
8039
7922
  "... and ",
8040
7923
  result.changed.length - 5,
8041
7924
  " more"
8042
7925
  ] }) })
8043
7926
  ] }),
8044
- result.added.length > 0 && /* @__PURE__ */ jsx29(Box26, { marginTop: 1, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textMuted, children: [
7927
+ result.added.length > 0 && /* @__PURE__ */ jsx28(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
8045
7928
  "Run ",
8046
- /* @__PURE__ */ jsx29(Text27, { color: colors.primary, children: "npx @intlpullhq/cli upload" }),
7929
+ /* @__PURE__ */ jsx28(Text26, { color: colors.primary, children: "npx @intlpullhq/cli upload" }),
8047
7930
  " to upload new keys to IntlPull"
8048
7931
  ] }) }),
8049
- result.removed.length > 0 && /* @__PURE__ */ jsx29(Box26, { marginTop: 1, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textMuted, children: [
7932
+ result.removed.length > 0 && /* @__PURE__ */ jsx28(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
8050
7933
  "Run ",
8051
- /* @__PURE__ */ jsx29(Text27, { color: colors.primary, children: "npx @intlpullhq/cli download" }),
7934
+ /* @__PURE__ */ jsx28(Text26, { color: colors.primary, children: "npx @intlpullhq/cli download" }),
8052
7935
  " to download missing keys from IntlPull"
8053
7936
  ] }) })
8054
7937
  ] })
@@ -8068,30 +7951,30 @@ function truncate(str, length) {
8068
7951
  return str.length > length ? str.substring(0, length) + "..." : str;
8069
7952
  }
8070
7953
  function runDiff(options) {
8071
- render12(/* @__PURE__ */ jsx29(DiffCommand, { options }));
7954
+ render11(/* @__PURE__ */ jsx28(DiffCommand, { options }));
8072
7955
  }
8073
7956
 
8074
7957
  // src/commands/fix.tsx
8075
- import { useState as useState17, useEffect as useEffect14 } from "react";
8076
- import { render as render13, Box as Box27, Text as Text28 } from "ink";
8077
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
7958
+ import { useState as useState16, useEffect as useEffect13 } from "react";
7959
+ import { render as render12, Box as Box26, Text as Text27 } from "ink";
7960
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
8078
7961
  import { glob as glob2 } from "glob";
8079
- import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
7962
+ import { jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
8080
7963
  function FixCommand({ options }) {
8081
- const [tasks, setTasks] = useState17([
7964
+ const [tasks, setTasks] = useState16([
8082
7965
  { id: "config", label: "Loading configuration", status: "pending" },
8083
7966
  { id: "scan", label: "Scanning for missing keys", status: "pending" },
8084
7967
  { id: "fix", label: "Adding missing keys", status: "pending" }
8085
7968
  ]);
8086
- const [result, setResult] = useState17(null);
8087
- const [error, setError] = useState17(null);
8088
- const [done, setDone] = useState17(false);
7969
+ const [result, setResult] = useState16(null);
7970
+ const [error, setError] = useState16(null);
7971
+ const [done, setDone] = useState16(false);
8089
7972
  const updateTask = (id, update) => {
8090
7973
  setTasks(
8091
7974
  (prev) => prev.map((t) => t.id === id ? { ...t, ...update } : t)
8092
7975
  );
8093
7976
  };
8094
- useEffect14(() => {
7977
+ useEffect13(() => {
8095
7978
  async function run() {
8096
7979
  try {
8097
7980
  updateTask("config", { status: "running" });
@@ -8110,7 +7993,8 @@ function FixCommand({ options }) {
8110
7993
  `${localeDir}/**/common.json`,
8111
7994
  `${localeDir}/**/*.json`,
8112
7995
  `${localeDir}/*.json`,
8113
- `./messages/*.json`
7996
+ `./messages/*.json`,
7997
+ `./locales/**/*.json`
8114
7998
  ];
8115
7999
  let localeFiles = [];
8116
8000
  for (const pattern of patterns) {
@@ -8123,15 +8007,16 @@ function FixCommand({ options }) {
8123
8007
  if (localeFiles.length === 0) {
8124
8008
  throw new Error(`No translation files found in ${localeDir}`);
8125
8009
  }
8010
+ const LANG_CODE_REGEX2 = /^[a-z]{2}([-_][a-zA-Z]{2})?$/i;
8126
8011
  const filesByLang = {};
8127
8012
  for (const file of localeFiles) {
8128
8013
  const parts = file.split("/");
8129
8014
  const fileName = parts[parts.length - 1];
8130
8015
  const dirName = parts[parts.length - 2];
8131
- if (/^[a-z]{2}(-[A-Z]{2})?$/.test(dirName)) {
8016
+ if (LANG_CODE_REGEX2.test(dirName)) {
8132
8017
  if (!filesByLang[dirName]) filesByLang[dirName] = [];
8133
8018
  filesByLang[dirName].push(file);
8134
- } else if (/^[a-z]{2}(-[A-Z]{2})?\.json$/.test(fileName)) {
8019
+ } else if (LANG_CODE_REGEX2.test(fileName.replace(".json", ""))) {
8135
8020
  const lang = fileName.replace(".json", "");
8136
8021
  if (!filesByLang[lang]) filesByLang[lang] = [];
8137
8022
  filesByLang[lang].push(file);
@@ -8180,13 +8065,13 @@ ${parseErrors.join("\n")}`);
8180
8065
  for (const key of sourceKeys) {
8181
8066
  if (!(key in flatTarget)) {
8182
8067
  const sourceValue = flatSource[key] || "";
8183
- setNestedValue(content, key, `[${targetLang.toUpperCase()}] ${sourceValue}`);
8068
+ setNestedValue2(content, key, `[${targetLang.toUpperCase()}] ${sourceValue}`);
8184
8069
  keysAdded++;
8185
8070
  modified = true;
8186
8071
  }
8187
8072
  }
8188
8073
  if (modified && !options.dryRun) {
8189
- writeFileSync4(file, JSON.stringify(content, null, 2) + "\n");
8074
+ writeFileSync5(file, JSON.stringify(content, null, 2) + "\n");
8190
8075
  filesModified++;
8191
8076
  } else if (modified) {
8192
8077
  filesModified++;
@@ -8210,44 +8095,44 @@ ${parseErrors.join("\n")}`);
8210
8095
  }
8211
8096
  run();
8212
8097
  }, [options]);
8213
- return /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", children: [
8214
- /* @__PURE__ */ jsx30(Header, { compact: true }),
8215
- /* @__PURE__ */ jsx30(TaskList, { tasks, title: "Fixing missing translations" }),
8216
- error && /* @__PURE__ */ jsx30(Alert, { type: "error", title: "Error", children: error }),
8217
- done && result && /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", marginTop: 1, children: [
8218
- result.keysAdded === 0 ? /* @__PURE__ */ jsx30(Alert, { type: "success", title: "No fixes needed", children: "All translations are complete" }) : options.dryRun ? /* @__PURE__ */ jsxs28(Alert, { type: "warning", title: "Dry run complete", children: [
8098
+ return /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", children: [
8099
+ /* @__PURE__ */ jsx29(Header, { compact: true }),
8100
+ /* @__PURE__ */ jsx29(TaskList, { tasks, title: "Fixing missing translations" }),
8101
+ error && /* @__PURE__ */ jsx29(Alert, { type: "error", title: "Error", children: error }),
8102
+ done && result && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", marginTop: 1, children: [
8103
+ result.keysAdded === 0 ? /* @__PURE__ */ jsx29(Alert, { type: "success", title: "No fixes needed", children: "All translations are complete" }) : options.dryRun ? /* @__PURE__ */ jsxs27(Alert, { type: "warning", title: "Dry run complete", children: [
8219
8104
  "Would add ",
8220
8105
  result.keysAdded,
8221
8106
  " missing key(s) to ",
8222
8107
  result.filesModified,
8223
8108
  " file(s)"
8224
- ] }) : /* @__PURE__ */ jsxs28(Alert, { type: "success", title: "Fix complete", children: [
8109
+ ] }) : /* @__PURE__ */ jsxs27(Alert, { type: "success", title: "Fix complete", children: [
8225
8110
  "Added ",
8226
8111
  result.keysAdded,
8227
8112
  " missing key(s) to ",
8228
8113
  result.filesModified,
8229
8114
  " file(s)"
8230
8115
  ] }),
8231
- result.keysAdded > 0 && /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", marginTop: 1, children: [
8232
- /* @__PURE__ */ jsx30(Text28, { bold: true, color: colors.text, children: "Languages fixed:" }),
8233
- result.languages.map((lang) => /* @__PURE__ */ jsxs28(Box27, { marginLeft: 2, children: [
8234
- /* @__PURE__ */ jsxs28(Text28, { color: colors.success, children: [
8116
+ result.keysAdded > 0 && /* @__PURE__ */ jsxs27(Box26, { flexDirection: "column", marginTop: 1, children: [
8117
+ /* @__PURE__ */ jsx29(Text27, { bold: true, color: colors.text, children: "Languages fixed:" }),
8118
+ result.languages.map((lang) => /* @__PURE__ */ jsxs27(Box26, { marginLeft: 2, children: [
8119
+ /* @__PURE__ */ jsxs27(Text27, { color: colors.success, children: [
8235
8120
  icons.success,
8236
8121
  " "
8237
8122
  ] }),
8238
- /* @__PURE__ */ jsx30(Text28, { color: colors.text, children: lang })
8123
+ /* @__PURE__ */ jsx29(Text27, { color: colors.text, children: lang })
8239
8124
  ] }, lang))
8240
8125
  ] }),
8241
- result.keysAdded > 0 && !options.dryRun && /* @__PURE__ */ jsxs28(Box27, { marginTop: 1, children: [
8242
- /* @__PURE__ */ jsxs28(Text28, { color: colors.warning, children: [
8126
+ result.keysAdded > 0 && !options.dryRun && /* @__PURE__ */ jsxs27(Box26, { marginTop: 1, children: [
8127
+ /* @__PURE__ */ jsxs27(Text27, { color: colors.warning, children: [
8243
8128
  icons.warning,
8244
8129
  " "
8245
8130
  ] }),
8246
- /* @__PURE__ */ jsx30(Text28, { color: colors.textMuted, children: "Missing keys have been added with [LANG] prefix. Review and translate them." })
8131
+ /* @__PURE__ */ jsx29(Text27, { color: colors.textMuted, children: "Missing keys have been added with [LANG] prefix. Review and translate them." })
8247
8132
  ] }),
8248
- options.dryRun && result.keysAdded > 0 && /* @__PURE__ */ jsx30(Box27, { marginTop: 1, children: /* @__PURE__ */ jsxs28(Text28, { color: colors.textMuted, children: [
8133
+ options.dryRun && result.keysAdded > 0 && /* @__PURE__ */ jsx29(Box26, { marginTop: 1, children: /* @__PURE__ */ jsxs27(Text27, { color: colors.textMuted, children: [
8249
8134
  "Run without ",
8250
- /* @__PURE__ */ jsx30(Text28, { color: colors.primary, children: "--dry-run" }),
8135
+ /* @__PURE__ */ jsx29(Text27, { color: colors.primary, children: "--dry-run" }),
8251
8136
  " to apply fixes"
8252
8137
  ] }) })
8253
8138
  ] })
@@ -8265,7 +8150,7 @@ function flattenObject4(obj, prefix = "") {
8265
8150
  }
8266
8151
  return result;
8267
8152
  }
8268
- function setNestedValue(obj, key, value) {
8153
+ function setNestedValue2(obj, key, value) {
8269
8154
  const parts = key.split(".");
8270
8155
  let current = obj;
8271
8156
  for (let i = 0; i < parts.length - 1; i++) {
@@ -8277,16 +8162,16 @@ function setNestedValue(obj, key, value) {
8277
8162
  current[parts[parts.length - 1]] = value;
8278
8163
  }
8279
8164
  function runFix(options) {
8280
- render13(/* @__PURE__ */ jsx30(FixCommand, { options }));
8165
+ render12(/* @__PURE__ */ jsx29(FixCommand, { options }));
8281
8166
  }
8282
8167
 
8283
8168
  // src/commands/listen.tsx
8284
- import { useState as useState18, useEffect as useEffect15, useCallback, useRef as useRef2 } from "react";
8285
- import { render as render14, Box as Box28, Text as Text29, useApp as useApp6, useInput as useInput7 } from "ink";
8286
- import Spinner13 from "ink-spinner";
8287
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3, existsSync as existsSync9 } from "fs";
8288
- import { join as join7 } from "path";
8289
- import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
8169
+ import { useState as useState17, useEffect as useEffect14, useCallback, useRef as useRef2 } from "react";
8170
+ import { render as render13, Box as Box27, Text as Text28, useApp as useApp6, useInput as useInput7 } from "ink";
8171
+ import Spinner12 from "ink-spinner";
8172
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
8173
+ import { join as join8 } from "path";
8174
+ import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
8290
8175
  async function fetchProjects6(apiUrl, apiKey) {
8291
8176
  const response = await fetch(`${apiUrl}/api/v1/projects`, {
8292
8177
  headers: { Accept: "application/json", "X-API-Key": apiKey }
@@ -8423,9 +8308,9 @@ function writeTranslationFiles(bundle, outputDir, format, languages) {
8423
8308
  const targetLanguages = languages || Object.keys(bundle);
8424
8309
  for (const locale of targetLanguages) {
8425
8310
  if (!bundle[locale]) continue;
8426
- const localeDir = join7(outputDir, locale);
8311
+ const localeDir = join8(outputDir, locale);
8427
8312
  if (!existsSync9(localeDir)) {
8428
- mkdirSync3(localeDir, { recursive: true });
8313
+ mkdirSync4(localeDir, { recursive: true });
8429
8314
  }
8430
8315
  let content;
8431
8316
  let filename;
@@ -8445,8 +8330,8 @@ function writeTranslationFiles(bundle, outputDir, format, languages) {
8445
8330
  filename = "translations.json";
8446
8331
  break;
8447
8332
  }
8448
- const filePath = join7(localeDir, filename);
8449
- writeFileSync5(filePath, content);
8333
+ const filePath = join8(localeDir, filename);
8334
+ writeFileSync6(filePath, content);
8450
8335
  writtenFiles.push(filePath);
8451
8336
  }
8452
8337
  return writtenFiles;
@@ -8468,7 +8353,7 @@ function formatRelativeTime(date) {
8468
8353
  }
8469
8354
  function ListenComponent({ options }) {
8470
8355
  const { exit } = useApp6();
8471
- const [state, setState] = useState18({
8356
+ const [state, setState] = useState17({
8472
8357
  status: "connecting",
8473
8358
  message: "Connecting to IntlPull...",
8474
8359
  lastSync: null,
@@ -8592,7 +8477,7 @@ function ListenComponent({ options }) {
8592
8477
  }));
8593
8478
  }
8594
8479
  }, [options, state.version, state.syncCount]);
8595
- useEffect15(() => {
8480
+ useEffect14(() => {
8596
8481
  sync();
8597
8482
  if (options.once) {
8598
8483
  return;
@@ -8604,7 +8489,7 @@ function ListenComponent({ options }) {
8604
8489
  }, intervalMs);
8605
8490
  return () => clearInterval(interval);
8606
8491
  }, [sync, intervalMs, options.once]);
8607
- useEffect15(() => {
8492
+ useEffect14(() => {
8608
8493
  if (!options.timeout || options.once) return;
8609
8494
  const timeoutMs = options.timeout * 1e3;
8610
8495
  const timer = setTimeout(() => {
@@ -8617,7 +8502,7 @@ function ListenComponent({ options }) {
8617
8502
  }, timeoutMs);
8618
8503
  return () => clearTimeout(timer);
8619
8504
  }, [options.timeout, options.once, exit]);
8620
- useEffect15(() => {
8505
+ useEffect14(() => {
8621
8506
  if (options.once && state.status === "watching") {
8622
8507
  setTimeout(() => exit(), 100);
8623
8508
  }
@@ -8636,57 +8521,57 @@ function ListenComponent({ options }) {
8636
8521
  error: "\u2717",
8637
8522
  stopped: "\u25FB"
8638
8523
  }[state.status];
8639
- return /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", paddingX: 1, children: [
8640
- /* @__PURE__ */ jsxs29(Box28, { marginBottom: 1, children: [
8641
- /* @__PURE__ */ jsx31(Text29, { bold: true, color: "cyan", children: "IntlPull" }),
8642
- /* @__PURE__ */ jsx31(Text29, { children: " \u2022 Live Sync" }),
8643
- isInteractive && /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: " (press q to quit)" })
8524
+ return /* @__PURE__ */ jsxs28(Box27, { flexDirection: "column", paddingX: 1, children: [
8525
+ /* @__PURE__ */ jsxs28(Box27, { marginBottom: 1, children: [
8526
+ /* @__PURE__ */ jsx30(Text28, { bold: true, color: "cyan", children: "IntlPull" }),
8527
+ /* @__PURE__ */ jsx30(Text28, { children: " \u2022 Live Sync" }),
8528
+ isInteractive && /* @__PURE__ */ jsx30(Text28, { dimColor: true, children: " (press q to quit)" })
8644
8529
  ] }),
8645
- /* @__PURE__ */ jsxs29(Box28, { children: [
8646
- state.status === "syncing" ? /* @__PURE__ */ jsx31(Text29, { color: "cyan", children: /* @__PURE__ */ jsx31(Spinner13, { type: "dots" }) }) : /* @__PURE__ */ jsx31(Text29, { color: statusColor, children: statusIcon }),
8647
- /* @__PURE__ */ jsx31(Text29, { children: " " }),
8648
- /* @__PURE__ */ jsx31(Text29, { color: statusColor, children: state.message })
8530
+ /* @__PURE__ */ jsxs28(Box27, { children: [
8531
+ state.status === "syncing" ? /* @__PURE__ */ jsx30(Text28, { color: "cyan", children: /* @__PURE__ */ jsx30(Spinner12, { type: "dots" }) }) : /* @__PURE__ */ jsx30(Text28, { color: statusColor, children: statusIcon }),
8532
+ /* @__PURE__ */ jsx30(Text28, { children: " " }),
8533
+ /* @__PURE__ */ jsx30(Text28, { color: statusColor, children: state.message })
8649
8534
  ] }),
8650
- state.error && /* @__PURE__ */ jsxs29(Box28, { marginTop: 1, flexDirection: "column", children: [
8651
- /* @__PURE__ */ jsx31(Text29, { color: "red", children: state.error }),
8652
- /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
8535
+ state.error && /* @__PURE__ */ jsxs28(Box27, { marginTop: 1, flexDirection: "column", children: [
8536
+ /* @__PURE__ */ jsx30(Text28, { color: "red", children: state.error }),
8537
+ /* @__PURE__ */ jsxs28(Text28, { dimColor: true, children: [
8653
8538
  "Retrying in ",
8654
8539
  intervalMs / 1e3,
8655
8540
  "s..."
8656
8541
  ] })
8657
8542
  ] }),
8658
- state.lastSync && !options.quiet && /* @__PURE__ */ jsxs29(Box28, { marginTop: 1, flexDirection: "column", children: [
8659
- /* @__PURE__ */ jsxs29(Box28, { children: [
8660
- /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Output: " }),
8661
- /* @__PURE__ */ jsx31(Text29, { children: state.outputDir })
8543
+ state.lastSync && !options.quiet && /* @__PURE__ */ jsxs28(Box27, { marginTop: 1, flexDirection: "column", children: [
8544
+ /* @__PURE__ */ jsxs28(Box27, { children: [
8545
+ /* @__PURE__ */ jsx30(Text28, { dimColor: true, children: "Output: " }),
8546
+ /* @__PURE__ */ jsx30(Text28, { children: state.outputDir })
8662
8547
  ] }),
8663
- /* @__PURE__ */ jsxs29(Box28, { children: [
8664
- /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Version: " }),
8665
- /* @__PURE__ */ jsx31(Text29, { children: state.version })
8548
+ /* @__PURE__ */ jsxs28(Box27, { children: [
8549
+ /* @__PURE__ */ jsx30(Text28, { dimColor: true, children: "Version: " }),
8550
+ /* @__PURE__ */ jsx30(Text28, { children: state.version })
8666
8551
  ] }),
8667
- /* @__PURE__ */ jsxs29(Box28, { children: [
8668
- /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Languages: " }),
8669
- /* @__PURE__ */ jsx31(Text29, { children: state.languages.join(", ") })
8552
+ /* @__PURE__ */ jsxs28(Box27, { children: [
8553
+ /* @__PURE__ */ jsx30(Text28, { dimColor: true, children: "Languages: " }),
8554
+ /* @__PURE__ */ jsx30(Text28, { children: state.languages.join(", ") })
8670
8555
  ] }),
8671
- /* @__PURE__ */ jsxs29(Box28, { children: [
8672
- /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Keys: " }),
8673
- /* @__PURE__ */ jsx31(Text29, { children: state.keyCount })
8556
+ /* @__PURE__ */ jsxs28(Box27, { children: [
8557
+ /* @__PURE__ */ jsx30(Text28, { dimColor: true, children: "Keys: " }),
8558
+ /* @__PURE__ */ jsx30(Text28, { children: state.keyCount })
8674
8559
  ] }),
8675
- /* @__PURE__ */ jsxs29(Box28, { children: [
8676
- /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Last sync: " }),
8677
- /* @__PURE__ */ jsxs29(Text29, { children: [
8560
+ /* @__PURE__ */ jsxs28(Box27, { children: [
8561
+ /* @__PURE__ */ jsx30(Text28, { dimColor: true, children: "Last sync: " }),
8562
+ /* @__PURE__ */ jsxs28(Text28, { children: [
8678
8563
  formatTime(state.lastSync),
8679
8564
  " (",
8680
8565
  formatRelativeTime(state.lastSync),
8681
8566
  ")"
8682
8567
  ] })
8683
8568
  ] }),
8684
- /* @__PURE__ */ jsxs29(Box28, { children: [
8685
- /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Syncs: " }),
8686
- /* @__PURE__ */ jsx31(Text29, { children: state.syncCount })
8569
+ /* @__PURE__ */ jsxs28(Box27, { children: [
8570
+ /* @__PURE__ */ jsx30(Text28, { dimColor: true, children: "Syncs: " }),
8571
+ /* @__PURE__ */ jsx30(Text28, { children: state.syncCount })
8687
8572
  ] })
8688
8573
  ] }),
8689
- state.status === "watching" && !options.once && !options.quiet && /* @__PURE__ */ jsx31(Box28, { marginTop: 1, children: /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
8574
+ state.status === "watching" && !options.once && !options.quiet && /* @__PURE__ */ jsx30(Box27, { marginTop: 1, children: /* @__PURE__ */ jsxs28(Text28, { dimColor: true, children: [
8690
8575
  "Polling every ",
8691
8576
  intervalMs / 1e3,
8692
8577
  "s for changes..."
@@ -8809,14 +8694,14 @@ function runListen(options) {
8809
8694
  runOnceSync(options);
8810
8695
  return;
8811
8696
  }
8812
- render14(/* @__PURE__ */ jsx31(ListenComponent, { options }));
8697
+ render13(/* @__PURE__ */ jsx30(ListenComponent, { options }));
8813
8698
  }
8814
8699
 
8815
8700
  // src/commands/publish.tsx
8816
- import { useState as useState19, useEffect as useEffect16 } from "react";
8817
- import { render as render15, Box as Box29, Text as Text30 } from "ink";
8818
- import Spinner14 from "ink-spinner";
8819
- import { jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
8701
+ import { useState as useState18, useEffect as useEffect15 } from "react";
8702
+ import { render as render14, Box as Box28, Text as Text29 } from "ink";
8703
+ import Spinner13 from "ink-spinner";
8704
+ import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
8820
8705
  async function fetchProjects7(apiUrl, apiKey) {
8821
8706
  const response = await fetch(`${apiUrl}/api/v1/projects`, {
8822
8707
  headers: { Accept: "application/json", "X-API-Key": apiKey }
@@ -8986,11 +8871,11 @@ function generateVersion() {
8986
8871
  return `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}-${now.getHours()}${now.getMinutes()}`;
8987
8872
  }
8988
8873
  function PublishComponent({ options }) {
8989
- const [state, setState] = useState19({
8874
+ const [state, setState] = useState18({
8990
8875
  status: "detecting",
8991
8876
  message: "Detecting project..."
8992
8877
  });
8993
- useEffect16(() => {
8878
+ useEffect15(() => {
8994
8879
  async function run() {
8995
8880
  try {
8996
8881
  const resolved = getResolvedApiKey();
@@ -9074,96 +8959,96 @@ function PublishComponent({ options }) {
9074
8959
  }
9075
8960
  return null;
9076
8961
  }
9077
- return /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", paddingX: 1, children: [
9078
- /* @__PURE__ */ jsxs30(Box29, { marginBottom: 1, children: [
9079
- /* @__PURE__ */ jsx32(Text30, { bold: true, color: "cyan", children: "IntlPull" }),
9080
- /* @__PURE__ */ jsx32(Text30, { children: " Publish OTA Release" }),
9081
- options.dryRun && /* @__PURE__ */ jsx32(Text30, { color: "yellow", children: " (dry run)" })
8962
+ return /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", paddingX: 1, children: [
8963
+ /* @__PURE__ */ jsxs29(Box28, { marginBottom: 1, children: [
8964
+ /* @__PURE__ */ jsx31(Text29, { bold: true, color: "cyan", children: "IntlPull" }),
8965
+ /* @__PURE__ */ jsx31(Text29, { children: " Publish OTA Release" }),
8966
+ options.dryRun && /* @__PURE__ */ jsx31(Text29, { color: "yellow", children: " (dry run)" })
9082
8967
  ] }),
9083
- (state.status === "detecting" || state.status === "publishing") && /* @__PURE__ */ jsxs30(Box29, { children: [
9084
- /* @__PURE__ */ jsx32(Text30, { color: "cyan", children: /* @__PURE__ */ jsx32(Spinner14, { type: "dots" }) }),
9085
- /* @__PURE__ */ jsxs30(Text30, { children: [
8968
+ (state.status === "detecting" || state.status === "publishing") && /* @__PURE__ */ jsxs29(Box28, { children: [
8969
+ /* @__PURE__ */ jsx31(Text29, { color: "cyan", children: /* @__PURE__ */ jsx31(Spinner13, { type: "dots" }) }),
8970
+ /* @__PURE__ */ jsxs29(Text29, { children: [
9086
8971
  " ",
9087
8972
  state.message
9088
8973
  ] })
9089
8974
  ] }),
9090
- state.status === "success" && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", children: [
9091
- /* @__PURE__ */ jsxs30(Text30, { color: "green", children: [
8975
+ state.status === "success" && /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", children: [
8976
+ /* @__PURE__ */ jsxs29(Text29, { color: "green", children: [
9092
8977
  "\u2713 ",
9093
8978
  state.message
9094
8979
  ] }),
9095
- state.projectName && /* @__PURE__ */ jsx32(Box29, { marginTop: 1, children: /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
8980
+ state.projectName && /* @__PURE__ */ jsx31(Box28, { marginTop: 1, children: /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9096
8981
  "Project: ",
9097
- /* @__PURE__ */ jsx32(Text30, { color: "white", children: state.projectName })
8982
+ /* @__PURE__ */ jsx31(Text29, { color: "white", children: state.projectName })
9098
8983
  ] }) }),
9099
- state.branch && /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
8984
+ state.branch && /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9100
8985
  "Branch: ",
9101
- /* @__PURE__ */ jsx32(Text30, { color: "white", children: state.branch })
8986
+ /* @__PURE__ */ jsx31(Text29, { color: "white", children: state.branch })
9102
8987
  ] }),
9103
- state.release && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: 1, children: [
9104
- /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
8988
+ state.release && /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginTop: 1, children: [
8989
+ /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9105
8990
  "Version: ",
9106
- /* @__PURE__ */ jsx32(Text30, { color: "green", bold: true, children: state.release.version })
8991
+ /* @__PURE__ */ jsx31(Text29, { color: "green", bold: true, children: state.release.version })
9107
8992
  ] }),
9108
- /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
8993
+ /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9109
8994
  "Languages: ",
9110
8995
  state.release.languages.join(", ")
9111
8996
  ] }),
9112
- /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
8997
+ /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9113
8998
  "Keys: ",
9114
8999
  state.release.keyCount
9115
9000
  ] }),
9116
- /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
9001
+ /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9117
9002
  "Bundle size: ",
9118
9003
  formatBytes(state.release.bundleSize)
9119
9004
  ] }),
9120
- /* @__PURE__ */ jsxs30(Box29, { marginTop: 1, flexDirection: "column", children: [
9121
- /* @__PURE__ */ jsx32(Text30, { dimColor: true, children: "SDK Manifest URL:" }),
9122
- /* @__PURE__ */ jsxs30(Text30, { color: "cyan", children: [
9005
+ /* @__PURE__ */ jsxs29(Box28, { marginTop: 1, flexDirection: "column", children: [
9006
+ /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "SDK Manifest URL:" }),
9007
+ /* @__PURE__ */ jsxs29(Text29, { color: "cyan", children: [
9123
9008
  " ",
9124
9009
  state.release.bundleUrl.replace(/\/[^/]+\.json$/, "/manifest")
9125
9010
  ] })
9126
9011
  ] })
9127
9012
  ] }),
9128
- !state.release && state.summary && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: 1, children: [
9129
- /* @__PURE__ */ jsx32(Text30, { dimColor: true, children: "Would publish:" }),
9130
- /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
9013
+ !state.release && state.summary && /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginTop: 1, children: [
9014
+ /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Would publish:" }),
9015
+ /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9131
9016
  " Languages: ",
9132
9017
  state.summary.languages.join(", ")
9133
9018
  ] }),
9134
- /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
9019
+ /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9135
9020
  " Keys: ",
9136
9021
  state.summary.keyCount
9137
9022
  ] }),
9138
- /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
9023
+ /* @__PURE__ */ jsxs29(Text29, { dimColor: true, children: [
9139
9024
  " Estimated size: ",
9140
9025
  formatBytes(state.summary.estimatedSize)
9141
9026
  ] })
9142
9027
  ] })
9143
9028
  ] }),
9144
- state.status === "error" && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", children: [
9145
- /* @__PURE__ */ jsxs30(Text30, { color: "red", children: [
9029
+ state.status === "error" && /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", children: [
9030
+ /* @__PURE__ */ jsxs29(Text29, { color: "red", children: [
9146
9031
  "\u2717 ",
9147
9032
  state.message
9148
9033
  ] }),
9149
- /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: 1, children: [
9150
- /* @__PURE__ */ jsx32(Text30, { dimColor: true, children: "Tips:" }),
9151
- /* @__PURE__ */ jsx32(Text30, { color: "gray", children: " \u2022 Run `npx @intlpullhq/cli upload` to upload translations first" }),
9152
- /* @__PURE__ */ jsx32(Text30, { color: "gray", children: " \u2022 Use `npx @intlpullhq/cli publish 1.0.0` to specify a version" }),
9153
- /* @__PURE__ */ jsx32(Text30, { color: "gray", children: " \u2022 Use `npx @intlpullhq/cli publish --dry-run` to preview" })
9034
+ /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", marginTop: 1, children: [
9035
+ /* @__PURE__ */ jsx31(Text29, { dimColor: true, children: "Tips:" }),
9036
+ /* @__PURE__ */ jsx31(Text29, { color: "gray", children: " \u2022 Run `npx @intlpullhq/cli upload` to upload translations first" }),
9037
+ /* @__PURE__ */ jsx31(Text29, { color: "gray", children: " \u2022 Use `npx @intlpullhq/cli publish 1.0.0` to specify a version" }),
9038
+ /* @__PURE__ */ jsx31(Text29, { color: "gray", children: " \u2022 Use `npx @intlpullhq/cli publish --dry-run` to preview" })
9154
9039
  ] })
9155
9040
  ] })
9156
9041
  ] });
9157
9042
  }
9158
9043
  function runPublish(options) {
9159
- render15(/* @__PURE__ */ jsx32(PublishComponent, { options }));
9044
+ render14(/* @__PURE__ */ jsx31(PublishComponent, { options }));
9160
9045
  }
9161
9046
 
9162
9047
  // src/commands/releases.tsx
9163
- import { useState as useState20, useEffect as useEffect17 } from "react";
9164
- import { render as render16, Box as Box30, Text as Text31 } from "ink";
9165
- import Spinner15 from "ink-spinner";
9166
- import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
9048
+ import { useState as useState19, useEffect as useEffect16 } from "react";
9049
+ import { render as render15, Box as Box29, Text as Text30 } from "ink";
9050
+ import Spinner14 from "ink-spinner";
9051
+ import { jsx as jsx32, jsxs as jsxs30 } from "react/jsx-runtime";
9167
9052
  async function fetchProjects8(apiUrl, apiKey) {
9168
9053
  const response = await fetch(`${apiUrl}/api/v1/projects`, {
9169
9054
  headers: { Accept: "application/json", "X-API-Key": apiKey }
@@ -9237,11 +9122,11 @@ function formatDate(dateStr) {
9237
9122
  });
9238
9123
  }
9239
9124
  function ReleasesComponent({ options }) {
9240
- const [state, setState] = useState20({
9125
+ const [state, setState] = useState19({
9241
9126
  status: "loading",
9242
9127
  message: "Loading releases..."
9243
9128
  });
9244
- useEffect17(() => {
9129
+ useEffect16(() => {
9245
9130
  async function run() {
9246
9131
  try {
9247
9132
  const resolved = getResolvedApiKey();
@@ -9306,41 +9191,41 @@ function ReleasesComponent({ options }) {
9306
9191
  }
9307
9192
  return null;
9308
9193
  }
9309
- return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", paddingX: 1, children: [
9310
- /* @__PURE__ */ jsxs31(Box30, { marginBottom: 1, children: [
9311
- /* @__PURE__ */ jsx33(Text31, { bold: true, color: "cyan", children: "IntlPull" }),
9312
- /* @__PURE__ */ jsx33(Text31, { children: " OTA Releases" })
9194
+ return /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", paddingX: 1, children: [
9195
+ /* @__PURE__ */ jsxs30(Box29, { marginBottom: 1, children: [
9196
+ /* @__PURE__ */ jsx32(Text30, { bold: true, color: "cyan", children: "IntlPull" }),
9197
+ /* @__PURE__ */ jsx32(Text30, { children: " OTA Releases" })
9313
9198
  ] }),
9314
- state.status === "loading" && /* @__PURE__ */ jsxs31(Box30, { children: [
9315
- /* @__PURE__ */ jsx33(Text31, { color: "cyan", children: /* @__PURE__ */ jsx33(Spinner15, { type: "dots" }) }),
9316
- /* @__PURE__ */ jsxs31(Text31, { children: [
9199
+ state.status === "loading" && /* @__PURE__ */ jsxs30(Box29, { children: [
9200
+ /* @__PURE__ */ jsx32(Text30, { color: "cyan", children: /* @__PURE__ */ jsx32(Spinner14, { type: "dots" }) }),
9201
+ /* @__PURE__ */ jsxs30(Text30, { children: [
9317
9202
  " ",
9318
9203
  state.message
9319
9204
  ] })
9320
9205
  ] }),
9321
- state.status === "success" && state.deleted && /* @__PURE__ */ jsxs31(Text31, { color: "green", children: [
9206
+ state.status === "success" && state.deleted && /* @__PURE__ */ jsxs30(Text30, { color: "green", children: [
9322
9207
  "\u2713 ",
9323
9208
  state.message
9324
9209
  ] }),
9325
- state.status === "success" && state.releases && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9326
- state.projectName && /* @__PURE__ */ jsx33(Box30, { marginBottom: 1, children: /* @__PURE__ */ jsxs31(Text31, { dimColor: true, children: [
9210
+ state.status === "success" && state.releases && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", children: [
9211
+ state.projectName && /* @__PURE__ */ jsx32(Box29, { marginBottom: 1, children: /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
9327
9212
  "Project: ",
9328
- /* @__PURE__ */ jsx33(Text31, { color: "white", children: state.projectName })
9213
+ /* @__PURE__ */ jsx32(Text30, { color: "white", children: state.projectName })
9329
9214
  ] }) }),
9330
- state.releases.length === 0 ? /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9331
- /* @__PURE__ */ jsx33(Text31, { color: "yellow", children: "No releases found" }),
9332
- /* @__PURE__ */ jsx33(Box30, { marginTop: 1, children: /* @__PURE__ */ jsx33(Text31, { dimColor: true, children: "Run `npx @intlpullhq/cli publish [version]` to create your first OTA release." }) })
9333
- ] }) : /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9334
- state.releases.map((release, i) => /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginBottom: i < state.releases.length - 1 ? 1 : 0, children: [
9335
- /* @__PURE__ */ jsxs31(Box30, { children: [
9336
- /* @__PURE__ */ jsx33(Text31, { color: "green", bold: true, children: release.version }),
9337
- /* @__PURE__ */ jsxs31(Text31, { dimColor: true, children: [
9215
+ state.releases.length === 0 ? /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", children: [
9216
+ /* @__PURE__ */ jsx32(Text30, { color: "yellow", children: "No releases found" }),
9217
+ /* @__PURE__ */ jsx32(Box29, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text30, { dimColor: true, children: "Run `npx @intlpullhq/cli publish [version]` to create your first OTA release." }) })
9218
+ ] }) : /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", children: [
9219
+ state.releases.map((release, i) => /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginBottom: i < state.releases.length - 1 ? 1 : 0, children: [
9220
+ /* @__PURE__ */ jsxs30(Box29, { children: [
9221
+ /* @__PURE__ */ jsx32(Text30, { color: "green", bold: true, children: release.version }),
9222
+ /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
9338
9223
  " \u2022 ",
9339
9224
  formatDate(release.publishedAt)
9340
9225
  ] }),
9341
- i === 0 && /* @__PURE__ */ jsx33(Text31, { color: "cyan", children: " (latest)" })
9226
+ i === 0 && /* @__PURE__ */ jsx32(Text30, { color: "cyan", children: " (latest)" })
9342
9227
  ] }),
9343
- /* @__PURE__ */ jsx33(Box30, { paddingLeft: 2, children: /* @__PURE__ */ jsxs31(Text31, { dimColor: true, children: [
9228
+ /* @__PURE__ */ jsx32(Box29, { paddingLeft: 2, children: /* @__PURE__ */ jsxs30(Text30, { dimColor: true, children: [
9344
9229
  release.keyCount,
9345
9230
  " keys \u2022 ",
9346
9231
  release.languages.length,
@@ -9348,44 +9233,44 @@ function ReleasesComponent({ options }) {
9348
9233
  formatBytes2(release.bundleSize),
9349
9234
  release.downloadCount > 0 && ` \u2022 ${release.downloadCount} downloads`
9350
9235
  ] }) }),
9351
- /* @__PURE__ */ jsx33(Box30, { paddingLeft: 2, children: /* @__PURE__ */ jsxs31(Text31, { color: "gray", children: [
9236
+ /* @__PURE__ */ jsx32(Box29, { paddingLeft: 2, children: /* @__PURE__ */ jsxs30(Text30, { color: "gray", children: [
9352
9237
  "ID: ",
9353
9238
  release.id
9354
9239
  ] }) })
9355
9240
  ] }, release.id)),
9356
- /* @__PURE__ */ jsx33(Box30, { marginTop: 1, children: /* @__PURE__ */ jsx33(Text31, { dimColor: true, children: "Use `npx @intlpullhq/cli releases delete <id>` to delete a release." }) })
9241
+ /* @__PURE__ */ jsx32(Box29, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text30, { dimColor: true, children: "Use `npx @intlpullhq/cli releases delete <id>` to delete a release." }) })
9357
9242
  ] })
9358
9243
  ] }),
9359
- state.status === "error" && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9360
- /* @__PURE__ */ jsxs31(Text31, { color: "red", children: [
9244
+ state.status === "error" && /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", children: [
9245
+ /* @__PURE__ */ jsxs30(Text30, { color: "red", children: [
9361
9246
  "\u2717 ",
9362
9247
  state.message
9363
9248
  ] }),
9364
- /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: 1, children: [
9365
- /* @__PURE__ */ jsx33(Text31, { dimColor: true, children: "Tips:" }),
9366
- /* @__PURE__ */ jsx33(Text31, { color: "gray", children: " \u2022 Run `npx @intlpullhq/cli publish` to create a release first" }),
9367
- /* @__PURE__ */ jsx33(Text31, { color: "gray", children: " \u2022 OTA requires Professional or Enterprise plan" })
9249
+ /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", marginTop: 1, children: [
9250
+ /* @__PURE__ */ jsx32(Text30, { dimColor: true, children: "Tips:" }),
9251
+ /* @__PURE__ */ jsx32(Text30, { color: "gray", children: " \u2022 Run `npx @intlpullhq/cli publish` to create a release first" }),
9252
+ /* @__PURE__ */ jsx32(Text30, { color: "gray", children: " \u2022 OTA requires Professional or Enterprise plan" })
9368
9253
  ] })
9369
9254
  ] })
9370
9255
  ] });
9371
9256
  }
9372
9257
  function runReleases(options) {
9373
- render16(/* @__PURE__ */ jsx33(ReleasesComponent, { options }));
9258
+ render15(/* @__PURE__ */ jsx32(ReleasesComponent, { options }));
9374
9259
  }
9375
9260
 
9376
9261
  // src/commands/workflow.tsx
9377
- import { useState as useState21, useEffect as useEffect18 } from "react";
9378
- import { render as render17, Box as Box31, Text as Text32 } from "ink";
9379
- import { Fragment as Fragment5, jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
9262
+ import { useState as useState20, useEffect as useEffect17 } from "react";
9263
+ import { render as render16, Box as Box30, Text as Text31 } from "ink";
9264
+ import { Fragment as Fragment5, jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
9380
9265
  function WorkflowStatusCommand({ projectId }) {
9381
- const [loading, setLoading] = useState21(true);
9382
- const [error, setError] = useState21(null);
9383
- const [projectName, setProjectName] = useState21("");
9384
- const [workflowsEnabled, setWorkflowsEnabled] = useState21(false);
9385
- const [workflows, setWorkflows] = useState21([]);
9386
- const [pending, setPending] = useState21([]);
9387
- const [locked, setLocked] = useState21([]);
9388
- useEffect18(() => {
9266
+ const [loading, setLoading] = useState20(true);
9267
+ const [error, setError] = useState20(null);
9268
+ const [projectName, setProjectName] = useState20("");
9269
+ const [workflowsEnabled, setWorkflowsEnabled] = useState20(false);
9270
+ const [workflows, setWorkflows] = useState20([]);
9271
+ const [pending, setPending] = useState20([]);
9272
+ const [locked, setLocked] = useState20([]);
9273
+ useEffect17(() => {
9389
9274
  async function fetchData() {
9390
9275
  const resolved = getResolvedApiKey();
9391
9276
  if (!resolved?.key) {
@@ -9425,36 +9310,36 @@ function WorkflowStatusCommand({ projectId }) {
9425
9310
  }
9426
9311
  fetchData();
9427
9312
  }, [projectId]);
9428
- return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
9429
- /* @__PURE__ */ jsx34(Header, { compact: true }),
9430
- loading && /* @__PURE__ */ jsx34(Box31, { marginY: 1, children: /* @__PURE__ */ jsx34(Spinner2, { label: "Fetching workflow status..." }) }),
9431
- error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Error", children: error }),
9432
- !loading && !error && !workflowsEnabled && /* @__PURE__ */ jsxs32(Alert, { type: "warning", title: "Workflows Not Enabled", children: [
9313
+ return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9314
+ /* @__PURE__ */ jsx33(Header, { compact: true }),
9315
+ loading && /* @__PURE__ */ jsx33(Box30, { marginY: 1, children: /* @__PURE__ */ jsx33(Spinner2, { label: "Fetching workflow status..." }) }),
9316
+ error && /* @__PURE__ */ jsx33(Alert, { type: "error", title: "Error", children: error }),
9317
+ !loading && !error && !workflowsEnabled && /* @__PURE__ */ jsxs31(Alert, { type: "warning", title: "Workflows Not Enabled", children: [
9433
9318
  'Workflows are not enabled for project "',
9434
9319
  projectName,
9435
9320
  '".',
9436
9321
  "\n",
9437
9322
  "Enable them in the project settings at app.intlpull.com"
9438
9323
  ] }),
9439
- !loading && !error && workflowsEnabled && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
9440
- /* @__PURE__ */ jsx34(
9441
- Box31,
9324
+ !loading && !error && workflowsEnabled && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9325
+ /* @__PURE__ */ jsx33(
9326
+ Box30,
9442
9327
  {
9443
9328
  borderStyle: "round",
9444
9329
  borderColor: colors.primary,
9445
9330
  paddingX: 2,
9446
9331
  paddingY: 1,
9447
9332
  marginY: 1,
9448
- children: /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
9449
- /* @__PURE__ */ jsxs32(Box31, { children: [
9450
- /* @__PURE__ */ jsx34(Text32, { bold: true, color: colors.text, children: projectName }),
9451
- /* @__PURE__ */ jsxs32(Text32, { color: colors.success, children: [
9333
+ children: /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9334
+ /* @__PURE__ */ jsxs31(Box30, { children: [
9335
+ /* @__PURE__ */ jsx33(Text31, { bold: true, color: colors.text, children: projectName }),
9336
+ /* @__PURE__ */ jsxs31(Text31, { color: colors.success, children: [
9452
9337
  " ",
9453
9338
  icons.check,
9454
9339
  " Workflows Enabled"
9455
9340
  ] })
9456
9341
  ] }),
9457
- /* @__PURE__ */ jsx34(Box31, { marginTop: 1, children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9342
+ /* @__PURE__ */ jsx33(Box30, { marginTop: 1, children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9458
9343
  icons.bullet,
9459
9344
  " ",
9460
9345
  workflows.length,
@@ -9462,14 +9347,14 @@ function WorkflowStatusCommand({ projectId }) {
9462
9347
  workflows.length !== 1 ? "s" : "",
9463
9348
  " configured"
9464
9349
  ] }) }),
9465
- /* @__PURE__ */ jsx34(Box31, { children: /* @__PURE__ */ jsxs32(Text32, { color: pending.length > 0 ? colors.warning : colors.textMuted, children: [
9350
+ /* @__PURE__ */ jsx33(Box30, { children: /* @__PURE__ */ jsxs31(Text31, { color: pending.length > 0 ? colors.warning : colors.textMuted, children: [
9466
9351
  icons.bullet,
9467
9352
  " ",
9468
9353
  pending.length,
9469
9354
  " pending approval",
9470
9355
  pending.length !== 1 ? "s" : ""
9471
9356
  ] }) }),
9472
- /* @__PURE__ */ jsx34(Box31, { children: /* @__PURE__ */ jsxs32(Text32, { color: locked.length > 0 ? colors.info : colors.textMuted, children: [
9357
+ /* @__PURE__ */ jsx33(Box30, { children: /* @__PURE__ */ jsxs31(Text31, { color: locked.length > 0 ? colors.info : colors.textMuted, children: [
9473
9358
  icons.bullet,
9474
9359
  " ",
9475
9360
  locked.length,
@@ -9479,15 +9364,15 @@ function WorkflowStatusCommand({ projectId }) {
9479
9364
  ] })
9480
9365
  }
9481
9366
  ),
9482
- workflows.filter((w) => w.is_active).length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: 1, children: [
9483
- /* @__PURE__ */ jsx34(Text32, { bold: true, color: colors.text, children: "Active Workflow" }),
9484
- workflows.filter((w) => w.is_active).map((w) => /* @__PURE__ */ jsxs32(Box31, { marginTop: 1, flexDirection: "column", children: [
9485
- /* @__PURE__ */ jsx34(Text32, { color: colors.primary, children: w.name }),
9486
- /* @__PURE__ */ jsx34(Box31, { marginLeft: 2, flexDirection: "column", children: w.stages.map((stage, i) => /* @__PURE__ */ jsx34(Box31, { children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9367
+ workflows.filter((w) => w.is_active).length > 0 && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: 1, children: [
9368
+ /* @__PURE__ */ jsx33(Text31, { bold: true, color: colors.text, children: "Active Workflow" }),
9369
+ workflows.filter((w) => w.is_active).map((w) => /* @__PURE__ */ jsxs31(Box30, { marginTop: 1, flexDirection: "column", children: [
9370
+ /* @__PURE__ */ jsx33(Text31, { color: colors.primary, children: w.name }),
9371
+ /* @__PURE__ */ jsx33(Box30, { marginLeft: 2, flexDirection: "column", children: w.stages.map((stage, i) => /* @__PURE__ */ jsx33(Box30, { children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9487
9372
  i + 1,
9488
9373
  ". ",
9489
9374
  stage.name,
9490
- stage.required_role && /* @__PURE__ */ jsxs32(Text32, { dimColor: true, children: [
9375
+ stage.required_role && /* @__PURE__ */ jsxs31(Text31, { dimColor: true, children: [
9491
9376
  " (requires: ",
9492
9377
  stage.required_role,
9493
9378
  ")"
@@ -9495,56 +9380,56 @@ function WorkflowStatusCommand({ projectId }) {
9495
9380
  ] }) }, stage.id)) })
9496
9381
  ] }, w.id))
9497
9382
  ] }),
9498
- pending.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: 2, children: [
9499
- /* @__PURE__ */ jsxs32(Text32, { bold: true, color: colors.warning, children: [
9383
+ pending.length > 0 && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", marginTop: 2, children: [
9384
+ /* @__PURE__ */ jsxs31(Text31, { bold: true, color: colors.warning, children: [
9500
9385
  "Pending Approvals (",
9501
9386
  pending.length,
9502
9387
  ")"
9503
9388
  ] }),
9504
- pending.slice(0, 10).map((p) => /* @__PURE__ */ jsxs32(Box31, { marginTop: 1, flexDirection: "column", children: [
9505
- /* @__PURE__ */ jsxs32(Box31, { children: [
9506
- /* @__PURE__ */ jsx34(Text32, { color: colors.text, children: p.key }),
9507
- /* @__PURE__ */ jsxs32(Text32, { color: colors.textDim, children: [
9389
+ pending.slice(0, 10).map((p) => /* @__PURE__ */ jsxs31(Box30, { marginTop: 1, flexDirection: "column", children: [
9390
+ /* @__PURE__ */ jsxs31(Box30, { children: [
9391
+ /* @__PURE__ */ jsx33(Text31, { color: colors.text, children: p.key }),
9392
+ /* @__PURE__ */ jsxs31(Text31, { color: colors.textDim, children: [
9508
9393
  " [",
9509
9394
  p.language,
9510
9395
  "]"
9511
9396
  ] })
9512
9397
  ] }),
9513
- /* @__PURE__ */ jsx34(Box31, { marginLeft: 2, children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9398
+ /* @__PURE__ */ jsx33(Box30, { marginLeft: 2, children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9514
9399
  "Stage: ",
9515
9400
  p.stage_name,
9516
9401
  p.required_role && ` (requires: ${p.required_role})`
9517
9402
  ] }) }),
9518
- /* @__PURE__ */ jsx34(Box31, { marginLeft: 2, children: /* @__PURE__ */ jsxs32(Text32, { dimColor: true, children: [
9403
+ /* @__PURE__ */ jsx33(Box30, { marginLeft: 2, children: /* @__PURE__ */ jsxs31(Text31, { dimColor: true, children: [
9519
9404
  "ID: ",
9520
9405
  p.translation_id
9521
9406
  ] }) })
9522
9407
  ] }, p.id)),
9523
- pending.length > 10 && /* @__PURE__ */ jsx34(Box31, { marginTop: 1, children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9408
+ pending.length > 10 && /* @__PURE__ */ jsx33(Box30, { marginTop: 1, children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9524
9409
  "... and ",
9525
9410
  pending.length - 10,
9526
9411
  " more"
9527
9412
  ] }) })
9528
9413
  ] }),
9529
- pending.length === 0 && /* @__PURE__ */ jsx34(Box31, { marginTop: 2, children: /* @__PURE__ */ jsxs32(Text32, { color: colors.success, children: [
9414
+ pending.length === 0 && /* @__PURE__ */ jsx33(Box30, { marginTop: 2, children: /* @__PURE__ */ jsxs31(Text31, { color: colors.success, children: [
9530
9415
  icons.check,
9531
9416
  " No pending approvals"
9532
9417
  ] }) }),
9533
- /* @__PURE__ */ jsx34(Box31, { marginTop: 2, children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9418
+ /* @__PURE__ */ jsx33(Box30, { marginTop: 2, children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9534
9419
  icons.info,
9535
9420
  " Use",
9536
9421
  " ",
9537
- /* @__PURE__ */ jsx34(Text32, { color: colors.primary, children: "npx @intlpullhq/cli workflow approve <id>" }),
9422
+ /* @__PURE__ */ jsx33(Text31, { color: colors.primary, children: "npx @intlpullhq/cli workflow approve <id>" }),
9538
9423
  " to approve"
9539
9424
  ] }) })
9540
9425
  ] })
9541
9426
  ] });
9542
9427
  }
9543
9428
  function WorkflowPendingCommand({ projectId }) {
9544
- const [loading, setLoading] = useState21(true);
9545
- const [error, setError] = useState21(null);
9546
- const [pending, setPending] = useState21([]);
9547
- useEffect18(() => {
9429
+ const [loading, setLoading] = useState20(true);
9430
+ const [error, setError] = useState20(null);
9431
+ const [pending, setPending] = useState20([]);
9432
+ useEffect17(() => {
9548
9433
  async function fetchData() {
9549
9434
  const resolved = getResolvedApiKey();
9550
9435
  if (!resolved?.key) {
@@ -9571,19 +9456,19 @@ function WorkflowPendingCommand({ projectId }) {
9571
9456
  }
9572
9457
  fetchData();
9573
9458
  }, [projectId]);
9574
- return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
9575
- /* @__PURE__ */ jsx34(Header, { compact: true }),
9576
- loading && /* @__PURE__ */ jsx34(Box31, { marginY: 1, children: /* @__PURE__ */ jsx34(Spinner2, { label: "Fetching pending approvals..." }) }),
9577
- error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Error", children: error }),
9578
- !loading && !error && pending.length === 0 && /* @__PURE__ */ jsx34(Alert, { type: "success", title: "All Clear", children: "No pending approvals" }),
9579
- !loading && !error && pending.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
9580
- /* @__PURE__ */ jsx34(Box31, { marginY: 1, children: /* @__PURE__ */ jsxs32(Text32, { bold: true, color: colors.warning, children: [
9459
+ return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9460
+ /* @__PURE__ */ jsx33(Header, { compact: true }),
9461
+ loading && /* @__PURE__ */ jsx33(Box30, { marginY: 1, children: /* @__PURE__ */ jsx33(Spinner2, { label: "Fetching pending approvals..." }) }),
9462
+ error && /* @__PURE__ */ jsx33(Alert, { type: "error", title: "Error", children: error }),
9463
+ !loading && !error && pending.length === 0 && /* @__PURE__ */ jsx33(Alert, { type: "success", title: "All Clear", children: "No pending approvals" }),
9464
+ !loading && !error && pending.length > 0 && /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9465
+ /* @__PURE__ */ jsx33(Box30, { marginY: 1, children: /* @__PURE__ */ jsxs31(Text31, { bold: true, color: colors.warning, children: [
9581
9466
  pending.length,
9582
9467
  " Pending Approval",
9583
9468
  pending.length !== 1 ? "s" : ""
9584
9469
  ] }) }),
9585
- pending.map((p) => /* @__PURE__ */ jsxs32(
9586
- Box31,
9470
+ pending.map((p) => /* @__PURE__ */ jsxs31(
9471
+ Box30,
9587
9472
  {
9588
9473
  borderStyle: "single",
9589
9474
  borderColor: colors.textDim,
@@ -9592,33 +9477,33 @@ function WorkflowPendingCommand({ projectId }) {
9592
9477
  marginBottom: 1,
9593
9478
  flexDirection: "column",
9594
9479
  children: [
9595
- /* @__PURE__ */ jsx34(Box31, { children: /* @__PURE__ */ jsx34(Text32, { bold: true, color: colors.text, children: p.key }) }),
9596
- /* @__PURE__ */ jsx34(Box31, { children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9480
+ /* @__PURE__ */ jsx33(Box30, { children: /* @__PURE__ */ jsx33(Text31, { bold: true, color: colors.text, children: p.key }) }),
9481
+ /* @__PURE__ */ jsx33(Box30, { children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9597
9482
  "Language: ",
9598
- /* @__PURE__ */ jsx34(Text32, { color: colors.info, children: p.language }),
9483
+ /* @__PURE__ */ jsx33(Text31, { color: colors.info, children: p.language }),
9599
9484
  " | ",
9600
9485
  "Namespace: ",
9601
- /* @__PURE__ */ jsx34(Text32, { color: colors.info, children: p.namespace })
9486
+ /* @__PURE__ */ jsx33(Text31, { color: colors.info, children: p.namespace })
9602
9487
  ] }) }),
9603
- /* @__PURE__ */ jsx34(Box31, { children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9488
+ /* @__PURE__ */ jsx33(Box30, { children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9604
9489
  "Stage: ",
9605
- /* @__PURE__ */ jsx34(Text32, { color: colors.warning, children: p.stage_name }),
9606
- p.required_role && /* @__PURE__ */ jsxs32(Text32, { dimColor: true, children: [
9490
+ /* @__PURE__ */ jsx33(Text31, { color: colors.warning, children: p.stage_name }),
9491
+ p.required_role && /* @__PURE__ */ jsxs31(Text31, { dimColor: true, children: [
9607
9492
  " (requires: ",
9608
9493
  p.required_role,
9609
9494
  ")"
9610
9495
  ] })
9611
9496
  ] }) }),
9612
- /* @__PURE__ */ jsx34(Box31, { children: /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
9497
+ /* @__PURE__ */ jsx33(Box30, { children: /* @__PURE__ */ jsxs31(Text31, { color: colors.textMuted, children: [
9613
9498
  "Value: ",
9614
- /* @__PURE__ */ jsxs32(Text32, { color: colors.text, children: [
9499
+ /* @__PURE__ */ jsxs31(Text31, { color: colors.text, children: [
9615
9500
  '"',
9616
9501
  p.value.substring(0, 50),
9617
9502
  p.value.length > 50 ? "..." : "",
9618
9503
  '"'
9619
9504
  ] })
9620
9505
  ] }) }),
9621
- /* @__PURE__ */ jsx34(Box31, { marginTop: 1, children: /* @__PURE__ */ jsxs32(Text32, { dimColor: true, children: [
9506
+ /* @__PURE__ */ jsx33(Box30, { marginTop: 1, children: /* @__PURE__ */ jsxs31(Text31, { dimColor: true, children: [
9622
9507
  "ID: ",
9623
9508
  p.translation_id
9624
9509
  ] }) })
@@ -9649,11 +9534,11 @@ The translation is locked and cannot be modified.`;
9649
9534
  return errorMessage;
9650
9535
  }
9651
9536
  function ApproveCommand({ projectId, translationId, comment }) {
9652
- const [loading, setLoading] = useState21(true);
9653
- const [error, setError] = useState21(null);
9654
- const [success, setSuccess] = useState21(false);
9655
- const [stageName, setStageName] = useState21("");
9656
- useEffect18(() => {
9537
+ const [loading, setLoading] = useState20(true);
9538
+ const [error, setError] = useState20(null);
9539
+ const [success, setSuccess] = useState20(false);
9540
+ const [stageName, setStageName] = useState20("");
9541
+ useEffect17(() => {
9657
9542
  async function approve() {
9658
9543
  const resolved = getResolvedApiKey();
9659
9544
  if (!resolved?.key) {
@@ -9682,15 +9567,15 @@ function ApproveCommand({ projectId, translationId, comment }) {
9682
9567
  }
9683
9568
  approve();
9684
9569
  }, [projectId, translationId, comment]);
9685
- return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
9686
- /* @__PURE__ */ jsx34(Header, { compact: true }),
9687
- loading && /* @__PURE__ */ jsx34(Box31, { marginY: 1, children: /* @__PURE__ */ jsx34(Spinner2, { label: "Approving translation..." }) }),
9688
- error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Approval Failed", children: error }),
9689
- success && /* @__PURE__ */ jsxs32(Alert, { type: "success", title: "Approved", children: [
9570
+ return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9571
+ /* @__PURE__ */ jsx33(Header, { compact: true }),
9572
+ loading && /* @__PURE__ */ jsx33(Box30, { marginY: 1, children: /* @__PURE__ */ jsx33(Spinner2, { label: "Approving translation..." }) }),
9573
+ error && /* @__PURE__ */ jsx33(Alert, { type: "error", title: "Approval Failed", children: error }),
9574
+ success && /* @__PURE__ */ jsxs31(Alert, { type: "success", title: "Approved", children: [
9690
9575
  'Translation approved at stage "',
9691
9576
  stageName,
9692
9577
  '"',
9693
- comment && /* @__PURE__ */ jsxs32(Fragment5, { children: [
9578
+ comment && /* @__PURE__ */ jsxs31(Fragment5, { children: [
9694
9579
  "\n",
9695
9580
  "Comment: ",
9696
9581
  comment
@@ -9699,10 +9584,10 @@ function ApproveCommand({ projectId, translationId, comment }) {
9699
9584
  ] });
9700
9585
  }
9701
9586
  function RejectCommand({ projectId, translationId, reason }) {
9702
- const [loading, setLoading] = useState21(true);
9703
- const [error, setError] = useState21(null);
9704
- const [success, setSuccess] = useState21(false);
9705
- useEffect18(() => {
9587
+ const [loading, setLoading] = useState20(true);
9588
+ const [error, setError] = useState20(null);
9589
+ const [success, setSuccess] = useState20(false);
9590
+ useEffect17(() => {
9706
9591
  async function reject() {
9707
9592
  const resolved = getResolvedApiKey();
9708
9593
  if (!resolved?.key) {
@@ -9730,11 +9615,11 @@ function RejectCommand({ projectId, translationId, reason }) {
9730
9615
  }
9731
9616
  reject();
9732
9617
  }, [projectId, translationId, reason]);
9733
- return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
9734
- /* @__PURE__ */ jsx34(Header, { compact: true }),
9735
- loading && /* @__PURE__ */ jsx34(Box31, { marginY: 1, children: /* @__PURE__ */ jsx34(Spinner2, { label: "Rejecting translation..." }) }),
9736
- error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Rejection Failed", children: error }),
9737
- success && /* @__PURE__ */ jsxs32(Alert, { type: "warning", title: "Rejected", children: [
9618
+ return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
9619
+ /* @__PURE__ */ jsx33(Header, { compact: true }),
9620
+ loading && /* @__PURE__ */ jsx33(Box30, { marginY: 1, children: /* @__PURE__ */ jsx33(Spinner2, { label: "Rejecting translation..." }) }),
9621
+ error && /* @__PURE__ */ jsx33(Alert, { type: "error", title: "Rejection Failed", children: error }),
9622
+ success && /* @__PURE__ */ jsxs31(Alert, { type: "warning", title: "Rejected", children: [
9738
9623
  'Translation rejected with reason: "',
9739
9624
  reason,
9740
9625
  '"'
@@ -9742,16 +9627,16 @@ function RejectCommand({ projectId, translationId, reason }) {
9742
9627
  ] });
9743
9628
  }
9744
9629
  function runWorkflowStatus(options) {
9745
- render17(/* @__PURE__ */ jsx34(WorkflowStatusCommand, { projectId: options.project }));
9630
+ render16(/* @__PURE__ */ jsx33(WorkflowStatusCommand, { projectId: options.project }));
9746
9631
  }
9747
9632
  function runWorkflowPending(options) {
9748
- render17(/* @__PURE__ */ jsx34(WorkflowPendingCommand, { projectId: options.project }));
9633
+ render16(/* @__PURE__ */ jsx33(WorkflowPendingCommand, { projectId: options.project }));
9749
9634
  }
9750
9635
  function runWorkflowApprove(translationId, options) {
9751
- render17(/* @__PURE__ */ jsx34(ApproveCommand, { projectId: options.project, translationId, comment: options.comment }));
9636
+ render16(/* @__PURE__ */ jsx33(ApproveCommand, { projectId: options.project, translationId, comment: options.comment }));
9752
9637
  }
9753
9638
  function runWorkflowReject(translationId, options) {
9754
- render17(/* @__PURE__ */ jsx34(RejectCommand, { projectId: options.project, translationId, reason: options.reason }));
9639
+ render16(/* @__PURE__ */ jsx33(RejectCommand, { projectId: options.project, translationId, reason: options.reason }));
9755
9640
  }
9756
9641
 
9757
9642
  // src/commands/emails.tsx
@@ -10010,9 +9895,9 @@ async function runEmailsStatus(options) {
10010
9895
  }
10011
9896
 
10012
9897
  // src/commands/zendesk.tsx
10013
- import { useState as useState22, useEffect as useEffect19 } from "react";
10014
- import { render as render18, Box as Box32, Text as Text33 } from "ink";
10015
- import { Fragment as Fragment6, jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
9898
+ import { useState as useState21, useEffect as useEffect18 } from "react";
9899
+ import { render as render17, Box as Box31, Text as Text32 } from "ink";
9900
+ import { Fragment as Fragment6, jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
10016
9901
  async function fetchZendeskIntegration(apiUrl, apiKey, projectId) {
10017
9902
  const response = await fetch(`${apiUrl}/api/v1/projects/${projectId}/integrations/zendesk`, {
10018
9903
  headers: { Accept: "application/json", "X-API-Key": apiKey }
@@ -10083,11 +9968,11 @@ async function disconnectZendesk(apiUrl, apiKey, projectId) {
10083
9968
  if (!response.ok) throw new Error(`Failed to disconnect: ${response.status}`);
10084
9969
  }
10085
9970
  function ZendeskStatusCommand() {
10086
- const [loading, setLoading] = useState22(true);
10087
- const [integration, setIntegration] = useState22(null);
10088
- const [status, setStatus] = useState22(null);
10089
- const [error, setError] = useState22(null);
10090
- useEffect19(() => {
9971
+ const [loading, setLoading] = useState21(true);
9972
+ const [integration, setIntegration] = useState21(null);
9973
+ const [status, setStatus] = useState21(null);
9974
+ const [error, setError] = useState21(null);
9975
+ useEffect18(() => {
10091
9976
  async function fetch2() {
10092
9977
  const resolved = getResolvedApiKey();
10093
9978
  if (!resolved?.key) {
@@ -10119,54 +10004,54 @@ function ZendeskStatusCommand() {
10119
10004
  }
10120
10005
  fetch2();
10121
10006
  }, []);
10122
- return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10123
- /* @__PURE__ */ jsx35(Header, { compact: true }),
10124
- loading && /* @__PURE__ */ jsx35(Spinner2, { label: "Fetching Zendesk status..." }),
10125
- error && /* @__PURE__ */ jsx35(Alert, { type: "error", title: "Error", children: error }),
10126
- integration && !integration.connected && /* @__PURE__ */ jsx35(Alert, { type: "warning", title: "Not Connected", children: "Zendesk is not connected. Run: npx @intlpullhq/cli zendesk connect --subdomain YOUR_SUBDOMAIN --email YOUR_EMAIL --token YOUR_TOKEN" }),
10127
- integration?.connected && integration.integration && /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginY: 1, children: [
10128
- /* @__PURE__ */ jsx35(Box32, { borderStyle: "round", borderColor: colors.success, paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10129
- /* @__PURE__ */ jsxs33(Text33, { bold: true, color: colors.success, children: [
10007
+ return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
10008
+ /* @__PURE__ */ jsx34(Header, { compact: true }),
10009
+ loading && /* @__PURE__ */ jsx34(Spinner2, { label: "Fetching Zendesk status..." }),
10010
+ error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Error", children: error }),
10011
+ integration && !integration.connected && /* @__PURE__ */ jsx34(Alert, { type: "warning", title: "Not Connected", children: "Zendesk is not connected. Run: npx @intlpullhq/cli zendesk connect --subdomain YOUR_SUBDOMAIN --email YOUR_EMAIL --token YOUR_TOKEN" }),
10012
+ integration?.connected && integration.integration && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginY: 1, children: [
10013
+ /* @__PURE__ */ jsx34(Box31, { borderStyle: "round", borderColor: colors.success, paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
10014
+ /* @__PURE__ */ jsxs32(Text32, { bold: true, color: colors.success, children: [
10130
10015
  icons.success,
10131
10016
  " Connected to Zendesk"
10132
10017
  ] }),
10133
- /* @__PURE__ */ jsxs33(Text33, { color: colors.textMuted, children: [
10018
+ /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
10134
10019
  "Subdomain: ",
10135
10020
  integration.integration.subdomain,
10136
10021
  ".zendesk.com"
10137
10022
  ] }),
10138
- /* @__PURE__ */ jsxs33(Text33, { color: colors.textMuted, children: [
10023
+ /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
10139
10024
  "Source Locale: ",
10140
10025
  integration.integration.source_locale
10141
10026
  ] }),
10142
- integration.integration.last_sync_at && /* @__PURE__ */ jsxs33(Text33, { color: colors.textMuted, children: [
10027
+ integration.integration.last_sync_at && /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
10143
10028
  "Last Sync: ",
10144
10029
  new Date(integration.integration.last_sync_at).toLocaleString()
10145
10030
  ] })
10146
10031
  ] }) }),
10147
- status && /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginTop: 1, children: [
10148
- /* @__PURE__ */ jsx35(Text33, { bold: true, children: "Content Summary" }),
10149
- /* @__PURE__ */ jsxs33(Text33, { color: colors.textMuted, children: [
10032
+ status && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: 1, children: [
10033
+ /* @__PURE__ */ jsx34(Text32, { bold: true, children: "Content Summary" }),
10034
+ /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
10150
10035
  icons.bullet,
10151
10036
  " ",
10152
10037
  status.total_articles,
10153
10038
  " articles"
10154
10039
  ] }),
10155
- /* @__PURE__ */ jsxs33(Text33, { color: colors.textMuted, children: [
10040
+ /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
10156
10041
  icons.bullet,
10157
10042
  " ",
10158
10043
  status.total_categories,
10159
10044
  " categories"
10160
10045
  ] }),
10161
- /* @__PURE__ */ jsxs33(Text33, { color: colors.textMuted, children: [
10046
+ /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
10162
10047
  icons.bullet,
10163
10048
  " ",
10164
10049
  status.total_sections,
10165
10050
  " sections"
10166
10051
  ] }),
10167
- status.target_locales.length > 0 && /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", marginTop: 1, children: [
10168
- /* @__PURE__ */ jsx35(Text33, { bold: true, children: "Translation Progress" }),
10169
- status.target_locales.map((locale) => /* @__PURE__ */ jsxs33(Text33, { color: colors.textMuted, children: [
10052
+ status.target_locales.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginTop: 1, children: [
10053
+ /* @__PURE__ */ jsx34(Text32, { bold: true, children: "Translation Progress" }),
10054
+ status.target_locales.map((locale) => /* @__PURE__ */ jsxs32(Text32, { color: colors.textMuted, children: [
10170
10055
  icons.bullet,
10171
10056
  " ",
10172
10057
  locale,
@@ -10182,10 +10067,10 @@ function ZendeskStatusCommand() {
10182
10067
  ] });
10183
10068
  }
10184
10069
  function ZendeskConnectCommand({ subdomain, email, token }) {
10185
- const [loading, setLoading] = useState22(true);
10186
- const [result, setResult] = useState22(null);
10187
- const [error, setError] = useState22(null);
10188
- useEffect19(() => {
10070
+ const [loading, setLoading] = useState21(true);
10071
+ const [result, setResult] = useState21(null);
10072
+ const [error, setError] = useState21(null);
10073
+ useEffect18(() => {
10189
10074
  async function connect() {
10190
10075
  const resolved = getResolvedApiKey();
10191
10076
  if (!resolved?.key) {
@@ -10213,11 +10098,11 @@ function ZendeskConnectCommand({ subdomain, email, token }) {
10213
10098
  }
10214
10099
  connect();
10215
10100
  }, [subdomain, email, token]);
10216
- return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10217
- /* @__PURE__ */ jsx35(Header, { compact: true }),
10218
- loading && /* @__PURE__ */ jsx35(Spinner2, { label: "Connecting to Zendesk..." }),
10219
- error && /* @__PURE__ */ jsx35(Alert, { type: "error", title: "Connection Failed", children: error }),
10220
- result?.connected && /* @__PURE__ */ jsxs33(Alert, { type: "success", title: "Connected!", children: [
10101
+ return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
10102
+ /* @__PURE__ */ jsx34(Header, { compact: true }),
10103
+ loading && /* @__PURE__ */ jsx34(Spinner2, { label: "Connecting to Zendesk..." }),
10104
+ error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Connection Failed", children: error }),
10105
+ result?.connected && /* @__PURE__ */ jsxs32(Alert, { type: "success", title: "Connected!", children: [
10221
10106
  "Successfully connected to ",
10222
10107
  subdomain,
10223
10108
  ".zendesk.com",
@@ -10227,10 +10112,10 @@ function ZendeskConnectCommand({ subdomain, email, token }) {
10227
10112
  ] });
10228
10113
  }
10229
10114
  function ZendeskSyncCommand({ direction, locale, excludeDrafts, autoTranslate, translateProvider }) {
10230
- const [loading, setLoading] = useState22(true);
10231
- const [result, setResult] = useState22(null);
10232
- const [error, setError] = useState22(null);
10233
- useEffect19(() => {
10115
+ const [loading, setLoading] = useState21(true);
10116
+ const [result, setResult] = useState21(null);
10117
+ const [error, setError] = useState21(null);
10118
+ useEffect18(() => {
10234
10119
  async function sync() {
10235
10120
  const resolved = getResolvedApiKey();
10236
10121
  if (!resolved?.key) {
@@ -10272,11 +10157,11 @@ function ZendeskSyncCommand({ direction, locale, excludeDrafts, autoTranslate, t
10272
10157
  }
10273
10158
  sync();
10274
10159
  }, [direction, locale, excludeDrafts, autoTranslate, translateProvider]);
10275
- return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10276
- /* @__PURE__ */ jsx35(Header, { compact: true }),
10277
- loading && /* @__PURE__ */ jsx35(Spinner2, { label: `${direction === "pull" ? "Pulling from" : "Pushing to"} Zendesk...${autoTranslate ? " (with auto-translation)" : ""}` }),
10278
- error && /* @__PURE__ */ jsx35(Alert, { type: "error", title: "Sync Failed", children: error }),
10279
- result?.success && /* @__PURE__ */ jsx35(Alert, { type: "success", title: "Sync Complete", children: direction === "pull" ? /* @__PURE__ */ jsxs33(Fragment6, { children: [
10160
+ return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
10161
+ /* @__PURE__ */ jsx34(Header, { compact: true }),
10162
+ loading && /* @__PURE__ */ jsx34(Spinner2, { label: `${direction === "pull" ? "Pulling from" : "Pushing to"} Zendesk...${autoTranslate ? " (with auto-translation)" : ""}` }),
10163
+ error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Sync Failed", children: error }),
10164
+ result?.success && /* @__PURE__ */ jsx34(Alert, { type: "success", title: "Sync Complete", children: direction === "pull" ? /* @__PURE__ */ jsxs32(Fragment6, { children: [
10280
10165
  "Imported ",
10281
10166
  result.articles_synced,
10282
10167
  " articles, ",
@@ -10289,24 +10174,24 @@ function ZendeskSyncCommand({ direction, locale, excludeDrafts, autoTranslate, t
10289
10174
  result.keys_created,
10290
10175
  ", updated: ",
10291
10176
  result.keys_updated,
10292
- result.auto_translate_job && /* @__PURE__ */ jsxs33(Fragment6, { children: [
10177
+ result.auto_translate_job && /* @__PURE__ */ jsxs32(Fragment6, { children: [
10293
10178
  "\n",
10294
10179
  "Auto-translation started: ",
10295
10180
  result.auto_translate_job
10296
10181
  ] })
10297
- ] }) : /* @__PURE__ */ jsxs33(Fragment6, { children: [
10182
+ ] }) : /* @__PURE__ */ jsxs32(Fragment6, { children: [
10298
10183
  "Pushed translations for ",
10299
10184
  result.articles_synced,
10300
10185
  " articles to Zendesk"
10301
10186
  ] }) }),
10302
- result && !result.success && /* @__PURE__ */ jsx35(Alert, { type: "error", title: "Sync Failed", children: result.error || "Unknown error" })
10187
+ result && !result.success && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Sync Failed", children: result.error || "Unknown error" })
10303
10188
  ] });
10304
10189
  }
10305
10190
  function ZendeskDisconnectCommand() {
10306
- const [loading, setLoading] = useState22(true);
10307
- const [success, setSuccess] = useState22(false);
10308
- const [error, setError] = useState22(null);
10309
- useEffect19(() => {
10191
+ const [loading, setLoading] = useState21(true);
10192
+ const [success, setSuccess] = useState21(false);
10193
+ const [error, setError] = useState21(null);
10194
+ useEffect18(() => {
10310
10195
  async function disconnect() {
10311
10196
  const resolved = getResolvedApiKey();
10312
10197
  if (!resolved?.key) {
@@ -10334,22 +10219,22 @@ function ZendeskDisconnectCommand() {
10334
10219
  }
10335
10220
  disconnect();
10336
10221
  }, []);
10337
- return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10338
- /* @__PURE__ */ jsx35(Header, { compact: true }),
10339
- loading && /* @__PURE__ */ jsx35(Spinner2, { label: "Disconnecting..." }),
10340
- error && /* @__PURE__ */ jsx35(Alert, { type: "error", title: "Error", children: error }),
10341
- success && /* @__PURE__ */ jsx35(Alert, { type: "success", title: "Disconnected", children: "Zendesk integration has been removed." })
10222
+ return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
10223
+ /* @__PURE__ */ jsx34(Header, { compact: true }),
10224
+ loading && /* @__PURE__ */ jsx34(Spinner2, { label: "Disconnecting..." }),
10225
+ error && /* @__PURE__ */ jsx34(Alert, { type: "error", title: "Error", children: error }),
10226
+ success && /* @__PURE__ */ jsx34(Alert, { type: "success", title: "Disconnected", children: "Zendesk integration has been removed." })
10342
10227
  ] });
10343
10228
  }
10344
10229
  function runZendeskStatus() {
10345
- render18(/* @__PURE__ */ jsx35(ZendeskStatusCommand, {}));
10230
+ render17(/* @__PURE__ */ jsx34(ZendeskStatusCommand, {}));
10346
10231
  }
10347
10232
  function runZendeskConnect(options) {
10348
10233
  if (!options.subdomain || !options.email || !options.token) {
10349
- render18(
10350
- /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10351
- /* @__PURE__ */ jsx35(Header, { compact: true }),
10352
- /* @__PURE__ */ jsxs33(Alert, { type: "error", title: "Missing Options", children: [
10234
+ render17(
10235
+ /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", children: [
10236
+ /* @__PURE__ */ jsx34(Header, { compact: true }),
10237
+ /* @__PURE__ */ jsxs32(Alert, { type: "error", title: "Missing Options", children: [
10353
10238
  "Required: --subdomain, --email, --token",
10354
10239
  "\n",
10355
10240
  "Example: npx @intlpullhq/cli zendesk connect --subdomain mycompany --email admin@example.com --token YOUR_API_TOKEN"
@@ -10358,12 +10243,12 @@ function runZendeskConnect(options) {
10358
10243
  );
10359
10244
  return;
10360
10245
  }
10361
- render18(/* @__PURE__ */ jsx35(ZendeskConnectCommand, { subdomain: options.subdomain, email: options.email, token: options.token }));
10246
+ render17(/* @__PURE__ */ jsx34(ZendeskConnectCommand, { subdomain: options.subdomain, email: options.email, token: options.token }));
10362
10247
  }
10363
10248
  function runZendeskSync(options) {
10364
10249
  const direction = options.direction || "pull";
10365
- render18(
10366
- /* @__PURE__ */ jsx35(
10250
+ render17(
10251
+ /* @__PURE__ */ jsx34(
10367
10252
  ZendeskSyncCommand,
10368
10253
  {
10369
10254
  direction,
@@ -10376,17 +10261,17 @@ function runZendeskSync(options) {
10376
10261
  );
10377
10262
  }
10378
10263
  function runZendeskDisconnect() {
10379
- render18(/* @__PURE__ */ jsx35(ZendeskDisconnectCommand, {}));
10264
+ render17(/* @__PURE__ */ jsx34(ZendeskDisconnectCommand, {}));
10380
10265
  }
10381
10266
 
10382
10267
  // src/commands/documents/index.tsx
10383
10268
  import { Command } from "commander";
10384
10269
 
10385
10270
  // src/commands/documents/list.tsx
10386
- import { useState as useState23, useEffect as useEffect20 } from "react";
10387
- import { render as render19, Box as Box33, Text as Text34, Newline } from "ink";
10388
- import Spinner16 from "ink-spinner";
10389
- import { jsx as jsx36, jsxs as jsxs34 } from "react/jsx-runtime";
10271
+ import { useState as useState22, useEffect as useEffect19 } from "react";
10272
+ import { render as render18, Box as Box32, Text as Text33, Newline } from "ink";
10273
+ import Spinner15 from "ink-spinner";
10274
+ import { jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
10390
10275
  function SimpleTable({ data }) {
10391
10276
  if (data.length === 0) return null;
10392
10277
  const headers = Object.keys(data[0]);
@@ -10394,27 +10279,27 @@ function SimpleTable({ data }) {
10394
10279
  (h) => Math.max(h.length, ...data.map((row) => String(row[h] || "").length))
10395
10280
  );
10396
10281
  const separator = "\u2500".repeat(colWidths.reduce((a, b) => a + b + 3, 1));
10397
- return /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
10398
- /* @__PURE__ */ jsx36(Text34, { children: separator }),
10399
- /* @__PURE__ */ jsxs34(Text34, { children: [
10282
+ return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10283
+ /* @__PURE__ */ jsx35(Text33, { children: separator }),
10284
+ /* @__PURE__ */ jsxs33(Text33, { children: [
10400
10285
  "\u2502 ",
10401
10286
  headers.map((h, i) => h.padEnd(colWidths[i])).join(" \u2502 "),
10402
10287
  " \u2502"
10403
10288
  ] }),
10404
- /* @__PURE__ */ jsx36(Text34, { children: separator }),
10405
- data.map((row, rowIdx) => /* @__PURE__ */ jsxs34(Text34, { children: [
10289
+ /* @__PURE__ */ jsx35(Text33, { children: separator }),
10290
+ data.map((row, rowIdx) => /* @__PURE__ */ jsxs33(Text33, { children: [
10406
10291
  "\u2502 ",
10407
10292
  headers.map((h, i) => String(row[h] || "").padEnd(colWidths[i])).join(" \u2502 "),
10408
10293
  " \u2502"
10409
10294
  ] }, rowIdx)),
10410
- /* @__PURE__ */ jsx36(Text34, { children: separator })
10295
+ /* @__PURE__ */ jsx35(Text33, { children: separator })
10411
10296
  ] });
10412
10297
  }
10413
10298
  var DocumentsList = ({ project }) => {
10414
- const [documents, setDocuments] = useState23([]);
10415
- const [isLoading, setIsLoading] = useState23(true);
10416
- const [error, setError] = useState23(null);
10417
- useEffect20(() => {
10299
+ const [documents, setDocuments] = useState22([]);
10300
+ const [isLoading, setIsLoading] = useState22(true);
10301
+ const [error, setError] = useState22(null);
10302
+ useEffect19(() => {
10418
10303
  const fetchDocuments = async () => {
10419
10304
  try {
10420
10305
  const config = getProjectConfig();
@@ -10436,19 +10321,19 @@ var DocumentsList = ({ project }) => {
10436
10321
  fetchDocuments();
10437
10322
  }, [project]);
10438
10323
  if (error) {
10439
- return /* @__PURE__ */ jsxs34(Text34, { color: "red", children: [
10324
+ return /* @__PURE__ */ jsxs33(Text33, { color: "red", children: [
10440
10325
  "Error: ",
10441
10326
  error
10442
10327
  ] });
10443
10328
  }
10444
10329
  if (isLoading) {
10445
- return /* @__PURE__ */ jsxs34(Text34, { children: [
10446
- /* @__PURE__ */ jsx36(Text34, { color: "green", children: /* @__PURE__ */ jsx36(Spinner16, { type: "dots" }) }),
10330
+ return /* @__PURE__ */ jsxs33(Text33, { children: [
10331
+ /* @__PURE__ */ jsx35(Text33, { color: "green", children: /* @__PURE__ */ jsx35(Spinner15, { type: "dots" }) }),
10447
10332
  " Loading documents..."
10448
10333
  ] });
10449
10334
  }
10450
10335
  if (documents.length === 0) {
10451
- return /* @__PURE__ */ jsx36(Text34, { children: "No documents found." });
10336
+ return /* @__PURE__ */ jsx35(Text33, { children: "No documents found." });
10452
10337
  }
10453
10338
  const data = documents.map((doc) => ({
10454
10339
  ID: doc.id.substring(0, 8) + "...",
@@ -10456,30 +10341,30 @@ var DocumentsList = ({ project }) => {
10456
10341
  Status: doc.status,
10457
10342
  Created: new Date(doc.created_at).toLocaleDateString()
10458
10343
  }));
10459
- return /* @__PURE__ */ jsxs34(Box33, { flexDirection: "column", children: [
10460
- /* @__PURE__ */ jsxs34(Text34, { children: [
10344
+ return /* @__PURE__ */ jsxs33(Box32, { flexDirection: "column", children: [
10345
+ /* @__PURE__ */ jsxs33(Text33, { children: [
10461
10346
  "Target Project: ",
10462
10347
  project || "Current"
10463
10348
  ] }),
10464
- /* @__PURE__ */ jsx36(Newline, {}),
10465
- /* @__PURE__ */ jsx36(SimpleTable, { data })
10349
+ /* @__PURE__ */ jsx35(Newline, {}),
10350
+ /* @__PURE__ */ jsx35(SimpleTable, { data })
10466
10351
  ] });
10467
10352
  };
10468
10353
  function runDocumentsList(options) {
10469
- render19(/* @__PURE__ */ jsx36(DocumentsList, { ...options }));
10354
+ render18(/* @__PURE__ */ jsx35(DocumentsList, { ...options }));
10470
10355
  }
10471
10356
 
10472
10357
  // src/commands/documents/upload.tsx
10473
- import { useState as useState24, useEffect as useEffect21 } from "react";
10474
- import { render as render20, Text as Text35 } from "ink";
10475
- import Spinner17 from "ink-spinner";
10358
+ import { useState as useState23, useEffect as useEffect20 } from "react";
10359
+ import { render as render19, Text as Text34 } from "ink";
10360
+ import Spinner16 from "ink-spinner";
10476
10361
  import fs2 from "fs";
10477
10362
  import path2 from "path";
10478
- import { jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
10363
+ import { jsx as jsx36, jsxs as jsxs34 } from "react/jsx-runtime";
10479
10364
  var DocumentsUpload = ({ file, project, source, target }) => {
10480
- const [status, setStatus] = useState24("uploading");
10481
- const [message, setMessage] = useState24("Uploading document...");
10482
- useEffect21(() => {
10365
+ const [status, setStatus] = useState23("uploading");
10366
+ const [message, setMessage] = useState23("Uploading document...");
10367
+ useEffect20(() => {
10483
10368
  const upload = async () => {
10484
10369
  try {
10485
10370
  const config = getProjectConfig();
@@ -10510,38 +10395,38 @@ var DocumentsUpload = ({ file, project, source, target }) => {
10510
10395
  upload();
10511
10396
  }, [file, project, source, target]);
10512
10397
  if (status === "error") {
10513
- return /* @__PURE__ */ jsxs35(Text35, { color: "red", children: [
10398
+ return /* @__PURE__ */ jsxs34(Text34, { color: "red", children: [
10514
10399
  "Error: ",
10515
10400
  message
10516
10401
  ] });
10517
10402
  }
10518
10403
  if (status === "success") {
10519
- return /* @__PURE__ */ jsxs35(Text35, { color: "green", children: [
10404
+ return /* @__PURE__ */ jsxs34(Text34, { color: "green", children: [
10520
10405
  "\u2713 ",
10521
10406
  message
10522
10407
  ] });
10523
10408
  }
10524
- return /* @__PURE__ */ jsxs35(Text35, { children: [
10525
- /* @__PURE__ */ jsx37(Text35, { color: "green", children: /* @__PURE__ */ jsx37(Spinner17, { type: "dots" }) }),
10409
+ return /* @__PURE__ */ jsxs34(Text34, { children: [
10410
+ /* @__PURE__ */ jsx36(Text34, { color: "green", children: /* @__PURE__ */ jsx36(Spinner16, { type: "dots" }) }),
10526
10411
  " ",
10527
10412
  message
10528
10413
  ] });
10529
10414
  };
10530
10415
  function runDocumentsUpload(options) {
10531
- render20(/* @__PURE__ */ jsx37(DocumentsUpload, { ...options }));
10416
+ render19(/* @__PURE__ */ jsx36(DocumentsUpload, { ...options }));
10532
10417
  }
10533
10418
 
10534
10419
  // src/commands/documents/download.tsx
10535
- import { useState as useState25, useEffect as useEffect22 } from "react";
10536
- import { render as render21, Text as Text36 } from "ink";
10537
- import Spinner18 from "ink-spinner";
10420
+ import { useState as useState24, useEffect as useEffect21 } from "react";
10421
+ import { render as render20, Text as Text35 } from "ink";
10422
+ import Spinner17 from "ink-spinner";
10538
10423
  import fs3 from "fs";
10539
10424
  import path3 from "path";
10540
- import { jsx as jsx38, jsxs as jsxs36 } from "react/jsx-runtime";
10425
+ import { jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
10541
10426
  var DocumentsDownload = ({ documentId, language, project, output }) => {
10542
- const [status, setStatus] = useState25("downloading");
10543
- const [message, setMessage] = useState25("Downloading document...");
10544
- useEffect22(() => {
10427
+ const [status, setStatus] = useState24("downloading");
10428
+ const [message, setMessage] = useState24("Downloading document...");
10429
+ useEffect21(() => {
10545
10430
  const download = async () => {
10546
10431
  try {
10547
10432
  const config = getProjectConfig();
@@ -10571,25 +10456,25 @@ var DocumentsDownload = ({ documentId, language, project, output }) => {
10571
10456
  download();
10572
10457
  }, [documentId, language, project, output]);
10573
10458
  if (status === "error") {
10574
- return /* @__PURE__ */ jsxs36(Text36, { color: "red", children: [
10459
+ return /* @__PURE__ */ jsxs35(Text35, { color: "red", children: [
10575
10460
  "Error: ",
10576
10461
  message
10577
10462
  ] });
10578
10463
  }
10579
10464
  if (status === "success") {
10580
- return /* @__PURE__ */ jsxs36(Text36, { color: "green", children: [
10465
+ return /* @__PURE__ */ jsxs35(Text35, { color: "green", children: [
10581
10466
  "\u2713 ",
10582
10467
  message
10583
10468
  ] });
10584
10469
  }
10585
- return /* @__PURE__ */ jsxs36(Text36, { children: [
10586
- /* @__PURE__ */ jsx38(Text36, { color: "green", children: /* @__PURE__ */ jsx38(Spinner18, { type: "dots" }) }),
10470
+ return /* @__PURE__ */ jsxs35(Text35, { children: [
10471
+ /* @__PURE__ */ jsx37(Text35, { color: "green", children: /* @__PURE__ */ jsx37(Spinner17, { type: "dots" }) }),
10587
10472
  " ",
10588
10473
  message
10589
10474
  ] });
10590
10475
  };
10591
10476
  function runDocumentsDownload(options) {
10592
- render21(/* @__PURE__ */ jsx38(DocumentsDownload, { ...options }));
10477
+ render20(/* @__PURE__ */ jsx37(DocumentsDownload, { ...options }));
10593
10478
  }
10594
10479
 
10595
10480
  // src/commands/documents/index.tsx
@@ -10797,15 +10682,6 @@ migrate.command("from <provider>").description("Migrate from Lokalise, Crowdin,
10797
10682
  dryRun: options.dryRun
10798
10683
  });
10799
10684
  });
10800
- program.command("compare").description("Compare pricing with Lokalise, Crowdin, Phrase").option("--from <provider>", "Specific competitor to compare (lokalise, crowdin, phrase)").option("--keys <n>", "Number of translation keys", "5000").option("--languages <n>", "Number of languages", "3").option("--users <n>", "Number of team members", "5").option("--json", "Output as JSON", false).action((options) => {
10801
- runCompare({
10802
- from: options.from,
10803
- keys: parseInt(options.keys, 10),
10804
- languages: parseInt(options.languages, 10),
10805
- users: parseInt(options.users, 10),
10806
- json: options.json
10807
- });
10808
- });
10809
10685
  program.command("check").description("Check for missing translations").option("--source <lang>", "Source language", "en").option("--target <langs>", "Target languages (comma-separated)").option("--output <file>", "Output report file").option("--fix", "Auto-fix missing translations", false).action((options) => {
10810
10686
  runCheck({
10811
10687
  source: options.source,