Azbox publica un cliente pequeño para Node.js, azbox-node. Esta guía cuenta qué hace, qué no hace y cómo montar encima una capa de traducción que funcione.
Actualizado en septiembre de 2026. Las versiones 0.1.0 y 0.1.1 de
azbox-nodeno funcionaban contra la API. Usa la 0.2.0 o superior; los ejemplos de abajo están escritos para ella.
Qué hace el cliente, y qué no
El paquete es una clase:
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[]>;
}
En concreto:
- Solo lee. No hay ningún método para subir strings. Las keywords se crean en el panel de Azbox, una a una con Add Keyword o importando un archivo (ARB, JSON/i18next, .xcstrings, XML, XLSX, CSV, YAML o PHP). Ver la guía rápida de Azbox.
- Trae un idioma cada vez.
languagese fija en el cliente, no por llamada. Para varios idiomas, un cliente por idioma. - No existe ninguna función
t(). Recibes un diccionario y haces la búsqueda tú. De eso va casi toda esta guía.
Requisitos
- Node.js 18 o superior — el cliente usa el
fetchglobal - Un proyecto de Azbox, con su Project ID y una API key (panel → Settings → API keys)
- Algunas keywords ya creadas en el panel
Paso 1: instalar
npm install azbox-node
Paso 2: traer las traducciones
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", … }
Por debajo es una sola llamada a GET https://api.azbox.io/v1/projects/:projectId/keywords. Las keywords que aún no tienen texto en ese idioma se quedan fuera. getKeywords() devuelve en cambio la respuesta tal cual, [{ id, data: { keyword, translation, … } }], donde la clave es data.keyword y el id es un identificador interno.
Paso 3: un diccionario por idioma
Llamar a la API por cada string sería una petición HTTP por string, así que se trae una vez al arrancar:
// 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; // devolver la clave, nunca una cadena vacía
if (!params) return value;
return value.replace(/\{(\w+)\}/g, (m, p) => String(params[p] ?? m));
}
La interpolación corre de tu cuenta: la API devuelve la cadena tal como está guardada, con sus placeholders.
Paso 4: cargar al arrancar, no por petición
const LANGUAGES = ["EN", "ES", "FR"];
await Promise.all(LANGUAGES.map(loadLanguage));
app.listen(3000);
Si un idioma falla al cargar, decide a conciencia si arrancar igualmente con un fallback o fallar de forma ruidosa. Arrancar con un diccionario a medias y sin log es como se acaba enseñando claves crudas a los usuarios.
Paso 5: refrescar solo lo que cambió
Los dos métodos aceptan afterUpdatedAt, que se traduce al parámetro afterUpdatedAtStr. Sirve para consultar cambios sin volver a traerlo todo:
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;
}
La hora de sincronización se toma antes de la petición: así, un texto editado mientras la petición está en vuelo se recoge en el siguiente refresco en vez de perderse.
Esto es lo que hace posibles las actualizaciones over-the-air en el servidor: corriges un texto en el panel y el siguiente refresco lo recoge sin desplegar.
Paso 6: middleware de Express
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" }) });
});
Manejo de errores
El cliente lanza AzboxError, con status (401 si la clave es incorrecta o está revocada, 403 si no tiene acceso al proyecto) y el detail de la API. Un proyecto sin keywords todavía no es un error: recibes un resultado vacío. Un refresco fallido no es fatal: ya tienes el diccionario anterior en memoria.
setInterval(() => {
refresh("ES").catch((err) => console.error("[i18n] refresco fallido:", err.status, err.message));
}, 5 * 60 * 1000);
De dónde salen los strings
Conviene repetirlo, porque es la parte que todo el mundo espera automatizar y no se puede: las keywords se crean en el panel, no desde el código. El flujo habitual es importar una vez tu en.json existente (o ARB, o XML), traducir en Azbox y dejar que la aplicación se traiga el resultado con el cliente de arriba. La API es de solo lectura para keywords, así que no hay forma de crearlas por programa.
Si lo que necesitas son ficheros de traducción en disco y no un diccionario en memoria, el CLI de AZbox los escribe sin código.