110 lines
2.9 KiB
TypeScript
110 lines
2.9 KiB
TypeScript
'use client'
|
|
|
|
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
|
import enTranslations from './translations/en.json'
|
|
import deTranslations from './translations/de.json'
|
|
|
|
type Language = 'en' | 'de'
|
|
|
|
type TranslationKey = string
|
|
type Translations = typeof enTranslations
|
|
|
|
interface I18nContextType {
|
|
language: Language
|
|
setLanguage: (lang: Language) => void
|
|
t: (key: TranslationKey, params?: Record<string, string | number>) => string
|
|
}
|
|
|
|
const I18nContext = createContext<I18nContextType | undefined>(undefined)
|
|
|
|
const translations: Record<Language, Translations> = {
|
|
en: enTranslations,
|
|
de: deTranslations,
|
|
}
|
|
|
|
export function I18nProvider({ children }: { children: ReactNode }) {
|
|
const [language, setLanguageState] = useState<Language>('en')
|
|
|
|
// Load language from localStorage on mount
|
|
useEffect(() => {
|
|
const savedLanguage = localStorage.getItem('language') as Language
|
|
if (savedLanguage && (savedLanguage === 'en' || savedLanguage === 'de')) {
|
|
setLanguageState(savedLanguage)
|
|
} else {
|
|
// Detect browser language
|
|
const browserLang = navigator.language.split('-')[0]
|
|
if (browserLang === 'de') {
|
|
setLanguageState('de')
|
|
} else {
|
|
setLanguageState('en')
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
const setLanguage = (lang: Language) => {
|
|
setLanguageState(lang)
|
|
localStorage.setItem('language', lang)
|
|
// Update HTML lang attribute
|
|
if (typeof document !== 'undefined') {
|
|
document.documentElement.lang = lang
|
|
}
|
|
}
|
|
|
|
const t = (key: TranslationKey, params?: Record<string, string | number>): string => {
|
|
const keys = key.split('.')
|
|
let value: any = translations[language]
|
|
|
|
for (const k of keys) {
|
|
if (value && typeof value === 'object' && k in value) {
|
|
value = value[k]
|
|
} else {
|
|
// Fallback to English if key not found
|
|
value = translations.en
|
|
for (const fallbackKey of keys) {
|
|
if (value && typeof value === 'object' && fallbackKey in value) {
|
|
value = value[fallbackKey]
|
|
} else {
|
|
return key // Return key if translation not found
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if (typeof value !== 'string') {
|
|
return key
|
|
}
|
|
|
|
// Replace parameters in the translation string
|
|
if (params) {
|
|
return value.replace(/\{(\w+)\}/g, (match, paramKey) => {
|
|
return params[paramKey]?.toString() || match
|
|
})
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
// Update HTML lang attribute when language changes
|
|
useEffect(() => {
|
|
if (typeof document !== 'undefined') {
|
|
document.documentElement.lang = language
|
|
}
|
|
}, [language])
|
|
|
|
return (
|
|
<I18nContext.Provider value={{ language, setLanguage, t }}>
|
|
{children}
|
|
</I18nContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function useI18n() {
|
|
const context = useContext(I18nContext)
|
|
if (context === undefined) {
|
|
throw new Error('useI18n must be used within an I18nProvider')
|
|
}
|
|
return context
|
|
}
|
|
|