@vocoder/cli 0.1.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/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # @vocoder/cli
2
+
3
+ CLI tool for the Vocoder translation workflow. Extract translatable strings from your React code and get them translated automatically.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @vocoder/cli
9
+ # or
10
+ pnpm add -D @vocoder/cli
11
+ # or
12
+ yarn add -D @vocoder/cli
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ 1. **Set up environment variables** (create `.env` in your project root):
18
+
19
+ ```bash
20
+ VOCODER_API_KEY=your-api-key-here
21
+ ```
22
+
23
+ 2. **Add to your build script**:
24
+
25
+ ```json
26
+ {
27
+ "scripts": {
28
+ "prebuild": "vocoder sync",
29
+ "build": "next build"
30
+ }
31
+ }
32
+ ```
33
+
34
+ 3. **Use `<T>` components in your code**:
35
+
36
+ ```tsx
37
+ import { T } from '@vocoder/react';
38
+
39
+ function MyComponent() {
40
+ return (
41
+ <div>
42
+ <T>Welcome to our app!</T>
43
+ <T name={userName}>Hello, {name}!</T>
44
+ </div>
45
+ );
46
+ }
47
+ ```
48
+
49
+ 4. **Run the CLI**:
50
+
51
+ ```bash
52
+ npx vocoder sync
53
+ ```
54
+
55
+ This will:
56
+ - Extract all `<T>` components from your code
57
+ - Submit them to Vocoder for translation
58
+ - Download translations to `.vocoder/locales/*.json`
59
+ - Only translate NEW strings (incremental updates are fast!)
60
+
61
+ ## Configuration
62
+
63
+ The CLI uses environment variables for configuration:
64
+
65
+ ### Required
66
+
67
+ - **`VOCODER_API_KEY`**: Your Vocoder API key (get from https://vocoder.dev)
68
+
69
+ ### Optional (Development Only)
70
+
71
+ - **`VOCODER_API_URL`**: Override API endpoint (defaults to `https://api.vocoder.dev`)
72
+
73
+ ### Defaults (Not Configurable)
74
+
75
+ - **Target locales**: `es`, `fr`, `de`
76
+ - **Extraction pattern**: `src/**/*.{tsx,jsx,ts,js}`
77
+ - **Output directory**: `.vocoder/locales`
78
+ - **Target branches**: `main`, `master`, `production`, `staging`
79
+
80
+ To customize these defaults, configure them in your Vocoder dashboard.
81
+
82
+ ## Commands
83
+
84
+ ### `vocoder sync`
85
+
86
+ Extract and translate strings.
87
+
88
+ ```bash
89
+ npx vocoder sync [options]
90
+ ```
91
+
92
+ **Options:**
93
+
94
+ - `--branch <name>` - Specify branch name (auto-detected from git)
95
+ - `--force` - Translate even if not on a target branch
96
+ - `--dry-run` - Show what would be translated without making API calls
97
+ - `--verbose` - Show detailed output
98
+
99
+ **Examples:**
100
+
101
+ ```bash
102
+ # Normal usage (auto-detects branch from git)
103
+ npx vocoder sync
104
+
105
+ # Specify branch manually
106
+ npx vocoder sync --branch feature/new-ui
107
+
108
+ # See what would be translated without making API calls
109
+ npx vocoder sync --dry-run
110
+
111
+ # Force translation even if not on a target branch
112
+ npx vocoder sync --force
113
+
114
+ # Verbose output for debugging
115
+ npx vocoder sync --verbose
116
+ ```
117
+
118
+ ## Workflow
119
+
120
+ ### First Run (100 strings)
121
+ ```bash
122
+ $ npx vocoder sync
123
+ ✓ Detected branch: main
124
+ ✓ Loaded config for project: abc123
125
+ ✓ Extracted 100 strings from src/**/*.{tsx,jsx,ts,js}
126
+ ✓ Submitted to API - Batch ID: batch-xyz
127
+ Found 100 new strings to translate
128
+ ⏳ Translating to 3 locales (es, fr, de)
129
+ Estimated time: ~30 seconds
130
+ ✓ Translations complete!
131
+ ✓ Wrote 3 locale files
132
+
133
+ ✅ Translation complete! (32.4s)
134
+ ```
135
+
136
+ ### Second Run (Same strings, 0 new)
137
+ ```bash
138
+ $ npx vocoder sync
139
+ ✓ Detected branch: main
140
+ ✓ Loaded config for project: abc123
141
+ ✓ Extracted 100 strings from src/**/*.{tsx,jsx,ts,js}
142
+ ✓ Submitted to API - Batch ID: batch-abc
143
+ Found 0 new strings to translate
144
+
145
+ ✅ No new strings - using existing translations
146
+ ✓ Wrote 3 locale files
147
+
148
+ ✅ Translation complete! (0.8s)
149
+ ```
150
+
151
+ ### Incremental Run (1 new string)
152
+ ```bash
153
+ $ npx vocoder sync
154
+ ✓ Detected branch: main
155
+ ✓ Loaded config for project: abc123
156
+ ✓ Extracted 101 strings from src/**/*.{tsx,jsx,ts,js}
157
+ ✓ Submitted to API - Batch ID: batch-def
158
+ Found 1 new strings to translate
159
+ ⏳ Translating to 3 locales (es, fr, de)
160
+ Estimated time: ~1 seconds
161
+ ✓ Translations complete!
162
+ ✓ Wrote 3 locale files
163
+
164
+ ✅ Translation complete! (1.2s)
165
+ ```
166
+
167
+ ## Branch-Scoped Translations
168
+
169
+ Translations are isolated per git branch:
170
+
171
+ - **Main branch** translations are shared across the team
172
+ - **Feature branches** get their own translations
173
+ - Feature branches fall back to main branch translations
174
+ - Merge to main to promote feature translations
175
+
176
+ This allows you to:
177
+ - Test translations in feature branches
178
+ - Preview translations before merging
179
+ - Avoid conflicts between features
180
+
181
+ ## Performance
182
+
183
+ The CLI is optimized for incremental updates:
184
+
185
+ | Scenario | Time | Cost |
186
+ |----------|------|------|
187
+ | 100 new strings | ~30s | 100 strings × 3 locales |
188
+ | 0 new strings | <1s | No API calls |
189
+ | 1 new string | ~1s | 1 string × 3 locales |
190
+
191
+ **Speedup: 30x faster** for incremental updates!
192
+
193
+ ## Output
194
+
195
+ Translations are written to `.vocoder/locales/`:
196
+
197
+ ```
198
+ .vocoder/
199
+ └── locales/
200
+ ├── es.json
201
+ ├── fr.json
202
+ └── de.json
203
+ ```
204
+
205
+ Each file contains a flat key-value mapping:
206
+
207
+ ```json
208
+ {
209
+ "Welcome to our app!": "¡Bienvenido a nuestra aplicación!",
210
+ "Hello, {name}!": "¡Hola, {name}!",
211
+ "You have {count} messages": "Tienes {count} mensajes"
212
+ }
213
+ ```
214
+
215
+ **Add to `.gitignore`:**
216
+
217
+ ```
218
+ .vocoder/
219
+ ```
220
+
221
+ Translations are generated at build time, not checked into git.
222
+
223
+ ## Integration with React
224
+
225
+ Use the generated locale files with `@vocoder/react`:
226
+
227
+ ```tsx
228
+ import { VocoderProvider } from '@vocoder/react';
229
+ import en from './.vocoder/locales/en.json';
230
+ import es from './.vocoder/locales/es.json';
231
+ import fr from './.vocoder/locales/fr.json';
232
+
233
+ export default function App({ children }) {
234
+ return (
235
+ <VocoderProvider
236
+ translations={{ en, es, fr }}
237
+ defaultLocale="en"
238
+ >
239
+ {children}
240
+ </VocoderProvider>
241
+ );
242
+ }
243
+ ```
244
+
245
+ ## Troubleshooting
246
+
247
+ ### "VOCODER_API_KEY is required"
248
+
249
+ Create a `.env` file in your project root:
250
+
251
+ ```bash
252
+ VOCODER_API_KEY=your-api-key
253
+ ```
254
+
255
+ ### "No translatable strings found"
256
+
257
+ Make sure you're using `<T>` components from `@vocoder/react`:
258
+
259
+ ```tsx
260
+ import { T } from '@vocoder/react';
261
+
262
+ // ✅ Good
263
+ <T>Welcome!</T>
264
+
265
+ // ❌ Bad (not detected)
266
+ <span>Welcome!</span>
267
+ ```
268
+
269
+ ### "Not a git repository"
270
+
271
+ The CLI auto-detects the branch from git. Either:
272
+ - Initialize git: `git init`
273
+ - Specify branch manually: `vocoder sync --branch main`
274
+
275
+ ### "Skipping translations (not a target branch)"
276
+
277
+ The CLI only runs on target branches (`main`, `master`, `production`, `staging`) by default. Either:
278
+ - Merge to a target branch
279
+ - Use `--force` flag: `vocoder sync --force`
280
+
281
+ ## Development
282
+
283
+ ```bash
284
+ # Install dependencies
285
+ pnpm install
286
+
287
+ # Build
288
+ pnpm build
289
+
290
+ # Run tests
291
+ pnpm test
292
+
293
+ # Run unit tests only (fast)
294
+ pnpm test:unit
295
+
296
+ # Run integration tests (requires API)
297
+ pnpm test:integration
298
+
299
+ # Watch mode
300
+ pnpm test:watch
301
+ ```
302
+
303
+ ## License
304
+
305
+ MIT
package/dist/bin.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/bin.mjs ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ sync
4
+ } from "./chunk-N45Q4R6O.mjs";
5
+
6
+ // src/bin.ts
7
+ import { Command } from "commander";
8
+ var program = new Command();
9
+ program.name("vocoder").description("Vocoder CLI - Sync translations for your application").version("0.1.0");
10
+ program.command("sync").description("Extract strings and sync translations").option("--branch <name>", "Override branch detection").option("--force", "Sync even if not a target branch").option("--dry-run", "Show what would be synced without doing it").option("--verbose", "Show detailed progress").option("--max-age <seconds>", "Use cache if younger than this (seconds)", parseInt).action(sync);
11
+ program.parse(process.argv);
12
+ //# sourceMappingURL=bin.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { sync } from './commands/sync.js';\n\nconst program = new Command();\n\nprogram\n .name('vocoder')\n .description('Vocoder CLI - Sync translations for your application')\n .version('0.1.0');\n\nprogram\n .command('sync')\n .description('Extract strings and sync translations')\n .option('--branch <name>', 'Override branch detection')\n .option('--force', 'Sync even if not a target branch')\n .option('--dry-run', 'Show what would be synced without doing it')\n .option('--verbose', 'Show detailed progress')\n .option('--max-age <seconds>', 'Use cache if younger than this (seconds)', parseInt)\n .action(sync);\n\nprogram.parse(process.argv);\n"],"mappings":";;;;;;AAEA,SAAS,eAAe;AAGxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,SAAS,EACd,YAAY,sDAAsD,EAClE,QAAQ,OAAO;AAElB,QACG,QAAQ,MAAM,EACd,YAAY,uCAAuC,EACnD,OAAO,mBAAmB,2BAA2B,EACrD,OAAO,WAAW,kCAAkC,EACpD,OAAO,aAAa,4CAA4C,EAChE,OAAO,aAAa,wBAAwB,EAC5C,OAAO,uBAAuB,4CAA4C,QAAQ,EAClF,OAAO,IAAI;AAEd,QAAQ,MAAM,QAAQ,IAAI;","names":[]}
@@ -0,0 +1,635 @@
1
+ // src/utils/branch.ts
2
+ import { execSync } from "child_process";
3
+ function detectBranch(override) {
4
+ if (override) {
5
+ return override;
6
+ }
7
+ const envBranch = process.env.GITHUB_REF_NAME || // GitHub Actions
8
+ process.env.VERCEL_GIT_COMMIT_REF || // Vercel
9
+ process.env.BRANCH || // Netlify, generic
10
+ process.env.CI_COMMIT_REF_NAME || // GitLab
11
+ process.env.BITBUCKET_BRANCH || // Bitbucket
12
+ process.env.CIRCLE_BRANCH;
13
+ if (envBranch) {
14
+ return envBranch;
15
+ }
16
+ try {
17
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
18
+ encoding: "utf-8",
19
+ stdio: ["pipe", "pipe", "ignore"]
20
+ }).trim();
21
+ return branch;
22
+ } catch (error) {
23
+ throw new Error(
24
+ "Failed to detect git branch. Make sure you are in a git repository or set the --branch flag."
25
+ );
26
+ }
27
+ }
28
+ function isTargetBranch(currentBranch, targetBranches) {
29
+ return targetBranches.includes(currentBranch);
30
+ }
31
+
32
+ // src/utils/config.ts
33
+ import { config as loadEnv } from "dotenv";
34
+ loadEnv();
35
+ function getLocalConfig() {
36
+ const apiKey = process.env.VOCODER_API_KEY;
37
+ if (!apiKey) {
38
+ throw new Error(
39
+ 'VOCODER_API_KEY is required. Set it in your .env file or environment:\n export VOCODER_API_KEY="your-api-key"\n\nGet your API key from: https://vocoder.app/settings/api-keys'
40
+ );
41
+ }
42
+ return {
43
+ apiKey,
44
+ apiUrl: process.env.VOCODER_API_URL || "https://vocoder.app"
45
+ };
46
+ }
47
+ function validateLocalConfig(config) {
48
+ if (!config.apiKey || config.apiKey.length === 0) {
49
+ throw new Error("Invalid API key");
50
+ }
51
+ if (!config.apiKey.startsWith("vc_")) {
52
+ throw new Error("Invalid API key format. Expected format: vc_...");
53
+ }
54
+ if (!config.apiUrl || !config.apiUrl.startsWith("http")) {
55
+ throw new Error("Invalid API URL");
56
+ }
57
+ }
58
+
59
+ // src/commands/sync.ts
60
+ import { mkdirSync, writeFileSync } from "fs";
61
+ import { join, dirname } from "path";
62
+ import chalk from "chalk";
63
+ import ora from "ora";
64
+
65
+ // src/utils/api.ts
66
+ var VocoderAPI = class {
67
+ constructor(config) {
68
+ this.apiUrl = config.apiUrl;
69
+ this.apiKey = config.apiKey;
70
+ }
71
+ /**
72
+ * Fetch project configuration from API
73
+ * Project is determined from the API key
74
+ */
75
+ async getProjectConfig() {
76
+ const response = await fetch(
77
+ `${this.apiUrl}/api/cli/config`,
78
+ {
79
+ headers: {
80
+ Authorization: `Bearer ${this.apiKey}`
81
+ }
82
+ }
83
+ );
84
+ if (!response.ok) {
85
+ const error = await response.text();
86
+ throw new Error(`Failed to fetch project config: ${error}`);
87
+ }
88
+ const data = await response.json();
89
+ return {
90
+ sourceLocale: data.sourceLocale,
91
+ targetLocales: data.targetLocales,
92
+ targetBranches: data.targetBranches
93
+ };
94
+ }
95
+ /**
96
+ * Submit strings for translation
97
+ * Project is determined from the API key
98
+ */
99
+ async submitTranslation(branch, strings, targetLocales) {
100
+ const crypto = await import("crypto");
101
+ const sortedStrings = [...strings].sort();
102
+ const stringsHash = crypto.createHash("sha256").update(JSON.stringify(sortedStrings)).digest("hex");
103
+ const response = await fetch(`${this.apiUrl}/api/cli/sync`, {
104
+ method: "POST",
105
+ headers: {
106
+ "Content-Type": "application/json",
107
+ Authorization: `Bearer ${this.apiKey}`
108
+ },
109
+ body: JSON.stringify({
110
+ branch,
111
+ strings,
112
+ targetLocales,
113
+ stringsHash
114
+ })
115
+ });
116
+ if (!response.ok) {
117
+ const error = await response.text();
118
+ throw new Error(`Translation submission failed: ${error}`);
119
+ }
120
+ return response.json();
121
+ }
122
+ /**
123
+ * Check translation status
124
+ */
125
+ async getTranslationStatus(batchId) {
126
+ const response = await fetch(
127
+ `${this.apiUrl}/api/cli/sync/status/${batchId}`,
128
+ {
129
+ headers: {
130
+ Authorization: `Bearer ${this.apiKey}`
131
+ }
132
+ }
133
+ );
134
+ if (!response.ok) {
135
+ const error = await response.text();
136
+ throw new Error(`Failed to check translation status: ${error}`);
137
+ }
138
+ return response.json();
139
+ }
140
+ /**
141
+ * Wait for translation to complete with polling
142
+ */
143
+ async waitForCompletion(batchId, timeout = 6e4, onProgress) {
144
+ const startTime = Date.now();
145
+ const pollInterval = 1e3;
146
+ while (Date.now() - startTime < timeout) {
147
+ const status = await this.getTranslationStatus(batchId);
148
+ if (onProgress) {
149
+ onProgress(status.progress);
150
+ }
151
+ if (status.status === "COMPLETED") {
152
+ if (!status.translations) {
153
+ throw new Error("Translation completed but no translations returned");
154
+ }
155
+ return {
156
+ translations: status.translations,
157
+ localeMetadata: status.localeMetadata
158
+ };
159
+ }
160
+ if (status.status === "FAILED") {
161
+ throw new Error(
162
+ `Translation failed: ${status.errorMessage || "Unknown error"}`
163
+ );
164
+ }
165
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
166
+ }
167
+ throw new Error(`Translation timeout after ${timeout}ms`);
168
+ }
169
+ };
170
+
171
+ // src/utils/extract.ts
172
+ import { readFileSync } from "fs";
173
+ import { parse } from "@babel/parser";
174
+ import babelTraverse from "@babel/traverse";
175
+ import { glob } from "glob";
176
+ var traverse = babelTraverse.default || babelTraverse;
177
+ var StringExtractor = class {
178
+ /**
179
+ * Extract strings from all files matching the pattern
180
+ */
181
+ async extractFromProject(pattern, projectRoot = process.cwd()) {
182
+ const files = await glob(pattern, {
183
+ cwd: projectRoot,
184
+ absolute: true,
185
+ ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"]
186
+ });
187
+ const allStrings = [];
188
+ for (const file of files) {
189
+ try {
190
+ const strings = await this.extractFromFile(file);
191
+ allStrings.push(...strings);
192
+ } catch (error) {
193
+ console.warn(`Warning: Failed to extract from ${file}:`, error);
194
+ }
195
+ }
196
+ const unique = this.deduplicateStrings(allStrings);
197
+ return unique;
198
+ }
199
+ /**
200
+ * Extract strings from a single file
201
+ */
202
+ async extractFromFile(filePath) {
203
+ const code = readFileSync(filePath, "utf-8");
204
+ const strings = [];
205
+ try {
206
+ const ast = parse(code, {
207
+ sourceType: "module",
208
+ plugins: ["jsx", "typescript"]
209
+ });
210
+ const vocoderImports = /* @__PURE__ */ new Map();
211
+ const tFunctionNames = /* @__PURE__ */ new Set();
212
+ traverse(ast, {
213
+ // Track imports of <T> component and t function
214
+ ImportDeclaration: (path) => {
215
+ const source = path.node.source.value;
216
+ if (source === "@vocoder/react") {
217
+ path.node.specifiers.forEach((spec) => {
218
+ if (spec.type === "ImportSpecifier") {
219
+ const imported = spec.imported.type === "Identifier" ? spec.imported.name : null;
220
+ const local = spec.local.name;
221
+ if (imported === "T") {
222
+ vocoderImports.set(local, "T");
223
+ }
224
+ if (imported === "t") {
225
+ tFunctionNames.add(local);
226
+ }
227
+ if (imported === "useVocoder") {
228
+ }
229
+ }
230
+ });
231
+ }
232
+ },
233
+ // Track destructured 't' from useVocoder hook
234
+ VariableDeclarator: (path) => {
235
+ const init = path.node.init;
236
+ if (init && init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "useVocoder" && path.node.id.type === "ObjectPattern") {
237
+ path.node.id.properties.forEach((prop) => {
238
+ if (prop.type === "ObjectProperty" && prop.key.type === "Identifier" && prop.key.name === "t") {
239
+ const localName = prop.value.type === "Identifier" ? prop.value.name : "t";
240
+ tFunctionNames.add(localName);
241
+ }
242
+ });
243
+ }
244
+ },
245
+ // Extract from t() function calls
246
+ CallExpression: (path) => {
247
+ const callee = path.node.callee;
248
+ const isTFunction = callee.type === "Identifier" && tFunctionNames.has(callee.name);
249
+ if (!isTFunction) return;
250
+ const firstArg = path.node.arguments[0];
251
+ if (!firstArg) return;
252
+ let text = null;
253
+ if (firstArg.type === "StringLiteral") {
254
+ text = firstArg.value;
255
+ } else if (firstArg.type === "TemplateLiteral") {
256
+ text = this.extractTemplateText(firstArg);
257
+ }
258
+ if (!text || text.trim().length === 0) return;
259
+ const secondArg = path.node.arguments[1];
260
+ let context;
261
+ let formality;
262
+ if (secondArg && secondArg.type === "ObjectExpression") {
263
+ secondArg.properties.forEach((prop) => {
264
+ if (prop.type === "ObjectProperty" && prop.key.type === "Identifier") {
265
+ if (prop.key.name === "context" && prop.value.type === "StringLiteral") {
266
+ context = prop.value.value;
267
+ }
268
+ if (prop.key.name === "formality" && prop.value.type === "StringLiteral") {
269
+ formality = prop.value.value;
270
+ }
271
+ }
272
+ });
273
+ }
274
+ strings.push({
275
+ text: text.trim(),
276
+ file: filePath,
277
+ line: path.node.loc?.start.line || 0,
278
+ context,
279
+ formality
280
+ });
281
+ },
282
+ // Extract from JSX elements
283
+ JSXElement: (path) => {
284
+ const opening = path.node.openingElement;
285
+ const tagName = opening.name.type === "JSXIdentifier" ? opening.name.name : null;
286
+ if (!tagName) return;
287
+ const isTranslationComponent = vocoderImports.has(tagName);
288
+ if (!isTranslationComponent) return;
289
+ const text = this.extractTextContent(path.node.children);
290
+ if (!text || text.trim().length === 0) return;
291
+ const context = this.getStringAttribute(opening.attributes, "context");
292
+ const formality = this.getStringAttribute(
293
+ opening.attributes,
294
+ "formality"
295
+ );
296
+ strings.push({
297
+ text: text.trim(),
298
+ file: filePath,
299
+ line: path.node.loc?.start.line || 0,
300
+ context,
301
+ formality
302
+ });
303
+ }
304
+ });
305
+ } catch (error) {
306
+ throw new Error(
307
+ `Failed to parse ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
308
+ );
309
+ }
310
+ return strings;
311
+ }
312
+ /**
313
+ * Extract text from template literal
314
+ * Converts template literals like `Hello ${name}` to `Hello {name}`
315
+ */
316
+ extractTemplateText(node) {
317
+ let text = "";
318
+ for (let i = 0; i < node.quasis.length; i++) {
319
+ const quasi = node.quasis[i];
320
+ text += quasi.value.raw;
321
+ if (i < node.expressions.length) {
322
+ const expr = node.expressions[i];
323
+ if (expr.type === "Identifier") {
324
+ text += `{${expr.name}}`;
325
+ } else {
326
+ text += "{value}";
327
+ }
328
+ }
329
+ }
330
+ return text;
331
+ }
332
+ /**
333
+ * Extract text content from JSX children
334
+ */
335
+ extractTextContent(children) {
336
+ let text = "";
337
+ for (const child of children) {
338
+ if (child.type === "JSXText") {
339
+ text += child.value;
340
+ } else if (child.type === "JSXExpressionContainer") {
341
+ const expr = child.expression;
342
+ if (expr.type === "Identifier") {
343
+ text += `{${expr.name}}`;
344
+ } else if (expr.type === "StringLiteral") {
345
+ text += expr.value;
346
+ } else if (expr.type === "TemplateLiteral") {
347
+ text += this.extractTemplateText(expr);
348
+ }
349
+ }
350
+ }
351
+ return text;
352
+ }
353
+ /**
354
+ * Get string value from JSX attribute
355
+ */
356
+ getStringAttribute(attributes, name) {
357
+ const attr = attributes.find(
358
+ (a) => a.type === "JSXAttribute" && a.name.name === name
359
+ );
360
+ if (!attr || !attr.value) return void 0;
361
+ if (attr.value.type === "StringLiteral") {
362
+ return attr.value.value;
363
+ }
364
+ return void 0;
365
+ }
366
+ /**
367
+ * Deduplicate strings (keep first occurrence)
368
+ */
369
+ deduplicateStrings(strings) {
370
+ const seen = /* @__PURE__ */ new Set();
371
+ const unique = [];
372
+ for (const str of strings) {
373
+ const key = `${str.text}|${str.context || ""}|${str.formality || ""}`;
374
+ if (!seen.has(key)) {
375
+ seen.add(key);
376
+ unique.push(str);
377
+ }
378
+ }
379
+ return unique;
380
+ }
381
+ };
382
+
383
+ // src/commands/sync.ts
384
+ function generateIndexFile(locales, translations, localeMetadata) {
385
+ const toIdentifier = (locale) => locale.replace(/-/g, "_");
386
+ const imports = locales.map(
387
+ (locale) => `import ${toIdentifier(locale)} from './${locale}.json';`
388
+ ).join("\n");
389
+ const translationsObj = locales.map(
390
+ (locale) => ` '${locale}': ${toIdentifier(locale)},`
391
+ ).join("\n");
392
+ const localesObjEntries = locales.map((locale) => {
393
+ const metadata = localeMetadata?.[locale];
394
+ if (metadata) {
395
+ const escapedNativeName = metadata.nativeName.replace(/'/g, "\\'");
396
+ const dirProp = metadata.dir ? `, dir: '${metadata.dir}' as const` : "";
397
+ return ` '${locale}': { nativeName: '${escapedNativeName}'${dirProp} }`;
398
+ } else {
399
+ return ` '${locale}': { nativeName: '${locale}' }`;
400
+ }
401
+ });
402
+ const localesObjString = localesObjEntries.join(",\n");
403
+ return `// Auto-generated by Vocoder CLI
404
+ // This file imports all locale JSON files and exports them as a single object
405
+ // Usage: import { translations, locales } from './.vocoder/locales';
406
+
407
+ ${imports}
408
+
409
+ export const translations = {
410
+ ${translationsObj}
411
+ };
412
+
413
+ /**
414
+ * Flat locale metadata map (O(N))
415
+ * Structure: locales[localeCode] = { nativeName, dir? }
416
+ * - nativeName: Name in the locale's own language (e.g., "Espa\xF1ol", "\u7B80\u4F53\u4E2D\u6587")
417
+ * - dir: Optional 'rtl' for right-to-left locales
418
+ *
419
+ * Translated names are generated at runtime using Intl.DisplayNames:
420
+ * Example: new Intl.DisplayNames(['es'], { type: 'language' }).of('en') \u2192 "ingl\xE9s"
421
+ * Display format: \`\${getDisplayName(code)} (\${locales[code].nativeName})\` \u2192 "ingl\xE9s (English)"
422
+ */
423
+ export const locales = {
424
+ ${localesObjString}
425
+ };
426
+
427
+ export type SupportedLocale = ${locales.map((l) => `'${l}'`).join(" | ")};
428
+ `;
429
+ }
430
+ async function sync(options = {}) {
431
+ const startTime = Date.now();
432
+ const projectRoot = process.cwd();
433
+ try {
434
+ const spinner = ora("Detecting branch...").start();
435
+ const branch = detectBranch(options.branch);
436
+ spinner.succeed(`Detected branch: ${chalk.cyan(branch)}`);
437
+ spinner.start("Loading project configuration...");
438
+ const localConfig = getLocalConfig();
439
+ validateLocalConfig(localConfig);
440
+ const api = new VocoderAPI(localConfig);
441
+ const apiConfig = await api.getProjectConfig();
442
+ const config = {
443
+ ...localConfig,
444
+ ...apiConfig,
445
+ extractionPattern: process.env.VOCODER_EXTRACTION_PATTERN || "src/**/*.{tsx,jsx,ts,js}",
446
+ outputDir: ".vocoder/locales",
447
+ timeout: 6e4
448
+ };
449
+ spinner.succeed("Project configuration loaded");
450
+ if (!options.force && !isTargetBranch(branch, config.targetBranches)) {
451
+ console.log(
452
+ chalk.yellow(
453
+ `\u2139\uFE0F Skipping translations (${branch} is not a target branch)`
454
+ )
455
+ );
456
+ console.log(
457
+ chalk.dim(
458
+ ` Target branches: ${config.targetBranches.join(", ")}`
459
+ )
460
+ );
461
+ console.log(chalk.dim(` Use --force to translate anyway`));
462
+ process.exit(0);
463
+ }
464
+ spinner.start(`Extracting strings from ${config.extractionPattern}...`);
465
+ const extractor = new StringExtractor();
466
+ const extractedStrings = await extractor.extractFromProject(
467
+ config.extractionPattern,
468
+ projectRoot
469
+ );
470
+ if (extractedStrings.length === 0) {
471
+ spinner.warn("No translatable strings found");
472
+ console.log(chalk.yellow("Make sure you are using <T> components from @vocoder/react"));
473
+ process.exit(0);
474
+ }
475
+ spinner.succeed(
476
+ `Extracted ${chalk.cyan(extractedStrings.length)} strings from ${chalk.cyan(config.extractionPattern)}`
477
+ );
478
+ if (options.verbose) {
479
+ console.log(chalk.dim("\nSample strings:"));
480
+ extractedStrings.slice(0, 5).forEach((s) => {
481
+ console.log(chalk.dim(` - "${s.text}" (${s.file}:${s.line})`));
482
+ });
483
+ if (extractedStrings.length > 5) {
484
+ console.log(chalk.dim(` ... and ${extractedStrings.length - 5} more`));
485
+ }
486
+ console.log();
487
+ }
488
+ if (options.dryRun) {
489
+ console.log(chalk.cyan("\n\u{1F4CB} Dry run mode - would translate:"));
490
+ console.log(chalk.dim(` Strings: ${extractedStrings.length}`));
491
+ console.log(chalk.dim(` Branch: ${branch}`));
492
+ console.log(chalk.dim(` Target locales: ${config.targetLocales.join(", ")}`));
493
+ console.log(chalk.dim(`
494
+ No API calls made.`));
495
+ process.exit(0);
496
+ }
497
+ spinner.start("Submitting strings to Vocoder API...");
498
+ const strings = extractedStrings.map((s) => s.text);
499
+ const batchResponse = await api.submitTranslation(
500
+ branch,
501
+ strings,
502
+ config.targetLocales
503
+ );
504
+ spinner.succeed(
505
+ `Submitted to API - Batch ID: ${chalk.cyan(batchResponse.batchId)}`
506
+ );
507
+ if (batchResponse.status === "UP_TO_DATE" && batchResponse.noChanges) {
508
+ console.log(chalk.green("\n\u2714 No changes detected - strings are up to date"));
509
+ console.log(chalk.dim(" (Files will be written for build environment)\n"));
510
+ }
511
+ console.log(
512
+ chalk.dim(
513
+ ` New strings: ${chalk.cyan(batchResponse.newStrings)}`
514
+ )
515
+ );
516
+ if (batchResponse.deletedStrings && batchResponse.deletedStrings > 0) {
517
+ console.log(
518
+ chalk.dim(
519
+ ` Deleted strings: ${chalk.yellow(batchResponse.deletedStrings)} (archived)`
520
+ )
521
+ );
522
+ }
523
+ console.log(
524
+ chalk.dim(
525
+ ` Total strings: ${chalk.cyan(batchResponse.totalStrings)}`
526
+ )
527
+ );
528
+ if (batchResponse.newStrings === 0) {
529
+ console.log(
530
+ chalk.green("\n\u2705 No new strings - using existing translations")
531
+ );
532
+ } else {
533
+ console.log(
534
+ chalk.cyan(
535
+ `
536
+ \u23F3 Syncing to ${config.targetLocales.length} locales (${config.targetLocales.join(", ")})`
537
+ )
538
+ );
539
+ if (batchResponse.estimatedTime) {
540
+ console.log(
541
+ chalk.dim(
542
+ ` Estimated time: ~${batchResponse.estimatedTime} seconds`
543
+ )
544
+ );
545
+ }
546
+ }
547
+ spinner.start("Waiting for translations to complete...");
548
+ let lastProgress = 0;
549
+ const result = await api.waitForCompletion(
550
+ batchResponse.batchId,
551
+ config.timeout,
552
+ (progress) => {
553
+ const percent = Math.round(progress * 100);
554
+ if (percent > lastProgress) {
555
+ spinner.text = `Syncing... ${percent}% complete`;
556
+ lastProgress = percent;
557
+ }
558
+ }
559
+ );
560
+ const { translations, localeMetadata: apiLocaleMetadata } = result;
561
+ spinner.succeed("Translations complete!");
562
+ spinner.start(`Writing locale files to ${config.outputDir}...`);
563
+ const outputPath = join(projectRoot, config.outputDir);
564
+ mkdirSync(outputPath, { recursive: true });
565
+ let filesWritten = 0;
566
+ const localeNames = [];
567
+ for (const [locale, strings2] of Object.entries(translations)) {
568
+ const filePath = join(outputPath, `${locale}.json`);
569
+ const content = JSON.stringify(strings2, null, 2);
570
+ mkdirSync(dirname(filePath), { recursive: true });
571
+ writeFileSync(filePath, content, "utf-8");
572
+ filesWritten++;
573
+ localeNames.push(locale);
574
+ const sizeKB = (content.length / 1024).toFixed(1);
575
+ console.log(
576
+ chalk.dim(` \u2713 Wrote ${locale}.json (${sizeKB}KB)`)
577
+ );
578
+ }
579
+ const indexContent = generateIndexFile(localeNames, translations, apiLocaleMetadata);
580
+ const indexPath = join(outputPath, "index.ts");
581
+ writeFileSync(indexPath, indexContent, "utf-8");
582
+ console.log(chalk.dim(` \u2713 Generated index.ts (with flat locales map)`));
583
+ spinner.succeed(`Wrote ${chalk.cyan(filesWritten)} locale files`);
584
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
585
+ console.log(
586
+ chalk.green(`
587
+ \u2705 Sync complete! (${duration}s)
588
+ `)
589
+ );
590
+ console.log(chalk.dim("Next steps:"));
591
+ console.log(
592
+ chalk.dim(
593
+ ` 1. Import translations: import { translations } from '${config.outputDir}'`
594
+ )
595
+ );
596
+ console.log(
597
+ chalk.dim(
598
+ ` 2. Use VocoderProvider: <VocoderProvider translations={translations} defaultLocale="en">`
599
+ )
600
+ );
601
+ console.log(
602
+ chalk.dim(
603
+ ` 3. Commit ${config.outputDir}/ to your repository`
604
+ )
605
+ );
606
+ } catch (error) {
607
+ if (error instanceof Error) {
608
+ console.error(chalk.red(`
609
+ \u274C Error: ${error.message}
610
+ `));
611
+ if (error.message.includes("VOCODER_API_KEY")) {
612
+ console.log(chalk.yellow("\u{1F4A1} Solution:"));
613
+ console.log(chalk.dim(" Set your API key:"));
614
+ console.log(chalk.dim(' export VOCODER_API_KEY="your-api-key"'));
615
+ console.log(chalk.dim(" or add it to your .env file"));
616
+ } else if (error.message.includes("git branch")) {
617
+ console.log(chalk.yellow("\u{1F4A1} Solution:"));
618
+ console.log(chalk.dim(" Run from a git repository, or use:"));
619
+ console.log(chalk.dim(" vocoder translate --branch main"));
620
+ }
621
+ if (options.verbose) {
622
+ console.error(chalk.dim("\nFull error:"), error);
623
+ }
624
+ }
625
+ process.exit(1);
626
+ }
627
+ }
628
+
629
+ export {
630
+ detectBranch,
631
+ getLocalConfig,
632
+ validateLocalConfig,
633
+ sync
634
+ };
635
+ //# sourceMappingURL=chunk-N45Q4R6O.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/branch.ts","../src/utils/config.ts","../src/commands/sync.ts","../src/utils/api.ts","../src/utils/extract.ts"],"sourcesContent":["import { execSync } from 'child_process';\n\n/**\n * Detects the current git branch from multiple sources in priority order:\n * 1. Explicit --branch flag (passed as parameter)\n * 2. CI environment variables (GitHub Actions, Vercel, Netlify, etc.)\n * 3. Git command (local development)\n *\n * @param override - Optional branch name to override detection\n * @returns The current branch name\n */\nexport function detectBranch(override?: string): string {\n // 1. Explicit override (from --branch flag)\n if (override) {\n return override;\n }\n\n // 2. CI environment variables\n const envBranch =\n process.env.GITHUB_REF_NAME || // GitHub Actions\n process.env.VERCEL_GIT_COMMIT_REF || // Vercel\n process.env.BRANCH || // Netlify, generic\n process.env.CI_COMMIT_REF_NAME || // GitLab\n process.env.BITBUCKET_BRANCH || // Bitbucket\n process.env.CIRCLE_BRANCH; // CircleCI\n\n if (envBranch) {\n return envBranch;\n }\n\n // 3. Git command (local development)\n try {\n const branch = execSync('git rev-parse --abbrev-ref HEAD', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'ignore'],\n }).trim();\n\n return branch;\n } catch (error) {\n throw new Error(\n 'Failed to detect git branch. Make sure you are in a git repository or set the --branch flag.',\n );\n }\n}\n\n/**\n * Checks if the current branch is a target branch that should trigger translations\n *\n * @param currentBranch - The current branch name\n * @param targetBranches - List of branches that should trigger translations\n * @returns True if the branch should trigger translations\n */\nexport function isTargetBranch(\n currentBranch: string,\n targetBranches: string[],\n): boolean {\n return targetBranches.includes(currentBranch);\n}\n","import type { LocalConfig } from '../types.js';\nimport { config as loadEnv } from 'dotenv';\n\n// Load .env file if present\nloadEnv();\n\n/**\n * Loads local configuration from environment variables\n *\n * Required environment variables:\n * - VOCODER_API_KEY: Your Vocoder project API key\n *\n * Optional environment variables:\n * - VOCODER_API_URL: Override API URL (default: https://vocoder.app)\n *\n * @returns Local configuration\n */\nexport function getLocalConfig(): LocalConfig {\n const apiKey = process.env.VOCODER_API_KEY;\n\n if (!apiKey) {\n throw new Error(\n 'VOCODER_API_KEY is required. Set it in your .env file or environment:\\n' +\n ' export VOCODER_API_KEY=\"your-api-key\"\\n\\n' +\n 'Get your API key from: https://vocoder.app/settings/api-keys'\n );\n }\n\n return {\n apiKey,\n apiUrl: process.env.VOCODER_API_URL || 'https://vocoder.app',\n };\n}\n\n/**\n * Validates the local configuration\n */\nexport function validateLocalConfig(config: LocalConfig): void {\n if (!config.apiKey || config.apiKey.length === 0) {\n throw new Error('Invalid API key');\n }\n\n if (!config.apiKey.startsWith('vc_')) {\n throw new Error('Invalid API key format. Expected format: vc_...');\n }\n\n if (!config.apiUrl || !config.apiUrl.startsWith('http')) {\n throw new Error('Invalid API URL');\n }\n}\n","import { mkdirSync, writeFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { detectBranch, isTargetBranch } from '../utils/branch.js';\nimport { getLocalConfig, validateLocalConfig } from '../utils/config.js';\nimport { VocoderAPI } from '../utils/api.js';\nimport { StringExtractor } from '../utils/extract.js';\nimport type { TranslateOptions, ProjectConfig } from '../types.js';\n\n/**\n * Generate index.ts file that auto-imports all locale files and creates a flat locales map (O(N))\n * Translated names are generated at runtime using Intl.DisplayNames\n */\nfunction generateIndexFile(\n locales: string[],\n translations: Record<string, Record<string, string>>,\n localeMetadata?: Record<string, { nativeName: string; dir?: 'rtl' }>\n): string {\n // Convert locale names to valid JS identifiers (replace hyphens with underscores)\n const toIdentifier = (locale: string) => locale.replace(/-/g, '_');\n\n const imports = locales.map(\n (locale: string) => `import ${toIdentifier(locale)} from './${locale}.json';`\n ).join('\\n');\n\n const translationsObj = locales.map(\n (locale: string) => ` '${locale}': ${toIdentifier(locale)},`\n ).join('\\n');\n\n // Build flat locales map (O(N) instead of O(N²))\n // Use API-provided locale metadata if available, otherwise fallback to locale code\n const localesObjEntries = locales.map((locale: string) => {\n const metadata = localeMetadata?.[locale];\n\n if (metadata) {\n const escapedNativeName = metadata.nativeName.replace(/'/g, \"\\\\'\");\n const dirProp = metadata.dir ? `, dir: '${metadata.dir}' as const` : '';\n return ` '${locale}': { nativeName: '${escapedNativeName}'${dirProp} }`;\n } else {\n // Fallback: just use locale code as nativeName\n return ` '${locale}': { nativeName: '${locale}' }`;\n }\n });\n\n const localesObjString = localesObjEntries.join(',\\n');\n\n return `// Auto-generated by Vocoder CLI\n// This file imports all locale JSON files and exports them as a single object\n// Usage: import { translations, locales } from './.vocoder/locales';\n\n${imports}\n\nexport const translations = {\n${translationsObj}\n};\n\n/**\n * Flat locale metadata map (O(N))\n * Structure: locales[localeCode] = { nativeName, dir? }\n * - nativeName: Name in the locale's own language (e.g., \"Español\", \"简体中文\")\n * - dir: Optional 'rtl' for right-to-left locales\n *\n * Translated names are generated at runtime using Intl.DisplayNames:\n * Example: new Intl.DisplayNames(['es'], { type: 'language' }).of('en') → \"inglés\"\n * Display format: \\`\\${getDisplayName(code)} (\\${locales[code].nativeName})\\` → \"inglés (English)\"\n */\nexport const locales = {\n${localesObjString}\n};\n\nexport type SupportedLocale = ${locales.map((l: string) => `'${l}'`).join(' | ')};\n`;\n}\n\n/**\n * Main sync command\n *\n * Workflow:\n * 1. Detect branch\n * 2. Load project config\n * 3. Check if target branch (skip if not)\n * 4. Extract strings from source code\n * 5. Submit to API for translation\n * 6. Poll for completion\n * 7. Write locale files to .vocoder/locales/\n */\nexport async function sync(options: TranslateOptions = {}): Promise<void> {\n const startTime = Date.now();\n const projectRoot = process.cwd();\n\n try {\n // 1. Detect branch\n const spinner = ora('Detecting branch...').start();\n const branch = detectBranch(options.branch);\n spinner.succeed(`Detected branch: ${chalk.cyan(branch)}`);\n\n // 2. Load local config and fetch project config from API\n spinner.start('Loading project configuration...');\n const localConfig = getLocalConfig();\n validateLocalConfig(localConfig);\n\n const api = new VocoderAPI(localConfig);\n const apiConfig = await api.getProjectConfig();\n\n // Merge local and API config\n const config: ProjectConfig = {\n ...localConfig,\n ...apiConfig,\n extractionPattern: process.env.VOCODER_EXTRACTION_PATTERN || 'src/**/*.{tsx,jsx,ts,js}',\n outputDir: '.vocoder/locales',\n timeout: 60000,\n };\n\n spinner.succeed('Project configuration loaded');\n\n // 3. Check if target branch\n if (!options.force && !isTargetBranch(branch, config.targetBranches)) {\n console.log(\n chalk.yellow(\n `ℹ️ Skipping translations (${branch} is not a target branch)`,\n ),\n );\n console.log(\n chalk.dim(\n ` Target branches: ${config.targetBranches.join(', ')}`,\n ),\n );\n console.log(chalk.dim(` Use --force to translate anyway`));\n process.exit(0);\n }\n\n // 4. Extract strings\n spinner.start(`Extracting strings from ${config.extractionPattern}...`);\n const extractor = new StringExtractor();\n const extractedStrings = await extractor.extractFromProject(\n config.extractionPattern,\n projectRoot,\n );\n\n if (extractedStrings.length === 0) {\n spinner.warn('No translatable strings found');\n console.log(chalk.yellow('Make sure you are using <T> components from @vocoder/react'));\n process.exit(0);\n }\n\n spinner.succeed(\n `Extracted ${chalk.cyan(extractedStrings.length)} strings from ${chalk.cyan(config.extractionPattern)}`,\n );\n\n // Show sample strings in verbose mode\n if (options.verbose) {\n console.log(chalk.dim('\\nSample strings:'));\n extractedStrings.slice(0, 5).forEach((s) => {\n console.log(chalk.dim(` - \"${s.text}\" (${s.file}:${s.line})`));\n });\n if (extractedStrings.length > 5) {\n console.log(chalk.dim(` ... and ${extractedStrings.length - 5} more`));\n }\n console.log();\n }\n\n // Dry run mode - stop here\n if (options.dryRun) {\n console.log(chalk.cyan('\\n📋 Dry run mode - would translate:'));\n console.log(chalk.dim(` Strings: ${extractedStrings.length}`));\n console.log(chalk.dim(` Branch: ${branch}`));\n console.log(chalk.dim(` Target locales: ${config.targetLocales.join(', ')}`));\n console.log(chalk.dim(`\\n No API calls made.`));\n process.exit(0);\n }\n\n // 5. Submit to API\n spinner.start('Submitting strings to Vocoder API...');\n\n const strings = extractedStrings.map((s) => s.text);\n const batchResponse = await api.submitTranslation(\n branch,\n strings,\n config.targetLocales,\n );\n\n spinner.succeed(\n `Submitted to API - Batch ID: ${chalk.cyan(batchResponse.batchId)}`,\n );\n\n // Handle UP_TO_DATE status (hash matched, no changes)\n // Note: We still write locale files even when up-to-date, because\n // .vocoder/ might be gitignored and not present in the build environment\n if (batchResponse.status === 'UP_TO_DATE' && batchResponse.noChanges) {\n console.log(chalk.green('\\n✔ No changes detected - strings are up to date'));\n console.log(chalk.dim(' (Files will be written for build environment)\\n'));\n }\n\n // Display diff metrics\n console.log(\n chalk.dim(\n ` New strings: ${chalk.cyan(batchResponse.newStrings)}`,\n ),\n );\n\n if (batchResponse.deletedStrings && batchResponse.deletedStrings > 0) {\n console.log(\n chalk.dim(\n ` Deleted strings: ${chalk.yellow(batchResponse.deletedStrings)} (archived)`,\n ),\n );\n }\n\n console.log(\n chalk.dim(\n ` Total strings: ${chalk.cyan(batchResponse.totalStrings)}`,\n ),\n );\n\n if (batchResponse.newStrings === 0) {\n console.log(\n chalk.green('\\n✅ No new strings - using existing translations'),\n );\n // Still fetch and write translations\n } else {\n console.log(\n chalk.cyan(\n `\\n⏳ Syncing to ${config.targetLocales.length} locales (${config.targetLocales.join(', ')})`,\n ),\n );\n\n if (batchResponse.estimatedTime) {\n console.log(\n chalk.dim(\n ` Estimated time: ~${batchResponse.estimatedTime} seconds`,\n ),\n );\n }\n }\n\n // 6. Poll for completion\n spinner.start('Waiting for translations to complete...');\n\n let lastProgress = 0;\n const result = await api.waitForCompletion(\n batchResponse.batchId,\n config.timeout,\n (progress) => {\n const percent = Math.round(progress * 100);\n if (percent > lastProgress) {\n spinner.text = `Syncing... ${percent}% complete`;\n lastProgress = percent;\n }\n },\n );\n\n const { translations, localeMetadata: apiLocaleMetadata } = result;\n spinner.succeed('Translations complete!');\n\n // 7. Write locale files\n spinner.start(`Writing locale files to ${config.outputDir}...`);\n\n const outputPath = join(projectRoot, config.outputDir);\n mkdirSync(outputPath, { recursive: true });\n\n let filesWritten = 0;\n const localeNames: string[] = [];\n\n // Write locale files from API response (includes source locale now)\n for (const [locale, strings] of Object.entries(translations)) {\n const filePath = join(outputPath, `${locale}.json`);\n const content = JSON.stringify(strings, null, 2);\n\n // Ensure directory exists\n mkdirSync(dirname(filePath), { recursive: true });\n\n writeFileSync(filePath, content, 'utf-8');\n filesWritten++;\n localeNames.push(locale);\n\n const sizeKB = (content.length / 1024).toFixed(1);\n console.log(\n chalk.dim(` ✓ Wrote ${locale}.json (${sizeKB}KB)`),\n );\n }\n\n // Generate index file for auto-importing all locales (including source)\n const indexContent = generateIndexFile(localeNames, translations, apiLocaleMetadata);\n const indexPath = join(outputPath, 'index.ts');\n writeFileSync(indexPath, indexContent, 'utf-8');\n console.log(chalk.dim(` ✓ Generated index.ts (with flat locales map)`));\n\n spinner.succeed(`Wrote ${chalk.cyan(filesWritten)} locale files`);\n\n // Success!\n const duration = ((Date.now() - startTime) / 1000).toFixed(1);\n console.log(\n chalk.green(`\\n✅ Sync complete! (${duration}s)\\n`),\n );\n\n // Show next steps\n console.log(chalk.dim('Next steps:'));\n console.log(\n chalk.dim(\n ` 1. Import translations: import { translations } from '${config.outputDir}'`,\n ),\n );\n console.log(\n chalk.dim(\n ` 2. Use VocoderProvider: <VocoderProvider translations={translations} defaultLocale=\"en\">`,\n ),\n );\n console.log(\n chalk.dim(\n ` 3. Commit ${config.outputDir}/ to your repository`,\n ),\n );\n } catch (error) {\n if (error instanceof Error) {\n console.error(chalk.red(`\\n❌ Error: ${error.message}\\n`));\n\n // Show helpful error messages\n if (error.message.includes('VOCODER_API_KEY')) {\n console.log(chalk.yellow('💡 Solution:'));\n console.log(chalk.dim(' Set your API key:'));\n console.log(chalk.dim(' export VOCODER_API_KEY=\"your-api-key\"'));\n console.log(chalk.dim(' or add it to your .env file'));\n } else if (error.message.includes('git branch')) {\n console.log(chalk.yellow('💡 Solution:'));\n console.log(chalk.dim(' Run from a git repository, or use:'));\n console.log(chalk.dim(' vocoder translate --branch main'));\n }\n\n if (options.verbose) {\n console.error(chalk.dim('\\nFull error:'), error);\n }\n }\n\n process.exit(1);\n }\n}\n","import type {\n TranslationBatchResponse,\n TranslationStatusResponse,\n LocalConfig,\n APIProjectConfig,\n} from '../types.js';\n\nexport class VocoderAPI {\n private apiUrl: string;\n private apiKey: string;\n\n constructor(config: LocalConfig) {\n this.apiUrl = config.apiUrl;\n this.apiKey = config.apiKey;\n }\n\n /**\n * Fetch project configuration from API\n * Project is determined from the API key\n */\n async getProjectConfig(): Promise<APIProjectConfig> {\n const response = await fetch(\n `${this.apiUrl}/api/cli/config`,\n {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n },\n );\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to fetch project config: ${error}`);\n }\n\n const data = await response.json();\n\n return {\n sourceLocale: data.sourceLocale,\n targetLocales: data.targetLocales,\n targetBranches: data.targetBranches,\n };\n }\n\n /**\n * Submit strings for translation\n * Project is determined from the API key\n */\n async submitTranslation(\n branch: string,\n strings: string[],\n targetLocales: string[],\n ): Promise<TranslationBatchResponse> {\n // Compute hash of sorted strings for fast comparison\n const crypto = await import('crypto');\n const sortedStrings = [...strings].sort();\n const stringsHash = crypto\n .createHash('sha256')\n .update(JSON.stringify(sortedStrings))\n .digest('hex');\n\n const response = await fetch(`${this.apiUrl}/api/cli/sync`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n branch,\n strings,\n targetLocales,\n stringsHash,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Translation submission failed: ${error}`);\n }\n\n return response.json();\n }\n\n /**\n * Check translation status\n */\n async getTranslationStatus(\n batchId: string,\n ): Promise<TranslationStatusResponse> {\n const response = await fetch(\n `${this.apiUrl}/api/cli/sync/status/${batchId}`,\n {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n },\n );\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to check translation status: ${error}`);\n }\n\n return response.json();\n }\n\n /**\n * Wait for translation to complete with polling\n */\n async waitForCompletion(\n batchId: string,\n timeout: number = 60000,\n onProgress?: (progress: number) => void,\n ): Promise<{\n translations: Record<string, Record<string, string>>;\n localeMetadata?: Record<string, { nativeName: string; dir?: 'rtl' }>;\n }> {\n const startTime = Date.now();\n const pollInterval = 1000; // Poll every second\n\n while (Date.now() - startTime < timeout) {\n const status = await this.getTranslationStatus(batchId);\n\n // Call progress callback\n if (onProgress) {\n onProgress(status.progress);\n }\n\n if (status.status === 'COMPLETED') {\n if (!status.translations) {\n throw new Error('Translation completed but no translations returned');\n }\n return {\n translations: status.translations,\n localeMetadata: status.localeMetadata,\n };\n }\n\n if (status.status === 'FAILED') {\n throw new Error(\n `Translation failed: ${status.errorMessage || 'Unknown error'}`,\n );\n }\n\n // Wait before polling again\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n }\n\n throw new Error(`Translation timeout after ${timeout}ms`);\n }\n}\n","import { readFileSync } from 'fs';\nimport { parse } from '@babel/parser';\nimport babelTraverse from '@babel/traverse';\nimport { glob } from 'glob';\nimport type { ExtractedString } from '../types.js';\n\n// Handle default export difference between ESM and CommonJS\nconst traverse = (babelTraverse as any).default || babelTraverse;\n\n/**\n * Extract translatable strings from source files\n *\n * NOTE: This is a simplified version for the CLI MVP.\n * Eventually this logic should be moved to a shared @vocoder/extraction package\n * that can be used by both the CLI and the backend.\n */\nexport class StringExtractor {\n /**\n * Extract strings from all files matching the pattern\n */\n async extractFromProject(\n pattern: string,\n projectRoot: string = process.cwd(),\n ): Promise<ExtractedString[]> {\n // Find all files matching the pattern\n const files = await glob(pattern, {\n cwd: projectRoot,\n absolute: true,\n ignore: ['**/node_modules/**', '**/.next/**', '**/dist/**', '**/build/**'],\n });\n\n const allStrings: ExtractedString[] = [];\n\n // Extract from each file\n for (const file of files) {\n try {\n const strings = await this.extractFromFile(file);\n allStrings.push(...strings);\n } catch (error) {\n console.warn(`Warning: Failed to extract from ${file}:`, error);\n }\n }\n\n // Deduplicate strings (same text = one entry)\n const unique = this.deduplicateStrings(allStrings);\n\n return unique;\n }\n\n /**\n * Extract strings from a single file\n */\n private async extractFromFile(filePath: string): Promise<ExtractedString[]> {\n const code = readFileSync(filePath, 'utf-8');\n const strings: ExtractedString[] = [];\n\n try {\n // Parse the code\n const ast = parse(code, {\n sourceType: 'module',\n plugins: ['jsx', 'typescript'],\n });\n\n // Track imports from @vocoder/react\n const vocoderImports = new Map<string, string>();\n const tFunctionNames = new Set<string>(); // Track 't' function names\n\n // Traverse the AST\n traverse(ast, {\n // Track imports of <T> component and t function\n ImportDeclaration: (path) => {\n const source = path.node.source.value;\n\n if (source === '@vocoder/react') {\n path.node.specifiers.forEach((spec) => {\n if (spec.type === 'ImportSpecifier') {\n const imported =\n spec.imported.type === 'Identifier'\n ? spec.imported.name\n : null;\n const local = spec.local.name;\n\n if (imported === 'T') {\n vocoderImports.set(local, 'T');\n }\n // Track direct import of 't' function\n if (imported === 't') {\n tFunctionNames.add(local);\n }\n // Track useVocoder hook (which provides 't')\n if (imported === 'useVocoder') {\n // We'll track destructured 't' in VariableDeclarator\n }\n }\n });\n }\n },\n\n // Track destructured 't' from useVocoder hook\n VariableDeclarator: (path) => {\n const init = path.node.init;\n\n // Check if this is: const { t } = useVocoder()\n if (\n init &&\n init.type === 'CallExpression' &&\n init.callee.type === 'Identifier' &&\n init.callee.name === 'useVocoder' &&\n path.node.id.type === 'ObjectPattern'\n ) {\n path.node.id.properties.forEach((prop: any) => {\n if (\n prop.type === 'ObjectProperty' &&\n prop.key.type === 'Identifier' &&\n prop.key.name === 't'\n ) {\n const localName =\n prop.value.type === 'Identifier' ? prop.value.name : 't';\n tFunctionNames.add(localName);\n }\n });\n }\n },\n\n // Extract from t() function calls\n CallExpression: (path) => {\n const callee = path.node.callee;\n\n // Check if this is a call to 't' function\n const isTFunction =\n callee.type === 'Identifier' && tFunctionNames.has(callee.name);\n\n if (!isTFunction) return;\n\n // Get the first argument (the string to translate)\n const firstArg = path.node.arguments[0];\n if (!firstArg) return;\n\n let text: string | null = null;\n\n // Handle string literal: t('Hello')\n if (firstArg.type === 'StringLiteral') {\n text = firstArg.value;\n }\n // Handle template literal: t(`Hello ${name}`)\n else if (firstArg.type === 'TemplateLiteral') {\n text = this.extractTemplateText(firstArg);\n }\n\n if (!text || text.trim().length === 0) return;\n\n // Get options from second argument\n const secondArg = path.node.arguments[1];\n let context: string | undefined;\n let formality: 'formal' | 'informal' | 'auto' | undefined;\n\n if (secondArg && secondArg.type === 'ObjectExpression') {\n secondArg.properties.forEach((prop: any) => {\n if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {\n if (prop.key.name === 'context' && prop.value.type === 'StringLiteral') {\n context = prop.value.value;\n }\n if (prop.key.name === 'formality' && prop.value.type === 'StringLiteral') {\n formality = prop.value.value as 'formal' | 'informal' | 'auto';\n }\n }\n });\n }\n\n strings.push({\n text: text.trim(),\n file: filePath,\n line: path.node.loc?.start.line || 0,\n context,\n formality,\n });\n },\n\n // Extract from JSX elements\n JSXElement: (path) => {\n const opening = path.node.openingElement;\n const tagName =\n opening.name.type === 'JSXIdentifier'\n ? opening.name.name\n : null;\n\n if (!tagName) return;\n\n // Check if this is a <T> component (or aliased import)\n const isTranslationComponent = vocoderImports.has(tagName);\n\n if (!isTranslationComponent) return;\n\n // Extract text content\n const text = this.extractTextContent(path.node.children);\n\n if (!text || text.trim().length === 0) return;\n\n // Extract context and formality from props\n const context = this.getStringAttribute(opening.attributes, 'context');\n const formality = this.getStringAttribute(\n opening.attributes,\n 'formality',\n ) as 'formal' | 'informal' | 'auto' | undefined;\n\n strings.push({\n text: text.trim(),\n file: filePath,\n line: path.node.loc?.start.line || 0,\n context,\n formality,\n });\n },\n });\n } catch (error) {\n throw new Error(\n `Failed to parse ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n\n return strings;\n }\n\n /**\n * Extract text from template literal\n * Converts template literals like `Hello ${name}` to `Hello {name}`\n */\n private extractTemplateText(node: any): string {\n let text = '';\n\n for (let i = 0; i < node.quasis.length; i++) {\n const quasi = node.quasis[i];\n text += quasi.value.raw;\n\n // Add placeholder for expressions\n if (i < node.expressions.length) {\n const expr = node.expressions[i];\n if (expr.type === 'Identifier') {\n text += `{${expr.name}}`;\n } else {\n // For complex expressions, use generic placeholder\n text += '{value}';\n }\n }\n }\n\n return text;\n }\n\n /**\n * Extract text content from JSX children\n */\n private extractTextContent(children: any[]): string {\n let text = '';\n\n for (const child of children) {\n if (child.type === 'JSXText') {\n text += child.value;\n } else if (child.type === 'JSXExpressionContainer') {\n const expr = child.expression;\n\n // Handle {variableName} - actual identifier\n if (expr.type === 'Identifier') {\n text += `{${expr.name}}`;\n }\n // Handle {\"{variableName}\"} - string literal placeholder\n else if (expr.type === 'StringLiteral') {\n text += expr.value;\n }\n // Handle {`${variableName}`} - template literal\n // Convert template literal syntax to ICU MessageFormat: `$${price}` → ${price}\n else if (expr.type === 'TemplateLiteral') {\n text += this.extractTemplateText(expr);\n }\n }\n }\n\n return text;\n }\n\n /**\n * Get string value from JSX attribute\n */\n private getStringAttribute(\n attributes: any[],\n name: string,\n ): string | undefined {\n const attr = attributes.find(\n (a) => a.type === 'JSXAttribute' && a.name.name === name,\n );\n\n if (!attr || !attr.value) return undefined;\n\n if (attr.value.type === 'StringLiteral') {\n return attr.value.value;\n }\n\n return undefined;\n }\n\n /**\n * Deduplicate strings (keep first occurrence)\n */\n private deduplicateStrings(strings: ExtractedString[]): ExtractedString[] {\n const seen = new Set<string>();\n const unique: ExtractedString[] = [];\n\n for (const str of strings) {\n // Create a key based on text + context + formality\n const key = `${str.text}|${str.context || ''}|${str.formality || ''}`;\n\n if (!seen.has(key)) {\n seen.add(key);\n unique.push(str);\n }\n }\n\n return unique;\n }\n}\n"],"mappings":"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","names":["strings"]}
@@ -0,0 +1,97 @@
1
+ interface TranslateOptions {
2
+ branch?: string;
3
+ force?: boolean;
4
+ dryRun?: boolean;
5
+ verbose?: boolean;
6
+ maxAge?: number;
7
+ }
8
+ interface LocalConfig {
9
+ apiKey: string;
10
+ apiUrl: string;
11
+ }
12
+ interface APIProjectConfig {
13
+ sourceLocale: string;
14
+ targetLocales: string[];
15
+ targetBranches: string[];
16
+ }
17
+ interface ProjectConfig extends LocalConfig, APIProjectConfig {
18
+ extractionPattern: string;
19
+ outputDir: string;
20
+ timeout: number;
21
+ }
22
+ interface ExtractedString {
23
+ text: string;
24
+ file: string;
25
+ line: number;
26
+ context?: string;
27
+ formality?: 'formal' | 'informal' | 'auto';
28
+ }
29
+ interface TranslationBatchResponse {
30
+ batchId: string;
31
+ newStrings: number;
32
+ deletedStrings?: number;
33
+ totalStrings: number;
34
+ status: 'PENDING' | 'TRANSLATING' | 'COMPLETED' | 'FAILED' | 'UP_TO_DATE';
35
+ noChanges?: boolean;
36
+ estimatedTime?: number;
37
+ translations?: Record<string, Record<string, string>>;
38
+ }
39
+ interface TranslationStatusResponse {
40
+ status: 'PENDING' | 'TRANSLATING' | 'COMPLETED' | 'FAILED';
41
+ progress: number;
42
+ jobs?: Array<{
43
+ locale: string;
44
+ status: string;
45
+ progress: number;
46
+ }>;
47
+ translations?: Record<string, Record<string, string>>;
48
+ localeMetadata?: Record<string, {
49
+ nativeName: string;
50
+ dir?: 'rtl';
51
+ }>;
52
+ errorMessage?: string;
53
+ }
54
+
55
+ /**
56
+ * Main sync command
57
+ *
58
+ * Workflow:
59
+ * 1. Detect branch
60
+ * 2. Load project config
61
+ * 3. Check if target branch (skip if not)
62
+ * 4. Extract strings from source code
63
+ * 5. Submit to API for translation
64
+ * 6. Poll for completion
65
+ * 7. Write locale files to .vocoder/locales/
66
+ */
67
+ declare function sync(options?: TranslateOptions): Promise<void>;
68
+
69
+ /**
70
+ * Detects the current git branch from multiple sources in priority order:
71
+ * 1. Explicit --branch flag (passed as parameter)
72
+ * 2. CI environment variables (GitHub Actions, Vercel, Netlify, etc.)
73
+ * 3. Git command (local development)
74
+ *
75
+ * @param override - Optional branch name to override detection
76
+ * @returns The current branch name
77
+ */
78
+ declare function detectBranch(override?: string): string;
79
+
80
+ /**
81
+ * Loads local configuration from environment variables
82
+ *
83
+ * Required environment variables:
84
+ * - VOCODER_API_KEY: Your Vocoder project API key
85
+ *
86
+ * Optional environment variables:
87
+ * - VOCODER_API_URL: Override API URL (default: https://vocoder.app)
88
+ *
89
+ * @returns Local configuration
90
+ */
91
+ declare function getLocalConfig(): LocalConfig;
92
+ /**
93
+ * Validates the local configuration
94
+ */
95
+ declare function validateLocalConfig(config: LocalConfig): void;
96
+
97
+ export { type APIProjectConfig, type ExtractedString, type LocalConfig, type ProjectConfig, type TranslateOptions, type TranslationBatchResponse, type TranslationStatusResponse, detectBranch, getLocalConfig, sync, validateLocalConfig };
package/dist/index.mjs ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ detectBranch,
3
+ getLocalConfig,
4
+ sync,
5
+ validateLocalConfig
6
+ } from "./chunk-N45Q4R6O.mjs";
7
+ export {
8
+ detectBranch,
9
+ getLocalConfig,
10
+ sync,
11
+ validateLocalConfig
12
+ };
13
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@vocoder/cli",
3
+ "version": "0.1.1",
4
+ "description": "CLI tool for Vocoder translation workflow",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "bin": {
11
+ "vocoder": "dist/bin.mjs"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/vocoder/vocoder-sdk.git",
19
+ "directory": "packages/cli"
20
+ },
21
+ "keywords": [
22
+ "i18n",
23
+ "internationalization",
24
+ "translation",
25
+ "cli",
26
+ "localization",
27
+ "extraction",
28
+ "ast",
29
+ "babel"
30
+ ],
31
+ "author": "Vocoder <admin@vocoder.app>",
32
+ "license": "MIT",
33
+ "homepage": "https://github.com/vocoder/vocoder-sdk#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/vocoder/vocoder-sdk/issues"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "dev": "tsup --watch",
40
+ "watch": "tsup --watch",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "test:unit": "vitest run --exclude 'src/__tests__/integration/**'",
44
+ "test:integration": "vitest run src/__tests__/integration",
45
+ "test:integration:skip": "SKIP_INTEGRATION=true vitest run",
46
+ "lint": "eslint . --ext .ts",
47
+ "typecheck": "tsc --noEmit"
48
+ },
49
+ "dependencies": {
50
+ "@babel/core": "^7.26.0",
51
+ "@babel/parser": "^7.26.0",
52
+ "@babel/traverse": "^7.26.0",
53
+ "@babel/types": "^7.26.0",
54
+ "@vocoder/types": "^0.1.0",
55
+ "chalk": "^5.3.0",
56
+ "commander": "^11.1.0",
57
+ "dotenv": "^16.3.1",
58
+ "glob": "^10.3.10",
59
+ "ora": "^8.0.1"
60
+ },
61
+ "devDependencies": {
62
+ "@types/babel__core": "^7.20.5",
63
+ "@types/babel__traverse": "^7.20.6",
64
+ "@types/node": "^20.19.9",
65
+ "tsup": "^8.0.0",
66
+ "typescript": "^5.4.0",
67
+ "vitest": "^1.0.0"
68
+ }
69
+ }