107 lines
3.3 KiB
Svelte
107 lines
3.3 KiB
Svelte
<script>
|
|
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
|
|
import { locale, t } from '$lib/translations';
|
|
import { normalizeLocaleUnderscore } from '$lib/utils/normalizeLocaleUnderscore';
|
|
import { isDevelopment } from '$lib/utils/env';
|
|
|
|
/**
|
|
* =========================
|
|
* Props
|
|
* =========================
|
|
*/
|
|
|
|
/** @type {string} Page title (translation key) */
|
|
export let title;
|
|
|
|
/** @type {string} Page description (translation key) */
|
|
export let description;
|
|
|
|
/** @type {string} SEO keywords (translation key or raw string) */
|
|
export let keywords;
|
|
|
|
/** @type {string} Canonical URL (absolute) */
|
|
export let url;
|
|
|
|
/** @type {string} Open Graph type (e.g., 'website', 'article') */
|
|
export let ogType = 'website';
|
|
|
|
/** @type {string} Optional override for Open Graph description */
|
|
export let ogDescription = description;
|
|
|
|
/** @type {string | undefined} Optional Open Graph image URL (translation key or absolute URL) */
|
|
export let ogImage = undefined;
|
|
|
|
/** @type {string} Twitter card type */
|
|
export let twitterCard = 'summary_large_image';
|
|
|
|
/** @type {boolean} Whether the page should be indexed */
|
|
export let shouldBeIndexed = !isDevelopment();
|
|
|
|
/**
|
|
* =========================
|
|
* Derived / Computed values
|
|
* =========================
|
|
*/
|
|
|
|
let defaultImage = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/logo-icon.webp`;
|
|
|
|
// Translations (avoid repeating $t everywhere)
|
|
$: translatedTitle = title ? $t(title) : '';
|
|
$: translatedDescription = description ? $t(description) : '';
|
|
$: translatedKeywords = keywords ? $t(keywords) : '';
|
|
|
|
// Fallbacks
|
|
$: finalOgDescription = ogDescription
|
|
? $t(ogDescription)
|
|
: translatedDescription;
|
|
|
|
$: ogImageUrl = ogImage
|
|
? ogImage.startsWith('http')
|
|
? ogImage
|
|
: $t(ogImage)
|
|
: defaultImage;
|
|
|
|
$: canonicalUrl = url
|
|
? url.startsWith('http')
|
|
? url
|
|
: $t(url)
|
|
: '';
|
|
|
|
// Locale formatting (e.g., en-US -> en_US)
|
|
$: ogLocale = normalizeLocaleUnderscore($locale);
|
|
|
|
// Robots directive
|
|
$: robotsContent = shouldBeIndexed ? 'index, follow' : 'noindex, nofollow';
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<!-- Primary SEO -->
|
|
<title>{translatedTitle}</title>
|
|
<meta name="description" content={translatedDescription} />
|
|
<meta name="keywords" content={translatedKeywords} />
|
|
{#if canonicalUrl}
|
|
<link rel="canonical" href={canonicalUrl} />
|
|
{/if}
|
|
|
|
<!-- Robots -->
|
|
<meta name="robots" content={robotsContent} />
|
|
<meta name="googlebot" content={robotsContent} />
|
|
|
|
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
|
|
<meta property="og:type" content={ogType} />
|
|
<meta property="og:title" content={translatedTitle} />
|
|
<meta property="og:description" content={finalOgDescription} />
|
|
<meta property="og:image" content={ogImageUrl} />
|
|
<meta property="og:url" content={canonicalUrl} />
|
|
<meta property="og:locale" content={ogLocale} />
|
|
<meta property="og:site_name" content="Embroidery Viewer" />
|
|
|
|
<!-- Twitter -->
|
|
<meta name="twitter:card" content={twitterCard} />
|
|
<meta name="twitter:title" content={translatedTitle} />
|
|
<meta name="twitter:description" content={finalOgDescription} />
|
|
<meta name="twitter:image" content={ogImageUrl} />
|
|
|
|
<!-- Optional: improves link previews in some platforms -->
|
|
<meta property="og:image:alt" content={translatedTitle} />
|
|
</svelte:head>
|