Compare commits

...

2 Commits

Author SHA1 Message Date
bb7d17001b og
All checks were successful
Build & Deploy KLZ Cables / build-and-deploy (push) Successful in 3m57s
2026-01-29 16:33:04 +01:00
920efa0083 pdf sheets 2026-01-29 16:27:09 +01:00
4 changed files with 173 additions and 21 deletions

View File

@@ -2,18 +2,29 @@ import { ImageResponse } from 'next/og';
import { getProductBySlug } from '@/lib/mdx';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
import { NextRequest } from 'next/server';
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];
export async function GET(
request: NextRequest,
{ params }: { params: { locale: string } }
) {
const { searchParams } = new URL(request.url);
const slug = searchParams.get('slug');
const locale = params.locale || 'en';
if (!slug) {
return new Response('Missing slug', { status: 400 });
}
const t = await getTranslations({ locale, namespace: 'Products' });
// 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;
if (categories.includes(slug)) {
const categoryKey = slug.replace(/-cables$/, '').replace(/-([a-z])/g, (g) => g[1].toUpperCase());
const categoryTitle = t.has(`categories.${categoryKey}.title`) ? t(`categories.${categoryKey}.title`) : slug;
const categoryDesc = t.has(`categories.${categoryKey}.description`) ? t(`categories.${categoryKey}.description`) : '';
return new ImageResponse(
@@ -31,7 +42,7 @@ export default async function Image({ params: { locale, slug } }: { params: { lo
);
}
const product = await getProductBySlug(productSlug, locale);
const product = await getProductBySlug(slug, locale);
if (!product) {
return new ImageResponse(

View File

@@ -0,0 +1,25 @@
import { ImageResponse } from 'next/og';
import { getTranslations } from 'next-intl/server';
import { OGImageTemplate } from '@/components/OGImageTemplate';
export const runtime = 'nodejs';
export default async function Image({ params: { locale } }: { params: { locale: string } }) {
const t = await getTranslations({ locale, namespace: 'Blog.meta' });
const title = t('title');
const description = t('description');
return new ImageResponse(
(
<OGImageTemplate
title={title}
description={description}
label="Blog"
/>
),
{
width: 1200,
height: 630,
}
);
}

View File

@@ -51,6 +51,14 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
title: `${categoryTitle} | KLZ Cables`,
description: categoryDesc,
url: `https://klz-cables.com/${locale}/products/${productSlug}`,
images: [
{
url: `/api/og/product?slug=${fileSlug}`,
width: 1200,
height: 630,
alt: categoryTitle,
},
],
},
twitter: {
card: 'summary_large_image',
@@ -79,6 +87,14 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
description: product.frontmatter.description,
type: 'website',
url: `https://klz-cables.com/${locale}/products/${slug.join('/')}`,
images: [
{
url: `/api/og/product?slug=${productSlug}`,
width: 1200,
height: 630,
alt: product.frontmatter.title,
},
],
},
twitter: {
card: 'summary_large_image',

View File

@@ -206,6 +206,7 @@ function technicalFullLabel(args: { key: string; excelKey: string; locale: 'en'
.replace(/\bconductor material\b/gi, 'Conductor material')
.replace(/\bconductor class\b/gi, 'Conductor class')
.replace(/\bcore insulation\b/gi, 'Core insulation')
.replace(/\binsulation\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')
@@ -221,7 +222,49 @@ function technicalFullLabel(args: { key: string; excelKey: string; locale: 'en'
.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');
.replace(/\bpartial discharge\b/gi, 'Partial discharge')
.replace(/\bcapacitance\b/gi, 'Capacitance')
.replace(/\binductance\b/gi, 'Inductance')
.replace(/\breactance\b/gi, 'Reactance')
.replace(/\btest voltage\b/gi, 'Test voltage')
.replace(/\brated voltage\b/gi, 'Rated voltage')
.replace(/\boperating temperature range\b/gi, 'Operating temperature range')
.replace(/\bminimum sheath thickness\b/gi, 'Sheath thickness (min.)')
.replace(/\bsheath thickness\b/gi, 'Sheath thickness')
.replace(/\bnominal insulation thickness\b/gi, 'Insulation thickness (nom.)')
.replace(/\binsulation thickness\b/gi, 'Insulation thickness')
.replace(/\bdc resistance at 20\s*°?c\b/gi, 'DC resistance (20 °C)')
.replace(/\bouter diameter(?: of cable)?\b/gi, 'Outer diameter')
.replace(/\bbending radius\b/gi, 'Bending radius')
.replace(/\bpackaging\b/gi, 'Packaging')
.replace(/\bce\s*-?conformity\b/gi, 'CE conformity');
}
function technicalValueTranslation(args: { label: string; value: string; locale: 'en' | 'de' }): string {
const v = normalizeValue(args.value);
if (!v) return '';
if (args.locale === 'de') {
if (/^yes$/i.test(v)) return 'ja';
if (/^no$/i.test(v)) return 'nein';
if (/^copper$/i.test(v)) return 'Kupfer';
if (/^aluminum$/i.test(v)) return 'Aluminium';
if (/^black$/i.test(v)) return 'schwarz';
if (/^stranded$/i.test(v)) return 'mehrdrähtig';
if (/^(\d+)xD$/i.test(v)) return v.replace(/^(\d+)xD$/i, '$1 facher Durchmesser');
if (/^XLPE/i.test(v)) return v.replace(/^XLPE/i, 'VPE');
return v;
}
if (/^ja$/i.test(v)) return 'yes';
if (/^nein$/i.test(v)) return 'no';
if (/^kupfer$/i.test(v)) return 'Copper';
if (/^aluminium$/i.test(v)) return 'Aluminum';
if (/^schwarz$/i.test(v)) return 'black';
if (/^mehrdrähtig$/i.test(v)) return 'stranded';
if (/^(\d+)xD$/i.test(v)) return v.replace(/^(\d+)xD$/i, '$1 times diameter');
if (/^VPE/i.test(v)) return v.replace(/^VPE/i, 'XLPE');
return v;
}
function metaFullLabel(args: { key: string; excelKey: string; locale: 'en' | 'de' }): string {
@@ -642,11 +685,66 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
const unit = normalizeUnit(units[excelKey] || mapping.unit || '');
const labelBase = technicalFullLabel({ key: mapping.key, excelKey, locale: args.locale });
const label = formatExcelHeaderLabel(labelBase, unit);
const value = compactCellForDenseTable(values[0], unit, args.locale);
const rawValue = compactCellForDenseTable(values[0], unit, args.locale);
const value = technicalValueTranslation({ label: labelBase, value: rawValue, locale: args.locale });
if (!technicalItems.find(t => t.label === label)) technicalItems.push({ label, value, unit });
}
}
technicalItems.sort((a, b) => a.label.localeCompare(b.label));
const TECHNICAL_DATA_ORDER_DE = [
'Leitermaterial',
'Leiterklasse',
'Aderisolation',
'Feldsteuerung',
'Schirm',
'Längswasserdichtigkeit',
'Querwasserdichtigkeit',
'Mantelmaterial',
'Mantelfarbe',
'Flammwidrigkeit',
'UV-bestandig',
'Max. zulässige Leitertemperatur',
'Zul. Kabelaußentemperatur, fest verlegt',
'Zul. Kabelaußentemperatur, in Bewegung',
'Maximale Kurzschlußtemperatur',
'Min. Biegeradius, fest verlegt',
'Mindesttemperatur Verlegung',
'Metermarkierung',
'Teilentladung',
];
const TECHNICAL_DATA_ORDER_EN = [
'Conductor material',
'Conductor class',
'Core insulation',
'Field control',
'Screen',
'Longitudinal water tightness',
'Transverse water tightness',
'Sheath material',
'Sheath color',
'Flame retardancy',
'UV resistant',
'Max. permissible conductor temperature',
'Permissible cable outer temperature, fixed',
'Permissible cable outer temperature, in motion',
'Maximum short-circuit temperature',
'Min. bending radius, fixed',
'Minimum laying temperature',
'Meter marking',
'Partial discharge',
];
const order = args.locale === 'de' ? TECHNICAL_DATA_ORDER_DE : TECHNICAL_DATA_ORDER_EN;
technicalItems.sort((a, b) => {
const indexA = order.findIndex(label => a.label.startsWith(label));
const indexB = order.findIndex(label => b.label.startsWith(label));
if (indexA !== -1 && indexB !== -1) return indexA - indexB;
if (indexA !== -1) return -1;
if (indexB !== -1) return 1;
return a.label.localeCompare(b.label);
});
const voltageTables: VoltageTableModel[] = [];
for (const vKey of voltageKeysSorted) {
@@ -957,17 +1055,19 @@ export function buildDatasheetModel(args: { product: ProductData; locale: 'en' |
labels,
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' },
]),
...(isMediumVoltageProduct(args.product)
? 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 || [],