At PostNitro, we're committed to making our NextJS-based platform accessible to users worldwide. A crucial part of this mission is providing our interface in multiple languages. Today, we're excited to share how we've automated our i18n (internationalization) process using react-intl, significantly reducing manual effort and speeding up our localization workflow.
Our Tech Stack for i18n
PostNitro is built on Next.js, a robust React framework that enables us to create fast, SEO-friendly web applications. We've chosen react-intl, a comprehensive i18n solution for React applications for our internationalization needs. This combination allows us to manage translations efficiently while maintaining Next.js' performance benefits.
The Challenge
Despite the robust i18n support provided by react-intl, managing translations for multiple languages was still time-consuming and error-prone. We needed a solution that could:
- Extract translatable strings from our Next.js codebase
- Automatically translate these strings into various languages
- Generate language-specific JSON files compatible with react-intl
Our Automated Solution
We've developed a two-step process that has dramatically improved our i18n workflow:
Step 1: Extracting Translatable Strings
We start by running:
npm run extract:i18n
This command scans our Next.js components and pages, extracting all translatable strings (marked with react-intl's <FormattedMessage> components or intl.formatMessage() calls) into our base en.json file. It ensures that we capture all the latest strings that need translation.
Step 2: Automated Translation
Once we have our updated en.json, we use a Node.js script to automate the translation process. Here's the core of our translation script:
const fs = require('fs').promises;
const path = require('path');
const { Translate } = require('@google-cloud/translate').v2;
const keyFilePath = path.join(__dirname, 'keyfile.json');
const translate = new Translate({ keyFilename: keyFilePath });
const sourceDir = path.join(__dirname, 'content/locales');
const sourceFile = path.join(sourceDir, 'en.json');
const languages = ['de', 'fr', 'es', 'it'];
const BATCH_SIZE = 100; // Adjust this value based on your needs and API limits
async function translateBatch(batch, targetLang) {
const [translations] = await translate.translate(batch, targetLang);
return Array.isArray(translations) ? translations : [translations];
}
async function translateAllValues(values, targetLang) {
let allTranslations = [];
for (let i = 0; i < values.length; i += BATCH_SIZE) {
const batch = values.slice(i, i + BATCH_SIZE);
const batchTranslations = await translateBatch(batch, targetLang);
allTranslations = allTranslations.concat(batchTranslations);
console.log(`Translated batch ${i / BATCH_SIZE + 1}`);
}
return allTranslations;
}
async function main() {
try {
const data = await fs.readFile(sourceFile, 'utf8');
const jsonData = JSON.parse(data);
for (const lang of languages) {
console.log(`Translating to ${lang}...`);
const keys = Object.keys(jsonData);
const values = Object.values(jsonData);
const translatedValues = await translateAllValues(values, lang);
const translations = keys.reduce((acc, key, index) => {
acc[key] = translatedValues[index];
return acc;
}, {});
const targetFile = path.join(sourceDir, `${lang}.json`);
await fs.writeFile(targetFile, JSON.stringify(translations, null, 2), 'utf8');
console.log(`${lang}.json has been created successfully.`);
}
} catch (err) {
console.error(`An error occurred: ${err}`);
}
}
main();
This script does the following:
- Reads our en.json file
- Translates the content into specified languages (German, French, Spanish, and Italian in this example)
- Generates language-specific JSON files (e.g., de.json, fr.json) that are compatible with react-intl
We translate in batches of 100 strings at a time to handle large files and API limitations.
The Benefits
This automated approach has brought several significant benefits to our Next.js and react-intl-based i18n process:
- Time Savings: What used to take days of manual work now happens in minutes. Our developers can focus on building features rather than managing translations.
- Consistency: Automated translation ensures consistency across our platform, reducing the risk of discrepancies with manual translation.
- Scalability: Adding new languages is as simple as adding a new language code to our script. We can quickly scale our language support as our user base grows.
- Up-to-date Translations: With the ease of running these scripts, we can update our translations more frequently, ensuring our international users always have the latest content.
- Cost-Effective: By reducing manual translation work, we've significantly cut down on localization costs.
- Seamless Integration: The generated JSON files integrate seamlessly with react-intl, allowing us to leverage all its features in our Next.js application.
Looking Ahead
While this automated system has dramatically improved our i18n process, we recognize that machine translation could be better. Our next steps include:
- Implementing a review process for machine-translated content
- Fine-tuning translations for industry-specific terminology
- Exploring ways to preserve formatting and variables in translated strings
- Optimizing our Next.js application to load translations efficiently
By combining automation with human expertise, we're creating a robust, efficient, and scalable i18n system that will serve PostNitro and our users for years to come. This system leverages the power of Next.js and react-intl.
About Seerat Awan
Seerat Awan, Co-Founder & CTO at PostNitro Inc. My role is to be the engineering powerhouse architecting the core capabilities of our platform.