From ba4dd41123f4d46f977973998813de7cd3b7db1a Mon Sep 17 00:00:00 2001 From: Renato Almeida Date: Wed, 15 Oct 2025 14:32:14 -0300 Subject: [PATCH 1/4] Added translated flag to ctx --- node/index.ts | 2 +- node/services/product.ts | 3 ++- node/utils/i18n.ts | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/node/index.ts b/node/index.ts index efd3d3c2..7a178ced 100644 --- a/node/index.ts +++ b/node/index.ts @@ -6,8 +6,8 @@ import { Clients } from './clients' import { schemaDirectives } from './directives' import { resolvers } from './resolvers' import { - setWorkspaceSearchParams, getWorkspaceSearchParams, + setWorkspaceSearchParams, } from './routes/workspaceSearchParams' const TWO_SECONDS_MS = 2 * 1000 diff --git a/node/services/product.ts b/node/services/product.ts index b0d59c4c..c41721a0 100644 --- a/node/services/product.ts +++ b/node/services/product.ts @@ -1,5 +1,5 @@ -import { fetchAppSettings } from './settings' import type { SegmentData } from '../typings/Search' +import { fetchAppSettings } from './settings' export type ProductIdentifier = { field: 'id' | 'slug' | 'ean' | 'reference' | 'sku' @@ -144,6 +144,7 @@ export async function fetchProduct( // Check if current account should skip comparison and use intsch directly if (shouldUseNewPDPEndpoint) { + ctx.translated = true return fetchProductFromIntsch(ctx, args) } diff --git a/node/utils/i18n.ts b/node/utils/i18n.ts index 4df3e1e5..aee2e8fb 100644 --- a/node/utils/i18n.ts +++ b/node/utils/i18n.ts @@ -1,7 +1,7 @@ import { + createMessagesLoader, formatTranslatableStringV2, parseTranslatableStringV2, - createMessagesLoader, } from '@vtex/api' import { logDegradedSearchError } from '../resolvers/search/utils' @@ -28,13 +28,15 @@ export interface Message extends BaseMessage { } export const addContextToTranslatableString = (message: Message, ctx: Context) => { - const { vtex: { tenant } } = ctx + const { vtex: { tenant }, translated } = ctx const { locale } = tenant! if (!message.content) { return message.content } + const state = translated ? 'translated' : 'original' + try { const { @@ -45,7 +47,7 @@ export const addContextToTranslatableString = (message: Message, ctx: Context) = const context = (originalContext || message.context)?.toString() const from = originalFrom || message.from || locale - return formatTranslatableStringV2({ content, context, from }) + return formatTranslatableStringV2({ content, context, from, state }) } catch (e) { logDegradedSearchError(ctx.vtex.logger, { service: 'node-vtex-api translation', From 60815bcb185246714c1a140aca50a9f226496ad6 Mon Sep 17 00:00:00 2001 From: Renato Almeida Date: Thu, 16 Oct 2025 10:27:18 -0300 Subject: [PATCH 2/4] Added changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c249532..05ad0ac2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added + +- `state` parameter to `formatTranslatableStringV2` indicating if the label is already translated + ## [1.85.1] - 2025-10-14 ### Fixed From ccfd78d9967caf23c87f0369cd2861ff7948cbf6 Mon Sep 17 00:00:00 2001 From: Renato Almeida Date: Wed, 22 Oct 2025 19:25:45 -0300 Subject: [PATCH 3/4] Added integration between shouldUseNewPDPEndpoint flag and productRecommendations --- node/clients/search.ts | 8 +++++--- node/resolvers/search/index.ts | 28 ++++++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/node/clients/search.ts b/node/clients/search.ts index bd972a1f..2568e01a 100644 --- a/node/clients/search.ts +++ b/node/clients/search.ts @@ -1,16 +1,16 @@ import { AppClient, + CacheType, InstanceOptions, IOContext, RequestConfig, SegmentData, - CacheType, } from '@vtex/api' import { stringify } from 'qs' import { - searchEncodeURI, SearchCrossSellingTypes, + searchEncodeURI, } from '../resolvers/search/utils' interface AutocompleteArgs { @@ -308,11 +308,13 @@ export class Search extends AppClient { metric: 'search-category', }) - public crossSelling = (id: string, type: SearchCrossSellingTypes, groupByProduct = true) => + public crossSelling = (id: string, type: SearchCrossSellingTypes, groupByProduct = true, acceptLanguage?: string) => this.get( `/pub/products/crossselling/${type}/${id}?groupByProduct=${groupByProduct}`, { metric: 'search-crossSelling', + // This Accept Language header is used to request the cross selling products in the desired language from new dataplane endpoint + headers: acceptLanguage ? { 'Accept-Language': acceptLanguage } : {} } ) diff --git a/node/resolvers/search/index.ts b/node/resolvers/search/index.ts index 88424ae5..a5bc591b 100644 --- a/node/resolvers/search/index.ts +++ b/node/resolvers/search/index.ts @@ -7,11 +7,20 @@ import { } from '../../commons/compatibility-layer' import { getWorkspaceSearchParamsFromStorage } from '../../routes/workspaceSearchParams' import { - buildVtexSegment, + fetchAutocompleteSuggestions, + fetchCorrection, + fetchSearchSuggestions, + fetchTopSearches, +} from '../../services/autocomplete' +import { fetchBanners } from '../../services/banners' +import { ProductArgs, ProductIdentifier, + buildVtexSegment, resolveProduct, } from '../../services/product' +import { fetchAppSettings } from '../../services/settings' +import { AdvertisementOptions, FacetsInput, ProductSearchInput, ProductsInput, SegmentData, SuggestionProductsArgs } from '../../typings/Search' import { shouldTranslateToTenantLocale } from '../../utils/i18n' import { resolvers as assemblyOptionResolvers } from './assemblyOption' import { resolvers as autocompleteResolvers } from './autocomplete' @@ -39,14 +48,6 @@ import { getShippingOptionsFromSelectedFacets, validMapAndQuery, } from './utils' -import { - fetchAutocompleteSuggestions, - fetchTopSearches, - fetchSearchSuggestions, - fetchCorrection, -} from '../../services/autocomplete' -import { fetchBanners } from '../../services/banners' -import { AdvertisementOptions, FacetsInput, ProductSearchInput, ProductsInput, SegmentData, SuggestionProductsArgs } from '../../typings/Search' enum CrossSellingInput { view = 'view', @@ -597,6 +598,8 @@ export const queries = { if (identifier == null || type == null) { throw new UserInputError('Wrong input provided') } + + const { shouldUseNewPDPEndpoint } = await fetchAppSettings(ctx) const searchType = inputToSearchCrossSelling[type] let productId = identifier.value if (identifier.field !== 'id') { @@ -607,10 +610,15 @@ export const queries = { const groupByProduct = groupBy === CrossSellingGroupByInput.PRODUCT ? true : false + if (shouldUseNewPDPEndpoint) { + ctx.translated = true + } + const products = await ctx.clients.search.crossSelling( productId, searchType, - groupByProduct + groupByProduct, + shouldUseNewPDPEndpoint ? ctx.vtex.locale : undefined ) searchFirstElements(products, 0, ctx.clients.search) From ab0d7da38b09250fec69ac5eb823f1cfb2854c91 Mon Sep 17 00:00:00 2001 From: Renato Almeida Date: Tue, 28 Oct 2025 09:55:28 -0300 Subject: [PATCH 4/4] Added categoryTree param to mount href with dataplane data --- node/resolvers/search/category.ts | 46 ++++++++++++++++--------------- node/resolvers/search/product.ts | 19 ++++++++++--- node/utils/i18n.ts | 1 + 3 files changed, 40 insertions(+), 26 deletions(-) diff --git a/node/resolvers/search/category.ts b/node/resolvers/search/category.ts index 731fc667..a5310d95 100644 --- a/node/resolvers/search/category.ts +++ b/node/resolvers/search/category.ts @@ -1,9 +1,9 @@ import { compose, last, prop, split } from 'ramda' -import { getCategoryInfo, logDegradedSearchError } from './utils' import { formatTranslatableProp, shouldTranslateToBinding } from '../../utils/i18n' import { Slugify } from '../../utils/slug' import { APP_NAME } from './constants' +import { getCategoryInfo, logDegradedSearchError } from './utils' const lastSegment = compose( last, @@ -14,6 +14,28 @@ function cleanUrl(url: string) { return url.replace(/https:\/\/[A-z0-9]+\.vtexcommercestable\.com\.br/, '').toLowerCase() } +export async function mountHrefRewritter({ url, id }: any, _: unknown, ctx: Context) { + const settings: AppSettings = await ctx.clients.apps.getAppSettings(APP_NAME) + + if (shouldTranslateToBinding(ctx)) { + try { + const rewriterUrl = await ctx.clients.rewriter.getRoute(id.toString(), 'anyCategoryEntity', ctx.vtex.binding!.id!) + if (rewriterUrl) { + url = rewriterUrl + } + } catch (e) { + logDegradedSearchError(ctx.vtex.logger, { + service: 'Rewriter getRoute', + error: `Rewriter getRoute query returned an error for category ${id}. Category href may be incorrect.`, + errorStack: e, + }) + } + } + const pathname = cleanUrl(url) + + return settings.slugifyLinks ? Slugify(pathname) : pathname + } + /** This type has to be created because the Catlog API to get category by ID does not return the url or children for now. * These fields only come if you get the category from the categroy tree api. */ @@ -29,27 +51,7 @@ export const resolvers = { cacheId: prop('id'), - href: async ({ url, id }: SafeCategory, _: unknown, ctx: Context) => { - const settings: AppSettings = await ctx.clients.apps.getAppSettings(APP_NAME) - - if (shouldTranslateToBinding(ctx)) { - try { - const rewriterUrl = await ctx.clients.rewriter.getRoute(id.toString(), 'anyCategoryEntity', ctx.vtex.binding!.id!) - if (rewriterUrl) { - url = rewriterUrl - } - } catch (e) { - logDegradedSearchError(ctx.vtex.logger, { - service: 'Rewriter getRoute', - error: `Rewriter getRoute query returned an error for category ${id}. Category href may be incorrect.`, - errorStack: e, - }) - } - } - const pathname = cleanUrl(url) - - return settings.slugifyLinks ? Slugify(pathname) : pathname - }, + href: mountHrefRewritter, metaTagDescription: formatTranslatableProp( 'MetaTagDescription', diff --git a/node/resolvers/search/product.ts b/node/resolvers/search/product.ts index 0e2aef30..e45c560b 100644 --- a/node/resolvers/search/product.ts +++ b/node/resolvers/search/product.ts @@ -1,5 +1,6 @@ -import { compose, last, omit, pathOr, split, flatten } from 'ramda' +import { compose, flatten, last, omit, pathOr, split } from 'ramda' +import { fetchAppSettings } from '../../services/settings' import { addContextToTranslatableString, formatTranslatableProp, @@ -7,6 +8,7 @@ import { shouldTranslateToUserLocale, } from '../../utils/i18n' import { getBenefits } from '../benefits' +import { mountHrefRewritter } from './category' import { buildCategoryMap, logDegradedSearchError } from './utils' type DynamicKey = Record @@ -121,10 +123,13 @@ const findMainTree = (categoriesIds: string[], prodCategoryId: string) => { } const productCategoriesToCategoryTree = async ( - { categories, categoriesIds, categoryId: prodCategoryId }: SearchProduct, + // TODO categoryTree will be added + { categories, categoriesIds, categoryId: prodCategoryId, categoryTree }: SearchProduct & { categoryTree: Array<{ id: number, name: string, href: string }> }, _: any, - { clients: { search }, vtex: { platform } }: Context + ctx: Context ) => { + const { clients: { search }, vtex: { platform } } = ctx + const { shouldUseNewPDPEndpoint } = await fetchAppSettings(ctx) if (!categories || !categoriesIds) { return [] } @@ -132,7 +137,13 @@ const productCategoriesToCategoryTree = async ( const mainTreeIds = findMainTree(categoriesIds, prodCategoryId) if (platform === 'vtex') { - return mainTreeIds.map(categoryId => search.category(Number(categoryId))) + if (shouldUseNewPDPEndpoint) { + return categoryTree.map(category => ({ ...category, href: mountHrefRewritter(category, _, ctx) })) + } + return mainTreeIds.map(async categoryId => { + const category = await search.category(Number(categoryId)) + return {...category,} + }) } const categoriesTree = await search.categories(mainTreeIds.length) const categoryMap = buildCategoryMap(categoriesTree) diff --git a/node/utils/i18n.ts b/node/utils/i18n.ts index aee2e8fb..af321ae5 100644 --- a/node/utils/i18n.ts +++ b/node/utils/i18n.ts @@ -47,6 +47,7 @@ export const addContextToTranslatableString = (message: Message, ctx: Context) = const context = (originalContext || message.context)?.toString() const from = originalFrom || message.from || locale + // @ts-expect-error return formatTranslatableStringV2({ content, context, from, state }) } catch (e) { logDegradedSearchError(ctx.vtex.logger, {