Next.js usage
The Next adapter is where most of the routing-specific behaviour lives.
Path routing
pathRouting lives in the init() options you give the adapter.
// src/lib/ways.ts
import { init as initWays } from '@18ways/next/server';
export const {
waysMiddleware,
} = initWays({
apiKey: 'YOUR_18WAYS_PUBLIC_API_KEY',
baseLocale: 'en-GB',
persistLocaleCookie: true,
pathRouting: {
exclude: ['/dashboard'],
},
});That tells the adapter which paths middleware should localize.
With that config in place:
/pricingcan become/fr-FR/pricing/dashboardstays/dashboard
The adapter also auto-excludes obvious non-page routes like /_next and /api.
Middleware flow
waysMiddleware() is the middleware API from init(). It:
- resolves the locale from cookie, pathname, browser preference, or
baseLocale - normalizes the incoming pathname if a locale prefix should be added or removed
- creates the right
NextResponsefor redirects, rewrites, or passthrough requests - writes the locale preference cookie using the policy from
init() - forwards the locale-aware request headers your app can read later
For the normal case, this is enough:
// src/middleware.ts
import type { NextRequest } from 'next/server';
import { waysMiddleware } from '@/lib/ways';
export default function middleware(
request: NextRequest
) {
return waysMiddleware(request);
}When you do need to customize the middleware result, pass options explicitly:
return waysMiddleware(request, {
/**
* Defaults to noop.
* Use this to add or replace
* request headers before the
* response is created.
*/
transformRequestHeaders: (context) =>
context.requestHeaders,
/**
* Defaults to noop.
* Use this to decorate or replace
* the final NextResponse.
*/
transformResponse: (response) => response,
});Reach for transformRequestHeaders or transformResponse only when you need extra behavior on top
of the default middleware result.
Locale resolution order
The current adapter resolves locale in this order:
18ways_localecookie- locale prefix in the pathname
- browser preferences
baseLocale
That order matters because it lets an explicit user preference override everything else.
Client-side locale changes
Use useLocale() from @18ways/next/client.
'use client';
import {
LanguageSwitcher,
} from '@18ways/react';
import { useLocale } from '@18ways/next/client';
export function LocaleControls() {
const { locale, setLocale } = useLocale();
return (
<LanguageSwitcher
currentLocale={locale}
onLocaleChange={setLocale}
/>
);
}Use useLocale() when you want your own controls. Use LanguageSwitcher when you want the
ready-made selector wired to the same locale state.
When path routing is enabled, setLocale() updates the URL. When it is disabled, it updates the
locale state and refreshes the route.
Locale-aware links
There are two useful client helpers:
import {
useLocalizedHref,
useUnlocalizedPathname,
} from '@18ways/next/client';useLocalizedHref()builds locale-aware internal hrefs.useUnlocalizedPathname()lets you compare routes without worrying about the current locale prefix.
Those links should follow the same rules your middleware enforces. If middleware is adding or
removing locale prefixes for a route, useLocalizedHref() should be the helper you reach for on
the client.
Translated metadata
generateWaysMetadata() can translate string metadata fields for you.
export async function generateMetadata() {
return generateWaysMetadata((t) => ({
title: t('Pricing'),
description: t(
'Simple pricing for runtime translation'
),
}));
}When path routing is enabled, the adapter also emits locale-specific canonical and alternate language URLs.
Middleware composition
The adapter does not assume it owns your whole middleware. waysMiddleware() can be called inside
your auth layer, or customized with transformRequestHeaders and transformResponse.
What belongs in the Next track
Document behaviour here when it depends on:
- middleware
- pathname prefixes
- translated metadata
- SSR locale resolution
If the behaviour is just React rendering, it belongs in the React track instead.