@devcoda/lokal-cli 1.2.1 → 1.3.2
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/dist/index.js +120 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +120 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/commands/wrap.ts +178 -0
- package/src/index.ts +2 -0
- package/src/types/lokal-core.d.ts +30 -0
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// packages/cli/src/index.ts
|
|
27
27
|
var import_commander = require("commander");
|
|
28
|
-
var
|
|
28
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
29
29
|
|
|
30
30
|
// packages/cli/src/commands/init.ts
|
|
31
31
|
var fs = __toESM(require("fs"));
|
|
@@ -287,12 +287,129 @@ function registerTranslateCommand(program2) {
|
|
|
287
287
|
program2.command("translate").description("Translate missing strings using AI").option("-c, --config <path>", "Path to config file").option("-l, --locale <locale>", "Specific locale to translate").option("-a, --all", "Translate all locales", false).option("-v, --verbose", "Verbose output", false).action(translateCommand);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
// packages/cli/src/commands/wrap.ts
|
|
291
|
+
var path4 = __toESM(require("path"));
|
|
292
|
+
var fs2 = __toESM(require("fs"));
|
|
293
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
294
|
+
var import_ora4 = __toESM(require("ora"));
|
|
295
|
+
var import_lokal_core3 = require("lokal-core");
|
|
296
|
+
async function wrapCommand(options) {
|
|
297
|
+
const spinner = (0, import_ora4.default)("Preparing to wrap strings...").start();
|
|
298
|
+
try {
|
|
299
|
+
const configLoader = new import_lokal_core3.ConfigLoader();
|
|
300
|
+
let config;
|
|
301
|
+
if (options.config) {
|
|
302
|
+
config = configLoader.loadSync(options.config);
|
|
303
|
+
} else {
|
|
304
|
+
config = await configLoader.load();
|
|
305
|
+
}
|
|
306
|
+
const projectRoot = process.cwd();
|
|
307
|
+
const sourceDir = options.src ? path4.resolve(projectRoot, options.src) : path4.resolve(projectRoot, config.sourceDir);
|
|
308
|
+
if (!fs2.existsSync(sourceDir)) {
|
|
309
|
+
spinner.fail(import_chalk4.default.red(`Source directory not found: ${sourceDir}`));
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
const wrapper = new import_lokal_core3.ASTWrapper({
|
|
313
|
+
functionName: options.function || config.functionName || "t",
|
|
314
|
+
componentName: config.componentName || "T"
|
|
315
|
+
});
|
|
316
|
+
spinner.text = `Scanning ${import_chalk4.default.cyan(sourceDir)} for translatable strings...`;
|
|
317
|
+
const results = wrapper.wrapDirectory(sourceDir, [".js", ".jsx", ".ts", ".tsx"], true);
|
|
318
|
+
let totalStrings = 0;
|
|
319
|
+
const fileResults = [];
|
|
320
|
+
for (const result of results.results) {
|
|
321
|
+
if (result.wrapped.length > 0) {
|
|
322
|
+
fileResults.push({
|
|
323
|
+
file: result.file,
|
|
324
|
+
wrapped: result.wrapped
|
|
325
|
+
});
|
|
326
|
+
totalStrings += result.wrapped.length;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
spinner.succeed(import_chalk4.default.green(`Found ${import_chalk4.default.bold(totalStrings)} strings to wrap in ${import_chalk4.default.bold(results.modifiedFiles)} files`));
|
|
330
|
+
if (totalStrings === 0) {
|
|
331
|
+
spinner.info(import_chalk4.default.gray("No strings found that need wrapping."));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (!options.dryRun) {
|
|
335
|
+
console.log(import_chalk4.default.bold("\nPreview (first 10 files):"));
|
|
336
|
+
let count = 0;
|
|
337
|
+
for (const fileResult of fileResults.slice(0, 10)) {
|
|
338
|
+
console.log(import_chalk4.default.cyan(`
|
|
339
|
+
${path4.relative(projectRoot, fileResult.file)}:`));
|
|
340
|
+
for (const wrapped of fileResult.wrapped.slice(0, 5)) {
|
|
341
|
+
console.log(import_chalk4.default.gray(` "${wrapped.original}" \u2192 ${wrapped.wrapped}`));
|
|
342
|
+
count++;
|
|
343
|
+
}
|
|
344
|
+
if (fileResult.wrapped.length > 5) {
|
|
345
|
+
console.log(import_chalk4.default.gray(` ... and ${fileResult.wrapped.length - 5} more`));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (fileResults.length > 10) {
|
|
349
|
+
console.log(import_chalk4.default.gray(`
|
|
350
|
+
... and ${fileResults.length - 10} more files`));
|
|
351
|
+
}
|
|
352
|
+
console.log(import_chalk4.default.bold("\n\u26A0 This will modify your source files!"));
|
|
353
|
+
console.log(import_chalk4.default.gray("Use --dry-run to preview without making changes"));
|
|
354
|
+
} else {
|
|
355
|
+
console.log(import_chalk4.default.bold("\nDry Run - Files that would be modified:"));
|
|
356
|
+
for (const fileResult of fileResults) {
|
|
357
|
+
console.log(import_chalk4.default.cyan(` ${path4.relative(projectRoot, fileResult.file)}`) + import_chalk4.default.gray(` (${fileResult.wrapped.length} strings)`));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (options.dryRun) {
|
|
361
|
+
spinner.info(import_chalk4.default.yellow("Dry run complete. No files were modified."));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const confirmSpinner = (0, import_ora4.default)("Applying changes...").start();
|
|
365
|
+
let modifiedCount = 0;
|
|
366
|
+
for (const fileResult of fileResults) {
|
|
367
|
+
const result = wrapper.wrapFile(fileResult.file);
|
|
368
|
+
if (result.modified) {
|
|
369
|
+
modifiedCount++;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
confirmSpinner.succeed(import_chalk4.default.green(`Modified ${import_chalk4.default.bold(modifiedCount)} files`));
|
|
373
|
+
const outputDir = path4.resolve(projectRoot, config.outputDir);
|
|
374
|
+
const storage = new import_lokal_core3.FileStorage(outputDir);
|
|
375
|
+
const newKeys = {};
|
|
376
|
+
for (const fileResult of fileResults) {
|
|
377
|
+
for (const wrapped of fileResult.wrapped) {
|
|
378
|
+
newKeys[wrapped.key] = wrapped.original;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const defaultLocale = config.defaultLocale;
|
|
382
|
+
const existingLocale = storage.loadLocale(defaultLocale);
|
|
383
|
+
let existingData = {};
|
|
384
|
+
if (existingLocale) {
|
|
385
|
+
existingData = existingLocale.data;
|
|
386
|
+
}
|
|
387
|
+
const mergedData = storage.mergeLocaleData(defaultLocale, newKeys);
|
|
388
|
+
storage.saveLocale(defaultLocale, mergedData);
|
|
389
|
+
console.log(import_chalk4.default.green(`
|
|
390
|
+
\u2713 Added ${import_chalk4.default.bold(Object.keys(newKeys).length)} new keys to locales/${defaultLocale}.json`));
|
|
391
|
+
console.log(import_chalk4.default.bold("\nNext steps:"));
|
|
392
|
+
console.log(import_chalk4.default.gray(" 1. Review the changes in your source files"));
|
|
393
|
+
console.log(import_chalk4.default.gray(" 2. Run ") + import_chalk4.default.cyan("npx lokal translate") + import_chalk4.default.gray(" to translate to other locales"));
|
|
394
|
+
} catch (error) {
|
|
395
|
+
spinner.fail(import_chalk4.default.red(`Wrap failed: ${error}`));
|
|
396
|
+
if (options.verbose) {
|
|
397
|
+
console.error(error);
|
|
398
|
+
}
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function registerWrapCommand(program2) {
|
|
403
|
+
program2.command("wrap").description("Automatically wrap translatable strings in source files").option("-c, --config <path>", "Path to config file").option("-s, --src <path>", "Source directory to scan").option("-f, --function <name>", "Translation function name", "t").option("-d, --dry-run", "Preview changes without modifying files", false).option("-v, --verbose", "Verbose output", false).action(wrapCommand);
|
|
404
|
+
}
|
|
405
|
+
|
|
290
406
|
// packages/cli/src/index.ts
|
|
291
407
|
var program = new import_commander.Command();
|
|
292
408
|
program.name("lokal").description("AI-powered localization ecosystem for React and React Native").version("1.0.0");
|
|
293
409
|
registerInitCommand(program);
|
|
294
410
|
registerScanCommand(program);
|
|
295
411
|
registerTranslateCommand(program);
|
|
412
|
+
registerWrapCommand(program);
|
|
296
413
|
program.option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand) => {
|
|
297
414
|
const opts = thisCommand.opts();
|
|
298
415
|
if (opts.verbose) {
|
|
@@ -300,8 +417,8 @@ program.option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand
|
|
|
300
417
|
}
|
|
301
418
|
});
|
|
302
419
|
program.on("command:*", () => {
|
|
303
|
-
console.error(
|
|
304
|
-
console.log(
|
|
420
|
+
console.error(import_chalk5.default.red(`Invalid command: ${program.args.join(" ")}`));
|
|
421
|
+
console.log(import_chalk5.default.gray(`See ${import_chalk5.default.cyan("--help")} for a list of available commands.`));
|
|
305
422
|
process.exit(1);
|
|
306
423
|
});
|
|
307
424
|
program.parse(process.argv);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/commands/scan.ts","../src/commands/translate.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { registerInitCommand } from './commands/init';\nimport { registerScanCommand } from './commands/scan';\nimport { registerTranslateCommand } from './commands/translate';\n\nconst program = new Command();\n\nprogram\n .name('lokal')\n .description('AI-powered localization ecosystem for React and React Native')\n .version('1.0.0');\n\n// Register commands\nregisterInitCommand(program);\nregisterScanCommand(program);\nregisterTranslateCommand(program);\n\n// Global options\nprogram\n .option('-v, --verbose', 'Verbose output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts.verbose) {\n process.env.LOKAL_VERBOSE = 'true';\n }\n });\n\n// Handle unknown commands\nprogram.on('command:*', () => {\n console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`));\n console.log(chalk.gray(`See ${chalk.cyan('--help')} for a list of available commands.`));\n process.exit(1);\n});\n\n// Run the program\nprogram.parse(process.argv);\n","import * as fs from 'fs';\nimport * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\n\ninterface InitOptions {\n locales?: string;\n defaultLocale?: string;\n force?: boolean;\n}\n\n/**\n * Create the initial lokal config file and directory structure\n */\nexport async function initCommand(options: InitOptions): Promise<void> {\n const spinner = ora('Initializing LOKAL...').start();\n\n try {\n const projectRoot = process.cwd();\n const configPath = path.join(projectRoot, 'lokal.config.js');\n const localesDir = path.join(projectRoot, 'locales');\n\n // Parse locales\n const locales = options.locales\n ? options.locales.split(',').map(l => l.trim())\n : ['en'];\n\n const defaultLocale = options.defaultLocale || locales[0];\n\n // Check if already initialized\n if (fs.existsSync(configPath) && !options.force) {\n spinner.warn(chalk.yellow('LOKAL is already initialized. Use --force to reinitialize.'));\n return;\n }\n\n // Create config file\n const configContent = `module.exports = {\n // Supported locales\n locales: ${JSON.stringify(locales)},\n \n // Default locale\n defaultLocale: '${defaultLocale}',\n \n // Function name for translations (t(\"key\"))\n functionName: 't',\n \n // Component name for translations (<T>key</T>)\n componentName: 'T',\n \n // Source directory to scan\n sourceDir: './src',\n \n // Output directory for locale files\n outputDir: './locales',\n \n // AI Translation settings (optional)\n // ai: {\n // provider: 'openai', // or 'gemini'\n // apiKey: process.env.OPENAI_API_KEY,\n // model: 'gpt-4'\n // }\n};\n`;\n\n fs.writeFileSync(configPath, configContent, 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold('lokal.config.js')}`));\n\n // Create locales directory with default locale\n if (!fs.existsSync(localesDir)) {\n fs.mkdirSync(localesDir, { recursive: true });\n }\n\n // Create default locale file\n const defaultLocalePath = path.join(localesDir, `${defaultLocale}.json`);\n if (!fs.existsSync(defaultLocalePath)) {\n const initialData = {\n _meta: {\n generated: new Date().toISOString(),\n description: 'Default locale file'\n }\n };\n fs.writeFileSync(defaultLocalePath, JSON.stringify(initialData, null, 2), 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n }\n\n // Create .gitignore entry for locale files if .gitignore exists\n const gitignorePath = path.join(projectRoot, '.gitignore');\n if (fs.existsSync(gitignorePath)) {\n const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');\n if (!gitignoreContent.includes('/locales/')) {\n fs.appendFileSync(gitignorePath, '\\n# LOKAL translations\\nlocales/\\n');\n spinner.succeed(chalk.green('Updated .gitignore'));\n }\n }\n\n console.log(chalk.bold('\\n✓ LOKAL initialized successfully!'));\n console.log(chalk.gray('\\nNext steps:'));\n console.log(chalk.gray(' 1. Add translation strings to your code using t(\"key\") or <T>key</T>'));\n console.log(chalk.gray(' 2. Run ') + chalk.cyan('npx lokal scan') + chalk.gray(' to extract strings'));\n console.log(chalk.gray(' 3. Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate with AI'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Failed to initialize: ${error}`));\n process.exit(1);\n }\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Initialize LOKAL in your project')\n .option('-l, --locales <locales>', 'Comma-separated list of locales', 'en')\n .option('-d, --default-locale <locale>', 'Default locale', 'en')\n .option('-f, --force', 'Force reinitialization', false)\n .action(initCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { ASTParser, ConfigLoader, FileStorage, type ExtractedString } from 'lokal-core';\n\ninterface ScanOptions {\n config?: string;\n output?: string;\n verbose?: boolean;\n}\n\n/**\n * Scan source files for translation strings and update locale files\n */\nexport async function scanCommand(options: ScanOptions): Promise<void> {\n const spinner = ora('Scanning for translation strings...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n const projectRoot = process.cwd();\n const sourceDir = path.resolve(projectRoot, config.sourceDir);\n const outputDir = options.output\n ? path.resolve(projectRoot, options.output)\n : path.resolve(projectRoot, config.outputDir);\n\n spinner.text = `Scanning ${chalk.cyan(sourceDir)}...`;\n\n // Create parser\n const parser = new ASTParser({\n filePath: sourceDir,\n functionName: config.functionName,\n componentName: config.componentName,\n });\n\n // Scan directory\n const result = parser.scanDirectory(sourceDir);\n\n if (result.errors.length > 0 && options.verbose) {\n for (const error of result.errors) {\n spinner.warn(chalk.yellow(error));\n }\n }\n\n spinner.succeed(chalk.green(`Found ${chalk.bold(result.strings.length)} translation strings`));\n\n if (result.strings.length === 0) {\n spinner.info(chalk.gray('No strings found. Make sure to use t(\"key\") or <T>key</T> in your code.'));\n return;\n }\n\n // Create storage\n const storage = new FileStorage(outputDir);\n\n // Create unique key-value map\n const uniqueStrings = new Map<string, ExtractedString>();\n for (const str of result.strings) {\n uniqueStrings.set(str.key, str);\n }\n\n // Get the default locale\n const defaultLocale = config.defaultLocale;\n const existingLocale = storage.loadLocale(defaultLocale);\n\n // Merge with existing keys\n let existingData: Record<string, any> = {};\n if (existingLocale) {\n existingData = existingLocale.data as Record<string, any>;\n }\n\n // Convert extracted strings to flat key-value object\n const newData: Record<string, string> = {};\n for (const [key, value] of uniqueStrings) {\n // Use the key itself as the translation value for now\n newData[key] = existingData[key] || value.value;\n }\n\n // Save the merged data\n const mergedData = storage.mergeLocaleData(defaultLocale, newData);\n storage.saveLocale(defaultLocale, mergedData);\n\n spinner.succeed(chalk.green(`Updated ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n\n // Show sample of extracted strings\n if (options.verbose) {\n console.log(chalk.bold('\\nExtracted strings:'));\n const sampleKeys = Array.from(uniqueStrings.keys()).slice(0, 10);\n for (const key of sampleKeys) {\n console.log(chalk.gray(` • ${key}`));\n }\n if (uniqueStrings.size > 10) {\n console.log(chalk.gray(` ... and ${uniqueStrings.size - 10} more`));\n }\n }\n\n // Check for other locales that need translation\n const locales = storage.getAvailableLocales();\n const otherLocales = locales.filter(l => l !== defaultLocale);\n\n if (otherLocales.length > 0) {\n console.log(chalk.gray(`\\nOther locales detected: ${otherLocales.join(', ')}`));\n console.log(chalk.gray('Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate missing strings'));\n }\n\n } catch (error) {\n spinner.fail(chalk.red(`Scan failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerScanCommand(program: Command): void {\n program\n .command('scan')\n .description('Scan source files for translation strings')\n .option('-c, --config <path>', 'Path to config file')\n .option('-o, --output <path>', 'Output directory for locale files')\n .option('-v, --verbose', 'Verbose output', false)\n .action(scanCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport {\n ConfigLoader,\n FileStorage,\n AITranslator,\n TranslationProviderFactory,\n type LocaleData\n} from 'lokal-core';\n\ninterface TranslateOptions {\n config?: string;\n locale?: string;\n all?: boolean;\n verbose?: boolean;\n}\n\n/**\n * Translate missing strings using AI\n */\nexport async function translateCommand(options: TranslateOptions): Promise<void> {\n const spinner = ora('Loading configuration...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n // Check if AI is configured\n if (!config.ai) {\n spinner.fail(chalk.red('AI provider not configured. Add ai configuration to lokal.config.js'));\n console.log(chalk.gray('\\nExample configuration:'));\n console.log(chalk.gray(' ai: {'));\n console.log(chalk.gray(' provider: \"openai\",'));\n console.log(chalk.gray(' apiKey: process.env.OPENAI_API_KEY'));\n console.log(chalk.gray(' }'));\n process.exit(1);\n }\n\n const apiKey = config.ai.apiKey || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY;\n if (!apiKey) {\n spinner.fail(chalk.red('No API key found. Set ai.apiKey in config or environment variable.'));\n process.exit(1);\n }\n\n spinner.text = 'Initializing AI translator...';\n\n // Create provider\n const provider = TranslationProviderFactory.create(\n config.ai.provider,\n apiKey,\n config.ai.model\n );\n\n // Create translator\n const translator = new AITranslator(provider);\n\n const projectRoot = process.cwd();\n const outputDir = path.resolve(projectRoot, config.outputDir);\n const storage = new FileStorage(outputDir);\n\n spinner.succeed('AI translator ready');\n\n // Determine which locales to translate\n let targetLocales: string[] = [];\n const availableLocales = config.locales || [];\n\n if (options.all) {\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale);\n } else if (options.locale) {\n targetLocales = [options.locale];\n } else {\n // Default: translate to first non-default locale\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale).slice(0, 1);\n }\n\n if (targetLocales.length === 0) {\n spinner.warn(chalk.yellow('No target locales to translate. Add more locales to your config.'));\n return;\n }\n\n // Load source locale data\n const sourceLocale = config.defaultLocale;\n const sourceData = storage.loadLocale(sourceLocale);\n\n if (!sourceData) {\n spinner.fail(chalk.red(`Source locale ${sourceLocale} not found. Run 'lokal scan' first.`));\n process.exit(1);\n }\n\n // Translate to each target locale\n for (const targetLocale of targetLocales) {\n const translateSpinner = ora(`Translating to ${chalk.cyan(targetLocale)}...`).start();\n\n // Load existing target data (if any)\n const targetLocaleFile = storage.loadLocale(targetLocale);\n const targetData: LocaleData = targetLocaleFile ? targetLocaleFile.data : {};\n\n // Translate missing keys\n const translatedData = await translator.translateMissingKeys(\n sourceData.data,\n targetData,\n sourceLocale,\n targetLocale\n );\n\n // Save translated data\n storage.saveLocale(targetLocale, translatedData);\n\n translateSpinner.succeed(chalk.green(`Translated to ${chalk.bold(targetLocale)}`));\n\n if (options.verbose) {\n // Show sample translations\n const keys = Object.keys(translatedData).slice(0, 5);\n for (const key of keys) {\n const value = translatedData[key];\n if (typeof value === 'string') {\n console.log(chalk.gray(` ${key}: ${value}`));\n }\n }\n }\n }\n\n console.log(chalk.bold('\\n✓ Translation complete!'));\n console.log(chalk.gray(`\\nRun `) + chalk.cyan('npx lokal scan') + chalk.gray(' to see updated translations'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Translation failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerTranslateCommand(program: Command): void {\n program\n .command('translate')\n .description('Translate missing strings using AI')\n .option('-c, --config <path>', 'Path to config file')\n .option('-l, --locale <locale>', 'Specific locale to translate')\n .option('-a, --all', 'Translate all locales', false)\n .option('-v, --verbose', 'Verbose output', false)\n .action(translateCommand);\n}\n"],"mappings":"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","names":["import_chalk","ora","chalk","program","path","import_chalk","import_ora","ora","chalk","program","path","import_chalk","import_ora","import_lokal_core","ora","chalk","program","chalk"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/commands/scan.ts","../src/commands/translate.ts","../src/commands/wrap.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { registerInitCommand } from './commands/init';\nimport { registerScanCommand } from './commands/scan';\nimport { registerTranslateCommand } from './commands/translate';\nimport { registerWrapCommand } from './commands/wrap';\n\nconst program = new Command();\n\nprogram\n .name('lokal')\n .description('AI-powered localization ecosystem for React and React Native')\n .version('1.0.0');\n\n// Register commands\nregisterInitCommand(program);\nregisterScanCommand(program);\nregisterTranslateCommand(program);\nregisterWrapCommand(program);\n\n// Global options\nprogram\n .option('-v, --verbose', 'Verbose output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts.verbose) {\n process.env.LOKAL_VERBOSE = 'true';\n }\n });\n\n// Handle unknown commands\nprogram.on('command:*', () => {\n console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`));\n console.log(chalk.gray(`See ${chalk.cyan('--help')} for a list of available commands.`));\n process.exit(1);\n});\n\n// Run the program\nprogram.parse(process.argv);\n","import * as fs from 'fs';\nimport * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\n\ninterface InitOptions {\n locales?: string;\n defaultLocale?: string;\n force?: boolean;\n}\n\n/**\n * Create the initial lokal config file and directory structure\n */\nexport async function initCommand(options: InitOptions): Promise<void> {\n const spinner = ora('Initializing LOKAL...').start();\n\n try {\n const projectRoot = process.cwd();\n const configPath = path.join(projectRoot, 'lokal.config.js');\n const localesDir = path.join(projectRoot, 'locales');\n\n // Parse locales\n const locales = options.locales\n ? options.locales.split(',').map(l => l.trim())\n : ['en'];\n\n const defaultLocale = options.defaultLocale || locales[0];\n\n // Check if already initialized\n if (fs.existsSync(configPath) && !options.force) {\n spinner.warn(chalk.yellow('LOKAL is already initialized. Use --force to reinitialize.'));\n return;\n }\n\n // Create config file\n const configContent = `module.exports = {\n // Supported locales\n locales: ${JSON.stringify(locales)},\n \n // Default locale\n defaultLocale: '${defaultLocale}',\n \n // Function name for translations (t(\"key\"))\n functionName: 't',\n \n // Component name for translations (<T>key</T>)\n componentName: 'T',\n \n // Source directory to scan\n sourceDir: './src',\n \n // Output directory for locale files\n outputDir: './locales',\n \n // AI Translation settings (optional)\n // ai: {\n // provider: 'openai', // or 'gemini'\n // apiKey: process.env.OPENAI_API_KEY,\n // model: 'gpt-4'\n // }\n};\n`;\n\n fs.writeFileSync(configPath, configContent, 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold('lokal.config.js')}`));\n\n // Create locales directory with default locale\n if (!fs.existsSync(localesDir)) {\n fs.mkdirSync(localesDir, { recursive: true });\n }\n\n // Create default locale file\n const defaultLocalePath = path.join(localesDir, `${defaultLocale}.json`);\n if (!fs.existsSync(defaultLocalePath)) {\n const initialData = {\n _meta: {\n generated: new Date().toISOString(),\n description: 'Default locale file'\n }\n };\n fs.writeFileSync(defaultLocalePath, JSON.stringify(initialData, null, 2), 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n }\n\n // Create .gitignore entry for locale files if .gitignore exists\n const gitignorePath = path.join(projectRoot, '.gitignore');\n if (fs.existsSync(gitignorePath)) {\n const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');\n if (!gitignoreContent.includes('/locales/')) {\n fs.appendFileSync(gitignorePath, '\\n# LOKAL translations\\nlocales/\\n');\n spinner.succeed(chalk.green('Updated .gitignore'));\n }\n }\n\n console.log(chalk.bold('\\n✓ LOKAL initialized successfully!'));\n console.log(chalk.gray('\\nNext steps:'));\n console.log(chalk.gray(' 1. Add translation strings to your code using t(\"key\") or <T>key</T>'));\n console.log(chalk.gray(' 2. Run ') + chalk.cyan('npx lokal scan') + chalk.gray(' to extract strings'));\n console.log(chalk.gray(' 3. Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate with AI'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Failed to initialize: ${error}`));\n process.exit(1);\n }\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Initialize LOKAL in your project')\n .option('-l, --locales <locales>', 'Comma-separated list of locales', 'en')\n .option('-d, --default-locale <locale>', 'Default locale', 'en')\n .option('-f, --force', 'Force reinitialization', false)\n .action(initCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { ASTParser, ConfigLoader, FileStorage, type ExtractedString } from 'lokal-core';\n\ninterface ScanOptions {\n config?: string;\n output?: string;\n verbose?: boolean;\n}\n\n/**\n * Scan source files for translation strings and update locale files\n */\nexport async function scanCommand(options: ScanOptions): Promise<void> {\n const spinner = ora('Scanning for translation strings...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n const projectRoot = process.cwd();\n const sourceDir = path.resolve(projectRoot, config.sourceDir);\n const outputDir = options.output\n ? path.resolve(projectRoot, options.output)\n : path.resolve(projectRoot, config.outputDir);\n\n spinner.text = `Scanning ${chalk.cyan(sourceDir)}...`;\n\n // Create parser\n const parser = new ASTParser({\n filePath: sourceDir,\n functionName: config.functionName,\n componentName: config.componentName,\n });\n\n // Scan directory\n const result = parser.scanDirectory(sourceDir);\n\n if (result.errors.length > 0 && options.verbose) {\n for (const error of result.errors) {\n spinner.warn(chalk.yellow(error));\n }\n }\n\n spinner.succeed(chalk.green(`Found ${chalk.bold(result.strings.length)} translation strings`));\n\n if (result.strings.length === 0) {\n spinner.info(chalk.gray('No strings found. Make sure to use t(\"key\") or <T>key</T> in your code.'));\n return;\n }\n\n // Create storage\n const storage = new FileStorage(outputDir);\n\n // Create unique key-value map\n const uniqueStrings = new Map<string, ExtractedString>();\n for (const str of result.strings) {\n uniqueStrings.set(str.key, str);\n }\n\n // Get the default locale\n const defaultLocale = config.defaultLocale;\n const existingLocale = storage.loadLocale(defaultLocale);\n\n // Merge with existing keys\n let existingData: Record<string, any> = {};\n if (existingLocale) {\n existingData = existingLocale.data as Record<string, any>;\n }\n\n // Convert extracted strings to flat key-value object\n const newData: Record<string, string> = {};\n for (const [key, value] of uniqueStrings) {\n // Use the key itself as the translation value for now\n newData[key] = existingData[key] || value.value;\n }\n\n // Save the merged data\n const mergedData = storage.mergeLocaleData(defaultLocale, newData);\n storage.saveLocale(defaultLocale, mergedData);\n\n spinner.succeed(chalk.green(`Updated ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n\n // Show sample of extracted strings\n if (options.verbose) {\n console.log(chalk.bold('\\nExtracted strings:'));\n const sampleKeys = Array.from(uniqueStrings.keys()).slice(0, 10);\n for (const key of sampleKeys) {\n console.log(chalk.gray(` • ${key}`));\n }\n if (uniqueStrings.size > 10) {\n console.log(chalk.gray(` ... and ${uniqueStrings.size - 10} more`));\n }\n }\n\n // Check for other locales that need translation\n const locales = storage.getAvailableLocales();\n const otherLocales = locales.filter(l => l !== defaultLocale);\n\n if (otherLocales.length > 0) {\n console.log(chalk.gray(`\\nOther locales detected: ${otherLocales.join(', ')}`));\n console.log(chalk.gray('Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate missing strings'));\n }\n\n } catch (error) {\n spinner.fail(chalk.red(`Scan failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerScanCommand(program: Command): void {\n program\n .command('scan')\n .description('Scan source files for translation strings')\n .option('-c, --config <path>', 'Path to config file')\n .option('-o, --output <path>', 'Output directory for locale files')\n .option('-v, --verbose', 'Verbose output', false)\n .action(scanCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport {\n ConfigLoader,\n FileStorage,\n AITranslator,\n TranslationProviderFactory,\n type LocaleData\n} from 'lokal-core';\n\ninterface TranslateOptions {\n config?: string;\n locale?: string;\n all?: boolean;\n verbose?: boolean;\n}\n\n/**\n * Translate missing strings using AI\n */\nexport async function translateCommand(options: TranslateOptions): Promise<void> {\n const spinner = ora('Loading configuration...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n // Check if AI is configured\n if (!config.ai) {\n spinner.fail(chalk.red('AI provider not configured. Add ai configuration to lokal.config.js'));\n console.log(chalk.gray('\\nExample configuration:'));\n console.log(chalk.gray(' ai: {'));\n console.log(chalk.gray(' provider: \"openai\",'));\n console.log(chalk.gray(' apiKey: process.env.OPENAI_API_KEY'));\n console.log(chalk.gray(' }'));\n process.exit(1);\n }\n\n const apiKey = config.ai.apiKey || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY;\n if (!apiKey) {\n spinner.fail(chalk.red('No API key found. Set ai.apiKey in config or environment variable.'));\n process.exit(1);\n }\n\n spinner.text = 'Initializing AI translator...';\n\n // Create provider\n const provider = TranslationProviderFactory.create(\n config.ai.provider,\n apiKey,\n config.ai.model\n );\n\n // Create translator\n const translator = new AITranslator(provider);\n\n const projectRoot = process.cwd();\n const outputDir = path.resolve(projectRoot, config.outputDir);\n const storage = new FileStorage(outputDir);\n\n spinner.succeed('AI translator ready');\n\n // Determine which locales to translate\n let targetLocales: string[] = [];\n const availableLocales = config.locales || [];\n\n if (options.all) {\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale);\n } else if (options.locale) {\n targetLocales = [options.locale];\n } else {\n // Default: translate to first non-default locale\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale).slice(0, 1);\n }\n\n if (targetLocales.length === 0) {\n spinner.warn(chalk.yellow('No target locales to translate. Add more locales to your config.'));\n return;\n }\n\n // Load source locale data\n const sourceLocale = config.defaultLocale;\n const sourceData = storage.loadLocale(sourceLocale);\n\n if (!sourceData) {\n spinner.fail(chalk.red(`Source locale ${sourceLocale} not found. Run 'lokal scan' first.`));\n process.exit(1);\n }\n\n // Translate to each target locale\n for (const targetLocale of targetLocales) {\n const translateSpinner = ora(`Translating to ${chalk.cyan(targetLocale)}...`).start();\n\n // Load existing target data (if any)\n const targetLocaleFile = storage.loadLocale(targetLocale);\n const targetData: LocaleData = targetLocaleFile ? targetLocaleFile.data : {};\n\n // Translate missing keys\n const translatedData = await translator.translateMissingKeys(\n sourceData.data,\n targetData,\n sourceLocale,\n targetLocale\n );\n\n // Save translated data\n storage.saveLocale(targetLocale, translatedData);\n\n translateSpinner.succeed(chalk.green(`Translated to ${chalk.bold(targetLocale)}`));\n\n if (options.verbose) {\n // Show sample translations\n const keys = Object.keys(translatedData).slice(0, 5);\n for (const key of keys) {\n const value = translatedData[key];\n if (typeof value === 'string') {\n console.log(chalk.gray(` ${key}: ${value}`));\n }\n }\n }\n }\n\n console.log(chalk.bold('\\n✓ Translation complete!'));\n console.log(chalk.gray(`\\nRun `) + chalk.cyan('npx lokal scan') + chalk.gray(' to see updated translations'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Translation failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerTranslateCommand(program: Command): void {\n program\n .command('translate')\n .description('Translate missing strings using AI')\n .option('-c, --config <path>', 'Path to config file')\n .option('-l, --locale <locale>', 'Specific locale to translate')\n .option('-a, --all', 'Translate all locales', false)\n .option('-v, --verbose', 'Verbose output', false)\n .action(translateCommand);\n}\n","import * as path from 'path';\nimport * as fs from 'fs';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { ASTWrapper, ConfigLoader, FileStorage, type WrappedString } from 'lokal-core';\n\ninterface WrapOptions {\n config?: string;\n src?: string;\n function?: string;\n dryRun?: boolean;\n verbose?: boolean;\n}\n\n/**\n * Auto-wrap translatable strings in source files\n * Converts plain text to t(\"key\") calls\n */\nexport async function wrapCommand(options: WrapOptions): Promise<void> {\n const spinner = ora('Preparing to wrap strings...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n const projectRoot = process.cwd();\n \n // Determine source directory\n const sourceDir = options.src\n ? path.resolve(projectRoot, options.src)\n : path.resolve(projectRoot, config.sourceDir);\n\n // Check if source directory exists\n if (!fs.existsSync(sourceDir)) {\n spinner.fail(chalk.red(`Source directory not found: ${sourceDir}`));\n process.exit(1);\n }\n\n // Create wrapper\n const wrapper = new ASTWrapper({\n functionName: options.function || config.functionName || 't',\n componentName: config.componentName || 'T',\n });\n\n spinner.text = `Scanning ${chalk.cyan(sourceDir)} for translatable strings...`;\n\n // First pass: collect all wrapped strings without modifying\n const results = wrapper.wrapDirectory(sourceDir, ['.js', '.jsx', '.ts', '.tsx'], true);\n \n // Count total strings to wrap\n let totalStrings = 0;\n const fileResults: { file: string, wrapped: WrappedString[] }[] = [];\n \n for (const result of results.results) {\n if (result.wrapped.length > 0) {\n fileResults.push({\n file: result.file,\n wrapped: result.wrapped\n });\n totalStrings += result.wrapped.length;\n }\n }\n\n spinner.succeed(chalk.green(`Found ${chalk.bold(totalStrings)} strings to wrap in ${chalk.bold(results.modifiedFiles)} files`));\n\n if (totalStrings === 0) {\n spinner.info(chalk.gray('No strings found that need wrapping.'));\n return;\n }\n\n // Show preview\n if (!options.dryRun) {\n console.log(chalk.bold('\\nPreview (first 10 files):'));\n let count = 0;\n for (const fileResult of fileResults.slice(0, 10)) {\n console.log(chalk.cyan(`\\n${path.relative(projectRoot, fileResult.file)}:`));\n for (const wrapped of fileResult.wrapped.slice(0, 5)) {\n console.log(chalk.gray(` \"${wrapped.original}\" → ${wrapped.wrapped}`));\n count++;\n }\n if (fileResult.wrapped.length > 5) {\n console.log(chalk.gray(` ... and ${fileResult.wrapped.length - 5} more`));\n }\n }\n if (fileResults.length > 10) {\n console.log(chalk.gray(`\\n... and ${fileResults.length - 10} more files`));\n }\n \n console.log(chalk.bold('\\n⚠ This will modify your source files!'));\n console.log(chalk.gray('Use --dry-run to preview without making changes'));\n } else {\n // Dry run - show what would be changed\n console.log(chalk.bold('\\nDry Run - Files that would be modified:'));\n for (const fileResult of fileResults) {\n console.log(chalk.cyan(` ${path.relative(projectRoot, fileResult.file)}`) + \n chalk.gray(` (${fileResult.wrapped.length} strings)`));\n }\n }\n\n if (options.dryRun) {\n spinner.info(chalk.yellow('Dry run complete. No files were modified.'));\n return;\n }\n\n // Confirm before making changes\n const confirmSpinner = ora('Applying changes...').start();\n\n // Apply changes to each file\n let modifiedCount = 0;\n for (const fileResult of fileResults) {\n const result = wrapper.wrapFile(fileResult.file);\n if (result.modified) {\n modifiedCount++;\n }\n }\n\n confirmSpinner.succeed(chalk.green(`Modified ${chalk.bold(modifiedCount)} files`));\n\n // Also update the locale file with the new keys\n const outputDir = path.resolve(projectRoot, config.outputDir);\n const storage = new FileStorage(outputDir);\n \n // Collect all new keys\n const newKeys: Record<string, string> = {};\n for (const fileResult of fileResults) {\n for (const wrapped of fileResult.wrapped) {\n newKeys[wrapped.key] = wrapped.original;\n }\n }\n\n // Add keys to default locale\n const defaultLocale = config.defaultLocale;\n const existingLocale = storage.loadLocale(defaultLocale);\n let existingData: Record<string, any> = {};\n \n if (existingLocale) {\n existingData = existingLocale.data as Record<string, any>;\n }\n\n // Merge new keys\n const mergedData = storage.mergeLocaleData(defaultLocale, newKeys);\n storage.saveLocale(defaultLocale, mergedData);\n\n console.log(chalk.green(`\\n✓ Added ${chalk.bold(Object.keys(newKeys).length)} new keys to locales/${defaultLocale}.json`));\n\n // Show next steps\n console.log(chalk.bold('\\nNext steps:'));\n console.log(chalk.gray(' 1. Review the changes in your source files'));\n console.log(chalk.gray(' 2. Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate to other locales'));\n \n } catch (error) {\n spinner.fail(chalk.red(`Wrap failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerWrapCommand(program: Command): void {\n program\n .command('wrap')\n .description('Automatically wrap translatable strings in source files')\n .option('-c, --config <path>', 'Path to config file')\n .option('-s, --src <path>', 'Source directory to scan')\n .option('-f, --function <name>', 'Translation function name', 't')\n .option('-d, --dry-run', 'Preview changes without modifying files', false)\n .option('-v, --verbose', 'Verbose output', false)\n .action(wrapCommand);\n}\n"],"mappings":"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","names":["import_chalk","ora","chalk","program","path","import_chalk","import_ora","ora","chalk","program","path","import_chalk","import_ora","import_lokal_core","ora","chalk","program","path","fs","import_chalk","import_ora","import_lokal_core","ora","chalk","program","chalk"]}
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// packages/cli/src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk5 from "chalk";
|
|
6
6
|
|
|
7
7
|
// packages/cli/src/commands/init.ts
|
|
8
8
|
import * as fs from "fs";
|
|
@@ -269,12 +269,129 @@ function registerTranslateCommand(program2) {
|
|
|
269
269
|
program2.command("translate").description("Translate missing strings using AI").option("-c, --config <path>", "Path to config file").option("-l, --locale <locale>", "Specific locale to translate").option("-a, --all", "Translate all locales", false).option("-v, --verbose", "Verbose output", false).action(translateCommand);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
// packages/cli/src/commands/wrap.ts
|
|
273
|
+
import * as path4 from "path";
|
|
274
|
+
import * as fs2 from "fs";
|
|
275
|
+
import chalk4 from "chalk";
|
|
276
|
+
import ora4 from "ora";
|
|
277
|
+
import { ASTWrapper, ConfigLoader as ConfigLoader3, FileStorage as FileStorage3 } from "lokal-core";
|
|
278
|
+
async function wrapCommand(options) {
|
|
279
|
+
const spinner = ora4("Preparing to wrap strings...").start();
|
|
280
|
+
try {
|
|
281
|
+
const configLoader = new ConfigLoader3();
|
|
282
|
+
let config;
|
|
283
|
+
if (options.config) {
|
|
284
|
+
config = configLoader.loadSync(options.config);
|
|
285
|
+
} else {
|
|
286
|
+
config = await configLoader.load();
|
|
287
|
+
}
|
|
288
|
+
const projectRoot = process.cwd();
|
|
289
|
+
const sourceDir = options.src ? path4.resolve(projectRoot, options.src) : path4.resolve(projectRoot, config.sourceDir);
|
|
290
|
+
if (!fs2.existsSync(sourceDir)) {
|
|
291
|
+
spinner.fail(chalk4.red(`Source directory not found: ${sourceDir}`));
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
const wrapper = new ASTWrapper({
|
|
295
|
+
functionName: options.function || config.functionName || "t",
|
|
296
|
+
componentName: config.componentName || "T"
|
|
297
|
+
});
|
|
298
|
+
spinner.text = `Scanning ${chalk4.cyan(sourceDir)} for translatable strings...`;
|
|
299
|
+
const results = wrapper.wrapDirectory(sourceDir, [".js", ".jsx", ".ts", ".tsx"], true);
|
|
300
|
+
let totalStrings = 0;
|
|
301
|
+
const fileResults = [];
|
|
302
|
+
for (const result of results.results) {
|
|
303
|
+
if (result.wrapped.length > 0) {
|
|
304
|
+
fileResults.push({
|
|
305
|
+
file: result.file,
|
|
306
|
+
wrapped: result.wrapped
|
|
307
|
+
});
|
|
308
|
+
totalStrings += result.wrapped.length;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
spinner.succeed(chalk4.green(`Found ${chalk4.bold(totalStrings)} strings to wrap in ${chalk4.bold(results.modifiedFiles)} files`));
|
|
312
|
+
if (totalStrings === 0) {
|
|
313
|
+
spinner.info(chalk4.gray("No strings found that need wrapping."));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!options.dryRun) {
|
|
317
|
+
console.log(chalk4.bold("\nPreview (first 10 files):"));
|
|
318
|
+
let count = 0;
|
|
319
|
+
for (const fileResult of fileResults.slice(0, 10)) {
|
|
320
|
+
console.log(chalk4.cyan(`
|
|
321
|
+
${path4.relative(projectRoot, fileResult.file)}:`));
|
|
322
|
+
for (const wrapped of fileResult.wrapped.slice(0, 5)) {
|
|
323
|
+
console.log(chalk4.gray(` "${wrapped.original}" \u2192 ${wrapped.wrapped}`));
|
|
324
|
+
count++;
|
|
325
|
+
}
|
|
326
|
+
if (fileResult.wrapped.length > 5) {
|
|
327
|
+
console.log(chalk4.gray(` ... and ${fileResult.wrapped.length - 5} more`));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (fileResults.length > 10) {
|
|
331
|
+
console.log(chalk4.gray(`
|
|
332
|
+
... and ${fileResults.length - 10} more files`));
|
|
333
|
+
}
|
|
334
|
+
console.log(chalk4.bold("\n\u26A0 This will modify your source files!"));
|
|
335
|
+
console.log(chalk4.gray("Use --dry-run to preview without making changes"));
|
|
336
|
+
} else {
|
|
337
|
+
console.log(chalk4.bold("\nDry Run - Files that would be modified:"));
|
|
338
|
+
for (const fileResult of fileResults) {
|
|
339
|
+
console.log(chalk4.cyan(` ${path4.relative(projectRoot, fileResult.file)}`) + chalk4.gray(` (${fileResult.wrapped.length} strings)`));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (options.dryRun) {
|
|
343
|
+
spinner.info(chalk4.yellow("Dry run complete. No files were modified."));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const confirmSpinner = ora4("Applying changes...").start();
|
|
347
|
+
let modifiedCount = 0;
|
|
348
|
+
for (const fileResult of fileResults) {
|
|
349
|
+
const result = wrapper.wrapFile(fileResult.file);
|
|
350
|
+
if (result.modified) {
|
|
351
|
+
modifiedCount++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
confirmSpinner.succeed(chalk4.green(`Modified ${chalk4.bold(modifiedCount)} files`));
|
|
355
|
+
const outputDir = path4.resolve(projectRoot, config.outputDir);
|
|
356
|
+
const storage = new FileStorage3(outputDir);
|
|
357
|
+
const newKeys = {};
|
|
358
|
+
for (const fileResult of fileResults) {
|
|
359
|
+
for (const wrapped of fileResult.wrapped) {
|
|
360
|
+
newKeys[wrapped.key] = wrapped.original;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const defaultLocale = config.defaultLocale;
|
|
364
|
+
const existingLocale = storage.loadLocale(defaultLocale);
|
|
365
|
+
let existingData = {};
|
|
366
|
+
if (existingLocale) {
|
|
367
|
+
existingData = existingLocale.data;
|
|
368
|
+
}
|
|
369
|
+
const mergedData = storage.mergeLocaleData(defaultLocale, newKeys);
|
|
370
|
+
storage.saveLocale(defaultLocale, mergedData);
|
|
371
|
+
console.log(chalk4.green(`
|
|
372
|
+
\u2713 Added ${chalk4.bold(Object.keys(newKeys).length)} new keys to locales/${defaultLocale}.json`));
|
|
373
|
+
console.log(chalk4.bold("\nNext steps:"));
|
|
374
|
+
console.log(chalk4.gray(" 1. Review the changes in your source files"));
|
|
375
|
+
console.log(chalk4.gray(" 2. Run ") + chalk4.cyan("npx lokal translate") + chalk4.gray(" to translate to other locales"));
|
|
376
|
+
} catch (error) {
|
|
377
|
+
spinner.fail(chalk4.red(`Wrap failed: ${error}`));
|
|
378
|
+
if (options.verbose) {
|
|
379
|
+
console.error(error);
|
|
380
|
+
}
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function registerWrapCommand(program2) {
|
|
385
|
+
program2.command("wrap").description("Automatically wrap translatable strings in source files").option("-c, --config <path>", "Path to config file").option("-s, --src <path>", "Source directory to scan").option("-f, --function <name>", "Translation function name", "t").option("-d, --dry-run", "Preview changes without modifying files", false).option("-v, --verbose", "Verbose output", false).action(wrapCommand);
|
|
386
|
+
}
|
|
387
|
+
|
|
272
388
|
// packages/cli/src/index.ts
|
|
273
389
|
var program = new Command();
|
|
274
390
|
program.name("lokal").description("AI-powered localization ecosystem for React and React Native").version("1.0.0");
|
|
275
391
|
registerInitCommand(program);
|
|
276
392
|
registerScanCommand(program);
|
|
277
393
|
registerTranslateCommand(program);
|
|
394
|
+
registerWrapCommand(program);
|
|
278
395
|
program.option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand) => {
|
|
279
396
|
const opts = thisCommand.opts();
|
|
280
397
|
if (opts.verbose) {
|
|
@@ -282,8 +399,8 @@ program.option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand
|
|
|
282
399
|
}
|
|
283
400
|
});
|
|
284
401
|
program.on("command:*", () => {
|
|
285
|
-
console.error(
|
|
286
|
-
console.log(
|
|
402
|
+
console.error(chalk5.red(`Invalid command: ${program.args.join(" ")}`));
|
|
403
|
+
console.log(chalk5.gray(`See ${chalk5.cyan("--help")} for a list of available commands.`));
|
|
287
404
|
process.exit(1);
|
|
288
405
|
});
|
|
289
406
|
program.parse(process.argv);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/commands/scan.ts","../src/commands/translate.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { registerInitCommand } from './commands/init';\nimport { registerScanCommand } from './commands/scan';\nimport { registerTranslateCommand } from './commands/translate';\n\nconst program = new Command();\n\nprogram\n .name('lokal')\n .description('AI-powered localization ecosystem for React and React Native')\n .version('1.0.0');\n\n// Register commands\nregisterInitCommand(program);\nregisterScanCommand(program);\nregisterTranslateCommand(program);\n\n// Global options\nprogram\n .option('-v, --verbose', 'Verbose output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts.verbose) {\n process.env.LOKAL_VERBOSE = 'true';\n }\n });\n\n// Handle unknown commands\nprogram.on('command:*', () => {\n console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`));\n console.log(chalk.gray(`See ${chalk.cyan('--help')} for a list of available commands.`));\n process.exit(1);\n});\n\n// Run the program\nprogram.parse(process.argv);\n","import * as fs from 'fs';\nimport * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\n\ninterface InitOptions {\n locales?: string;\n defaultLocale?: string;\n force?: boolean;\n}\n\n/**\n * Create the initial lokal config file and directory structure\n */\nexport async function initCommand(options: InitOptions): Promise<void> {\n const spinner = ora('Initializing LOKAL...').start();\n\n try {\n const projectRoot = process.cwd();\n const configPath = path.join(projectRoot, 'lokal.config.js');\n const localesDir = path.join(projectRoot, 'locales');\n\n // Parse locales\n const locales = options.locales\n ? options.locales.split(',').map(l => l.trim())\n : ['en'];\n\n const defaultLocale = options.defaultLocale || locales[0];\n\n // Check if already initialized\n if (fs.existsSync(configPath) && !options.force) {\n spinner.warn(chalk.yellow('LOKAL is already initialized. Use --force to reinitialize.'));\n return;\n }\n\n // Create config file\n const configContent = `module.exports = {\n // Supported locales\n locales: ${JSON.stringify(locales)},\n \n // Default locale\n defaultLocale: '${defaultLocale}',\n \n // Function name for translations (t(\"key\"))\n functionName: 't',\n \n // Component name for translations (<T>key</T>)\n componentName: 'T',\n \n // Source directory to scan\n sourceDir: './src',\n \n // Output directory for locale files\n outputDir: './locales',\n \n // AI Translation settings (optional)\n // ai: {\n // provider: 'openai', // or 'gemini'\n // apiKey: process.env.OPENAI_API_KEY,\n // model: 'gpt-4'\n // }\n};\n`;\n\n fs.writeFileSync(configPath, configContent, 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold('lokal.config.js')}`));\n\n // Create locales directory with default locale\n if (!fs.existsSync(localesDir)) {\n fs.mkdirSync(localesDir, { recursive: true });\n }\n\n // Create default locale file\n const defaultLocalePath = path.join(localesDir, `${defaultLocale}.json`);\n if (!fs.existsSync(defaultLocalePath)) {\n const initialData = {\n _meta: {\n generated: new Date().toISOString(),\n description: 'Default locale file'\n }\n };\n fs.writeFileSync(defaultLocalePath, JSON.stringify(initialData, null, 2), 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n }\n\n // Create .gitignore entry for locale files if .gitignore exists\n const gitignorePath = path.join(projectRoot, '.gitignore');\n if (fs.existsSync(gitignorePath)) {\n const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');\n if (!gitignoreContent.includes('/locales/')) {\n fs.appendFileSync(gitignorePath, '\\n# LOKAL translations\\nlocales/\\n');\n spinner.succeed(chalk.green('Updated .gitignore'));\n }\n }\n\n console.log(chalk.bold('\\n✓ LOKAL initialized successfully!'));\n console.log(chalk.gray('\\nNext steps:'));\n console.log(chalk.gray(' 1. Add translation strings to your code using t(\"key\") or <T>key</T>'));\n console.log(chalk.gray(' 2. Run ') + chalk.cyan('npx lokal scan') + chalk.gray(' to extract strings'));\n console.log(chalk.gray(' 3. Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate with AI'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Failed to initialize: ${error}`));\n process.exit(1);\n }\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Initialize LOKAL in your project')\n .option('-l, --locales <locales>', 'Comma-separated list of locales', 'en')\n .option('-d, --default-locale <locale>', 'Default locale', 'en')\n .option('-f, --force', 'Force reinitialization', false)\n .action(initCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { ASTParser, ConfigLoader, FileStorage, type ExtractedString } from 'lokal-core';\n\ninterface ScanOptions {\n config?: string;\n output?: string;\n verbose?: boolean;\n}\n\n/**\n * Scan source files for translation strings and update locale files\n */\nexport async function scanCommand(options: ScanOptions): Promise<void> {\n const spinner = ora('Scanning for translation strings...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n const projectRoot = process.cwd();\n const sourceDir = path.resolve(projectRoot, config.sourceDir);\n const outputDir = options.output\n ? path.resolve(projectRoot, options.output)\n : path.resolve(projectRoot, config.outputDir);\n\n spinner.text = `Scanning ${chalk.cyan(sourceDir)}...`;\n\n // Create parser\n const parser = new ASTParser({\n filePath: sourceDir,\n functionName: config.functionName,\n componentName: config.componentName,\n });\n\n // Scan directory\n const result = parser.scanDirectory(sourceDir);\n\n if (result.errors.length > 0 && options.verbose) {\n for (const error of result.errors) {\n spinner.warn(chalk.yellow(error));\n }\n }\n\n spinner.succeed(chalk.green(`Found ${chalk.bold(result.strings.length)} translation strings`));\n\n if (result.strings.length === 0) {\n spinner.info(chalk.gray('No strings found. Make sure to use t(\"key\") or <T>key</T> in your code.'));\n return;\n }\n\n // Create storage\n const storage = new FileStorage(outputDir);\n\n // Create unique key-value map\n const uniqueStrings = new Map<string, ExtractedString>();\n for (const str of result.strings) {\n uniqueStrings.set(str.key, str);\n }\n\n // Get the default locale\n const defaultLocale = config.defaultLocale;\n const existingLocale = storage.loadLocale(defaultLocale);\n\n // Merge with existing keys\n let existingData: Record<string, any> = {};\n if (existingLocale) {\n existingData = existingLocale.data as Record<string, any>;\n }\n\n // Convert extracted strings to flat key-value object\n const newData: Record<string, string> = {};\n for (const [key, value] of uniqueStrings) {\n // Use the key itself as the translation value for now\n newData[key] = existingData[key] || value.value;\n }\n\n // Save the merged data\n const mergedData = storage.mergeLocaleData(defaultLocale, newData);\n storage.saveLocale(defaultLocale, mergedData);\n\n spinner.succeed(chalk.green(`Updated ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n\n // Show sample of extracted strings\n if (options.verbose) {\n console.log(chalk.bold('\\nExtracted strings:'));\n const sampleKeys = Array.from(uniqueStrings.keys()).slice(0, 10);\n for (const key of sampleKeys) {\n console.log(chalk.gray(` • ${key}`));\n }\n if (uniqueStrings.size > 10) {\n console.log(chalk.gray(` ... and ${uniqueStrings.size - 10} more`));\n }\n }\n\n // Check for other locales that need translation\n const locales = storage.getAvailableLocales();\n const otherLocales = locales.filter(l => l !== defaultLocale);\n\n if (otherLocales.length > 0) {\n console.log(chalk.gray(`\\nOther locales detected: ${otherLocales.join(', ')}`));\n console.log(chalk.gray('Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate missing strings'));\n }\n\n } catch (error) {\n spinner.fail(chalk.red(`Scan failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerScanCommand(program: Command): void {\n program\n .command('scan')\n .description('Scan source files for translation strings')\n .option('-c, --config <path>', 'Path to config file')\n .option('-o, --output <path>', 'Output directory for locale files')\n .option('-v, --verbose', 'Verbose output', false)\n .action(scanCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport {\n ConfigLoader,\n FileStorage,\n AITranslator,\n TranslationProviderFactory,\n type LocaleData\n} from 'lokal-core';\n\ninterface TranslateOptions {\n config?: string;\n locale?: string;\n all?: boolean;\n verbose?: boolean;\n}\n\n/**\n * Translate missing strings using AI\n */\nexport async function translateCommand(options: TranslateOptions): Promise<void> {\n const spinner = ora('Loading configuration...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n // Check if AI is configured\n if (!config.ai) {\n spinner.fail(chalk.red('AI provider not configured. Add ai configuration to lokal.config.js'));\n console.log(chalk.gray('\\nExample configuration:'));\n console.log(chalk.gray(' ai: {'));\n console.log(chalk.gray(' provider: \"openai\",'));\n console.log(chalk.gray(' apiKey: process.env.OPENAI_API_KEY'));\n console.log(chalk.gray(' }'));\n process.exit(1);\n }\n\n const apiKey = config.ai.apiKey || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY;\n if (!apiKey) {\n spinner.fail(chalk.red('No API key found. Set ai.apiKey in config or environment variable.'));\n process.exit(1);\n }\n\n spinner.text = 'Initializing AI translator...';\n\n // Create provider\n const provider = TranslationProviderFactory.create(\n config.ai.provider,\n apiKey,\n config.ai.model\n );\n\n // Create translator\n const translator = new AITranslator(provider);\n\n const projectRoot = process.cwd();\n const outputDir = path.resolve(projectRoot, config.outputDir);\n const storage = new FileStorage(outputDir);\n\n spinner.succeed('AI translator ready');\n\n // Determine which locales to translate\n let targetLocales: string[] = [];\n const availableLocales = config.locales || [];\n\n if (options.all) {\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale);\n } else if (options.locale) {\n targetLocales = [options.locale];\n } else {\n // Default: translate to first non-default locale\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale).slice(0, 1);\n }\n\n if (targetLocales.length === 0) {\n spinner.warn(chalk.yellow('No target locales to translate. Add more locales to your config.'));\n return;\n }\n\n // Load source locale data\n const sourceLocale = config.defaultLocale;\n const sourceData = storage.loadLocale(sourceLocale);\n\n if (!sourceData) {\n spinner.fail(chalk.red(`Source locale ${sourceLocale} not found. Run 'lokal scan' first.`));\n process.exit(1);\n }\n\n // Translate to each target locale\n for (const targetLocale of targetLocales) {\n const translateSpinner = ora(`Translating to ${chalk.cyan(targetLocale)}...`).start();\n\n // Load existing target data (if any)\n const targetLocaleFile = storage.loadLocale(targetLocale);\n const targetData: LocaleData = targetLocaleFile ? targetLocaleFile.data : {};\n\n // Translate missing keys\n const translatedData = await translator.translateMissingKeys(\n sourceData.data,\n targetData,\n sourceLocale,\n targetLocale\n );\n\n // Save translated data\n storage.saveLocale(targetLocale, translatedData);\n\n translateSpinner.succeed(chalk.green(`Translated to ${chalk.bold(targetLocale)}`));\n\n if (options.verbose) {\n // Show sample translations\n const keys = Object.keys(translatedData).slice(0, 5);\n for (const key of keys) {\n const value = translatedData[key];\n if (typeof value === 'string') {\n console.log(chalk.gray(` ${key}: ${value}`));\n }\n }\n }\n }\n\n console.log(chalk.bold('\\n✓ Translation complete!'));\n console.log(chalk.gray(`\\nRun `) + chalk.cyan('npx lokal scan') + chalk.gray(' to see updated translations'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Translation failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerTranslateCommand(program: Command): void {\n program\n .command('translate')\n .description('Translate missing strings using AI')\n .option('-c, --config <path>', 'Path to config file')\n .option('-l, --locale <locale>', 'Specific locale to translate')\n .option('-a, --all', 'Translate all locales', false)\n .option('-v, --verbose', 'Verbose output', false)\n .action(translateCommand);\n}\n"],"mappings":"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","names":["chalk","program","path","chalk","ora","program","path","chalk","ora","ConfigLoader","FileStorage","program","chalk"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/commands/scan.ts","../src/commands/translate.ts","../src/commands/wrap.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { registerInitCommand } from './commands/init';\nimport { registerScanCommand } from './commands/scan';\nimport { registerTranslateCommand } from './commands/translate';\nimport { registerWrapCommand } from './commands/wrap';\n\nconst program = new Command();\n\nprogram\n .name('lokal')\n .description('AI-powered localization ecosystem for React and React Native')\n .version('1.0.0');\n\n// Register commands\nregisterInitCommand(program);\nregisterScanCommand(program);\nregisterTranslateCommand(program);\nregisterWrapCommand(program);\n\n// Global options\nprogram\n .option('-v, --verbose', 'Verbose output')\n .hook('preAction', (thisCommand) => {\n const opts = thisCommand.opts();\n if (opts.verbose) {\n process.env.LOKAL_VERBOSE = 'true';\n }\n });\n\n// Handle unknown commands\nprogram.on('command:*', () => {\n console.error(chalk.red(`Invalid command: ${program.args.join(' ')}`));\n console.log(chalk.gray(`See ${chalk.cyan('--help')} for a list of available commands.`));\n process.exit(1);\n});\n\n// Run the program\nprogram.parse(process.argv);\n","import * as fs from 'fs';\nimport * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\n\ninterface InitOptions {\n locales?: string;\n defaultLocale?: string;\n force?: boolean;\n}\n\n/**\n * Create the initial lokal config file and directory structure\n */\nexport async function initCommand(options: InitOptions): Promise<void> {\n const spinner = ora('Initializing LOKAL...').start();\n\n try {\n const projectRoot = process.cwd();\n const configPath = path.join(projectRoot, 'lokal.config.js');\n const localesDir = path.join(projectRoot, 'locales');\n\n // Parse locales\n const locales = options.locales\n ? options.locales.split(',').map(l => l.trim())\n : ['en'];\n\n const defaultLocale = options.defaultLocale || locales[0];\n\n // Check if already initialized\n if (fs.existsSync(configPath) && !options.force) {\n spinner.warn(chalk.yellow('LOKAL is already initialized. Use --force to reinitialize.'));\n return;\n }\n\n // Create config file\n const configContent = `module.exports = {\n // Supported locales\n locales: ${JSON.stringify(locales)},\n \n // Default locale\n defaultLocale: '${defaultLocale}',\n \n // Function name for translations (t(\"key\"))\n functionName: 't',\n \n // Component name for translations (<T>key</T>)\n componentName: 'T',\n \n // Source directory to scan\n sourceDir: './src',\n \n // Output directory for locale files\n outputDir: './locales',\n \n // AI Translation settings (optional)\n // ai: {\n // provider: 'openai', // or 'gemini'\n // apiKey: process.env.OPENAI_API_KEY,\n // model: 'gpt-4'\n // }\n};\n`;\n\n fs.writeFileSync(configPath, configContent, 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold('lokal.config.js')}`));\n\n // Create locales directory with default locale\n if (!fs.existsSync(localesDir)) {\n fs.mkdirSync(localesDir, { recursive: true });\n }\n\n // Create default locale file\n const defaultLocalePath = path.join(localesDir, `${defaultLocale}.json`);\n if (!fs.existsSync(defaultLocalePath)) {\n const initialData = {\n _meta: {\n generated: new Date().toISOString(),\n description: 'Default locale file'\n }\n };\n fs.writeFileSync(defaultLocalePath, JSON.stringify(initialData, null, 2), 'utf-8');\n spinner.succeed(chalk.green(`Created ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n }\n\n // Create .gitignore entry for locale files if .gitignore exists\n const gitignorePath = path.join(projectRoot, '.gitignore');\n if (fs.existsSync(gitignorePath)) {\n const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');\n if (!gitignoreContent.includes('/locales/')) {\n fs.appendFileSync(gitignorePath, '\\n# LOKAL translations\\nlocales/\\n');\n spinner.succeed(chalk.green('Updated .gitignore'));\n }\n }\n\n console.log(chalk.bold('\\n✓ LOKAL initialized successfully!'));\n console.log(chalk.gray('\\nNext steps:'));\n console.log(chalk.gray(' 1. Add translation strings to your code using t(\"key\") or <T>key</T>'));\n console.log(chalk.gray(' 2. Run ') + chalk.cyan('npx lokal scan') + chalk.gray(' to extract strings'));\n console.log(chalk.gray(' 3. Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate with AI'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Failed to initialize: ${error}`));\n process.exit(1);\n }\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('Initialize LOKAL in your project')\n .option('-l, --locales <locales>', 'Comma-separated list of locales', 'en')\n .option('-d, --default-locale <locale>', 'Default locale', 'en')\n .option('-f, --force', 'Force reinitialization', false)\n .action(initCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { ASTParser, ConfigLoader, FileStorage, type ExtractedString } from 'lokal-core';\n\ninterface ScanOptions {\n config?: string;\n output?: string;\n verbose?: boolean;\n}\n\n/**\n * Scan source files for translation strings and update locale files\n */\nexport async function scanCommand(options: ScanOptions): Promise<void> {\n const spinner = ora('Scanning for translation strings...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n const projectRoot = process.cwd();\n const sourceDir = path.resolve(projectRoot, config.sourceDir);\n const outputDir = options.output\n ? path.resolve(projectRoot, options.output)\n : path.resolve(projectRoot, config.outputDir);\n\n spinner.text = `Scanning ${chalk.cyan(sourceDir)}...`;\n\n // Create parser\n const parser = new ASTParser({\n filePath: sourceDir,\n functionName: config.functionName,\n componentName: config.componentName,\n });\n\n // Scan directory\n const result = parser.scanDirectory(sourceDir);\n\n if (result.errors.length > 0 && options.verbose) {\n for (const error of result.errors) {\n spinner.warn(chalk.yellow(error));\n }\n }\n\n spinner.succeed(chalk.green(`Found ${chalk.bold(result.strings.length)} translation strings`));\n\n if (result.strings.length === 0) {\n spinner.info(chalk.gray('No strings found. Make sure to use t(\"key\") or <T>key</T> in your code.'));\n return;\n }\n\n // Create storage\n const storage = new FileStorage(outputDir);\n\n // Create unique key-value map\n const uniqueStrings = new Map<string, ExtractedString>();\n for (const str of result.strings) {\n uniqueStrings.set(str.key, str);\n }\n\n // Get the default locale\n const defaultLocale = config.defaultLocale;\n const existingLocale = storage.loadLocale(defaultLocale);\n\n // Merge with existing keys\n let existingData: Record<string, any> = {};\n if (existingLocale) {\n existingData = existingLocale.data as Record<string, any>;\n }\n\n // Convert extracted strings to flat key-value object\n const newData: Record<string, string> = {};\n for (const [key, value] of uniqueStrings) {\n // Use the key itself as the translation value for now\n newData[key] = existingData[key] || value.value;\n }\n\n // Save the merged data\n const mergedData = storage.mergeLocaleData(defaultLocale, newData);\n storage.saveLocale(defaultLocale, mergedData);\n\n spinner.succeed(chalk.green(`Updated ${chalk.bold(`locales/${defaultLocale}.json`)}`));\n\n // Show sample of extracted strings\n if (options.verbose) {\n console.log(chalk.bold('\\nExtracted strings:'));\n const sampleKeys = Array.from(uniqueStrings.keys()).slice(0, 10);\n for (const key of sampleKeys) {\n console.log(chalk.gray(` • ${key}`));\n }\n if (uniqueStrings.size > 10) {\n console.log(chalk.gray(` ... and ${uniqueStrings.size - 10} more`));\n }\n }\n\n // Check for other locales that need translation\n const locales = storage.getAvailableLocales();\n const otherLocales = locales.filter(l => l !== defaultLocale);\n\n if (otherLocales.length > 0) {\n console.log(chalk.gray(`\\nOther locales detected: ${otherLocales.join(', ')}`));\n console.log(chalk.gray('Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate missing strings'));\n }\n\n } catch (error) {\n spinner.fail(chalk.red(`Scan failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerScanCommand(program: Command): void {\n program\n .command('scan')\n .description('Scan source files for translation strings')\n .option('-c, --config <path>', 'Path to config file')\n .option('-o, --output <path>', 'Output directory for locale files')\n .option('-v, --verbose', 'Verbose output', false)\n .action(scanCommand);\n}\n","import * as path from 'path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport {\n ConfigLoader,\n FileStorage,\n AITranslator,\n TranslationProviderFactory,\n type LocaleData\n} from 'lokal-core';\n\ninterface TranslateOptions {\n config?: string;\n locale?: string;\n all?: boolean;\n verbose?: boolean;\n}\n\n/**\n * Translate missing strings using AI\n */\nexport async function translateCommand(options: TranslateOptions): Promise<void> {\n const spinner = ora('Loading configuration...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n // Check if AI is configured\n if (!config.ai) {\n spinner.fail(chalk.red('AI provider not configured. Add ai configuration to lokal.config.js'));\n console.log(chalk.gray('\\nExample configuration:'));\n console.log(chalk.gray(' ai: {'));\n console.log(chalk.gray(' provider: \"openai\",'));\n console.log(chalk.gray(' apiKey: process.env.OPENAI_API_KEY'));\n console.log(chalk.gray(' }'));\n process.exit(1);\n }\n\n const apiKey = config.ai.apiKey || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY;\n if (!apiKey) {\n spinner.fail(chalk.red('No API key found. Set ai.apiKey in config or environment variable.'));\n process.exit(1);\n }\n\n spinner.text = 'Initializing AI translator...';\n\n // Create provider\n const provider = TranslationProviderFactory.create(\n config.ai.provider,\n apiKey,\n config.ai.model\n );\n\n // Create translator\n const translator = new AITranslator(provider);\n\n const projectRoot = process.cwd();\n const outputDir = path.resolve(projectRoot, config.outputDir);\n const storage = new FileStorage(outputDir);\n\n spinner.succeed('AI translator ready');\n\n // Determine which locales to translate\n let targetLocales: string[] = [];\n const availableLocales = config.locales || [];\n\n if (options.all) {\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale);\n } else if (options.locale) {\n targetLocales = [options.locale];\n } else {\n // Default: translate to first non-default locale\n targetLocales = availableLocales.filter(l => l !== config.defaultLocale).slice(0, 1);\n }\n\n if (targetLocales.length === 0) {\n spinner.warn(chalk.yellow('No target locales to translate. Add more locales to your config.'));\n return;\n }\n\n // Load source locale data\n const sourceLocale = config.defaultLocale;\n const sourceData = storage.loadLocale(sourceLocale);\n\n if (!sourceData) {\n spinner.fail(chalk.red(`Source locale ${sourceLocale} not found. Run 'lokal scan' first.`));\n process.exit(1);\n }\n\n // Translate to each target locale\n for (const targetLocale of targetLocales) {\n const translateSpinner = ora(`Translating to ${chalk.cyan(targetLocale)}...`).start();\n\n // Load existing target data (if any)\n const targetLocaleFile = storage.loadLocale(targetLocale);\n const targetData: LocaleData = targetLocaleFile ? targetLocaleFile.data : {};\n\n // Translate missing keys\n const translatedData = await translator.translateMissingKeys(\n sourceData.data,\n targetData,\n sourceLocale,\n targetLocale\n );\n\n // Save translated data\n storage.saveLocale(targetLocale, translatedData);\n\n translateSpinner.succeed(chalk.green(`Translated to ${chalk.bold(targetLocale)}`));\n\n if (options.verbose) {\n // Show sample translations\n const keys = Object.keys(translatedData).slice(0, 5);\n for (const key of keys) {\n const value = translatedData[key];\n if (typeof value === 'string') {\n console.log(chalk.gray(` ${key}: ${value}`));\n }\n }\n }\n }\n\n console.log(chalk.bold('\\n✓ Translation complete!'));\n console.log(chalk.gray(`\\nRun `) + chalk.cyan('npx lokal scan') + chalk.gray(' to see updated translations'));\n\n } catch (error) {\n spinner.fail(chalk.red(`Translation failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerTranslateCommand(program: Command): void {\n program\n .command('translate')\n .description('Translate missing strings using AI')\n .option('-c, --config <path>', 'Path to config file')\n .option('-l, --locale <locale>', 'Specific locale to translate')\n .option('-a, --all', 'Translate all locales', false)\n .option('-v, --verbose', 'Verbose output', false)\n .action(translateCommand);\n}\n","import * as path from 'path';\nimport * as fs from 'fs';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { ASTWrapper, ConfigLoader, FileStorage, type WrappedString } from 'lokal-core';\n\ninterface WrapOptions {\n config?: string;\n src?: string;\n function?: string;\n dryRun?: boolean;\n verbose?: boolean;\n}\n\n/**\n * Auto-wrap translatable strings in source files\n * Converts plain text to t(\"key\") calls\n */\nexport async function wrapCommand(options: WrapOptions): Promise<void> {\n const spinner = ora('Preparing to wrap strings...').start();\n\n try {\n // Load config\n const configLoader = new ConfigLoader();\n let config;\n\n if (options.config) {\n config = configLoader.loadSync(options.config);\n } else {\n config = await configLoader.load();\n }\n\n const projectRoot = process.cwd();\n \n // Determine source directory\n const sourceDir = options.src\n ? path.resolve(projectRoot, options.src)\n : path.resolve(projectRoot, config.sourceDir);\n\n // Check if source directory exists\n if (!fs.existsSync(sourceDir)) {\n spinner.fail(chalk.red(`Source directory not found: ${sourceDir}`));\n process.exit(1);\n }\n\n // Create wrapper\n const wrapper = new ASTWrapper({\n functionName: options.function || config.functionName || 't',\n componentName: config.componentName || 'T',\n });\n\n spinner.text = `Scanning ${chalk.cyan(sourceDir)} for translatable strings...`;\n\n // First pass: collect all wrapped strings without modifying\n const results = wrapper.wrapDirectory(sourceDir, ['.js', '.jsx', '.ts', '.tsx'], true);\n \n // Count total strings to wrap\n let totalStrings = 0;\n const fileResults: { file: string, wrapped: WrappedString[] }[] = [];\n \n for (const result of results.results) {\n if (result.wrapped.length > 0) {\n fileResults.push({\n file: result.file,\n wrapped: result.wrapped\n });\n totalStrings += result.wrapped.length;\n }\n }\n\n spinner.succeed(chalk.green(`Found ${chalk.bold(totalStrings)} strings to wrap in ${chalk.bold(results.modifiedFiles)} files`));\n\n if (totalStrings === 0) {\n spinner.info(chalk.gray('No strings found that need wrapping.'));\n return;\n }\n\n // Show preview\n if (!options.dryRun) {\n console.log(chalk.bold('\\nPreview (first 10 files):'));\n let count = 0;\n for (const fileResult of fileResults.slice(0, 10)) {\n console.log(chalk.cyan(`\\n${path.relative(projectRoot, fileResult.file)}:`));\n for (const wrapped of fileResult.wrapped.slice(0, 5)) {\n console.log(chalk.gray(` \"${wrapped.original}\" → ${wrapped.wrapped}`));\n count++;\n }\n if (fileResult.wrapped.length > 5) {\n console.log(chalk.gray(` ... and ${fileResult.wrapped.length - 5} more`));\n }\n }\n if (fileResults.length > 10) {\n console.log(chalk.gray(`\\n... and ${fileResults.length - 10} more files`));\n }\n \n console.log(chalk.bold('\\n⚠ This will modify your source files!'));\n console.log(chalk.gray('Use --dry-run to preview without making changes'));\n } else {\n // Dry run - show what would be changed\n console.log(chalk.bold('\\nDry Run - Files that would be modified:'));\n for (const fileResult of fileResults) {\n console.log(chalk.cyan(` ${path.relative(projectRoot, fileResult.file)}`) + \n chalk.gray(` (${fileResult.wrapped.length} strings)`));\n }\n }\n\n if (options.dryRun) {\n spinner.info(chalk.yellow('Dry run complete. No files were modified.'));\n return;\n }\n\n // Confirm before making changes\n const confirmSpinner = ora('Applying changes...').start();\n\n // Apply changes to each file\n let modifiedCount = 0;\n for (const fileResult of fileResults) {\n const result = wrapper.wrapFile(fileResult.file);\n if (result.modified) {\n modifiedCount++;\n }\n }\n\n confirmSpinner.succeed(chalk.green(`Modified ${chalk.bold(modifiedCount)} files`));\n\n // Also update the locale file with the new keys\n const outputDir = path.resolve(projectRoot, config.outputDir);\n const storage = new FileStorage(outputDir);\n \n // Collect all new keys\n const newKeys: Record<string, string> = {};\n for (const fileResult of fileResults) {\n for (const wrapped of fileResult.wrapped) {\n newKeys[wrapped.key] = wrapped.original;\n }\n }\n\n // Add keys to default locale\n const defaultLocale = config.defaultLocale;\n const existingLocale = storage.loadLocale(defaultLocale);\n let existingData: Record<string, any> = {};\n \n if (existingLocale) {\n existingData = existingLocale.data as Record<string, any>;\n }\n\n // Merge new keys\n const mergedData = storage.mergeLocaleData(defaultLocale, newKeys);\n storage.saveLocale(defaultLocale, mergedData);\n\n console.log(chalk.green(`\\n✓ Added ${chalk.bold(Object.keys(newKeys).length)} new keys to locales/${defaultLocale}.json`));\n\n // Show next steps\n console.log(chalk.bold('\\nNext steps:'));\n console.log(chalk.gray(' 1. Review the changes in your source files'));\n console.log(chalk.gray(' 2. Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate to other locales'));\n \n } catch (error) {\n spinner.fail(chalk.red(`Wrap failed: ${error}`));\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n}\n\nexport function registerWrapCommand(program: Command): void {\n program\n .command('wrap')\n .description('Automatically wrap translatable strings in source files')\n .option('-c, --config <path>', 'Path to config file')\n .option('-s, --src <path>', 'Source directory to scan')\n .option('-f, --function <name>', 'Translation function name', 't')\n .option('-d, --dry-run', 'Preview changes without modifying files', false)\n .option('-v, --verbose', 'Verbose output', false)\n .action(wrapCommand);\n}\n"],"mappings":"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","names":["chalk","program","path","chalk","ora","program","path","chalk","ora","ConfigLoader","FileStorage","program","path","fs","chalk","ora","ConfigLoader","FileStorage","program","chalk"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devcoda/lokal-cli",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "CLI tool for LOKAL - Automates string extraction and AI translations",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"lint": "echo 'Use root lint command'"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"lokal-core": "
|
|
20
|
+
"lokal-core": "file:../core",
|
|
21
21
|
"chalk": "^4.1.2",
|
|
22
22
|
"commander": "^11.1.0",
|
|
23
23
|
"ora": "^5.4.1"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { ASTWrapper, ConfigLoader, FileStorage, type WrappedString } from 'lokal-core';
|
|
7
|
+
|
|
8
|
+
interface WrapOptions {
|
|
9
|
+
config?: string;
|
|
10
|
+
src?: string;
|
|
11
|
+
function?: string;
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Auto-wrap translatable strings in source files
|
|
18
|
+
* Converts plain text to t("key") calls
|
|
19
|
+
*/
|
|
20
|
+
export async function wrapCommand(options: WrapOptions): Promise<void> {
|
|
21
|
+
const spinner = ora('Preparing to wrap strings...').start();
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Load config
|
|
25
|
+
const configLoader = new ConfigLoader();
|
|
26
|
+
let config;
|
|
27
|
+
|
|
28
|
+
if (options.config) {
|
|
29
|
+
config = configLoader.loadSync(options.config);
|
|
30
|
+
} else {
|
|
31
|
+
config = await configLoader.load();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const projectRoot = process.cwd();
|
|
35
|
+
|
|
36
|
+
// Determine source directory
|
|
37
|
+
const sourceDir = options.src
|
|
38
|
+
? path.resolve(projectRoot, options.src)
|
|
39
|
+
: path.resolve(projectRoot, config.sourceDir);
|
|
40
|
+
|
|
41
|
+
// Check if source directory exists
|
|
42
|
+
if (!fs.existsSync(sourceDir)) {
|
|
43
|
+
spinner.fail(chalk.red(`Source directory not found: ${sourceDir}`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Create wrapper
|
|
48
|
+
const wrapper = new ASTWrapper({
|
|
49
|
+
functionName: options.function || config.functionName || 't',
|
|
50
|
+
componentName: config.componentName || 'T',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
spinner.text = `Scanning ${chalk.cyan(sourceDir)} for translatable strings...`;
|
|
54
|
+
|
|
55
|
+
// First pass: collect all wrapped strings without modifying
|
|
56
|
+
const results = wrapper.wrapDirectory(sourceDir, ['.js', '.jsx', '.ts', '.tsx'], true);
|
|
57
|
+
|
|
58
|
+
// Count total strings to wrap
|
|
59
|
+
let totalStrings = 0;
|
|
60
|
+
const fileResults: { file: string, wrapped: WrappedString[] }[] = [];
|
|
61
|
+
|
|
62
|
+
for (const result of results.results) {
|
|
63
|
+
if (result.wrapped.length > 0) {
|
|
64
|
+
fileResults.push({
|
|
65
|
+
file: result.file,
|
|
66
|
+
wrapped: result.wrapped
|
|
67
|
+
});
|
|
68
|
+
totalStrings += result.wrapped.length;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
spinner.succeed(chalk.green(`Found ${chalk.bold(totalStrings)} strings to wrap in ${chalk.bold(results.modifiedFiles)} files`));
|
|
73
|
+
|
|
74
|
+
if (totalStrings === 0) {
|
|
75
|
+
spinner.info(chalk.gray('No strings found that need wrapping.'));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Show preview
|
|
80
|
+
if (!options.dryRun) {
|
|
81
|
+
console.log(chalk.bold('\nPreview (first 10 files):'));
|
|
82
|
+
let count = 0;
|
|
83
|
+
for (const fileResult of fileResults.slice(0, 10)) {
|
|
84
|
+
console.log(chalk.cyan(`\n${path.relative(projectRoot, fileResult.file)}:`));
|
|
85
|
+
for (const wrapped of fileResult.wrapped.slice(0, 5)) {
|
|
86
|
+
console.log(chalk.gray(` "${wrapped.original}" → ${wrapped.wrapped}`));
|
|
87
|
+
count++;
|
|
88
|
+
}
|
|
89
|
+
if (fileResult.wrapped.length > 5) {
|
|
90
|
+
console.log(chalk.gray(` ... and ${fileResult.wrapped.length - 5} more`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (fileResults.length > 10) {
|
|
94
|
+
console.log(chalk.gray(`\n... and ${fileResults.length - 10} more files`));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(chalk.bold('\n⚠ This will modify your source files!'));
|
|
98
|
+
console.log(chalk.gray('Use --dry-run to preview without making changes'));
|
|
99
|
+
} else {
|
|
100
|
+
// Dry run - show what would be changed
|
|
101
|
+
console.log(chalk.bold('\nDry Run - Files that would be modified:'));
|
|
102
|
+
for (const fileResult of fileResults) {
|
|
103
|
+
console.log(chalk.cyan(` ${path.relative(projectRoot, fileResult.file)}`) +
|
|
104
|
+
chalk.gray(` (${fileResult.wrapped.length} strings)`));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (options.dryRun) {
|
|
109
|
+
spinner.info(chalk.yellow('Dry run complete. No files were modified.'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Confirm before making changes
|
|
114
|
+
const confirmSpinner = ora('Applying changes...').start();
|
|
115
|
+
|
|
116
|
+
// Apply changes to each file
|
|
117
|
+
let modifiedCount = 0;
|
|
118
|
+
for (const fileResult of fileResults) {
|
|
119
|
+
const result = wrapper.wrapFile(fileResult.file);
|
|
120
|
+
if (result.modified) {
|
|
121
|
+
modifiedCount++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
confirmSpinner.succeed(chalk.green(`Modified ${chalk.bold(modifiedCount)} files`));
|
|
126
|
+
|
|
127
|
+
// Also update the locale file with the new keys
|
|
128
|
+
const outputDir = path.resolve(projectRoot, config.outputDir);
|
|
129
|
+
const storage = new FileStorage(outputDir);
|
|
130
|
+
|
|
131
|
+
// Collect all new keys
|
|
132
|
+
const newKeys: Record<string, string> = {};
|
|
133
|
+
for (const fileResult of fileResults) {
|
|
134
|
+
for (const wrapped of fileResult.wrapped) {
|
|
135
|
+
newKeys[wrapped.key] = wrapped.original;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Add keys to default locale
|
|
140
|
+
const defaultLocale = config.defaultLocale;
|
|
141
|
+
const existingLocale = storage.loadLocale(defaultLocale);
|
|
142
|
+
let existingData: Record<string, any> = {};
|
|
143
|
+
|
|
144
|
+
if (existingLocale) {
|
|
145
|
+
existingData = existingLocale.data as Record<string, any>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Merge new keys
|
|
149
|
+
const mergedData = storage.mergeLocaleData(defaultLocale, newKeys);
|
|
150
|
+
storage.saveLocale(defaultLocale, mergedData);
|
|
151
|
+
|
|
152
|
+
console.log(chalk.green(`\n✓ Added ${chalk.bold(Object.keys(newKeys).length)} new keys to locales/${defaultLocale}.json`));
|
|
153
|
+
|
|
154
|
+
// Show next steps
|
|
155
|
+
console.log(chalk.bold('\nNext steps:'));
|
|
156
|
+
console.log(chalk.gray(' 1. Review the changes in your source files'));
|
|
157
|
+
console.log(chalk.gray(' 2. Run ') + chalk.cyan('npx lokal translate') + chalk.gray(' to translate to other locales'));
|
|
158
|
+
|
|
159
|
+
} catch (error) {
|
|
160
|
+
spinner.fail(chalk.red(`Wrap failed: ${error}`));
|
|
161
|
+
if (options.verbose) {
|
|
162
|
+
console.error(error);
|
|
163
|
+
}
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function registerWrapCommand(program: Command): void {
|
|
169
|
+
program
|
|
170
|
+
.command('wrap')
|
|
171
|
+
.description('Automatically wrap translatable strings in source files')
|
|
172
|
+
.option('-c, --config <path>', 'Path to config file')
|
|
173
|
+
.option('-s, --src <path>', 'Source directory to scan')
|
|
174
|
+
.option('-f, --function <name>', 'Translation function name', 't')
|
|
175
|
+
.option('-d, --dry-run', 'Preview changes without modifying files', false)
|
|
176
|
+
.option('-v, --verbose', 'Verbose output', false)
|
|
177
|
+
.action(wrapCommand);
|
|
178
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import chalk from 'chalk';
|
|
|
5
5
|
import { registerInitCommand } from './commands/init';
|
|
6
6
|
import { registerScanCommand } from './commands/scan';
|
|
7
7
|
import { registerTranslateCommand } from './commands/translate';
|
|
8
|
+
import { registerWrapCommand } from './commands/wrap';
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
@@ -17,6 +18,7 @@ program
|
|
|
17
18
|
registerInitCommand(program);
|
|
18
19
|
registerScanCommand(program);
|
|
19
20
|
registerTranslateCommand(program);
|
|
21
|
+
registerWrapCommand(program);
|
|
20
22
|
|
|
21
23
|
// Global options
|
|
22
24
|
program
|
|
@@ -29,6 +29,36 @@ declare module 'lokal-core' {
|
|
|
29
29
|
componentName?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export interface WrapOptions {
|
|
33
|
+
functionName?: string;
|
|
34
|
+
componentName?: string;
|
|
35
|
+
excludePatterns?: RegExp[];
|
|
36
|
+
keyPrefix?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WrappedString {
|
|
40
|
+
original: string;
|
|
41
|
+
wrapped: string;
|
|
42
|
+
key: string;
|
|
43
|
+
line: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WrapResult {
|
|
47
|
+
file: string;
|
|
48
|
+
wrapped: WrappedString[];
|
|
49
|
+
errors: string[];
|
|
50
|
+
modified: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class ASTWrapper {
|
|
54
|
+
constructor(options?: WrapOptions);
|
|
55
|
+
generateKey(text: string, filePath: string): string;
|
|
56
|
+
shouldExclude(text: string): boolean;
|
|
57
|
+
wrapFile(filePath: string): WrapResult;
|
|
58
|
+
wrapContent(content: string, filePath?: string): WrapResult;
|
|
59
|
+
wrapDirectory(dirPath: string, extensions?: string[], dryRun?: boolean): { results: WrapResult[], modifiedFiles: number };
|
|
60
|
+
}
|
|
61
|
+
|
|
32
62
|
export class ConfigLoader {
|
|
33
63
|
load(configPath?: string): Promise<LokalConfig>;
|
|
34
64
|
loadSync(configPath?: string): LokalConfig;
|