@devcoda/lokal-cli 1.2.0 → 1.2.1

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 ADDED
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // packages/cli/src/index.ts
27
+ var import_commander = require("commander");
28
+ var import_chalk4 = __toESM(require("chalk"));
29
+
30
+ // packages/cli/src/commands/init.ts
31
+ var fs = __toESM(require("fs"));
32
+ var path = __toESM(require("path"));
33
+ var import_chalk = __toESM(require("chalk"));
34
+ var import_ora = __toESM(require("ora"));
35
+ async function initCommand(options) {
36
+ const spinner = (0, import_ora.default)("Initializing LOKAL...").start();
37
+ try {
38
+ const projectRoot = process.cwd();
39
+ const configPath = path.join(projectRoot, "lokal.config.js");
40
+ const localesDir = path.join(projectRoot, "locales");
41
+ const locales = options.locales ? options.locales.split(",").map((l) => l.trim()) : ["en"];
42
+ const defaultLocale = options.defaultLocale || locales[0];
43
+ if (fs.existsSync(configPath) && !options.force) {
44
+ spinner.warn(import_chalk.default.yellow("LOKAL is already initialized. Use --force to reinitialize."));
45
+ return;
46
+ }
47
+ const configContent = `module.exports = {
48
+ // Supported locales
49
+ locales: ${JSON.stringify(locales)},
50
+
51
+ // Default locale
52
+ defaultLocale: '${defaultLocale}',
53
+
54
+ // Function name for translations (t("key"))
55
+ functionName: 't',
56
+
57
+ // Component name for translations (<T>key</T>)
58
+ componentName: 'T',
59
+
60
+ // Source directory to scan
61
+ sourceDir: './src',
62
+
63
+ // Output directory for locale files
64
+ outputDir: './locales',
65
+
66
+ // AI Translation settings (optional)
67
+ // ai: {
68
+ // provider: 'openai', // or 'gemini'
69
+ // apiKey: process.env.OPENAI_API_KEY,
70
+ // model: 'gpt-4'
71
+ // }
72
+ };
73
+ `;
74
+ fs.writeFileSync(configPath, configContent, "utf-8");
75
+ spinner.succeed(import_chalk.default.green(`Created ${import_chalk.default.bold("lokal.config.js")}`));
76
+ if (!fs.existsSync(localesDir)) {
77
+ fs.mkdirSync(localesDir, { recursive: true });
78
+ }
79
+ const defaultLocalePath = path.join(localesDir, `${defaultLocale}.json`);
80
+ if (!fs.existsSync(defaultLocalePath)) {
81
+ const initialData = {
82
+ _meta: {
83
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
84
+ description: "Default locale file"
85
+ }
86
+ };
87
+ fs.writeFileSync(defaultLocalePath, JSON.stringify(initialData, null, 2), "utf-8");
88
+ spinner.succeed(import_chalk.default.green(`Created ${import_chalk.default.bold(`locales/${defaultLocale}.json`)}`));
89
+ }
90
+ const gitignorePath = path.join(projectRoot, ".gitignore");
91
+ if (fs.existsSync(gitignorePath)) {
92
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
93
+ if (!gitignoreContent.includes("/locales/")) {
94
+ fs.appendFileSync(gitignorePath, "\n# LOKAL translations\nlocales/\n");
95
+ spinner.succeed(import_chalk.default.green("Updated .gitignore"));
96
+ }
97
+ }
98
+ console.log(import_chalk.default.bold("\n\u2713 LOKAL initialized successfully!"));
99
+ console.log(import_chalk.default.gray("\nNext steps:"));
100
+ console.log(import_chalk.default.gray(' 1. Add translation strings to your code using t("key") or <T>key</T>'));
101
+ console.log(import_chalk.default.gray(" 2. Run ") + import_chalk.default.cyan("npx lokal scan") + import_chalk.default.gray(" to extract strings"));
102
+ console.log(import_chalk.default.gray(" 3. Run ") + import_chalk.default.cyan("npx lokal translate") + import_chalk.default.gray(" to translate with AI"));
103
+ } catch (error) {
104
+ spinner.fail(import_chalk.default.red(`Failed to initialize: ${error}`));
105
+ process.exit(1);
106
+ }
107
+ }
108
+ function registerInitCommand(program2) {
109
+ program2.command("init").description("Initialize LOKAL in your project").option("-l, --locales <locales>", "Comma-separated list of locales", "en").option("-d, --default-locale <locale>", "Default locale", "en").option("-f, --force", "Force reinitialization", false).action(initCommand);
110
+ }
111
+
112
+ // packages/cli/src/commands/scan.ts
113
+ var path2 = __toESM(require("path"));
114
+ var import_chalk2 = __toESM(require("chalk"));
115
+ var import_ora2 = __toESM(require("ora"));
116
+ var import_lokal_core = require("lokal-core");
117
+ async function scanCommand(options) {
118
+ const spinner = (0, import_ora2.default)("Scanning for translation strings...").start();
119
+ try {
120
+ const configLoader = new import_lokal_core.ConfigLoader();
121
+ let config;
122
+ if (options.config) {
123
+ config = configLoader.loadSync(options.config);
124
+ } else {
125
+ config = await configLoader.load();
126
+ }
127
+ const projectRoot = process.cwd();
128
+ const sourceDir = path2.resolve(projectRoot, config.sourceDir);
129
+ const outputDir = options.output ? path2.resolve(projectRoot, options.output) : path2.resolve(projectRoot, config.outputDir);
130
+ spinner.text = `Scanning ${import_chalk2.default.cyan(sourceDir)}...`;
131
+ const parser = new import_lokal_core.ASTParser({
132
+ filePath: sourceDir,
133
+ functionName: config.functionName,
134
+ componentName: config.componentName
135
+ });
136
+ const result = parser.scanDirectory(sourceDir);
137
+ if (result.errors.length > 0 && options.verbose) {
138
+ for (const error of result.errors) {
139
+ spinner.warn(import_chalk2.default.yellow(error));
140
+ }
141
+ }
142
+ spinner.succeed(import_chalk2.default.green(`Found ${import_chalk2.default.bold(result.strings.length)} translation strings`));
143
+ if (result.strings.length === 0) {
144
+ spinner.info(import_chalk2.default.gray('No strings found. Make sure to use t("key") or <T>key</T> in your code.'));
145
+ return;
146
+ }
147
+ const storage = new import_lokal_core.FileStorage(outputDir);
148
+ const uniqueStrings = /* @__PURE__ */ new Map();
149
+ for (const str of result.strings) {
150
+ uniqueStrings.set(str.key, str);
151
+ }
152
+ const defaultLocale = config.defaultLocale;
153
+ const existingLocale = storage.loadLocale(defaultLocale);
154
+ let existingData = {};
155
+ if (existingLocale) {
156
+ existingData = existingLocale.data;
157
+ }
158
+ const newData = {};
159
+ for (const [key, value] of uniqueStrings) {
160
+ newData[key] = existingData[key] || value.value;
161
+ }
162
+ const mergedData = storage.mergeLocaleData(defaultLocale, newData);
163
+ storage.saveLocale(defaultLocale, mergedData);
164
+ spinner.succeed(import_chalk2.default.green(`Updated ${import_chalk2.default.bold(`locales/${defaultLocale}.json`)}`));
165
+ if (options.verbose) {
166
+ console.log(import_chalk2.default.bold("\nExtracted strings:"));
167
+ const sampleKeys = Array.from(uniqueStrings.keys()).slice(0, 10);
168
+ for (const key of sampleKeys) {
169
+ console.log(import_chalk2.default.gray(` \u2022 ${key}`));
170
+ }
171
+ if (uniqueStrings.size > 10) {
172
+ console.log(import_chalk2.default.gray(` ... and ${uniqueStrings.size - 10} more`));
173
+ }
174
+ }
175
+ const locales = storage.getAvailableLocales();
176
+ const otherLocales = locales.filter((l) => l !== defaultLocale);
177
+ if (otherLocales.length > 0) {
178
+ console.log(import_chalk2.default.gray(`
179
+ Other locales detected: ${otherLocales.join(", ")}`));
180
+ console.log(import_chalk2.default.gray("Run ") + import_chalk2.default.cyan("npx lokal translate") + import_chalk2.default.gray(" to translate missing strings"));
181
+ }
182
+ } catch (error) {
183
+ spinner.fail(import_chalk2.default.red(`Scan failed: ${error}`));
184
+ if (options.verbose) {
185
+ console.error(error);
186
+ }
187
+ process.exit(1);
188
+ }
189
+ }
190
+ function registerScanCommand(program2) {
191
+ program2.command("scan").description("Scan source files for translation strings").option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Output directory for locale files").option("-v, --verbose", "Verbose output", false).action(scanCommand);
192
+ }
193
+
194
+ // packages/cli/src/commands/translate.ts
195
+ var path3 = __toESM(require("path"));
196
+ var import_chalk3 = __toESM(require("chalk"));
197
+ var import_ora3 = __toESM(require("ora"));
198
+ var import_lokal_core2 = require("lokal-core");
199
+ async function translateCommand(options) {
200
+ const spinner = (0, import_ora3.default)("Loading configuration...").start();
201
+ try {
202
+ const configLoader = new import_lokal_core2.ConfigLoader();
203
+ let config;
204
+ if (options.config) {
205
+ config = configLoader.loadSync(options.config);
206
+ } else {
207
+ config = await configLoader.load();
208
+ }
209
+ if (!config.ai) {
210
+ spinner.fail(import_chalk3.default.red("AI provider not configured. Add ai configuration to lokal.config.js"));
211
+ console.log(import_chalk3.default.gray("\nExample configuration:"));
212
+ console.log(import_chalk3.default.gray(" ai: {"));
213
+ console.log(import_chalk3.default.gray(' provider: "openai",'));
214
+ console.log(import_chalk3.default.gray(" apiKey: process.env.OPENAI_API_KEY"));
215
+ console.log(import_chalk3.default.gray(" }"));
216
+ process.exit(1);
217
+ }
218
+ const apiKey = config.ai.apiKey || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY;
219
+ if (!apiKey) {
220
+ spinner.fail(import_chalk3.default.red("No API key found. Set ai.apiKey in config or environment variable."));
221
+ process.exit(1);
222
+ }
223
+ spinner.text = "Initializing AI translator...";
224
+ const provider = import_lokal_core2.TranslationProviderFactory.create(
225
+ config.ai.provider,
226
+ apiKey,
227
+ config.ai.model
228
+ );
229
+ const translator = new import_lokal_core2.AITranslator(provider);
230
+ const projectRoot = process.cwd();
231
+ const outputDir = path3.resolve(projectRoot, config.outputDir);
232
+ const storage = new import_lokal_core2.FileStorage(outputDir);
233
+ spinner.succeed("AI translator ready");
234
+ let targetLocales = [];
235
+ const availableLocales = config.locales || [];
236
+ if (options.all) {
237
+ targetLocales = availableLocales.filter((l) => l !== config.defaultLocale);
238
+ } else if (options.locale) {
239
+ targetLocales = [options.locale];
240
+ } else {
241
+ targetLocales = availableLocales.filter((l) => l !== config.defaultLocale).slice(0, 1);
242
+ }
243
+ if (targetLocales.length === 0) {
244
+ spinner.warn(import_chalk3.default.yellow("No target locales to translate. Add more locales to your config."));
245
+ return;
246
+ }
247
+ const sourceLocale = config.defaultLocale;
248
+ const sourceData = storage.loadLocale(sourceLocale);
249
+ if (!sourceData) {
250
+ spinner.fail(import_chalk3.default.red(`Source locale ${sourceLocale} not found. Run 'lokal scan' first.`));
251
+ process.exit(1);
252
+ }
253
+ for (const targetLocale of targetLocales) {
254
+ const translateSpinner = (0, import_ora3.default)(`Translating to ${import_chalk3.default.cyan(targetLocale)}...`).start();
255
+ const targetLocaleFile = storage.loadLocale(targetLocale);
256
+ const targetData = targetLocaleFile ? targetLocaleFile.data : {};
257
+ const translatedData = await translator.translateMissingKeys(
258
+ sourceData.data,
259
+ targetData,
260
+ sourceLocale,
261
+ targetLocale
262
+ );
263
+ storage.saveLocale(targetLocale, translatedData);
264
+ translateSpinner.succeed(import_chalk3.default.green(`Translated to ${import_chalk3.default.bold(targetLocale)}`));
265
+ if (options.verbose) {
266
+ const keys = Object.keys(translatedData).slice(0, 5);
267
+ for (const key of keys) {
268
+ const value = translatedData[key];
269
+ if (typeof value === "string") {
270
+ console.log(import_chalk3.default.gray(` ${key}: ${value}`));
271
+ }
272
+ }
273
+ }
274
+ }
275
+ console.log(import_chalk3.default.bold("\n\u2713 Translation complete!"));
276
+ console.log(import_chalk3.default.gray(`
277
+ Run `) + import_chalk3.default.cyan("npx lokal scan") + import_chalk3.default.gray(" to see updated translations"));
278
+ } catch (error) {
279
+ spinner.fail(import_chalk3.default.red(`Translation failed: ${error}`));
280
+ if (options.verbose) {
281
+ console.error(error);
282
+ }
283
+ process.exit(1);
284
+ }
285
+ }
286
+ function registerTranslateCommand(program2) {
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
+ }
289
+
290
+ // packages/cli/src/index.ts
291
+ var program = new import_commander.Command();
292
+ program.name("lokal").description("AI-powered localization ecosystem for React and React Native").version("1.0.0");
293
+ registerInitCommand(program);
294
+ registerScanCommand(program);
295
+ registerTranslateCommand(program);
296
+ program.option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand) => {
297
+ const opts = thisCommand.opts();
298
+ if (opts.verbose) {
299
+ process.env.LOKAL_VERBOSE = "true";
300
+ }
301
+ });
302
+ program.on("command:*", () => {
303
+ console.error(import_chalk4.default.red(`Invalid command: ${program.args.join(" ")}`));
304
+ console.log(import_chalk4.default.gray(`See ${import_chalk4.default.cyan("--help")} for a list of available commands.`));
305
+ process.exit(1);
306
+ });
307
+ program.parse(process.argv);
308
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,uBAAwB;AACxB,IAAAA,gBAAkB;;;ACHlB,SAAoB;AACpB,WAAsB;AAEtB,mBAAkB;AAClB,iBAAgB;AAWhB,eAAsB,YAAY,SAAqC;AACnE,QAAM,cAAU,WAAAC,SAAI,uBAAuB,EAAE,MAAM;AAEnD,MAAI;AACA,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,aAAkB,UAAK,aAAa,iBAAiB;AAC3D,UAAM,aAAkB,UAAK,aAAa,SAAS;AAGnD,UAAM,UAAU,QAAQ,UAClB,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC,IAC5C,CAAC,IAAI;AAEX,UAAM,gBAAgB,QAAQ,iBAAiB,QAAQ,CAAC;AAGxD,QAAO,cAAW,UAAU,KAAK,CAAC,QAAQ,OAAO;AAC7C,cAAQ,KAAK,aAAAC,QAAM,OAAO,4DAA4D,CAAC;AACvF;AAAA,IACJ;AAGA,UAAM,gBAAgB;AAAA;AAAA,aAEjB,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA;AAAA,oBAGhB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBzB,IAAG,iBAAc,YAAY,eAAe,OAAO;AACnD,YAAQ,QAAQ,aAAAA,QAAM,MAAM,WAAW,aAAAA,QAAM,KAAK,iBAAiB,CAAC,EAAE,CAAC;AAGvE,QAAI,CAAI,cAAW,UAAU,GAAG;AAC5B,MAAG,aAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,IAChD;AAGA,UAAM,oBAAyB,UAAK,YAAY,GAAG,aAAa,OAAO;AACvE,QAAI,CAAI,cAAW,iBAAiB,GAAG;AACnC,YAAM,cAAc;AAAA,QAChB,OAAO;AAAA,UACH,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,aAAa;AAAA,QACjB;AAAA,MACJ;AACA,MAAG,iBAAc,mBAAmB,KAAK,UAAU,aAAa,MAAM,CAAC,GAAG,OAAO;AACjF,cAAQ,QAAQ,aAAAA,QAAM,MAAM,WAAW,aAAAA,QAAM,KAAK,WAAW,aAAa,OAAO,CAAC,EAAE,CAAC;AAAA,IACzF;AAGA,UAAM,gBAAqB,UAAK,aAAa,YAAY;AACzD,QAAO,cAAW,aAAa,GAAG;AAC9B,YAAM,mBAAsB,gBAAa,eAAe,OAAO;AAC/D,UAAI,CAAC,iBAAiB,SAAS,WAAW,GAAG;AACzC,QAAG,kBAAe,eAAe,oCAAoC;AACrE,gBAAQ,QAAQ,aAAAA,QAAM,MAAM,oBAAoB,CAAC;AAAA,MACrD;AAAA,IACJ;AAEA,YAAQ,IAAI,aAAAA,QAAM,KAAK,0CAAqC,CAAC;AAC7D,YAAQ,IAAI,aAAAA,QAAM,KAAK,eAAe,CAAC;AACvC,YAAQ,IAAI,aAAAA,QAAM,KAAK,wEAAwE,CAAC;AAChG,YAAQ,IAAI,aAAAA,QAAM,KAAK,WAAW,IAAI,aAAAA,QAAM,KAAK,gBAAgB,IAAI,aAAAA,QAAM,KAAK,qBAAqB,CAAC;AACtG,YAAQ,IAAI,aAAAA,QAAM,KAAK,WAAW,IAAI,aAAAA,QAAM,KAAK,qBAAqB,IAAI,aAAAA,QAAM,KAAK,uBAAuB,CAAC;AAAA,EAEjH,SAAS,OAAO;AACZ,YAAQ,KAAK,aAAAA,QAAM,IAAI,yBAAyB,KAAK,EAAE,CAAC;AACxD,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEO,SAAS,oBAAoBC,UAAwB;AACxD,EAAAA,SACK,QAAQ,MAAM,EACd,YAAY,kCAAkC,EAC9C,OAAO,2BAA2B,mCAAmC,IAAI,EACzE,OAAO,iCAAiC,kBAAkB,IAAI,EAC9D,OAAO,eAAe,0BAA0B,KAAK,EACrD,OAAO,WAAW;AAC3B;;;ACpHA,IAAAC,QAAsB;AAEtB,IAAAC,gBAAkB;AAClB,IAAAC,cAAgB;AAChB,wBAA2E;AAW3E,eAAsB,YAAY,SAAqC;AACnE,QAAM,cAAU,YAAAC,SAAI,qCAAqC,EAAE,MAAM;AAEjE,MAAI;AAEA,UAAM,eAAe,IAAI,+BAAa;AACtC,QAAI;AAEJ,QAAI,QAAQ,QAAQ;AAChB,eAAS,aAAa,SAAS,QAAQ,MAAM;AAAA,IACjD,OAAO;AACH,eAAS,MAAM,aAAa,KAAK;AAAA,IACrC;AAEA,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,YAAiB,cAAQ,aAAa,OAAO,SAAS;AAC5D,UAAM,YAAY,QAAQ,SACf,cAAQ,aAAa,QAAQ,MAAM,IACnC,cAAQ,aAAa,OAAO,SAAS;AAEhD,YAAQ,OAAO,YAAY,cAAAC,QAAM,KAAK,SAAS,CAAC;AAGhD,UAAM,SAAS,IAAI,4BAAU;AAAA,MACzB,UAAU;AAAA,MACV,cAAc,OAAO;AAAA,MACrB,eAAe,OAAO;AAAA,IAC1B,CAAC;AAGD,UAAM,SAAS,OAAO,cAAc,SAAS;AAE7C,QAAI,OAAO,OAAO,SAAS,KAAK,QAAQ,SAAS;AAC7C,iBAAW,SAAS,OAAO,QAAQ;AAC/B,gBAAQ,KAAK,cAAAA,QAAM,OAAO,KAAK,CAAC;AAAA,MACpC;AAAA,IACJ;AAEA,YAAQ,QAAQ,cAAAA,QAAM,MAAM,SAAS,cAAAA,QAAM,KAAK,OAAO,QAAQ,MAAM,CAAC,sBAAsB,CAAC;AAE7F,QAAI,OAAO,QAAQ,WAAW,GAAG;AAC7B,cAAQ,KAAK,cAAAA,QAAM,KAAK,yEAAyE,CAAC;AAClG;AAAA,IACJ;AAGA,UAAM,UAAU,IAAI,8BAAY,SAAS;AAGzC,UAAM,gBAAgB,oBAAI,IAA6B;AACvD,eAAW,OAAO,OAAO,SAAS;AAC9B,oBAAc,IAAI,IAAI,KAAK,GAAG;AAAA,IAClC;AAGA,UAAM,gBAAgB,OAAO;AAC7B,UAAM,iBAAiB,QAAQ,WAAW,aAAa;AAGvD,QAAI,eAAoC,CAAC;AACzC,QAAI,gBAAgB;AAChB,qBAAe,eAAe;AAAA,IAClC;AAGA,UAAM,UAAkC,CAAC;AACzC,eAAW,CAAC,KAAK,KAAK,KAAK,eAAe;AAEtC,cAAQ,GAAG,IAAI,aAAa,GAAG,KAAK,MAAM;AAAA,IAC9C;AAGA,UAAM,aAAa,QAAQ,gBAAgB,eAAe,OAAO;AACjE,YAAQ,WAAW,eAAe,UAAU;AAE5C,YAAQ,QAAQ,cAAAA,QAAM,MAAM,WAAW,cAAAA,QAAM,KAAK,WAAW,aAAa,OAAO,CAAC,EAAE,CAAC;AAGrF,QAAI,QAAQ,SAAS;AACjB,cAAQ,IAAI,cAAAA,QAAM,KAAK,sBAAsB,CAAC;AAC9C,YAAM,aAAa,MAAM,KAAK,cAAc,KAAK,CAAC,EAAE,MAAM,GAAG,EAAE;AAC/D,iBAAW,OAAO,YAAY;AAC1B,gBAAQ,IAAI,cAAAA,QAAM,KAAK,YAAO,GAAG,EAAE,CAAC;AAAA,MACxC;AACA,UAAI,cAAc,OAAO,IAAI;AACzB,gBAAQ,IAAI,cAAAA,QAAM,KAAK,aAAa,cAAc,OAAO,EAAE,OAAO,CAAC;AAAA,MACvE;AAAA,IACJ;AAGA,UAAM,UAAU,QAAQ,oBAAoB;AAC5C,UAAM,eAAe,QAAQ,OAAO,OAAK,MAAM,aAAa;AAE5D,QAAI,aAAa,SAAS,GAAG;AACzB,cAAQ,IAAI,cAAAA,QAAM,KAAK;AAAA,0BAA6B,aAAa,KAAK,IAAI,CAAC,EAAE,CAAC;AAC9E,cAAQ,IAAI,cAAAA,QAAM,KAAK,MAAM,IAAI,cAAAA,QAAM,KAAK,qBAAqB,IAAI,cAAAA,QAAM,KAAK,+BAA+B,CAAC;AAAA,IACpH;AAAA,EAEJ,SAAS,OAAO;AACZ,YAAQ,KAAK,cAAAA,QAAM,IAAI,gBAAgB,KAAK,EAAE,CAAC;AAC/C,QAAI,QAAQ,SAAS;AACjB,cAAQ,MAAM,KAAK;AAAA,IACvB;AACA,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEO,SAAS,oBAAoBC,UAAwB;AACxD,EAAAA,SACK,QAAQ,MAAM,EACd,YAAY,2CAA2C,EACvD,OAAO,uBAAuB,qBAAqB,EACnD,OAAO,uBAAuB,mCAAmC,EACjE,OAAO,iBAAiB,kBAAkB,KAAK,EAC/C,OAAO,WAAW;AAC3B;;;AClIA,IAAAC,QAAsB;AAEtB,IAAAC,gBAAkB;AAClB,IAAAC,cAAgB;AAChB,IAAAC,qBAMO;AAYP,eAAsB,iBAAiB,SAA0C;AAC7E,QAAM,cAAU,YAAAC,SAAI,0BAA0B,EAAE,MAAM;AAEtD,MAAI;AAEA,UAAM,eAAe,IAAI,gCAAa;AACtC,QAAI;AAEJ,QAAI,QAAQ,QAAQ;AAChB,eAAS,aAAa,SAAS,QAAQ,MAAM;AAAA,IACjD,OAAO;AACH,eAAS,MAAM,aAAa,KAAK;AAAA,IACrC;AAGA,QAAI,CAAC,OAAO,IAAI;AACZ,cAAQ,KAAK,cAAAC,QAAM,IAAI,qEAAqE,CAAC;AAC7F,cAAQ,IAAI,cAAAA,QAAM,KAAK,0BAA0B,CAAC;AAClD,cAAQ,IAAI,cAAAA,QAAM,KAAK,SAAS,CAAC;AACjC,cAAQ,IAAI,cAAAA,QAAM,KAAK,yBAAyB,CAAC;AACjD,cAAQ,IAAI,cAAAA,QAAM,KAAK,wCAAwC,CAAC;AAChE,cAAQ,IAAI,cAAAA,QAAM,KAAK,KAAK,CAAC;AAC7B,cAAQ,KAAK,CAAC;AAAA,IAClB;AAEA,UAAM,SAAS,OAAO,GAAG,UAAU,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;AAC7E,QAAI,CAAC,QAAQ;AACT,cAAQ,KAAK,cAAAA,QAAM,IAAI,oEAAoE,CAAC;AAC5F,cAAQ,KAAK,CAAC;AAAA,IAClB;AAEA,YAAQ,OAAO;AAGf,UAAM,WAAW,8CAA2B;AAAA,MACxC,OAAO,GAAG;AAAA,MACV;AAAA,MACA,OAAO,GAAG;AAAA,IACd;AAGA,UAAM,aAAa,IAAI,gCAAa,QAAQ;AAE5C,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,YAAiB,cAAQ,aAAa,OAAO,SAAS;AAC5D,UAAM,UAAU,IAAI,+BAAY,SAAS;AAEzC,YAAQ,QAAQ,qBAAqB;AAGrC,QAAI,gBAA0B,CAAC;AAC/B,UAAM,mBAAmB,OAAO,WAAW,CAAC;AAE5C,QAAI,QAAQ,KAAK;AACb,sBAAgB,iBAAiB,OAAO,OAAK,MAAM,OAAO,aAAa;AAAA,IAC3E,WAAW,QAAQ,QAAQ;AACvB,sBAAgB,CAAC,QAAQ,MAAM;AAAA,IACnC,OAAO;AAEH,sBAAgB,iBAAiB,OAAO,OAAK,MAAM,OAAO,aAAa,EAAE,MAAM,GAAG,CAAC;AAAA,IACvF;AAEA,QAAI,cAAc,WAAW,GAAG;AAC5B,cAAQ,KAAK,cAAAA,QAAM,OAAO,kEAAkE,CAAC;AAC7F;AAAA,IACJ;AAGA,UAAM,eAAe,OAAO;AAC5B,UAAM,aAAa,QAAQ,WAAW,YAAY;AAElD,QAAI,CAAC,YAAY;AACb,cAAQ,KAAK,cAAAA,QAAM,IAAI,iBAAiB,YAAY,qCAAqC,CAAC;AAC1F,cAAQ,KAAK,CAAC;AAAA,IAClB;AAGA,eAAW,gBAAgB,eAAe;AACtC,YAAM,uBAAmB,YAAAD,SAAI,kBAAkB,cAAAC,QAAM,KAAK,YAAY,CAAC,KAAK,EAAE,MAAM;AAGpF,YAAM,mBAAmB,QAAQ,WAAW,YAAY;AACxD,YAAM,aAAyB,mBAAmB,iBAAiB,OAAO,CAAC;AAG3E,YAAM,iBAAiB,MAAM,WAAW;AAAA,QACpC,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,MACJ;AAGA,cAAQ,WAAW,cAAc,cAAc;AAE/C,uBAAiB,QAAQ,cAAAA,QAAM,MAAM,iBAAiB,cAAAA,QAAM,KAAK,YAAY,CAAC,EAAE,CAAC;AAEjF,UAAI,QAAQ,SAAS;AAEjB,cAAM,OAAO,OAAO,KAAK,cAAc,EAAE,MAAM,GAAG,CAAC;AACnD,mBAAW,OAAO,MAAM;AACpB,gBAAM,QAAQ,eAAe,GAAG;AAChC,cAAI,OAAO,UAAU,UAAU;AAC3B,oBAAQ,IAAI,cAAAA,QAAM,KAAK,KAAK,GAAG,KAAK,KAAK,EAAE,CAAC;AAAA,UAChD;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,YAAQ,IAAI,cAAAA,QAAM,KAAK,gCAA2B,CAAC;AACnD,YAAQ,IAAI,cAAAA,QAAM,KAAK;AAAA,KAAQ,IAAI,cAAAA,QAAM,KAAK,gBAAgB,IAAI,cAAAA,QAAM,KAAK,8BAA8B,CAAC;AAAA,EAEhH,SAAS,OAAO;AACZ,YAAQ,KAAK,cAAAA,QAAM,IAAI,uBAAuB,KAAK,EAAE,CAAC;AACtD,QAAI,QAAQ,SAAS;AACjB,cAAQ,MAAM,KAAK;AAAA,IACvB;AACA,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEO,SAAS,yBAAyBC,UAAwB;AAC7D,EAAAA,SACK,QAAQ,WAAW,EACnB,YAAY,oCAAoC,EAChD,OAAO,uBAAuB,qBAAqB,EACnD,OAAO,yBAAyB,8BAA8B,EAC9D,OAAO,aAAa,yBAAyB,KAAK,EAClD,OAAO,iBAAiB,kBAAkB,KAAK,EAC/C,OAAO,gBAAgB;AAChC;;;AHhJA,IAAM,UAAU,IAAI,yBAAQ;AAE5B,QACK,KAAK,OAAO,EACZ,YAAY,8DAA8D,EAC1E,QAAQ,OAAO;AAGpB,oBAAoB,OAAO;AAC3B,oBAAoB,OAAO;AAC3B,yBAAyB,OAAO;AAGhC,QACK,OAAO,iBAAiB,gBAAgB,EACxC,KAAK,aAAa,CAAC,gBAAgB;AAChC,QAAM,OAAO,YAAY,KAAK;AAC9B,MAAI,KAAK,SAAS;AACd,YAAQ,IAAI,gBAAgB;AAAA,EAChC;AACJ,CAAC;AAGL,QAAQ,GAAG,aAAa,MAAM;AAC1B,UAAQ,MAAM,cAAAC,QAAM,IAAI,oBAAoB,QAAQ,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;AACrE,UAAQ,IAAI,cAAAA,QAAM,KAAK,OAAO,cAAAA,QAAM,KAAK,QAAQ,CAAC,oCAAoC,CAAC;AACvF,UAAQ,KAAK,CAAC;AAClB,CAAC;AAGD,QAAQ,MAAM,QAAQ,IAAI;","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"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+
3
+ // packages/cli/src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk4 from "chalk";
6
+
7
+ // packages/cli/src/commands/init.ts
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import chalk from "chalk";
11
+ import ora from "ora";
12
+ async function initCommand(options) {
13
+ const spinner = ora("Initializing LOKAL...").start();
14
+ try {
15
+ const projectRoot = process.cwd();
16
+ const configPath = path.join(projectRoot, "lokal.config.js");
17
+ const localesDir = path.join(projectRoot, "locales");
18
+ const locales = options.locales ? options.locales.split(",").map((l) => l.trim()) : ["en"];
19
+ const defaultLocale = options.defaultLocale || locales[0];
20
+ if (fs.existsSync(configPath) && !options.force) {
21
+ spinner.warn(chalk.yellow("LOKAL is already initialized. Use --force to reinitialize."));
22
+ return;
23
+ }
24
+ const configContent = `module.exports = {
25
+ // Supported locales
26
+ locales: ${JSON.stringify(locales)},
27
+
28
+ // Default locale
29
+ defaultLocale: '${defaultLocale}',
30
+
31
+ // Function name for translations (t("key"))
32
+ functionName: 't',
33
+
34
+ // Component name for translations (<T>key</T>)
35
+ componentName: 'T',
36
+
37
+ // Source directory to scan
38
+ sourceDir: './src',
39
+
40
+ // Output directory for locale files
41
+ outputDir: './locales',
42
+
43
+ // AI Translation settings (optional)
44
+ // ai: {
45
+ // provider: 'openai', // or 'gemini'
46
+ // apiKey: process.env.OPENAI_API_KEY,
47
+ // model: 'gpt-4'
48
+ // }
49
+ };
50
+ `;
51
+ fs.writeFileSync(configPath, configContent, "utf-8");
52
+ spinner.succeed(chalk.green(`Created ${chalk.bold("lokal.config.js")}`));
53
+ if (!fs.existsSync(localesDir)) {
54
+ fs.mkdirSync(localesDir, { recursive: true });
55
+ }
56
+ const defaultLocalePath = path.join(localesDir, `${defaultLocale}.json`);
57
+ if (!fs.existsSync(defaultLocalePath)) {
58
+ const initialData = {
59
+ _meta: {
60
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
61
+ description: "Default locale file"
62
+ }
63
+ };
64
+ fs.writeFileSync(defaultLocalePath, JSON.stringify(initialData, null, 2), "utf-8");
65
+ spinner.succeed(chalk.green(`Created ${chalk.bold(`locales/${defaultLocale}.json`)}`));
66
+ }
67
+ const gitignorePath = path.join(projectRoot, ".gitignore");
68
+ if (fs.existsSync(gitignorePath)) {
69
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
70
+ if (!gitignoreContent.includes("/locales/")) {
71
+ fs.appendFileSync(gitignorePath, "\n# LOKAL translations\nlocales/\n");
72
+ spinner.succeed(chalk.green("Updated .gitignore"));
73
+ }
74
+ }
75
+ console.log(chalk.bold("\n\u2713 LOKAL initialized successfully!"));
76
+ console.log(chalk.gray("\nNext steps:"));
77
+ console.log(chalk.gray(' 1. Add translation strings to your code using t("key") or <T>key</T>'));
78
+ console.log(chalk.gray(" 2. Run ") + chalk.cyan("npx lokal scan") + chalk.gray(" to extract strings"));
79
+ console.log(chalk.gray(" 3. Run ") + chalk.cyan("npx lokal translate") + chalk.gray(" to translate with AI"));
80
+ } catch (error) {
81
+ spinner.fail(chalk.red(`Failed to initialize: ${error}`));
82
+ process.exit(1);
83
+ }
84
+ }
85
+ function registerInitCommand(program2) {
86
+ program2.command("init").description("Initialize LOKAL in your project").option("-l, --locales <locales>", "Comma-separated list of locales", "en").option("-d, --default-locale <locale>", "Default locale", "en").option("-f, --force", "Force reinitialization", false).action(initCommand);
87
+ }
88
+
89
+ // packages/cli/src/commands/scan.ts
90
+ import * as path2 from "path";
91
+ import chalk2 from "chalk";
92
+ import ora2 from "ora";
93
+ import { ASTParser, ConfigLoader, FileStorage } from "lokal-core";
94
+ async function scanCommand(options) {
95
+ const spinner = ora2("Scanning for translation strings...").start();
96
+ try {
97
+ const configLoader = new ConfigLoader();
98
+ let config;
99
+ if (options.config) {
100
+ config = configLoader.loadSync(options.config);
101
+ } else {
102
+ config = await configLoader.load();
103
+ }
104
+ const projectRoot = process.cwd();
105
+ const sourceDir = path2.resolve(projectRoot, config.sourceDir);
106
+ const outputDir = options.output ? path2.resolve(projectRoot, options.output) : path2.resolve(projectRoot, config.outputDir);
107
+ spinner.text = `Scanning ${chalk2.cyan(sourceDir)}...`;
108
+ const parser = new ASTParser({
109
+ filePath: sourceDir,
110
+ functionName: config.functionName,
111
+ componentName: config.componentName
112
+ });
113
+ const result = parser.scanDirectory(sourceDir);
114
+ if (result.errors.length > 0 && options.verbose) {
115
+ for (const error of result.errors) {
116
+ spinner.warn(chalk2.yellow(error));
117
+ }
118
+ }
119
+ spinner.succeed(chalk2.green(`Found ${chalk2.bold(result.strings.length)} translation strings`));
120
+ if (result.strings.length === 0) {
121
+ spinner.info(chalk2.gray('No strings found. Make sure to use t("key") or <T>key</T> in your code.'));
122
+ return;
123
+ }
124
+ const storage = new FileStorage(outputDir);
125
+ const uniqueStrings = /* @__PURE__ */ new Map();
126
+ for (const str of result.strings) {
127
+ uniqueStrings.set(str.key, str);
128
+ }
129
+ const defaultLocale = config.defaultLocale;
130
+ const existingLocale = storage.loadLocale(defaultLocale);
131
+ let existingData = {};
132
+ if (existingLocale) {
133
+ existingData = existingLocale.data;
134
+ }
135
+ const newData = {};
136
+ for (const [key, value] of uniqueStrings) {
137
+ newData[key] = existingData[key] || value.value;
138
+ }
139
+ const mergedData = storage.mergeLocaleData(defaultLocale, newData);
140
+ storage.saveLocale(defaultLocale, mergedData);
141
+ spinner.succeed(chalk2.green(`Updated ${chalk2.bold(`locales/${defaultLocale}.json`)}`));
142
+ if (options.verbose) {
143
+ console.log(chalk2.bold("\nExtracted strings:"));
144
+ const sampleKeys = Array.from(uniqueStrings.keys()).slice(0, 10);
145
+ for (const key of sampleKeys) {
146
+ console.log(chalk2.gray(` \u2022 ${key}`));
147
+ }
148
+ if (uniqueStrings.size > 10) {
149
+ console.log(chalk2.gray(` ... and ${uniqueStrings.size - 10} more`));
150
+ }
151
+ }
152
+ const locales = storage.getAvailableLocales();
153
+ const otherLocales = locales.filter((l) => l !== defaultLocale);
154
+ if (otherLocales.length > 0) {
155
+ console.log(chalk2.gray(`
156
+ Other locales detected: ${otherLocales.join(", ")}`));
157
+ console.log(chalk2.gray("Run ") + chalk2.cyan("npx lokal translate") + chalk2.gray(" to translate missing strings"));
158
+ }
159
+ } catch (error) {
160
+ spinner.fail(chalk2.red(`Scan failed: ${error}`));
161
+ if (options.verbose) {
162
+ console.error(error);
163
+ }
164
+ process.exit(1);
165
+ }
166
+ }
167
+ function registerScanCommand(program2) {
168
+ program2.command("scan").description("Scan source files for translation strings").option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Output directory for locale files").option("-v, --verbose", "Verbose output", false).action(scanCommand);
169
+ }
170
+
171
+ // packages/cli/src/commands/translate.ts
172
+ import * as path3 from "path";
173
+ import chalk3 from "chalk";
174
+ import ora3 from "ora";
175
+ import {
176
+ ConfigLoader as ConfigLoader2,
177
+ FileStorage as FileStorage2,
178
+ AITranslator,
179
+ TranslationProviderFactory
180
+ } from "lokal-core";
181
+ async function translateCommand(options) {
182
+ const spinner = ora3("Loading configuration...").start();
183
+ try {
184
+ const configLoader = new ConfigLoader2();
185
+ let config;
186
+ if (options.config) {
187
+ config = configLoader.loadSync(options.config);
188
+ } else {
189
+ config = await configLoader.load();
190
+ }
191
+ if (!config.ai) {
192
+ spinner.fail(chalk3.red("AI provider not configured. Add ai configuration to lokal.config.js"));
193
+ console.log(chalk3.gray("\nExample configuration:"));
194
+ console.log(chalk3.gray(" ai: {"));
195
+ console.log(chalk3.gray(' provider: "openai",'));
196
+ console.log(chalk3.gray(" apiKey: process.env.OPENAI_API_KEY"));
197
+ console.log(chalk3.gray(" }"));
198
+ process.exit(1);
199
+ }
200
+ const apiKey = config.ai.apiKey || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY;
201
+ if (!apiKey) {
202
+ spinner.fail(chalk3.red("No API key found. Set ai.apiKey in config or environment variable."));
203
+ process.exit(1);
204
+ }
205
+ spinner.text = "Initializing AI translator...";
206
+ const provider = TranslationProviderFactory.create(
207
+ config.ai.provider,
208
+ apiKey,
209
+ config.ai.model
210
+ );
211
+ const translator = new AITranslator(provider);
212
+ const projectRoot = process.cwd();
213
+ const outputDir = path3.resolve(projectRoot, config.outputDir);
214
+ const storage = new FileStorage2(outputDir);
215
+ spinner.succeed("AI translator ready");
216
+ let targetLocales = [];
217
+ const availableLocales = config.locales || [];
218
+ if (options.all) {
219
+ targetLocales = availableLocales.filter((l) => l !== config.defaultLocale);
220
+ } else if (options.locale) {
221
+ targetLocales = [options.locale];
222
+ } else {
223
+ targetLocales = availableLocales.filter((l) => l !== config.defaultLocale).slice(0, 1);
224
+ }
225
+ if (targetLocales.length === 0) {
226
+ spinner.warn(chalk3.yellow("No target locales to translate. Add more locales to your config."));
227
+ return;
228
+ }
229
+ const sourceLocale = config.defaultLocale;
230
+ const sourceData = storage.loadLocale(sourceLocale);
231
+ if (!sourceData) {
232
+ spinner.fail(chalk3.red(`Source locale ${sourceLocale} not found. Run 'lokal scan' first.`));
233
+ process.exit(1);
234
+ }
235
+ for (const targetLocale of targetLocales) {
236
+ const translateSpinner = ora3(`Translating to ${chalk3.cyan(targetLocale)}...`).start();
237
+ const targetLocaleFile = storage.loadLocale(targetLocale);
238
+ const targetData = targetLocaleFile ? targetLocaleFile.data : {};
239
+ const translatedData = await translator.translateMissingKeys(
240
+ sourceData.data,
241
+ targetData,
242
+ sourceLocale,
243
+ targetLocale
244
+ );
245
+ storage.saveLocale(targetLocale, translatedData);
246
+ translateSpinner.succeed(chalk3.green(`Translated to ${chalk3.bold(targetLocale)}`));
247
+ if (options.verbose) {
248
+ const keys = Object.keys(translatedData).slice(0, 5);
249
+ for (const key of keys) {
250
+ const value = translatedData[key];
251
+ if (typeof value === "string") {
252
+ console.log(chalk3.gray(` ${key}: ${value}`));
253
+ }
254
+ }
255
+ }
256
+ }
257
+ console.log(chalk3.bold("\n\u2713 Translation complete!"));
258
+ console.log(chalk3.gray(`
259
+ Run `) + chalk3.cyan("npx lokal scan") + chalk3.gray(" to see updated translations"));
260
+ } catch (error) {
261
+ spinner.fail(chalk3.red(`Translation failed: ${error}`));
262
+ if (options.verbose) {
263
+ console.error(error);
264
+ }
265
+ process.exit(1);
266
+ }
267
+ }
268
+ function registerTranslateCommand(program2) {
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
+ }
271
+
272
+ // packages/cli/src/index.ts
273
+ var program = new Command();
274
+ program.name("lokal").description("AI-powered localization ecosystem for React and React Native").version("1.0.0");
275
+ registerInitCommand(program);
276
+ registerScanCommand(program);
277
+ registerTranslateCommand(program);
278
+ program.option("-v, --verbose", "Verbose output").hook("preAction", (thisCommand) => {
279
+ const opts = thisCommand.opts();
280
+ if (opts.verbose) {
281
+ process.env.LOKAL_VERBOSE = "true";
282
+ }
283
+ });
284
+ program.on("command:*", () => {
285
+ console.error(chalk4.red(`Invalid command: ${program.args.join(" ")}`));
286
+ console.log(chalk4.gray(`See ${chalk4.cyan("--help")} for a list of available commands.`));
287
+ process.exit(1);
288
+ });
289
+ program.parse(process.argv);
290
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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":";;;AAEA,SAAS,eAAe;AACxB,OAAOA,YAAW;;;ACHlB,YAAY,QAAQ;AACpB,YAAY,UAAU;AAEtB,OAAO,WAAW;AAClB,OAAO,SAAS;AAWhB,eAAsB,YAAY,SAAqC;AACnE,QAAM,UAAU,IAAI,uBAAuB,EAAE,MAAM;AAEnD,MAAI;AACA,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,aAAkB,UAAK,aAAa,iBAAiB;AAC3D,UAAM,aAAkB,UAAK,aAAa,SAAS;AAGnD,UAAM,UAAU,QAAQ,UAClB,QAAQ,QAAQ,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC,IAC5C,CAAC,IAAI;AAEX,UAAM,gBAAgB,QAAQ,iBAAiB,QAAQ,CAAC;AAGxD,QAAO,cAAW,UAAU,KAAK,CAAC,QAAQ,OAAO;AAC7C,cAAQ,KAAK,MAAM,OAAO,4DAA4D,CAAC;AACvF;AAAA,IACJ;AAGA,UAAM,gBAAgB;AAAA;AAAA,aAEjB,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA;AAAA,oBAGhB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBzB,IAAG,iBAAc,YAAY,eAAe,OAAO;AACnD,YAAQ,QAAQ,MAAM,MAAM,WAAW,MAAM,KAAK,iBAAiB,CAAC,EAAE,CAAC;AAGvE,QAAI,CAAI,cAAW,UAAU,GAAG;AAC5B,MAAG,aAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,IAChD;AAGA,UAAM,oBAAyB,UAAK,YAAY,GAAG,aAAa,OAAO;AACvE,QAAI,CAAI,cAAW,iBAAiB,GAAG;AACnC,YAAM,cAAc;AAAA,QAChB,OAAO;AAAA,UACH,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,aAAa;AAAA,QACjB;AAAA,MACJ;AACA,MAAG,iBAAc,mBAAmB,KAAK,UAAU,aAAa,MAAM,CAAC,GAAG,OAAO;AACjF,cAAQ,QAAQ,MAAM,MAAM,WAAW,MAAM,KAAK,WAAW,aAAa,OAAO,CAAC,EAAE,CAAC;AAAA,IACzF;AAGA,UAAM,gBAAqB,UAAK,aAAa,YAAY;AACzD,QAAO,cAAW,aAAa,GAAG;AAC9B,YAAM,mBAAsB,gBAAa,eAAe,OAAO;AAC/D,UAAI,CAAC,iBAAiB,SAAS,WAAW,GAAG;AACzC,QAAG,kBAAe,eAAe,oCAAoC;AACrE,gBAAQ,QAAQ,MAAM,MAAM,oBAAoB,CAAC;AAAA,MACrD;AAAA,IACJ;AAEA,YAAQ,IAAI,MAAM,KAAK,0CAAqC,CAAC;AAC7D,YAAQ,IAAI,MAAM,KAAK,eAAe,CAAC;AACvC,YAAQ,IAAI,MAAM,KAAK,wEAAwE,CAAC;AAChG,YAAQ,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,gBAAgB,IAAI,MAAM,KAAK,qBAAqB,CAAC;AACtG,YAAQ,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,qBAAqB,IAAI,MAAM,KAAK,uBAAuB,CAAC;AAAA,EAEjH,SAAS,OAAO;AACZ,YAAQ,KAAK,MAAM,IAAI,yBAAyB,KAAK,EAAE,CAAC;AACxD,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEO,SAAS,oBAAoBC,UAAwB;AACxD,EAAAA,SACK,QAAQ,MAAM,EACd,YAAY,kCAAkC,EAC9C,OAAO,2BAA2B,mCAAmC,IAAI,EACzE,OAAO,iCAAiC,kBAAkB,IAAI,EAC9D,OAAO,eAAe,0BAA0B,KAAK,EACrD,OAAO,WAAW;AAC3B;;;ACpHA,YAAYC,WAAU;AAEtB,OAAOC,YAAW;AAClB,OAAOC,UAAS;AAChB,SAAS,WAAW,cAAc,mBAAyC;AAW3E,eAAsB,YAAY,SAAqC;AACnE,QAAM,UAAUA,KAAI,qCAAqC,EAAE,MAAM;AAEjE,MAAI;AAEA,UAAM,eAAe,IAAI,aAAa;AACtC,QAAI;AAEJ,QAAI,QAAQ,QAAQ;AAChB,eAAS,aAAa,SAAS,QAAQ,MAAM;AAAA,IACjD,OAAO;AACH,eAAS,MAAM,aAAa,KAAK;AAAA,IACrC;AAEA,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,YAAiB,cAAQ,aAAa,OAAO,SAAS;AAC5D,UAAM,YAAY,QAAQ,SACf,cAAQ,aAAa,QAAQ,MAAM,IACnC,cAAQ,aAAa,OAAO,SAAS;AAEhD,YAAQ,OAAO,YAAYD,OAAM,KAAK,SAAS,CAAC;AAGhD,UAAM,SAAS,IAAI,UAAU;AAAA,MACzB,UAAU;AAAA,MACV,cAAc,OAAO;AAAA,MACrB,eAAe,OAAO;AAAA,IAC1B,CAAC;AAGD,UAAM,SAAS,OAAO,cAAc,SAAS;AAE7C,QAAI,OAAO,OAAO,SAAS,KAAK,QAAQ,SAAS;AAC7C,iBAAW,SAAS,OAAO,QAAQ;AAC/B,gBAAQ,KAAKA,OAAM,OAAO,KAAK,CAAC;AAAA,MACpC;AAAA,IACJ;AAEA,YAAQ,QAAQA,OAAM,MAAM,SAASA,OAAM,KAAK,OAAO,QAAQ,MAAM,CAAC,sBAAsB,CAAC;AAE7F,QAAI,OAAO,QAAQ,WAAW,GAAG;AAC7B,cAAQ,KAAKA,OAAM,KAAK,yEAAyE,CAAC;AAClG;AAAA,IACJ;AAGA,UAAM,UAAU,IAAI,YAAY,SAAS;AAGzC,UAAM,gBAAgB,oBAAI,IAA6B;AACvD,eAAW,OAAO,OAAO,SAAS;AAC9B,oBAAc,IAAI,IAAI,KAAK,GAAG;AAAA,IAClC;AAGA,UAAM,gBAAgB,OAAO;AAC7B,UAAM,iBAAiB,QAAQ,WAAW,aAAa;AAGvD,QAAI,eAAoC,CAAC;AACzC,QAAI,gBAAgB;AAChB,qBAAe,eAAe;AAAA,IAClC;AAGA,UAAM,UAAkC,CAAC;AACzC,eAAW,CAAC,KAAK,KAAK,KAAK,eAAe;AAEtC,cAAQ,GAAG,IAAI,aAAa,GAAG,KAAK,MAAM;AAAA,IAC9C;AAGA,UAAM,aAAa,QAAQ,gBAAgB,eAAe,OAAO;AACjE,YAAQ,WAAW,eAAe,UAAU;AAE5C,YAAQ,QAAQA,OAAM,MAAM,WAAWA,OAAM,KAAK,WAAW,aAAa,OAAO,CAAC,EAAE,CAAC;AAGrF,QAAI,QAAQ,SAAS;AACjB,cAAQ,IAAIA,OAAM,KAAK,sBAAsB,CAAC;AAC9C,YAAM,aAAa,MAAM,KAAK,cAAc,KAAK,CAAC,EAAE,MAAM,GAAG,EAAE;AAC/D,iBAAW,OAAO,YAAY;AAC1B,gBAAQ,IAAIA,OAAM,KAAK,YAAO,GAAG,EAAE,CAAC;AAAA,MACxC;AACA,UAAI,cAAc,OAAO,IAAI;AACzB,gBAAQ,IAAIA,OAAM,KAAK,aAAa,cAAc,OAAO,EAAE,OAAO,CAAC;AAAA,MACvE;AAAA,IACJ;AAGA,UAAM,UAAU,QAAQ,oBAAoB;AAC5C,UAAM,eAAe,QAAQ,OAAO,OAAK,MAAM,aAAa;AAE5D,QAAI,aAAa,SAAS,GAAG;AACzB,cAAQ,IAAIA,OAAM,KAAK;AAAA,0BAA6B,aAAa,KAAK,IAAI,CAAC,EAAE,CAAC;AAC9E,cAAQ,IAAIA,OAAM,KAAK,MAAM,IAAIA,OAAM,KAAK,qBAAqB,IAAIA,OAAM,KAAK,+BAA+B,CAAC;AAAA,IACpH;AAAA,EAEJ,SAAS,OAAO;AACZ,YAAQ,KAAKA,OAAM,IAAI,gBAAgB,KAAK,EAAE,CAAC;AAC/C,QAAI,QAAQ,SAAS;AACjB,cAAQ,MAAM,KAAK;AAAA,IACvB;AACA,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEO,SAAS,oBAAoBE,UAAwB;AACxD,EAAAA,SACK,QAAQ,MAAM,EACd,YAAY,2CAA2C,EACvD,OAAO,uBAAuB,qBAAqB,EACnD,OAAO,uBAAuB,mCAAmC,EACjE,OAAO,iBAAiB,kBAAkB,KAAK,EAC/C,OAAO,WAAW;AAC3B;;;AClIA,YAAYC,WAAU;AAEtB,OAAOC,YAAW;AAClB,OAAOC,UAAS;AAChB;AAAA,EACI,gBAAAC;AAAA,EACA,eAAAC;AAAA,EACA;AAAA,EACA;AAAA,OAEG;AAYP,eAAsB,iBAAiB,SAA0C;AAC7E,QAAM,UAAUF,KAAI,0BAA0B,EAAE,MAAM;AAEtD,MAAI;AAEA,UAAM,eAAe,IAAIC,cAAa;AACtC,QAAI;AAEJ,QAAI,QAAQ,QAAQ;AAChB,eAAS,aAAa,SAAS,QAAQ,MAAM;AAAA,IACjD,OAAO;AACH,eAAS,MAAM,aAAa,KAAK;AAAA,IACrC;AAGA,QAAI,CAAC,OAAO,IAAI;AACZ,cAAQ,KAAKF,OAAM,IAAI,qEAAqE,CAAC;AAC7F,cAAQ,IAAIA,OAAM,KAAK,0BAA0B,CAAC;AAClD,cAAQ,IAAIA,OAAM,KAAK,SAAS,CAAC;AACjC,cAAQ,IAAIA,OAAM,KAAK,yBAAyB,CAAC;AACjD,cAAQ,IAAIA,OAAM,KAAK,wCAAwC,CAAC;AAChE,cAAQ,IAAIA,OAAM,KAAK,KAAK,CAAC;AAC7B,cAAQ,KAAK,CAAC;AAAA,IAClB;AAEA,UAAM,SAAS,OAAO,GAAG,UAAU,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;AAC7E,QAAI,CAAC,QAAQ;AACT,cAAQ,KAAKA,OAAM,IAAI,oEAAoE,CAAC;AAC5F,cAAQ,KAAK,CAAC;AAAA,IAClB;AAEA,YAAQ,OAAO;AAGf,UAAM,WAAW,2BAA2B;AAAA,MACxC,OAAO,GAAG;AAAA,MACV;AAAA,MACA,OAAO,GAAG;AAAA,IACd;AAGA,UAAM,aAAa,IAAI,aAAa,QAAQ;AAE5C,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,YAAiB,cAAQ,aAAa,OAAO,SAAS;AAC5D,UAAM,UAAU,IAAIG,aAAY,SAAS;AAEzC,YAAQ,QAAQ,qBAAqB;AAGrC,QAAI,gBAA0B,CAAC;AAC/B,UAAM,mBAAmB,OAAO,WAAW,CAAC;AAE5C,QAAI,QAAQ,KAAK;AACb,sBAAgB,iBAAiB,OAAO,OAAK,MAAM,OAAO,aAAa;AAAA,IAC3E,WAAW,QAAQ,QAAQ;AACvB,sBAAgB,CAAC,QAAQ,MAAM;AAAA,IACnC,OAAO;AAEH,sBAAgB,iBAAiB,OAAO,OAAK,MAAM,OAAO,aAAa,EAAE,MAAM,GAAG,CAAC;AAAA,IACvF;AAEA,QAAI,cAAc,WAAW,GAAG;AAC5B,cAAQ,KAAKH,OAAM,OAAO,kEAAkE,CAAC;AAC7F;AAAA,IACJ;AAGA,UAAM,eAAe,OAAO;AAC5B,UAAM,aAAa,QAAQ,WAAW,YAAY;AAElD,QAAI,CAAC,YAAY;AACb,cAAQ,KAAKA,OAAM,IAAI,iBAAiB,YAAY,qCAAqC,CAAC;AAC1F,cAAQ,KAAK,CAAC;AAAA,IAClB;AAGA,eAAW,gBAAgB,eAAe;AACtC,YAAM,mBAAmBC,KAAI,kBAAkBD,OAAM,KAAK,YAAY,CAAC,KAAK,EAAE,MAAM;AAGpF,YAAM,mBAAmB,QAAQ,WAAW,YAAY;AACxD,YAAM,aAAyB,mBAAmB,iBAAiB,OAAO,CAAC;AAG3E,YAAM,iBAAiB,MAAM,WAAW;AAAA,QACpC,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,MACJ;AAGA,cAAQ,WAAW,cAAc,cAAc;AAE/C,uBAAiB,QAAQA,OAAM,MAAM,iBAAiBA,OAAM,KAAK,YAAY,CAAC,EAAE,CAAC;AAEjF,UAAI,QAAQ,SAAS;AAEjB,cAAM,OAAO,OAAO,KAAK,cAAc,EAAE,MAAM,GAAG,CAAC;AACnD,mBAAW,OAAO,MAAM;AACpB,gBAAM,QAAQ,eAAe,GAAG;AAChC,cAAI,OAAO,UAAU,UAAU;AAC3B,oBAAQ,IAAIA,OAAM,KAAK,KAAK,GAAG,KAAK,KAAK,EAAE,CAAC;AAAA,UAChD;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,YAAQ,IAAIA,OAAM,KAAK,gCAA2B,CAAC;AACnD,YAAQ,IAAIA,OAAM,KAAK;AAAA,KAAQ,IAAIA,OAAM,KAAK,gBAAgB,IAAIA,OAAM,KAAK,8BAA8B,CAAC;AAAA,EAEhH,SAAS,OAAO;AACZ,YAAQ,KAAKA,OAAM,IAAI,uBAAuB,KAAK,EAAE,CAAC;AACtD,QAAI,QAAQ,SAAS;AACjB,cAAQ,MAAM,KAAK;AAAA,IACvB;AACA,YAAQ,KAAK,CAAC;AAAA,EAClB;AACJ;AAEO,SAAS,yBAAyBI,UAAwB;AAC7D,EAAAA,SACK,QAAQ,WAAW,EACnB,YAAY,oCAAoC,EAChD,OAAO,uBAAuB,qBAAqB,EACnD,OAAO,yBAAyB,8BAA8B,EAC9D,OAAO,aAAa,yBAAyB,KAAK,EAClD,OAAO,iBAAiB,kBAAkB,KAAK,EAC/C,OAAO,gBAAgB;AAChC;;;AHhJA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACK,KAAK,OAAO,EACZ,YAAY,8DAA8D,EAC1E,QAAQ,OAAO;AAGpB,oBAAoB,OAAO;AAC3B,oBAAoB,OAAO;AAC3B,yBAAyB,OAAO;AAGhC,QACK,OAAO,iBAAiB,gBAAgB,EACxC,KAAK,aAAa,CAAC,gBAAgB;AAChC,QAAM,OAAO,YAAY,KAAK;AAC9B,MAAI,KAAK,SAAS;AACd,YAAQ,IAAI,gBAAgB;AAAA,EAChC;AACJ,CAAC;AAGL,QAAQ,GAAG,aAAa,MAAM;AAC1B,UAAQ,MAAMC,OAAM,IAAI,oBAAoB,QAAQ,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC;AACrE,UAAQ,IAAIA,OAAM,KAAK,OAAOA,OAAM,KAAK,QAAQ,CAAC,oCAAoC,CAAC;AACvF,UAAQ,KAAK,CAAC;AAClB,CAAC;AAGD,QAAQ,MAAM,QAAQ,IAAI;","names":["chalk","program","path","chalk","ora","program","path","chalk","ora","ConfigLoader","FileStorage","program","chalk"]}
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@devcoda/lokal-cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLI tool for LOKAL - Automates string extraction and AI translations",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
7
7
  "lokal": "./bin/lokal"
8
8
  },
9
+ "files": [
10
+ "dist",
11
+ "bin",
12
+ "src"
13
+ ],
9
14
  "scripts": {
10
15
  "dev": "tsup --watch",
11
16
  "clean": "rm -rf dist",
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "module": "ESNext",
7
- "target": "ES2020"
8
- },
9
- "include": [
10
- "src/**/*"
11
- ],
12
- "exclude": [
13
- "node_modules",
14
- "dist",
15
- "**/*.test.ts"
16
- ]
17
- }