@localheroai/cli 0.0.17 → 0.0.19

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 (36) hide show
  1. package/README.md +28 -2
  2. package/dist/api/client.js +41 -11
  3. package/dist/api/client.js.map +1 -1
  4. package/dist/cli.js +2 -1
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/init.js +252 -127
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/translate.js +25 -13
  9. package/dist/commands/translate.js.map +1 -1
  10. package/dist/types/index.js.map +1 -1
  11. package/dist/utils/chunked-json-processor.js +52 -0
  12. package/dist/utils/chunked-json-processor.js.map +1 -0
  13. package/dist/utils/config.js +10 -4
  14. package/dist/utils/config.js.map +1 -1
  15. package/dist/utils/file-size.js +32 -0
  16. package/dist/utils/file-size.js.map +1 -0
  17. package/dist/utils/files.js +7 -1
  18. package/dist/utils/files.js.map +1 -1
  19. package/dist/utils/git-changes.js +204 -0
  20. package/dist/utils/git-changes.js.map +1 -0
  21. package/dist/utils/git-diff.js +251 -0
  22. package/dist/utils/git-diff.js.map +1 -0
  23. package/dist/utils/github.js +1 -0
  24. package/dist/utils/github.js.map +1 -1
  25. package/dist/utils/import-service.js +1 -1
  26. package/dist/utils/import-service.js.map +1 -1
  27. package/dist/utils/po-surgical.js +103 -131
  28. package/dist/utils/po-surgical.js.map +1 -1
  29. package/dist/utils/po-utils.js +73 -69
  30. package/dist/utils/po-utils.js.map +1 -1
  31. package/dist/utils/streaming-json-processor.js +125 -0
  32. package/dist/utils/streaming-json-processor.js.map +1 -0
  33. package/dist/utils/translation-processor.js +1 -1
  34. package/dist/utils/translation-utils.js +22 -5
  35. package/dist/utils/translation-utils.js.map +1 -1
  36. package/package.json +18 -18
package/README.md CHANGED
@@ -7,9 +7,9 @@ LocalHero.ai is an AI-powered I18n translation service that seamlessly integrate
7
7
  ## Features šŸš€
8
8
 
9
9
  - šŸ¤– AI-powered translations that preserve your brand voice
10
- - šŸ”Œ Seamless integration with Rails, React and other frameworks coming soon
10
+ - šŸ”Œ Seamless integration with with framworks like Rails, React, Django.
11
11
  - šŸš€ Automated workflow with GitHub Actions support
12
- - šŸ“¦ Works with YAML and JSON translation files
12
+ - šŸ“¦ Works with YAML, JSON, .po (experimental) translation files
13
13
 
14
14
  ## Getting Started šŸ
15
15
 
@@ -68,6 +68,30 @@ Translating your missing keys:
68
68
  - Updates translation files with any new or update translations
69
69
  - It's run manually or by GitHub Actions. When run as a GitHub action any new translations are automatically committed to git.
70
70
 
71
+ #### Options
72
+
73
+ **`--verbose`**: Enable verbose logging for the translation process.
74
+
75
+ **`--changed-only`**: Only translate keys that have changed in the current branch _[experimental]_
76
+
77
+ ```bash
78
+ npx @localheroai/cli translate --changed-only
79
+ ```
80
+
81
+ The command uses git to identify which keys have been added or modified in your translation files by comparing to your base branch. It then only translates those specific keys, not like the default, which finds all missing translations and translates them.
82
+
83
+ You can customize the base branch in your `localhero.json`:
84
+
85
+ ```json
86
+ {
87
+ "translationFiles": {
88
+ "paths": ["locales/"],
89
+ "baseBranch": "develop"
90
+ }
91
+ }
92
+ ```
93
+
94
+
71
95
  ### Pull / push
72
96
 
73
97
  ```bash
@@ -117,6 +141,8 @@ LocalHero.ai automatically translate your I18n files when you push changes. Duri
117
141
  - Run on push to pull requests
118
142
  - Check for missing translations and add new/updated translations to the repo.
119
143
 
144
+ 🚧 **Skip translations on a PR**: Add the `skip-translation` label to any PR to skip the translation workflow. Useful when you're still working on copy changes.
145
+
120
146
  ## Support šŸ’¬
121
147
 
122
148
  - Documentation: [localhero.ai/docs](https://localhero.ai/docs)
@@ -1,5 +1,12 @@
1
1
  import { ApiResponseError } from '../types/index.js';
2
2
  const DEFAULT_API_HOST = 'https://api.localhero.ai';
3
+ // Retry configuration for network errors
4
+ const RETRY_CONFIG = {
5
+ maxRetries: 5,
6
+ initialDelay: 1000, // 1 second
7
+ maxDelay: 8000, // 8 seconds
8
+ backoffFactor: 2
9
+ };
3
10
  export function getApiHost() {
4
11
  return process.env.LOCALHERO_API_HOST || DEFAULT_API_HOST;
5
12
  }
@@ -18,6 +25,39 @@ function getNetworkErrorMessage(error) {
18
25
  }
19
26
  return `Network error while connecting to ${getApiHost()}. Please check your internet connection and try again.`;
20
27
  }
28
+ function isRetryableError(error) {
29
+ if (error.code === 'ECONNREFUSED')
30
+ return true;
31
+ if (error.code === 'ECONNRESET')
32
+ return true;
33
+ if (error.code === 'ETIMEDOUT')
34
+ return true;
35
+ if (error.cause?.code === 'ENOTFOUND')
36
+ return true;
37
+ if (error.cause?.code === 'ETIMEDOUT')
38
+ return true;
39
+ if (error.cause?.code === 'ECONNRESET')
40
+ return true;
41
+ return false;
42
+ }
43
+ async function fetchWithRetry(url, options, retryCount = 0) {
44
+ try {
45
+ return await fetch(url, options);
46
+ }
47
+ catch (error) {
48
+ const networkError = error;
49
+ if (isRetryableError(networkError) && retryCount < RETRY_CONFIG.maxRetries) {
50
+ const delay = Math.min(RETRY_CONFIG.initialDelay * Math.pow(RETRY_CONFIG.backoffFactor, retryCount), RETRY_CONFIG.maxDelay);
51
+ console.log(`Network error, retrying in ${delay / 1000}s... (attempt ${retryCount + 1}/${RETRY_CONFIG.maxRetries})`);
52
+ await sleep(delay / 1000);
53
+ return fetchWithRetry(url, options, retryCount + 1);
54
+ }
55
+ const message = getNetworkErrorMessage(networkError);
56
+ networkError.message = message;
57
+ networkError.cliErrorMessage = message;
58
+ throw networkError;
59
+ }
60
+ }
21
61
  export async function apiRequest(endpoint, options = {}) {
22
62
  const apiHost = getApiHost();
23
63
  const url = `${apiHost}${endpoint}`;
@@ -36,17 +76,7 @@ export async function apiRequest(endpoint, options = {}) {
36
76
  if (options.body) {
37
77
  fetchOptions.body = JSON.stringify(options.body);
38
78
  }
39
- let response;
40
- try {
41
- response = await fetch(url, fetchOptions);
42
- }
43
- catch (error) {
44
- const networkError = error;
45
- const message = getNetworkErrorMessage(networkError);
46
- networkError.message = message;
47
- networkError.cliErrorMessage = message;
48
- throw networkError;
49
- }
79
+ const response = await fetchWithRetry(url, fetchOptions);
50
80
  let data;
51
81
  try {
52
82
  data = await response.json();
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -66,7 +66,8 @@ program
66
66
  .command('translate')
67
67
  .description('Translate missing keys in your i18n files')
68
68
  .option('-v, --verbose', 'Show detailed progress information')
69
- .option('-c, --commit', 'Automatically commit changes (useful for CI/CD)')
69
+ .option('-c, --commit', 'Automatically commit changes (for CI/CD)')
70
+ .option('--changed-only', 'Only translate keys changed in current branch (experimental)')
70
71
  .action(wrapCommandAction((options) => translate(options)));
71
72
  program
72
73
  .command('pull')
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -257,6 +257,234 @@ async function detectProjectType() {
257
257
  }
258
258
  };
259
259
  }
260
+ async function validateExistingConfig(config) {
261
+ const requiredFields = ['projectId', 'sourceLocale', 'outputLocales'];
262
+ const missingFields = requiredFields.filter(field => !config[field]);
263
+ return {
264
+ isValid: missingFields.length === 0,
265
+ missingFields
266
+ };
267
+ }
268
+ async function handleImportProcess(config, basePath, importUtils, console, configUtils) {
269
+ console.log('\nSearching for translation files...');
270
+ try {
271
+ const importResult = await importUtils.importTranslations(config, basePath);
272
+ if (importResult.status === 'no_files') {
273
+ console.log(chalk.yellow('No translation files found.'));
274
+ return { success: true, hasWarnings: true };
275
+ }
276
+ if ((importResult.status === 'completed' || importResult.status === 'success') && importResult.statistics) {
277
+ console.log(chalk.green('\nāœ“ Successfully imported translations'));
278
+ console.log('\nImport summary:');
279
+ if (importResult.files) {
280
+ const sourceCount = importResult.files.source.length;
281
+ const targetCount = importResult.files.target.length;
282
+ console.log(`- ${sourceCount + targetCount} translation files imported`);
283
+ console.log(`- ${sourceCount} source files (${config.sourceLocale})`);
284
+ console.log(`- ${targetCount} target files (${config.outputLocales.join(', ')})`);
285
+ }
286
+ const stats = importResult.statistics;
287
+ if (stats) {
288
+ console.log(`- Total keys: ${stats.total_keys || 0}`);
289
+ if (Array.isArray(stats.languages)) {
290
+ const validLanguages = stats.languages.filter(lang => lang.code);
291
+ console.log(`- Languages: ${validLanguages.length}`);
292
+ validLanguages.forEach((lang) => {
293
+ console.log(` - ${lang.code}: ${lang.translated || 0} keys (${lang.missing || 0} missing)`);
294
+ });
295
+ }
296
+ }
297
+ if (importResult.warnings && importResult.warnings.length > 0) {
298
+ console.log(`\n${chalk.yellow('⚠')} Warnings:`);
299
+ importResult.warnings.forEach((warning) => {
300
+ const message = typeof warning === 'string' ? warning : warning.message || 'Unknown warning';
301
+ console.log(`- ${message}`);
302
+ });
303
+ }
304
+ if (importResult.translations_url) {
305
+ console.log(chalk.green(`\nView your translations at: ${importResult.translations_url}`));
306
+ }
307
+ await configUtils.updateLastSyncedAt(basePath);
308
+ return { success: true, hasWarnings: (importResult.warnings?.length || 0) > 0 };
309
+ }
310
+ if (importResult.status === 'failed' || importResult.status === 'error') {
311
+ console.log(chalk.red('āœ— Failed to import translations'));
312
+ console.log(chalk.red(`Error: ${importResult.error || 'Import failed'}`));
313
+ return { success: false, hasWarnings: false };
314
+ }
315
+ return { success: true, hasWarnings: false };
316
+ }
317
+ catch (error) {
318
+ const errorMessage = error instanceof Error ? error.message : 'Import failed';
319
+ console.log(chalk.red('āœ— Failed to import translations'));
320
+ console.log(chalk.red(`Error: ${errorMessage}`));
321
+ return { success: false, hasWarnings: false };
322
+ }
323
+ }
324
+ async function handleGitHubWorkflowSetup(basePath, translationPaths, promptService, console) {
325
+ if (workflowExists(basePath)) {
326
+ console.log(chalk.green('āœ“ GitHub Actions workflow found'));
327
+ console.log(chalk.yellow('\nāš ļø Remember to add your API key to repository secrets:'));
328
+ console.log(' Name: LOCALHERO_API_KEY');
329
+ console.log(' Value: Get from https://localhero.ai/api-keys');
330
+ console.log(' Location: Repository Settings → Secrets and variables → Actions (On GitHub repo page)\n');
331
+ return { created: false };
332
+ }
333
+ const shouldSetupGitHubAction = await promptService.confirm({
334
+ message: 'Would you like to set up GitHub Actions for automatic translations?',
335
+ default: true
336
+ });
337
+ if (!shouldSetupGitHubAction) {
338
+ return { created: false };
339
+ }
340
+ try {
341
+ const workflowFile = await createGitHubActionFile(basePath, translationPaths);
342
+ console.log(chalk.green(`\nāœ“ Created GitHub Action workflow at ${workflowFile}`));
343
+ console.log('\nNext steps:');
344
+ console.log('1. Add your API key to your repository\'s secrets:');
345
+ console.log(' - Go to Settings > Secrets and variables > Actions > New repository secret');
346
+ console.log(' - Name: LOCALHERO_API_KEY');
347
+ console.log(' - Value: [Your API Key] (find this at https://localhero.ai/api-keys)');
348
+ console.log('2. Commit and push the workflow file to enable automatic translations\n');
349
+ return { created: true };
350
+ }
351
+ catch (error) {
352
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
353
+ console.log(chalk.yellow('Failed to create GitHub Action workflow:'), errorMessage);
354
+ return { created: false, error: errorMessage };
355
+ }
356
+ }
357
+ function displayFinalInstructions(workflowCreated, workflowExists, hasErrors, console) {
358
+ if (hasErrors) {
359
+ return;
360
+ }
361
+ console.log('\nšŸŽ‰ Setup complete!');
362
+ if (workflowCreated) {
363
+ console.log('\nšŸ“ Don\'t forget to commit and push the new workflow file.');
364
+ }
365
+ if (workflowExists) {
366
+ console.log('\nTranslations will run automatically on pull requests, or manually with:');
367
+ }
368
+ else {
369
+ console.log('\nYou can run translations manually with:');
370
+ }
371
+ console.log(' npx @localheroai/cli translate');
372
+ }
373
+ async function handleExistingConfiguration(existingConfig, deps) {
374
+ const { console, basePath, promptService, authUtils, projectApi, login: loginFn, importUtils, configUtils } = deps;
375
+ let workflowCreated = false;
376
+ console.log(chalk.green('āœ“ Configuration found! Let\'s verify and set up your API access.\n'));
377
+ const validation = await validateExistingConfig(existingConfig);
378
+ if (!validation.isValid) {
379
+ console.log(chalk.red(`āœ— Invalid configuration: missing fields ${validation.missingFields.join(', ')}`));
380
+ console.log(chalk.yellow('Please check your localhero.json file and try again.\n'));
381
+ return;
382
+ }
383
+ console.log(chalk.blue('šŸ“‹ Project Configuration:'));
384
+ console.log(` Project ID: ${existingConfig.projectId}`);
385
+ console.log(` Source: ${existingConfig.sourceLocale}`);
386
+ console.log(` Targets: ${existingConfig.outputLocales.join(', ')}`);
387
+ console.log(` Pattern: ${existingConfig.translationFiles?.pattern || 'not specified'}\n`);
388
+ const isAuthenticated = await authUtils.checkAuth();
389
+ if (!isAuthenticated) {
390
+ console.log('Let\'s connect to your LocalHero account.');
391
+ await loginFn({
392
+ console,
393
+ basePath,
394
+ promptService,
395
+ configUtils: deps.configUtils,
396
+ verifyApiKey: authUtils.verifyApiKey,
397
+ isCalledFromInit: true
398
+ });
399
+ }
400
+ else {
401
+ console.log(chalk.green('āœ“ API key found and valid'));
402
+ try {
403
+ const projects = await projectApi.listProjects();
404
+ const projectExists = projects.some(p => p.id === existingConfig.projectId);
405
+ if (!projectExists) {
406
+ console.log(chalk.red(`āœ— Project ${existingConfig.projectId} not found in your organization`));
407
+ console.log(chalk.yellow('Please check your localhero.json file or contact support.\n'));
408
+ return;
409
+ }
410
+ console.log(chalk.green('āœ“ Project access verified'));
411
+ }
412
+ catch {
413
+ console.log(chalk.yellow('āš ļø Could not verify project access. Continuing anyway...\n'));
414
+ }
415
+ }
416
+ // Check if we've previously imported files
417
+ if (existingConfig.lastSyncedAt) {
418
+ console.log(chalk.green('āœ“ Translation files previously imported'));
419
+ }
420
+ else {
421
+ const shouldImport = await promptService.confirm({
422
+ message: 'Would you like to import existing translation files? (recommended)',
423
+ default: true
424
+ });
425
+ if (shouldImport) {
426
+ await handleImportProcess(existingConfig, basePath, importUtils, console, configUtils);
427
+ }
428
+ }
429
+ const workflowResult = await handleGitHubWorkflowSetup(basePath, existingConfig.translationFiles?.paths || [''], promptService, console);
430
+ workflowCreated = workflowResult.created;
431
+ displayFinalInstructions(workflowCreated, workflowExists(basePath), false, console);
432
+ }
433
+ async function handleNewProjectSetup(deps) {
434
+ const { console, basePath, promptService, authUtils, projectApi, login: loginFn, importUtils, configUtils } = deps;
435
+ const isAuthenticated = await authUtils.checkAuth();
436
+ if (!isAuthenticated) {
437
+ console.log('LocalHero.ai - Automate your i18n translations\n');
438
+ console.log(chalk.yellow('No API key found. Let\'s get you authenticated.'));
439
+ await loginFn({
440
+ console,
441
+ basePath,
442
+ promptService,
443
+ configUtils,
444
+ verifyApiKey: authUtils.verifyApiKey || verifyApiKey,
445
+ isCalledFromInit: true
446
+ });
447
+ }
448
+ console.log('\nLet\'s set up configuration for your project.\n');
449
+ let workflowCreated = false;
450
+ const projectDefaults = await detectProjectType();
451
+ const answers = await promptForConfig(projectDefaults, projectApi, promptService, console);
452
+ if (!answers) {
453
+ return;
454
+ }
455
+ const config = {
456
+ schemaVersion: '1.0',
457
+ projectId: answers.projectId,
458
+ sourceLocale: answers.sourceLocale,
459
+ outputLocales: answers.outputLocales,
460
+ translationFiles: {
461
+ paths: answers.translationPath ? [answers.translationPath] : [],
462
+ pattern: answers.filePattern || '**/*.{json,yml,yaml,po}',
463
+ ignore: answers.ignorePaths || [],
464
+ ...(projectDefaults.defaults.workflow && { workflow: projectDefaults.defaults.workflow })
465
+ },
466
+ lastSyncedAt: null
467
+ };
468
+ await configUtils.saveProjectConfig(config, basePath);
469
+ console.log(chalk.green('\nāœ“ Created localhero.json'));
470
+ if (answers.newProject) {
471
+ console.log(chalk.green(`āœ“ Project created, view it at: ${answers.url}\n`));
472
+ }
473
+ const workflowResult = await handleGitHubWorkflowSetup(basePath, answers.translationPath ? [answers.translationPath] : [''], promptService, console);
474
+ workflowCreated = workflowResult.created;
475
+ const shouldImport = await promptService.confirm({
476
+ message: 'Would you like to import existing translation files? (recommended)',
477
+ default: true
478
+ });
479
+ let hasErrors = false;
480
+ if (shouldImport) {
481
+ console.log('\nSearching for translation files in:');
482
+ console.log(`${config.translationFiles.paths.join(', ')}`);
483
+ const importResult = await handleImportProcess(config, basePath, importUtils, console, configUtils);
484
+ hasErrors = !importResult.success;
485
+ }
486
+ displayFinalInstructions(workflowCreated, workflowExists(basePath), hasErrors, console);
487
+ }
260
488
  async function promptForConfig(projectDefaults, projectService, promptService, console = global.console) {
261
489
  const { choice: projectChoice, project: existingProject } = await promptService.selectProject(projectService);
262
490
  if (!projectChoice) {
@@ -265,7 +493,7 @@ async function promptForConfig(projectDefaults, projectService, promptService, c
265
493
  let projectId = projectChoice;
266
494
  let newProject = null;
267
495
  let projectUrl = null;
268
- let config = await promptService.getProjectSetup();
496
+ let config;
269
497
  if (!existingProject) {
270
498
  config = {
271
499
  projectName: await promptService.input({
@@ -284,10 +512,12 @@ async function promptForConfig(projectDefaults, projectService, promptService, c
284
512
  };
285
513
  }
286
514
  else {
515
+ // existingProject is guaranteed to exist here since !existingProject was false
516
+ const project = existingProject;
287
517
  config = {
288
- projectName: existingProject.name,
289
- sourceLocale: existingProject.source_language,
290
- outputLocales: existingProject.target_languages
518
+ projectName: project.name,
519
+ sourceLocale: project.source_language,
520
+ outputLocales: project.target_languages
291
521
  };
292
522
  }
293
523
  const commonPaths = projectDefaults.defaults.commonPaths || [];
@@ -328,13 +558,15 @@ async function promptForConfig(projectDefaults, projectService, promptService, c
328
558
  projectUrl = newProject.url;
329
559
  }
330
560
  catch (error) {
331
- console.log(chalk.red(`\nāœ— Failed to create project: ${error.message}`));
561
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
562
+ console.log(chalk.red(`\nāœ— Failed to create project: ${errorMessage}`));
332
563
  return null;
333
564
  }
334
565
  }
335
566
  else {
336
- projectId = existingProject.id;
337
- projectUrl = existingProject.url;
567
+ const project = existingProject;
568
+ projectId = project.id;
569
+ projectUrl = project.url;
338
570
  }
339
571
  return {
340
572
  projectId,
@@ -349,129 +581,22 @@ async function promptForConfig(projectDefaults, projectService, promptService, c
349
581
  }
350
582
  export async function init(deps = {}) {
351
583
  const { console = global.console, basePath = process.cwd(), promptService = createPromptService({ inquirer: await import('@inquirer/prompts') }), configUtils = configService, authUtils = { checkAuth }, importUtils = importService, projectApi = { createProject, listProjects }, login: loginFn = login } = deps;
584
+ const requiredDeps = {
585
+ console,
586
+ basePath,
587
+ promptService,
588
+ configUtils,
589
+ authUtils,
590
+ importUtils,
591
+ projectApi,
592
+ login: loginFn
593
+ };
352
594
  const existingConfig = await configUtils.getProjectConfig(basePath);
353
595
  if (existingConfig) {
354
- console.log(chalk.yellow('Existing configuration found in localhero.json. Skipping initialization.'));
355
- return;
596
+ await handleExistingConfiguration(existingConfig, requiredDeps);
356
597
  }
357
- const isAuthenticated = await authUtils.checkAuth();
358
- if (!isAuthenticated) {
359
- console.log('LocalHero.ai - Automate your i18n translations\n');
360
- console.log(chalk.yellow('No API key found. Let\'s get you authenticated.'));
361
- await loginFn({
362
- console,
363
- basePath,
364
- promptService,
365
- configUtils,
366
- verifyApiKey,
367
- isCalledFromInit: true
368
- });
369
- }
370
- console.log('\nLet\'s set up configuration for your project.\n');
371
- const projectDefaults = await detectProjectType();
372
- const answers = await promptForConfig(projectDefaults, projectApi, promptService, console);
373
- if (!answers) {
374
- return;
375
- }
376
- const config = {
377
- schemaVersion: '1.0',
378
- projectId: answers.projectId,
379
- sourceLocale: answers.sourceLocale,
380
- outputLocales: answers.outputLocales,
381
- translationFiles: {
382
- paths: answers.translationPath ? [answers.translationPath] : [],
383
- pattern: answers.filePattern || '**/*.{json,yml,yaml,po}',
384
- ignore: answers.ignorePaths || [],
385
- ...(projectDefaults.defaults.workflow && { workflow: projectDefaults.defaults.workflow })
386
- },
387
- lastSyncedAt: null
388
- };
389
- await configUtils.saveProjectConfig(config, basePath);
390
- console.log(chalk.green('\nāœ“ Created localhero.json'));
391
- if (answers.newProject) {
392
- console.log(chalk.green(`āœ“ Project created, view it at: ${answers.url}\n`));
393
- }
394
- if (!workflowExists(basePath)) {
395
- const shouldSetupGitHubAction = await promptService.confirm({
396
- message: 'Would you like to set up GitHub Actions for automatic translations?',
397
- default: true
398
- });
399
- if (shouldSetupGitHubAction) {
400
- try {
401
- const paths = answers.translationPath ? [answers.translationPath] : [''];
402
- const workflowFile = await createGitHubActionFile(basePath, paths);
403
- console.log(chalk.green(`\nāœ“ Created GitHub Action workflow at ${workflowFile}`));
404
- console.log('\nNext steps:');
405
- console.log('1. Add your API key to your repository\'s secrets:');
406
- console.log(' - Go to Settings > Secrets and variables > Actions > New repository secret');
407
- console.log(' - Name: LOCALHERO_API_KEY');
408
- console.log(' - Value: [Your API Key] (find this at https://localhero.ai/api-keys or in your local .localhero_key file)');
409
- console.log('\n2. Commit and push the workflow file to enable automatic translations\n');
410
- }
411
- catch (error) {
412
- console.log(chalk.yellow('\nFailed to create GitHub Action workflow:'), error.message);
413
- }
414
- }
415
- }
416
- const shouldImport = await promptService.confirm({
417
- message: 'Would you like to import existing translation files? (recommended)',
418
- default: true
419
- });
420
- let hasErrors = false;
421
- if (shouldImport) {
422
- console.log('\nSearching for translation files in:');
423
- console.log(`${config.translationFiles.paths.join(', ')}`);
424
- try {
425
- const importResult = await importUtils.importTranslations(config, basePath);
426
- if (importResult.status === 'no_files') {
427
- console.log(chalk.yellow('\nNo translation files found.'));
428
- }
429
- else if ((importResult.status === 'completed' || importResult.status === 'success') && importResult.statistics) {
430
- console.log(chalk.green('\nāœ“ Successfully imported translations'));
431
- console.log('\nImport summary:');
432
- if (importResult.files) {
433
- const sourceCount = importResult.files.source.length;
434
- const targetCount = importResult.files.target.length;
435
- console.log(`- ${sourceCount + targetCount} translation files imported`);
436
- console.log(`- ${sourceCount} source files (${config.sourceLocale})`);
437
- console.log(`- ${targetCount} target files (${config.outputLocales.join(', ')})`);
438
- }
439
- const stats = importResult.statistics;
440
- if (stats) {
441
- console.log(`- Total keys: ${stats.total_keys || 0}`);
442
- if (Array.isArray(stats.languages)) {
443
- const validLanguages = stats.languages.filter(lang => lang.code);
444
- console.log(`- Languages: ${validLanguages.length}`);
445
- validLanguages.forEach((lang) => {
446
- console.log(` - ${lang.code}: ${lang.translated || 0} keys (${lang.missing || 0} missing)`);
447
- });
448
- }
449
- }
450
- if (importResult.warnings && importResult.warnings.length > 0) {
451
- console.log(`\n${chalk.yellow('⚠')} Warnings:`);
452
- importResult.warnings.forEach((warning) => {
453
- const message = typeof warning === 'string' ? warning : warning.message || 'Unknown warning';
454
- console.log(`- ${message}`);
455
- });
456
- }
457
- if (importResult.translations_url) {
458
- console.log(chalk.green(`\nView your translations at: ${importResult.translations_url}`));
459
- }
460
- }
461
- else if (importResult.status === 'failed' || importResult.status === 'error') {
462
- console.log(chalk.red('āœ— Failed to import translations'));
463
- console.log(chalk.red(`Error: ${importResult.error || 'Import failed'}`));
464
- hasErrors = true;
465
- }
466
- }
467
- catch (error) {
468
- console.log(chalk.red('āœ— Failed to import translations'));
469
- console.log(chalk.red(`Error: ${error.message || 'Import failed'}`));
470
- hasErrors = true;
471
- }
472
- }
473
- if (!hasErrors) {
474
- console.log('\nšŸš€ Done! Start translating with: npx @localheroai/cli translate');
598
+ else {
599
+ await handleNewProjectSetup(requiredDeps);
475
600
  }
476
601
  }
477
602
  //# sourceMappingURL=init.js.map