Azbox publica un cliente pequeño para Node.js, azbox-node. Esta guía cuenta qué hace realmente —menos de lo que parece— y cómo montar encima una capa de traducción que funcione.
Qué hace el cliente, y qué no
El paquete publicado expone exactamente una clase con un método:
export declare class AzboxClient {
constructor(options: { token: string; projectId: string; language: string; baseUrl?: string });
getKeywords(options?: { afterUpdatedAt?: Date }): Promise<AzboxKeyword[]>;
}
Eso es toda la superficie de la API. 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 ningún
translate(). Recibes la lista completa de keywords y haces la búsqueda tú. De eso va casi toda esta guía.
La credencial se pasa como
token, no comoapiKey. PasarapiKeylanzaAzboxClient: 'token' is required.
Requisitos
- Node.js 18 o superior — el cliente usa el
fetchglobal - Un proyecto de Azbox, con su Project ID y su API key
- Algunas keywords ya creadas en el panel
Paso 1: instalar
npm install azbox-node
Paso 2: traer las keywords
import { AzboxClient } from "azbox-node";
const client = new AzboxClient({
token: process.env.AZBOX_API_KEY,
projectId: process.env.AZBOX_PROJECT_ID,
language: "ES",
});
const keywords = await client.getKeywords();
// [{ id: "home.title", data: { translation: "Bienvenido", context, reference, ... } }]
Por debajo es una sola llamada a
GET https://api.azbox.io/v1/projects/:projectId/keywords?token=…&language=….
Paso 3: convertir la lista en un índice
getKeywords() devuelve un array. Llamarlo por cada string sería una petición HTTP por string, así que se trae una vez al arrancar y se construye un mapa:
// i18n.ts
import { AzboxClient } from "azbox-node";
type Dictionary = Map<string, string>;
const dictionaries = new Map<string, Dictionary>();
export async function loadLanguage(language: string): Promise<Dictionary> {
const client = new AzboxClient({
token: process.env.AZBOX_API_KEY!,
projectId: process.env.AZBOX_PROJECT_ID!,
language,
});
const keywords = await client.getKeywords();
const dict: Dictionary = new Map(
keywords
.filter((kw) => typeof kw.data.translation === "string")
.map((kw) => [kw.id, kw.data.translation as string]),
);
dictionaries.set(language, dict);
return dict;
}
export function t(language: string, key: string, params?: Record<string, string | number>) {
const value = dictionaries.get(language)?.get(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ó
getKeywords() acepta 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 client = new AzboxClient({
token: process.env.AZBOX_API_KEY!,
projectId: process.env.AZBOX_PROJECT_ID!,
language,
});
const changed = await client.getKeywords(since ? { afterUpdatedAt: since } : {});
const dict = dictionaries.get(language) ?? new Map();
for (const kw of changed) {
if (typeof kw.data.translation === "string") dict.set(kw.id, kw.data.translation);
}
dictionaries.set(language, dict);
lastSync.set(language, new Date());
return changed.length;
}
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
getKeywords() lanza si la respuesta no es 2xx y si el payload no es el esperado. Un refresco fallido no es fatal: ya tienes el diccionario anterior en memoria.
setInterval(() => {
refresh("ES").catch((err) => console.error("[i18n] refresco fallido:", 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.
Si necesitas crearlas programáticamente, el cliente no lo hace: tendrías que llamar directamente a la API HTTP de Azbox.