Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<!--
@component
Automatically detect and manage the language of your page.
It also adds `<link rel="alternate">` tags to the head of your page
It also adds `<link rel="alternate">` tags to the head of your page.

The component handles:
- Language detection and switching
- URL localization
- SEO meta tags
- Document language attributes
-->
<script lang="ts" generics="T extends string">
import type { I18n } from "./adapter.server.js"
Expand All @@ -13,44 +19,78 @@
import { LANGUAGE_CHANGE_INVALIDATION_KEY } from "../constants.js"
import { base as maybe_relative_base } from "$app/paths"
import { isExternal } from "./utils/external.js"
import { get } from "svelte/store"
import { invalidate } from "$app/navigation"
import { setParaglideContext } from "./internal/index.js"
import AlternateLinks from "./AlternateLinks.svelte"
import { createLangCookie } from "./utils/cookie.js"

/**
* Component props interface using Svelte 5's $props
* @property {T | undefined} languageTag - Override the language detection with a specific language tag
* @property {I18n<T>} i18n - The routing instance created with `createI18n()` from `@inlang/paraglide-sveltekit`
*/
const { languageTag, i18n } = $props<{
languageTag: T | undefined;
i18n: I18n<T>;
}>()

// The base path may be relative during SSR.
// To make sure it is absolute, we need to resolve it against the current page URL.
const absoluteBase = normaliseBase(maybe_relative_base, new URL($page.url)) || "/"
const absoluteBase = $derived(normaliseBase(maybe_relative_base, new URL($page.url)) || "/")

/**
* Override the language detection with a specific language tag.
*/
export let languageTag: T | undefined = undefined
// Track the number of language changes to handle invalidation
let numberOfLanguageChanges = $state(0)

// Derive the current language from props or URL
const currentUrl = $derived(new URL($page.url))
const lang = $derived(languageTag ?? i18n.getLanguageFromUrl(currentUrl))

// This key is used to force re-renders of slot content when language changes
const langKey = $derived(lang)

// Track if we've initialized the language
let isInitialized = $state(false)

/**
* The routing instance to use.
* You can create one with `createI18n()` from `@inlang/paraglide-sveltekit`.
* Effect to handle language changes and browser-specific operations
* Only runs after initial mount and when language actually changes
*/
export let i18n: I18n<T>

$: lang = languageTag ?? i18n.getLanguageFromUrl($page.url)
$: if (browser) i18n.config.runtime.setLanguageTag(lang)
$: if (browser) document.documentElement.lang = lang
$: if (browser) document.documentElement.dir = i18n.config.textDirection[lang] ?? "ltr"

// count the number of language changes.
let numberOfLanugageChanges = 0
$: if (lang) numberOfLanugageChanges += 1
$effect(() => {
if (!browser || !lang) return

if (!isInitialized) {
isInitialized = true
i18n.config.runtime.setLanguageTag(lang)
document.documentElement.lang = lang
document.documentElement.dir = i18n.config.textDirection[lang] ?? "ltr"
document.cookie = createLangCookie(lang, absoluteBase)
return
}

// on all but the first language change, invalidate language-dependent data
// the development mode always performs csr, therefore invalidate immediately
$: if (browser && lang && (numberOfLanugageChanges > 1 || dev))
invalidate(LANGUAGE_CHANGE_INVALIDATION_KEY)
// Handle subsequent language changes
i18n.config.runtime.setLanguageTag(lang)
document.documentElement.lang = lang
document.documentElement.dir = i18n.config.textDirection[lang] ?? "ltr"

numberOfLanguageChanges++

// Invalidate language-dependent data after first change or in dev mode
if (numberOfLanguageChanges > 1 || dev) {
invalidate(LANGUAGE_CHANGE_INVALIDATION_KEY)
}

document.cookie = createLangCookie(lang, absoluteBase)
})

/**
* Translates a URL to the target language
* @param {string} href - The URL to translate
* @param {T | undefined} hreflang - The target language tag
* @returns {string} The translated URL
*/
function translateHref(href: string, hreflang: T | undefined): string {
try {
const localisedCurrentUrl = new URL(get(page).url)
const localisedCurrentUrl = new URL($page.url)
const [localisedCurrentPath, suffix] = parseRoute(localisedCurrentUrl.pathname, absoluteBase)
const canonicalCurrentPath = i18n.strategy.getCanonicalPath(localisedCurrentPath, lang)

Expand All @@ -70,7 +110,6 @@
const translatedPath = i18n.strategy.getLocalisedPath(canonicalPath, targetLanguage)

const to = new URL(original_to)

to.pathname = serializeRoute(translatedPath, absoluteBase, dataSuffix)

return getHrefBetween(localisedCurrentUrl, to)
Expand All @@ -80,14 +119,8 @@
}
}

// Set up the context for child components
setParaglideContext({ translateHref })

// In svelte 5 the #key block will re-render the second the key changes,
// not after the all the updates in the Component are done.
// We need to make sure that changing the key happens last.
// See https://github.com/sveltejs/svelte/issues/10597
$: langKey = lang
$: if (browser) document.cookie = createLangCookie(lang, absoluteBase)
</script>

<svelte:head>
Expand All @@ -100,7 +133,7 @@
{/if}
</svelte:head>

<!-- Trigger a Re-Render whenever the language changes -->
<!-- Re-render slot content whenever the language changes -->
{#key langKey}
<slot />
{/key}
Loading