lup-language
Version:
Node express middleware for detecting requested language
232 lines (185 loc) • 7.32 kB
Markdown




# lup-language
Node express middleware for detecting requested language based on:
1. URI prefix e.g. /**en**/home/
2. Cookie value e.g. lang=en
3. HTTP header e.g. Accept-Language=en-US,en;q=0.5
4. Default language code
Request will be modified such that URI does not start with language code and new attribute `lang` gets added to the request object.
## Request object after LanguageRouter
* `req.lang` - requested language code e.g. 'en'
* `req.langs` - list of all supported language codes (based on found translation files in `translations` dir or `next.config.js`).
* `req.TEXT` - object with all translations for the requested language code (Disabled by default!).
* `req.PATH` - update URI path without language code prefix.
## Examples
### Express
`server.js`
```javascript
const express = require('express');
const {LanguageRouter} = require('lup-language');
const app = express();
// Add LanguageRouter as middleware that modifies request object
// based on given options (see documentation).
// Here parameter 'languages' is a list of language codes that
// your app will accept.
app.use(LanguageRouter({
defaultLanguage: 'en',
}));
// Create your custom routes and retriev request language code
// by reading it from req.lang
app.get('/', function(req, res){
res.send("Your requested language is: "+req.lang.toUpperCase()+"<br>"+
"You can access translations from the translations dir using: "+req.TEXT['TranslationKey']);
});
app.listen(80, function(){
console.log("Server running");
});
```
### Next.js ≥13 Page
`app/[locale]/translations.ts`
```typescript
'use server';
import "server-only";
import { getTranslations } from "lup-language";
export default async function loadTranslations(locale: string, translationKeys: string[]): Promise<{[key: string]: string}> {
return await getTranslations(locale, 'en', translationKeys); // second argument is default locale
};
```
`app/[locale]/layout.tsx`
```typescript
export default async function RootLayout({ params, children }: LocaleLayoutProps){
const { locale } = await params;
// if locale is not supported redirect to a supported one
const validLocale = await checkLanguage(locale, getDefaultLocale());
if(validLocale && locale !== validLocale){
permanentRedirect('/'+validLocale+'/'); // response code 308 - Permanent Redirect
}
// load translations for given translation keys (note this loadTranslations function is defined in translations.ts, see below)
const TEXT = await loadTranslations(locale, [
'HelloWorld',
]);
return <html lang={locale}>
<body>
{TEXT['HelloWorld']}
</body>
</html>;
}
export async function generateStaticParams(context: StaticParamsContext){
const locales = await getLocales();
return locales.map((locale) => ({ locale: locale }));
}
```
`app/[locale]/page.tsx`
```typescript
import React from "react";
import styles from './page.module.css';
import loadTranslations from "../translations";
import { StaticParamsContext } from "../../../services/Types.service";
export default async function Home({ params }: StaticParamsContext) {
const locale = params.locale;
const TEXT = await loadTranslations(locale, ['HelloWorld']);
return <>
<b>{TEXT['HelloWorld']}</b>
</>
}
```
`app/[locale]/translations.ts`
Server action that also allows client components to load translations.
```typescript
'use server';
import 'server-only';
import { getTranslations } from "lup-language";
export default async function loadTranslations(locale: string, translationKeys: string[]): Promise<{[key: string]: string}> {
return await getTranslations(locale, getDefaultLocale(), translationKeys);
};
```
Optional if unsupported languages or root should be redirected:
`middleware.ts`
```typescript
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { LanguageRouter } from "lup-language";
// Settings
const lupLang = LanguageRouter({
defaultLanguage: 'en',
});
export function middleware(request: NextRequest): NextResponse {
// Redirect to correct language
const langInfo = lupLang.nextJsMiddlewareHandler(request);
if(langInfo.redirect || langInfo.cookie){
const langResponse = langInfo.redirect ? NextResponse.redirect(langInfo.redirect, { status: langInfo.redirectResponseCode }) : NextResponse.next();
if(langInfo.cookie){
langResponse.cookies.set(langInfo.cookie.name, langInfo.cookie.value, langInfo.cookie.options);
}
return langResponse;
}
// Other middleware logic
return NextResponse.next();
}
```
### Next.js with Express
`server.ts`
```typescript
import express from 'express';
import next from 'next';
const dev = Config.isDevMode();
const HTTP_BIND = process.env.HTTP_BIND || "0.0.0.0";
const HTTP_PORT = parseInt(process.env.HTTP_PORT || "80") || 80;
const nextApp = next({dev: dev});
const nextHandler = nextApp.getRequestHandler();
nextApp.prepare().then(async () => {
const app = express();
// language middleware
app.use(await LanguageRouter({
useNextLanguages: false, /* true if Next ≤12 and i18n */
languagesFromTranslations: true,
redirectRoot: true,
}));
// TODO add here your custom routes ...
// all frontend routes
app.all('*', (req: Request | any, res: Response) => {
// add language prefix back to url (got removed by LanguageRouter)
const idx1 = req.url.lastIndexOf("."), idx2 = req.url.lastIndexOf("/");
req.url = (idx1 > idx2 || req.url.startsWith("/"+req.lang) || req.url.startsWith("/_next")) ?
req.originalUrl : "/"+req.lang+req.url;
return nextHandler(req, res);
});
// start server
app.listen(HTTP_PORT, HTTP_BIND, function(){
console.log("Server running at "+HTTP_BIND+":"+HTTP_PORT+" in "+(dev ? "development" : "production")+" mode");
});
});
```
### Next.js ≤12 Page
`index.tsx`
```typescript
// Next.js page
export default function Home({LANGUAGE_NAMES, TEXT}){
let components = [];
for(let lang in LANGUAGE_NAMES){
let name = LANGUAGE_NAMES[lang];
components.push(<a href={'/'+lang+'/'}>{name}</a>);
}
return (
<>
<h1>{TEXT['TranslationKey']}</h1>
<h2>{TEXT['HelloWorld']}</h2>
{components}
</>
);
}
export async function getStaticProps(context){
const TEXT = await getTranslations(context.locale, context.defaultLocale, [
'TranslationKey', 'HelloWorld'
]);
return {
props: {
LANGUAGE_NAMES: await getLanguageNames(),
TEXT: TEXT
}
};
}
```