git-push-time 1.1.3 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/ui.js CHANGED
@@ -3,16 +3,15 @@ import boxen from "boxen";
3
3
  import stringWidth from "string-width";
4
4
 
5
5
  /**
6
- * Chiều rộng khung thống nhất cho toàn bộ UI.
7
- * - Luôn bằng nhau giữa dashboard / banner / prompt / box chi tiết
8
- * nên các khung xếp chồng không bị lệch (vỡ giao diện).
9
- * - Terminal hẹp thì co theo `cols - 2`, terminal rộng thì cap 76.
6
+ * Shared frame width for the entire UI.
7
+ * - Keep dashboards, banners, prompts, and detail boxes aligned.
8
+ * - Shrink to `cols - 2` in narrow terminals and cap at 76 columns.
10
9
  */
11
10
  export function getW() {
12
11
  const cols = process.stdout.columns;
13
12
  if (!cols || cols < 10) return 72;
14
13
  const avail = cols - 2;
15
- // Terminal quá hẹp (<34): dùng đúng phần hiển thị được, tránh tràn dòng.
14
+ // On very narrow terminals (<34 columns), use the available width to prevent wrapping.
16
15
  if (avail < 34) return Math.max(24, avail);
17
16
  return Math.min(76, avail);
18
17
  }
@@ -81,10 +80,10 @@ export function truncateVisual(str, maxLen) {
81
80
  return fitStyled(str, maxLen);
82
81
  }
83
82
 
84
- /** Khung tiêu đề nhất quán cho từng bước tương tác trong terminal. */
83
+ /** Consistent title frame for each interactive terminal step. */
85
84
  export function framePrompt(message) {
86
- // Dùng cùng getW() với mọi box khác để các khung luôn thẳng hàng,
87
- // không còn tình trạng prompt hẹp (56) còn dashboard rộng (76) gây vỡ.
85
+ // Use the same width as other boxes to keep frames aligned and prevent narrow prompts
86
+ // from breaking against a wider dashboard.
88
87
  return boxen(String(message ?? ""), {
89
88
  borderStyle: "round",
90
89
  borderColor: "#818CF8",
@@ -95,15 +94,15 @@ export function framePrompt(message) {
95
94
  }
96
95
 
97
96
  /* ==========================================================================
98
- BOX ENGINE CHUẨN (Bo góc mềm & Căn lề pixel-perfect)
97
+ SHARED BOX ENGINE (rounded corners and precise alignment)
99
98
  ========================================================================== */
100
99
 
101
100
  export function boxTop(title = "", borderColor = chalk.hex("#334155")) {
102
101
  const W = getW();
103
102
  if (!title) return borderColor("╭" + "─".repeat(Math.max(2, W - 2)) + "╮");
104
103
 
105
- // Căn giữa tiêu đề để 2 bên đều nhau: ╭──── Title ────╮
106
- // Chừa 1 space mỗi bên title + tối thiểu 2 gạch mỗi bên.
104
+ // Center the title with even spacing on both sides: ╭──── Title ────╮
105
+ // Leave one space beside the title and at least two dashes on each side.
107
106
  const maxTitle = Math.max(4, W - 8);
108
107
  const fittedTitle = fitStyled(title, maxTitle);
109
108
  const titleWidth = stringWidth(fittedTitle);
@@ -117,7 +116,7 @@ export function boxTop(title = "", borderColor = chalk.hex("#334155")) {
117
116
  );
118
117
  }
119
118
 
120
- /** Khung tiêu đề căn trái theo lề nội dung của boxRow. */
119
+ /** Left-aligned title frame matching the content margin of boxRow. */
121
120
  export function boxTopLeft(title = "", borderColor = chalk.hex("#334155")) {
122
121
  const W = getW();
123
122
  if (!title) return borderColor("╭" + "─".repeat(Math.max(2, W - 2)) + "╮");
@@ -144,14 +143,12 @@ export function boxDivider(borderColor = chalk.hex("#334155")) {
144
143
  }
145
144
 
146
145
  /**
147
- * Hàng Key-Value có đường nối bằng dấu chấm · · ·
148
- * Dùng "·" (U+00B7) thay vì "․" (U+2024) để không bị vỡ font trên
149
- * Windows PowerShell / Consolas. Label không còn bị cắt oan khi
150
- * terminal hẹp nhờ công thức fitted mới.
146
+ * Key-value row connected with dot leaders.
147
+ * Use "·" (U+00B7) instead of "․" (U+2024) for compatibility with
148
+ * Windows PowerShell and Consolas. The label width adapts to narrow terminals.
151
149
  */
152
150
  /**
153
- * Hàng Key-Value có đường nối bằng dấu chấm · · ·
154
- * Căn lề chuẩn xác, khoảng cách đều và không bị vỡ font trên mọi terminal.
151
+ * Key-value row with dot leaders, precise alignment, and consistent spacing.
155
152
  */
156
153
  export function boxKvDot(
157
154
  label,
@@ -260,7 +257,7 @@ export function printBanner(version = "1.1.1") {
260
257
  "#F472B6",
261
258
  ]);
262
259
  const subtitle = chalk.hex("#94A3B8")(
263
- "GitHub · Push / Pull · Lịch sử commit · Secret scan",
260
+ "GitHub · Push / Pull · Commit history · Secret scan",
264
261
  );
265
262
  const mascot = mascotLines();
266
263
  const body = [...mascot, "", title, subtitle, "", ...rows].join("\n");
@@ -275,7 +272,7 @@ export function printBanner(version = "1.1.1") {
275
272
  }
276
273
 
277
274
  /**
278
- * Hiển thị Dashboard trạng thái ngắn gọn & trực quan ở đầu menu
275
+ * Display a concise status dashboard at the top of the menu.
279
276
  */
280
277
  export function printDashboardStatus({
281
278
  targetDir = process.cwd(),
@@ -288,34 +285,34 @@ export function printDashboardStatus({
288
285
  }) {
289
286
  const border = chalk.hex("#6366F1");
290
287
  const W = getW();
291
- const remote = remoteUrl ? formatRemoteLabel(remoteUrl) : chalk.hex("#94A3B8")("Chưa liên kết");
288
+ const remote = remoteUrl ? formatRemoteLabel(remoteUrl) : chalk.hex("#94A3B8")("Not linked");
292
289
 
293
290
  const ghStatus = githubUser
294
- ? `${chalk.hex("#38BDF8").bold(`@${githubUser.login}`)} ${chalk.bgHex("#064E3B").hex("#34D399").bold(" [✓ ĐÃ KẾT NỐI] ")}`
295
- : chalk.bgHex("#1E1B4B").hex("#A5B4FC").bold(" [○ CHƯA ĐĂNG NHẬP] ");
291
+ ? `${chalk.hex("#38BDF8").bold(`@${githubUser.login}`)} ${chalk.bgHex("#064E3B").hex("#34D399").bold(" [✓ CONNECTED] ")}`
292
+ : chalk.bgHex("#1E1B4B").hex("#A5B4FC").bold(" [○ SIGNED OUT] ");
296
293
 
297
294
  const branchPill = changes === 0
298
- ? chalk.bgHex("#064E3B").hex("#34D399").bold(" [✓ SẠCH] ")
299
- : chalk.bgHex("#78350F").hex("#FDE047").bold(` [● ${changes} THAY ĐỔI] `);
295
+ ? chalk.bgHex("#064E3B").hex("#34D399").bold(" [✓ CLEAN] ")
296
+ : chalk.bgHex("#78350F").hex("#FDE047").bold(` [● ${changes} CHANGES] `);
300
297
 
301
298
  const branchDisplay = `${chalk.hex("#38BDF8").bold(currentBranch)} ${branchPill}`;
302
299
 
303
300
  console.log("");
304
301
  console.log(
305
302
  boxTop(
306
- `${chalk.bold.hex("#F8FAFC")(" BẢNG ĐIỀU KHIỂN GIT ")} ${chalk.hex("#94A3B8")(`v${version}`)}`,
303
+ `${chalk.bold.hex("#F8FAFC")(" GIT DASHBOARD ")} ${chalk.hex("#94A3B8")(`v${version}`)}`,
307
304
  border,
308
305
  ),
309
306
  );
310
307
  console.log(boxRow("", border));
311
- console.log(boxKvDot("Thư mục", chalk.hex("#F1F5F9").bold(targetDir), 14, border));
312
- console.log(boxKvDot("Nhánh Git", branchDisplay, 14, border));
308
+ console.log(boxKvDot("Folder", chalk.hex("#F1F5F9").bold(targetDir), 14, border));
309
+ console.log(boxKvDot("Branch", branchDisplay, 14, border));
313
310
  console.log(boxKvDot("GitHub", ghStatus, 14, border));
314
311
  console.log(boxKvDot("Remote", remote, 14, border));
315
312
 
316
313
  if (lastCommit && lastCommit.hash) {
317
314
  const commitText = `${chalk.hex("#A78BFA").bold(lastCommit.hash)} ${chalk.hex("#CBD5E1")(truncateVisual(lastCommit.message || "", 26))} ${chalk.hex("#64748B")(`(${lastCommit.relativeTime || ""})`)}`;
318
- console.log(boxKvDot("Commit gần", commitText, 14, border));
315
+ console.log(boxKvDot("Latest commit", commitText, 14, border));
319
316
  }
320
317
 
321
318
  console.log(boxRow("", border));
@@ -338,7 +335,7 @@ export function printPushProgress({ current, total = 6, title, completed = [] })
338
335
  });
339
336
  const rail = nodes.join(chalk.hex("#334155")("──"));
340
337
  const lead = `${chalk.hex("#38BDF8").bold("PUSH PROGRESS")} ${rail}`;
341
- const detail = chalk.hex("#94A3B8")(`[Bước ${current}/${total} · ${title}]`);
338
+ const detail = chalk.hex("#94A3B8")(`[Step ${current}/${total} · ${title}]`);
342
339
  console.log("");
343
340
  if (stringWidth(`${lead} ${detail}`) <= getW() - 2) console.log(`${lead} ${detail}`);
344
341
  else {
@@ -356,7 +353,7 @@ export function printPushProgress({ current, total = 6, title, completed = [] })
356
353
  }
357
354
 
358
355
  /**
359
- * Tiêu đề phân đoạn với điểm nhấn màu sắc
356
+ * Section heading with a color accent.
360
357
  */
361
358
  export function printSection(title) {
362
359
  const W = getW();
@@ -377,7 +374,7 @@ export function printSection(title) {
377
374
  }
378
375
 
379
376
  /**
380
- * Pill Step Badge (Ví dụ: 1/6)
377
+ * Step badge (for example, 1/6).
381
378
  */
382
379
  export function stepLabel(current, total, text) {
383
380
  const pill = chalk.bgHex("#2563EB").hex("#FFFFFF").bold(` ${current}/${total} `);
@@ -385,23 +382,23 @@ export function stepLabel(current, total, text) {
385
382
  }
386
383
 
387
384
  /**
388
- * Bảng tóm tắt thông tin trước khi Push
385
+ * Summary panel displayed before pushing.
389
386
  */
390
387
  export function printSummary({ repoUrl, commitMsg, dateDisplay, branch, force, sourceDir }) {
391
388
  const border = chalk.hex("#38BDF8");
392
- const title = chalk.bold.hex("#F8FAFC")(" XÁC NHẬN THÔNG TIN PUSH ");
389
+ const title = chalk.bold.hex("#F8FAFC")(" REVIEW PUSH DETAILS ");
393
390
  console.log("");
394
391
  console.log(boxTop(title, border));
395
392
  console.log(boxRow("", border));
396
- if (sourceDir) console.log(boxKvDot("Thư mục nguồn", chalk.hex("#F1F5F9")(sourceDir), 16, border));
397
- console.log(boxKvDot("Kho lưu trữ", formatRemoteLabel(repoUrl), 16, border));
398
- console.log(boxKvDot("Nhánh đích", chalk.hex("#38BDF8").bold(branch), 16, border));
399
- console.log(boxKvDot("Thông điệp", chalk.hex("#FDE047")(`"${commitMsg}"`), 16, border));
400
- console.log(boxKvDot("Thời điểm", chalk.hex("#A78BFA")(dateDisplay), 16, border));
401
- console.log(boxKvDot("Quét Secret", chalk.hex("#34D399")("✓ Tự động kiểm tra trước khi đẩy"), 16, border));
393
+ if (sourceDir) console.log(boxKvDot("Source folder", chalk.hex("#F1F5F9")(sourceDir), 16, border));
394
+ console.log(boxKvDot("Repository", formatRemoteLabel(repoUrl), 16, border));
395
+ console.log(boxKvDot("Target branch", chalk.hex("#38BDF8").bold(branch), 16, border));
396
+ console.log(boxKvDot("Message", chalk.hex("#FDE047")(`"${commitMsg}"`), 16, border));
397
+ console.log(boxKvDot("Commit time", chalk.hex("#A78BFA")(dateDisplay), 16, border));
398
+ console.log(boxKvDot("Secret scan", chalk.hex("#34D399")("✓ Automatically checked before push"), 16, border));
402
399
  if (force) {
403
400
  console.log(boxRow("", border));
404
- console.log(boxRow(chalk.hex("#F43F5E").bold(" [!] CẢNH BÁO: Chế độ ghi đè (--force) đang kích hoạt!"), border));
401
+ console.log(boxRow(chalk.hex("#F43F5E").bold(" [!] WARNING: Force push (--force) is enabled!"), border));
405
402
  }
406
403
  console.log(boxRow("", border));
407
404
  console.log(boxBottom(border));
@@ -411,18 +408,18 @@ export function printTargetDir(dir = "") {
411
408
  const W = getW();
412
409
  console.log(
413
410
  chalk.hex("#64748B")(
414
- ` Thư mục: ${truncateVisual(String(dir || ""), W - 13)}`,
411
+ ` Folder: ${truncateVisual(String(dir || ""), W - 13)}`,
415
412
  ),
416
413
  );
417
414
  }
418
415
 
419
416
  /**
420
- * Hiển thị thẻ Profile thông tin tài khoản GitHub
417
+ * Display the GitHub account profile card.
421
418
  */
422
419
  export function printGitHubProfile(user, source = '') {
423
420
  const border = chalk.hex("#4F46E5");
424
421
  const title = chalk.bold.bgHex("#4F46E5").hex("#FFFFFF")(
425
- " TÀI KHOẢN GITHUB KẾT NỐI ",
422
+ " CONNECTED GITHUB ACCOUNT ",
426
423
  );
427
424
 
428
425
  console.log("");
@@ -432,31 +429,31 @@ export function printGitHubProfile(user, source = '') {
432
429
  boxKvDot("Username", chalk.hex("#38BDF8").bold(`@${user.login}`), 16, border)
433
430
  );
434
431
  if (user.name) {
435
- console.log(boxKvDot("Tên hiển thị", chalk.hex("#F8FAFC")(user.name), 16, border));
432
+ console.log(boxKvDot("Display name", chalk.hex("#F8FAFC")(user.name), 16, border));
436
433
  }
437
434
  if (user.email) {
438
435
  console.log(boxKvDot("Email", chalk.hex("#94A3B8")(user.email), 16, border));
439
436
  }
440
437
  if (user.bio) {
441
- console.log(boxKvDot("Tiểu sử", chalk.hex("#CBD5E1")(truncateVisual(user.bio, 40)), 16, border));
438
+ console.log(boxKvDot("Bio", chalk.hex("#CBD5E1")(truncateVisual(user.bio, 40)), 16, border));
442
439
  }
443
440
 
444
441
  const repoStats = `${user.public_repos || 0} public • ${user.total_private_repos || 0} private`;
445
442
  console.log(boxKvDot("Repositories", chalk.hex("#34D399").bold(repoStats), 16, border));
446
443
 
447
444
  if (source) {
448
- const srcDesc = source === 'gh-cli' ? 'GitHub CLI (gh)' : source === 'env' ? 'Biến môi trường' : 'Lưu trữ cá nhân';
449
- console.log(boxKvDot("Nguồn xác thực", chalk.hex("#A78BFA")(srcDesc), 16, border));
445
+ const srcDesc = source === 'gh-cli' ? 'GitHub CLI (gh)' : source === 'env' ? 'Environment variable' : 'Local config';
446
+ console.log(boxKvDot("Auth source", chalk.hex("#A78BFA")(srcDesc), 16, border));
450
447
  }
451
448
 
452
449
  console.log(boxDivider(border));
453
- console.log(boxRow(chalk.hex("#64748B")(`» Trang cá nhân: ${user.html_url}`), border));
450
+ console.log(boxRow(chalk.hex("#64748B")(`» Profile: ${user.html_url}`), border));
454
451
  console.log(boxBottom(border));
455
452
  console.log("");
456
453
  }
457
454
 
458
455
  /**
459
- * Hiển thị mã xác thực GitHub Device Flow cho người dùng
456
+ * Display the GitHub Device Flow verification code.
460
457
  */
461
458
  export function printDeviceCodePrompt({ userCode, verificationUri }) {
462
459
  const border = chalk.hex("#6366F1");
@@ -467,27 +464,27 @@ export function printDeviceCodePrompt({ userCode, verificationUri }) {
467
464
  console.log("");
468
465
  console.log(boxTop(title, border));
469
466
  console.log(boxRow("", border));
470
- console.log(boxRow(chalk.hex("#F8FAFC").bold("Bước 1: Mở trình duyệt và truy cập liên kết:"), border));
467
+ console.log(boxRow(chalk.hex("#F8FAFC").bold("Step 1: Open this link in your browser:"), border));
471
468
  console.log(boxRow(chalk.hex("#38BDF8").bold.underline(` ${verificationUri}`), border));
472
469
  console.log(boxRow("", border));
473
- console.log(boxRow(chalk.hex("#F8FAFC").bold("Bước 2: Nhập mã xác nhận (User Code):"), border));
470
+ console.log(boxRow(chalk.hex("#F8FAFC").bold("Step 2: Enter this user code:"), border));
474
471
 
475
472
  const codeBox = chalk.bgHex("#1E1B4B").hex("#A5B4FC").bold(` >>> ${userCode} <<< `);
476
473
  console.log(boxRow(` ${codeBox}`, border));
477
474
  console.log(boxRow("", border));
478
- console.log(boxRow(chalk.hex("#94A3B8")("» Hệ thống đang tự động mở trình duyệt cho bạn..."), border));
479
- console.log(boxRow(chalk.hex("#64748B")("» Nhấn Authorize trên trình duyệt để hoàn tất."), border));
475
+ console.log(boxRow(chalk.hex("#94A3B8")("» Your browser is opening automatically..."), border));
476
+ console.log(boxRow(chalk.hex("#64748B")("» Select Authorize in your browser to finish."), border));
480
477
  console.log(boxBottom(border));
481
478
  console.log("");
482
479
  }
483
480
 
484
481
  /**
485
- * Hiển thị thông báo sau khi tạo repository GitHub mới thành công
482
+ * Display a success message after creating a GitHub repository.
486
483
  */
487
484
  export function printRepoCreatedBox(repo) {
488
485
  const border = chalk.hex("#059669");
489
486
  const title = chalk.bold.bgHex("#10B981").hex("#064E3B")(
490
- " TẠO REPOSITORY GITHUB THÀNH CÔNG! ",
487
+ " GITHUB REPOSITORY CREATED ",
491
488
  );
492
489
 
493
490
  console.log("");
@@ -498,28 +495,28 @@ export function printRepoCreatedBox(repo) {
498
495
  ? chalk.bgHex("#7F1D1D").hex("#FCA5A5").bold(" PRIVATE ")
499
496
  : chalk.bgHex("#065F46").hex("#6EE7B7").bold(" PUBLIC ");
500
497
 
501
- console.log(boxKvDot("Tên Repo", chalk.hex("#F8FAFC").bold(repo.fullName || repo.name), 14, border));
502
- console.log(boxKvDot("Quyền riêng tư", visibilityPill, 14, border));
498
+ console.log(boxKvDot("Repository", chalk.hex("#F8FAFC").bold(repo.fullName || repo.name), 14, border));
499
+ console.log(boxKvDot("Visibility", visibilityPill, 14, border));
503
500
  if (repo.description) {
504
- console.log(boxKvDot("Mô tả", chalk.hex("#CBD5E1")(truncateVisual(repo.description, 42)), 14, border));
501
+ console.log(boxKvDot("Description", chalk.hex("#CBD5E1")(truncateVisual(repo.description, 42)), 14, border));
505
502
  }
506
503
  console.log(boxKvDot("HTTPS Clone", chalk.hex("#38BDF8").underline(repo.cloneUrl), 14, border));
507
504
  console.log(boxKvDot("SSH Clone", chalk.hex("#A78BFA")(repo.sshUrl), 14, border));
508
505
 
509
506
  console.log(boxRow("", border));
510
507
  console.log(boxDivider(border));
511
- console.log(boxRow(chalk.hex("#6EE7B7")(`» Xem trên GitHub: ${repo.htmlUrl}`), border));
508
+ console.log(boxRow(chalk.hex("#6EE7B7")(`» View on GitHub: ${repo.htmlUrl}`), border));
512
509
  console.log(boxBottom(border));
513
510
  console.log("");
514
511
  }
515
512
 
516
513
  /**
517
- * Cảnh báo Folder lồng trong Repo cha
514
+ * Warn when a folder is nested inside a parent repository.
518
515
  */
519
516
  export function printNestedRepoWarning({ targetDir = "", topLevel = "" } = {}) {
520
517
  const border = chalk.hex("#D97706");
521
518
  const title = chalk.bold.bgHex("#D97706").hex("#FFFFFF")(
522
- " FOLDER NẰM TRONG REPO CHA ",
519
+ " FOLDER INSIDE A PARENT REPOSITORY ",
523
520
  );
524
521
 
525
522
  console.log("");
@@ -527,7 +524,7 @@ export function printNestedRepoWarning({ targetDir = "", topLevel = "" } = {}) {
527
524
  console.log(
528
525
  boxRow(
529
526
  chalk.hex("#FDE047")(
530
- "Folder nguồn đang nằm trong Git repo của folder cha:",
527
+ "The source folder is inside the parent Git repository:",
531
528
  ),
532
529
  border,
533
530
  ),
@@ -540,7 +537,7 @@ export function printNestedRepoWarning({ targetDir = "", topLevel = "" } = {}) {
540
537
  console.log(
541
538
  boxRow(
542
539
  chalk.hex("#94A3B8")(
543
- "Nếu push tiếp, lịch sử/file của folder cha có thể đi kèm.",
540
+ "Pushing may include the parent folder's history and files.",
544
541
  ),
545
542
  border,
546
543
  ),
@@ -548,7 +545,7 @@ export function printNestedRepoWarning({ targetDir = "", topLevel = "" } = {}) {
548
545
  console.log(
549
546
  boxRow(
550
547
  chalk.hex("#34D399")(
551
- "Gợi ý: Tạo repo RIÊNG để chỉ push folder này.",
548
+ "Tip: Create a SEPARATE repository to push only this folder.",
552
549
  ),
553
550
  border,
554
551
  ),
@@ -557,12 +554,12 @@ export function printNestedRepoWarning({ targetDir = "", topLevel = "" } = {}) {
557
554
  }
558
555
 
559
556
  /**
560
- * Cảnh báo Secret nhạy cảm
557
+ * Warn about sensitive secrets.
561
558
  */
562
559
  export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
563
560
  const border = chalk.hex("#D97706");
564
561
  const title = chalk.bold.bgHex("#D97706").hex("#FFFFFF")(
565
- " PHÁT HIỆN DỮ LIỆU NHẠY CẢM ",
562
+ " SENSITIVE DATA DETECTED ",
566
563
  );
567
564
 
568
565
  console.log("");
@@ -570,7 +567,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
570
567
  console.log(
571
568
  boxRow(
572
569
  chalk.hex("#FDE047")(
573
- "GitHub có thể chặn push nếu phát hiện key bí mật (GH013)",
570
+ "GitHub may block the push if it detects a secret (GH013)",
574
571
  ),
575
572
  border,
576
573
  ),
@@ -584,7 +581,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
584
581
 
585
582
  if (riskyFiles.length > 0) {
586
583
  console.log(
587
- boxRow(chalk.bold.hex("#F8FAFC")("File cấu hình nguy cơ:"), border),
584
+ boxRow(chalk.bold.hex("#F8FAFC")("Sensitive files:"), border),
588
585
  );
589
586
  const showFiles = riskyFiles.slice(0, 4);
590
587
  showFiles.forEach((f) => {
@@ -596,7 +593,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
596
593
  if (riskyFiles.length > 4) {
597
594
  console.log(
598
595
  boxRow(
599
- chalk.hex("#64748B")(` +- ...và ${riskyFiles.length - 4} file khác`),
596
+ chalk.hex("#64748B")(` +- ...and ${riskyFiles.length - 4} more file(s)`),
600
597
  border,
601
598
  ),
602
599
  );
@@ -607,7 +604,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
607
604
  if (riskyFiles.length > 0) console.log(boxRow("", border));
608
605
  console.log(
609
606
  boxRow(
610
- chalk.bold.hex("#F8FAFC")("Vị trí nghi vấn Secret trong code:"),
607
+ chalk.bold.hex("#F8FAFC")("Possible secrets in source files:"),
611
608
  border,
612
609
  ),
613
610
  );
@@ -637,7 +634,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
637
634
  if (findings.length > 4) {
638
635
  console.log(
639
636
  boxRow(
640
- chalk.hex("#64748B")(` +- ...và ${findings.length - 4} vị trí khác`),
637
+ chalk.hex("#64748B")(` +- ...and ${findings.length - 4} more location(s)`),
641
638
  border,
642
639
  ),
643
640
  );
@@ -648,7 +645,7 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
648
645
  console.log(
649
646
  boxRow(
650
647
  chalk.hex("#34D399")(
651
- 'Gợi ý: Chọn "Tự động fix" để gỡ file + .gitignore an toàn',
648
+ 'Tip: Choose "Auto-fix" to safely unstage files and update .gitignore',
652
649
  ),
653
650
  border,
654
651
  ),
@@ -657,14 +654,14 @@ export function printPrePushSecretWarning({ riskyFiles = [], findings = [] }) {
657
654
  }
658
655
 
659
656
  /**
660
- * Cảnh báo Push bị chặn bởi GitHub Push Protection (GH013)
657
+ * Warn when GitHub Push Protection blocks a push (GH013).
661
658
  */
662
659
  export function printSecretBlockedBox(rawMessage, details = {}) {
663
660
  const border = chalk.hex("#E11D48");
664
661
  const files = details.files || [];
665
662
  const types = details.secretTypes || [];
666
663
  const title = chalk.bold.bgHex("#E11D48").hex("#FFFFFF")(
667
- " PUSH BỊ CHẶN — PHÁT HIỆN SECRET (GH013) ",
664
+ " PUSH BLOCKED — SECRET DETECTED (GH013) ",
668
665
  );
669
666
 
670
667
  console.log("");
@@ -672,7 +669,7 @@ export function printSecretBlockedBox(rawMessage, details = {}) {
672
669
  console.log(
673
670
  boxRow(
674
671
  chalk.hex("#FDA4AF")(
675
- "GitHub Push Protection đã chặn push vì phát hiện secret!",
672
+ "GitHub Push Protection blocked the push because it detected a secret.",
676
673
  ),
677
674
  border,
678
675
  ),
@@ -682,7 +679,7 @@ export function printSecretBlockedBox(rawMessage, details = {}) {
682
679
  if (types.length > 0) {
683
680
  console.log(
684
681
  boxKvDot(
685
- "Loại secret",
682
+ "Secret type",
686
683
  chalk.hex("#F8FAFC").bold(types.slice(0, 2).join(", ")),
687
684
  14,
688
685
  border,
@@ -692,25 +689,25 @@ export function printSecretBlockedBox(rawMessage, details = {}) {
692
689
  if (files.length > 0) {
693
690
  files.slice(0, 2).forEach((f) => {
694
691
  console.log(
695
- boxKvDot("File nguồn", chalk.hex("#FCD34D")(f), 14, border),
692
+ boxKvDot("Source file", chalk.hex("#FCD34D")(f), 14, border),
696
693
  );
697
694
  });
698
695
  }
699
696
 
700
697
  console.log(boxRow("", border));
701
698
  console.log(
702
- boxRow(chalk.bold.hex("#F8FAFC")("» Các giải pháp khắc phục:"), border),
699
+ boxRow(chalk.bold.hex("#F8FAFC")("» How to resolve this:"), border),
703
700
  );
704
701
  console.log(
705
702
  boxRow(
706
- chalk.hex("#E2E8F0")(" [1] Thu hồi (revoke) key ngay nếu là token thật"),
703
+ chalk.hex("#E2E8F0")(" [1] Revoke the key immediately if it is real"),
707
704
  border,
708
705
  ),
709
706
  );
710
707
  console.log(
711
708
  boxRow(
712
709
  chalk.hex("#38BDF8")(
713
- " [2] Dùng tool tự động gỡ file & squash làm sạch commit",
710
+ " [2] Use this tool to unstage files and squash the commits",
714
711
  ),
715
712
  border,
716
713
  ),
@@ -718,7 +715,7 @@ export function printSecretBlockedBox(rawMessage, details = {}) {
718
715
  console.log(
719
716
  boxRow(
720
717
  chalk.hex("#94A3B8")(
721
- " [3] Xóa secret trong code / dùng biến môi trường (process.env)",
718
+ " [3] Remove the secret from source code and use an environment variable (process.env)",
722
719
  ),
723
720
  border,
724
721
  ),
@@ -729,40 +726,40 @@ export function printSecretBlockedBox(rawMessage, details = {}) {
729
726
  if (details.unblockUrl) {
730
727
  console.log(
731
728
  chalk.hex("#F59E0B")(
732
- `\n » URL mở khóa tạm thời (nếu là key mẫu thử nghiệm):\n ${chalk.underline(details.unblockUrl)}`,
729
+ `\n » Temporary unblock URL (for test credentials only):\n ${chalk.underline(details.unblockUrl)}`,
733
730
  ),
734
731
  );
735
732
  }
736
733
  console.log(
737
734
  chalk.hex("#64748B")(
738
- `\n » Tài liệu GitHub: ${details.resolveUrl || "https://docs.github.com/code-security/secret-scanning/pushing-a-branch-blocked-by-push-protection"}\n`,
735
+ `\n » GitHub documentation: ${details.resolveUrl || "https://docs.github.com/code-security/secret-scanning/pushing-a-branch-blocked-by-push-protection"}\n`,
739
736
  ),
740
737
  );
741
738
  }
742
739
 
743
740
  /**
744
- * Card Thông báo Thành công sau khi Push
741
+ * Success card displayed after a push.
745
742
  */
746
743
  /**
747
- * Card Thông báo Thành công sau khi Push
744
+ * Success card displayed after a push.
748
745
  */
749
746
  export function printSuccessBox({
750
747
  branch,
751
748
  repoUrl,
752
749
  durationSeconds = null,
753
- secretStatus = "Đã quét (0 rủi ro)",
750
+ secretStatus = "Scanned (no risks)",
754
751
  }) {
755
752
  const border = chalk.hex("#10B981");
756
- const title = chalk.bold.hex("#F8FAFC")(" PUSH THÀNH CÔNG ");
753
+ const title = chalk.bold.hex("#F8FAFC")(" PUSH SUCCEEDED ");
757
754
  console.log("");
758
755
  console.log(boxTop(title, border));
759
756
  console.log(boxRow("", border));
760
- console.log(boxKvDot("Nhánh đích", chalk.hex("#34D399").bold(branch), 18, border));
761
- console.log(boxKvDot("Trạng thái Secret", chalk.hex("#38BDF8")(secretStatus), 18, border));
757
+ console.log(boxKvDot("Target branch", chalk.hex("#34D399").bold(branch), 18, border));
758
+ console.log(boxKvDot("Secret scan", chalk.hex("#38BDF8")(secretStatus), 18, border));
762
759
  if (durationSeconds !== null) {
763
760
  console.log(
764
761
  boxKvDot(
765
- "Thời gian xử lý",
762
+ "Duration",
766
763
  chalk.hex("#FBBF24").bold(`${durationSeconds.toFixed(1)}s`),
767
764
  18,
768
765
  border,
@@ -775,7 +772,7 @@ export function printSuccessBox({
775
772
  console.log(boxDivider(border));
776
773
  console.log(
777
774
  boxRow(
778
- `${chalk.hex("#34D399")("» Xem trên GitHub:")} ${chalk.hex("#38BDF8").underline(webUrl)}`,
775
+ `${chalk.hex("#34D399")("» View on GitHub:")} ${chalk.hex("#38BDF8").underline(webUrl)}`,
779
776
  border,
780
777
  ),
781
778
  );
@@ -795,7 +792,7 @@ function toGitHubWebUrl(url) {
795
792
  export function printCancelled() {
796
793
  const border = chalk.hex("#64748B");
797
794
  const title = chalk.bold.hex("#F8FAFC")(
798
- " ĐÃ HỦY THAO TÁC ",
795
+ " OPERATION CANCELLED ",
799
796
  );
800
797
  console.log("");
801
798
  console.log(boxTop(title, border));
@@ -803,7 +800,7 @@ export function printCancelled() {
803
800
  console.log(
804
801
  boxRow(
805
802
  chalk.hex("#94A3B8")(
806
- "Thao tác đã dừng lại an toàn. Không có thay đổi nào được thực hiện.",
803
+ "The operation was safely stopped. No changes were made.",
807
804
  ),
808
805
  border,
809
806
  ),
@@ -813,11 +810,11 @@ export function printCancelled() {
813
810
  console.log("");
814
811
  }
815
812
 
816
- export function printErrorBox(title = "XẢY RA LỖI", lines = [], hint = "") {
813
+ export function printErrorBox(title = "AN ERROR OCCURRED", lines = [], hint = "") {
817
814
  const W = getW();
818
815
  const border = chalk.hex("#E11D48");
819
816
  const cleanTitle = String(title || "").toUpperCase();
820
- const titleStyled = chalk.bold.bgHex("#E11D48").hex("#FFFFFF")(` [LỖI] ${cleanTitle} `);
817
+ const titleStyled = chalk.bold.bgHex("#E11D48").hex("#FFFFFF")(` [ERROR] ${cleanTitle} `);
821
818
  const rows = Array.isArray(lines) ? lines : [String(lines || "")];
822
819
 
823
820
  console.log("");
@@ -843,19 +840,19 @@ export function printErrorBox(title = "XẢY RA LỖI", lines = [], hint = "") {
843
840
 
844
841
  export function printGitMissing() {
845
842
  printErrorBox(
846
- "CHƯA CÀI ĐẶT GIT",
843
+ "GIT IS NOT INSTALLED",
847
844
  [
848
- "Không tìm thấy Git trên hệ thống máy tính của bạn.",
849
- "Vui lòng tải và cài đặt Git để tiếp tục!",
845
+ "Git was not found on this system.",
846
+ "Download and install Git to continue.",
850
847
  ],
851
- "Tải ngay tại: https://git-scm.com/downloads",
848
+ "Download it at: https://git-scm.com/downloads",
852
849
  );
853
850
  }
854
851
 
855
852
  export function printAuthFailedBox(message = "") {
856
853
  const border = chalk.hex("#E11D48");
857
854
  const title = chalk.bold.bgHex("#E11D48").hex("#FFFFFF")(
858
- " LỖI XÁC THỰC GIT ",
855
+ " GIT AUTHENTICATION FAILED ",
859
856
  );
860
857
  const firstLine = String(message || "")
861
858
  .split("\n")[0]
@@ -866,7 +863,7 @@ export function printAuthFailedBox(message = "") {
866
863
  console.log(
867
864
  boxRow(
868
865
  chalk.hex("#FDA4AF")(
869
- "Không thể xác thực với remote. Kiểm tra lại quyền truy cập.",
866
+ "Could not authenticate with the remote. Check your access permissions.",
870
867
  ),
871
868
  border,
872
869
  ),
@@ -880,23 +877,23 @@ export function printAuthFailedBox(message = "") {
880
877
  );
881
878
  console.log(boxRow("", border));
882
879
  console.log(
883
- boxRow(chalk.bold.hex("#F8FAFC")("» Cách khắc phục nhanh:"), border),
880
+ boxRow(chalk.bold.hex("#F8FAFC")("» Quick fixes:"), border),
884
881
  );
885
882
  console.log(
886
883
  boxRow(
887
- chalk.hex("#E2E8F0")(" [1] Chạy tính năng GITHUB trong tool để kết nối tài khoản"),
884
+ chalk.hex("#E2E8F0")(" [1] Use the GITHUB menu to connect your account"),
888
885
  border,
889
886
  ),
890
887
  );
891
888
  console.log(
892
889
  boxRow(
893
- chalk.hex("#E2E8F0")(" [2] Dùng lệnh gh: gh auth login"),
890
+ chalk.hex("#E2E8F0")(" [2] Use GitHub CLI: gh auth login"),
894
891
  border,
895
892
  ),
896
893
  );
897
894
  console.log(
898
895
  boxRow(
899
- chalk.hex("#94A3B8")(" [3] Sử dụng SSH URL: git@github.com:user/repo.git"),
896
+ chalk.hex("#94A3B8")(" [3] Use an SSH URL: git@github.com:user/repo.git"),
900
897
  border,
901
898
  ),
902
899
  );
@@ -907,7 +904,7 @@ export function printAuthFailedBox(message = "") {
907
904
  export function printNonFastForwardBox(message = "", branch = "main") {
908
905
  const border = chalk.hex("#D97706");
909
906
  const title = chalk.bold.bgHex("#D97706").hex("#FFFFFF")(
910
- " REMOTE ĐÃ CÓ COMMIT MỚI HƠN ",
907
+ " REMOTE HAS NEWER COMMITS ",
911
908
  );
912
909
  const firstLine = String(message || "")
913
910
  .split("\n")[0]
@@ -918,7 +915,7 @@ export function printNonFastForwardBox(message = "", branch = "main") {
918
915
  console.log(
919
916
  boxRow(
920
917
  chalk.hex("#FDE68A")(
921
- "Kho lưu trữ trên Remote đang chứa commit mới hơn Local.",
918
+ "The remote repository has commits that are newer than your local branch.",
922
919
  ),
923
920
  border,
924
921
  ),
@@ -934,7 +931,7 @@ export function printNonFastForwardBox(message = "", branch = "main") {
934
931
  console.log(
935
932
  boxRow(
936
933
  chalk.hex("#38BDF8")(
937
- ` » Bạn có thể chọn Pull (rebase) rồi push lại ngay trong tool`,
934
+ ` » Choose Pull (rebase), then push again from this tool.`,
938
935
  ),
939
936
  border,
940
937
  ),
@@ -942,7 +939,7 @@ export function printNonFastForwardBox(message = "", branch = "main") {
942
939
  console.log(
943
940
  boxRow(
944
941
  chalk.hex("#94A3B8")(
945
- " Chỉ dùng --force khi bạn chắc chắn muốn ghi đè Remote.",
942
+ " Use --force only when you are certain you want to overwrite the remote.",
946
943
  ),
947
944
  border,
948
945
  ),
@@ -959,7 +956,7 @@ export function printFixResult({
959
956
  } = {}) {
960
957
  const W = getW();
961
958
  const border = chalk.hex("#10B981");
962
- const title = chalk.bold.hex("#F8FAFC")(" KẾT QUẢ BẢO VỆ SECRET ");
959
+ const title = chalk.bold.hex("#F8FAFC")(" SECRET PROTECTION RESULTS ");
963
960
 
964
961
  console.log("");
965
962
  console.log(boxTop(title, border));
@@ -968,7 +965,7 @@ export function printFixResult({
968
965
  if (fixed.length > 0) {
969
966
  console.log(
970
967
  boxRow(
971
- `${chalk.hex("#34D399").bold("[OK] Đã gỡ khỏi commit:")} ${chalk.hex("#F1F5F9")(truncateVisual(fixed.join(", "), W - 28))}`,
968
+ `${chalk.hex("#34D399").bold("[OK] Removed from commit:")} ${chalk.hex("#F1F5F9")(truncateVisual(fixed.join(", "), W - 28))}`,
972
969
  border,
973
970
  ),
974
971
  );
@@ -976,7 +973,7 @@ export function printFixResult({
976
973
  if (gitignoreAdded.length > 0) {
977
974
  console.log(
978
975
  boxRow(
979
- `${chalk.hex("#38BDF8").bold("[+] Thêm vào .gitignore:")} ${chalk.hex("#CBD5E1")(truncateVisual(gitignoreAdded.join(", "), W - 30))}`,
976
+ `${chalk.hex("#38BDF8").bold("[+] Added to .gitignore:")} ${chalk.hex("#CBD5E1")(truncateVisual(gitignoreAdded.join(", "), W - 30))}`,
980
977
  border,
981
978
  ),
982
979
  );
@@ -985,7 +982,7 @@ export function printFixResult({
985
982
  console.log(boxRow("", border));
986
983
  console.log(
987
984
  boxRow(
988
- chalk.hex("#FBBF24").bold("[!] File code chứa secret (đã gỡ stage, KHÔNG ignore):"),
985
+ chalk.hex("#FBBF24").bold("[!] Source files containing secrets (unstaged, not ignored):"),
989
986
  border,
990
987
  ),
991
988
  );
@@ -993,7 +990,7 @@ export function printFixResult({
993
990
  console.log(boxRow(` - ${chalk.hex("#CBD5E1")(truncateVisual(f, W - 10))}`, border)),
994
991
  );
995
992
  console.log(
996
- boxRow(chalk.hex("#94A3B8")(" » Hãy xóa key trong file và thay bằng process.env."), border),
993
+ boxRow(chalk.hex("#94A3B8")(" » Remove the key from the file and use process.env instead."), border),
997
994
  );
998
995
  }
999
996
  if (failed.length > 0) {
@@ -1010,7 +1007,7 @@ export function printFixResult({
1010
1007
 
1011
1008
  export function printFooter() {
1012
1009
  const W = getW();
1013
- // Dải phân cách căn đều 2 bên, full width như mọi khung.
1010
+ // Full-width divider, aligned like the other frames.
1014
1011
  const core = "»";
1015
1012
  const remaining = Math.max(4, W - 3); // 1 core + 2 space
1016
1013
  const leftLen = Math.ceil(remaining / 2);
@@ -1021,10 +1018,10 @@ export function printFooter() {
1021
1018
  chalk.hex("#334155")(" " + "─".repeat(rightLen))
1022
1019
  );
1023
1020
  const tip1 =
1024
- chalk.hex("#64748B")("» Mẹo: Chạy ") +
1021
+ chalk.hex("#64748B")("» Tip: Run ") +
1025
1022
  chalk.hex("#38BDF8").bold("git-push-time --help") +
1026
- chalk.hex("#64748B")(" để xem toàn bộ lệnh.");
1027
- const tip2 = chalk.hex("#475569")("» GitHub CLI & Push Time — Chúc hiệu quả!");
1023
+ chalk.hex("#64748B")(" to see all commands.");
1024
+ const tip2 = chalk.hex("#475569")("» Git & GitHub workspace — Happy coding!");
1028
1025
  for (const tip of [tip1, tip2]) {
1029
1026
  const w = stringWidth(tip);
1030
1027
  if (w >= W) {
@@ -1049,12 +1046,12 @@ export function styleCliHelp(program) {
1049
1046
  program.addHelpText(
1050
1047
  "after",
1051
1048
  `
1052
- ${chalk.hex("#334155")("─── » Ví dụ phổ biến ───")}
1053
- ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time')} # Mở menu điều khiển tương tác trực quan
1054
- ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time github create')} # Tạo nhanh repo mới trên GitHub
1055
- ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time -r <repo-url> -d "-2d" -y')} # Push tự động lùi 2 ngày
1056
- ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time fix')} # Quét & gỡ sạch secret
1057
- ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time pull')} # Cập nhật code mới từ remote
1049
+ ${chalk.hex("#334155")("─── » Common examples ───")}
1050
+ ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time')} # Open the interactive dashboard
1051
+ ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time github create')} # Create a GitHub repository
1052
+ ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time -r <repo-url> -d "-2d" -y')} # Push with a commit date two days ago
1053
+ ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time fix')} # Scan for and remove staged secrets
1054
+ ${chalk.hex("#34D399")("$")} ${chalk.white('git-push-time pull')} # Pull the latest changes
1058
1055
  `,
1059
1056
  );
1060
1057
  }