Skip to main content

Language-Aware API Server

StatusFlow includes built-in support for bilingual responses (Spanish and English). This example shows how to build an API that automatically responds in the user’s preferred language.

Complete Bilingual Server

import express, { Request, Response, NextFunction } from 'express';
import { 
  StatusFlow, 
  StatusFlowCodes,
  StatusFlowLang,
  statusFlowMiddleware 
} from 'status-flow';

const app = express();
app.use(express.json());

// Language detection middleware
const detectLanguage = (req: Request, res: Response, next: NextFunction) => {
  // Priority 1: x-lang header
  let lang = req.headers['x-lang'] as StatusFlowLang;
  
  // Priority 2: Accept-Language header
  if (!lang) {
    const acceptLanguage = req.headers['accept-language'];
    if (acceptLanguage) {
      lang = acceptLanguage.startsWith('en') ? 'en' : 'es';
    }
  }
  
  // Priority 3: Query parameter
  if (!lang && req.query.lang) {
    lang = req.query.lang as StatusFlowLang;
  }
  
  // Default to Spanish
  (req as any).lang = (lang === 'en' || lang === 'es') ? lang : 'es';
  next();
};

app.use(detectLanguage);

// Helper to get language from request
const getLang = (req: Request): StatusFlowLang => {
  return (req as any).lang || 'es';
};

// Mock data store
interface Product {
  id: string;
  name: { en: string; es: string };
  description: { en: string; es: string };
  price: number;
  stock: number;
}

const products: Product[] = [
  {
    id: '1',
    name: { en: 'Laptop', es: 'Portátil' },
    description: { 
      en: 'High-performance laptop',
      es: 'Portátil de alto rendimiento'
    },
    price: 999.99,
    stock: 10
  },
  {
    id: '2',
    name: { en: 'Mouse', es: 'Ratón' },
    description: { 
      en: 'Wireless ergonomic mouse',
      es: 'Ratón inalámbrico ergonómico'
    },
    price: 29.99,
    stock: 50
  },
  {
    id: '3',
    name: { en: 'Keyboard', es: 'Teclado' },
    description: { 
      en: 'Mechanical gaming keyboard',
      es: 'Teclado mecánico para gaming'
    },
    price: 149.99,
    stock: 0
  }
];

// Localized messages
const messages = {
  productNotFound: {
    en: 'Product not found',
    es: 'Producto no encontrado'
  },
  outOfStock: {
    en: 'Product is out of stock',
    es: 'Producto sin stock'
  },
  invalidQuantity: {
    en: 'Invalid quantity requested',
    es: 'Cantidad solicitada inválida'
  },
  insufficientStock: {
    en: 'Insufficient stock available',
    es: 'Stock insuficiente disponible'
  },
  orderCreated: {
    en: 'Order created successfully',
    es: 'Pedido creado exitosamente'
  }
};

// API Routes

// Welcome endpoint with language detection
app.get('/api/welcome', (req, res) => {
  const lang = getLang(req);
  
  res.json(
    StatusFlow({
      code: StatusFlowCodes.OK,
      lang,
      extra: {
        greeting: lang === 'en' 
          ? 'Welcome to our Bilingual API!' 
          : '¡Bienvenido a nuestra API Bilingüe!',
        detectedLanguage: lang,
        supportedLanguages: ['en', 'es']
      }
    })
  );
});

// Get all products (with localized content)
app.get('/api/products', (req, res) => {
  const lang = getLang(req);
  
  const localizedProducts = products.map(p => ({
    id: p.id,
    name: p.name[lang],
    description: p.description[lang],
    price: p.price,
    stock: p.stock,
    available: p.stock > 0
  }));
  
  res.json(
    StatusFlow({
      code: StatusFlowCodes.OK,
      lang,
      extra: {
        products: localizedProducts,
        total: localizedProducts.length
      }
    })
  );
});

// Get single product
app.get('/api/products/:id', (req, res, next) => {
  const lang = getLang(req);
  const product = products.find(p => p.id === req.params.id);
  
  if (!product) {
    return next({
      code: StatusFlowCodes.NOT_FOUND,
      message: messages.productNotFound[lang],
      extra: { productId: req.params.id }
    });
  }
  
  res.json(
    StatusFlow({
      code: StatusFlowCodes.OK,
      lang,
      extra: {
        product: {
          id: product.id,
          name: product.name[lang],
          description: product.description[lang],
          price: product.price,
          stock: product.stock,
          available: product.stock > 0
        }
      }
    })
  );
});

// Create order (with validation and localized errors)
app.post('/api/orders', (req, res, next) => {
  const lang = getLang(req);
  const { productId, quantity } = req.body;
  
  // Validate quantity
  if (!quantity || quantity < 1) {
    return next({
      code: StatusFlowCodes.BAD_REQUEST,
      message: messages.invalidQuantity[lang],
      extra: { field: 'quantity', provided: quantity }
    });
  }
  
  // Find product
  const product = products.find(p => p.id === productId);
  if (!product) {
    return next({
      code: StatusFlowCodes.NOT_FOUND,
      message: messages.productNotFound[lang],
      extra: { productId }
    });
  }
  
  // Check stock
  if (product.stock === 0) {
    return next({
      code: StatusFlowCodes.CONFLICT,
      message: messages.outOfStock[lang],
      extra: { 
        productId,
        productName: product.name[lang]
      }
    });
  }
  
  if (product.stock < quantity) {
    return next({
      code: StatusFlowCodes.CONFLICT,
      message: messages.insufficientStock[lang],
      extra: { 
        requested: quantity,
        available: product.stock,
        productName: product.name[lang]
      }
    });
  }
  
  // Create order
  const order = {
    id: `order-${Date.now()}`,
    productId,
    productName: product.name[lang],
    quantity,
    unitPrice: product.price,
    total: product.price * quantity,
    createdAt: new Date().toISOString()
  };
  
  // Update stock (in real app, this would be in a transaction)
  product.stock -= quantity;
  
  res.status(201).json(
    StatusFlow({
      code: StatusFlowCodes.CREATED,
      lang,
      overrideMessage: messages.orderCreated[lang],
      extra: { order }
    })
  );
});

// Search products
app.get('/api/search', (req, res, next) => {
  const lang = getLang(req);
  const query = (req.query.q as string)?.toLowerCase();
  
  if (!query) {
    return next({
      code: StatusFlowCodes.BAD_REQUEST,
      message: lang === 'en' 
        ? 'Search query is required'
        : 'Se requiere una consulta de búsqueda',
      extra: { parameter: 'q' }
    });
  }
  
  const results = products.filter(p => {
    const name = p.name[lang].toLowerCase();
    const desc = p.description[lang].toLowerCase();
    return name.includes(query) || desc.includes(query);
  }).map(p => ({
    id: p.id,
    name: p.name[lang],
    description: p.description[lang],
    price: p.price,
    stock: p.stock
  }));
  
  res.json(
    StatusFlow({
      code: StatusFlowCodes.OK,
      lang,
      extra: {
        query,
        results,
        count: results.length
      }
    })
  );
});

// Language preference endpoint
app.get('/api/language-info', (req, res) => {
  const lang = getLang(req);
  
  res.json({
    currentLanguage: lang,
    detectionMethod: req.headers['x-lang'] ? 'x-lang header' 
      : req.query.lang ? 'query parameter' 
      : req.headers['accept-language'] ? 'Accept-Language header' 
      : 'default',
    supportedLanguages: [
      { code: 'en', name: 'English' },
      { code: 'es', name: 'Español' }
    ],
    examples: {
      header: 'x-lang: en',
      query: '?lang=es',
      acceptLanguage: 'Accept-Language: en-US,en;q=0.9'
    }
  });
});

// Enhanced statusFlowMiddleware with language detection
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
  const lang = getLang(req);
  
  if (err && typeof err.code === 'number') {
    const response = StatusFlow({
      code: err.code,
      lang,
      extra: err.extra || {},
      overrideMessage: err.message || undefined,
    });
    res.status(err.code).json(response);
  } else {
    const response = StatusFlow({
      code: 500,
      lang,
      overrideMessage: lang === 'en' 
        ? 'Internal server error'
        : 'Error interno del servidor',
      extra: { originalError: err },
    });
    res.status(500).json(response);
  }
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Bilingual API running on http://localhost:${PORT}`);
  console.log('Supported languages: English (en), Spanish (es)');
  console.log('\nTry these examples:');
  console.log('  English: curl -H "x-lang: en" http://localhost:3000/api/welcome');
  console.log('  Spanish: curl -H "x-lang: es" http://localhost:3000/api/welcome');
});

export default app;

Testing Language Detection

1

Start the Server

npx tsx bilingual-server.ts
2

Test Different Language Headers

curl -H "x-lang: en" http://localhost:3000/api/welcome
3

Test Localized Content

curl -H "x-lang: en" http://localhost:3000/api/products
4

Test Localized Errors

curl -H "x-lang: en" http://localhost:3000/api/products/999

Example Responses

{
  "success": true,
  "message": "HTTP 200 - OK",
  "code": 200,
  "greeting": "Welcome to our Bilingual API!",
  "detectedLanguage": "en",
  "supportedLanguages": ["en", "es"],
  "info": {
    "name": "OK",
    "category": "Successful Response",
    "description": "The request was successful",
    "possibleCauses": [
      "Operation completed successfully"
    ]
  }
}
{
  "success": true,
  "message": "HTTP 200 - Correcto",
  "code": 200,
  "greeting": "¡Bienvenido a nuestra API Bilingüe!",
  "detectedLanguage": "es",
  "supportedLanguages": ["en", "es"],
  "info": {
    "name": "Correcto",
    "category": "Respuesta exitosa",
    "description": "La solicitud se completó exitosamente",
    "possibleCauses": [
      "Operación completada correctamente"
    ]
  }
}
{
  "success": false,
  "message": "Product not found",
  "code": 404,
  "productId": "999",
  "info": {
    "name": "Not Found",
    "category": "Client Error",
    "description": "The requested resource does not exist",
    "possibleCauses": [
      "Incorrect URL",
      "Resource deleted or moved"
    ]
  }
}
{
  "success": false,
  "message": "Producto no encontrado",
  "code": 404,
  "productId": "999",
  "info": {
    "name": "No encontrado",
    "category": "Error del cliente",
    "description": "El recurso solicitado no existe",
    "possibleCauses": [
      "URL incorrecta",
      "Recurso eliminado o movido"
    ]
  }
}

Language Detection Strategy

StatusFlow detects the preferred language using this priority order:
1

x-lang Header (Highest Priority)

curl -H "x-lang: en" http://localhost:3000/api/welcome
Explicit language selection via custom header.
2

Accept-Language Header

curl -H "Accept-Language: en-US,en;q=0.9" http://localhost:3000/api/welcome
Standard browser language preference.
3

Query Parameter

curl "http://localhost:3000/api/welcome?lang=en"
Useful for testing and direct URL sharing.
4

Default to Spanish (Lowest Priority)

If no language is specified, defaults to Spanish (es).

Best Practices for Bilingual APIs

Store all user-facing messages in a centralized object:
const messages = {
  productNotFound: {
    en: 'Product not found',
    es: 'Producto no encontrado'
  },
  // ... more messages
};

// Use throughout your app
return next({
  code: StatusFlowCodes.NOT_FOUND,
  message: messages.productNotFound[lang]
});
Store multilingual content in your database:
interface Product {
  id: string;
  name: { en: string; es: string };
  description: { en: string; es: string };
  // ... other fields
}

// Return localized version
const localizedProduct = {
  id: product.id,
  name: product.name[lang],
  description: product.description[lang]
};
Use middleware to detect language once:
app.use((req, res, next) => {
  const lang = req.headers['x-lang'] 
    || detectFromAcceptLanguage(req) 
    || 'es';
  (req as any).lang = lang;
  next();
});
Provide an endpoint that explains language options:
app.get('/api/language-info', (req, res) => {
  res.json({
    supportedLanguages: ['en', 'es'],
    detectionMethod: 'x-lang header, Accept-Language, or query param',
    examples: {
      header: 'x-lang: en',
      query: '?lang=es'
    }
  });
});

Language Middleware Pattern

Create reusable middleware to detect and attach language to requests:
import { Request, Response, NextFunction } from 'express';
import { StatusFlowLang } from 'status-flow';

export const detectLanguage = (req: Request, res: Response, next: NextFunction) => {
  let lang: StatusFlowLang = 'es'; // default
  
  // Check x-lang header
  const xLang = req.headers['x-lang'];
  if (xLang === 'en' || xLang === 'es') {
    lang = xLang;
  }
  // Check Accept-Language
  else if (req.headers['accept-language']?.startsWith('en')) {
    lang = 'en';
  }
  // Check query parameter
  else if (req.query.lang === 'en' || req.query.lang === 'es') {
    lang = req.query.lang as StatusFlowLang;
  }
  
  (req as any).lang = lang;
  next();
};

export const getLang = (req: Request): StatusFlowLang => {
  return (req as any).lang || 'es';
};

Integration with Frontend

// Detect browser language
const userLang = navigator.language.startsWith('es') ? 'es' : 'en';

fetch('http://localhost:3000/api/products', {
  headers: {
    'x-lang': userLang
  }
})
  .then(res => res.json())
  .then(data => console.log(data));

Key Features

Automatic Detection

StatusFlow automatically detects language from headers, query params, or defaults.

Rich Metadata

All responses include localized info with name, category, description, and causes.

Flexible Integration

Works with any language detection strategy - headers, cookies, JWT, database.

Complete Coverage

All HTTP status codes have translations in both English and Spanish.

Next Steps

Basic Usage

Learn the fundamentals of StatusFlow

Advanced Integration

Explore advanced patterns and production practices

Build docs developers (and LLMs) love