Skip to content

Commit

Permalink
feat(next-international): added cookieName and headerName options…
Browse files Browse the repository at this point in the history
… to middleware config
  • Loading branch information
valentinpolitov committed Feb 17, 2024
1 parent a049386 commit d11b9d7
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 25 deletions.
45 changes: 38 additions & 7 deletions docs/pages/docs/app-middleware-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Navigate to the `middleware.ts` file and set the `urlMappingStrategy` to `rewrit
const I18nMiddleware = createI18nMiddleware({
locales: ['en', 'fr'],
defaultLocale: 'en',
urlMappingStrategy: 'rewrite'
})
urlMappingStrategy: 'rewrite',
});
```

You can also choose to only rewrite the URL for the default locale, and keep others locale in the URL (e.g use `/products` instead of `/en/products`, but keep `/fr/products`) using the `rewriteDefault` strategy:
Expand All @@ -22,8 +22,8 @@ You can also choose to only rewrite the URL for the default locale, and keep oth
const I18nMiddleware = createI18nMiddleware({
locales: ['en', 'fr'],
defaultLocale: 'en',
urlMappingStrategy: 'rewriteDefault'
})
urlMappingStrategy: 'rewriteDefault',
});
```

## Override the user's locale resolution
Expand All @@ -39,8 +39,39 @@ const I18nMiddleware = createI18nMiddleware({
defaultLocale: 'en',
resolveLocaleFromRequest: request => {
// Do your logic here to resolve the locale
return 'fr'
}
})
return 'fr';
},
});
```

## Customizing Locale Cookie Name

By default, next-international sets the user's selected locale in a cookie named `Next-Locale`. If your application requires a different naming convention for cookies, you can specify a custom name for this cookie. This customization can be particularly useful for aligning with your project's naming guidelines or avoiding conflicts with existing cookies.

To change the locale cookie name, adjust your `middleware.ts` configuration as follows:

```ts {5}
// middleware.ts
const I18nMiddleware = createI18nMiddleware({
locales: ['en', 'fr'],
defaultLocale: 'en',
cookieName: 'NEXT_LOCALE',
});
```

This setting allows you to seamlessly integrate the internationalization middleware with your application's existing infrastructure, enhancing both flexibility and consistency.

## Customizing Locale Header Name

Next-international approach also involves setting an HTTP header (`X-Next-Locale`) to reflect the current user's locale. In certain scenarios, you might need to customize this header's name — whether to comply with your API's standards, to integrate with third-party services more effectively, or simply to maintain consistency across your headers.

For customizing the locale header name, update your `middleware.ts` file as illustrated below:

```ts {5}
// middleware.ts
const I18nMiddleware = createI18nMiddleware({
locales: ['en', 'fr'],
defaultLocale: 'en',
headerName: 'X-Locale',
});
```
52 changes: 34 additions & 18 deletions packages/next-international/src/app/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,26 @@ const DEFAULT_STRATEGY: NonNullable<I18nMiddlewareConfig<[]>['urlMappingStrategy

export function createI18nMiddleware<const Locales extends readonly string[]>(config: I18nMiddlewareConfig<Locales>) {
return function I18nMiddleware(request: NextRequest) {
const locale = localeFromRequest(config.locales, request, config.resolveLocaleFromRequest) ?? config.defaultLocale;
const locale = localeFromRequest(request, config);
const nextUrl = request.nextUrl;

const { cookieName, headerName } = config;

// If the locale from the request is not an handled locale, then redirect to the same URL with the default locale
if (noLocalePrefix(config.locales, nextUrl.pathname)) {
nextUrl.pathname = `/${locale}${nextUrl.pathname}`;

const strategy = config.urlMappingStrategy ?? DEFAULT_STRATEGY;
if (strategy === 'rewrite' || (strategy === 'rewriteDefault' && locale === config.defaultLocale)) {
const response = NextResponse.rewrite(nextUrl);
return addLocaleToResponse(request, response, locale);
return addLocaleToResponse(request, response, locale, { cookieName, headerName });
} else {
if (!['redirect', 'rewriteDefault'].includes(strategy)) {
warn(`Invalid urlMappingStrategy: ${strategy}. Defaulting to redirect.`);
}

const response = NextResponse.redirect(nextUrl);
return addLocaleToResponse(request, response, locale);
return addLocaleToResponse(request, response, locale, { cookieName, headerName });
}
}

Expand All @@ -53,28 +55,31 @@ export function createI18nMiddleware<const Locales extends readonly string[]>(co
response = NextResponse.redirect(newUrl);
}

return addLocaleToResponse(request, response, pathnameLocale ?? config.defaultLocale);
return addLocaleToResponse(request, response, pathnameLocale ?? config.defaultLocale, { cookieName, headerName });
}

return response;
};
}

/**
* Retrieve `Next-Locale` header from request
* and check if it is an handled locale.
* Retrieve locale header from request and check if it is an handled locale.
*/
function localeFromRequest<Locales extends readonly string[]>(
locales: Locales,
request: NextRequest,
resolveLocaleFromRequest: NonNullable<
I18nMiddlewareConfig<Locales>['resolveLocaleFromRequest']
> = defaultResolveLocaleFromRequest,
config: I18nMiddlewareConfig<Locales>,
) {
const locale = request.cookies.get(LOCALE_COOKIE)?.value ?? resolveLocaleFromRequest(request);
const {
locales,
defaultLocale,
cookieName = LOCALE_COOKIE,
resolveLocaleFromRequest = defaultResolveLocaleFromRequest,
} = config;

const locale = request.cookies.get(cookieName)?.value ?? resolveLocaleFromRequest(request);

if (!locale || !locales.includes(locale)) {
return null;
return defaultLocale;
}

return locale;
Expand All @@ -84,7 +89,9 @@ function localeFromRequest<Locales extends readonly string[]>(
* Default implementation of the `resolveLocaleFromRequest` function for the I18nMiddlewareConfig.
* This function extracts the locale from the 'Accept-Language' header of the request.
*/
const defaultResolveLocaleFromRequest: NonNullable<I18nMiddlewareConfig<any>['resolveLocaleFromRequest']> = request => {
const defaultResolveLocaleFromRequest: NonNullable<
I18nMiddlewareConfig<ReadonlyArray<string>>['resolveLocaleFromRequest']
> = request => {
const header = request.headers.get('Accept-Language');
const locale = header?.split(',', 1)?.[0]?.split('-', 1)?.[0];
return locale ?? null;
Expand All @@ -100,15 +107,24 @@ function noLocalePrefix(locales: readonly string[], pathname: string) {
}

/**
* Add `X-Next-Locale` header and `Next-Locale` cookie to response
* Add locale header (default `X-Next-Locale`) and cookie (default `Next-Locale`) to response
*
* Header and cookie names can be customized using the `headerName` and `cookieName` options in the I18nMiddlewareConfig.
*
* **NOTE:** The cookie is only set if the locale is different from the one in the cookie
*/
function addLocaleToResponse(request: NextRequest, response: NextResponse, locale: string) {
response.headers.set(LOCALE_HEADER, locale);
function addLocaleToResponse<const Locales extends readonly string[]>(
request: NextRequest,
response: NextResponse,
locale: string,
options?: Partial<Pick<I18nMiddlewareConfig<Locales>, 'cookieName' | 'headerName'>>,
) {
const { cookieName = LOCALE_COOKIE, headerName = LOCALE_HEADER } = options ?? {};

response.headers.set(headerName, locale);

if (request.cookies.get(LOCALE_COOKIE)?.value !== locale) {
response.cookies.set(LOCALE_COOKIE, locale, { sameSite: 'strict' });
if (request.cookies.get(cookieName)?.value !== locale) {
response.cookies.set(cookieName, locale, { sameSite: 'strict' });
}
return response;
}
15 changes: 15 additions & 0 deletions packages/next-international/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ export type I18nServerConfig = {
export type I18nMiddlewareConfig<Locales extends readonly string[]> = {
locales: Locales;
defaultLocale: Locales[number];

/**
* The name of the cookie that will be set and used to determine the locale.
*
* @default 'Next-Locale'
*/
cookieName?: string;

/**
* The name of the header that will be added to the response.
*
* @default 'X-Next-Locale'
*/
headerName?: `X-${string}`;

/**
* When a url is not prefixed with a locale, this setting determines whether the middleware should perform a *redirect* or *rewrite* to the default locale.
*
Expand Down

0 comments on commit d11b9d7

Please sign in to comment.