Fastify Hook
Fastify integrates with Polingo through the same polingoMiddleware
helper used by Express. Register it as an onRequest
hook to provide localized helpers on each request.
Project Setup
bash
mkdir polingo-fastify-example
cd polingo-fastify-example
pnpm init
git init
Install dependencies:
bash
pnpm add fastify @polingo/node
pnpm add -D typescript tsx @types/node @polingo/cli
TypeScript configuration (tsconfig.json
):
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Add scripts to package.json
:
json
{
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"extract": "polingo extract src --locales locales --languages en,es,fr --default-locale en",
"validate": "polingo validate locales"
}
}
Register the Hook
Create src/server.ts
:
ts
import fastify from 'fastify';
import { polingoMiddleware } from '@polingo/node';
const app = fastify({ logger: true });
app.addHook(
'onRequest',
polingoMiddleware({
directory: './locales',
locales: ['en', 'es', 'fr'],
fallback: 'en',
perLocale: true,
localeExtractor: (req) =>
typeof req.query?.lang === 'string'
? req.query.lang
: ((req.headers['accept-language'] as string | undefined)?.split(',')[0]?.split('-')[0] ??
'en'),
})
);
app.get('/', async (request) => request.polingo.t('Hello from Fastify!'));
app.get('/items', async (request) => {
const count = Number.parseInt((request.query as Record<string, string>).count ?? '0', 10);
return request.polingo.tn('{count} item in cart', '{count} items in cart', count);
});
app.get('/api/status', async (request) => ({
message: request.polingo.t('Server is running'),
status: request.polingo.tp('status', 'OK'),
locale: request.polingo.getLocale(),
}));
const PORT = Number.parseInt(process.env.PORT ?? '3000', 10);
await app.listen({ port: PORT });
console.log(`Fastify server listening on http://localhost:${PORT}`);
Using perLocale: true
ensures each request receives an isolated translator, matching Fastify's concurrency model.
Translate Catalogs
Extract strings and create the catalogs:
bash
pnpm extract
Translate locales/es/messages.po
:
po
msgid "Hello from Fastify!"
msgstr "¡Hola desde Fastify!"
msgid "{count} item in cart"
msgid_plural "{count} items in cart"
msgstr[0] "{count} artículo en el carrito"
msgstr[1] "{count} artículos en el carrito"
msgid "Server is running"
msgstr "El servidor está funcionando"
msgctxt "status"
msgid "OK"
msgstr "Bien"
Translate locales/fr/messages.po
:
po
msgid "Hello from Fastify!"
msgstr "Bonjour depuis Fastify!"
msgid "{count} item in cart"
msgid_plural "{count} items in cart"
msgstr[0] "{count} article dans le panier"
msgstr[1] "{count} articles dans le panier"
msgid "Server is running"
msgstr "Le serveur fonctionne"
msgctxt "status"
msgid "OK"
msgstr "D'accord"
Validate & Run
bash
pnpm validate
pnpm dev
Hit the endpoints:
bash
curl http://localhost:3000/
curl http://localhost:3000/?lang=es
curl "http://localhost:3000/items?count=3&lang=fr"
curl http://localhost:3000/api/status?lang=es
- The middleware is framework-agnostic; swap Fastify for Express without changing translation logic.
- Prefer environment variables or cookies in
localeExtractor
for production deployments. - Shut down gracefully by awaiting
app.close()
and callingrequest.polingo.stopWatching?.()
whenwatch
is enabled.