slug i18n
This commit is contained in:
@@ -6,6 +6,7 @@ import RelatedProducts from '@/components/RelatedProducts';
|
||||
import { Badge, Container, Section } from '@/components/ui';
|
||||
import { getDatasheetPath } from '@/lib/datasheets';
|
||||
import { getAllProducts, getProductBySlug } from '@/lib/mdx';
|
||||
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||
import { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
@@ -38,9 +39,9 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
||||
alternates: {
|
||||
canonical: `/${locale}/products/${productSlug}`,
|
||||
languages: {
|
||||
'de': `/de/products/${productSlug}`,
|
||||
'en': `/en/products/${productSlug}`,
|
||||
'x-default': `/en/products/${productSlug}`,
|
||||
'de': `/de/products/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
'en': `/en/products/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `/en/products/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
@@ -65,9 +66,9 @@ export async function generateMetadata({ params }: ProductPageProps): Promise<Me
|
||||
alternates: {
|
||||
canonical: `/${locale}/products/${slug.join('/')}`,
|
||||
languages: {
|
||||
'de': `/de/products/${slug.join('/')}`,
|
||||
'en': `/en/products/${slug.join('/')}`,
|
||||
'x-default': `/en/products/${slug.join('/')}`,
|
||||
'de': `/de/products/${await mapFileSlugToTranslated(slug[0], 'de')}/${await mapFileSlugToTranslated(productSlug, 'de')}`,
|
||||
'en': `/en/products/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
'x-default': `/en/products/${await mapFileSlugToTranslated(slug[0], 'en')}/${await mapFileSlugToTranslated(productSlug, 'en')}`,
|
||||
},
|
||||
},
|
||||
openGraph: {
|
||||
@@ -133,13 +134,21 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
const categoryTitle = t(`categories.${categoryKey}.title`);
|
||||
|
||||
// Filter products for this category
|
||||
const filteredProducts = allProducts.filter(p =>
|
||||
p.frontmatter.categories.some(cat =>
|
||||
cat.toLowerCase().replace(/\s+/g, '-') === productSlug ||
|
||||
const filteredProducts = allProducts.filter(p =>
|
||||
p.frontmatter.categories.some(cat =>
|
||||
cat.toLowerCase().replace(/\s+/g, '-') === productSlug ||
|
||||
cat === categoryTitle
|
||||
)
|
||||
);
|
||||
|
||||
// Get translated product slugs
|
||||
const productsWithTranslatedSlugs = await Promise.all(
|
||||
filteredProducts.map(async (p) => ({
|
||||
...p,
|
||||
translatedSlug: await mapFileSlugToTranslated(p.slug, locale)
|
||||
}))
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-white">
|
||||
<section className="relative min-h-[50vh] flex items-center pt-32 pb-20 overflow-hidden bg-primary-dark">
|
||||
@@ -161,10 +170,10 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
<Section className="bg-neutral-light relative">
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredProducts.map((product) => (
|
||||
<Link
|
||||
key={product.slug}
|
||||
href={`/${locale}/products/${productSlug}/${product.slug}`}
|
||||
{productsWithTranslatedSlugs.map((product) => (
|
||||
<Link
|
||||
key={product.slug}
|
||||
href={`/${locale}/products/${productSlug}/${product.translatedSlug}`}
|
||||
className="group block bg-white rounded-[32px] overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-500 border border-neutral-dark/5"
|
||||
>
|
||||
<div className="aspect-[4/3] relative bg-neutral-light/30 p-12 overflow-hidden">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Reveal from '@/components/Reveal';
|
||||
import Scribble from '@/components/Scribble';
|
||||
import { Badge, Button, Card, Container, Section } from '@/components/ui';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||
|
||||
interface ProductsPageProps {
|
||||
params: {
|
||||
@@ -39,37 +39,43 @@ export async function generateMetadata({ params: { locale } }: ProductsPageProps
|
||||
};
|
||||
}
|
||||
|
||||
export default function ProductsPage({ params }: ProductsPageProps) {
|
||||
const t = useTranslations('Products');
|
||||
export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
const t = await getTranslations('Products');
|
||||
|
||||
// Get translated category slugs
|
||||
const lowVoltageSlug = await mapFileSlugToTranslated('low-voltage-cables', params.locale);
|
||||
const mediumVoltageSlug = await mapFileSlugToTranslated('medium-voltage-cables', params.locale);
|
||||
const highVoltageSlug = await mapFileSlugToTranslated('high-voltage-cables', params.locale);
|
||||
const solarSlug = await mapFileSlugToTranslated('solar-cables', params.locale);
|
||||
|
||||
const categories = [
|
||||
{
|
||||
title: t('categories.lowVoltage.title'),
|
||||
{
|
||||
title: t('categories.lowVoltage.title'),
|
||||
desc: t('categories.lowVoltage.description'),
|
||||
img: '/uploads/2024/11/low-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/Low-Voltage.svg',
|
||||
href: `/${params.locale}/products/low-voltage-cables`
|
||||
href: `/${params.locale}/products/${lowVoltageSlug}`
|
||||
},
|
||||
{
|
||||
title: t('categories.mediumVoltage.title'),
|
||||
{
|
||||
title: t('categories.mediumVoltage.title'),
|
||||
desc: t('categories.mediumVoltage.description'),
|
||||
img: '/uploads/2024/11/medium-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/Medium-Voltage.svg',
|
||||
href: `/${params.locale}/products/medium-voltage-cables`
|
||||
href: `/${params.locale}/products/${mediumVoltageSlug}`
|
||||
},
|
||||
{
|
||||
title: t('categories.highVoltage.title'),
|
||||
{
|
||||
title: t('categories.highVoltage.title'),
|
||||
desc: t('categories.highVoltage.description'),
|
||||
img: '/uploads/2024/11/high-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/High-Voltage.svg',
|
||||
href: `/${params.locale}/products/high-voltage-cables`
|
||||
href: `/${params.locale}/products/${highVoltageSlug}`
|
||||
},
|
||||
{
|
||||
title: t('categories.solar.title'),
|
||||
{
|
||||
title: t('categories.solar.title'),
|
||||
desc: t('categories.solar.description'),
|
||||
img: '/uploads/2024/11/solar-category.webp',
|
||||
icon: '/uploads/2024/11/Solar.svg',
|
||||
href: `/${params.locale}/products/solar-cables`
|
||||
href: `/${params.locale}/products/${solarSlug}`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -124,7 +130,7 @@ export default function ProductsPage({ params }: ProductsPageProps) {
|
||||
<div className="absolute inset-0 image-overlay-gradient opacity-80 group-hover:opacity-90 transition-opacity duration-500" />
|
||||
|
||||
<div className="absolute top-3 right-3 md:top-8 md:right-8 w-10 h-10 md:w-20 md:h-20 bg-white/10 backdrop-blur-md rounded-xl md:rounded-[24px] flex items-center justify-center border border-white/20 shadow-2xl transition-all duration-500 group-hover:scale-110 group-hover:bg-white/20">
|
||||
<img src={category.icon} alt="" className="w-6 h-6 md:w-12 md:h-12 brightness-0 invert opacity-80" />
|
||||
<Image src={category.icon} alt="" width={24} height={24} className="w-6 h-6 md:w-12 md:h-12 brightness-0 invert opacity-80" />
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 left-4 md:bottom-10 md:left-10 right-4 md:right-10">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getAllProducts } from '@/lib/mdx';
|
||||
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
@@ -35,7 +36,7 @@ export default async function RelatedProducts({ currentSlug, categories, locale
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{related.map((product) => {
|
||||
{related.map(async (product) => {
|
||||
// Find the category slug for the link
|
||||
const categorySlugs = ['low-voltage-cables', 'medium-voltage-cables', 'high-voltage-cables', 'solar-cables'];
|
||||
const catSlug = categorySlugs.find(slug => {
|
||||
@@ -46,10 +47,12 @@ export default async function RelatedProducts({ currentSlug, categories, locale
|
||||
);
|
||||
}) || 'low-voltage-cables';
|
||||
|
||||
const translatedProductSlug = await mapFileSlugToTranslated(product.slug, locale);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={product.slug}
|
||||
href={`/${locale}/products/${catSlug}/${product.slug}`}
|
||||
href={`/${locale}/products/${catSlug}/${translatedProductSlug}`}
|
||||
className="group block bg-white rounded-[32px] overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-500 border border-neutral-dark/5"
|
||||
>
|
||||
<div className="aspect-[16/10] relative bg-neutral-light/30 p-8 overflow-hidden">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import { mapSlugToFileSlug } from './slugs';
|
||||
|
||||
export interface PostFrontmatter {
|
||||
title: string;
|
||||
@@ -18,8 +19,10 @@ export interface PostMdx {
|
||||
}
|
||||
|
||||
export async function getPostBySlug(slug: string, locale: string): Promise<PostMdx | null> {
|
||||
// Map translated slug to file slug
|
||||
const fileSlug = await mapSlugToFileSlug(slug, locale);
|
||||
const postsDir = path.join(process.cwd(), 'data', 'blog', locale);
|
||||
const filePath = path.join(postsDir, `${slug}.mdx`);
|
||||
const filePath = path.join(postsDir, `${fileSlug}.mdx`);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
@@ -29,7 +32,7 @@ export async function getPostBySlug(slug: string, locale: string): Promise<PostM
|
||||
const { data, content } = matter(fileContent);
|
||||
|
||||
return {
|
||||
slug,
|
||||
slug: fileSlug,
|
||||
frontmatter: data as PostFrontmatter,
|
||||
content,
|
||||
};
|
||||
|
||||
15
lib/mdx.ts
15
lib/mdx.ts
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import { mapSlugToFileSlug } from './slugs';
|
||||
|
||||
export interface ProductFrontmatter {
|
||||
title: string;
|
||||
@@ -18,30 +19,32 @@ export interface ProductMdx {
|
||||
}
|
||||
|
||||
export async function getProductBySlug(slug: string, locale: string): Promise<ProductMdx | null> {
|
||||
// Map translated slug to file slug
|
||||
const fileSlug = await mapSlugToFileSlug(slug, locale);
|
||||
const productsDir = path.join(process.cwd(), 'data', 'products', locale);
|
||||
|
||||
// Try exact slug first
|
||||
let filePath = path.join(productsDir, `${slug}.mdx`);
|
||||
let filePath = path.join(productsDir, `${fileSlug}.mdx`);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
// Try with -2 suffix (common in the dumped files)
|
||||
filePath = path.join(productsDir, `${slug}-2.mdx`);
|
||||
filePath = path.join(productsDir, `${fileSlug}-2.mdx`);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
// Fallback to English if locale is not 'en'
|
||||
if (locale !== 'en') {
|
||||
const enProductsDir = path.join(process.cwd(), 'data', 'products', 'en');
|
||||
let enFilePath = path.join(enProductsDir, `${slug}.mdx`);
|
||||
let enFilePath = path.join(enProductsDir, `${fileSlug}.mdx`);
|
||||
if (!fs.existsSync(enFilePath)) {
|
||||
enFilePath = path.join(enProductsDir, `${slug}-2.mdx`);
|
||||
enFilePath = path.join(enProductsDir, `${fileSlug}-2.mdx`);
|
||||
}
|
||||
|
||||
if (fs.existsSync(enFilePath)) {
|
||||
const fileContent = fs.readFileSync(enFilePath, 'utf8');
|
||||
const { data, content } = matter(fileContent);
|
||||
return {
|
||||
slug,
|
||||
slug: fileSlug,
|
||||
frontmatter: {
|
||||
...data,
|
||||
isFallback: true
|
||||
@@ -57,7 +60,7 @@ export async function getProductBySlug(slug: string, locale: string): Promise<Pr
|
||||
const { data, content } = matter(fileContent);
|
||||
|
||||
return {
|
||||
slug,
|
||||
slug: fileSlug,
|
||||
frontmatter: data as ProductFrontmatter,
|
||||
content,
|
||||
};
|
||||
|
||||
19
lib/pages.ts
19
lib/pages.ts
@@ -1,6 +1,7 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import { mapSlugToFileSlug } from './slugs';
|
||||
|
||||
export interface PageFrontmatter {
|
||||
title: string;
|
||||
@@ -16,8 +17,10 @@ export interface PageMdx {
|
||||
}
|
||||
|
||||
export async function getPageBySlug(slug: string, locale: string): Promise<PageMdx | null> {
|
||||
// Map translated slug to file slug
|
||||
const fileSlug = await mapSlugToFileSlug(slug, locale);
|
||||
const pagesDir = path.join(process.cwd(), 'data', 'pages', locale);
|
||||
const filePath = path.join(pagesDir, `${slug}.mdx`);
|
||||
const filePath = path.join(pagesDir, `${fileSlug}.mdx`);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
@@ -27,7 +30,7 @@ export async function getPageBySlug(slug: string, locale: string): Promise<PageM
|
||||
const { data, content } = matter(fileContent);
|
||||
|
||||
return {
|
||||
slug,
|
||||
slug: fileSlug,
|
||||
frontmatter: data as PageFrontmatter,
|
||||
content,
|
||||
};
|
||||
@@ -41,7 +44,17 @@ export async function getAllPages(locale: string): Promise<PageMdx[]> {
|
||||
const pages = await Promise.all(
|
||||
files
|
||||
.filter(file => file.endsWith('.mdx'))
|
||||
.map(file => getPageBySlug(file.replace(/\.mdx$/, ''), locale))
|
||||
.map(file => {
|
||||
const fileSlug = file.replace(/\.mdx$/, '');
|
||||
const filePath = path.join(pagesDir, file);
|
||||
const fileContent = { content: fs.readFileSync(filePath, 'utf8') };
|
||||
const { data, content } = matter(fileContent.content);
|
||||
return {
|
||||
slug: fileSlug,
|
||||
frontmatter: data as PageFrontmatter,
|
||||
content,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return pages.filter((p): p is PageMdx => p !== null);
|
||||
|
||||
96
lib/slugs.ts
Normal file
96
lib/slugs.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
/**
|
||||
* Maps a translated slug to original file slug
|
||||
* @param translatedSlug - The slug from URL (translated)
|
||||
* @param _locale - The current locale (unused, kept for API consistency)
|
||||
* @returns The original file slug, or input slug if no mapping exists
|
||||
*/
|
||||
export async function mapSlugToFileSlug(translatedSlug: string, _locale: string): Promise<string> {
|
||||
const t = await getTranslations('Slugs');
|
||||
|
||||
// Check pages
|
||||
try {
|
||||
const pageSlug = t.raw(`pages.${translatedSlug}`);
|
||||
if (pageSlug && typeof pageSlug === 'string') {
|
||||
return pageSlug;
|
||||
}
|
||||
} catch {
|
||||
// Key doesn't exist, continue
|
||||
}
|
||||
|
||||
// Check products
|
||||
try {
|
||||
const productSlug = t.raw(`products.${translatedSlug}`);
|
||||
if (productSlug && typeof productSlug === 'string') {
|
||||
return productSlug;
|
||||
}
|
||||
} catch {
|
||||
// Key doesn't exist, continue
|
||||
}
|
||||
|
||||
// Check categories
|
||||
try {
|
||||
const categorySlug = t.raw(`categories.${translatedSlug}`);
|
||||
if (categorySlug && typeof categorySlug === 'string') {
|
||||
return categorySlug;
|
||||
}
|
||||
} catch {
|
||||
// Key doesn't exist, continue
|
||||
}
|
||||
|
||||
// Return original slug if no mapping found
|
||||
return translatedSlug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an original file slug to translated slug for a locale
|
||||
* @param fileSlug - The original file slug
|
||||
* @param _locale - The target locale (unused, kept for API consistency)
|
||||
* @returns The translated slug, or input slug if no mapping exists
|
||||
*/
|
||||
export async function mapFileSlugToTranslated(fileSlug: string, _locale: string): Promise<string> {
|
||||
const t = await getTranslations('Slugs');
|
||||
|
||||
// Find the key that maps to this file slug
|
||||
const sections = ['pages', 'products', 'categories'];
|
||||
|
||||
for (const section of sections) {
|
||||
try {
|
||||
const sectionData = t.raw(section);
|
||||
if (sectionData && typeof sectionData === 'object') {
|
||||
for (const [translatedSlug, mappedSlug] of Object.entries(sectionData)) {
|
||||
if (mappedSlug === fileSlug) {
|
||||
return translatedSlug;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Section doesn't exist, continue
|
||||
}
|
||||
}
|
||||
|
||||
// Return original slug if no mapping found
|
||||
return fileSlug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all translated slugs for a section
|
||||
* @param section - The section name (pages, products, categories)
|
||||
* @param _locale - The current locale (unused, kept for API consistency)
|
||||
* @returns Object mapping translated slugs to file slugs
|
||||
*/
|
||||
export async function getSlugMappings(section: 'pages' | 'products' | 'categories', _locale: string): Promise<Record<string, string>> {
|
||||
const t = await getTranslations('Slugs');
|
||||
|
||||
try {
|
||||
const sectionData = t.raw(section);
|
||||
if (sectionData && typeof (sectionData as any) === 'object') {
|
||||
return sectionData as Record<string, string>;
|
||||
}
|
||||
} catch {
|
||||
// Section doesn't exist
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -1,4 +1,50 @@
|
||||
{
|
||||
"Slugs": {
|
||||
"pages": {
|
||||
"impressum": "legal-notice",
|
||||
"datenschutz": "privacy-policy",
|
||||
"agbs": "terms",
|
||||
"kontakt": "contact",
|
||||
"team": "team",
|
||||
"blog": "blog",
|
||||
"produkte": "products",
|
||||
"start": "start",
|
||||
"danke": "thanks"
|
||||
},
|
||||
"products": {
|
||||
"n2x2y": "n2x2y",
|
||||
"n2xfk2y": "n2xfk2y",
|
||||
"n2xfkld2y": "n2xfkld2y",
|
||||
"n2xs2y": "n2xs2y",
|
||||
"n2xsf2y": "n2xsf2y",
|
||||
"n2xsfl2y-hv": "n2xsfl2y-hv",
|
||||
"n2xsfl2y-mv": "n2xsfl2y-mv",
|
||||
"n2xsy": "n2xsy",
|
||||
"n2xy": "n2xy",
|
||||
"na2x2y": "na2x2y",
|
||||
"na2xfk2y": "na2xfk2y",
|
||||
"na2xfkld2y": "na2xfkld2y",
|
||||
"na2xs2y": "na2xs2y",
|
||||
"na2xsf2y": "na2xsf2y",
|
||||
"na2xsfl2y-hv": "na2xsfl2y-hv",
|
||||
"na2xsfl2y-mv": "na2xsfl2y-mv",
|
||||
"na2xsy": "na2xsy",
|
||||
"na2xy": "na2xy",
|
||||
"nay2y": "nay2y",
|
||||
"naycwy": "naycwy",
|
||||
"nayy": "nayy",
|
||||
"ny2y": "ny2y",
|
||||
"nycwy": "nycwy",
|
||||
"nyy": "nyy",
|
||||
"h1z2z2-k": "h1z2z2-k"
|
||||
},
|
||||
"categories": {
|
||||
"niederspannungskabel": "low-voltage-cables",
|
||||
"mittelspannungskabel": "medium-voltage-cables",
|
||||
"hochspannungskabel": "high-voltage-cables",
|
||||
"solarkabel": "solar-cables"
|
||||
}
|
||||
},
|
||||
"Index": {
|
||||
"title": "KLZ Cables - Hochwertige Kabel",
|
||||
"description": "Ihr Partner für hochwertige Kabel.",
|
||||
|
||||
@@ -1,4 +1,50 @@
|
||||
{
|
||||
"Slugs": {
|
||||
"pages": {
|
||||
"legal-notice": "legal-notice",
|
||||
"privacy-policy": "privacy-policy",
|
||||
"terms": "terms",
|
||||
"contact": "contact",
|
||||
"team": "team",
|
||||
"blog": "blog",
|
||||
"products": "products",
|
||||
"start": "start",
|
||||
"thanks": "thanks"
|
||||
},
|
||||
"products": {
|
||||
"n2x2y": "n2x2y",
|
||||
"n2xfk2y": "n2xfk2y",
|
||||
"n2xfkld2y": "n2xfkld2y",
|
||||
"n2xs2y": "n2xs2y",
|
||||
"n2xsf2y": "n2xsf2y",
|
||||
"n2xsfl2y-hv": "n2xsfl2y-hv",
|
||||
"n2xsfl2y-mv": "n2xsfl2y-mv",
|
||||
"n2xsy": "n2xsy",
|
||||
"n2xy": "n2xy",
|
||||
"na2x2y": "na2x2y",
|
||||
"na2xfk2y": "na2xfk2y",
|
||||
"na2xfkld2y": "na2xfkld2y",
|
||||
"na2xs2y": "na2xs2y",
|
||||
"na2xsf2y": "na2xsf2y",
|
||||
"na2xsfl2y-hv": "na2xsfl2y-hv",
|
||||
"na2xsfl2y-mv": "na2xsfl2y-mv",
|
||||
"na2xsy": "na2xsy",
|
||||
"na2xy": "na2xy",
|
||||
"nay2y": "nay2y",
|
||||
"naycwy": "naycwy",
|
||||
"nayy": "nayy",
|
||||
"ny2y": "ny2y",
|
||||
"nycwy": "nycwy",
|
||||
"nyy": "nyy",
|
||||
"h1z2z2-k": "h1z2z2-k"
|
||||
},
|
||||
"categories": {
|
||||
"low-voltage-cables": "low-voltage-cables",
|
||||
"medium-voltage-cables": "medium-voltage-cables",
|
||||
"high-voltage-cables": "high-voltage-cables",
|
||||
"solar-cables": "solar-cables"
|
||||
}
|
||||
},
|
||||
"Index": {
|
||||
"title": "KLZ Cables - High Quality Cables",
|
||||
"description": "Your partner for high quality cables.",
|
||||
|
||||
Reference in New Issue
Block a user