@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.
- package/README.md +28 -2
- package/dist/api/client.js +41 -11
- package/dist/api/client.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.js +252 -127
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/translate.js +25 -13
- package/dist/commands/translate.js.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/chunked-json-processor.js +52 -0
- package/dist/utils/chunked-json-processor.js.map +1 -0
- package/dist/utils/config.js +10 -4
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/file-size.js +32 -0
- package/dist/utils/file-size.js.map +1 -0
- package/dist/utils/files.js +7 -1
- package/dist/utils/files.js.map +1 -1
- package/dist/utils/git-changes.js +204 -0
- package/dist/utils/git-changes.js.map +1 -0
- package/dist/utils/git-diff.js +251 -0
- package/dist/utils/git-diff.js.map +1 -0
- package/dist/utils/github.js +1 -0
- package/dist/utils/github.js.map +1 -1
- package/dist/utils/import-service.js +1 -1
- package/dist/utils/import-service.js.map +1 -1
- package/dist/utils/po-surgical.js +103 -131
- package/dist/utils/po-surgical.js.map +1 -1
- package/dist/utils/po-utils.js +73 -69
- package/dist/utils/po-utils.js.map +1 -1
- package/dist/utils/streaming-json-processor.js +125 -0
- package/dist/utils/streaming-json-processor.js.map +1 -0
- package/dist/utils/translation-processor.js +1 -1
- package/dist/utils/translation-utils.js +22 -5
- package/dist/utils/translation-utils.js.map +1 -1
- 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
|
|
10
|
+
- š Seamless integration with with framworks like Rails, React, Django.
|
|
11
11
|
- š Automated workflow with GitHub Actions support
|
|
12
|
-
- š¦ Works with YAML
|
|
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)
|
package/dist/api/client.js
CHANGED
|
@@ -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
|
-
|
|
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();
|
package/dist/api/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAEpD,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,gBAAgB,CAAC;AAC5D,CAAC;AAED,SAAS,KAAK,CAAC,OAAe;IAC5B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;AACrE,CAAC;AAiBD,SAAS,sBAAsB,CAAC,KAAmB;IACjD,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;QAClC,OAAO,wBAAwB,UAAU,EAAE,wDAAwD,CAAC;IACtG,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,WAAW,EAAE,CAAC;QACtC,OAAO,0BAA0B,UAAU,EAAE,wDAAwD,CAAC;IACxG,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,WAAW,EAAE,CAAC;QACtC,OAAO,iBAAiB,UAAU,EAAE,qCAAqC,CAAC;IAC5E,CAAC;IACD,OAAO,qCAAqC,UAAU,EAAE,wDAAwD,CAAC;AACnH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAU,QAAgB,EAAE,UAA6B,EAAE;IACzF,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,QAAQ,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAE/D,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,kBAAkB;QAClC,GAAG,OAAO,CAAC,OAAO;KACnB,CAAC;IAEF,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,MAAM,EAAE,CAAC;IAChD,CAAC;IAED,MAAM,YAAY,GAAgB;QAChC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,OAAO;KACR,CAAC;IAEF,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,YAAY,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,
|
|
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 (
|
|
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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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
|
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:
|
|
289
|
-
sourceLocale:
|
|
290
|
-
outputLocales:
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
|
|
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
|
-
|
|
355
|
-
return;
|
|
596
|
+
await handleExistingConfiguration(existingConfig, requiredDeps);
|
|
356
597
|
}
|
|
357
|
-
|
|
358
|
-
|
|
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
|