Code translation — converting a working codebase from one language to another — is one of the highest-stakes AI tasks. A wrong line can introduce a subtle bug that passes tests but fails in production. This guide tests the tools and workflows for language migration, focusing on the translations developers actually need.
The Translation Challenge
AI code translation isn’t just syntax substitution. The hard parts are:
- Idiomatic translation: Python’s list comprehensions don’t have a direct Go equivalent
- Type system mapping: Python’s duck typing to Go’s interfaces requires judgment
- Error handling patterns: Python exceptions to Go error returns, Java checked exceptions to Kotlin’s unchecked
- Standard library differences: Python’s
datetimevs Java’sLocalDateTime
Tools that do syntax translation produce code that compiles but reads like the source language. Good translation produces idiomatic target language code.
Tool 1: Claude for Full-File Translation
Claude handles the most complex translations because it understands idioms, not just syntax.
Python to Go Translation Test
Input (Python):
from dataclasses import dataclass
from typing import Optional, List
from datetime import datetime
import json
@dataclass
class Order:
id: str
customer_email: str
items: List[dict]
total: float
created_at: datetime
shipped_at: Optional[datetime] = None
def is_shipped(self) -> bool:
return self.shipped_at is not None
def to_json(self) -> str:
return json.dumps({
"id": self.id,
"customer_email": self.customer_email,
"items": self.items,
"total": self.total,
"created_at": self.created_at.isoformat(),
"shipped_at": self.shipped_at.isoformat() if self.shipped_at else None,
})
def process_orders(orders: List[Order]) -> dict:
shipped = [o for o in orders if o.is_shipped()]
revenue = sum(o.total for o in orders)
return {"shipped_count": len(shipped), "total_revenue": revenue}
Claude’s Go output:
package orders
import (
"encoding/json"
"time"
)
type Order struct {
ID string `json:"id"`
CustomerEmail string `json:"customer_email"`
Items []map[string]any `json:"items"`
Total float64 `json:"total"`
CreatedAt time.Time `json:"created_at"`
ShippedAt *time.Time `json:"shipped_at,omitempty"`
}
func (o *Order) IsShipped() bool {
return o.ShippedAt != nil
}
func (o *Order) ToJSON() ([]byte, error) {
return json.Marshal(o)
}
type OrderSummary struct {
ShippedCount int `json:"shipped_count"`
TotalRevenue float64 `json:"total_revenue"`
}
func ProcessOrders(orders []Order) OrderSummary {
var shippedCount int
var totalRevenue float64
for _, o := range orders {
if o.IsShipped() {
shippedCount++
}
totalRevenue += o.Total
}
return OrderSummary{
ShippedCount: shippedCount,
TotalRevenue: totalRevenue,
}
}
Key translation decisions Claude made correctly:
Optional[datetime]→*time.Time(pointer for nullability)json.dumps()→json.Marshal()(returning error as second value, not raising)- List comprehension → explicit for loop (idiomatic Go)
- Python dict →
OrderSummarystruct (typed, notmap[string]any) - Added JSON struct tags automatically
Tool 2: GPT-4o for JavaScript to TypeScript
TypeScript migration is the most common translation need for web teams. GPT-4o excels here:
// Input: JavaScript module
const express = require('express');
const db = require('./db');
const router = express.Router();
router.get('/users', async (req, res) => {
try {
const { page = 1, limit = 20, role } = req.query;
const offset = (page - 1) * limit;
let query = db('users').select('*');
if (role) query = query.where({ role });
const users = await query.limit(limit).offset(offset);
const total = await db('users').count('id as count').first();
res.json({
users,
pagination: { page: Number(page), limit: Number(limit), total: Number(total.count) }
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
GPT-4o TypeScript output:
import { Router, Request, Response } from 'express';
import type { Knex } from 'knex';
import db from './db';
interface User {
id: number;
email: string;
name: string;
role: 'admin' | 'member' | 'viewer';
created_at: Date;
}
interface PaginationParams {
page?: string;
limit?: string;
role?: string;
}
interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
const router = Router();
router.get('/users', async (req: Request<{}, {}, {}, PaginationParams>, res: Response) => {
try {
const page = parseInt(req.query.page ?? '1', 10);
const limit = parseInt(req.query.limit ?? '20', 10);
const { role } = req.query;
const offset = (page - 1) * limit;
let query = db<User>('users').select('*');
if (role) query = query.where({ role });
const [users, countResult] = await Promise.all([
query.limit(limit).offset(offset),
db('users').count<{ count: string }>('id as count').first(),
]);
const total = parseInt(countResult?.count ?? '0', 10);
const response: PaginatedResponse<User> = {
data: users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
};
res.json(response);
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
res.status(500).json({ error: message });
}
});
export default router;
GPT-4o added: proper TypeScript generics, parallel database calls with Promise.all, safer error message extraction, and a totalPages field it inferred was useful. This is better than the original JavaScript.
Tool 3: GitHub Copilot for In-Editor Translation
Copilot’s inline translate works well for smaller functions. Open the target file, add a comment:
// Translate this Python function to idiomatic Go:
// def fibonacci(n: int) -> list[int]:
// if n <= 0: return []
// if n == 1: return [0]
// fib = [0, 1]
// while len(fib) < n:
// fib.append(fib[-1] + fib[-2])
// return fib
Copilot generates the Go implementation inline. This works well for function-by-function migration but doesn’t handle cross-file context.
Java to Kotlin Migration
The most common JVM migration uses IntelliJ’s built-in converter first, then AI to fix idioms:
# Step 1: Use IntelliJ's Java-to-Kotlin converter (Code > Convert Java File to Kotlin)
# It handles 80% of the conversion automatically
# Step 2: Use AI to fix non-idiomatic patterns
# Common patterns that need AI cleanup:
// IntelliJ produces (non-idiomatic):
fun getUser(id: String): User? {
if (id == null) return null
val user = repository.findById(id)
if (user == null) return null
return user
}
// Ask Claude to make it idiomatic Kotlin:
// "Make this Kotlin more idiomatic using Elvis operator, let, and takeIf"
// Claude output:
fun getUser(id: String?): User? = id?.let { repository.findById(it) }
Batch Translation Workflow
For translating entire directories:
# translate_codebase.py
import anthropic
from pathlib import Path
import sys
client = anthropic.Anthropic()
SOURCE_LANG = "Python"
TARGET_LANG = "Go"
SOURCE_EXT = ".py"
TARGET_EXT = ".go"
def translate_file(source_path: Path, target_path: Path) -> bool:
source_code = source_path.read_text()
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Translate this {SOURCE_LANG} file to idiomatic {TARGET_LANG}.
Rules:
- Use {TARGET_LANG} idioms and patterns, not {SOURCE_LANG} patterns
- Keep the same public API (function/method names and signatures where possible)
- Add proper error handling in the {TARGET_LANG} style
- Include package declaration and imports
- Do NOT include explanatory comments about translation choices
{SOURCE_LANG} source:
```{SOURCE_LANG.lower()}
{source_code}
Output only the {TARGET_LANG} code, no explanations.””” }] )
code = response.content[0].text
if f"```{TARGET_LANG.lower()}" in code:
code = code.split(f"```{TARGET_LANG.lower()}")[1].split("```")[0].strip()
target_path.parent.mkdir(parents=True, exist_ok=True)
target_path.write_text(code)
return True
Translate all files in a directory
source_dir = Path(sys.argv[1]) target_dir = Path(sys.argv[2])
for source_file in source_dir.rglob(f”*{SOURCE_EXT}”): if “pycache” in str(source_file): continue
relative = source_file.relative_to(source_dir)
target_file = target_dir / relative.with_suffix(TARGET_EXT)
print(f"Translating {source_file} → {target_file}")
translate_file(source_file, target_file) ```
Accuracy by Translation Type
| Translation | Claude | GPT-4o | Copilot |
|---|---|---|---|
| Python → TypeScript | Excellent | Excellent | Good |
| JavaScript → TypeScript | Good | Excellent | Good |
| Python → Go | Excellent | Good | Fair |
| Java → Kotlin | Good | Good | Good |
| TypeScript → Python | Good | Good | Fair |
Neither tool produces code that compiles without any review. Plan for 15-30% of translated files needing fixes before tests pass.
Related Reading
- Best AI IDE Features for Refactoring Class Hierarchies
- Claude vs ChatGPT for Refactoring Legacy Java to Kotlin
- How to Use AI Context Management for Large Refactoring
Built by theluckystrike — More at zovo.one