Skip to content

How to Add Translations to Your Node.js Application with Azbox: Complete Guide

Learn how to integrate translations into your Node.js application using the azbox-node package. This comprehensive guide covers API integration, translation management, caching, error handli

  • date icon

    January 20, 2025

  • 03 min read
How to Add Translations to Your Node.js Application with Azbox: Complete Guide

Azbox publishes a small Node.js client, azbox-node. This guide covers what it does, what it does not, and how to build a working translation layer on top of it.

Updated September 2026. Versions 0.1.0 and 0.1.1 of azbox-node did not work against the API. Use 0.2.0 or newer; the examples below are written for it.

What the client does, and what it does not

The package is one class:

export declare class AzboxClient {
  constructor(options: { apiKey: string; projectId: string; language: string; baseUrl?: string });
  getTranslations(options?: { afterUpdatedAt?: Date }): Promise<Record<string, string>>;
  getKeywords(options?: { afterUpdatedAt?: Date }): Promise<AzboxKeyword[]>;
}

In particular:

  • It only reads. There is no method to push or upload strings. Keywords are created in the Azbox panel, either one by one with Add Keyword or by importing a file (ARB, JSON/i18next, .xcstrings, XML, XLSX, CSV, YAML or PHP). See the Azbox quickstart.
  • It fetches one language at a time. language is set on the client, not per call. For several languages, create one client per language.
  • There is no t() function. You get a dictionary and do the lookup yourself. That is what most of this guide is about.

Prerequisites

  • Node.js 18 or newer — the client uses the global fetch
  • An Azbox project, with its Project ID and an API key (panel → Settings → API keys)
  • Some keywords already added in the panel

Step 1: Install

npm install azbox-node

Step 2: Fetch the translations

import { AzboxClient } from "azbox-node";

const client = new AzboxClient({
  apiKey: process.env.AZBOX_API_KEY,
  projectId: process.env.AZBOX_PROJECT_ID,
  language: "ES",
});

const translations = await client.getTranslations();
// { "home.title": "Bienvenido", … }

Under the hood this is a single call to GET https://api.azbox.io/v1/projects/:projectId/keywords. Keywords with no text yet in that language are left out. getKeywords() returns the raw response instead, [{ id, data: { keyword, translation, … } }], where the key is data.keyword and id is an internal identifier.

Step 3: Keep a dictionary per language

Calling the API per string would mean one HTTP request per string, so fetch once at startup:

// i18n.ts
import { AzboxClient } from "azbox-node";

type Dictionary = Record<string, string>;
const dictionaries = new Map<string, Dictionary>();

function clientFor(language: string) {
  return new AzboxClient({
    apiKey: process.env.AZBOX_API_KEY!,
    projectId: process.env.AZBOX_PROJECT_ID!,
    language,
  });
}

export async function loadLanguage(language: string): Promise<Dictionary> {
  const dict = await clientFor(language).getTranslations();
  dictionaries.set(language, dict);
  return dict;
}

export function t(language: string, key: string, params?: Record<string, string | number>) {
  const value = dictionaries.get(language)?.[key];
  if (value === undefined) return key; // fall back to the key, never to an empty string
  if (!params) return value;
  return value.replace(/\{(\w+)\}/g, (m, p) => String(params[p] ?? m));
}

Interpolation is your responsibility: the API returns the string as stored, placeholders included.

Step 4: Load at startup, not per request

const LANGUAGES = ["EN", "ES", "FR"];

await Promise.all(LANGUAGES.map(loadLanguage));

app.listen(3000);

If a language fails to load, decide deliberately whether to start anyway with a fallback or to fail loudly. Starting with a half-empty dictionary and no log is how you end up shipping raw keys to users.

Step 5: Refresh only what changed

Both methods accept afterUpdatedAt, which maps to the afterUpdatedAtStr query parameter. Use it to poll for changes without refetching everything:

const lastSync = new Map<string, Date>();

export async function refresh(language: string) {
  const since = lastSync.get(language);
  const startedAt = new Date();

  const changed = await clientFor(language).getTranslations(since ? { afterUpdatedAt: since } : {});
  dictionaries.set(language, { ...dictionaries.get(language), ...changed });

  lastSync.set(language, startedAt);
  return Object.keys(changed).length;
}

The sync time is taken before the request, so a string edited while the request is in flight is picked up on the next refresh instead of being skipped.

This is what makes over-the-air updates work on the server: correct a string in the panel, and the next refresh picks it up without a deploy.

Step 6: Express middleware

import express from "express";
import { t } from "./i18n";

const app = express();

app.use((req, res, next) => {
  const header = req.headers["accept-language"] ?? "";
  const lang = String(header).slice(0, 2).toUpperCase();
  req.language = ["EN", "ES", "FR"].includes(lang) ? lang : "EN";
  next();
});

app.get("/api/welcome", (req, res) => {
  res.json({ message: t(req.language, "home.title", { name: "Ada" }) });
});

Error handling

The client throws AzboxError, with status (401 for a wrong or revoked key, 403 for a key without access to the project) and the API’s detail. A project with no keywords yet is not an error: you get an empty result. Treat a failed refresh as non-fatal — you already have the previous dictionary in memory:

setInterval(() => {
  refresh("ES").catch((err) => console.error("[i18n] refresh failed:", err.status, err.message));
}, 5 * 60 * 1000);

Where the strings come from

Worth repeating, because it is the part people expect to automate and cannot: keywords are created in the panel, not from code. The usual flow is to import your existing en.json (or ARB, or XML) once, translate in Azbox, and let your application pull the results with the client above. The API is read-only for keywords, so there is no endpoint to create them programmatically.

If what you need is translation files on disk rather than a dictionary in memory, the AZbox CLI writes them without any code.

Blog

Latest Posts

Discover our latest articles and updates.

Gaming Localization Services: The 2026 Guide to Taking Your Game Global
date icon

February 21, 2026

04 min read

Gaming Localization Services: The 2026 Guide to Taking Your Game Global

Going global with your game isn’t just about translating text—it’s about making players in every market feel like the ga

Read More
App Localization Software: The 2026 Guide to Choosing and Using the Right Tool
date icon

February 20, 2026

04 min read

App Localization Software: The 2026 Guide to Choosing and Using the Right Tool

If you're scaling your app beyond one language, app localization software is the lever that turns "we should go glob

Read More
Translation for SaaS: Complete Guide to Localizing Your Product
date icon

February 15, 2026

04 min read

Translation for SaaS: Complete Guide to Localizing Your Product

Translation for SaaS is no longer optional. If you want to grow beyond your home market, you need a clear strategy t

Read More
Call to action background

Start Global Growth Today

Upload your language files, get them translated, and keep every locale in sync as your product keeps changing.

Get Started - It's Free