@intlpullhq/cli 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +138 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7527,7 +7527,7 @@ function runCompare(options) {
7527
7527
  import { useState as useState15, useEffect as useEffect12 } from "react";
7528
7528
  import { render as render11, Box as Box25, Text as Text26 } from "ink";
7529
7529
  import { glob } from "glob";
7530
- import { readFileSync as readFileSync6 } from "fs";
7530
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
7531
7531
 
7532
7532
  // src/components/TaskList.tsx
7533
7533
  import { Box as Box24, Text as Text25 } from "ink";
@@ -7579,14 +7579,23 @@ function TaskList({ tasks, title }) {
7579
7579
 
7580
7580
  // src/commands/check.tsx
7581
7581
  import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
7582
+ var LANG_CODE_REGEX = /^[a-z]{2}([-_][a-zA-Z]{2})?$/i;
7582
7583
  function CheckCommand({ options }) {
7583
- const [tasks, setTasks] = useState15([
7584
+ const baseTasks = [
7584
7585
  { id: "config", label: "Loading configuration", status: "pending" },
7585
7586
  { id: "scan", label: "Scanning translation files", status: "pending" },
7586
7587
  { id: "compare", label: "Comparing translations", status: "pending" },
7587
7588
  { id: "report", label: "Generating report", status: "pending" }
7588
- ]);
7589
+ ];
7590
+ if (options.fix) {
7591
+ baseTasks.push({ id: "fix", label: "Fixing missing translations", status: "pending" });
7592
+ }
7593
+ if (options.output) {
7594
+ baseTasks.push({ id: "output", label: "Writing report file", status: "pending" });
7595
+ }
7596
+ const [tasks, setTasks] = useState15(baseTasks);
7589
7597
  const [result, setResult] = useState15(null);
7598
+ const [fixResult, setFixResult] = useState15(null);
7590
7599
  const [error, setError] = useState15(null);
7591
7600
  const [done, setDone] = useState15(false);
7592
7601
  const updateTask = (id, update) => {
@@ -7636,10 +7645,10 @@ function CheckCommand({ options }) {
7636
7645
  const parts = file.split("/");
7637
7646
  const fileName = parts[parts.length - 1];
7638
7647
  const dirName = parts[parts.length - 2];
7639
- if (/^[a-z]{2}(-[A-Z]{2})?$/.test(dirName)) {
7648
+ if (LANG_CODE_REGEX.test(dirName)) {
7640
7649
  if (!filesByLang[dirName]) filesByLang[dirName] = [];
7641
7650
  filesByLang[dirName].push(file);
7642
- } else if (/^[a-z]{2}(-[A-Z]{2})?\.json$/.test(fileName)) {
7651
+ } else if (LANG_CODE_REGEX.test(fileName.replace(".json", ""))) {
7643
7652
  const lang = fileName.replace(".json", "");
7644
7653
  if (!filesByLang[lang]) filesByLang[lang] = [];
7645
7654
  filesByLang[lang].push(file);
@@ -7727,14 +7736,77 @@ ${parseErrors.join("\n")}`);
7727
7736
  status: missingKeys.length > 0 ? "warning" : "success",
7728
7737
  output: missingKeys.length > 0 ? `Found ${missingKeys.length} missing key(s)` : "All translations complete!"
7729
7738
  });
7730
- setResult({
7739
+ const checkResult = {
7731
7740
  sourceLanguage,
7732
7741
  targetLanguages,
7733
7742
  totalKeys,
7734
7743
  missingKeys: missingKeys.slice(0, 10),
7735
7744
  // Show first 10
7736
7745
  coveragePercentage
7737
- });
7746
+ };
7747
+ setResult(checkResult);
7748
+ if (options.fix && missingKeys.length > 0) {
7749
+ updateTask("fix", { status: "running" });
7750
+ let filesModified = 0;
7751
+ let keysAdded = 0;
7752
+ for (const targetLang of targetLanguages) {
7753
+ if (!filesByLang[targetLang]) continue;
7754
+ for (const file of filesByLang[targetLang]) {
7755
+ let content = {};
7756
+ try {
7757
+ content = JSON.parse(readFileSync6(file, "utf-8"));
7758
+ } catch {
7759
+ content = {};
7760
+ }
7761
+ const flatTarget = flattenObjectToRecord(content);
7762
+ let modified = false;
7763
+ for (const mk of missingKeys) {
7764
+ if (mk.missingIn.includes(targetLang) && !(mk.key in flatTarget)) {
7765
+ setNestedValue(content, mk.key, `[${targetLang.toUpperCase()}] ${mk.sourceValue}`);
7766
+ keysAdded++;
7767
+ modified = true;
7768
+ }
7769
+ }
7770
+ if (modified) {
7771
+ writeFileSync4(file, JSON.stringify(content, null, 2) + "\n");
7772
+ filesModified++;
7773
+ }
7774
+ }
7775
+ }
7776
+ setFixResult({ filesModified, keysAdded });
7777
+ updateTask("fix", {
7778
+ status: keysAdded > 0 ? "success" : "warning",
7779
+ output: keysAdded > 0 ? `Added ${keysAdded} key(s) to ${filesModified} file(s)` : "No keys to fix"
7780
+ });
7781
+ }
7782
+ if (options.output) {
7783
+ updateTask("output", { status: "running" });
7784
+ const fullReport = {
7785
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7786
+ sourceLanguage,
7787
+ targetLanguages,
7788
+ totalKeys,
7789
+ missingKeysCount: missingKeys.length,
7790
+ missingKeys: missingKeys.map((mk) => ({
7791
+ key: mk.key,
7792
+ sourceValue: mk.sourceValue,
7793
+ missingIn: mk.missingIn
7794
+ })),
7795
+ coveragePercentage
7796
+ };
7797
+ try {
7798
+ writeFileSync4(options.output, JSON.stringify(fullReport, null, 2) + "\n");
7799
+ updateTask("output", {
7800
+ status: "success",
7801
+ output: `Report written to ${options.output}`
7802
+ });
7803
+ } catch (writeErr) {
7804
+ updateTask("output", {
7805
+ status: "error",
7806
+ output: `Failed to write report: ${writeErr instanceof Error ? writeErr.message : "Unknown error"}`
7807
+ });
7808
+ }
7809
+ }
7738
7810
  setDone(true);
7739
7811
  } catch (err) {
7740
7812
  setError(err instanceof Error ? err.message : "Unknown error");
@@ -7785,10 +7857,31 @@ ${parseErrors.join("\n")}`);
7785
7857
  ] }) })
7786
7858
  ] }, i))
7787
7859
  ] }),
7788
- /* @__PURE__ */ jsx28(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
7860
+ fixResult && fixResult.keysAdded > 0 && /* @__PURE__ */ jsxs26(Box25, { flexDirection: "column", marginTop: 1, children: [
7861
+ /* @__PURE__ */ jsxs26(Text26, { bold: true, color: colors.success, children: [
7862
+ icons.success,
7863
+ " Fixed ",
7864
+ fixResult.keysAdded,
7865
+ " key(s) in ",
7866
+ fixResult.filesModified,
7867
+ " file(s)"
7868
+ ] }),
7869
+ /* @__PURE__ */ jsxs26(Box25, { marginTop: 1, children: [
7870
+ /* @__PURE__ */ jsxs26(Text26, { color: colors.warning, children: [
7871
+ icons.warning,
7872
+ " "
7873
+ ] }),
7874
+ /* @__PURE__ */ jsx28(Text26, { color: colors.textMuted, children: "Missing keys have been added with [LANG] prefix. Review and translate them." })
7875
+ ] })
7876
+ ] }),
7877
+ !options.fix && result.missingKeys.length > 0 && /* @__PURE__ */ jsx28(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
7789
7878
  "Run ",
7790
7879
  /* @__PURE__ */ jsx28(Text26, { color: colors.primary, children: "npx @intlpullhq/cli fix" }),
7791
7880
  " to auto-generate missing translations"
7881
+ ] }) }),
7882
+ options.output && /* @__PURE__ */ jsx28(Box25, { marginTop: 1, children: /* @__PURE__ */ jsxs26(Text26, { color: colors.textMuted, children: [
7883
+ "Report saved to ",
7884
+ /* @__PURE__ */ jsx28(Text26, { color: colors.primary, children: options.output })
7792
7885
  ] }) })
7793
7886
  ] })
7794
7887
  ] });
@@ -7803,6 +7896,29 @@ function flattenObject2(obj, prefix, result) {
7803
7896
  }
7804
7897
  }
7805
7898
  }
7899
+ function flattenObjectToRecord(obj, prefix = "") {
7900
+ const result = {};
7901
+ for (const [key, value] of Object.entries(obj)) {
7902
+ const newKey = prefix ? `${prefix}.${key}` : key;
7903
+ if (typeof value === "string") {
7904
+ result[newKey] = value;
7905
+ } else if (typeof value === "object" && value !== null) {
7906
+ Object.assign(result, flattenObjectToRecord(value, newKey));
7907
+ }
7908
+ }
7909
+ return result;
7910
+ }
7911
+ function setNestedValue(obj, key, value) {
7912
+ const parts = key.split(".");
7913
+ let current = obj;
7914
+ for (let i = 0; i < parts.length - 1; i++) {
7915
+ if (!current[parts[i]] || typeof current[parts[i]] === "string") {
7916
+ current[parts[i]] = {};
7917
+ }
7918
+ current = current[parts[i]];
7919
+ }
7920
+ current[parts[parts.length - 1]] = value;
7921
+ }
7806
7922
  function runCheck(options) {
7807
7923
  render11(/* @__PURE__ */ jsx28(CheckCommand, { options }));
7808
7924
  }
@@ -7810,7 +7926,7 @@ function runCheck(options) {
7810
7926
  // src/commands/diff.tsx
7811
7927
  import { useState as useState16, useEffect as useEffect13 } from "react";
7812
7928
  import { render as render12, Box as Box26, Text as Text27 } from "ink";
7813
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
7929
+ import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
7814
7930
  import { jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
7815
7931
  async function fetchProjects5(apiUrl, apiKey) {
7816
7932
  const response = await fetch(`${apiUrl}/api/v1/projects`, {
@@ -7890,7 +8006,7 @@ Or use a project-scoped API key for automatic selection.`
7890
8006
  ];
7891
8007
  let localFile = null;
7892
8008
  for (const path4 of possiblePaths) {
7893
- if (existsSync7(path4)) {
8009
+ if (existsSync6(path4)) {
7894
8010
  localFile = path4;
7895
8011
  break;
7896
8012
  }
@@ -8074,7 +8190,7 @@ function runDiff(options) {
8074
8190
  // src/commands/fix.tsx
8075
8191
  import { useState as useState17, useEffect as useEffect14 } from "react";
8076
8192
  import { render as render13, Box as Box27, Text as Text28 } from "ink";
8077
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
8193
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
8078
8194
  import { glob as glob2 } from "glob";
8079
8195
  import { jsx as jsx30, jsxs as jsxs28 } from "react/jsx-runtime";
8080
8196
  function FixCommand({ options }) {
@@ -8110,7 +8226,8 @@ function FixCommand({ options }) {
8110
8226
  `${localeDir}/**/common.json`,
8111
8227
  `${localeDir}/**/*.json`,
8112
8228
  `${localeDir}/*.json`,
8113
- `./messages/*.json`
8229
+ `./messages/*.json`,
8230
+ `./locales/**/*.json`
8114
8231
  ];
8115
8232
  let localeFiles = [];
8116
8233
  for (const pattern of patterns) {
@@ -8123,15 +8240,16 @@ function FixCommand({ options }) {
8123
8240
  if (localeFiles.length === 0) {
8124
8241
  throw new Error(`No translation files found in ${localeDir}`);
8125
8242
  }
8243
+ const LANG_CODE_REGEX2 = /^[a-z]{2}([-_][a-zA-Z]{2})?$/i;
8126
8244
  const filesByLang = {};
8127
8245
  for (const file of localeFiles) {
8128
8246
  const parts = file.split("/");
8129
8247
  const fileName = parts[parts.length - 1];
8130
8248
  const dirName = parts[parts.length - 2];
8131
- if (/^[a-z]{2}(-[A-Z]{2})?$/.test(dirName)) {
8249
+ if (LANG_CODE_REGEX2.test(dirName)) {
8132
8250
  if (!filesByLang[dirName]) filesByLang[dirName] = [];
8133
8251
  filesByLang[dirName].push(file);
8134
- } else if (/^[a-z]{2}(-[A-Z]{2})?\.json$/.test(fileName)) {
8252
+ } else if (LANG_CODE_REGEX2.test(fileName.replace(".json", ""))) {
8135
8253
  const lang = fileName.replace(".json", "");
8136
8254
  if (!filesByLang[lang]) filesByLang[lang] = [];
8137
8255
  filesByLang[lang].push(file);
@@ -8180,13 +8298,13 @@ ${parseErrors.join("\n")}`);
8180
8298
  for (const key of sourceKeys) {
8181
8299
  if (!(key in flatTarget)) {
8182
8300
  const sourceValue = flatSource[key] || "";
8183
- setNestedValue(content, key, `[${targetLang.toUpperCase()}] ${sourceValue}`);
8301
+ setNestedValue2(content, key, `[${targetLang.toUpperCase()}] ${sourceValue}`);
8184
8302
  keysAdded++;
8185
8303
  modified = true;
8186
8304
  }
8187
8305
  }
8188
8306
  if (modified && !options.dryRun) {
8189
- writeFileSync4(file, JSON.stringify(content, null, 2) + "\n");
8307
+ writeFileSync5(file, JSON.stringify(content, null, 2) + "\n");
8190
8308
  filesModified++;
8191
8309
  } else if (modified) {
8192
8310
  filesModified++;
@@ -8265,7 +8383,7 @@ function flattenObject4(obj, prefix = "") {
8265
8383
  }
8266
8384
  return result;
8267
8385
  }
8268
- function setNestedValue(obj, key, value) {
8386
+ function setNestedValue2(obj, key, value) {
8269
8387
  const parts = key.split(".");
8270
8388
  let current = obj;
8271
8389
  for (let i = 0; i < parts.length - 1; i++) {
@@ -8284,7 +8402,7 @@ function runFix(options) {
8284
8402
  import { useState as useState18, useEffect as useEffect15, useCallback, useRef as useRef2 } from "react";
8285
8403
  import { render as render14, Box as Box28, Text as Text29, useApp as useApp6, useInput as useInput7 } from "ink";
8286
8404
  import Spinner13 from "ink-spinner";
8287
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3, existsSync as existsSync9 } from "fs";
8405
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
8288
8406
  import { join as join7 } from "path";
8289
8407
  import { jsx as jsx31, jsxs as jsxs29 } from "react/jsx-runtime";
8290
8408
  async function fetchProjects6(apiUrl, apiKey) {
@@ -8424,7 +8542,7 @@ function writeTranslationFiles(bundle, outputDir, format, languages) {
8424
8542
  for (const locale of targetLanguages) {
8425
8543
  if (!bundle[locale]) continue;
8426
8544
  const localeDir = join7(outputDir, locale);
8427
- if (!existsSync9(localeDir)) {
8545
+ if (!existsSync8(localeDir)) {
8428
8546
  mkdirSync3(localeDir, { recursive: true });
8429
8547
  }
8430
8548
  let content;
@@ -8446,7 +8564,7 @@ function writeTranslationFiles(bundle, outputDir, format, languages) {
8446
8564
  break;
8447
8565
  }
8448
8566
  const filePath = join7(localeDir, filename);
8449
- writeFileSync5(filePath, content);
8567
+ writeFileSync6(filePath, content);
8450
8568
  writtenFiles.push(filePath);
8451
8569
  }
8452
8570
  return writtenFiles;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlpullhq/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "The official CLI for IntlPull - intelligent i18n for modern apps. Manage translations, sync with cloud, and automate localization workflows.",
5
5
  "type": "module",
6
6
  "bin": {