UNPKG

@dmvicent3/tcli

Version:

A CLI tool for managing translations in projects using next-translate

107 lines (106 loc) 3.94 kB
import { confirm, isCancel } from '@clack/prompts'; import { requireConfig, saveConfig, getApiKey } from '../lib/config.js'; import { loadTranslationFile, saveTranslationFile } from '../lib/files.js'; import { GeminiTranslator } from '../lib/gemini.js'; import { existsSync, rmSync } from 'fs'; import { join } from 'path'; export async function langAddCommand(lang) { const config = requireConfig(); if (config.langs.includes(lang)) { console.error(`[${config.defaultNamespace}] Language '${lang}' already exists`); process.exit(1); } config.langs.push(lang); config.langs.sort(); saveConfig(config); config.namespaces.forEach((ns) => { saveTranslationFile(config.langDir, lang, ns, {}); }); const autoTranslate = await confirm({ message: `Auto-translate all existing keys to ${lang}?`, }); if (isCancel(autoTranslate)) { console.log('[tcli] Operation cancelled'); process.exit(0); } if (autoTranslate) { const translator = new GeminiTranslator(getApiKey()); for (const ns of config.namespaces) { const sourceTranslations = loadTranslationFile(config.langDir, config.sourceLang, ns); const flatKeys = flattenObject(sourceTranslations); if (Object.keys(flatKeys).length === 0) continue; const translatedKeys = await translator.batchTranslate(flatKeys, config.sourceLang, lang); const targetTranslations = unflattenObject(translatedKeys); saveTranslationFile(config.langDir, lang, ns, targetTranslations); } console.log(`[${config.defaultNamespace}] Language added and translated`); } else { console.log(`[${config.defaultNamespace}] Language added`); } } export async function langRemoveCommand(lang) { const config = requireConfig(); if (!config.langs.includes(lang)) { console.error(`[${config.defaultNamespace}] Language '${lang}' not found`); process.exit(1); } if (lang === config.sourceLang) { console.error(`[${config.defaultNamespace}] Cannot remove source language`); process.exit(1); } const confirmed = await confirm({ message: `Remove language '${lang}' and all its translations?`, }); if (isCancel(confirmed)) { console.log('[tcli] Operation cancelled'); process.exit(0); } if (!confirmed) return; config.langs = config.langs.filter((l) => l !== lang); saveConfig(config); const langDirPath = join(config.langDir, lang); if (existsSync(langDirPath)) { rmSync(langDirPath, { recursive: true, force: true }); } console.log(`[${config.defaultNamespace}] Language removed`); } export async function langListCommand() { const config = requireConfig(); console.log(`[${config.defaultNamespace}] Languages:`); config.langs.forEach((lang) => { const marker = lang === config.sourceLang ? ' (source)' : ''; console.log(` ${lang}${marker}`); }); } function flattenObject(obj, prefix = '') { const flattened = {}; Object.keys(obj).forEach((key) => { const newKey = prefix ? `${prefix}.${key}` : key; if (typeof obj[key] === 'object' && obj[key] !== null) { Object.assign(flattened, flattenObject(obj[key], newKey)); } else { flattened[newKey] = obj[key]; } }); return flattened; } function unflattenObject(obj) { const result = {}; Object.keys(obj).forEach((key) => { const parts = key.split('.'); let current = result; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; if (!(part in current)) { current[part] = {}; } current = current[part]; } current[parts[parts.length - 1]] = obj[key]; }); return result; }