Script to generate i18n files
Posted on 5 Nov 2025 by Claudio Santini
With LLMs it has become much easier to generate localized versions of your application's text.
On my NextJS projects, I typically use the next-intl module for localization.
Next-intl requires a folder with JSON files for each supported locale, e.g. en.json, es.json, fr.json etc.
This script generates those files automatically starting from the en.json file, using the Gemini API to translate the text.
import {promises as fs} from 'fs';
import path from 'path';
const supportedLocales = ['en', 'es', 'fr', 'de', 'it', 'ja']; // Edit this list as needed
const googleGenAI = new GoogleGenAI({apiKey: process.env.GOOGLE_API_KEY});
function errorAndExit(message: string): never {
console.error(message);
process.exit(1);
}
function findJsonInText(text: string): any | null {
const jsonStart = text.indexOf('{');
const jsonEnd = text.lastIndexOf('}');
if (jsonStart === -1 || jsonEnd === -1 || jsonEnd <= jsonStart) {
return null;
}
const jsonString = text.substring(jsonStart, jsonEnd + 1);
try {
return JSON.parse(jsonString);
} catch (e) {
console.error('Failed to parse JSON:', e);
return null;
}
}
async function translateWithAi(text: string, targetLocale: string): Promise<any> {
const prompt = `Create the i18n from english to ${targetLocale}. Output only the new JSON, no commentary.\n${text}`;
const response = await googleGenAI.models.generateContent({
model: 'gemini-2.5-flash', // 'gemini-2.5-pro' if you feel like trying a more powerful model
contents: prompt,
config: {}
});
if (!response.candidates || response.candidates.length === 0 || !response.candidates[0].content) {
throw new Error('No translation candidates received from Gemini API');
}
const parts = response.candidates[0].content.parts;
if (!parts || parts.length === 0) return errorAndExit('No translation parts received from Gemini API');
const responseText = parts.map(part => part.text).join('');
const translatedJson = findJsonInText(responseText);
if (!translatedJson) {
throw new Error('Failed to extract JSON from Gemini response');
}
return translatedJson;
}
export async function updateI18n(messagesDir: string) {
try {
const sourceFile = path.join(messagesDir, 'en.json');
const sourceContent = await fs.readFile(sourceFile, 'utf-8');
console.log('sourceContent:', sourceContent);
const sourceJson = JSON.parse(sourceContent);
const targetLocales = supportedLocales.filter(locale => locale !== 'en');
for (const locale of targetLocales) {
console.log(`Generating translation for locale: ${locale}`);
const translatedJson = await translateWithAi(JSON.stringify(sourceJson), locale);
const targetFile = path.join(messagesDir, `${locale}.json`);
await fs.writeFile(targetFile, JSON.stringify(translatedJson, null, 2));
console.log(`Successfully wrote translations to ${targetFile}`);
}
} catch (error) {
console.error('Failed to update i18n files:', error);
}
}
const args = process.argv.slice(2);
if (args.length < 1) return errorAndExit('Usage: tsx update-i18n.ts <messagesDir>');
const messagesDir = args[0];
updateI18n(messagesDir);
To run it, pass your GOOGLE_API_KEY in the environment and run the script with the path to your messages directory:
GOOGLE_API_KEY="your_api_key" tsx update-i18n.ts ./path/to/messages/folder