Translations
Tutorials
How to Bulk Upload Translations

How to Bulk Upload Translations

In application development, sometimes you need automation for translations such as bulk upload. You can do bulk upload translations in ROQ by using the Translations API and you can use the function upsertTranslationKeys() (opens in a new tab) for this purpose.

For detailed instructions on how to use the upsertTranslationKeys() method, please refer to the Translations API documentation.

You can use the upsertTranslationKeys() method and Node.js-based parser for bulk upload translations. For instance, you can write the translations in CSV or JSON format and then parse them before uploading them to the ROQ console.

File Upload

CSV

The format of the CSV file could be anything. For this tutorial, we will use a translation.csv file in this content format:

key,description,en-US,de-DE
greeting,Text for greeting,Hello,Hallo
farewell,Text for farewell,Goodbye,Auf Wiedersehen

The key is the translation key, description is the key description, en-US is the translation for English, and de-DE for the German language translation.

Before running the code example below, you need creds from ROQ Console and then set it in .env

ROQ_PLATFORM_URL=
ROQ_API_KEY=
ROQ_ENVIRONMENT_ID=
FILE_PATH=translation.csv

The creds are the environment variables from ROG Console. Go to Project DetailsSettings, choose the environment, and Copy Env File for basic setup without authentication.

// csvupload.js
import 'dotenv/config';
import fs from "fs";
import path from "path";
import csv from "csv-parser";
import { Platform } from "@roq/nodejs";
 
// Initialize a ROQ client using environment variables
const roqClient = new Platform({
	apiKey: process.env.ROQ_API_KEY,
	environmentId: process.env.ROQ_ENVIRONMENT_ID,
	host: process.env.ROQ_PLATFORM_URL
});
 
// Function to read a CSV file and return an array of translation rows
const readCsv = async (filePath) => {
	const results = [];
	const readStream = fs.createReadStream(filePath);
	readStream.on("error", (err) => {
		console.error(err);
		readStream.destroy();
	});
 
    // Read the CSV file row by row
	for await (const row of readStream.pipe(csv())) {
		results.push(row);
	}
	return results;
};
 
// Function to split an array into smaller chunks
const chunkArray = (arr, chunkSize = 50) => {
	const result = [];
	for (let i = 0; i < arr.length; i += chunkSize) {
		result.push(arr.slice(i, i + chunkSize));
	}
	return result;
};
 
// Function to get the absolute path of a file
const getAbsolutePath = (filepath) => {
	const isAbsolute = path.isAbsolute(filepath);
	if (!isAbsolute) {
		return path.join(process.cwd(), filepath);
	}
	return filepath;
};
 
// Function to map rows to translation keys
const mapRowsToTranslationKeys = (rows) => {
	return rows.map((row) => {
		const { key, ...locales } = row;
		const translations = Object.keys(locales).reduce(
			(acc, locale) =>
				locales[locale]?.trim()
					? [...acc, { locale, value: locales[locale].replace(/\\n/g, "\n") }]
					: acc,
			[]
		);
		return { key, translations };
	});
};
 
// Main function to import translations from a CSV file
const importTranslations = async () => {
	const filePath = process.env.FILE_PATH;
	if (!filePath) throw new Error("FILE_PATH env is not set");
 
    // Read the CSV file and map the rows to translation keys
	const csvRows = await readCsv(getAbsolutePath(process.env.FILE_PATH));
	const translationKeys = mapRowsToTranslationKeys(csvRows);
 
	// Split the translation keys into chunks and process each chunk
	const chunkSize = Number(process.env.TRANSLATION_LOAD_CHUNK_SIZE) || 50;
	const chunks = chunkArray(translationKeys, chunkSize);
 
	for (let i = 0; i < chunks.length; i++) {
		const translationsChunk = chunks[i];
		const startRow = i * chunkSize + 1;
		const endRow = startRow + translationsChunk.length - 1;
 
		 // Log the processing status
		console.log(`Processing chunk of rows ${startRow} - ${endRow} (chunk ${i + 1} of ${chunks.length})`);
		console.dir({ translationsChunk }, { depth: null });
 
		 // Make the API request to upload the translation keys
		await roqClient.asSuperAdmin().upsertTranslationKeys({ translationKeys: translationsChunk });
	}
};
 
// Execute the main function and handle any errors
(async () => {
	await importTranslations();
})().catch((err) => {
	console.log("*******-------Error-------*******");
	console.error(err);
	process.exit(1);
});

Make sure to install all node.js dependencies, file translation.csv is in the same directory, and then run:

node csvupload.js

If you check in the ROQ Console, you will see translation data are already saved.

ROQ Console translation

JSON

If you prefer a JSON file for the translation, you customize the code above. For example, the following is a JSON translation file:

[
	{
		"key": "greeting",
		"description": "Text for greeting",
		"en-US": "Hello",
		"de-DE": "Hallo"
	},
	{
		"key": "conversation",
		"description": "Text for conversation",
		"en-US": "How are you?",
		"de-DE": "Wie geht es dir?"
	},
	{
		"key": "farewell",
		"description": "Text for farewell",
		"en-US": "Goodbye",
		"de-DE": "Auf Wiedersehen"
	}
]

To read the data in a JSON file we can easily use the built-in JSON.parse() method.

const readJson = (filePath) => {
  const data = fs.readFileSync(filePath, 'utf8');
  const results = JSON.parse(data);
  return results;
};

and then we can change our previous code, and use readJson() function to parse the JSON file.

// Import necessary modules
import 'dotenv/config';
import fs from "node:fs";
import path from "node:path";
 
import { Platform } from '@roq/nodejs';
 
// Initialize a ROQ client using environment variables
const roqClient = new Platform({
	apiKey: process.env.ROQ_API_KEY,
	environmentId: process.env.ROQ_ENVIRONMENT_ID,
	host: process.env.ROQ_PLATFORM_URL
});
 
// Function to read a JSON file and return an array of translation rows
const readJson = (filePath) => {
	const data = fs.readFileSync(filePath, 'utf8');
	const results = JSON.parse(data);
	return results;
};
 
// Function to split an array into smaller chunks
const chunkArray = (arr, chunkSize = 50) => {
	const result = [];
	for (let i = 0; i < arr.length; i += chunkSize) {
		result.push(arr.slice(i, i + chunkSize));
	}
	return result;
};
 
// Function to get the absolute path of a file
const getAbsolutePath = (filepath) => {
	const isAbsolute = path.isAbsolute(filepath);
	if (!isAbsolute) {
		return path.join(process.cwd(), filepath);
	}
	return filepath;
};
 
// Function to map rows to translation keys
const mapRowsToTranslationKeys = (rows) => {
	return rows.map((row) => {
		const { key, ...locales } = row;
		const translations = Object.keys(locales).reduce(
			(acc, locale) =>
				locales[locale]?.trim()
					? [...acc, { locale, value: locales[locale].replace(/\\n/g, "\n") }]
					: acc,
			[]
		);
		return { key, translations };
	});
};
 
// Main function to import translations from a JSON file
const importTranslations = async () => {
	const filePath = process.env.FILE_PATH;
	if (!filePath) throw new Error("FILE_PATH env is not set");
 
	// Read the JSON file and map the rows to translation keys
	const jsonRows = readJson(getAbsolutePath(process.env.FILE_PATH));
	const translationKeys = mapRowsToTranslationKeys(jsonRows);
 
	// Split the translation keys into chunks and process each chunk
	const chunkSize = Number(process.env.TRANSLATION_LOAD_CHUNK_SIZE) || 50;
	const chunks = chunkArray(translationKeys, chunkSize);
 
	for (let i = 0; i < chunks.length; i++) {
		const translationsChunk = chunks[i];
		const startRow = i * chunkSize + 1;
		const endRow = startRow + translationsChunk.length - 1;
 
		// Log the processing status
		console.log(`Processing chunk of rows ${startRow} - ${endRow} (chunk ${i + 1} of ${chunks.length})`);
		console.dir({ translationsChunk }, { depth: null });
 
		// Make the API request to upload the translation keys
		await roqClient.asSuperAdmin().upsertTranslationKeys({ translationKeys: translationsChunk });
	}
};
 
// Execute the main function and handle any errors
(async () => {
	await importTranslations();
})().catch((err) => {
	console.log("*******-------Error-------*******");
	console.error(err);
	process.exit(1);
});

You can check again in the ROQ console after running the code above.

roq console json