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.
@@ -27,17 +27,17 @@ import {
27
27
  } from '../ui.js';
28
28
 
29
29
  /**
30
- * Hiển thị thông tin tài khoản GitHub hiện tại
30
+ * Display the currently connected GitHub account.
31
31
  */
32
32
  export async function showGitHubStatus() {
33
- const spinner = ora({ text: 'Đang kiểm tra kết nối GitHub…', color: 'cyan' }).start();
33
+ const spinner = ora({ text: 'Checking the GitHub connection…', color: 'cyan' }).start();
34
34
  const info = await getCurrentGitHubUser();
35
35
  spinner.stop();
36
36
 
37
37
  if (!info.authenticated) {
38
- printSection('TRẠNG THÁI GITHUB');
39
- console.log(chalk.hex('#F59E0B')(' [!] Chưa kết nối tài khoản GitHub.'));
40
- console.log(chalk.hex('#94A3B8')(' Chạy "git-push-time github login" để kết nối tài khoản qua trình duyệt.\n'));
38
+ printSection('GITHUB STATUS');
39
+ console.log(chalk.hex('#F59E0B')(' [!] No GitHub account is connected.'));
40
+ console.log(chalk.hex('#94A3B8')(' Run "git-push-time github login" to connect an account in your browser.\n'));
41
41
  return false;
42
42
  }
43
43
 
@@ -46,112 +46,112 @@ export async function showGitHubStatus() {
46
46
  }
47
47
 
48
48
  /**
49
- * Đăng nhập GitHub qua OAuth Device Flow
49
+ * Sign in to GitHub with OAuth Device Flow.
50
50
  */
51
51
  async function loginViaDeviceFlow() {
52
- const initSpinner = ora({ text: 'Đang kết nối GitHub OAuth…', color: 'cyan' }).start();
52
+ const initSpinner = ora({ text: 'Connecting to GitHub OAuth…', color: 'cyan' }).start();
53
53
  const init = await initiateDeviceFlow();
54
54
  initSpinner.stop();
55
55
 
56
56
  if (!init.ok || !init.data) {
57
- printErrorBox('KẾT NỐI OAUTH THẤT BẠI', [init.error || 'Không nhận được mã xác thực từ GitHub.']);
57
+ printErrorBox('OAUTH CONNECTION FAILED', [init.error || 'GitHub did not return a verification code.']);
58
58
  return null;
59
59
  }
60
60
 
61
61
  const { device_code, user_code, verification_uri, interval, expires_in } = init.data;
62
62
 
63
- // Hiển thị box mã xác thực cho user
63
+ // Show the verification code to the user.
64
64
  printDeviceCodePrompt({ userCode: user_code, verificationUri: verification_uri });
65
65
 
66
- // Tự động mở browser
66
+ // Open the browser automatically.
67
67
  openBrowser(verification_uri);
68
68
 
69
69
  const pollSpinner = ora({
70
- text: chalk.hex('#38BDF8')('Đang chờ bạn xác nhận và nhấn [Authorize] trên trình duyệt…'),
70
+ text: chalk.hex('#38BDF8')('Waiting for you to authorize access in your browser…'),
71
71
  color: 'cyan'
72
72
  }).start();
73
73
 
74
74
  const pollResult = await pollDeviceToken(device_code, interval, expires_in, (status) => {
75
75
  if (status === 'slow_down') {
76
- pollSpinner.text = chalk.hex('#F59E0B')('GitHub yêu cầu giãn cách kết nối, đang tiếp tục chờ bạn xác nhận…');
76
+ pollSpinner.text = chalk.hex('#F59E0B')('GitHub requested a longer polling interval. Still waiting for authorization…');
77
77
  }
78
78
  });
79
79
 
80
80
  if (!pollResult.ok || !pollResult.token) {
81
- pollSpinner.fail(chalk.red('Xác thực thất bại!'));
82
- printErrorBox('ĐĂNG NHẬP THẤT BẠI', [pollResult.error]);
81
+ pollSpinner.fail(chalk.red('Authorization failed.'));
82
+ printErrorBox('SIGN-IN FAILED', [pollResult.error]);
83
83
  return null;
84
84
  }
85
85
 
86
- pollSpinner.text = 'Đang tải thông tin tài khoản…';
86
+ pollSpinner.text = 'Loading account information…';
87
87
  const userRes = await verifyAndFetchUser(pollResult.token);
88
88
  if (!userRes.ok) {
89
- pollSpinner.fail(chalk.red('Không thể tải profile GitHub!'));
90
- printErrorBox('LỖI PROFILE', [userRes.error]);
89
+ pollSpinner.fail(chalk.red('Could not load the GitHub profile.'));
90
+ printErrorBox('PROFILE ERROR', [userRes.error]);
91
91
  return null;
92
92
  }
93
93
 
94
94
  saveGitHubToken(pollResult.token, userRes.user);
95
- pollSpinner.succeed(chalk.hex('#34D399').bold(`Đăng nhập thành công! Xin chào @${userRes.user.login}`));
95
+ pollSpinner.succeed(chalk.hex('#34D399').bold(`Signed in successfully. Welcome, @${userRes.user.login}.`));
96
96
  printGitHubProfile(userRes.user, 'oauth-device');
97
97
  return userRes.user;
98
98
  }
99
99
 
100
100
  /**
101
- * Đăng nhập GitHub bằng Personal Access Token (PAT)
101
+ * Sign in to GitHub with a Personal Access Token (PAT).
102
102
  */
103
103
  async function loginViaToken() {
104
- printSection('ĐĂNG NHẬP BẰNG PERSONAL ACCESS TOKEN');
105
- console.log(chalk.hex('#38BDF8')(' » Hướng dẫn lấy mã Token:'));
106
- console.log(chalk.hex('#94A3B8')(' 1. Mở: https://github.com/settings/tokens'));
107
- console.log(chalk.hex('#94A3B8')(' 2. Chọn "Generate new token (classic)"'));
108
- console.log(chalk.hex('#94A3B8')(' 3. Tích chọn quyền: ') + chalk.hex('#34D399').bold('repo') + chalk.hex('#94A3B8')(' (Full control of repositories)'));
109
- console.log(chalk.hex('#94A3B8')(' 4. Copy mã token (bắt đầu bằng ghp_ hoặc github_pat_) và dán vào bên dưới.\n'));
104
+ printSection('SIGN IN WITH A PERSONAL ACCESS TOKEN');
105
+ console.log(chalk.hex('#38BDF8')(' » To create a token:'));
106
+ console.log(chalk.hex('#94A3B8')(' 1. Open: https://github.com/settings/tokens'));
107
+ console.log(chalk.hex('#94A3B8')(' 2. Select "Generate new token (classic)"'));
108
+ console.log(chalk.hex('#94A3B8')(' 3. Grant the ') + chalk.hex('#34D399').bold('repo') + chalk.hex('#94A3B8')(' scope (full control of repositories)'));
109
+ console.log(chalk.hex('#94A3B8')(' 4. Copy the token (starts with ghp_ or github_pat_) and paste it below.\n'));
110
110
 
111
111
  const tokenInput = await inputWithBack({
112
- message: 'GitHub Token (PAT)',
113
- hint: ['Bắt đầu bằng ghp_ hoặc github_pat_ · cần quyền repo'],
112
+ message: 'GitHub token (PAT)',
113
+ hint: ['Starts with ghp_ or github_pat_ · requires the repo scope'],
114
114
  validate: (val) => {
115
- if (!val || !val.trim()) return 'Vui lòng không để trống token!';
116
- if (val.trim().length < 15) return 'Mã token dường như quá ngắn. Vui lòng kiểm tra lại.';
115
+ if (!val || !val.trim()) return 'Token cannot be empty.';
116
+ if (val.trim().length < 15) return 'This token seems too short. Check it and try again.';
117
117
  return true;
118
118
  }
119
119
  });
120
120
 
121
121
  if (tokenInput === BACK) return null;
122
122
 
123
- const spinner = ora({ text: 'Đang xác thực token với GitHub…', color: 'cyan' }).start();
123
+ const spinner = ora({ text: 'Verifying token with GitHub…', color: 'cyan' }).start();
124
124
  const res = await verifyAndFetchUser(tokenInput.trim());
125
125
  if (!res.ok) {
126
- spinner.fail(chalk.red('Xác thực thất bại!'));
127
- printErrorBox('TOKEN KHÔNG HỢP LỆ', [res.error], 'Kiểm tra token còn hạn và có quyền "repo" không.');
126
+ spinner.fail(chalk.red('Verification failed.'));
127
+ printErrorBox('INVALID TOKEN', [res.error], 'Check that the token is active and has the "repo" scope.');
128
128
  return null;
129
129
  }
130
130
 
131
131
  saveGitHubToken(tokenInput.trim(), res.user);
132
- spinner.succeed(chalk.hex('#34D399')(`Đăng nhập thành công! Xin chào @${res.user.login}`));
132
+ spinner.succeed(chalk.hex('#34D399')(`Signed in successfully. Welcome, @${res.user.login}.`));
133
133
  printGitHubProfile(res.user, 'config');
134
134
  return res.user;
135
135
  }
136
136
 
137
137
  /**
138
- * Menu đăng nhập GitHub linh hoạt (OAuth Device Flow hoặc Token)
138
+ * Flexible GitHub sign-in menu (OAuth Device Flow or token).
139
139
  */
140
140
  export async function promptGitHubLogin() {
141
- printSection('ĐĂNG NHẬP TÀI KHOẢN GITHUB');
141
+ printSection('SIGN IN TO GITHUB');
142
142
 
143
143
  const method = await selectWithBack({
144
- message: 'Chọn phương thức đăng nhập:',
144
+ message: 'Choose a sign-in method:',
145
145
  choices: [
146
146
  {
147
- name: '[OAuth] Mở trình duyệt xác nhận (Device Flow) — Khuyên dùng',
147
+ name: '[OAuth] Authorize in your browser (Device Flow) — Recommended',
148
148
  value: 'oauth',
149
- description: 'Tự động mở trình duyệt, nhập mã xác thực không cần copy token'
149
+ description: 'Open the browser and enter a verification code; no token copy is needed'
150
150
  },
151
151
  {
152
- name: '[Token] Dán Personal Access Token (PAT)',
152
+ name: '[Token] Paste a Personal Access Token (PAT)',
153
153
  value: 'token',
154
- description: 'Nhập token bảo mật tự tạo trên github.com/settings/tokens'
154
+ description: 'Enter a token created at github.com/settings/tokens'
155
155
  }
156
156
  ]
157
157
  });
@@ -163,30 +163,30 @@ export async function promptGitHubLogin() {
163
163
  }
164
164
 
165
165
  /**
166
- * Bước chọn GitHub Account trong luồng Wizard Push
166
+ * GitHub account selection step in the push wizard.
167
167
  * @returns {Promise<{ useGitHub: boolean, user?: any, back?: boolean }>}
168
168
  */
169
169
  export async function promptSelectGitHubAccount({ clearPromptOnDone = false } = {}) {
170
- if (!clearPromptOnDone) printSection('CHỌN TÀI KHOẢN GITHUB');
170
+ if (!clearPromptOnDone) printSection('CHOOSE A GITHUB ACCOUNT');
171
171
 
172
172
  const accounts = getAllGitHubAccounts();
173
173
  const currentStatus = await getCurrentGitHubUser();
174
174
 
175
175
  if (accounts.length === 0 && !currentStatus.authenticated) {
176
- // Chưa có tài khoản nào
176
+ // No account is connected.
177
177
  const loginChoice = await selectWithBack({
178
- message: 'Bạn chưa kết nối tài khoản GitHub. Bạn muốn làm gì?',
178
+ message: 'No GitHub account is connected. What would you like to do?',
179
179
  clearPromptOnDone,
180
180
  choices: [
181
181
  {
182
- name: 'Đăng nhập GitHub ngay (Mở trình duyệt xác thực OAuth)',
182
+ name: 'Sign in to GitHub (authorize in your browser)',
183
183
  value: 'login',
184
- description: 'Kết nối tài khoản để chọn/tạo repo tự động'
184
+ description: 'Connect an account to choose or create repositories'
185
185
  },
186
186
  {
187
- name: 'Bỏ qua GitHub (Dán URL Git repository thủ công)',
187
+ name: 'Skip GitHub (enter a repository URL manually)',
188
188
  value: 'skip',
189
- description: 'Không dùng tài khoản GitHub, nhập link https://... trực tiếp'
189
+ description: 'Continue without a GitHub account and enter an HTTPS URL'
190
190
  }
191
191
  ]
192
192
  });
@@ -197,48 +197,48 @@ export async function promptSelectGitHubAccount({ clearPromptOnDone = false } =
197
197
  if (user) {
198
198
  return { useGitHub: true, user };
199
199
  }
200
- // Nếu đăng nhập hủy/thất bại, hỏi có muốn nhập URL thủ công không
200
+ // If sign-in is canceled or fails, allow the user to enter a URL manually.
201
201
  return { useGitHub: false };
202
202
  }
203
203
  return { useGitHub: false };
204
204
  }
205
205
 
206
- // Đã có tài khoản
206
+ // An account is already connected.
207
207
  const currentLogin = currentStatus.authenticated ? currentStatus.user.login : accounts[0]?.login;
208
208
  const choices = [
209
209
  {
210
- name: `Tiếp tục với @${currentLogin} (Đang chọn)`,
210
+ name: `Continue as @${currentLogin} (Current account)`,
211
211
  value: `use:${currentLogin}`,
212
- description: `Dùng tài khoản @${currentLogin} để liên kết repository`
212
+ description: `Use @${currentLogin} to connect a repository`
213
213
  }
214
214
  ];
215
215
 
216
- // Danh sách các tài khoản khác
216
+ // List the other accounts.
217
217
  for (const acc of accounts) {
218
218
  if (acc.login !== currentLogin) {
219
219
  choices.push({
220
- name: `Đổi sang @${acc.login}`,
220
+ name: `Switch to @${acc.login}`,
221
221
  value: `switch:${acc.login}`,
222
- description: `Chuyển sang tài khoản @${acc.login}`
222
+ description: `Use the @${acc.login} account`
223
223
  });
224
224
  }
225
225
  }
226
226
 
227
227
  choices.push(
228
228
  {
229
- name: '+ Đăng nhập thêm tài khoản GitHub khác',
229
+ name: '+ Sign in to another GitHub account',
230
230
  value: 'add_account',
231
- description: 'Đăng nhập tài khoản mới bằng OAuth hoặc Token'
231
+ description: 'Add an account using OAuth or a token'
232
232
  },
233
233
  {
234
- name: 'Bỏ qua GitHub (Dán URL repository thủ công)',
234
+ name: 'Skip GitHub (enter a repository URL manually)',
235
235
  value: 'skip',
236
- description: 'Nhập link repo bằng tay'
236
+ description: 'Enter a repository URL manually'
237
237
  }
238
238
  );
239
239
 
240
240
  const picked = await selectWithBack({
241
- message: 'Chọn tài khoản GitHub sẽ sử dụng:',
241
+ message: 'Choose the GitHub account to use:',
242
242
  clearPromptOnDone,
243
243
  choices
244
244
  });
@@ -256,7 +256,7 @@ export async function promptSelectGitHubAccount({ clearPromptOnDone = false } =
256
256
  const targetLogin = picked.replace('switch:', '');
257
257
  setActiveGitHubAccount(targetLogin);
258
258
  const updated = await getCurrentGitHubUser();
259
- console.log(chalk.hex('#34D399')(` [OK] Đã chuyển sang tài khoản @${targetLogin}\n`));
259
+ console.log(chalk.hex('#34D399')(` [OK] Switched to @${targetLogin}.\n`));
260
260
  return { useGitHub: true, user: updated.user };
261
261
  }
262
262
 
@@ -264,15 +264,15 @@ export async function promptSelectGitHubAccount({ clearPromptOnDone = false } =
264
264
  }
265
265
 
266
266
  /**
267
- * Luồng tạo repository mới trên GitHub
267
+ * Create a new GitHub repository.
268
268
  * @param {object} options
269
- * @param {string} options.targetDir Thư mục nguồn (dùng để gợi ý tên repo mặc định)
269
+ * @param {string} options.targetDir Source folder (used to suggest a default repository name)
270
270
  * @returns {Promise<{ ok: boolean, repo?: any, action?: 'push' | 'done', back?: boolean }>}
271
271
  */
272
272
  export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
273
273
  let currentUser = await getCurrentGitHubUser();
274
274
  if (!currentUser.authenticated) {
275
- console.log(chalk.hex('#F59E0B')('\n [!] Bạn cần kết nối tài khoản GitHub trước khi tạo repository.'));
275
+ console.log(chalk.hex('#F59E0B')('\n [!] Connect a GitHub account before creating a repository.'));
276
276
  const loginUser = await promptGitHubLogin();
277
277
  if (!loginUser) {
278
278
  return { ok: false, back: true };
@@ -280,22 +280,22 @@ export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
280
280
  currentUser = { authenticated: true, user: loginUser };
281
281
  }
282
282
 
283
- printSection('TẠO REPOSITORY MỚI TRÊN GITHUB');
284
- console.log(chalk.hex('#94A3B8')(` » Đang tạo trên tài khoản: ${chalk.hex('#38BDF8').bold('@' + currentUser.user.login)}`));
283
+ printSection('CREATE A GITHUB REPOSITORY');
284
+ console.log(chalk.hex('#94A3B8')(` » Creating under account: ${chalk.hex('#38BDF8').bold('@' + currentUser.user.login)}`));
285
285
 
286
286
  const folderName = path.basename(path.resolve(targetDir))
287
287
  .toLowerCase()
288
288
  .replace(/[^a-z0-9_.-]/g, '-');
289
289
 
290
290
  const repoName = await inputWithBack({
291
- message: 'Tên repository mới',
292
- hint: ['Chỉ chữ, số, -, _ , . (VD: my-awesome-repo)'],
291
+ message: 'New repository name',
292
+ hint: ['Letters, numbers, -, _, and . only (e.g. my-awesome-repo)'],
293
293
  default: folderName || 'my-awesome-repo',
294
294
  validate: (val) => {
295
295
  const trimmed = (val || '').trim();
296
- if (!trimmed) return 'Tên repo không được để trống!';
296
+ if (!trimmed) return 'Repository name cannot be empty.';
297
297
  if (!/^[a-zA-Z0-9_.-]+$/.test(trimmed)) {
298
- return 'Tên repo chỉ chứa chữ, số, dấu gạch -, gạch dưới _ hoặc dấu chấm .';
298
+ return 'Use only letters, numbers, hyphens (-), underscores (_), or periods (.).';
299
299
  }
300
300
  return true;
301
301
  }
@@ -304,24 +304,24 @@ export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
304
304
  if (repoName === BACK) return { ok: false, back: true };
305
305
 
306
306
  const description = await inputWithBack({
307
- message: 'Mô tả ngắn cho dự án',
308
- hint: ['Tùy chọn, Enter để bỏ qua'],
307
+ message: 'Short project description',
308
+ hint: ['Optional; press Enter to skip'],
309
309
  default: ''
310
310
  });
311
311
  if (description === BACK) return { ok: false, back: true };
312
312
 
313
313
  const visibility = await selectWithBack({
314
- message: 'Quyền riêng tư của repository:',
314
+ message: 'Repository visibility:',
315
315
  choices: [
316
- { name: 'Public (Bất kỳ ai cũng có thể xem)', value: 'public', description: 'Mã nguồn mở công khai' },
317
- { name: 'Private (Chỉ bạn và người được phân quyền xem)', value: 'private', description: 'Kho lưu trữ riêng tư' }
316
+ { name: 'Public (Anyone can view it)', value: 'public', description: 'Public repository' },
317
+ { name: 'Private (Only you and collaborators can view it)', value: 'private', description: 'Private repository' }
318
318
  ]
319
319
  });
320
320
  if (visibility === BACK) return { ok: false, back: true };
321
321
 
322
322
  const isPrivate = visibility === 'private';
323
323
 
324
- const spinner = ora({ text: `Đang tạo repository '${repoName}' trên GitHub…`, color: 'cyan' }).start();
324
+ const spinner = ora({ text: `Creating '${repoName}' on GitHub…`, color: 'cyan' }).start();
325
325
  const createRes = await createGitHubRepo({
326
326
  name: repoName,
327
327
  description: String(description || ''),
@@ -330,36 +330,36 @@ export async function promptCreateRepo({ targetDir = process.cwd() } = {}) {
330
330
  });
331
331
 
332
332
  if (!createRes.ok) {
333
- spinner.fail(chalk.red('Không thể tạo repository!'));
334
- printErrorBox('TẠO REPO THẤT BẠI', [createRes.error]);
333
+ spinner.fail(chalk.red('Could not create repository.'));
334
+ printErrorBox('REPOSITORY CREATION FAILED', [createRes.error]);
335
335
  return { ok: false, error: createRes.error };
336
336
  }
337
337
 
338
- spinner.succeed(chalk.hex('#34D399')(`Đã tạo repo @${currentUser.user.login}/${repoName} thành công!`));
338
+ spinner.succeed(chalk.hex('#34D399')(`Created @${currentUser.user.login}/${repoName} successfully.`));
339
339
  printRepoCreatedBox(createRes.repo);
340
340
 
341
341
  return { ok: true, repo: createRes.repo };
342
342
  }
343
343
 
344
344
  /**
345
- * Hiển thị danh sách các repo gần nhất của user
345
+ * Display the user's most recently updated repositories.
346
346
  */
347
347
  export async function showUserRepos({ limit = 15 } = {}) {
348
- const spinner = ora({ text: 'Đang tải danh sách repository từ GitHub…', color: 'cyan' }).start();
348
+ const spinner = ora({ text: 'Loading repositories from GitHub…', color: 'cyan' }).start();
349
349
  const res = await listUserRepos({ limit });
350
350
  spinner.stop();
351
351
 
352
352
  if (!res.ok) {
353
- printErrorBox('KHÔNG TẢI ĐƯỢC DANH SÁCH REPO', [res.error]);
353
+ printErrorBox('COULD NOT LOAD REPOSITORIES', [res.error]);
354
354
  return [];
355
355
  }
356
356
 
357
357
  if (res.repos.length === 0) {
358
- console.log(chalk.hex('#94A3B8')(' » Bạn chưa có repository nào trên tài khoản này.\n'));
358
+ console.log(chalk.hex('#94A3B8')(' » This account does not have any repositories yet.\n'));
359
359
  return [];
360
360
  }
361
361
 
362
- printSection(`DANH SÁCH REPOSITORIES (${res.repos.length} REPO GẦN ĐÂY)`);
362
+ printSection(`REPOSITORIES (${res.repos.length} RECENT)`);
363
363
  res.repos.forEach((r, idx) => {
364
364
  const lock = r.private ? chalk.hex('#F59E0B')('[Private]') : chalk.hex('#10B981')('[Public ]');
365
365
  const num = chalk.hex('#64748B')(String(idx + 1).padStart(2, ' '));
@@ -373,43 +373,43 @@ export async function showUserRepos({ limit = 15 } = {}) {
373
373
  }
374
374
 
375
375
  /**
376
- * Đăng xuất tài khoản GitHub (mặc định: tài khoản đang active)
376
+ * Sign out of a GitHub account (defaults to the active account).
377
377
  */
378
378
  export function logoutGitHub(login = null) {
379
379
  const accounts = getAllGitHubAccounts();
380
380
  const target = login || accounts.find((a) => a.active)?.login || accounts[0]?.login || 'GitHub';
381
381
  removeGitHubToken(login);
382
- console.log(chalk.hex('#34D399')(`\n [OK] Đã đăng xuất @${target} và xóa token khỏi máy.\n`));
382
+ console.log(chalk.hex('#34D399')(`\n [OK] Signed out @${target} and removed the token from this machine.\n`));
383
383
  }
384
384
 
385
385
  /**
386
- * Menu tương tác quản lý GitHub
386
+ * Interactive GitHub management menu.
387
387
  */
388
388
  export async function runGitHubMenu() {
389
389
  while (true) {
390
390
  const userStatus = await getCurrentGitHubUser();
391
391
  const isLogged = userStatus.authenticated;
392
392
 
393
- printSection('QUẢN LÝ GITHUB & REPOSITORY');
393
+ printSection('GITHUB & REPOSITORIES');
394
394
  if (isLogged) {
395
- console.log(chalk.hex('#34D399')(` [✓] Đang đăng nhập: @${userStatus.user.login} (${userStatus.user.name || 'Developer'})`));
395
+ console.log(chalk.hex('#34D399')(` [✓] Signed in as @${userStatus.user.login} (${userStatus.user.name || 'Developer'})`));
396
396
  } else {
397
- console.log(chalk.hex('#F59E0B')(' [!] Chưa kết nối GitHub.'));
397
+ console.log(chalk.hex('#F59E0B')(' [!] GitHub is not connected.'));
398
398
  }
399
399
 
400
400
  const choices = [
401
- { name: 'Tài khoản', value: 'status', description: 'Xem profile và trạng thái kết nối' },
402
- { name: 'Danh sách repository', value: 'list', description: 'Duyệt repo cá nhân và URL clone' },
401
+ { name: 'Account', value: 'status', description: 'View profile and connection status' },
402
+ { name: 'Repositories', value: 'list', description: 'Browse your repositories and clone URLs' },
403
403
  isLogged
404
- ? { name: 'Đăng nhập tài khoản khác', value: 'login', description: 'Thêm hoặc chuyển tài khoản GitHub' }
405
- : { name: 'Đăng nhập', value: 'login', description: 'Kết nối bằng trình duyệt hoặc token' },
406
- ...(isLogged ? [{ name: 'Đăng xuất', value: 'logout', description: 'Xóa kết nối tài khoản đang dùng' }] : []),
407
- { name: 'Quay lại', value: BACK, description: 'Về bảng điều khiển' }
404
+ ? { name: 'Sign in to another account', value: 'login', description: 'Add or switch GitHub accounts' }
405
+ : { name: 'Sign in', value: 'login', description: 'Connect using a browser or token' },
406
+ ...(isLogged ? [{ name: 'Sign out', value: 'logout', description: 'Disconnect the current account' }] : []),
407
+ { name: 'Go back', value: BACK, description: 'Return to the dashboard' }
408
408
  ];
409
409
 
410
410
  const action = await selectBox({
411
- message: 'Chọn tác vụ GitHub:',
412
- hint: 'Tạo repo · xem profile · đổi tài khoản',
411
+ message: 'Choose a GitHub action:',
412
+ hint: 'View accounts · browse repositories · switch accounts',
413
413
  choices
414
414
  });
415
415
 
@@ -28,7 +28,7 @@ export async function runPullCommand(remote = 'origin', branch, options = {}) {
28
28
  if (options.dir) {
29
29
  const r = resolveTargetDir(options.dir, process.cwd());
30
30
  if (!r.ok) {
31
- printErrorBox('THƯ MỤC KHÔNG HỢP LỆ', [r.error]);
31
+ printErrorBox('INVALID DIRECTORY', [r.error]);
32
32
  if (fromMenu) return;
33
33
  process.exit(1);
34
34
  }
@@ -36,7 +36,7 @@ export async function runPullCommand(remote = 'origin', branch, options = {}) {
36
36
  }
37
37
 
38
38
  if (!isGitRepo(targetDir)) {
39
- printErrorBox('CHƯA PHẢI GIT REPOSITORY', [`Thư mục '${targetDir}' chưa phải Git repository.`]);
39
+ printErrorBox('NOT A GIT REPOSITORY', [`'${targetDir}' is not a Git repository.`]);
40
40
  if (fromMenu) return;
41
41
  process.exit(1);
42
42
  }
@@ -46,43 +46,43 @@ export async function runPullCommand(remote = 'origin', branch, options = {}) {
46
46
  }
47
47
 
48
48
  if (!branch) {
49
- printErrorBox('KHÔNG XÁC ĐỊNH ĐƯỢC BRANCH', ['Đang ở detached HEAD nên không biết pull branch nào.'], 'Checkout về branch chính rồi chạy lại.');
49
+ printErrorBox('CANNOT DETERMINE BRANCH', ['HEAD is detached, so the branch to pull is unknown.'], 'Check out a branch and try again.');
50
50
  if (fromMenu) return;
51
51
  process.exit(1);
52
52
  }
53
53
 
54
54
  const isInteractive = !options.yes;
55
55
  try {
56
- printSection(`PULL --ff-only ${remote}/${branch}`);
56
+ printSection(`PULL --FF-ONLY ${remote}/${branch}`);
57
57
  printTargetDir(targetDir);
58
58
  const result = pullFromRemote(remote, branch, targetDir);
59
- console.log(chalk.hex('#34D399')(` [OK] ${result || 'Repository đã cập nhật mới nhất (fast-forward).'}\n`));
59
+ console.log(chalk.hex('#34D399')(` [OK] ${result || 'Repository is up to date (fast-forward).'}\n`));
60
60
  if (!fromMenu) printFooter();
61
61
  } catch (error) {
62
- printErrorBox('PULL THẤT BẠI', [String(error.message).split('\n')[0]], 'Fast-forward thất bại khi lịch sử bị lệch. Có thể thử pull --rebase.');
62
+ printErrorBox('PULL FAILED', [String(error.message).split('\n')[0]], 'Fast-forward failed because the histories diverged. You can try pull --rebase.');
63
63
  if (!isInteractive) {
64
64
  if (fromMenu) return;
65
65
  process.exit(1);
66
66
  }
67
67
 
68
68
  const choice = await selectBox({
69
- message: 'Pull fast-forward thất bại. Bạn muốn làm gì tiếp?',
69
+ message: 'Fast-forward pull failed. What would you like to do?',
70
70
  hint: `git pull --rebase ${remote} ${branch}`,
71
71
  choices: [
72
- { name: 'Thử kéo về bằng pull --rebase', value: 'rebase', description: `git pull --rebase ${remote} ${branch}` },
73
- { name: 'Quay lại', value: 'back', description: 'Về màn hình trước' },
74
- { name: 'x Thoát', value: 'exit', description: 'Không làm gì thêm' }
72
+ { name: 'Try pulling with rebase', value: 'rebase', description: `git pull --rebase ${remote} ${branch}` },
73
+ { name: 'Go back', value: 'back', description: 'Return to the previous screen' },
74
+ { name: 'Exit', value: 'exit', description: 'Stop here' }
75
75
  ]
76
76
  });
77
77
 
78
78
  if (choice === 'rebase') {
79
79
  try {
80
- console.log(chalk.hex('#38BDF8')(`\n » Đang chạy: git pull --rebase ${remote} ${branch}...`));
80
+ console.log(chalk.hex('#38BDF8')(`\n » Running: git pull --rebase ${remote} ${branch}...`));
81
81
  const out = pullFromRemote(remote, branch, targetDir, { rebase: true });
82
- console.log(chalk.hex('#34D399')(` [OK] Pull --rebase thành công! ${String(out).split('\n')[0].slice(0, 100)}\n`));
82
+ console.log(chalk.hex('#34D399')(` [OK] Pull --rebase succeeded. ${String(out).split('\n')[0].slice(0, 100)}\n`));
83
83
  if (!fromMenu) printFooter();
84
84
  } catch (rebaseErr) {
85
- printErrorBox('PULL --REBASE THẤT BẠI', [String(rebaseErr.message).split('\n')[0]], 'Có thể bị xung đột code (conflict). Mở git status để giải quyết.');
85
+ printErrorBox('PULL --REBASE FAILED', [String(rebaseErr.message).split('\n')[0]], 'There may be merge conflicts. Run git status to review them.');
86
86
  if (!fromMenu) process.exit(1);
87
87
  }
88
88
  }