@localheroai/cli 0.0.2 → 0.0.5

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.
@@ -2,284 +2,484 @@ import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { createPromptService } from '../utils/prompt-service.js';
5
- import { defaultProjectService } from '../utils/project-service.js';
5
+ import { createProject, listProjects } from '../api/projects.js';
6
6
  import { configService } from '../utils/config.js';
7
7
  import { checkAuth } from '../utils/auth.js';
8
8
  import { login } from './login.js';
9
9
  import { importService } from '../utils/import-service.js';
10
10
  import { createGitHubActionFile } from '../utils/github.js';
11
+ import { directoryExists, findFirstExistingPath, getDirectoryContents } from '../utils/files.js';
11
12
 
12
13
  const PROJECT_TYPES = {
13
- rails: {
14
- indicators: ['config/application.rb', 'Gemfile'],
15
- defaults: {
16
- translationPath: 'config/locales/',
17
- filePattern: '**/*.{yml,yaml}'
18
- }
14
+ rails: {
15
+ directIndicators: ['config/application.rb', 'Gemfile'],
16
+ defaults: {
17
+ translationPath: 'config/locales/',
18
+ filePattern: '**/*.{yml,yaml}'
19
19
  },
20
- react: {
21
- indicators: ['package.json', 'src/locales', 'public/locales'],
22
- defaults: {
23
- translationPath: 'src/locales/',
24
- filePattern: '**/*.{json,yml}'
25
- }
20
+ commonPaths: [
21
+ 'config/locales'
22
+ ]
23
+ },
24
+ nextjs: {
25
+ directIndicators: ['next.config.js', 'next.config.mjs'],
26
+ packageCheck: {
27
+ requires: ['next'],
28
+ oneOf: ['next-i18next', 'next-translate']
26
29
  },
27
- generic: {
28
- indicators: [],
29
- defaults: {
30
- translationPath: 'locales/',
31
- filePattern: '**/*.{json,yml,yaml}'
32
- }
33
- }
30
+ defaults: {
31
+ translationPath: 'public/locales/',
32
+ filePattern: '**/*.json'
33
+ },
34
+ commonPaths: [
35
+ 'public/locales',
36
+ 'src/locales',
37
+ 'locales'
38
+ ]
39
+ },
40
+ vueI18n: {
41
+ directIndicators: ['vue.config.js'],
42
+ packageCheck: {
43
+ oneOf: ['vue-i18n', '@nuxtjs/i18n']
44
+ },
45
+ defaults: {
46
+ translationPath: 'src/locales/',
47
+ filePattern: '**/*.json'
48
+ },
49
+ commonPaths: [
50
+ 'src/locales',
51
+ 'src/i18n',
52
+ 'locales',
53
+ 'i18n'
54
+ ]
55
+ },
56
+ i18next: {
57
+ directIndicators: ['i18next.config.js', 'i18n.js', 'i18n/index.js'],
58
+ packageCheck: {
59
+ requires: ['i18next']
60
+ },
61
+ defaults: {
62
+ translationPath: 'public/locales/',
63
+ filePattern: '**/*.json'
64
+ },
65
+ commonPaths: [
66
+ 'public/locales',
67
+ 'src/locales',
68
+ 'locales',
69
+ 'src/i18n',
70
+ 'i18n'
71
+ ]
72
+ },
73
+ reactIntl: {
74
+ directIndicators: ['.babelrc'],
75
+ packageCheck: {
76
+ requires: ['react-intl']
77
+ },
78
+ defaults: {
79
+ translationPath: 'src/translations/',
80
+ filePattern: '**/*.json'
81
+ },
82
+ commonPaths: [
83
+ 'src/i18n',
84
+ 'src/translations',
85
+ 'src/lang',
86
+ 'src/locales',
87
+ 'translations',
88
+ 'locales'
89
+ ]
90
+ },
91
+ gatsbyReact: {
92
+ directIndicators: ['gatsby-config.js'],
93
+ packageCheck: {
94
+ requires: ['gatsby'],
95
+ oneOf: ['gatsby-plugin-intl', 'gatsby-plugin-i18n']
96
+ },
97
+ defaults: {
98
+ translationPath: 'src/data/i18n/',
99
+ filePattern: '**/*.json'
100
+ },
101
+ commonPaths: [
102
+ 'src/data/i18n',
103
+ 'src/i18n',
104
+ 'src/locales',
105
+ 'locales'
106
+ ]
107
+ },
108
+ react: {
109
+ directIndicators: ['src/App.js', 'src/App.jsx', 'src/index.js', 'src/index.jsx'],
110
+ packageCheck: {
111
+ requires: ['react']
112
+ },
113
+ defaults: {
114
+ translationPath: 'src/locales/',
115
+ filePattern: '**/*.{json,yml}'
116
+ },
117
+ commonPaths: [
118
+ 'src/locales',
119
+ 'public/locales',
120
+ 'src/i18n',
121
+ 'src/translations',
122
+ 'src/lang',
123
+ 'assets/i18n',
124
+ 'locales'
125
+ ]
126
+ },
127
+ generic: {
128
+ directIndicators: [],
129
+ defaults: {
130
+ translationPath: 'locales/',
131
+ filePattern: '**/*.{json,yml,yaml}'
132
+ },
133
+ commonPaths: [
134
+ 'locales',
135
+ 'src/locales',
136
+ 'public/locales',
137
+ 'src/i18n',
138
+ 'src/translations',
139
+ 'src/lang',
140
+ 'assets/i18n',
141
+ 'i18n',
142
+ 'translations',
143
+ 'lang'
144
+ ]
145
+ }
34
146
  };
35
147
 
36
- async function detectProjectType() {
37
- for (const [type, config] of Object.entries(PROJECT_TYPES)) {
38
- try {
39
- for (const indicator of config.indicators) {
40
- await fs.access(indicator);
41
- return { type, defaults: config.defaults };
42
- }
43
- } catch {
44
- continue;
45
- }
46
- }
47
- return {
48
- type: 'generic',
49
- defaults: PROJECT_TYPES.generic.defaults
50
- };
148
+ async function checkPackageJson() {
149
+ try {
150
+ const content = await fs.readFile('package.json', 'utf8');
151
+ const pkg = JSON.parse(content);
152
+ return { ...pkg.dependencies, ...pkg.devDependencies };
153
+ } catch {
154
+ return null;
155
+ }
51
156
  }
52
157
 
53
- async function checkExistingConfig() {
158
+ async function detectFramework(config) {
159
+ for (const indicator of config.directIndicators || []) {
54
160
  try {
55
- await fs.access('localhero.json');
56
- return true;
161
+ const stats = await fs.stat(indicator);
162
+ if (stats.isFile()) return true;
57
163
  } catch {
58
- return false;
164
+ continue;
59
165
  }
60
- }
166
+ }
61
167
 
62
- async function selectProject(projectService, promptService) {
63
- const projects = await projectService.listProjects();
168
+ if (config.packageCheck) {
169
+ const deps = await checkPackageJson();
170
+ if (deps) {
171
+ const { requires = [], oneOf = [] } = config.packageCheck;
64
172
 
65
- if (!projects || projects.length === 0) {
66
- return { choice: 'new' };
67
- }
173
+ if (requires.length && !requires.every(pkg => deps[pkg])) {
174
+ return false;
175
+ }
68
176
 
69
- const choices = [
70
- { name: '✨ Create new project', value: 'new' },
71
- { name: '─────────────', value: 'separator', disabled: true },
72
- ...projects.map(p => ({
73
- name: p.name,
74
- value: p.id
75
- }))
76
- ];
77
-
78
- const projectChoice = await promptService.select({
79
- message: 'Would you like to use an existing project or create a new one?',
80
- choices
81
- });
177
+ if (oneOf.length && !oneOf.some(pkg => deps[pkg])) {
178
+ return false;
179
+ }
82
180
 
83
- return {
84
- choice: projectChoice,
85
- project: projects.find(p => p.id === projectChoice)
86
- };
87
- }
181
+ return true;
182
+ }
183
+ }
88
184
 
89
- async function promptForConfig(projectDefaults, projectService, promptService) {
90
- const { choice: projectChoice, project: existingProject } = await selectProject(projectService, promptService);
91
- let projectId = projectChoice;
92
- let newProject = false;
93
- let config = await promptService.getProjectSetup();
94
-
95
- if (!existingProject) {
96
- config = {
97
- projectName: await promptService.input({
98
- message: 'Project name:',
99
- default: path.basename(process.cwd()),
100
- }),
101
- sourceLocale: await promptService.input({
102
- message: 'Source language code:',
103
- default: 'en'
104
- }),
105
- outputLocales: (await promptService.input({
106
- message: 'Target languages (comma-separated):',
107
- })).split(',').map(lang => lang.trim()).filter(Boolean)
108
- };
185
+ return false;
186
+ }
109
187
 
110
- newProject = await projectService.createProject({
111
- name: config.projectName,
112
- sourceLocale: config.sourceLocale,
113
- targetLocales: config.outputLocales
114
- });
115
- projectId = newProject.id;
116
- } else {
117
- config = {
118
- projectName: existingProject.name,
119
- sourceLocale: existingProject.source_language,
120
- outputLocales: existingProject.target_languages
188
+ async function detectProjectType() {
189
+ for (const [type, config] of Object.entries(PROJECT_TYPES)) {
190
+ if (!config.directIndicators?.length && !config.packageCheck) continue;
191
+
192
+ const isFramework = await detectFramework(config);
193
+ if (!isFramework) continue;
194
+
195
+ if (config.commonPaths) {
196
+ const translationPath = await findFirstExistingPath(config.commonPaths);
197
+ if (translationPath) {
198
+ return {
199
+ type,
200
+ defaults: {
201
+ ...config.defaults,
202
+ translationPath: `${translationPath}/`
203
+ }
121
204
  };
205
+ }
122
206
  }
123
-
124
- const translationPath = await promptService.input({
125
- message: 'Translation files path:',
126
- default: projectDefaults.defaults.translationPath,
127
- });
128
-
129
- const ignorePaths = await promptService.input({
130
- message: 'Paths to ignore (comma-separated, leave empty for none):',
131
- });
132
-
133
- if (newProject) {
134
- console.log(chalk.green(`\n✓ Project created, view it at: ${newProject.url}`));
207
+ return { type, defaults: config.defaults };
208
+ }
209
+
210
+ const translationPath = await findFirstExistingPath(PROJECT_TYPES.generic.commonPaths);
211
+ if (translationPath) {
212
+ const contents = await getDirectoryContents(translationPath);
213
+ if (contents) {
214
+ return {
215
+ type: 'detected',
216
+ defaults: {
217
+ translationPath: `${translationPath}/`,
218
+ filePattern: contents.jsonFiles.length > 0 && contents.yamlFiles.length === 0
219
+ ? '**/*.json'
220
+ : '**/*.{json,yml,yaml}'
221
+ }
222
+ };
135
223
  }
224
+ }
136
225
 
137
- return {
138
- ...config,
139
- projectId,
140
- translationPath,
141
- ignorePaths: ignorePaths.split(',').map(p => p.trim()).filter(Boolean)
142
- };
226
+ return {
227
+ type: 'generic',
228
+ defaults: PROJECT_TYPES.generic.defaults
229
+ };
143
230
  }
144
231
 
145
- export async function init(deps = {}) {
146
- const {
147
- console = global.console,
148
- basePath = process.cwd(),
149
- promptService = createPromptService({ inquirer: await import('@inquirer/prompts') }),
150
- projectService = defaultProjectService,
151
- configUtils = configService,
152
- authUtils = { checkAuth },
153
- importUtils = importService
154
- } = deps;
155
-
156
- const existingConfig = await configUtils.getProjectConfig(basePath);
157
- if (existingConfig) {
158
- console.log(chalk.yellow('localhero.json already exists. Skipping initialization.'));
159
- return;
160
- }
232
+ async function promptForConfig(projectDefaults, projectService, promptService, console = global.console) {
233
+ const { choice: projectChoice, project: existingProject } = await promptService.selectProject(projectService);
234
+ let projectId = projectChoice;
235
+ let newProject = false;
236
+ let config = await promptService.getProjectSetup();
237
+
238
+ if (!existingProject) {
239
+ config = {
240
+ projectName: await promptService.input({
241
+ message: 'Project name:',
242
+ default: path.basename(process.cwd()),
243
+ }),
244
+ sourceLocale: await promptService.input({
245
+ message: 'Source language - the language that we will translate from:',
246
+ default: 'en',
247
+ hint: 'Examples: "en" for en.json/en.yml, "en-US" for en-US.json, or directory name like "en" in /locales/en/common.json'
248
+ }),
249
+ outputLocales: (await promptService.input({
250
+ message: 'Target languages (comma-separated):',
251
+ hint: 'Must match your file names or directory names exactly. Examples: en.json → "en", fr-CA.json → "fr-CA", /locales/de/ → "de"'
252
+ })).split(',').map(lang => lang.trim()).filter(Boolean)
253
+ };
161
254
 
162
- const isAuthenticated = await authUtils.checkAuth();
163
- if (!isAuthenticated) {
164
- console.log(chalk.yellow('\nNo API key found. You need to authenticate first.'));
165
- console.log('Please run the login command to continue.\n');
255
+ try {
256
+ newProject = await projectService.createProject({
257
+ name: config.projectName,
258
+ sourceLocale: config.sourceLocale,
259
+ targetLocales: config.outputLocales
260
+ });
261
+ projectId = newProject.id;
262
+ } catch (error) {
263
+ console.log(chalk.red(`\n✗ Failed to create project: ${error.message}`));
264
+ return null;
265
+ }
266
+ } else {
267
+ config = {
268
+ projectName: existingProject.name,
269
+ sourceLocale: existingProject.source_language,
270
+ outputLocales: existingProject.target_languages
271
+ };
272
+ }
166
273
 
167
- const { shouldLogin } = await promptService.confirmLogin();
274
+ const commonPaths = projectDefaults.commonPaths || PROJECT_TYPES.generic.commonPaths;
275
+ const existingDirs = [];
168
276
 
169
- if (shouldLogin) {
170
- await login();
171
- } else {
172
- console.log('\nYou can run login later with: npx localhero login');
173
- return;
174
- }
277
+ for (const dir of commonPaths) {
278
+ if (await directoryExists(dir)) {
279
+ existingDirs.push(dir);
280
+ }
281
+ }
282
+
283
+ let dirHint = `Directory containing your translation files for ${projectDefaults.type || 'your'} project`;
284
+ if (existingDirs.length > 0) {
285
+ dirHint += `. Found existing directories: ${existingDirs.map(d => `"${d}/"`).join(', ')}`;
286
+ } else {
287
+ dirHint += `. Common paths: ${commonPaths.slice(0, 3).map(d => `"${d}/"`).join(', ')}`;
288
+ }
289
+
290
+ const translationPath = await promptService.input({
291
+ message: 'Translation files path:',
292
+ default: projectDefaults.defaults.translationPath,
293
+ hint: dirHint
294
+ });
295
+
296
+ let filePattern = projectDefaults.defaults.filePattern;
297
+ const contents = await getDirectoryContents(translationPath);
298
+
299
+ if (contents) {
300
+ if (contents.jsonFiles.length > 0 && contents.yamlFiles.length === 0) {
301
+ filePattern = '**/*.json';
302
+ } else if (contents.jsonFiles.length === 0 && contents.yamlFiles.length > 0) {
303
+ filePattern = '**/*.{yml,yaml}';
304
+ } else if (contents.jsonFiles.length > 0 && contents.yamlFiles.length > 0) {
305
+ filePattern = '**/*.{json,yml,yaml}';
175
306
  }
307
+ }
308
+
309
+ const ignorePaths = await promptService.input({
310
+ message: 'Paths to ignore (comma-separated, leave empty for none):',
311
+ hint: 'Example: "locales/ignored,locales/temp"'
312
+ });
313
+
314
+ return {
315
+ ...config,
316
+ projectId,
317
+ translationPath,
318
+ filePattern,
319
+ ignorePaths: ignorePaths.split(',').map(p => p.trim()).filter(Boolean),
320
+ newProject
321
+ };
322
+ }
176
323
 
177
- console.log(chalk.blue('\nWelcome to LocalHero.ai!'));
178
- console.log('Let\'s set up configuration for your project.\n');
324
+ export async function init(deps = {}) {
325
+ const {
326
+ console = global.console,
327
+ basePath = process.cwd(),
328
+ promptService = createPromptService({ inquirer: await import('@inquirer/prompts') }),
329
+ configUtils = configService,
330
+ authUtils = { checkAuth },
331
+ importUtils = importService,
332
+ projectApi = { createProject, listProjects },
333
+ login: loginFn = login
334
+ } = deps;
335
+
336
+ const existingConfig = await configUtils.getProjectConfig(basePath);
337
+ if (existingConfig) {
338
+ console.log(chalk.yellow('Existing configuration found in localhero.json. Skipping initialization.'));
339
+ return;
340
+ }
341
+
342
+ const isAuthenticated = await authUtils.checkAuth();
343
+ if (!isAuthenticated) {
344
+ console.log('LocalHero.ai - Automate your i18n translations\n');
345
+ console.log(chalk.yellow('No API key found. Let\'s get you authenticated.'));
346
+
347
+ await loginFn({
348
+ console,
349
+ basePath,
350
+ promptService,
351
+ configUtils,
352
+ verifyApiKey: authUtils.verifyApiKey,
353
+ isCalledFromInit: true
354
+ });
355
+ }
356
+
357
+ console.log('\nLet\'s set up configuration for your project.\n');
358
+
359
+ const projectDefaults = await detectProjectType();
360
+ const answers = await promptForConfig(projectDefaults, projectApi, promptService, console);
361
+
362
+ if (!answers) {
363
+ return;
364
+ }
365
+
366
+ const config = {
367
+ schemaVersion: '1.0',
368
+ projectId: answers.projectId,
369
+ sourceLocale: answers.sourceLocale,
370
+ outputLocales: answers.outputLocales,
371
+ translationFiles: {
372
+ paths: [answers.translationPath],
373
+ pattern: answers.filePattern,
374
+ ignore: answers.ignorePaths
375
+ }
376
+ };
179
377
 
180
- const projectDefaults = await detectProjectType();
181
- const answers = await promptForConfig(projectDefaults, projectService, promptService);
378
+ await configUtils.saveProjectConfig(config, basePath);
379
+ console.log(chalk.green('\n✓ Created localhero.json'));
182
380
 
183
- const config = {
184
- schemaVersion: '1.0',
185
- projectId: answers.projectId,
186
- sourceLocale: answers.sourceLocale,
187
- outputLocales: answers.outputLocales,
188
- translationFiles: {
189
- paths: [answers.translationPath],
190
- ignore: answers.ignorePaths
191
- }
192
- };
381
+ if (answers.newProject) {
382
+ console.log(chalk.green(`✓ Project created, view it at: https://localhero.ai/projects/${answers.projectId}`));
383
+ }
193
384
 
194
- await configUtils.saveProjectConfig(config, basePath);
195
- console.log(chalk.green('\n✓ Created localhero.json'));
196
- console.log('Configuration:');
197
- console.log(JSON.stringify(config, null, 2));
198
- console.log(' ');
385
+ console.log('Configuration:');
386
+ console.log(JSON.stringify(config, null, 2));
387
+ console.log(' ');
199
388
 
200
- const shouldSetupGitHubAction = await promptService.confirm({
201
- message: 'Would you like to set up GitHub Actions for automatic translations?',
202
- default: true
203
- });
389
+ const shouldSetupGitHubAction = await promptService.confirm({
390
+ message: 'Would you like to set up GitHub Actions for automatic translations?',
391
+ default: true
392
+ });
204
393
 
205
- if (shouldSetupGitHubAction) {
206
- try {
207
- const workflowFile = await createGitHubActionFile(basePath, config.translationFiles.paths);
208
- console.log(chalk.green(`\n✓ Created GitHub Action workflow at ${workflowFile}`));
209
- console.log('\nNext steps:');
210
- console.log('1. Add your API key to your repository\'s secrets:');
211
- console.log(' - Go to Settings > Secrets > Actions > New repository secret');
212
- console.log(' - Name: LOCALHERO_API_KEY');
213
- console.log(' - Value: [Your API Key] (find this at https://localhero.ai/api-keys or in your local .localhero_key file)');
214
- console.log('\n2. Commit and push the workflow file to enable automatic translations\n');
215
- } catch (error) {
216
- console.log(chalk.yellow('\nFailed to create GitHub Action workflow:'), error.message);
217
- }
394
+ if (shouldSetupGitHubAction) {
395
+ try {
396
+ const workflowFile = await createGitHubActionFile(basePath, config.translationFiles.paths);
397
+ console.log(chalk.green(`\n✓ Created GitHub Action workflow at ${workflowFile}`));
398
+ console.log('\nNext steps:');
399
+ console.log('1. Add your API key to your repository\'s secrets:');
400
+ console.log(' - Go to Settings > Secrets > Actions > New repository secret');
401
+ console.log(' - Name: LOCALHERO_API_KEY');
402
+ console.log(' - Value: [Your API Key] (find this at https://localhero.ai/api-keys or in your local .localhero_key file)');
403
+ console.log('\n2. Commit and push the workflow file to enable automatic translations\n');
404
+ } catch (error) {
405
+ console.log(chalk.yellow('\nFailed to create GitHub Action workflow:'), error.message);
218
406
  }
407
+ }
408
+
409
+ const shouldImport = await promptService.confirm({
410
+ message: 'Would you like to import existing translation files? (recommended)',
411
+ default: true
412
+ });
413
+
414
+ if (shouldImport) {
415
+ console.log('\nSearching for translation files...');
416
+ console.log(`Looking in: ${config.translationFiles.paths.join(', ')}`);
417
+
418
+ const importResult = await importUtils.importTranslations(config, basePath);
419
+
420
+ if (importResult.status === 'no_files') {
421
+ console.log(chalk.yellow('\nNo translation files found.'));
422
+ console.log('Make sure your translation files:');
423
+ console.log('1. Are in the specified path(s)');
424
+ console.log('2. Have the correct file extensions (.json, .yml, or .yaml)');
425
+ console.log('3. Follow the naming convention: [language-code].[extension] or are in language-specific directories');
426
+ console.log(`4. Include source language files (${config.sourceLocale}.[extension])`);
427
+ console.log('\nSupported JSON formats:');
428
+ console.log('- Nested format: { "navbar": { "home": "Home" } }');
429
+ console.log('- Flat format: { "navbar.home": "Home" }');
430
+ console.log('- With language wrapper: { "en": { "navbar": { "home": "Home" } } }');
431
+ console.log('\nSupported directory structures:');
432
+ console.log('- /locales/en.json, /locales/fr.json');
433
+ console.log('- /locales/en/common.json, /locales/fr/common.json');
434
+ console.log('- /locales/common.en.json, /locales/common.fr.json');
435
+ } else if (importResult.status === 'failed') {
436
+ console.log(chalk.red('\n✗ Failed to import translations'));
437
+ if (importResult.error) {
438
+ console.log(chalk.red(`Error: ${importResult.error}`));
439
+ }
440
+ return;
441
+ } else if (importResult.status === 'completed') {
442
+ console.log(chalk.green('\n✓ Successfully imported translations'));
443
+ await configUtils.updateLastSyncedAt();
444
+
445
+ if (importResult.files) {
446
+ console.log('\nImported files:');
447
+ [...importResult.files.source, ...importResult.files.target]
448
+ .sort((a, b) => a.path.localeCompare(b.path))
449
+ .forEach(file => {
450
+ const isSource = importResult.files.source.includes(file);
451
+ console.log(`- ${file.path}${isSource ? ' [source]' : ''}`);
452
+ });
453
+ }
454
+
455
+ if (importResult.sourceImport) {
456
+ console.log(`\nImported ${importResult.sourceImport.statistics.total_keys} source language keys`);
457
+
458
+ if (importResult.sourceImport.warnings?.length) {
459
+ console.log(chalk.yellow('\nWarnings:'));
460
+ importResult.sourceImport.warnings.forEach(warning => {
461
+ console.log(`- ${warning.message} (${warning.language})`);
462
+ });
463
+ }
464
+ }
219
465
 
220
- const shouldImport = await promptService.confirm({
221
- message: 'Would you like to import existing translation files? (recommended)',
222
- default: true
223
- });
466
+ console.log('\nTarget Languages:');
467
+ importResult.statistics.languages.forEach(lang => {
468
+ console.log(`${lang.code.toUpperCase()}: ${lang.translated}/${importResult.statistics.total_keys} translated`);
469
+ });
224
470
 
225
- if (shouldImport) {
226
- console.log('\nSearching for translation files...');
227
- console.log(`Looking in: ${config.translationFiles.paths.join(', ')}`);
228
- if (config.translationFiles.ignore.length) {
229
- console.log(`Ignoring: ${config.translationFiles.ignore.join(', ')}`);
230
- }
471
+ if (importResult.warnings?.length) {
472
+ console.log(chalk.yellow('\nWarnings:'));
473
+ importResult.warnings.forEach(warning => {
474
+ console.log(`- ${warning.message} (${warning.language})`);
475
+ });
476
+ }
231
477
 
232
- const importResult = await importUtils.importTranslations(config, basePath);
233
-
234
- if (importResult.status === 'no_files') {
235
- console.log(chalk.yellow('\nNo translation files found.'));
236
- console.log('Make sure your translation files:');
237
- console.log('1. Are in the specified path(s)');
238
- console.log('2. Have the correct file extensions (.json, .yml, or .yaml)');
239
- console.log('3. Follow the naming convention: [language-code].[extension]');
240
- console.log(`4. Include source language files (${config.sourceLocale}.[extension])`);
241
- } else if (importResult.status === 'failed') {
242
- console.log(chalk.red('\n✗ Failed to import translations'));
243
- if (importResult.error) {
244
- console.log(`Error: ${importResult.error}`);
245
- }
246
- } else if (importResult.status === 'completed') {
247
- console.log(chalk.green('\n✓ Successfully imported translations'));
248
-
249
- if (importResult.files) {
250
- console.log('\nImported files:');
251
- [...importResult.files.source, ...importResult.files.target]
252
- .sort((a, b) => a.path.localeCompare(b.path))
253
- .forEach(file => {
254
- const isSource = importResult.files.source.includes(file);
255
- console.log(`- ${file.path}${isSource ? ' [source]' : ''}`);
256
- });
257
- }
258
-
259
- if (importResult.sourceImport) {
260
- console.log(`\nImported ${importResult.sourceImport.statistics.total_keys} source language keys`);
261
-
262
- if (importResult.sourceImport.warnings?.length) {
263
- console.log(chalk.yellow('\nWarnings:'));
264
- importResult.sourceImport.warnings.forEach(warning => {
265
- console.log(`- ${warning.message} (${warning.language})`);
266
- });
267
- }
268
- }
269
-
270
- console.log('\nTarget Languages:');
271
- importResult.statistics.languages.forEach(lang => {
272
- console.log(`${lang.code.toUpperCase()}: ${lang.translated}/${importResult.statistics.total_keys} translated`);
273
- });
274
-
275
- if (importResult.warnings?.length) {
276
- console.log(chalk.yellow('\nWarnings:'));
277
- importResult.warnings.forEach(warning => {
278
- console.log(`- ${warning.message} (${warning.language})`);
279
- });
280
- }
281
- }
478
+ if (importResult.translations_url) {
479
+ console.log(chalk.blue(`\nView your translations at: ${importResult.translations_url}`));
480
+ }
282
481
  }
482
+ }
283
483
 
284
- console.log('\n🚀 Done! Start translating with: npx localhero translate');
484
+ console.log('\n🚀 Done! Start translating with: npx @localheroai/cli translate');
285
485
  }