diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 6732c1e7..e3c3ad72 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -2,11 +2,15 @@ import Footer from '@/components/Footer'; import Header from '@/components/Header'; import JsonLd from '@/components/JsonLd'; import AnalyticsProvider from '@/components/analytics/AnalyticsProvider'; -import { Viewport } from 'next'; +import { Metadata, Viewport } from 'next'; import { NextIntlClientProvider } from 'next-intl'; import { getMessages } from 'next-intl/server'; import '../../styles/globals.css'; +import { SITE_URL } from '@/lib/schema'; +export const metadata: Metadata = { + metadataBase: new URL(SITE_URL), +}; export const viewport: Viewport = { width: 'device-width', diff --git a/app/[locale]/products/[...slug]/opengraph-image.tsx b/app/[locale]/products/[...slug]/opengraph-image.tsx new file mode 100644 index 00000000..0cfb7e6d --- /dev/null +++ b/app/[locale]/products/[...slug]/opengraph-image.tsx @@ -0,0 +1,62 @@ +import { ImageResponse } from 'next/og'; +import { getProductBySlug } from '@/lib/mdx'; +import { getTranslations } from 'next-intl/server'; +import { OGImageTemplate } from '@/components/OGImageTemplate'; + +export const runtime = 'nodejs'; + +export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string[] } }) { + const t = await getTranslations('Products'); + const productSlug = slug[slug.length - 1]; + + // Check if it's a category page + const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables']; + if (categories.includes(productSlug)) { + const categoryKey = productSlug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase()); + const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : productSlug; + const categoryDesc = t.has(`categories.${categoryKey}.description`) ? t(`categories.${categoryKey}.description`) : ''; + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); + } + + const product = await getProductBySlug(productSlug, locale); + + if (!product) { + return new ImageResponse( +
+ ); + } + + const featuredImage = product.frontmatter.images?.[0] + ? (product.frontmatter.images[0].startsWith('http') + ? product.frontmatter.images[0] + : `https://klz-cables.com${product.frontmatter.images[0]}`) + : undefined; + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); +} diff --git a/app/[locale]/products/opengraph-image.tsx b/app/[locale]/products/opengraph-image.tsx index 54812d1a..79995d61 100644 --- a/app/[locale]/products/opengraph-image.tsx +++ b/app/[locale]/products/opengraph-image.tsx @@ -5,10 +5,31 @@ import { OGImageTemplate } from '@/components/OGImageTemplate'; export const runtime = 'nodejs'; -export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug: string[] } }) { - const productSlug = slug[slug.length - 1]; +export default async function Image({ params: { locale, slug } }: { params: { locale: string, slug?: string[] } }) { const t = await getTranslations('Products'); + // If no slug, it's the main products page + if (!slug || slug.length === 0) { + const title = t('meta.title') || t('title'); + const description = t('meta.description') || t('subtitle'); + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + } + ); + } + + const productSlug = slug[slug.length - 1]; + // Check if it's a category page const categories = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables']; if (categories.includes(productSlug)) { diff --git a/scripts/pdf/model/build-datasheet-model.ts b/scripts/pdf/model/build-datasheet-model.ts index e83dff3a..cc374973 100644 --- a/scripts/pdf/model/build-datasheet-model.ts +++ b/scripts/pdf/model/build-datasheet-model.ts @@ -159,26 +159,69 @@ function guessColumnKey(row: ExcelRow, patterns: RegExp[]): string | null { } function technicalFullLabel(args: { key: string; excelKey: string; locale: 'en' | 'de' }): string { - if (args.locale === 'en') return normalizeValue(args.excelKey); const raw = normalizeValue(args.excelKey); if (!raw) return ''; + + if (args.locale === 'de') { + return raw + .replace(/\(approx\.?\)/gi, '(ca.)') + .replace(/\bconductor material\b/gi, 'Leitermaterial') + .replace(/\bconductor class\b/gi, 'Leiterklasse') + .replace(/\bcore insulation\b/gi, 'Aderisolation') + .replace(/\binsulation\b/gi, 'Aderisolation') + .replace(/\bfield control\b/gi, 'Feldsteuerung') + .replace(/\bscreen\b/gi, 'Schirm') + .replace(/\blongitudinal water tightness\b/gi, 'Längswasserdichtigkeit') + .replace(/\btransverse water tightness\b/gi, 'Querwasserdichtigkeit') + .replace(/\bsheath material\b/gi, 'Mantelmaterial') + .replace(/\bsheath color\b/gi, 'Mantelfarbe') + .replace(/\bflame retardancy\b/gi, 'Flammwidrigkeit') + .replace(/\buv resistant\b/gi, 'UV-bestandig') + .replace(/\bmax\.? permissible conductor temperature\b/gi, 'Max. zulässige Leitertemperatur') + .replace(/\bpermissible cable outer temperature, fixed\b/gi, 'Zul. Kabelaußentemperatur, fest verlegt') + .replace(/\bpermissible cable outer temperature, in motion\b/gi, 'Zul. Kabelaußentemperatur, in Bewegung') + .replace(/\bmaximum short-circuit temperature\b/gi, 'Maximale Kurzschlußtemperatur') + .replace(/\bmin\.? bending radius, fixed\b/gi, 'Min. Biegeradius, fest verlegt') + .replace(/\bminimum laying temperature\b/gi, 'Mindesttemperatur Verlegung') + .replace(/\bmeter marking\b/gi, 'Metermarkierung') + .replace(/\bpartial discharge\b/gi, 'Teilentladung') + .replace(/\bcapacitance\b/gi, 'Kapazität') + .replace(/\binductance\b/gi, 'Induktivität') + .replace(/\breactance\b/gi, 'Reaktanz') + .replace(/\btest voltage\b/gi, 'Prüfspannung') + .replace(/\brated voltage\b/gi, 'Nennspannung') + .replace(/\boperating temperature range\b/gi, 'Temperaturbereich') + .replace(/\bminimum sheath thickness\b/gi, 'Manteldicke (min.)') + .replace(/\bsheath thickness\b/gi, 'Manteldicke') + .replace(/\bnominal insulation thickness\b/gi, 'Isolationsdicke (nom.)') + .replace(/\binsulation thickness\b/gi, 'Isolationsdicke') + .replace(/\bdc resistance at 20\s*°?c\b/gi, 'DC-Leiterwiderstand (20 °C)') + .replace(/\bouter diameter(?: of cable)?\b/gi, 'Außen-Ø') + .replace(/\bbending radius\b/gi, 'Biegeradius') + .replace(/\bpackaging\b/gi, 'Verpackung') + .replace(/\bce\s*-?conformity\b/gi, 'CE-Konformität'); + } + return raw - .replace(/\(approx\.?\)/gi, '(ca.)') - .replace(/\bcapacitance\b/gi, 'Kapazität') - .replace(/\binductance\b/gi, 'Induktivität') - .replace(/\breactance\b/gi, 'Reaktanz') - .replace(/\btest voltage\b/gi, 'Prüfspannung') - .replace(/\brated voltage\b/gi, 'Nennspannung') - .replace(/\boperating temperature range\b/gi, 'Temperaturbereich') - .replace(/\bminimum sheath thickness\b/gi, 'Manteldicke (min.)') - .replace(/\bsheath thickness\b/gi, 'Manteldicke') - .replace(/\bnominal insulation thickness\b/gi, 'Isolationsdicke (nom.)') - .replace(/\binsulation thickness\b/gi, 'Isolationsdicke') - .replace(/\bdc resistance at 20\s*°?c\b/gi, 'DC-Leiterwiderstand (20 °C)') - .replace(/\bouter diameter(?: of cable)?\b/gi, 'Außen-Ø') - .replace(/\bbending radius\b/gi, 'Biegeradius') - .replace(/\bpackaging\b/gi, 'Verpackung') - .replace(/\bce\s*-?conformity\b/gi, 'CE-Konformität'); + .replace(/\bconductor material\b/gi, 'Conductor material') + .replace(/\bconductor class\b/gi, 'Conductor class') + .replace(/\bcore insulation\b/gi, 'Core insulation') + .replace(/\bfield control\b/gi, 'Field control') + .replace(/\bscreen\b/gi, 'Screen') + .replace(/\blongitudinal water tightness\b/gi, 'Longitudinal water tightness') + .replace(/\btransverse water tightness\b/gi, 'Transverse water tightness') + .replace(/\bsheath material\b/gi, 'Sheath material') + .replace(/\bsheath color\b/gi, 'Sheath color') + .replace(/\bflame retardancy\b/gi, 'Flame retardancy') + .replace(/\buv resistant\b/gi, 'UV resistant') + .replace(/\bmax\.? permissible conductor temperature\b/gi, 'Max. permissible conductor temperature') + .replace(/\bpermissible cable outer temperature, fixed\b/gi, 'Permissible cable outer temperature, fixed') + .replace(/\bpermissible cable outer temperature, in motion\b/gi, 'Permissible cable outer temperature, in motion') + .replace(/\bmaximum short-circuit temperature\b/gi, 'Maximum short-circuit temperature') + .replace(/\bmin\.? bending radius, fixed\b/gi, 'Min. bending radius, fixed') + .replace(/\bminimum laying temperature\b/gi, 'Minimum laying temperature') + .replace(/\bmeter marking\b/gi, 'Meter marking') + .replace(/\bpartial discharge\b/gi, 'Partial discharge'); } function metaFullLabel(args: { key: string; excelKey: string; locale: 'en' | 'de' }): string { @@ -507,6 +550,26 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B 'flame retardant': { header: 'Flame retardant', unit: '', key: 'flame' }, 'self-extinguishing of single cable': { header: 'Flame retardant', unit: '', key: 'flame' }, + 'conductor material': { header: 'Conductor material', unit: '', key: 'cond_mat' }, + 'conductor class': { header: 'Conductor class', unit: '', key: 'cond_class' }, + 'core insulation': { header: 'Core insulation', unit: '', key: 'core_ins' }, + 'field control': { header: 'Field control', unit: '', key: 'field_ctrl' }, + 'screen': { header: 'Screen', unit: '', key: 'screen' }, + 'longitudinal water tightness': { header: 'Longitudinal water tightness', unit: '', key: 'long_water' }, + 'transverse water tightness': { header: 'Transverse water tightness', unit: '', key: 'trans_water' }, + 'sheath material': { header: 'Sheath material', unit: '', key: 'sheath_mat' }, + 'sheath color': { header: 'Sheath color', unit: '', key: 'sheath_color' }, + 'flame retardancy': { header: 'Flame retardancy', unit: '', key: 'flame_ret' }, + 'uv resistant': { header: 'UV resistant', unit: '', key: 'uv_res' }, + 'max. permissible conductor temperature': { header: 'Max. permissible conductor temperature', unit: '°C', key: 'max_cond_temp' }, + 'permissible cable outer temperature, fixed': { header: 'Permissible cable outer temperature, fixed', unit: '°C', key: 'out_temp_fixed' }, + 'permissible cable outer temperature, in motion': { header: 'Permissible cable outer temperature, in motion', unit: '°C', key: 'out_temp_motion' }, + 'maximum short-circuit temperature': { header: 'Maximum short-circuit temperature', unit: '°C', key: 'max_sc_temp_val' }, + 'min. bending radius, fixed': { header: 'Min. bending radius, fixed', unit: '', key: 'min_bend_fixed' }, + 'minimum laying temperature': { header: 'Minimum laying temperature', unit: '°C', key: 'min_lay_temp_val' }, + 'meter marking': { header: 'Meter marking', unit: '', key: 'meter_mark' }, + 'partial discharge': { header: 'Partial discharge', unit: 'pC', key: 'partial_dis' }, + // High-value electrical/screen columns 'capacitance (approx.)': { header: 'Capacitance', unit: 'uF/km', key: 'cap' }, 'capacitance': { header: 'Capacitance', unit: 'uF/km', key: 'cap' }, @@ -892,7 +955,20 @@ export function buildDatasheetModel(args: { product: ProductData; locale: 'en' | productUrl, }, labels, - technicalItems: excelModel.ok ? excelModel.technicalItems : [], + technicalItems: [ + ...(excelModel.ok ? excelModel.technicalItems : []), + ...(args.locale === 'de' + ? [ + { label: 'Prüfspannung 6/10 kV', value: '21 kV' }, + { label: 'Prüfspannung 12/20 kV', value: '42 kV' }, + { label: 'Prüfspannung 18/30 kV', value: '63 kV' }, + ] + : [ + { label: 'Test voltage 6/10 kV', value: '21 kV' }, + { label: 'Test voltage 12/20 kV', value: '42 kV' }, + { label: 'Test voltage 18/30 kV', value: '63 kV' }, + ]), + ], voltageTables, legendItems: crossSectionModel.legendItems || [], }; diff --git a/scripts/pdf/model/utils.ts b/scripts/pdf/model/utils.ts index 67c67527..6992436e 100644 --- a/scripts/pdf/model/utils.ts +++ b/scripts/pdf/model/utils.ts @@ -53,18 +53,18 @@ export function generateFileName(product: ProductData, locale: 'en' | 'de'): str export function getLabels(locale: 'en' | 'de') { return { en: { - datasheet: 'PRODUCT DATASHEET', + datasheet: 'Technical Datasheet', description: 'DESCRIPTION', technicalData: 'TECHNICAL DATA', - crossSection: 'CROSS-SECTION DATA', + crossSection: 'Cross-sections/Voltage', sku: 'SKU', noImage: 'No image available', }, de: { - datasheet: 'PRODUKTDATENBLATT', + datasheet: 'Technisches Datenblatt', description: 'BESCHREIBUNG', technicalData: 'TECHNISCHE DATEN', - crossSection: 'QUERSCHNITTSDATEN', + crossSection: 'Querschnitte/Spannung', sku: 'ARTIKELNUMMER', noImage: 'Kein Bild verfügbar', },