@hed-hog/catalog 0.0.293 → 0.0.295

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +391 -361
  2. package/dist/catalog-resource.config.d.ts.map +1 -1
  3. package/dist/catalog-resource.config.js +51 -24
  4. package/dist/catalog-resource.config.js.map +1 -1
  5. package/dist/catalog.controller.d.ts +420 -0
  6. package/dist/catalog.controller.d.ts.map +1 -1
  7. package/dist/catalog.controller.js +98 -0
  8. package/dist/catalog.controller.js.map +1 -1
  9. package/dist/catalog.module.d.ts.map +1 -1
  10. package/dist/catalog.module.js +5 -1
  11. package/dist/catalog.module.js.map +1 -1
  12. package/dist/catalog.service.d.ts +216 -1
  13. package/dist/catalog.service.d.ts.map +1 -1
  14. package/dist/catalog.service.js +1121 -7
  15. package/dist/catalog.service.js.map +1 -1
  16. package/hedhog/data/catalog_attribute.yaml +202 -0
  17. package/hedhog/data/catalog_attribute_option.yaml +109 -0
  18. package/hedhog/data/catalog_category.yaml +47 -0
  19. package/hedhog/data/catalog_category_attribute.yaml +209 -0
  20. package/hedhog/data/menu.yaml +46 -12
  21. package/hedhog/data/role.yaml +7 -7
  22. package/hedhog/data/route.yaml +64 -0
  23. package/hedhog/frontend/app/[resource]/page.tsx.ejs +358 -0
  24. package/hedhog/frontend/app/_components/catalog-ai-form-assist-dialog.tsx.ejs +340 -0
  25. package/hedhog/frontend/app/_components/catalog-resource-form-sheet.tsx.ejs +815 -0
  26. package/hedhog/frontend/app/_lib/catalog-resources.tsx.ejs +504 -736
  27. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -83
  28. package/hedhog/frontend/messages/en.json +150 -60
  29. package/hedhog/frontend/messages/pt.json +185 -95
  30. package/hedhog/table/catalog_affiliate_program.yaml +41 -41
  31. package/hedhog/table/catalog_attribute.yaml +22 -7
  32. package/hedhog/table/catalog_attribute_group.yaml +18 -18
  33. package/hedhog/table/catalog_attribute_option.yaml +40 -0
  34. package/hedhog/table/catalog_brand.yaml +34 -34
  35. package/hedhog/table/catalog_category.yaml +40 -0
  36. package/hedhog/table/catalog_category_attribute.yaml +13 -7
  37. package/hedhog/table/catalog_click_event.yaml +50 -50
  38. package/hedhog/table/catalog_comparison.yaml +3 -6
  39. package/hedhog/table/catalog_comparison_highlight.yaml +39 -39
  40. package/hedhog/table/catalog_comparison_item.yaml +30 -30
  41. package/hedhog/table/catalog_content_relation.yaml +42 -42
  42. package/hedhog/table/catalog_import_run.yaml +33 -33
  43. package/hedhog/table/catalog_import_source.yaml +24 -24
  44. package/hedhog/table/catalog_merchant.yaml +29 -29
  45. package/hedhog/table/catalog_offer.yaml +83 -83
  46. package/hedhog/table/catalog_price_history.yaml +34 -34
  47. package/hedhog/table/catalog_product.yaml +5 -3
  48. package/hedhog/table/catalog_product_attribute_value.yaml +15 -2
  49. package/hedhog/table/catalog_product_category.yaml +3 -3
  50. package/hedhog/table/catalog_product_image.yaml +34 -34
  51. package/hedhog/table/catalog_product_score.yaml +38 -38
  52. package/hedhog/table/catalog_product_site.yaml +47 -47
  53. package/hedhog/table/catalog_product_tag.yaml +19 -19
  54. package/hedhog/table/catalog_score_criterion.yaml +25 -8
  55. package/hedhog/table/catalog_seo_page_rule.yaml +2 -2
  56. package/hedhog/table/catalog_similarity_rule.yaml +19 -6
  57. package/hedhog/table/catalog_site.yaml +8 -0
  58. package/hedhog/table/catalog_site_category.yaml +3 -3
  59. package/package.json +7 -7
  60. package/src/catalog-resource.config.ts +51 -24
  61. package/src/catalog.controller.ts +67 -0
  62. package/src/catalog.module.ts +5 -1
  63. package/src/catalog.service.ts +1531 -6
  64. package/src/index.ts +1 -1
  65. package/src/language/en.json +4 -4
  66. package/src/language/pt.json +4 -4
@@ -1,9 +1,4 @@
1
1
  'use client';
2
- import {
3
- catalogKpiResources,
4
- catalogQuickActionResources,
5
- catalogResources,
6
- } from '../_lib/catalog-resources';
7
2
  import { Page, PageHeader } from '@/components/entity-list';
8
3
  import { Badge } from '@/components/ui/badge';
9
4
  import { Button } from '@/components/ui/button';
@@ -17,17 +12,9 @@ import {
17
12
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
18
13
  import { Skeleton } from '@/components/ui/skeleton';
19
14
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
20
- import {
21
- Activity,
22
- ArrowUpRight,
23
- Boxes,
24
- ChartNoAxesCombined,
25
- RefreshCcw,
26
- ShieldCheck,
27
- Sparkles,
28
- } from 'lucide-react';
29
- import Link from 'next/link';
15
+ import { ArrowUpRight, ChartNoAxesCombined, RefreshCcw } from 'lucide-react';
30
16
  import { useTranslations } from 'next-intl';
17
+ import Link from 'next/link';
31
18
  import {
32
19
  Bar,
33
20
  BarChart,
@@ -40,6 +27,11 @@ import {
40
27
  XAxis,
41
28
  YAxis,
42
29
  } from 'recharts';
30
+ import {
31
+ catalogKpiResources,
32
+ catalogQuickActionResources,
33
+ catalogResources,
34
+ } from '../_lib/catalog-resources';
43
35
 
44
36
  type CatalogStats = {
45
37
  resource: string;
@@ -209,72 +201,6 @@ export default function CatalogDashboardPage() {
209
201
  />
210
202
 
211
203
  <div className="min-w-0 space-y-6 overflow-x-hidden">
212
- <Card className="overflow-hidden border-orange-200/70 bg-gradient-to-br from-orange-50 via-background to-amber-50 py-0">
213
- <CardContent className="grid min-w-0 gap-6 px-6 py-6 lg:grid-cols-[minmax(0,1.4fr)_minmax(280px,0.9fr)]">
214
- <div className="min-w-0 space-y-4">
215
- <Badge className="w-fit rounded-full bg-orange-500/10 px-3 py-1 text-orange-700 hover:bg-orange-500/10">
216
- <Sparkles className="mr-2 size-3.5" />
217
- {t('dashboard.heroBadge')}
218
- </Badge>
219
- <div className="space-y-2">
220
- <h2 className="text-3xl font-semibold tracking-tight text-balance">
221
- {t('dashboard.heroTitle')}
222
- </h2>
223
- <p className="max-w-2xl text-sm leading-6 text-muted-foreground">
224
- {t('dashboard.heroDescription')}
225
- </p>
226
- </div>
227
- <div className="flex flex-wrap gap-3">
228
- <Button asChild>
229
- <Link href="/catalog/products">
230
- {t('dashboard.primaryAction')}
231
- </Link>
232
- </Button>
233
- <Button asChild variant="outline">
234
- <Link href="/catalog/import-sources">
235
- {t('dashboard.secondaryAction')}
236
- </Link>
237
- </Button>
238
- </div>
239
- </div>
240
-
241
- <div className="grid gap-3 sm:grid-cols-3 lg:grid-cols-1">
242
- {[
243
- {
244
- icon: Boxes,
245
- label: t('dashboard.summary.totalRecords'),
246
- value: totalRecords,
247
- },
248
- {
249
- icon: ShieldCheck,
250
- label: t('dashboard.summary.activeRecords'),
251
- value: totalActive,
252
- },
253
- {
254
- icon: Activity,
255
- label: t('dashboard.summary.monitoredResources'),
256
- value: monitoredResources,
257
- },
258
- ].map((item) => (
259
- <div
260
- key={item.label}
261
- className="rounded-2xl border border-white/70 bg-white/80 p-4 shadow-sm backdrop-blur"
262
- >
263
- <div className="mb-3 flex items-center gap-2 text-muted-foreground">
264
- <item.icon className="size-4" />
265
- <span className="text-xs uppercase tracking-[0.2em]">
266
- {item.label}
267
- </span>
268
- </div>
269
- <div className="text-3xl font-semibold tracking-tight">
270
- {item.value}
271
- </div>
272
- </div>
273
- ))}
274
- </div>
275
- </CardContent>
276
- </Card>
277
-
278
204
  <KpiCardsGrid items={kpiCards} />
279
205
 
280
206
  <div className="grid min-w-0 gap-6 xl:grid-cols-[minmax(0,1.3fr)_minmax(320px,0.9fr)]">
@@ -465,7 +391,9 @@ export default function CatalogDashboardPage() {
465
391
  className="inline-block size-3 rounded-full"
466
392
  style={{ backgroundColor: item.fill }}
467
393
  />
468
- <span className="truncate font-medium">{item.name}</span>
394
+ <span className="truncate font-medium">
395
+ {item.name}
396
+ </span>
469
397
  </div>
470
398
  <span className="shrink-0 text-sm text-muted-foreground">
471
399
  {item.total}
@@ -484,7 +412,10 @@ export default function CatalogDashboardPage() {
484
412
  const Icon = resource.icon;
485
413
 
486
414
  return (
487
- <Card key={resource.resource} className="min-w-0 overflow-hidden py-0">
415
+ <Card
416
+ key={resource.resource}
417
+ className="min-w-0 overflow-hidden py-0"
418
+ >
488
419
  <div
489
420
  className={`h-full bg-gradient-to-br ${resource.colorClass} px-6 py-5`}
490
421
  >
@@ -1,19 +1,39 @@
1
1
  {
2
2
  "title": "Catalog",
3
- "subtitle": "Manage the generic multi-niche catalog foundation.",
3
+ "subtitle": "Manage categories, attributes, products, offers, and publication for the structured catalog.",
4
4
  "openResource": "Open resource",
5
- "searchPlaceholder": "Search by id, slug, name or title...",
5
+ "searchPlaceholder": "Search by id, slug, name, or title...",
6
6
  "create": "Create",
7
7
  "edit": "Edit",
8
8
  "delete": "Delete",
9
9
  "save": "Save",
10
10
  "cancel": "Cancel",
11
- "payload": "Payload",
12
- "empty": "No records found.",
11
+ "aiAssist": {
12
+ "button": "Use AI",
13
+ "title": "Form assist",
14
+ "description": "Describe what should be filled for {resource}. The suggestion will only be applied to the current form.",
15
+ "promptLabel": "Prompt",
16
+ "promptPlaceholder": "Example: Create a brand named ASUS with the official website https://www.asus.com and active status.",
17
+ "promptHint": "AI suggests values for the current form, but you still review everything before saving.",
18
+ "generate": "Generate suggestion",
19
+ "apply": "Apply suggestions",
20
+ "previewTitle": "Suggestion preview",
21
+ "previewDescription": "Review the suggested values before applying them to the form.",
22
+ "sections": {
23
+ "fields": "Form fields",
24
+ "productAttributes": "Technical attributes",
25
+ "warnings": "Warnings"
26
+ },
27
+ "errors": {
28
+ "promptRequired": "Enter a prompt to use AI.",
29
+ "generate": "Could not generate the AI suggestion."
30
+ },
31
+ "toasts": {
32
+ "applied": "Suggestions applied to the form."
33
+ }
34
+ },
13
35
  "backToCatalog": "Back to dashboard",
14
36
  "refresh": "Refresh",
15
- "newRecord": "New record",
16
- "editRecord": "Edit record",
17
37
  "confirmDelete": "Delete this record?",
18
38
  "confirmDeleteDescription": "This operation cannot be undone.",
19
39
  "unsupportedResource": "Unsupported catalog resource.",
@@ -21,44 +41,41 @@
21
41
  "home": "Home",
22
42
  "catalog": "Catalog"
23
43
  },
24
- "navigation": {
25
- "current": "Current"
26
- },
27
44
  "dashboard": {
28
45
  "title": "Dashboard",
29
- "subtitle": "Operational overview of the catalog with indicators, distribution, and quick access.",
46
+ "subtitle": "Operational view of the catalog with real resource data.",
30
47
  "heroBadge": "Catalog overview",
31
- "heroTitle": "Track the catalog ecosystem from one control room.",
32
- "heroDescription": "Monitor the most important domains, compare resource volume, and jump straight to the areas your team touches every day.",
48
+ "heroTitle": "Track the structured catalog from one control panel.",
49
+ "heroDescription": "Monitor the resources the team uses most, compare domain volumes, and move quickly across the areas operated every day.",
33
50
  "primaryAction": "Open products",
34
51
  "secondaryAction": "View imports",
35
52
  "summary": {
36
53
  "totalRecords": "Total records",
37
- "activeRecords": "Active/published monitored",
54
+ "activeRecords": "Active/published",
38
55
  "monitoredResources": "Resources with data"
39
56
  },
40
57
  "card": {
41
58
  "totalLabel": "Total",
42
59
  "activeLabel": "{count} active/published",
43
- "noActiveLabel": "No tracked status metric"
60
+ "noActiveLabel": "No tracked metric"
44
61
  },
45
62
  "charts": {
46
63
  "volume": {
47
64
  "title": "Highest volume by resource",
48
- "description": "Comparison across the catalog resources with the largest record sets."
65
+ "description": "Comparison across the resources with the most records."
49
66
  },
50
67
  "status": {
51
- "title": "Tracked status vs other records",
52
- "description": "Status view across the {count} resources that expose tracked status metrics."
68
+ "title": "Tracked status",
69
+ "description": "Status view across the {count} resources with a tracked metric."
53
70
  },
54
71
  "distribution": {
55
72
  "title": "Volume distribution",
56
- "description": "Relative share of the top resources inside the current catalog base."
73
+ "description": "Relative share of the main resources."
57
74
  }
58
75
  },
59
76
  "ranking": {
60
77
  "title": "Resource ranking",
61
- "description": "The largest catalog areas right now.",
78
+ "description": "The largest modules right now.",
62
79
  "activeCount": "{count} active/published"
63
80
  },
64
81
  "quickActions": {
@@ -66,7 +83,7 @@
66
83
  },
67
84
  "footer": {
68
85
  "title": "Consolidated module view",
69
- "description": "The charts use real per-resource stats that already exist and organize team navigation around them.",
86
+ "description": "The charts use real statistics and organize navigation around them.",
70
87
  "realData": "Real data",
71
88
  "multiView": "Multi-resource view",
72
89
  "resourceCount": "{count} monitored resources"
@@ -75,13 +92,6 @@
75
92
  "resource": {
76
93
  "notFound": "Resource not found",
77
94
  "unsupportedDescription": "Choose one of the module resources to continue navigating through the catalog.",
78
- "createShortcut": "Create new record",
79
- "listTitle": "Resource records",
80
- "listDescription": "Track the resource data with search, filter, pagination, and quick actions.",
81
- "payloadHintTitle": "Raw format editing",
82
- "payloadHintDescription": "The dialogs use JSON to speed up testing and operational adjustments for the team.",
83
- "recordPreview": "Raw preview of the current payload.",
84
- "emptyState": "No records found for this current slice.",
85
95
  "emptyStateTitle": "No records found",
86
96
  "emptyStateDescription": "Adjust filters or create the first record for this resource.",
87
97
  "emptyStateAction": "Create new record",
@@ -97,14 +107,16 @@
97
107
  "inStockInSlice": "In stock in slice",
98
108
  "filterableInSlice": "Filterable in slice",
99
109
  "requiredInSlice": "Required in slice",
100
- "publishedInSlice": "Active/published in slice"
110
+ "publishedInSlice": "Published in slice",
111
+ "verifiedInSlice": "Verified in slice"
101
112
  },
102
113
  "filters": {
103
114
  "all": "All",
104
115
  "status": "Status",
105
116
  "availability_status": "Availability",
106
117
  "data_type": "Data type",
107
- "facet_mode": "Facet mode"
118
+ "facet_mode": "Facet mode",
119
+ "is_verified": "Verification"
108
120
  },
109
121
  "filterOptions": {
110
122
  "status": {
@@ -118,18 +130,25 @@
118
130
  "availability_status": {
119
131
  "inStock": "In stock",
120
132
  "outOfStock": "Out of stock",
121
- "preorder": "Pre-order"
133
+ "preOrder": "Pre-order",
134
+ "unknown": "Unknown"
122
135
  },
123
136
  "data_type": {
124
137
  "text": "Text",
138
+ "longText": "Long text",
125
139
  "number": "Number",
126
140
  "boolean": "Boolean",
127
- "json": "JSON"
141
+ "option": "Option"
128
142
  },
129
143
  "facet_mode": {
130
144
  "default": "Default",
131
145
  "range": "Range",
132
- "select": "Select"
146
+ "select": "Select",
147
+ "hidden": "Hidden"
148
+ },
149
+ "is_verified": {
150
+ "verified": "Verified",
151
+ "unverified": "Unverified"
133
152
  }
134
153
  },
135
154
  "fields": {
@@ -144,16 +163,22 @@
144
163
  "defaultLocaleId": "Default locale",
145
164
  "sku": "SKU",
146
165
  "brandId": "Brand",
147
- "categoryId": "Category",
166
+ "code": "Code",
148
167
  "groupId": "Group",
168
+ "groupName": "Group name",
149
169
  "label": "Label",
150
170
  "dataType": "Data type",
151
171
  "comparisonMode": "Comparison mode",
152
172
  "requiredForComparison": "Required for comparison",
173
+ "isComparable": "Comparable",
174
+ "isRequiredDefault": "Required by default",
153
175
  "facetMode": "Facet mode",
154
176
  "attributeId": "Attribute",
155
177
  "isRequired": "Required",
156
178
  "isHighlighted": "Highlighted",
179
+ "isHighlight": "Highlight",
180
+ "isFilterVisible": "Visible in filters",
181
+ "isComparisonVisible": "Visible in comparison",
157
182
  "weight": "Weight",
158
183
  "siteId": "Site",
159
184
  "merchantId": "Merchant",
@@ -165,12 +190,19 @@
165
190
  "defaultCommissionValue": "Default commission",
166
191
  "priority": "Priority",
167
192
  "sourceType": "Source type",
168
- "isFilterable": "Filterable"
169
- },
170
- "summary": {
171
- "total": "Resource total",
172
- "visible": "Visible in list",
173
- "active": "Active"
193
+ "isFilterable": "Filterable",
194
+ "parentCategoryId": "Parent category",
195
+ "comparisonEnabled": "Comparison enabled",
196
+ "sortOrder": "Sort order",
197
+ "catalogCategoryId": "Category",
198
+ "isActive": "Active",
199
+ "optionValue": "Option value",
200
+ "isDefault": "Default",
201
+ "attributeOptionId": "Attribute option",
202
+ "rawValue": "Raw value",
203
+ "normalizedValue": "Normalized value",
204
+ "valueText": "Text value",
205
+ "isVerified": "Verified"
174
206
  },
175
207
  "valueLabels": {
176
208
  "status": {
@@ -189,42 +221,85 @@
189
221
  "availability_status": {
190
222
  "in_stock": "In stock",
191
223
  "out_of_stock": "Out of stock",
192
- "preorder": "Pre-order"
224
+ "pre_order": "Pre-order",
225
+ "unknown": "Unknown"
193
226
  },
194
227
  "site_type": {
195
- "portal": "Portal"
228
+ "portal": "Portal",
229
+ "niche": "Niche",
230
+ "tenant": "Tenant"
196
231
  },
197
232
  "merchant_type": {
198
- "retailer": "Retailer"
233
+ "retailer": "Retailer",
234
+ "marketplace": "Marketplace",
235
+ "saas": "SaaS",
236
+ "direct": "Direct"
199
237
  },
200
238
  "network_type": {
201
- "direct": "Direct"
239
+ "amazon": "Amazon",
240
+ "mercado_livre": "Mercado Livre",
241
+ "aliexpress": "AliExpress",
242
+ "kabum": "KaBuM!",
243
+ "direct": "Direct",
244
+ "network": "Network"
202
245
  },
203
246
  "commission_type": {
204
- "percentage": "Percentage"
247
+ "percentage": "Percentage",
248
+ "fixed": "Fixed"
205
249
  },
206
250
  "page_type": {
207
- "comparison": "Comparison"
251
+ "comparison": "Comparison",
252
+ "best_of": "Best of",
253
+ "cost_benefit": "Cost benefit",
254
+ "attribute": "Attribute",
255
+ "price_range": "Price range",
256
+ "brand": "Brand",
257
+ "use_case": "Use case"
208
258
  },
209
259
  "canonical_strategy": {
210
- "self": "Self"
260
+ "self": "Self",
261
+ "parent": "Parent",
262
+ "custom": "Custom"
211
263
  },
212
264
  "source_type": {
213
- "api": "API"
265
+ "api": "API",
266
+ "feed": "Feed",
267
+ "file": "File",
268
+ "crawler": "Crawler"
214
269
  },
215
270
  "data_type": {
216
271
  "text": "Text",
272
+ "long_text": "Long text",
217
273
  "number": "Number",
218
274
  "boolean": "Boolean",
219
- "json": "JSON"
275
+ "option": "Option"
220
276
  },
221
277
  "comparison_mode": {
222
- "neutral": "Neutral"
278
+ "neutral": "Neutral",
279
+ "higher_better": "Higher is better",
280
+ "lower_better": "Lower is better",
281
+ "boolean_true_better": "True is better",
282
+ "exact_match": "Exact match"
223
283
  },
224
284
  "facet_mode": {
225
285
  "default": "Default",
226
286
  "range": "Range",
227
- "select": "Select"
287
+ "select": "Select",
288
+ "hidden": "Hidden"
289
+ },
290
+ "comparison_type": {
291
+ "manual": "Manual",
292
+ "automatic": "Automatic",
293
+ "similarity": "Similarity",
294
+ "compatibility": "Compatibility"
295
+ },
296
+ "generation_mode": {
297
+ "manual": "Manual",
298
+ "automatic": "Automatic"
299
+ },
300
+ "is_verified": {
301
+ "true": "Verified",
302
+ "false": "Unverified"
228
303
  }
229
304
  }
230
305
  },
@@ -235,6 +310,11 @@
235
310
  "deleteError": "Failed to delete record."
236
311
  },
237
312
  "resources": {
313
+ "categories": {
314
+ "title": "Categories",
315
+ "shortTitle": "Categories",
316
+ "description": "Official catalog taxonomy for attributes, products, and publication."
317
+ },
238
318
  "brands": {
239
319
  "title": "Brands",
240
320
  "shortTitle": "Brands",
@@ -243,32 +323,42 @@
243
323
  "sites": {
244
324
  "title": "Sites",
245
325
  "shortTitle": "Sites",
246
- "description": "Domains, visual identity, and multi-site publishing scope."
326
+ "description": "Domains, visual identity, and multi-site publication."
247
327
  },
248
328
  "products": {
249
329
  "title": "Products",
250
330
  "shortTitle": "Products",
251
- "description": "Core product catalog with reusable relationships."
331
+ "description": "Main product catalog with structured attributes."
252
332
  },
253
333
  "attributeGroups": {
254
334
  "title": "Attribute groups",
255
335
  "shortTitle": "Groups",
256
- "description": "Visual and logical groupings for comparable specifications."
336
+ "description": "Visual and logical groupings for comparable attributes."
257
337
  },
258
338
  "attributes": {
259
339
  "title": "Attributes",
260
340
  "shortTitle": "Attributes",
261
- "description": "Dictionary of dynamic and comparable technical attributes."
341
+ "description": "Typed dictionary of comparable attributes."
342
+ },
343
+ "attributeOptions": {
344
+ "title": "Attribute options",
345
+ "shortTitle": "Options",
346
+ "description": "Enumerable options for select-type attributes."
262
347
  },
263
348
  "categoryAttributes": {
264
349
  "title": "Category attributes",
265
350
  "shortTitle": "Cat. attrs",
266
- "description": "Relevance and display rules for attributes by category."
351
+ "description": "Required, highlight, and facet rules per category."
352
+ },
353
+ "productAttributes": {
354
+ "title": "Product attributes",
355
+ "shortTitle": "Prod. attrs",
356
+ "description": "Structured attribute values by product."
267
357
  },
268
358
  "comparisons": {
269
359
  "title": "Comparisons",
270
360
  "shortTitle": "Comparisons",
271
- "description": "Manual and automated comparison pages across products."
361
+ "description": "Comparison pages between products."
272
362
  },
273
363
  "offers": {
274
364
  "title": "Offers",
@@ -278,22 +368,22 @@
278
368
  "merchants": {
279
369
  "title": "Merchants",
280
370
  "shortTitle": "Merchants",
281
- "description": "Stores, retailers, and commerce partners."
371
+ "description": "Stores, retailers, and business partners."
282
372
  },
283
373
  "affiliatePrograms": {
284
374
  "title": "Affiliate programs",
285
375
  "shortTitle": "Affiliate",
286
- "description": "Affiliate networks and direct programs per merchant."
376
+ "description": "Affiliate networks and direct programs by merchant."
287
377
  },
288
378
  "seoRules": {
289
379
  "title": "SEO rules",
290
380
  "shortTitle": "SEO",
291
- "description": "Rules for programmatic page generation and canonical behavior."
381
+ "description": "Rules for programmatic page generation."
292
382
  },
293
383
  "importSources": {
294
384
  "title": "Import sources",
295
385
  "shortTitle": "Imports",
296
- "description": "Feeds, APIs, and external integrations for catalog ingestion."
386
+ "description": "Feeds, APIs, and external integrations for ingestion."
297
387
  }
298
388
  }
299
389
  }