169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
import { writable, derived, get } from "svelte/store"
|
|
import { location } from "./store"
|
|
|
|
/**
|
|
* Supported languages configuration.
|
|
* Add more languages as needed for your project.
|
|
*/
|
|
export const SUPPORTED_LANGUAGES = ["de", "en"] as const
|
|
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]
|
|
|
|
export const DEFAULT_LANGUAGE: SupportedLanguage = "de"
|
|
|
|
export const LANGUAGE_LABELS: Record<SupportedLanguage, string> = {
|
|
de: "Deutsch",
|
|
en: "English",
|
|
}
|
|
|
|
/**
|
|
* Route translations for localized URLs.
|
|
* Add entries for routes that need translated slugs.
|
|
* Example: { about: { de: "ueber-uns", en: "about" } }
|
|
*/
|
|
export const ROUTE_TRANSLATIONS: Record<string, Record<SupportedLanguage, string>> = {
|
|
// Add your route translations here:
|
|
// about: { de: "ueber-uns", en: "about" },
|
|
}
|
|
|
|
export const getLocalizedRoute = (canonicalRoute: string, lang: SupportedLanguage): string => {
|
|
const translations = ROUTE_TRANSLATIONS[canonicalRoute]
|
|
if (translations && translations[lang]) {
|
|
return translations[lang]
|
|
}
|
|
return canonicalRoute
|
|
}
|
|
|
|
export const getCanonicalRoute = (localizedSegment: string): string => {
|
|
for (const [canonical, translations] of Object.entries(ROUTE_TRANSLATIONS)) {
|
|
for (const translated of Object.values(translations)) {
|
|
if (translated === localizedSegment) {
|
|
return canonical
|
|
}
|
|
}
|
|
}
|
|
return localizedSegment
|
|
}
|
|
|
|
/**
|
|
* Extract the language code from a URL path.
|
|
* Returns null if no valid language prefix is found.
|
|
*/
|
|
export const extractLanguageFromPath = (path: string): SupportedLanguage | null => {
|
|
const match = path.match(/^\/([a-z]{2})(\/|$)/)
|
|
if (match && SUPPORTED_LANGUAGES.includes(match[1] as SupportedLanguage)) {
|
|
return match[1] as SupportedLanguage
|
|
}
|
|
return null
|
|
}
|
|
|
|
export const stripLanguageFromPath = (path: string): string => {
|
|
const lang = extractLanguageFromPath(path)
|
|
if (lang) {
|
|
const stripped = path.slice(3)
|
|
return stripped || "/"
|
|
}
|
|
return path
|
|
}
|
|
|
|
export const getRoutePath = (fullPath: string): string => {
|
|
const stripped = stripLanguageFromPath(fullPath)
|
|
if (stripped === "/" || stripped === "") {
|
|
return "/"
|
|
}
|
|
const segments = stripped.split("/").filter(Boolean)
|
|
if (segments.length > 0) {
|
|
const canonicalFirst = getCanonicalRoute(segments[0])
|
|
if (canonicalFirst !== segments[0]) {
|
|
segments[0] = canonicalFirst
|
|
return "/" + segments.join("/")
|
|
}
|
|
}
|
|
return stripped
|
|
}
|
|
|
|
/**
|
|
* Derived store: current language based on URL.
|
|
*/
|
|
export const currentLanguage = derived(location, ($location) => {
|
|
const path = $location.path || "/"
|
|
return extractLanguageFromPath(path) || DEFAULT_LANGUAGE
|
|
})
|
|
|
|
/**
|
|
* Writable store for the selected language.
|
|
*/
|
|
export const selectedLanguage = writable<SupportedLanguage>(DEFAULT_LANGUAGE)
|
|
|
|
if (typeof window !== "undefined") {
|
|
const initialLang = extractLanguageFromPath(window.location.pathname)
|
|
if (initialLang) {
|
|
selectedLanguage.set(initialLang)
|
|
}
|
|
}
|
|
|
|
if (typeof window !== "undefined") {
|
|
location.subscribe(($loc) => {
|
|
const lang = extractLanguageFromPath($loc.path)
|
|
if (lang) {
|
|
selectedLanguage.set(lang)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Get the localized path for a given route and language.
|
|
*/
|
|
export const localizedPath = (path: string, lang?: SupportedLanguage): string => {
|
|
const language = lang || get(currentLanguage)
|
|
if (path === "/" || path === "") {
|
|
return `/${language}`
|
|
}
|
|
let cleanPath = stripLanguageFromPath(path)
|
|
cleanPath = cleanPath.startsWith("/") ? cleanPath : `/${cleanPath}`
|
|
const segments = cleanPath.split("/").filter(Boolean)
|
|
if (segments.length > 0) {
|
|
const canonicalFirst = getCanonicalRoute(segments[0])
|
|
const localizedFirst = getLocalizedRoute(canonicalFirst, language)
|
|
segments[0] = localizedFirst
|
|
}
|
|
const translatedPath = "/" + segments.join("/")
|
|
return `/${language}${translatedPath}`
|
|
}
|
|
|
|
/**
|
|
* Derived store for localized href.
|
|
*/
|
|
export const localizedHref = (path: string) => {
|
|
return derived(currentLanguage, ($lang) => localizedPath(path, $lang))
|
|
}
|
|
|
|
export const isValidLanguage = (lang: string): lang is SupportedLanguage => {
|
|
return SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage)
|
|
}
|
|
|
|
/**
|
|
* Get the browser's preferred language, falling back to default.
|
|
*/
|
|
export const getBrowserLanguage = (): SupportedLanguage => {
|
|
if (typeof navigator === "undefined") {
|
|
return DEFAULT_LANGUAGE
|
|
}
|
|
const browserLangs = navigator.languages || [navigator.language]
|
|
for (const lang of browserLangs) {
|
|
const code = lang.split("-")[0].toLowerCase()
|
|
if (isValidLanguage(code)) {
|
|
return code
|
|
}
|
|
}
|
|
return DEFAULT_LANGUAGE
|
|
}
|
|
|
|
/**
|
|
* Get URL for switching to a different language.
|
|
*/
|
|
export const getLanguageSwitchUrl = (newLang: SupportedLanguage): string => {
|
|
const currentPath = typeof window !== "undefined" ? window.location.pathname : "/"
|
|
const canonicalPath = getRoutePath(currentPath)
|
|
return localizedPath(canonicalPath, newLang)
|
|
}
|