@vocoder/cli 0.1.2 → 0.1.4

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.
@@ -1 +0,0 @@
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"]}
package/dist/index.d.mts DELETED
@@ -1,97 +0,0 @@
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 DELETED
@@ -1,13 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}