✨ feat: implement new feature for enhanced user experience
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user