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',
},