Refactor i18n handling for language cookie and headers
The commit encompasses the aspect of refactoring the i18n handling for language cookies and headers. It also includes the deletion of get-language-cookie file and its transformation into a function inside i18n.server file. Extra functionalities were added to the i18n.server like enhancing the i18n server instance creation to consider the 'accept-language' header and default to environment provided values when necessary. The changes were also adjusted accordingly on the packages/i18n/package.json where deletion of "./cookie" was realized.
This commit is contained in:
@@ -1,10 +1,27 @@
|
|||||||
import getLanguageCookie from '@kit/i18n/cookie';
|
import { cookies, headers } from 'next/headers';
|
||||||
import { initializeServerI18n } from '@kit/i18n/server';
|
|
||||||
|
import {
|
||||||
|
getLanguageCookie,
|
||||||
|
initializeServerI18n,
|
||||||
|
parseAcceptLanguageHeader,
|
||||||
|
} from '@kit/i18n/server';
|
||||||
|
|
||||||
import { i18nResolver } from './i18n.resolver';
|
import { i18nResolver } from './i18n.resolver';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name createI18nServerInstance
|
||||||
|
* @description Creates an instance of the i18n server.
|
||||||
|
* It uses the language from the cookie if it exists, otherwise it uses the language from the accept-language header.
|
||||||
|
* If neither is available, it will default to the provided environment variable.
|
||||||
|
*
|
||||||
|
* Initialize the i18n instance for every RSC server request (eg. each page/layout)
|
||||||
|
*/
|
||||||
export function createI18nServerInstance() {
|
export function createI18nServerInstance() {
|
||||||
const cookie = getLanguageCookie();
|
const acceptLanguage = headers().get('accept-language');
|
||||||
|
const cookie = getLanguageCookie(cookies());
|
||||||
|
|
||||||
return initializeServerI18n(cookie, i18nResolver);
|
const language =
|
||||||
|
cookie ?? parseAcceptLanguageHeader(acceptLanguage)[0] ?? undefined;
|
||||||
|
|
||||||
|
return initializeServerI18n(language, i18nResolver);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
"./server": "./src/i18n.server.ts",
|
"./server": "./src/i18n.server.ts",
|
||||||
"./client": "./src/i18n.client.ts",
|
"./client": "./src/i18n.client.ts",
|
||||||
"./cookie": "./src/get-language-cookie.ts",
|
|
||||||
"./provider": "./src/i18n-provider.tsx"
|
"./provider": "./src/i18n-provider.tsx"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import { cookies } from 'next/headers';
|
|
||||||
|
|
||||||
import { I18N_COOKIE_NAME } from './i18n.settings';
|
|
||||||
|
|
||||||
function getLanguageCookie() {
|
|
||||||
return cookies().get(I18N_COOKIE_NAME)?.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getLanguageCookie;
|
|
||||||
@@ -2,7 +2,15 @@ import { createInstance } from 'i18next';
|
|||||||
import resourcesToBackend from 'i18next-resources-to-backend';
|
import resourcesToBackend from 'i18next-resources-to-backend';
|
||||||
import { initReactI18next } from 'react-i18next/initReactI18next';
|
import { initReactI18next } from 'react-i18next/initReactI18next';
|
||||||
|
|
||||||
import { getI18nSettings } from './i18n.settings';
|
import { I18N_COOKIE_NAME, getI18nSettings, languages } from './i18n.settings';
|
||||||
|
|
||||||
|
export function getLanguageCookie<
|
||||||
|
Cookies extends {
|
||||||
|
get: (name: string) => { value: string } | undefined;
|
||||||
|
},
|
||||||
|
>(cookies: Cookies) {
|
||||||
|
return cookies.get(I18N_COOKIE_NAME)?.value;
|
||||||
|
}
|
||||||
|
|
||||||
export async function initializeServerI18n(
|
export async function initializeServerI18n(
|
||||||
lang: string | undefined,
|
lang: string | undefined,
|
||||||
@@ -33,3 +41,39 @@ export async function initializeServerI18n(
|
|||||||
|
|
||||||
return i18nInstance;
|
return i18nInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseAcceptLanguageHeader(
|
||||||
|
languageHeaderValue: string | null | undefined,
|
||||||
|
acceptedLanguages = languages,
|
||||||
|
): string[] {
|
||||||
|
// Return an empty array if the header value is not provided
|
||||||
|
if (!languageHeaderValue) return [];
|
||||||
|
|
||||||
|
const ignoreWildcard = true;
|
||||||
|
|
||||||
|
// Split the header value by comma and map each language to its quality value
|
||||||
|
return languageHeaderValue
|
||||||
|
.split(',')
|
||||||
|
.map((lang): [number, string] => {
|
||||||
|
const [locale, q = 'q=1'] = lang.split(';');
|
||||||
|
|
||||||
|
if (!locale) return [0, ''];
|
||||||
|
|
||||||
|
const trimmedLocale = locale.trim();
|
||||||
|
const numQ = Number(q.replace(/q ?=/, ''));
|
||||||
|
|
||||||
|
return [isNaN(numQ) ? 0 : numQ, trimmedLocale];
|
||||||
|
})
|
||||||
|
.sort(([q1], [q2]) => q2 - q1) // Sort by quality value in descending order
|
||||||
|
.flatMap(([_, locale]) => {
|
||||||
|
// Ignore wildcard '*' if 'ignoreWildcard' is true
|
||||||
|
if (locale === '*' && ignoreWildcard) return [];
|
||||||
|
|
||||||
|
// Return the locale if it's included in the accepted languages
|
||||||
|
try {
|
||||||
|
return acceptedLanguages.includes(locale) ? [locale] : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { InitOptions } from 'i18next';
|
import { InitOptions } from 'i18next';
|
||||||
|
|
||||||
const fallbackLng = 'en';
|
const fallbackLng = 'en';
|
||||||
const languages: string[] = [fallbackLng];
|
export const languages: string[] = [fallbackLng];
|
||||||
|
|
||||||
export const I18N_COOKIE_NAME = 'lang';
|
export const I18N_COOKIE_NAME = 'lang';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user