Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion translator/bin/qwen-translator
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

const { TranslationCLI } = require("../dist/cli.js");

// Read version from package.json
function getPackageVersion() {
try {
const path = require("path");
const fs = require("fs");
const packageJsonPath = path.join(__dirname, "../package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
return packageJson.version;
} catch (error) {
console.warn("Could not read version from package.json, using default");
return "0.0.1";
}
}

async function main() {
const cli = new TranslationCLI();
const { Command } = require("commander");
Expand All @@ -10,7 +24,7 @@ async function main() {
program
.name("qwen-translator")
.description("AI-powered documentation translation tool")
.version("1.0.0");
.version(getPackageVersion());

// init command
program
Expand Down
2 changes: 1 addition & 1 deletion translator/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qwen-code/translator",
"version": "0.0.1",
"version": "0.0.2",
"description": "A universal documentation translator for any GitHub project. Instantly translate docs with AI and automatically build a Nextra-based documentation site.",
"main": "./dist/cli.js",
"bin": {
Expand Down
109 changes: 84 additions & 25 deletions translator/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import fs from "fs-extra";
import path from "path";
import { SyncManager } from "./sync";
import { DocumentTranslator } from "./translator";
import { readFileSync } from "fs";

/**
* CLI project configuration interface
Expand Down Expand Up @@ -43,7 +44,9 @@ class TranslationCLI {
*/
async initProject(): Promise<void> {
console.log(chalk.blue("🚀 Initializing document translation project"));
console.log(chalk.gray("Please provide the following project configuration:\n"));
console.log(
chalk.gray("Please provide the following project configuration:\n")
);

const answers = await inquirer.prompt([
{
Expand All @@ -62,7 +65,8 @@ class TranslationCLI {
validate: (input: string) => {
const urlPattern = /^https?:\/\/.+\.git$/;
return (
urlPattern.test(input) || "Please enter a valid Git repository URL (ending with .git)"
urlPattern.test(input) ||
"Please enter a valid Git repository URL (ending with .git)"
);
},
},
Expand Down Expand Up @@ -134,17 +138,31 @@ class TranslationCLI {
await this.copyEnvExample();

console.log(chalk.green("\n✅ Project initialization completed!"));
console.log(chalk.yellow(`📝 Project configuration saved to: ${this.projectConfigPath}`));
console.log(
chalk.yellow(
`📝 Project configuration saved to: ${this.projectConfigPath}`
)
);
console.log(chalk.blue("\n📋 Next steps:"));
console.log(chalk.gray("1. Configure environment variables:"));
console.log(chalk.gray(" cp .env.example .env"));
console.log(chalk.gray(" # Edit .env file and add your API keys"));
console.log(chalk.gray("2. Run commands:"));
console.log(chalk.gray(" qwen-translation sync # Sync source repository docs"));
console.log(chalk.gray(" qwen-translation translate # Translate documents"));
console.log(chalk.gray(" qwen-translation config # View/modify configuration"));
console.log(chalk.gray(" npm install # Install dependencies"));
console.log(chalk.gray(" npm run dev # Start development server"));
console.log(
chalk.gray(" qwen-translation sync # Sync source repository docs")
);
console.log(
chalk.gray(" qwen-translation translate # Translate documents")
);
console.log(
chalk.gray(" qwen-translation config # View/modify configuration")
);
console.log(
chalk.gray(" npm install # Install dependencies")
);
console.log(
chalk.gray(" npm run dev # Start development server")
);
}

/**
Expand Down Expand Up @@ -195,15 +213,17 @@ class TranslationCLI {
console.log(chalk.green("✅ Nextra template copied successfully"));
} catch (error: any) {
if (error.code === "EEXIST") {
console.log(chalk.yellow("⚠️ Target directory already contains files, skipping copy"));
console.log(
chalk.yellow(
"⚠️ Target directory already contains files, skipping copy"
)
);
} else {
throw new Error(`Failed to copy template: ${error.message}`);
}
}
}



/**
* Update .gitignore file, add translation-related ignore rules
*/
Expand Down Expand Up @@ -241,7 +261,9 @@ class TranslationCLI {
console.log(chalk.green("✅ .gitignore file updated successfully"));
} else {
console.log(
chalk.yellow("⚠️ .gitignore file already contains translation rules, skipping update")
chalk.yellow(
"⚠️ .gitignore file already contains translation rules, skipping update"
)
);
}
} catch (error: any) {
Expand All @@ -265,13 +287,19 @@ class TranslationCLI {
await fs.copy(examplePath, targetPath);
console.log(chalk.green("✅ .env example file copied successfully"));
console.log(
chalk.yellow("💡 Please copy .env.example to .env and add your API keys")
chalk.yellow(
"💡 Please copy .env.example to .env and add your API keys"
)
);
} else {
console.log(chalk.yellow("⚠️ .env example file does not exist, skipping copy"));
console.log(
chalk.yellow("⚠️ .env example file does not exist, skipping copy")
);
}
} catch (error: any) {
console.log(chalk.yellow(`⚠️ Failed to copy .env example file: ${error.message}`));
console.log(
chalk.yellow(`⚠️ Failed to copy .env example file: ${error.message}`)
);
}
}

Expand All @@ -282,7 +310,9 @@ class TranslationCLI {
const projectConfig = await this.loadProjectConfig();
if (!projectConfig) {
console.error(
chalk.red("❌ Project not initialized, please run 'qwen-translation init' first")
chalk.red(
"❌ Project not initialized, please run 'qwen-translation init' first"
)
);
return;
}
Expand All @@ -302,7 +332,9 @@ class TranslationCLI {
const result = await syncManager.syncDocuments(force);

if (result.success) {
console.log(chalk.green(`✅ Sync completed! Changed files: ${result.changes}`));
console.log(
chalk.green(`✅ Sync completed! Changed files: ${result.changes}`)
);

if (result.files.length > 0) {
console.log(chalk.blue("\n📄 Changed files:"));
Expand Down Expand Up @@ -338,7 +370,9 @@ class TranslationCLI {
const projectConfig = await this.loadProjectConfig();
if (!projectConfig) {
console.error(
chalk.red("❌ Project not initialized, please run 'qwen-translation init' first")
chalk.red(
"❌ Project not initialized, please run 'qwen-translation init' first"
)
);
return;
}
Expand All @@ -350,7 +384,11 @@ class TranslationCLI {
? [options.language]
: projectConfig.targetLanguages;

console.log(chalk.blue(`🌍 Starting document translation (${languages.join(", ")})...`));
console.log(
chalk.blue(
`🌍 Starting document translation (${languages.join(", ")})...`
)
);

try {
for (const lang of languages) {
Expand Down Expand Up @@ -396,7 +434,9 @@ class TranslationCLI {
const projectConfig = await this.loadProjectConfig();
if (!projectConfig) {
console.error(
chalk.red("❌ Project not initialized, please run 'qwen-translation init' first")
chalk.red(
"❌ Project not initialized, please run 'qwen-translation init' first"
)
);
return;
}
Expand Down Expand Up @@ -462,7 +502,6 @@ class TranslationCLI {
console.log(chalk.green("✅ Project configuration updated successfully"));
}


/**
* Save project configuration
*/
Expand Down Expand Up @@ -499,17 +538,23 @@ class TranslationCLI {

if (!projectConfig) {
console.log(chalk.red("❌ Project not initialized"));
console.log(chalk.gray("💡 Run 'qwen-translation init' to initialize project"));
console.log(
chalk.gray("💡 Run 'qwen-translation init' to initialize project")
);
return;
}

console.log(chalk.green("✅ Project initialized"));
console.log(chalk.gray(` Project name: ${projectConfig.name}`));
console.log(chalk.gray(` Source repository: ${projectConfig.sourceRepo}`));
console.log(chalk.gray(` Documentation path: ${projectConfig.docsPath}`));
console.log(chalk.gray(` Source language: ${projectConfig.sourceLanguage}`));
console.log(
chalk.gray(` Target languages: ${projectConfig.targetLanguages.join(", ")}`)
chalk.gray(` Source language: ${projectConfig.sourceLanguage}`)
);
console.log(
chalk.gray(
` Target languages: ${projectConfig.targetLanguages.join(", ")}`
)
);
console.log(chalk.gray(` Output directory: ${projectConfig.outputDir}`));

Expand Down Expand Up @@ -537,14 +582,28 @@ class TranslationCLI {
/**
* Main function - Setup CLI commands
*/
// Read version from package.json
function getPackageVersion(): string {
try {
// Get the directory where this script is located
const scriptDir = path.dirname(__filename);
const packageJsonPath = path.join(scriptDir, "../package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
return packageJson.version;
} catch (error) {
console.warn("Could not read version from package.json, using default");
return "0.0.1";
}
}

async function main() {
const cli = new TranslationCLI();
const program = new Command();

program
.name("qwen-translation")
.description("AI-powered document translation tool")
.version("1.0.0");
.version(getPackageVersion());

// init command
program
Expand Down
34 changes: 23 additions & 11 deletions translator/src/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ interface TranslationChunk extends DocumentSection {
index?: number;
}


export class DocumentTranslator {
private openai: OpenAI;
private apiConfig: TranslatorConfig;
Expand All @@ -40,7 +39,6 @@ export class DocumentTranslator {
const envLoader = createEnvLoader(this.projectRoot);
envLoader.loadEnv();


// 获取API配置
const apiConfig = envLoader.getApiConfig();

Expand Down Expand Up @@ -96,7 +94,6 @@ export class DocumentTranslator {
parsedContent.structure
);


console.log(chalk.green(`✓ Completed ${path.basename(filePath)}`));
return translatedDocument;
} catch (error: any) {
Expand All @@ -113,8 +110,12 @@ export class DocumentTranslator {
targetLang: string,
index: number
): Promise<TranslationChunk> {
// Skip code blocks and links
// Skip code blocks and links (all code blocks are skipped, including markdown)
if (chunk.type === "code" || chunk.type === "link") {
// Add special logging for markdown code blocks
if (chunk.type === "code" && chunk.metadata?.language === "markdown") {
console.log(chalk.gray(` ✓ Skipped markdown code block`));
}
return chunk;
}

Expand All @@ -128,7 +129,10 @@ export class DocumentTranslator {
console.log(chalk.cyan(` → Chunk ${index + 1} (${targetLang})`));

const prompt = this.buildTranslationPrompt(chunk.content, targetLang);
const translatedContent = await this.callTranslationAPI(prompt, targetLang);
const translatedContent = await this.callTranslationAPI(
prompt,
targetLang
);

// Cache translation result
this.translationCache.set(cacheKey, translatedContent);
Expand All @@ -148,7 +152,7 @@ export class DocumentTranslator {
buildSystemPrompt(targetLang: string): string {
const languageNames: Record<string, string> = {
zh: "Chinese",
de: "German",
de: "German",
fr: "French",
ru: "Russian",
ja: "Japanese",
Expand All @@ -167,7 +171,7 @@ Translate from a programmer's perspective - keep it natural and technically accu
- Tool names: Node.js, React, TypeScript, VS Code, etc.
- File extensions: .js, .md, .json, .yaml, etc.
- Command names: npm, git, curl, etc.
- Code blocks, variable names, function names
- Code blocks, variable names, function names (especially \`\`\`markdown blocks)
- URLs, file paths, configuration keys

**TRANSLATE NATURALLY:**
Expand Down Expand Up @@ -196,7 +200,7 @@ Think like a developer reading technical docs - what feels most natural and clea
buildTranslationPrompt(content: string, targetLang: string): string {
const languageNames: Record<string, string> = {
zh: "Chinese",
de: "German",
de: "German",
fr: "French",
ru: "Russian",
ja: "Japanese",
Expand All @@ -212,7 +216,11 @@ ${content}`;
/**
* Call translation API (using OpenAI SDK) with retry mechanism
*/
async callTranslationAPI(prompt: string, targetLang: string, retryCount = 0): Promise<string> {
async callTranslationAPI(
prompt: string,
targetLang: string,
retryCount = 0
): Promise<string> {
const maxRetries = 3;
const baseDelay = 1000; // 1 second base delay

Expand All @@ -236,7 +244,9 @@ ${content}`;
if (error.status === 429 && retryCount < maxRetries) {
const delay = baseDelay * Math.pow(2, retryCount); // Exponential backoff
console.log(
chalk.yellow(` ⏳ Rate limited, retrying in ${delay / 1000}s (${retryCount + 1}/${maxRetries})`)
chalk.yellow(
` ⏳ Rate limited, retrying in ${delay / 1000}s (${retryCount + 1}/${maxRetries})`
)
);
await new Promise((resolve) => setTimeout(resolve, delay));
return this.callTranslationAPI(prompt, targetLang, retryCount + 1);
Expand Down Expand Up @@ -336,7 +346,9 @@ ${content}`;
await fs.writeFile(targetPath, translatedContent, "utf-8");
console.log(chalk.green(`✓ Saved: ${path.basename(targetPath)}`));
} catch (error: any) {
console.error(chalk.red(`✗ Failed to translate ${file}: ${error.message}`));
console.error(
chalk.red(`✗ Failed to translate ${file}: ${error.message}`)
);
}
}
}
Expand Down
Loading