Files
tibi-svelte-starter/frontend/src/lib/i18n.ts
T

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)
}