UNPKG

@shelchin/svelte-i18n

Version:

The last Svelte i18n library you'll ever need. Type-safe, AI-powered, zero-config.

137 lines (136 loc) 5.12 kB
#!/usr/bin/env node /** * Extract translation keys from source code * Scans for t('key') and i18n.t('key') patterns */ import { readFileSync, writeFileSync, readdirSync, statSync } from 'fs'; import { join, extname } from 'path'; const DEFAULT_EXTENSIONS = ['.svelte', '.ts', '.js', '.tsx', '.jsx']; const DEFAULT_EXCLUDE = ['node_modules', '.svelte-kit', 'dist', 'build']; function extractKeysFromFile(filePath) { const keys = new Set(); let content = readFileSync(filePath, 'utf-8'); // Remove comments before extracting keys // Remove single-line comments content = content.replace(/\/\/.*$/gm, ''); // Remove multi-line comments content = content.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove HTML comments content = content.replace(/<!--[\s\S]*?-->/g, ''); // Match patterns like: // i18n.t('key') // i18n.t("key") // $t('key') // t('key') - but only when it's likely a translation function // <Trans key="key"> // More precise patterns to avoid false positives const patterns = [ // i18n.t() or any variable ending with i18n.t() /\bi18n\.t\s*\(\s*['"]([^'"]+)['"]/g, // $t() in Svelte files /\$t\s*\(\s*['"]([^'"]+)['"]/g, // Standalone t() but not import/export statements /(?<!export\s+)(?<!import\s+)(?<!\w)t\s*\(\s*['"]([^'"]+)['"]/g, // Trans component /<Trans\s+key\s*=\s*['"]([^'"]+)['"]/g, // getI18n().t() /getI18n\(\)\.t\s*\(\s*['"]([^'"]+)['"]/g ]; patterns.forEach((pattern) => { let match; while ((match = pattern.exec(content)) !== null) { const key = match[1]; // Filter out obvious non-translation keys (file paths, URLs, etc.) if (!key.includes('/') && !key.includes('\\') && !key.startsWith('http')) { keys.add(key); } } }); return keys; } function scanDirectory(dir, options) { const allKeys = new Set(); function scan(currentDir) { const files = readdirSync(currentDir); for (const file of files) { const filePath = join(currentDir, file); const stats = statSync(filePath); if (stats.isDirectory()) { // Skip excluded directories if (options.exclude?.some((exc) => filePath.includes(exc))) { continue; } scan(filePath); } else if (stats.isFile()) { const ext = extname(file); if (options.extensions?.includes(ext)) { const keys = extractKeysFromFile(filePath); keys.forEach((key) => allKeys.add(key)); } } } } scan(dir); return allKeys; } function buildTranslationObject(keys) { const result = {}; keys.forEach((key) => { const parts = key.split('.'); let current = result; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (i === parts.length - 1) { // Last part - set the value if (!current[part]) { current[part] = '[TODO: Add translation]'; } } else { // Intermediate part - create nested object if (!current[part]) { current[part] = {}; } else if (typeof current[part] === 'string') { // If current[part] is a string (from a previous key), convert to object console.warn(`⚠️ Key conflict: "${key}" conflicts with existing key. Converting to nested structure.`); current[part] = {}; } current = current[part]; } } }); return result; } export function extract(options) { const config = { ...options, extensions: options.extensions || DEFAULT_EXTENSIONS, exclude: options.exclude || DEFAULT_EXCLUDE }; console.log('🔍 Extracting translation keys...'); console.log(` Source: ${config.srcDir}`); console.log(` Extensions: ${config.extensions.join(', ')}`); const keys = scanDirectory(config.srcDir, config); console.log(`✅ Found ${keys.size} unique keys`); const translations = buildTranslationObject(keys); writeFileSync(config.outFile, JSON.stringify(translations, null, 2), 'utf-8'); console.log(`📝 Written to ${config.outFile}`); } // CLI interface // Check if this file is being run directly (ES module style) if (import.meta.url === `file://${process.argv[1]}`) { const args = process.argv.slice(2); if (args.length < 2) { console.error('Usage: extract <srcDir> <outFile> [extensions...]'); console.error('Example: extract ./src ./translations/template.json .svelte .ts'); process.exit(1); } const [srcDir, outFile, ...extensions] = args; extract({ srcDir, outFile, extensions: extensions.length > 0 ? extensions : undefined }); }