From d11b9d7e2fb4e1235c878159481fdcc8a58ca5bb Mon Sep 17 00:00:00 2001 From: Valentin Politov Date: Sat, 17 Feb 2024 04:45:43 +0200 Subject: [PATCH] feat(next-international): added `cookieName` and `headerName` options to middleware config --- .../docs/app-middleware-configuration.md | 45 +++++++++++++--- .../src/app/middleware/index.ts | 52 ++++++++++++------- packages/next-international/src/types.ts | 15 ++++++ 3 files changed, 87 insertions(+), 25 deletions(-) diff --git a/docs/pages/docs/app-middleware-configuration.md b/docs/pages/docs/app-middleware-configuration.md index 0c37fe0..915b026 100644 --- a/docs/pages/docs/app-middleware-configuration.md +++ b/docs/pages/docs/app-middleware-configuration.md @@ -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: @@ -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 @@ -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', +}); +``` diff --git a/packages/next-international/src/app/middleware/index.ts b/packages/next-international/src/app/middleware/index.ts index 427bad9..ced6379 100644 --- a/packages/next-international/src/app/middleware/index.ts +++ b/packages/next-international/src/app/middleware/index.ts @@ -9,9 +9,11 @@ const DEFAULT_STRATEGY: NonNullable['urlMappingStrategy export function createI18nMiddleware(config: I18nMiddlewareConfig) { 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}`; @@ -19,14 +21,14 @@ export function createI18nMiddleware(co 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 }); } } @@ -53,7 +55,7 @@ export function createI18nMiddleware(co response = NextResponse.redirect(newUrl); } - return addLocaleToResponse(request, response, pathnameLocale ?? config.defaultLocale); + return addLocaleToResponse(request, response, pathnameLocale ?? config.defaultLocale, { cookieName, headerName }); } return response; @@ -61,20 +63,23 @@ export function createI18nMiddleware(co } /** - * 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: Locales, request: NextRequest, - resolveLocaleFromRequest: NonNullable< - I18nMiddlewareConfig['resolveLocaleFromRequest'] - > = defaultResolveLocaleFromRequest, + config: I18nMiddlewareConfig, ) { - 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; @@ -84,7 +89,9 @@ function localeFromRequest( * 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['resolveLocaleFromRequest']> = request => { +const defaultResolveLocaleFromRequest: NonNullable< + I18nMiddlewareConfig>['resolveLocaleFromRequest'] +> = request => { const header = request.headers.get('Accept-Language'); const locale = header?.split(',', 1)?.[0]?.split('-', 1)?.[0]; return locale ?? null; @@ -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( + request: NextRequest, + response: NextResponse, + locale: string, + options?: Partial, '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; } diff --git a/packages/next-international/src/types.ts b/packages/next-international/src/types.ts index b665dd9..cee6bc9 100644 --- a/packages/next-international/src/types.ts +++ b/packages/next-international/src/types.ts @@ -50,6 +50,21 @@ export type I18nServerConfig = { export type I18nMiddlewareConfig = { 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. *