From ff44fdc2928b9ae2f06c2c772e65b89ec9d48447 Mon Sep 17 00:00:00 2001 From: Nitin Venkat Rachabathuni Date: Tue, 28 Jul 2026 14:19:38 +0530 Subject: [PATCH] EXLM-5361: normalize Coveo search locale to BCP-47 for multi-lang Co-authored-by: Cursor --- scripts/coveo-headless/index.js | 11 +---- .../coveo/coveo-exl-pipeline-helpers.js | 7 +-- scripts/scripts.js | 49 +++++++++++++++++++ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/scripts/coveo-headless/index.js b/scripts/coveo-headless/index.js index 0fbd3e592..06fbb2da6 100644 --- a/scripts/coveo-headless/index.js +++ b/scripts/coveo-headless/index.js @@ -1,5 +1,5 @@ import buildHeadlessSearchEngine from './engine.js'; -import { fetchLanguagePlaceholders } from '../scripts.js'; +import { fetchLanguagePlaceholders, getCoveoSearchLocale } from '../scripts.js'; import { handleCoverSearchSubmit } from '../../blocks/browse-filters/browse-filter-utils.js'; import { COVEO_SEARCH_CUSTOM_EVENTS } from '../search/search-utils.js'; @@ -12,20 +12,13 @@ try { console.error('Error fetching placeholders:', err); } -const locales = new Map([ - ['es', 'es-ES'], - ['pt-br', 'pt-BR'], - ['zh-hans', 'zh-CN'], - ['zh-hant', 'zh-TW'], -]); - function configureSearchHeadlessEngine({ module, searchEngine, searchHub, contextObject, advancedQueryRule }) { const advancedQuery = module.loadAdvancedSearchQueryActions(searchEngine).registerAdvancedSearchQueries({ aq: advancedQueryRule || '', }); const context = contextObject ? module.loadContextActions(searchEngine).setContext(contextObject) : null; const searchConfiguration = module.loadSearchConfigurationActions(searchEngine).updateSearchConfiguration({ - locale: locales.get(document.querySelector('html').lang) || document.querySelector('html').lang || 'en', + locale: getCoveoSearchLocale(), searchHub, }); const fields = module diff --git a/scripts/data-service/coveo/coveo-exl-pipeline-helpers.js b/scripts/data-service/coveo/coveo-exl-pipeline-helpers.js index e47e26f71..b28e3fa2c 100644 --- a/scripts/data-service/coveo/coveo-exl-pipeline-helpers.js +++ b/scripts/data-service/coveo/coveo-exl-pipeline-helpers.js @@ -1,4 +1,4 @@ -import { URL_SPECIAL_CASE_LOCALES, fetchLanguagePlaceholders, getConfig } from '../../scripts.js'; +import { fetchLanguagePlaceholders, getConfig, getCoveoSearchLocale } from '../../scripts.js'; import { rewriteDocsPath } from '../../utils/path-utils.js'; import CoveoDataService from './coveo-data-service.js'; import { CONTENT_TYPES, COMMUNITY_SEARCH_FACET } from './coveo-exl-pipeline-constants.js'; @@ -195,10 +195,7 @@ export function getExlPipelineDataSourceParams(param, fields = fieldsToInclude) const dataSource = { url: coveoSearchResultsUrl, param: { - locale: - URL_SPECIAL_CASE_LOCALES.get(document.querySelector('html').lang) || - document.querySelector('html').lang || - 'en', + locale: getCoveoSearchLocale(), searchHub: `Experience League Learning Hub`, numberOfResults: param.noOfResults, excerptLength: 200, diff --git a/scripts/scripts.js b/scripts/scripts.js index 28709712e..1b4bf4d31 100644 --- a/scripts/scripts.js +++ b/scripts/scripts.js @@ -958,6 +958,55 @@ export const URL_SPECIAL_CASE_LOCALES = new Map([ ['zh-hant', 'zh-TW'], ]); +/** + * Coveo search `locale` (BCP 47, hyphen). Keep separate from IMS `locales` (underscore). + * Bare html.lang codes (en/de/…) must map to region tags so multi-lang pipelines that + * key off en-US/de-DE still return Events V2; safe if multi-lang is off. + */ +export const COVEO_SEARCH_LOCALES = new Map([ + ['en', 'en-US'], + ['de', 'de-DE'], + ['fr', 'fr-FR'], + ['it', 'it-IT'], + ['ja', 'ja-JP'], + ['ko', 'ko-KR'], + ['es', 'es-ES'], + ['pt-br', 'pt-BR'], + ['zh-hans', 'zh-CN'], + ['zh-hant', 'zh-TW'], +]); + +const COVEO_SEARCH_LOCALE_DEFAULT = 'en-US'; + +/** + * Normalize page language to a Coveo search locale (BCP 47). + * @param {string} [lang] - html.lang or path lang; defaults to documentElement.lang + * @returns {string} + */ +export function getCoveoSearchLocale(lang) { + let raw = ''; + if (lang != null && String(lang).trim() !== '') { + raw = String(lang); + } else if (typeof document !== 'undefined') { + raw = document.documentElement?.lang || document.querySelector?.('html')?.lang || ''; + } + const normalized = raw.trim().replace(/_/g, '-'); + if (!normalized) return COVEO_SEARCH_LOCALE_DEFAULT; + + const lower = normalized.toLowerCase(); + if (COVEO_SEARCH_LOCALES.has(lower)) { + return COVEO_SEARCH_LOCALES.get(lower); + } + + // Already region-qualified (e.g. en-US, zh-CN). + if (/^[a-z]{2,3}-[a-z0-9]{2,8}$/i.test(normalized)) { + const [language, region] = normalized.split('-'); + return `${language.toLowerCase()}-${region.toUpperCase()}`; + } + + return COVEO_SEARCH_LOCALE_DEFAULT; +} + // TODO: Move loadIms() out of scripts.js into a dedicated utility . // and import it from there. Its current location causes a cyclic dependency because // premium-learning-utils.js → profile.js → scripts.js → premium-learning-utils.js.