Ensure correct URL handling for product page translations
This commit is contained in:
parent
a5fd59b1cd
commit
020679f26c
18 changed files with 664 additions and 73 deletions
|
@ -1,6 +1,5 @@
|
|||
import { defineConfig } from "astro/config";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import vercelStatic from "@astrojs/vercel/static";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
import compressor from "astro-compressor";
|
||||
import starlight from "@astrojs/starlight";
|
||||
|
@ -123,5 +122,4 @@ export default defineConfig({
|
|||
clientPrerender: true,
|
||||
directRenderScript: true,
|
||||
},
|
||||
adapter: vercelStatic(),
|
||||
});
|
||||
|
|
53
package-lock.json
generated
53
package-lock.json
generated
|
@ -12,7 +12,6 @@
|
|||
"@astrojs/starlight": "^0.26.2",
|
||||
"@astrojs/starlight-tailwind": "^2.0.3",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@astrojs/vercel": "^7.8.0",
|
||||
"@preline/accordion": "^2.4.1",
|
||||
"@preline/collapse": "^2.4.1",
|
||||
"@preline/dropdown": "^2.4.1",
|
||||
|
@ -278,23 +277,6 @@
|
|||
"node": "^18.17.1 || ^20.3.0 || >=21.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/vercel": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-7.8.0.tgz",
|
||||
"integrity": "sha512-cpY14PPrKhAsYZp/tfRSIfKdiPctvvPfOH8z3USLJEAJ5lLfToUHEGTJzNfGqCBtd61QftypIxIlTHGuFY30UQ==",
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "0.4.1",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/edge": "^1.1.2",
|
||||
"@vercel/nft": "^0.27.3",
|
||||
"esbuild": "^0.21.5",
|
||||
"fast-glob": "^3.3.2",
|
||||
"web-vitals": "^3.5.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/yaml2ts": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.1.tgz",
|
||||
|
@ -2078,31 +2060,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
|
||||
},
|
||||
"node_modules/@vercel/analytics": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-1.3.1.tgz",
|
||||
"integrity": "sha512-xhSlYgAuJ6Q4WQGkzYTLmXwhYl39sWjoMA3nHxfkvG+WdBT25c563a7QhwwKivEOZtPJXifYHR1m2ihoisbWyA==",
|
||||
"dependencies": {
|
||||
"server-only": "^0.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": ">= 13",
|
||||
"react": "^18 || ^19"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"next": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/edge": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.1.2.tgz",
|
||||
"integrity": "sha512-wt5SnhsMahWX8U9ZZhFUQoiXhMn/CUxA5xeMdZX1cwyOL1ZbDR3rNI8HRT9RSU73nDxeF6jlnqJyp/0Jy0VM2A=="
|
||||
},
|
||||
"node_modules/@vercel/nft": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.3.tgz",
|
||||
|
@ -7527,11 +7484,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/server-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
|
||||
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
|
@ -8776,11 +8728,6 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/web-vitals": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz",
|
||||
"integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg=="
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
"@astrojs/starlight": "^0.26.2",
|
||||
"@astrojs/starlight-tailwind": "^2.0.3",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@astrojs/vercel": "^7.8.0",
|
||||
"@preline/accordion": "^2.4.1",
|
||||
"@preline/collapse": "^2.4.1",
|
||||
"@preline/dropdown": "^2.4.1",
|
||||
|
|
|
@ -74,6 +74,26 @@ import Icon from "./icons/Icon.astro";
|
|||
// Disable the selection of the same language
|
||||
if (lang === url.pathname.split("/")[1]) return;
|
||||
|
||||
// If on product detail page, adjust the slug
|
||||
if (url.pathname.includes("/products/")) {
|
||||
let productSlug = pathParts[pathParts.length - 1]; // Get the last part of the URL which is the slug
|
||||
|
||||
if (lang === "fr") {
|
||||
// If switching to French, ensure the slug has "-fr"
|
||||
if (!productSlug.endsWith("-fr")) {
|
||||
productSlug += "-fr";
|
||||
}
|
||||
} else {
|
||||
// If switching to English, remove "-fr" from the slug
|
||||
if (productSlug.endsWith("-fr")) {
|
||||
productSlug = productSlug.replace("-fr", "");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last part of the path with the adjusted slug
|
||||
pathParts[pathParts.length - 1] = productSlug;
|
||||
}
|
||||
|
||||
if (
|
||||
url.pathname.includes("/post") ||
|
||||
url.pathname.includes("/insight")
|
||||
|
|
|
@ -4,7 +4,12 @@ import { Image } from "astro:assets";
|
|||
import Icon from "@components/ui/icons/Icon.astro";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
|
||||
const { product } = Astro.props;
|
||||
const { productEntry, productLocale = "" } = Astro.props;
|
||||
|
||||
interface Props {
|
||||
productEntry: CollectionEntry<"products">;
|
||||
productLocale?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
product: CollectionEntry<"products">;
|
||||
|
@ -16,14 +21,14 @@ const imageClass =
|
|||
|
||||
<!-- A clickable card that leads to the details of the product-->
|
||||
<a
|
||||
href={"/products/" + product.slug}
|
||||
href={productLocale !== "" ? `/${productLocale}/products/${productEntry.slug}/` : `/products/${productEntry.slug}/`}
|
||||
data-astro-prefetch
|
||||
class="group relative flex h-48 items-end overflow-hidden rounded-xl shadow-lg outline-none ring-zinc-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none md:h-80"
|
||||
>
|
||||
<!-- The product's main image -->
|
||||
<Image
|
||||
src={product.data.main.imgCard}
|
||||
alt={product.data.main.imgAlt}
|
||||
src={productEntry.data.main.imgCard}
|
||||
alt={productEntry.data.main.imgAlt}
|
||||
draggable={"false"}
|
||||
class={imageClass}
|
||||
format={"avif"}
|
||||
|
@ -36,6 +41,6 @@ const imageClass =
|
|||
<!-- The product's subtitle and the anchor SVG icon-->
|
||||
<span
|
||||
class="relative mb-3 ml-4 inline-block text-sm font-bold text-neutral-50 transition duration-[600ms] ease-[cubic-bezier(0.45,0,0.55,1)] group-hover:scale-110 md:ml-5 md:text-lg"
|
||||
>{product.data.description} <Icon name="openInNew" />
|
||||
>{productEntry.data.description} <Icon name="openInNew" />
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
@ -4,7 +4,12 @@ import { Image } from "astro:assets";
|
|||
import Icon from "@components/ui/icons/Icon.astro";
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
|
||||
const { product } = Astro.props;
|
||||
const { productEntry, productLocale = "" } = Astro.props;
|
||||
|
||||
interface Props {
|
||||
productEntry: CollectionEntry<"products">;
|
||||
productLocale?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
product: CollectionEntry<"products">;
|
||||
|
@ -16,14 +21,14 @@ const imageClass =
|
|||
|
||||
<!-- The anchor tag is the main container for the product card. When clicked, this leads to the details of the product. -->
|
||||
<a
|
||||
href={"/products/" + product.slug}
|
||||
href={productLocale !== "" ? `/${productLocale}/products/${productEntry.slug}/` : `/products/${productEntry.slug}/`}
|
||||
data-astro-prefetch
|
||||
class="group relative flex h-48 items-end overflow-hidden rounded-lg shadow-xl outline-none ring-zinc-500 focus-visible:ring dark:ring-zinc-200 dark:focus:outline-none md:col-span-2 md:h-80"
|
||||
>
|
||||
<!-- The product's main image -->
|
||||
<Image
|
||||
src={product.data.main.imgCard}
|
||||
alt={product.data.main.imgAlt}
|
||||
src={productEntry.data.main.imgCard}
|
||||
alt={productEntry.data.main.imgAlt}
|
||||
draggable={"false"}
|
||||
class={imageClass}
|
||||
format={"avif"}
|
||||
|
@ -36,6 +41,6 @@ const imageClass =
|
|||
<!-- This container includes product's subtitle and an SVG icon-->
|
||||
<span
|
||||
class="relative mb-3 ml-4 inline-block text-sm font-bold text-neutral-50 transition duration-[600ms] ease-[cubic-bezier(0.45,0,0.55,1)] group-hover:scale-110 md:ml-5 md:text-lg"
|
||||
>{product.data.description} <Icon name="openInNew" /></span
|
||||
>{productEntry.data.description} <Icon name="openInNew" /></span
|
||||
>
|
||||
</a>
|
||||
|
|
55
src/content/products/fr/a765.md
Normal file
55
src/content/products/fr/a765.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
title: "SF-AB A765"
|
||||
description: "Ensemble de Vis Assorties"
|
||||
main:
|
||||
id: 2
|
||||
content: |
|
||||
Découvrez l'ensemble de vis assorties SF-AB A765 – la solution ultime pour vos besoins de fixation. Ce set complet comprend une grande variété de vis soigneusement sélectionnées pour aborder divers projets avec facilité et précision.
|
||||
imgCard: "@/images/product-image-2.avif"
|
||||
imgMain: "@/images/product-image-main-2.avif"
|
||||
imgAlt: "Boîtes factices de l'ensemble de vis assorties"
|
||||
tabs:
|
||||
- id: "tabs-with-card-item-1"
|
||||
dataTab: "#tabs-with-card-1"
|
||||
title: "Description"
|
||||
- id: "tabs-with-card-item-2"
|
||||
dataTab: "#tabs-with-card-2"
|
||||
title: "Spécifications"
|
||||
- id: "tabs-with-card-item-3"
|
||||
dataTab: "#tabs-with-card-3"
|
||||
title: "Plans"
|
||||
longDescription:
|
||||
title: "Solutions Polyvalentes de Fixation"
|
||||
subTitle: |
|
||||
L'ensemble de vis assorties SF-AB A765 offre une polyvalence et une commodité inégalées, ce qui en fait le choix idéal pour les amateurs de bricolage et les professionnels. Avec une sélection complète de vis, vous aurez toujours le bon élément de fixation pour chaque tâche.
|
||||
btnTitle: "Contactez les ventes pour en savoir plus"
|
||||
btnURL: "#"
|
||||
descriptionList:
|
||||
- title: "Grande Variété"
|
||||
subTitle: "Comprend une gamme diversifiée de types et de tailles de vis pour s'adapter à diverses applications et matériaux."
|
||||
- title: "Facilité d'Utilisation"
|
||||
subTitle: "Chaque vis est conçue pour une installation facile, garantissant une fixation sans tracas à chaque fois."
|
||||
- title: "Commodité"
|
||||
subTitle: "Élimine le besoin de multiples déplacements au magasin de bricolage, économisant temps et effort sur vos projets."
|
||||
specificationsLeft:
|
||||
- title: "Matériau"
|
||||
subTitle: "Fabriqué à partir de matériaux de haute qualité tels que l'acier inoxydable, assurant durabilité et résistance à la corrosion."
|
||||
- title: "Assortiment"
|
||||
subTitle: "Contient un assortiment généreux de vis, incluant des vis à bois, des vis mécaniques, et des vis pour tôle."
|
||||
- title: "Quantité"
|
||||
subTitle: "Chaque set comprend une quantité suffisante de vis pour gérer une large gamme de projets et de tâches."
|
||||
- title: "Tailles"
|
||||
subTitle: "Disponible en différentes tailles pour s'adapter aux exigences de divers projets, assurant compatibilité et polyvalence."
|
||||
tableData:
|
||||
- feature: ["Spécification", "Valeur"]
|
||||
description:
|
||||
- ["Longueur (mm)", "Divers"]
|
||||
- ["Poids (g)", "N/A"]
|
||||
- ["Matériau", "Acier Inoxydable"]
|
||||
- ["Finition", "Assortie"]
|
||||
- ["Contenu du Pack", "Diverses vis dans un ensemble"]
|
||||
blueprints:
|
||||
first: "@/images/blueprint-1.avif"
|
||||
second: "@/images/blueprint-2.avif"
|
||||
slug: a765-fr
|
||||
---
|
56
src/content/products/fr/b203.md
Normal file
56
src/content/products/fr/b203.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: "SF-BN B203"
|
||||
description: "Ensemble de boulons à vis et écrous"
|
||||
main:
|
||||
id: 3
|
||||
content: |
|
||||
Découvrez le SF-BN B203 – votre compagnon fiable pour des fixations de qualité professionnelle. Cet ensemble complet comprend une sélection polyvalente de boulons à vis et d'écrous, méticuleusement conçus pour offrir la tenue la plus solide pour vos projets de construction et d'assemblage.
|
||||
imgCard: "@/images/product-image-3.avif"
|
||||
imgMain: "@/images/product-image-main-3.avif"
|
||||
imgAlt: "Maquettes de boîtes d'un ensemble de boulons à vis et écrous"
|
||||
tabs:
|
||||
- id: "tabs-with-card-item-1"
|
||||
dataTab: "#tabs-with-card-1"
|
||||
title: "Description"
|
||||
- id: "tabs-with-card-item-2"
|
||||
dataTab: "#tabs-with-card-2"
|
||||
title: "Spécifications"
|
||||
- id: "tabs-with-card-item-3"
|
||||
dataTab: "#tabs-with-card-3"
|
||||
title: "Plans"
|
||||
longDescription:
|
||||
title: "La force rencontre la précision"
|
||||
subTitle: |
|
||||
L'ensemble de boulons à vis et écrous SF-BN B203 offre une durabilité robuste et une précision pour les professionnels de la construction, garantissant des performances fiables dans chaque application, de l'encadrement de maisons à l'assemblage de machines.
|
||||
btnTitle: "Contactez le service commercial pour en savoir plus"
|
||||
btnURL: "#"
|
||||
descriptionList:
|
||||
- title: "Résistance à la corrosion"
|
||||
subTitle: "Le revêtement en zinc offre non seulement un aspect poli, mais protège également contre la corrosion, garantissant une longue durée de vie."
|
||||
- title: "Sécurité améliorée"
|
||||
subTitle: "Un ajustement sécurisé se traduit par des structures plus sûres avec un risque réduit de défaillance des composants."
|
||||
- title: "Praticité"
|
||||
subTitle: "Cet ensemble tout-en-un signifie que vous avez la bonne taille sous la main, réduisant les retards de projet et les allers-retours supplémentaires au magasin de bricolage."
|
||||
specificationsLeft:
|
||||
- title: "Composition du matériau"
|
||||
subTitle: "Fabriqué à partir d'acier de haute qualité, offrant résistance et fiabilité pour des applications exigeantes."
|
||||
- title: "Finition de surface"
|
||||
subTitle: "Protégé par un revêtement de zinc pour offrir une résistance accrue à la corrosion et une longévité."
|
||||
- title: "Quantité par ensemble"
|
||||
subTitle: "L'ensemble comprend une sélection équilibrée de 25 boulons à vis et 25 écrous assortis."
|
||||
- title: "Assortiment de tailles"
|
||||
subTitle: "Comprend une gamme complète de tailles pour répondre à diverses exigences de projet, garantissant compatibilité et polyvalence."
|
||||
specificationsRight:
|
||||
- title: "Détails du filetage"
|
||||
subTitle: "Conçu avec des filets coupés avec précision pour un ajustement sécurisé et une installation facile."
|
||||
- title: "Propriétés mécaniques"
|
||||
subTitle: "Chaque boulon et écrou est conçu pour répondre à des indices de charge spécifiques ou à des normes de résistance, adaptés aux applications structurelles."
|
||||
- title: "Normes et certifications"
|
||||
subTitle: "Conforme aux normes et certifications industrielles pertinentes, garantissant une qualité et une sécurité constantes."
|
||||
- title: "Applications adaptées"
|
||||
subTitle: "Idéal pour un large éventail d'utilisations, des environnements de construction aux assemblages mécaniques qui nécessitent des joints solides et sécurisés."
|
||||
blueprints:
|
||||
first: "@/images/blueprint-1.avif"
|
||||
second: "@/images/blueprint-2.avif"
|
||||
slug: b203-fr
|
||||
---
|
56
src/content/products/fr/f303.md
Normal file
56
src/content/products/fr/f303.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: "SF-FN F303"
|
||||
description: "Boulons hexagonaux"
|
||||
main:
|
||||
id: 4
|
||||
content: |
|
||||
Découvrez les boulons hexagonaux SF-FN F303 – le choix parfait pour les applications de fixation lourdes. Conçus avec précision et durabilité, ces boulons hexagonaux offrent la force et la fiabilité dont vous avez besoin pour vos projets les plus exigeants.
|
||||
imgCard: "@/images/product-image-4.avif"
|
||||
imgMain: "@/images/product-image-main-4.avif"
|
||||
imgAlt: "Maquettes de boîtes de boulons hexagonaux"
|
||||
tabs:
|
||||
- id: "tabs-with-card-item-1"
|
||||
dataTab: "#tabs-with-card-1"
|
||||
title: "Description"
|
||||
- id: "tabs-with-card-item-2"
|
||||
dataTab: "#tabs-with-card-2"
|
||||
title: "Spécifications"
|
||||
- id: "tabs-with-card-item-3"
|
||||
dataTab: "#tabs-with-card-3"
|
||||
title: "Plans"
|
||||
longDescription:
|
||||
title: "Solutions de fixation robustes"
|
||||
subTitle: |
|
||||
Les boulons hexagonaux SF-FN F303 sont conçus pour relever les défis de fixation les plus difficiles avec aisance. Que vous travailliez sur des projets de construction ou des machines lourdes, ces boulons hexagonaux offrent la force et la fiabilité nécessaires.
|
||||
btnTitle: "Contactez le service commercial pour en savoir plus"
|
||||
btnURL: "#"
|
||||
descriptionList:
|
||||
- title: "Solidité et durabilité"
|
||||
subTitle: "Fabriqués à partir de matériaux de haute qualité, ces boulons hexagonaux sont conçus pour supporter des charges lourdes et des conditions difficiles."
|
||||
- title: "Ingénierie de précision"
|
||||
subTitle: "Conçus avec des filets coupés avec précision et des spécifications exactes, garantissant un ajustement serré et sécurisé à chaque fois."
|
||||
- title: "Polyvalence"
|
||||
subTitle: "Adaptés à une large gamme d'applications, de la construction aux machines, offrant des solutions de fixation polyvalentes."
|
||||
specificationsLeft:
|
||||
- title: "Matériau"
|
||||
subTitle: "Fabriqués en acier ou en alliage de qualité supérieure, offrant une résistance exceptionnelle et une résistance à la corrosion."
|
||||
- title: "Conception du filetage"
|
||||
subTitle: "Des filets coupés avec précision assurent une adhérence optimale et une fiabilité même dans des environnements à haute contrainte."
|
||||
- title: "Quantité"
|
||||
subTitle: "Chaque ensemble comprend une quantité suffisante de boulons hexagonaux pour divers projets et applications."
|
||||
- title: "Tailles"
|
||||
subTitle: "Disponible dans une gamme de tailles pour répondre aux différentes exigences des projets, garantissant polyvalence et compatibilité."
|
||||
specificationsRight:
|
||||
- title: "Finition"
|
||||
subTitle: "Fini avec un revêtement protecteur pour améliorer la résistance à la corrosion et prolonger la durée de vie."
|
||||
- title: "Capacité de charge"
|
||||
subTitle: "Conçus pour répondre ou dépasser les normes industrielles en matière de capacité de charge, garantissant des performances fiables sous des charges lourdes."
|
||||
- title: "Certifications"
|
||||
subTitle: "Conformes aux normes et certifications industrielles pertinentes, garantissant qualité et fiabilité."
|
||||
- title: "Applications"
|
||||
subTitle: "Idéal pour une utilisation dans la construction, les machines, l'automobile et d'autres applications lourdes nécessitant une fixation solide et fiable."
|
||||
blueprints:
|
||||
first: "@/images/blueprint-1.avif"
|
||||
second: "@/images/blueprint-2.avif"
|
||||
slug: f303-fr
|
||||
---
|
56
src/content/products/fr/t845.md
Normal file
56
src/content/products/fr/t845.md
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: "SF-TB T845"
|
||||
description: "Vis à machine"
|
||||
main:
|
||||
id: 1
|
||||
content: |
|
||||
Découvrez le SF-TB T845 – votre solution de prédilection pour la fixation de précision dans les machines et équipements. Cet ensemble complet de vis à machine est méticuleusement conçu pour répondre aux exigences strictes des applications industrielles, garantissant une fixation sécurisée et fiable.
|
||||
imgCard: "@/images/product-image-1.avif"
|
||||
imgMain: "@/images/product-image-main-1.avif"
|
||||
imgAlt: "Maquettes de boîtes de vis à machine"
|
||||
tabs:
|
||||
- id: "tabs-with-card-item-1"
|
||||
dataTab: "#tabs-with-card-1"
|
||||
title: "Description"
|
||||
- id: "tabs-with-card-item-2"
|
||||
dataTab: "#tabs-with-card-2"
|
||||
title: "Spécifications"
|
||||
- id: "tabs-with-card-item-3"
|
||||
dataTab: "#tabs-with-card-3"
|
||||
title: "Plans"
|
||||
longDescription:
|
||||
title: "Solutions de fixation de précision"
|
||||
subTitle: |
|
||||
Les vis à machine SF-TB T845 offrent une précision et une fiabilité inégalées pour les applications industrielles, garantissant un fonctionnement fluide et une longue durée de vie pour vos machines et équipements.
|
||||
btnTitle: "Contactez le service commercial pour en savoir plus"
|
||||
btnURL: "#"
|
||||
descriptionList:
|
||||
- title: "Durabilité"
|
||||
subTitle: "Fabriquées à partir de matériaux de haute qualité, ces vis à machine sont conçues pour résister aux rigueurs des environnements industriels."
|
||||
- title: "Ingénierie de précision"
|
||||
subTitle: "Conçues avec des filets coupés avec précision et des spécifications exactes, garantissant un ajustement serré et sécurisé pour chaque application."
|
||||
- title: "Polyvalence"
|
||||
subTitle: "Adaptées à une large gamme de machines et d'équipements, offrant des solutions de fixation polyvalentes pour divers besoins industriels."
|
||||
specificationsLeft:
|
||||
- title: "Composition du matériau"
|
||||
subTitle: "Construites en acier ou alliage de qualité supérieure pour une résistance et une durabilité exceptionnelles."
|
||||
- title: "Finition de surface"
|
||||
subTitle: "Fini avec un revêtement protecteur pour améliorer la résistance à la corrosion et prolonger la durée de vie."
|
||||
- title: "Quantité par ensemble"
|
||||
subTitle: "Chaque ensemble contient un assortiment complet de vis à machine pour répondre aux divers besoins industriels."
|
||||
- title: "Gamme de tailles"
|
||||
subTitle: "Disponible en différentes tailles et longueurs pour s'adapter aux spécifications des machines et équipements."
|
||||
specificationsRight:
|
||||
- title: "Spécifications du filetage"
|
||||
subTitle: "Des filets conçus avec précision assurent une adhérence optimale et une fiabilité, même dans les environnements à haute vibration."
|
||||
- title: "Capacité de charge"
|
||||
subTitle: "Conçues pour répondre ou dépasser les normes industrielles en matière de capacité de charge, garantissant une opération sûre et fiable."
|
||||
- title: "Certifications"
|
||||
subTitle: "Conformes aux normes et certifications industrielles pertinentes, garantissant qualité et fiabilité."
|
||||
- title: "Applications"
|
||||
subTitle: "Idéal pour une utilisation dans une large gamme de machines industrielles, équipements et assemblages nécessitant une fixation précise et sécurisée."
|
||||
blueprints:
|
||||
first: "@/images/blueprint-1.avif"
|
||||
second: "@/images/blueprint-2.avif"
|
||||
slug: t845-fr
|
||||
---
|
387
src/pages/fr/products/[...slug].astro
Normal file
387
src/pages/fr/products/[...slug].astro
Normal file
|
@ -0,0 +1,387 @@
|
|||
---
|
||||
// Import section components
|
||||
import MainLayout from "@/layouts/MainLayout.astro";
|
||||
import ProductTabBtn from "@components/ui/buttons/ProductTabBtn.astro";
|
||||
import PrimaryCTA from "@components/ui/buttons/PrimaryCTA.astro";
|
||||
import { Image } from "astro:assets";
|
||||
import { getCollection } from "astro:content";
|
||||
import { SITE } from "@data/constants";
|
||||
|
||||
// Global declaration for gsap animation library
|
||||
declare global {
|
||||
interface Window {
|
||||
gsap: any;
|
||||
}
|
||||
}
|
||||
// This gets the static paths for all the unique products
|
||||
export async function getStaticPaths() {
|
||||
const productEntries = await getCollection("products", ({ id }) => {
|
||||
return id.startsWith("fr/");
|
||||
});
|
||||
return productEntries.map((product) => ({
|
||||
params: { slug: product.slug },
|
||||
props: { product },
|
||||
}));
|
||||
}
|
||||
|
||||
const { product } = Astro.props;
|
||||
|
||||
const pageTitle: string = `${product.data.title} | ${SITE.title}`;
|
||||
---
|
||||
|
||||
<MainLayout title={pageTitle}>
|
||||
<div id="overlay" class="fixed inset-0 bg-neutral-200 dark:bg-neutral-800">
|
||||
</div>
|
||||
|
||||
<section
|
||||
class="mx-auto flex max-w-[85rem] flex-col px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
id="fadeText"
|
||||
class="mb-8 max-w-prose text-pretty font-light text-neutral-700 dark:text-neutral-300 sm:text-xl"
|
||||
>
|
||||
{product.data.main.content}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col items-center justify-between space-y-4 sm:flex-row sm:space-y-0"
|
||||
>
|
||||
<div id="fadeInUp">
|
||||
<h1
|
||||
class="block text-4xl font-bold tracking-tighter text-neutral-800 dark:text-neutral-200 sm:text-5xl md:text-6xl lg:text-7xl"
|
||||
>
|
||||
{product.data.title}
|
||||
</h1>
|
||||
<p class="text-lg text-neutral-600 dark:text-neutral-400">
|
||||
{product.data.description}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Image
|
||||
id="fadeInMoveRight"
|
||||
src={product.data.main.imgMain}
|
||||
class="w-[600px]"
|
||||
alt={product.data.main.imgAlt}
|
||||
format={"avif"}
|
||||
loading={"eager"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="mx-auto max-w-[85rem] px-4 pt-10 sm:px-6 lg:px-8 lg:pt-14">
|
||||
<nav
|
||||
class="mx-auto grid max-w-6xl gap-y-px sm:flex sm:gap-x-4 sm:gap-y-0"
|
||||
aria-label="Tabs"
|
||||
role="tablist"
|
||||
>
|
||||
{
|
||||
product.data.tabs.map((tab, index) => (
|
||||
<ProductTabBtn
|
||||
title={tab.title}
|
||||
id={tab.id}
|
||||
dataTab={tab.dataTab}
|
||||
first={index === 0}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
|
||||
<div class="mt-12 md:mt-16">
|
||||
<div id="tabs-with-card-1" role="tabpanel">
|
||||
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
|
||||
<div class="grid gap-12 md:grid-cols-2">
|
||||
<div class="lg:w-3/4">
|
||||
<h2
|
||||
class="text-balance text-3xl font-bold tracking-tight text-neutral-800 dark:text-neutral-200 md:leading-tight lg:text-4xl"
|
||||
>
|
||||
{product.data.longDescription.title}
|
||||
</h2>
|
||||
<p
|
||||
class="mt-3 text-pretty text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{product.data.longDescription.subTitle}
|
||||
</p>
|
||||
<p class="mt-5">
|
||||
<PrimaryCTA
|
||||
title={product.data.longDescription.btnTitle}
|
||||
url={product.data.longDescription.btnURL}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6 lg:space-y-10">
|
||||
{
|
||||
product.data.descriptionList.map((list) => (
|
||||
<div class="flex">
|
||||
<div>
|
||||
<h3 class="text-base font-bold text-neutral-800 dark:text-neutral-200 sm:text-lg">
|
||||
{list.title}
|
||||
</h3>
|
||||
<p class="mt-1 text-neutral-600 dark:text-neutral-400">
|
||||
{list.subTitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tabs-with-card-2" class="hidden" role="tabpanel">
|
||||
<div class="mx-auto max-w-[85rem] px-4 pb-10 sm:px-6 lg:px-8 lg:pb-14">
|
||||
<div class="grid w-full grid-cols-1 gap-x-16 md:grid-cols-2">
|
||||
<div class="max-w-md space-y-6">
|
||||
{
|
||||
product.data.specificationsLeft.map((spec) => (
|
||||
<div>
|
||||
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
|
||||
{spec.title}
|
||||
</h3>
|
||||
<p class="text-neutral-600 dark:text-neutral-400">
|
||||
{spec.subTitle}
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{
|
||||
product.data.specificationsRight ? (
|
||||
<div class="mt-6 max-w-md space-y-6 md:ml-auto md:mt-0">
|
||||
{product.data.specificationsRight?.map((spec) => (
|
||||
<div>
|
||||
<h3 class="block font-bold text-neutral-800 dark:text-neutral-200">
|
||||
{spec.title}
|
||||
</h3>
|
||||
<p class="text-neutral-600 dark:text-neutral-400">
|
||||
{spec.subTitle}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : product.data.tableData ? (
|
||||
<div class="mt-6 space-y-6 md:ml-auto md:mt-0">
|
||||
<div class="flex flex-col">
|
||||
<div class="-m-1.5 overflow-x-auto">
|
||||
<div class="inline-block min-w-full p-1.5 align-middle">
|
||||
<div class="overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-neutral-300 dark:divide-neutral-700">
|
||||
<thead>
|
||||
<tr>
|
||||
{product.data.tableData?.[0].feature?.map(
|
||||
(header) => (
|
||||
<th
|
||||
scope="col"
|
||||
class="px-6 py-3 text-start text-xs font-medium uppercase text-neutral-500 dark:text-neutral-500"
|
||||
>
|
||||
{header}
|
||||
</th>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-neutral-300 dark:divide-neutral-700">
|
||||
{product.data.tableData?.map((row) =>
|
||||
// Wrap each row's content in a separate <tr> element
|
||||
row.description.map((rowData) => (
|
||||
<tr>
|
||||
{/* Iterate through each cell value in the row's description array */}
|
||||
{rowData.map((cellValue) => (
|
||||
// Render each cell value in its own <td> element
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
||||
{cellValue}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tabs-with-card-3" class="hidden" role="tabpanel">
|
||||
<div class="mx-auto mb-20 flex w-full md:mb-28 2xl:w-4/5">
|
||||
<div
|
||||
class="relative left-12 top-12 z-10 overflow-hidden rounded-xl shadow-lg md:left-12 md:top-16 md:-ml-12 lg:ml-0"
|
||||
>
|
||||
{
|
||||
product.data.blueprints.first && (
|
||||
<Image
|
||||
src={product.data.blueprints.first}
|
||||
class="h-full w-full object-cover object-center"
|
||||
alt="Blueprint Illustration"
|
||||
format={"avif"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="relative right-12 overflow-hidden rounded-xl shadow-xl">
|
||||
{
|
||||
product.data.blueprints.second && (
|
||||
<Image
|
||||
src={product.data.blueprints.second}
|
||||
class="h-full w-full object-cover object-center"
|
||||
alt="Blueprint Illustration"
|
||||
format={"avif"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MainLayout>
|
||||
|
||||
<script>
|
||||
import { gsap } from "gsap";
|
||||
|
||||
type AnimationSettings = {
|
||||
autoAlpha?: number;
|
||||
y?: number;
|
||||
x?: number;
|
||||
willChange?: string;
|
||||
};
|
||||
|
||||
function setElementAnimationDefaults(
|
||||
id: string,
|
||||
settings: AnimationSettings
|
||||
) {
|
||||
gsap.set(id, settings);
|
||||
}
|
||||
|
||||
setElementAnimationDefaults("#fadeText", {
|
||||
autoAlpha: 0,
|
||||
y: 50,
|
||||
willChange: "transform, opacity",
|
||||
});
|
||||
|
||||
setElementAnimationDefaults("#fadeInUp", {
|
||||
autoAlpha: 0,
|
||||
y: 50,
|
||||
willChange: "transform, opacity",
|
||||
});
|
||||
|
||||
setElementAnimationDefaults("#fadeInMoveRight", {
|
||||
autoAlpha: 0,
|
||||
x: 300,
|
||||
willChange: "transform, opacity",
|
||||
});
|
||||
|
||||
let timeline = gsap.timeline({ defaults: { overwrite: "auto" } });
|
||||
|
||||
timeline.to("#fadeText", {
|
||||
duration: 1.5,
|
||||
autoAlpha: 1,
|
||||
y: 0,
|
||||
delay: 1,
|
||||
ease: "power2.out",
|
||||
});
|
||||
|
||||
timeline.to(
|
||||
"#fadeInUp",
|
||||
{ duration: 1.5, autoAlpha: 1, y: 0, ease: "power2.out" },
|
||||
"-=1.2"
|
||||
);
|
||||
|
||||
timeline.to(
|
||||
"#fadeInMoveRight",
|
||||
{ duration: 1.5, autoAlpha: 1, x: 0, ease: "power2.inOut" },
|
||||
"-=1.4"
|
||||
);
|
||||
|
||||
timeline.to("#overlay", { duration: 1, autoAlpha: 0, delay: 0.2 });
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function setButtonInactive(btn: any, activeButton: any) {
|
||||
if (btn !== activeButton) {
|
||||
btn.classList.remove(
|
||||
"active",
|
||||
"bg-neutral-100",
|
||||
"hover:border-transparent",
|
||||
"dark:bg-white/[.05]"
|
||||
);
|
||||
|
||||
const tabId = btn.getAttribute("data-target");
|
||||
if (tabId) {
|
||||
const contentElement = document.querySelector(tabId);
|
||||
if (contentElement) {
|
||||
contentElement.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
changeHeadingStyle(
|
||||
btn,
|
||||
["text-neutral-800", "dark:text-neutral-200"],
|
||||
["text-orange-400", "dark:text-orange-300"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function activateButton(button: any) {
|
||||
button.classList.add(
|
||||
"active",
|
||||
"bg-neutral-100",
|
||||
",hover:border-transparent",
|
||||
"dark:bg-white/[.05]"
|
||||
);
|
||||
|
||||
const tabId = button.getAttribute("data-target");
|
||||
if (tabId) {
|
||||
const contentElementToShow = document.querySelector(tabId);
|
||||
if (contentElementToShow) {
|
||||
contentElementToShow.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
changeHeadingStyle(
|
||||
button,
|
||||
["text-orange-400", "dark:text-orange-300"],
|
||||
["text-neutral-800", "dark:text-neutral-200"]
|
||||
);
|
||||
}
|
||||
|
||||
function changeHeadingStyle(
|
||||
button: any,
|
||||
addClasses: any,
|
||||
removeClasses: any
|
||||
) {
|
||||
let heading = button.querySelector("span");
|
||||
if (heading) {
|
||||
heading.classList.remove(...removeClasses);
|
||||
heading.classList.add(...addClasses);
|
||||
}
|
||||
}
|
||||
|
||||
const tabButtons = document.querySelectorAll("[data-target]");
|
||||
|
||||
if (tabButtons.length > 0) {
|
||||
changeHeadingStyle(
|
||||
tabButtons[0],
|
||||
["text-orange-400", "dark:text-orange-300"],
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
tabButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
tabButtons.forEach((btn) => setButtonInactive(btn, button));
|
||||
activateButton(button);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -13,7 +13,9 @@ import type { CollectionEntry } from "astro:content";
|
|||
|
||||
// Fetching all the product related content and sorting it by main.id
|
||||
const product: CollectionEntry<"products">[] = (
|
||||
await getCollection("products")
|
||||
await getCollection("products", ({ id }) => {
|
||||
return id.startsWith("fr/");
|
||||
})
|
||||
).sort(
|
||||
(a: CollectionEntry<"products">, b: CollectionEntry<"products">) =>
|
||||
a.data.main.id - b.data.main.id
|
||||
|
@ -110,9 +112,9 @@ const testimonials = [
|
|||
product.map((product, index) => {
|
||||
const position = index % 4;
|
||||
if (position === 0 || position === 3) {
|
||||
return <CardSmall product={product} />;
|
||||
return <CardSmall productEntry={product} productLocale="fr" />;
|
||||
} else {
|
||||
return <CardWide product={product} />;
|
||||
return <CardWide productEntry={product} productLocale="fr" />;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,9 +13,12 @@ declare global {
|
|||
gsap: any;
|
||||
}
|
||||
}
|
||||
|
||||
// This gets the static paths for all the unique products
|
||||
export async function getStaticPaths() {
|
||||
const productEntries = await getCollection("products");
|
||||
const productEntries = await getCollection("products", ({ id }) => {
|
||||
return id.startsWith("en/");
|
||||
});
|
||||
return productEntries.map((product) => ({
|
||||
params: { slug: product.slug },
|
||||
props: { product },
|
||||
|
|
|
@ -14,7 +14,9 @@ import { SITE } from "@data/constants";
|
|||
|
||||
// Fetching all the product related content and sorting it by main.id
|
||||
const product: CollectionEntry<"products">[] = (
|
||||
await getCollection("products")
|
||||
await getCollection("products", ({ id }) => {
|
||||
return id.startsWith("en/");
|
||||
})
|
||||
).sort(
|
||||
(a: CollectionEntry<"products">, b: CollectionEntry<"products">) =>
|
||||
a.data.main.id - b.data.main.id,
|
||||
|
@ -107,9 +109,9 @@ const pageTitle: string = `Products | ${SITE.title}`;
|
|||
product.map((product, index) => {
|
||||
const position = index % 4;
|
||||
if (position === 0 || position === 3) {
|
||||
return <CardSmall product={product} />;
|
||||
return <CardSmall productEntry={product} />;
|
||||
} else {
|
||||
return <CardWide product={product} />;
|
||||
return <CardWide productEntry={product} />;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue