From 9c616570717ef20f7a5ecc084aba9a0421977b77 Mon Sep 17 00:00:00 2001 From: becarta Date: Mon, 3 Mar 2025 23:23:14 +0100 Subject: [PATCH] Added homepage and moved old homepage to aboutme page --- public/test-language-persistence.html | 183 +++++++++ public/test-language-switching.html | 177 +++++++++ src/components/LanguageDropdown.astro | 104 ++++-- src/components/LanguagePersistence.astro | 112 ++++++ src/components/common/BasicScripts.astro | 199 ++++++++++ src/components/widgets/Footer.astro | 52 ++- src/components/widgets/Header.astro | 43 ++- src/i18n/translations.ts | 451 ++++++++++++++++++++++- src/layouts/Layout.astro | 2 + src/navigation.ts | 74 ++-- src/pages/[lang]/aboutme.astro | 141 +++++++ src/pages/[lang]/index.astro | 351 +++++++++--------- src/pages/about.astro | 228 ------------ src/pages/aboutme.astro | 32 ++ src/pages/index.astro | 99 ++--- src/utils/permalinks.ts | 54 ++- 16 files changed, 1769 insertions(+), 533 deletions(-) create mode 100644 public/test-language-persistence.html create mode 100644 public/test-language-switching.html create mode 100644 src/components/LanguagePersistence.astro create mode 100644 src/pages/[lang]/aboutme.astro delete mode 100644 src/pages/about.astro create mode 100644 src/pages/aboutme.astro diff --git a/public/test-language-persistence.html b/public/test-language-persistence.html new file mode 100644 index 0000000..65baa0e --- /dev/null +++ b/public/test-language-persistence.html @@ -0,0 +1,183 @@ + + + + + + Language Persistence Test + + + +

Language Persistence Test

+ +
+

Current Language Status

+
+

URL Language: Checking...

+

LocalStorage Language: Checking...

+

Cookie Language: Checking...

+
+
+ +
+

Test Language Selection

+

Click on a language to test the language persistence:

+
+ + + + +
+
+ +
+

Navigation Test

+

Use these links to test language persistence during navigation:

+ +
+ + + + \ No newline at end of file diff --git a/public/test-language-switching.html b/public/test-language-switching.html new file mode 100644 index 0000000..ca017d3 --- /dev/null +++ b/public/test-language-switching.html @@ -0,0 +1,177 @@ + + + + + + Language Switching Test + + + +

Language Switching Test

+ +
+

Current URL Information

+
Loading...
+
+ +
+

Switch Language

+

Click on a language to switch:

+
+ English + Dutch + German + French +
+
+ +
+

Test Hash Navigation

+

Click on a section to navigate:

+
+ Services + Contact + About +
+
+ +
+

Test Page Navigation

+

Navigate to different pages:

+
+ Home + About Me +
+
+ + + + \ No newline at end of file diff --git a/src/components/LanguageDropdown.astro b/src/components/LanguageDropdown.astro index d74033d..432cb77 100644 --- a/src/components/LanguageDropdown.astro +++ b/src/components/LanguageDropdown.astro @@ -1,5 +1,6 @@ --- import { Icon } from 'astro-icon/components'; +import { supportedLanguages } from '~/i18n/translations'; interface Props { currentLang: string; @@ -7,8 +8,6 @@ interface Props { const { currentLang } = Astro.props; -import { supportedLanguages } from '~/i18n/translations'; - type SupportedLanguage = typeof supportedLanguages[number]; const languages = [ @@ -121,13 +120,13 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan } - \ No newline at end of file diff --git a/src/components/LanguagePersistence.astro b/src/components/LanguagePersistence.astro new file mode 100644 index 0000000..e9e919a --- /dev/null +++ b/src/components/LanguagePersistence.astro @@ -0,0 +1,112 @@ +--- +// This component handles the synchronization between localStorage and cookies +// for language persistence across page loads and navigation + +// Extend Window interface to include languageUtils +declare global { + interface Window { + languageUtils?: { + getStoredLanguage: () => string | null; + storeLanguagePreference: (langCode: string) => void; + }; + } +} +--- + + \ No newline at end of file diff --git a/src/components/common/BasicScripts.astro b/src/components/common/BasicScripts.astro index c7290b2..d7ab890 100644 --- a/src/components/common/BasicScripts.astro +++ b/src/components/common/BasicScripts.astro @@ -159,6 +159,205 @@ import { UI } from 'astrowind:config'; onLoad(); onPageShow(); }); + + // Handle smooth scrolling for anchor links across all pages + function setupSmoothScrolling() { + // Handle links that start with # (pure anchor links) + document.querySelectorAll('a[href^="#"]:not([href="#"])').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + + const targetId = this.getAttribute('href').substring(1); + const targetElement = document.getElementById(targetId); + + if (targetElement) { + window.scrollTo({ + top: targetElement.offsetTop - 50, // Offset for header + behavior: 'smooth' + }); + } + }); + }); + + // Handle links that contain # but don't start with it (page + anchor) + document.querySelectorAll('a[href*="#"]:not([href^="#"])').forEach(anchor => { + anchor.addEventListener('click', function (e) { + const href = this.getAttribute('href'); + const isHashLink = this.getAttribute('data-hash-link') === 'true'; + + // Check if this is a link to the current page + // First, extract the path part (before the hash) + const hrefPath = href.split('#')[0]; + const currentPath = window.location.pathname; + + // Consider it's the current page if: + // 1. The path matches exactly + // 2. The href is just a hash (like '/#services') + // 3. The href path is '/' and we're on the homepage + const isCurrentPage = + currentPath === hrefPath || + hrefPath === '' || + (hrefPath === '/' && (currentPath === '/' || currentPath.endsWith('/index.html'))); + + // For hash links, we want to update the URL and scroll to the element + if (isHashLink || isCurrentPage) { + e.preventDefault(); + + const hashIndex = href.indexOf('#'); + if (hashIndex !== -1) { + const targetId = href.substring(hashIndex + 1); + const targetElement = document.getElementById(targetId); + + if (targetElement) { + // Update the URL with the hash fragment + history.pushState(null, null, href); + + window.scrollTo({ + top: targetElement.offsetTop - 50, // Offset for header + behavior: 'smooth' + }); + } else { + // If the target element doesn't exist on the current page, navigate to the page + window.location.href = href; + } + } else { + // If there's no hash fragment, just navigate to the page + window.location.href = href; + } + } + }); + }); + } + + // Handle language changes and hash navigation + function setupLanguageNavigation() { + // Initialize language preference from localStorage or default to 'en' + function getStoredLanguage() { + return localStorage.getItem('preferredLanguage') || 'en'; + } + + // Store language preference in localStorage + function storeLanguagePreference(langCode) { + localStorage.setItem('preferredLanguage', langCode); + console.log('Language preference stored:', langCode); + } + + // Function to update URLs with the current language + function updateUrlsWithLanguage() { + const currentLang = getStoredLanguage(); + const supportedLanguages = ['en', 'nl', 'de', 'fr']; + + // Update all internal links to include the language prefix + document.querySelectorAll('a[href^="/"]:not([href^="//"])').forEach(link => { + const href = link.getAttribute('href'); + if (!href) return; + + // Skip hash-only links (e.g., "#services") + if (href.startsWith('#')) { + return; + } + + // Extract hash fragment if present + let hashFragment = ''; + let pathWithoutHash = href; + + if (href.includes('#')) { + const parts = href.split('#'); + pathWithoutHash = parts[0]; + hashFragment = '#' + parts[1]; + } + + // Parse the URL path (without hash) to check for existing language code + const pathSegments = pathWithoutHash.split('/').filter(Boolean); + const hasLanguagePrefix = pathSegments.length > 0 && supportedLanguages.includes(pathSegments[0]); + + // If it already has a language prefix but it's different from the current language, + // update it to the current language + if (hasLanguagePrefix && pathSegments[0] !== currentLang) { + // Replace the existing language prefix with the current one + pathSegments[0] = currentLang; + const newPath = '/' + pathSegments.join('/'); + + // Set the new href with the hash fragment (if any) + link.setAttribute('href', newPath + hashFragment); + return; + } + + // If it doesn't have a language prefix, add the current language + if (!hasLanguagePrefix) { + // Create the new path with the language prefix + const newPath = pathWithoutHash === '/' ? + `/${currentLang}` : + `/${currentLang}${pathWithoutHash}`; + + // Set the new href with the hash fragment (if any) + link.setAttribute('href', newPath + hashFragment); + } + }); + } + + // Listen for the custom languageChanged event + document.addEventListener('languageChanged', (event) => { + // Store the selected language in localStorage + if (event.detail && event.detail.langCode) { + storeLanguagePreference(event.detail.langCode); + console.log('Language changed:', event.detail); + + // Always update all internal links with the new language + // regardless of whether we're doing a full page reload + updateUrlsWithLanguage(); + } + }); + + // Process links when the page loads + updateUrlsWithLanguage(); + + // Process links after client-side navigation + document.addEventListener('astro:page-load', () => { + // Short delay to ensure DOM is fully updated + setTimeout(updateUrlsWithLanguage, 0); + }); + + // Also update links when the DOM content is loaded + document.addEventListener('DOMContentLoaded', updateUrlsWithLanguage); + + // Check for hash in URL on page load and scroll to it + function scrollToHashOnLoad() { + if (window.location.hash) { + const targetId = window.location.hash.substring(1); + const targetElement = document.getElementById(targetId); + + if (targetElement) { + // Use setTimeout to ensure the page has fully loaded + setTimeout(() => { + window.scrollTo({ + top: targetElement.offsetTop - 50, // Offset for header + behavior: 'smooth' + }); + }, 100); + } + } + } + + scrollToHashOnLoad(); + + // Make language functions available globally + window.languageUtils = { + getStoredLanguage, + storeLanguagePreference + }; + } + + document.addEventListener('DOMContentLoaded', () => { + setupSmoothScrolling(); + setupLanguageNavigation(); + }); + + // Re-attach event listeners after page transitions + document.addEventListener('astro:after-swap', () => { + setupSmoothScrolling(); + setupLanguageNavigation(); + }); - - - {t.hero.greeting}

{t.hero.subtitle} -
-
+ subtitle={t.hero.subtitle} + actions={[ + { + variant: 'primary', + text: t.homepage?.actions?.learnMore || 'Learn More', + href: '#services', + icon: 'tabler:arrow-down', + }, + { text: t.homepage?.actions?.contactMe || 'Contact Me', href: '#contact' }, + ]} + image={{ + src: '~/assets/images/richardbergsma.png', + alt: 'Richard Bergsma - IT Systems and Automation Manager', + }} + /> + + + ({...item, icon: item.icon || 'tabler:check'}))} + /> -

{t.about.title}

- {t.about.content.map((paragraph) => ( -

{paragraph}

-
+

+ {t.homepage?.approach?.missionTitle || "Mission Statement"} +

+ {(t.homepage?.approach?.missionContent || [ + 'My mission is to drive IT excellence by optimizing cloud solutions, automating processes, and providing outstanding technical support. I believe in leveraging technology to solve real business challenges and create value through innovation.', + 'With over 15 years of IT experience, I bring a wealth of knowledge in Microsoft technologies, automation tools, and system integration to help organizations transform their digital capabilities and achieve their strategic goals.' + ]).map((paragraph) => ( + <> +

{paragraph}

+
+ ))}
- - -
-
- - ({ - title: exp.title, - company: exp.company, - date: exp.period, - location: exp.location, - description: exp.description, - icon: 'tabler:briefcase', + + ({ + ...item, + image: { + src: '~/assets/images/default.png', + alt: item.name, + } }))} /> - - ({ - name: cert.name, - issueDate: cert.issueDate, - description: cert.description, - linkUrl: cert.linkUrl, - image: cert.image - }))} - /> - - - ({ - title: item.title, - description: item.description, - }))} - /> - - - ({ - title: item.title, - icon: 'tabler:school' - }))} - /> - - - + - -
+ {t.homepage?.callToAction?.title || 'Ready to optimize your IT systems?'} + + {t.homepage?.callToAction?.subtitle || 'Let\'s discuss how I can help your organization streamline processes, enhance collaboration, and drive digital transformation.'} -
+ + + + + + \ No newline at end of file diff --git a/src/pages/about.astro b/src/pages/about.astro deleted file mode 100644 index 648e158..0000000 --- a/src/pages/about.astro +++ /dev/null @@ -1,228 +0,0 @@ ---- -import Features2 from '~/components/widgets/Features2.astro'; -import Features3 from '~/components/widgets/Features3.astro'; -import Hero from '~/components/widgets/Hero.astro'; -import Stats from '~/components/widgets/Stats.astro'; -import Steps2 from '~/components/widgets/Steps2.astro'; -import Layout from '~/layouts/PageLayout.astro'; - -const metadata = { - title: 'About us', -}; ---- - - - - - - - Elevate your online presence with our
- Beautiful Website Templates -
- - - Donec efficitur, ipsum quis congue luctus, mauris magna convallis mauris, eu auctor nisi lectus non augue. Donec - quis lorem non massa vulputate efficitur ac at turpis. Sed tincidunt ex a nunc convallis, et lobortis nisi tempus. - Suspendisse vitae nisi eget tortor luctus maximus sed non lectus. - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/pages/aboutme.astro b/src/pages/aboutme.astro new file mode 100644 index 0000000..3297fae --- /dev/null +++ b/src/pages/aboutme.astro @@ -0,0 +1,32 @@ +--- +export const prerender = false; +import { supportedLanguages } from '~/i18n/translations'; + +// Check for language preference in cookies (set by client-side JS) +const cookies = Astro.request.headers.get('cookie') || ''; +const cookieLanguage = cookies.split(';') + .map(cookie => cookie.trim()) + .find(cookie => cookie.startsWith('preferredLanguage=')) + ?.split('=')[1]; + +// Get the user's preferred language from the browser if no cookie +const acceptLanguage = Astro.request.headers.get('accept-language') || ''; +// Define the type for supported languages +type SupportedLanguage = typeof supportedLanguages[number]; + +// Use cookie language if available, otherwise detect from browser +const preferredLanguage = + (cookieLanguage && supportedLanguages.includes(cookieLanguage as SupportedLanguage)) + ? cookieLanguage + : acceptLanguage + .split(',') + .map(lang => lang.split(';')[0].trim().substring(0, 2)) + .find(lang => supportedLanguages.includes(lang as SupportedLanguage)) || 'en'; + +// Get the hash fragment if present +const url = new URL(Astro.request.url); +const hash = url.hash; + +// Redirect to the language-specific about me page +return Astro.redirect(`/${preferredLanguage}/aboutme${hash}`); +--- \ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index f3ebdf8..3831974 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,77 +1,32 @@ - - - - - Redirecting... - - - -
-

Redirecting...

-

- You are being redirected to the version of our site. -

-

- If you are not redirected automatically, please - click here. -

-
- - - - \ No newline at end of file +// Redirect to the language-specific homepage +return Astro.redirect(`/${preferredLanguage}/${hash}`); +--- \ No newline at end of file diff --git a/src/utils/permalinks.ts b/src/utils/permalinks.ts index 4e3078d..b28429a 100644 --- a/src/utils/permalinks.ts +++ b/src/utils/permalinks.ts @@ -10,7 +10,7 @@ const createPath = (...params: string[]) => { .map((el) => trimSlash(el)) .filter((el) => !!el) .join('/'); - return '/' + paths + (SITE.trailingSlash && paths ? '/' : ''); + return '/' + paths + (SITE.trailingSlash && paths && !paths.includes('#') ? '/' : ''); }; const BASE_PATHNAME = SITE.base || '/'; @@ -29,36 +29,50 @@ export const POST_PERMALINK_PATTERN = trimSlash(APP_BLOG?.post?.permalink || `${ /** */ export const getCanonical = (path = ''): string | URL => { - const url = String(new URL(path, SITE.site)); + let url = String(new URL(path, SITE.site)); if (SITE.trailingSlash == false && path && url.endsWith('/')) { - return url.slice(0, -1); + url = url.slice(0, -1); } else if (SITE.trailingSlash == true && path && !url.endsWith('/')) { - return url + '/'; + url = url + '/'; + } + if (url.endsWith('/')) { + url = url.slice(0, -1); } return url; }; /** */ -export const getPermalink = (slug = '', type = 'page'): string => { +export const getPermalink = (slug = '', type = 'page', lang = ''): string => { + if (slug.startsWith('#')) { + return slug; + } + let permalink: string; if ( slug.startsWith('https://') || slug.startsWith('http://') || slug.startsWith('://') || - slug.startsWith('#') || slug.startsWith('javascript:') ) { return slug; } + + // Extract hash fragment if present + let hashFragment = ''; + if (slug.includes('#')) { + const parts = slug.split('#'); + slug = parts[0]; + hashFragment = '#' + parts[1]; + } switch (type) { case 'home': - permalink = getHomePermalink(); + permalink = getHomePermalink(lang); break; case 'blog': - permalink = getBlogPermalink(); + permalink = getBlogPermalink(lang); break; case 'asset': @@ -83,14 +97,15 @@ export const getPermalink = (slug = '', type = 'page'): string => { break; } - return definitivePermalink(permalink); + // Append hash fragment after creating the permalink + return definitivePermalink(permalink, lang) + hashFragment; }; /** */ -export const getHomePermalink = (): string => getPermalink('/'); +export const getHomePermalink = (lang = ''): string => getPermalink('/', 'page', lang); /** */ -export const getBlogPermalink = (): string => getPermalink(BLOG_BASE); +export const getBlogPermalink = (lang = ''): string => getPermalink(BLOG_BASE, 'page', lang); /** */ export const getAsset = (path: string): string => @@ -101,7 +116,22 @@ export const getAsset = (path: string): string => .join('/'); /** */ -const definitivePermalink = (permalink: string): string => createPath(BASE_PATHNAME, permalink); +const definitivePermalink = (permalink: string, lang = ''): string => { + // Don't add language prefix to hash-only links + if (permalink.startsWith('#')) { + return permalink; + } + + // Don't add language prefix to external links + if (permalink.startsWith('http://') || permalink.startsWith('https://') || permalink.startsWith('//')) { + return permalink; + } + + if (lang && ['en', 'nl', 'de', 'fr'].includes(lang)) { + return createPath(BASE_PATHNAME, lang, permalink); + } + return createPath(BASE_PATHNAME, permalink); +}; /** */ export const applyGetPermalinks = (menu: object = {}) => {