diff --git a/package-lock.json b/package-lock.json index 67933c20add0..f460782d406a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,6 +104,7 @@ "@graphql-tools/load": "^8.0.0", "@octokit/rest": "^20.1.0", "@playwright/test": "1.44.0", + "@types/accept-language-parser": "1.5.6", "@types/connect-datadog": "0.0.10", "@types/connect-timeout": "0.0.39", "@types/cookie": "0.6.0", @@ -3117,6 +3118,12 @@ "version": "0.3.0", "license": "MIT" }, + "node_modules/@types/accept-language-parser": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/accept-language-parser/-/accept-language-parser-1.5.6.tgz", + "integrity": "sha512-lhSQUsAhAtbKjYgaw3f0c4EQKNQHFXhX87+OXUIqDHMkycvHGaqGskSRtnzysIUiqHPqNJ4BqI5SE++drsxx6A==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", diff --git a/package.json b/package.json index f4668b680552..128ef701a831 100644 --- a/package.json +++ b/package.json @@ -290,6 +290,7 @@ "@graphql-tools/load": "^8.0.0", "@octokit/rest": "^20.1.0", "@playwright/test": "1.44.0", + "@types/accept-language-parser": "1.5.6", "@types/connect-datadog": "0.0.10", "@types/connect-timeout": "0.0.39", "@types/cookie": "0.6.0", diff --git a/src/frame/middleware/index.ts b/src/frame/middleware/index.ts index e382ba313d1e..8c6e05ecba6c 100644 --- a/src/frame/middleware/index.ts +++ b/src/frame/middleware/index.ts @@ -17,7 +17,7 @@ import { } from './set-fastly-surrogate-key.js' import handleErrors from '@/observability/middleware/handle-errors' import handleNextDataPath from './handle-next-data-path' -import detectLanguage from '@/languages/middleware/detect-language.js' +import detectLanguage from '@/languages/middleware/detect-language' import reloadTree from './reload-tree.js' import context from './context/context.js' import shortVersions from '@/versions/middleware/short-versions.js' diff --git a/src/languages/middleware/detect-language.js b/src/languages/middleware/detect-language.ts similarity index 54% rename from src/languages/middleware/detect-language.js rename to src/languages/middleware/detect-language.ts index d696113a7657..d1a3d01d8cd0 100644 --- a/src/languages/middleware/detect-language.js +++ b/src/languages/middleware/detect-language.ts @@ -1,7 +1,10 @@ -import languages, { languageKeys } from '#src/languages/lib/languages.js' +import type { Request, Response, NextFunction } from 'express' import parser from 'accept-language-parser' +import type { Language as parserLanguage } from 'accept-language-parser' -import { USER_LANGUAGE_COOKIE_NAME } from '#src/frame/lib/constants.js' +import languages, { languageKeys } from '@/languages/lib/languages.js' +import { USER_LANGUAGE_COOKIE_NAME } from '@/frame/lib/constants.js' +import type { ExtendedRequest, Languages } from '@/types' const chineseRegions = [ 'CN', // Mainland @@ -10,25 +13,28 @@ const chineseRegions = [ 'TW', // Taiwan ] -function translationExists(language) { +function translationExists(language: parserLanguage) { if (language.code === 'zh') { - return chineseRegions.includes(language.region) + return language.region && chineseRegions.includes(language.region) } // 92BD1212-61B8-4E7A: Remove ` && !languages[language.code].wip` for the public ship of ko, fr, de, ru - return languageKeys.includes(language.code) && !languages[language.code].wip + return languageKeys.includes(language.code) && !(languages as Languages)[language.code].wip } -function getLanguageCode(language) { - return language.code === 'cn' && chineseRegions.includes(language.region) ? 'zh' : language.code +function getLanguageCode(language: parserLanguage) { + return language.code === 'cn' && language.region && chineseRegions.includes(language.region) + ? 'zh' + : language.code } -function getUserLanguage(browserLanguages) { +function getUserLanguage(browserLanguages: parserLanguage[]) { try { let numTopPreferences = 1 for (let lang = 0; lang < browserLanguages.length; lang++) { // If language has multiple regions, Chrome adds the non-region language to list - if (lang > 0 && browserLanguages[lang].code !== browserLanguages[lang - 1].code) + if (lang > 0 && browserLanguages[lang].code !== browserLanguages[lang - 1].code) { numTopPreferences++ + } if (translationExists(browserLanguages[lang]) && numTopPreferences < 3) { return getLanguageCode(browserLanguages[lang]) } @@ -38,26 +44,27 @@ function getUserLanguage(browserLanguages) { } } -function getUserLanguageFromCookie(req) { - const value = req.cookies[USER_LANGUAGE_COOKIE_NAME] +function getUserLanguageFromCookie(req: Request) { + const value: undefined | string = req.cookies[USER_LANGUAGE_COOKIE_NAME] + // 92BD1212-61B8-4E7A: Remove ` && !languages[value].wip` for the public ship of ko, fr, de, ru - if (value && languages[value] && !languages[value].wip) { + if (value && (languages as Languages)[value] && !(languages as Languages)[value].wip) { return value } } // determine language code from a path. Default to en if no valid match -export function getLanguageCodeFromPath(path) { +export function getLanguageCodeFromPath(path: string) { const maybeLanguage = (path.split('/')[path.startsWith('/_next/data/') ? 4 : 1] || '').slice(0, 2) return languageKeys.includes(maybeLanguage) ? maybeLanguage : 'en' } -export function getLanguageCodeFromHeader(req) { +export function getLanguageCodeFromHeader(req: Request) { const browserLanguages = parser.parse(req.headers['accept-language']) return getUserLanguage(browserLanguages) } -export default function detectLanguage(req, res, next) { +export default function detectLanguage(req: ExtendedRequest, res: Response, next: NextFunction) { req.language = getLanguageCodeFromPath(req.path) // Detecting browser language by user preference req.userLanguage = getUserLanguageFromCookie(req) diff --git a/src/types.ts b/src/types.ts index 2b043d78a0ed..84ae6467c5ff 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,5 +10,19 @@ export type ExtendedRequest = Request & { currentCategory?: string error?: Error } + language?: string + userLanguage?: string // Add more properties here as needed } + +type Language = { + name: string + code: string + hreflang: string + dir: string + wip: boolean +} + +export type Languages = { + [key: string]: Language +}