transcripto-cli 1.0.0

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 (58) hide show
  1. package/README.md +576 -0
  2. package/dist/cli/generate.d.ts +2 -0
  3. package/dist/cli/generate.d.ts.map +1 -0
  4. package/dist/cli/generate.js +416 -0
  5. package/dist/cli/generate.js.map +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +43 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/init.d.ts +2 -0
  11. package/dist/cli/init.d.ts.map +1 -0
  12. package/dist/cli/init.js +81 -0
  13. package/dist/cli/init.js.map +1 -0
  14. package/dist/cli/report.d.ts +2 -0
  15. package/dist/cli/report.d.ts.map +1 -0
  16. package/dist/cli/report.js +137 -0
  17. package/dist/cli/report.js.map +1 -0
  18. package/dist/cli/scan.d.ts +2 -0
  19. package/dist/cli/scan.d.ts.map +1 -0
  20. package/dist/cli/scan.js +62 -0
  21. package/dist/cli/scan.js.map +1 -0
  22. package/dist/cli/watch-i18n.d.ts +2 -0
  23. package/dist/cli/watch-i18n.d.ts.map +1 -0
  24. package/dist/cli/watch-i18n.js +73 -0
  25. package/dist/cli/watch-i18n.js.map +1 -0
  26. package/dist/cli/watch.d.ts +2 -0
  27. package/dist/cli/watch.d.ts.map +1 -0
  28. package/dist/cli/watch.js +147 -0
  29. package/dist/cli/watch.js.map +1 -0
  30. package/dist/core/i18nGenerator.d.ts +16 -0
  31. package/dist/core/i18nGenerator.d.ts.map +1 -0
  32. package/dist/core/i18nGenerator.js +139 -0
  33. package/dist/core/i18nGenerator.js.map +1 -0
  34. package/dist/core/projectScanner.d.ts +12 -0
  35. package/dist/core/projectScanner.d.ts.map +1 -0
  36. package/dist/core/projectScanner.js +53 -0
  37. package/dist/core/projectScanner.js.map +1 -0
  38. package/dist/core/stringExtractor.d.ts +21 -0
  39. package/dist/core/stringExtractor.d.ts.map +1 -0
  40. package/dist/core/stringExtractor.js +268 -0
  41. package/dist/core/stringExtractor.js.map +1 -0
  42. package/dist/index.d.ts +7 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +10 -0
  45. package/dist/index.js.map +1 -0
  46. package/package.json +44 -0
  47. package/src/cli/generate.ts +422 -0
  48. package/src/cli/index.ts +50 -0
  49. package/src/cli/init.ts +96 -0
  50. package/src/cli/report.ts +160 -0
  51. package/src/cli/scan.ts +69 -0
  52. package/src/cli/watch-i18n.ts +77 -0
  53. package/src/cli/watch.ts +129 -0
  54. package/src/core/i18nGenerator.ts +127 -0
  55. package/src/core/projectScanner.ts +62 -0
  56. package/src/core/stringExtractor.ts +276 -0
  57. package/src/index.ts +7 -0
  58. package/tsconfig.json +20 -0
@@ -0,0 +1,422 @@
1
+ import { promises as fs } from 'fs';
2
+ import chalk from 'chalk';
3
+ import { I18nGenerator } from '../core/i18nGenerator';
4
+ import { ProjectScanner } from '../core/projectScanner';
5
+ import { StringExtractor } from '../core/stringExtractor';
6
+ import { ExtractedString } from '../core/stringExtractor';
7
+
8
+ export async function generateCommand(): Promise<void> {
9
+ console.log(chalk.blue('šŸ“ Generating i18n files...'));
10
+
11
+ try {
12
+ // Automatically scan the project first
13
+ console.log(chalk.cyan('šŸ” Scanning project for UI text strings...'));
14
+ const scanner = new ProjectScanner();
15
+ const files = await scanner.scanProject('./src');
16
+
17
+ const extractor = new StringExtractor();
18
+ const strings = await extractor.extractStrings(files);
19
+
20
+ if (strings.length === 0) {
21
+ console.log(chalk.yellow('āš ļø No UI text strings found in the project.'));
22
+ console.log(chalk.gray('šŸ’” Make sure your React components have text content to extract.'));
23
+ return;
24
+ }
25
+
26
+ console.log(chalk.green(`āœ… Found ${strings.length} UI text strings`));
27
+
28
+ // Save extracted strings for future use
29
+ await fs.mkdir('.transcripto', { recursive: true });
30
+ await fs.writeFile('.transcripto/extracted-strings.json', JSON.stringify(strings, null, 2), 'utf-8');
31
+
32
+ // Check if lingo.dev is configured
33
+ const hasLingoDevConfig = await checkLingoDevConfig();
34
+
35
+ if (!hasLingoDevConfig) {
36
+ console.log(chalk.yellow('\n🌐 Lingo.dev Setup Required'));
37
+ console.log(chalk.white('Setting up lingo.dev for AI translations...\n'));
38
+
39
+ // Setup lingo.dev automatically
40
+ await setupLingoDev();
41
+ }
42
+
43
+ // Get language configuration from user
44
+ const languageConfig = await getLanguageConfiguration();
45
+
46
+ // Generate i18n files (only JSON, no constants)
47
+ const generator = new I18nGenerator();
48
+ await generator.generateI18nFiles(strings, languageConfig);
49
+
50
+ // Generate lingo.dev config with automatic language handling
51
+ await generator.generateLingoDevConfig(); // Let lingo.dev decide target languages
52
+
53
+ // Generate welcome.md with extracted text
54
+ await generateWelcomeFile(strings, languageConfig);
55
+
56
+ console.log(chalk.green(`āœ… Generated i18n files for ${strings.length} strings`));
57
+ console.log(chalk.gray(`šŸ“ Translations: ${languageConfig.outputDir}/`));
58
+ console.log(chalk.gray(`šŸŒ Languages: ${languageConfig.languages.join(', ')}`));
59
+
60
+ // Run lingo.dev automatically
61
+ console.log(chalk.cyan('🌐 Running lingo.dev for translations...'));
62
+
63
+ try {
64
+ await generator.runLingoDev();
65
+ console.log(chalk.green('āœ… lingo.dev translations completed!'));
66
+
67
+ // Ask if user wants to create language dropdown (now automatic)
68
+ const shouldCreateDropdown = true; // Always create dropdown
69
+ await createLanguageDropdown(languageConfig);
70
+ } catch (error) {
71
+ console.log(chalk.yellow('āš ļø lingo.dev failed. You can run "npx lingo.dev@latest run" manually.'));
72
+ }
73
+
74
+ console.log(chalk.cyan('\nšŸŽÆ Next steps:'));
75
+ console.log(chalk.white(' npm start - Test your localized application'));
76
+ console.log(chalk.gray('šŸ’” Text is ready for translation. Use constants in your React components.'));
77
+
78
+ } catch (error) {
79
+ console.error(chalk.red('āŒ Generation failed:'), error);
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ async function checkLingoDevConfig(): Promise<boolean> {
85
+ try {
86
+ await fs.access('.lingodev.json');
87
+ return true;
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ async function setupLingoDev(): Promise<void> {
94
+ const readline = await import('readline');
95
+ const rl = readline.createInterface({
96
+ input: process.stdin,
97
+ output: process.stdout
98
+ });
99
+
100
+ console.log(chalk.cyan('šŸ”‘ Lingo.dev API Setup'));
101
+
102
+ return new Promise((resolve) => {
103
+ const askForApiKey = () => {
104
+ rl.question(chalk.white('Do you have a lingo.dev API key? (Y/n): '), async (answer) => {
105
+ const hasKey = answer.trim().toLowerCase() !== 'n' && answer.trim().toLowerCase() !== 'no';
106
+
107
+ if (hasKey) {
108
+ rl.question(chalk.white('Enter your lingo.dev API key: '), async (apiKey) => {
109
+ if (apiKey.trim()) {
110
+ // Save API key to environment
111
+ await saveApiKey(apiKey.trim());
112
+ console.log(chalk.green('āœ… API key saved!'));
113
+ rl.close();
114
+ resolve();
115
+ } else {
116
+ console.log(chalk.red('āŒ API key cannot be empty'));
117
+ askForApiKey();
118
+ }
119
+ });
120
+ } else {
121
+ rl.question(chalk.white('Would you like to create an account and get API key? (Y/n): '), async (answer) => {
122
+ if (answer.trim().toLowerCase() !== 'n' && answer.trim().toLowerCase() !== 'no') {
123
+ console.log(chalk.cyan('🌐 Opening lingo.dev to create account...'));
124
+ console.log(chalk.white('Please get your API key from: https://lingo.dev/dashboard'));
125
+ console.log(chalk.white('After getting your API key, run "transcripto generate" again.\n'));
126
+
127
+ // Open browser (simplified - in production use 'open' package)
128
+ const { spawn } = await import('child_process');
129
+ spawn('start', ['https://lingo.dev/dashboard'], { detached: true });
130
+ }
131
+ rl.close();
132
+ resolve();
133
+ });
134
+ }
135
+ });
136
+ };
137
+
138
+ askForApiKey();
139
+ });
140
+ }
141
+
142
+ async function saveApiKey(apiKey: string): Promise<void> {
143
+ const envContent = `# Transcripto Configuration
144
+ LINGO_DEV_API_KEY=${apiKey}
145
+ `;
146
+ await fs.writeFile('.env', envContent, 'utf-8');
147
+ }
148
+
149
+ async function generateWelcomeFile(strings: ExtractedString[], config: any): Promise<void> {
150
+ const welcomeContent = `# Welcome to Your Localized Project!
151
+
152
+ This file contains all the text strings that were extracted from your project.
153
+
154
+ ## šŸ“Š Extraction Summary
155
+ - **Total Strings**: ${strings.length}
156
+ - **Target Languages**: ${config.languages.join(', ')}
157
+ - **Generated**: ${new Date().toLocaleString()}
158
+
159
+ ## šŸ“ Extracted Text Strings
160
+
161
+ ${strings.map((str, index) =>
162
+ `${index + 1}. **${str.key}**\n \`${str.text}\`\n Location: ${str.filePath}:${str.line || 'unknown'}\n`
163
+ ).join('\n')}
164
+
165
+ ## 🌐 Next Steps
166
+
167
+ 1. **Review Translations**: Check the generated translation files in \`${config.outputDir}\`
168
+ 2. **Test Language Switching**: Use the language dropdown to verify functionality
169
+ 3. **Deploy**: Your project is now ready for multi-language deployment!
170
+
171
+ ---
172
+
173
+ *Generated by Transcripto - Automated Localization CLI*
174
+ *Learn more: https://github.com/brrashmi408-sys/transcripto-npm*
175
+ `;
176
+
177
+ await fs.writeFile('welcome.md', welcomeContent, 'utf-8');
178
+ console.log(chalk.green('šŸ“„ Created welcome.md with extracted text'));
179
+ }
180
+
181
+ async function createLanguageDropdown(config: any): Promise<void> {
182
+ const dropdownContent = `import React, { useState, useEffect } from 'react';
183
+
184
+ interface LanguageOption {
185
+ code: string;
186
+ name: string;
187
+ flag: string;
188
+ }
189
+
190
+ const languages: LanguageOption[] = [
191
+ { code: '${config.languages[0] || 'en'}', name: 'English', flag: 'šŸ‡ŗšŸ‡ø' },
192
+ ${config.languages.slice(1).map((lang: string) => ` { code: '${lang}', name: '${lang}', flag: 'šŸŒ' },`).join('\n')}
193
+ ];
194
+
195
+ export const LanguageDropdown: React.FC = () => {
196
+ const [currentLang, setCurrentLang] = useState('${config.languages[0] || 'en'}');
197
+ const [isOpen, setIsOpen] = useState(false);
198
+
199
+ useEffect(() => {
200
+ // Load saved language preference
201
+ const saved = localStorage.getItem('language');
202
+ if (saved && languages.find(l => l.code === saved)) {
203
+ setCurrentLang(saved);
204
+ }
205
+ }, []);
206
+
207
+ const handleLanguageChange = (langCode: string) => {
208
+ setCurrentLang(langCode);
209
+ localStorage.setItem('language', langCode);
210
+
211
+ // Reload page with new language
212
+ window.location.reload();
213
+ };
214
+
215
+ const currentLanguage = languages.find(l => l.code === currentLang);
216
+
217
+ return (
218
+ <div style={{
219
+ position: 'fixed',
220
+ top: '20px',
221
+ right: '20px',
222
+ zIndex: 1000
223
+ }}>
224
+ <button
225
+ onClick={() => setIsOpen(!isOpen)}
226
+ style={{
227
+ padding: '8px 16px',
228
+ border: '1px solid #ddd',
229
+ borderRadius: '6px',
230
+ background: 'white',
231
+ cursor: 'pointer',
232
+ fontSize: '14px',
233
+ display: 'flex',
234
+ alignItems: 'center',
235
+ gap: '8px',
236
+ boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
237
+ }}
238
+ >
239
+ <span>{currentLanguage?.flag}</span>
240
+ <span>{currentLanguage?.name}</span>
241
+ <span style={{ marginLeft: '4px' }}>ā–¼</span>
242
+ </button>
243
+
244
+ {isOpen && (
245
+ <div style={{
246
+ position: 'absolute',
247
+ top: '100%',
248
+ right: '0',
249
+ background: 'white',
250
+ border: '1px solid #ddd',
251
+ borderRadius: '6px',
252
+ boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
253
+ zIndex: 1000,
254
+ minWidth: '200px',
255
+ marginTop: '4px'
256
+ }}>
257
+ {languages.map(lang => (
258
+ <button
259
+ key={lang.code}
260
+ onClick={() => handleLanguageChange(lang.code)}
261
+ style={{
262
+ padding: '12px 16px',
263
+ border: 'none',
264
+ background: 'transparent',
265
+ textAlign: 'left',
266
+ cursor: 'pointer',
267
+ display: 'flex',
268
+ alignItems: 'center',
269
+ gap: '8px',
270
+ width: '100%'
271
+ }}
272
+ >
273
+ <span>{lang.flag}</span>
274
+ <span>{lang.name}</span>
275
+ {lang.code === currentLang && <span style={{ marginLeft: 'auto' }}>āœ“</span>}
276
+ </button>
277
+ ))}
278
+ </div>
279
+ )}
280
+ </div>
281
+ );
282
+ };
283
+
284
+ export default LanguageDropdown;
285
+ `;
286
+
287
+ await fs.writeFile('LanguageDropdown.tsx', dropdownContent, 'utf-8');
288
+ console.log(chalk.green('šŸŽØ Created LanguageDropdown.tsx component'));
289
+
290
+ console.log(chalk.cyan('\nšŸ“‹ How to use the language dropdown:'));
291
+ console.log(chalk.white('1. Copy LanguageDropdown.tsx to your project'));
292
+ console.log(chalk.white('2. Import and add to your main page/layout:'));
293
+ console.log(chalk.white(' import LanguageDropdown from "./components/LanguageDropdown";'));
294
+ console.log(chalk.white('3. Add to your App.tsx or layout:'));
295
+ console.log(chalk.white(' <LanguageDropdown />'));
296
+ console.log(chalk.white('4. The dropdown will automatically switch languages and reload page'));
297
+ console.log(chalk.white('5. Position: Fixed top-right corner for easy access'));
298
+ }
299
+
300
+ async function getLanguageConfiguration() {
301
+ console.log(chalk.cyan('\nšŸŒ Language Configuration'));
302
+
303
+ // Skip language selection - let lingo.dev handle it completely automatically
304
+ console.log(chalk.yellow('šŸ¤– Letting lingo.dev handle language selection completely automatically...'));
305
+
306
+ // Use default configuration - only English for now, lingo.dev will add more
307
+ const outputDir = './i18n'; // Changed to root folder
308
+
309
+ // Start with just English - lingo.dev will add target languages automatically
310
+ const languages = ['en'];
311
+
312
+ return {
313
+ outputDir,
314
+ languages,
315
+ keyPrefix: ''
316
+ };
317
+ }
318
+
319
+ async function promptForLanguage(message: string, defaultValue: string): Promise<string> {
320
+ const readline = await import('readline');
321
+ const rl = readline.createInterface({
322
+ input: process.stdin,
323
+ output: process.stdout
324
+ });
325
+
326
+ return new Promise((resolve) => {
327
+ rl.question(`${message} (${defaultValue}): `, (answer) => {
328
+ rl.close();
329
+ resolve(answer.trim() || defaultValue);
330
+ });
331
+ });
332
+ }
333
+
334
+ async function promptForTargetLanguages(): Promise<string[]> {
335
+ const readline = await import('readline');
336
+ const rl = readline.createInterface({
337
+ input: process.stdin,
338
+ output: process.stdout
339
+ });
340
+
341
+ console.log(chalk.yellow('\nAvailable language codes:'));
342
+ console.log(chalk.white(' en - English'));
343
+ console.log(chalk.white(' hi - Hindi'));
344
+ console.log(chalk.white(' kn - Kannada'));
345
+ console.log(chalk.white(' es - Spanish'));
346
+ console.log(chalk.white(' fr - French'));
347
+ console.log(chalk.white(' de - German'));
348
+ console.log(chalk.white(' zh - Chinese'));
349
+ console.log(chalk.white(' ja - Japanese'));
350
+ console.log(chalk.white(' ta - Tamil'));
351
+ console.log(chalk.white(' te - Telugu'));
352
+ console.log(chalk.white(' ml - Malayalam'));
353
+ console.log(chalk.white(' mr - Marathi'));
354
+ console.log(chalk.white(' gu - Gujarati'));
355
+ console.log(chalk.white(' bn - Bengali'));
356
+ console.log(chalk.white(' pa - Punjabi'));
357
+ console.log(chalk.white(' ur - Urdu'));
358
+ console.log(chalk.white(' ar - Arabic'));
359
+ console.log(chalk.white(' pt - Portuguese'));
360
+ console.log(chalk.white(' it - Italian'));
361
+ console.log(chalk.white(' ru - Russian'));
362
+
363
+ return new Promise((resolve) => {
364
+ rl.question('\nEnter target languages (comma-separated, e.g., hi,kn,es,fr): ', (answer) => {
365
+ rl.close();
366
+ const languages = answer.split(',')
367
+ .map(lang => lang.trim().toLowerCase())
368
+ .filter(lang => lang.length > 0);
369
+ resolve(languages.length > 0 ? languages : ['hi', 'kn']);
370
+ });
371
+ });
372
+ }
373
+
374
+ async function promptForOutputDirectory(): Promise<string> {
375
+ const readline = await import('readline');
376
+ const rl = readline.createInterface({
377
+ input: process.stdin,
378
+ output: process.stdout
379
+ });
380
+
381
+ return new Promise((resolve) => {
382
+ rl.question('Output directory for translation files (./src/i18n): ', (answer) => {
383
+ rl.close();
384
+ resolve(answer.trim() || './src/i18n');
385
+ });
386
+ });
387
+ }
388
+
389
+ async function promptForConstantsFile(outputDir: string): Promise<string> {
390
+ const readline = await import('readline');
391
+ const rl = readline.createInterface({
392
+ input: process.stdin,
393
+ output: process.stdout
394
+ });
395
+
396
+ return new Promise((resolve) => {
397
+ rl.question(`Constants file path (${outputDir}/constants.ts): `, (answer) => {
398
+ rl.close();
399
+ resolve(answer.trim() || `${outputDir}/constants.ts`);
400
+ });
401
+ });
402
+ }
403
+
404
+ async function askToRunLingoDev(): Promise<boolean> {
405
+ // Always return true to run lingo.dev automatically
406
+ return true;
407
+ }
408
+
409
+ async function askToCreateDropdown(): Promise<boolean> {
410
+ // Always return true to create language dropdown automatically
411
+ return true;
412
+ }
413
+
414
+ async function loadExtractedStrings(): Promise<ExtractedString[]> {
415
+ try {
416
+ const content = await fs.readFile('.transcripto/extracted-strings.json', 'utf-8');
417
+ return JSON.parse(content);
418
+ } catch (error) {
419
+ console.warn(chalk.yellow('āš ļø No extracted strings found. Run "transcripto scan" first.'));
420
+ return [];
421
+ }
422
+ }
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { generateCommand } from './generate';
5
+ import { initCommand } from './init';
6
+ import { reportCommand } from './report';
7
+ import { scanCommand } from './scan';
8
+ import { watchCommand } from './watch';
9
+ import { watchI18nCommand } from './watch-i18n';
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name('transcripto')
15
+ .description('Automated localization CLI that scans projects and generates i18n files')
16
+ .version('1.1.5');
17
+
18
+ program
19
+ .command('init')
20
+ .description('Initialize Transcripto in your project')
21
+ .option('--yes', 'Run in non-interactive mode')
22
+ .action(initCommand);
23
+
24
+ program
25
+ .command('scan')
26
+ .description('Scan project for UI text strings')
27
+ .action(scanCommand);
28
+
29
+ program
30
+ .command('generate')
31
+ .description('Generate i18n translation files')
32
+ .action(generateCommand);
33
+
34
+ program
35
+ .command('watch')
36
+ .description('Watch project files for changes and auto-generate i18n')
37
+ .option('--yes', 'Run in non-interactive mode')
38
+ .action(watchCommand);
39
+
40
+ program
41
+ .command('watch-i18n')
42
+ .description('Watch i18n/en.json file for changes and auto-translate')
43
+ .action(watchI18nCommand);
44
+
45
+ program
46
+ .command('report')
47
+ .description('Show localization coverage report')
48
+ .action(reportCommand);
49
+
50
+ program.parse();
@@ -0,0 +1,96 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export async function initCommand(options: any = {}): Promise<void> {
6
+ console.log(chalk.blue('šŸš€ Initializing Transcripto in your project...'));
7
+
8
+ const yes = options.yes || false;
9
+
10
+ if (yes) {
11
+ console.log(chalk.yellow('šŸ¤– Running in non-interactive mode (--yes)'));
12
+ }
13
+
14
+ try {
15
+ // Create .transcripto directory
16
+ await fs.mkdir('.transcripto', { recursive: true });
17
+
18
+ // Create config file
19
+ const config = {
20
+ version: '1.1.5',
21
+ scan: {
22
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.html'],
23
+ exclude: ['node_modules', 'dist', 'build', '.git', 'coverage', 'src/i18n'],
24
+ minStringLength: 2
25
+ },
26
+ i18n: {
27
+ outputDir: './src/i18n',
28
+ languages: ['en', 'hi', 'es', 'fr'],
29
+ constantsFile: './src/i18n/constants.ts',
30
+ keyPrefix: ''
31
+ },
32
+ replacement: {
33
+ importPath: './i18n/constants',
34
+ importName: 'TEXT',
35
+ functionName: 't'
36
+ }
37
+ };
38
+
39
+ await fs.writeFile(
40
+ '.transcripto/config.json',
41
+ JSON.stringify(config, null, 2),
42
+ 'utf-8'
43
+ );
44
+
45
+ // Create i18n directory structure
46
+ await fs.mkdir('./src/i18n', { recursive: true });
47
+
48
+ // Create initial i18n helper
49
+ const helperContent = `// Transcripto i18n helper
50
+ import { TEXT } from './constants';
51
+
52
+ export function t(key: keyof typeof TEXT): string {
53
+ // In a real app, you would use the current locale
54
+ // For now, return English text
55
+ return TEXT[key] as string;
56
+ }
57
+
58
+ export function setLocale(locale: string): void {
59
+ // TODO: Implement locale switching
60
+ console.log(\`Locale set to: \${locale}\`);
61
+ }
62
+ `;
63
+
64
+ await fs.writeFile('./src/i18n/index.ts', helperContent, 'utf-8');
65
+
66
+ // Create empty constants file
67
+ const constantsContent = `// Auto-generated localization constants
68
+ export const TEXT = {};
69
+
70
+ export type TextKey = keyof typeof TEXT;
71
+ `;
72
+
73
+ await fs.writeFile('./src/i18n/constants.ts', constantsContent, 'utf-8');
74
+
75
+ // Create empty translation files
76
+ for (const lang of config.i18n.languages) {
77
+ await fs.writeFile(
78
+ `./src/i18n/${lang}.json`,
79
+ JSON.stringify({}, null, 2),
80
+ 'utf-8'
81
+ );
82
+ }
83
+
84
+ console.log(chalk.green('āœ… Transcripto initialized successfully!'));
85
+ console.log(chalk.gray('šŸ“ Created .transcripto/config.json'));
86
+ console.log(chalk.gray('šŸ“ Created src/i18n/ directory structure'));
87
+ console.log(chalk.yellow('\nšŸŽÆ Next steps:'));
88
+ console.log(chalk.white(' transcripto scan - Scan for UI text strings'));
89
+ console.log(chalk.white(' transcripto generate - Generate i18n files'));
90
+ console.log(chalk.white(' transcripto replace - Replace inline text with constants'));
91
+
92
+ } catch (error) {
93
+ console.error(chalk.red('āŒ Initialization failed:'), error);
94
+ process.exit(1);
95
+ }
96
+ }