Skip to content

Commit 8bc4765

Browse files
committed
add i18n
1 parent 0845813 commit 8bc4765

13 files changed

Lines changed: 2103 additions & 757 deletions

File tree

dist/server/public/i18n.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* i18n.js - Internationalization system
3+
* Supports JSON translation files with dot notation keys
4+
*/
5+
6+
class I18n {
7+
constructor() {
8+
this.currentLanguage = 'en'
9+
this.translations = {}
10+
this.fallbackLanguage = 'en'
11+
}
12+
13+
async loadLanguage(lang) {
14+
try {
15+
const response = await fetch(`/locales/${lang}.json`)
16+
if (!response.ok) {
17+
throw new Error(`Failed to load language file: ${lang}`)
18+
}
19+
this.translations[lang] = await response.json()
20+
return true
21+
} catch (error) {
22+
console.error(`Error loading language ${lang}:`, error)
23+
return false
24+
}
25+
}
26+
27+
async setLanguage(lang) {
28+
// Load language if not already loaded
29+
if (!this.translations[lang]) {
30+
const success = await this.loadLanguage(lang)
31+
if (!success && lang !== this.fallbackLanguage) {
32+
// Try fallback
33+
await this.loadLanguage(this.fallbackLanguage)
34+
lang = this.fallbackLanguage
35+
}
36+
}
37+
38+
this.currentLanguage = lang
39+
localStorage.setItem('language', lang)
40+
this.updatePageTranslations()
41+
this.updateLanguageSelector()
42+
}
43+
44+
t(key, fallback = '') {
45+
const translation = this.translations[this.currentLanguage]?.[key] ||
46+
this.translations[this.fallbackLanguage]?.[key] ||
47+
fallback ||
48+
key
49+
return translation
50+
}
51+
52+
updatePageTranslations() {
53+
// Update all elements with data-i18n attribute
54+
document.querySelectorAll('[data-i18n]').forEach(element => {
55+
const key = element.getAttribute('data-i18n')
56+
const translation = this.t(key)
57+
58+
if (translation.includes('<') && translation.includes('>')) {
59+
element.innerHTML = translation
60+
} else {
61+
element.textContent = translation
62+
}
63+
})
64+
65+
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
66+
const key = element.getAttribute('data-i18n-placeholder')
67+
element.placeholder = this.t(key)
68+
})
69+
70+
document.querySelectorAll('[data-i18n-title]').forEach(element => {
71+
const key = element.getAttribute('data-i18n-title')
72+
element.title = this.t(key)
73+
})
74+
75+
document.querySelectorAll('[data-i18n-html]').forEach(element => {
76+
const key = element.getAttribute('data-i18n-html')
77+
element.innerHTML = this.t(key)
78+
})
79+
80+
document.querySelectorAll('[data-i18n-description]').forEach(element => {
81+
const key = element.getAttribute('data-i18n-description')
82+
const translation = this.t(key)
83+
element.setAttribute('data-description', translation)
84+
})
85+
}
86+
87+
updateLanguageSelector() {
88+
const selector = document.getElementById('language-selector')
89+
if (selector) {
90+
selector.value = this.currentLanguage
91+
}
92+
}
93+
94+
async init() {
95+
const savedLang = localStorage.getItem('language')
96+
97+
let initialLang = savedLang || navigator.language.split('-')[0] || 'en'
98+
99+
const supportedLanguages = ['en', 'de']
100+
if (!supportedLanguages.includes(initialLang)) {
101+
initialLang = 'en'
102+
}
103+
104+
await this.loadLanguage(this.fallbackLanguage)
105+
106+
if (initialLang !== this.fallbackLanguage) {
107+
await this.loadLanguage(initialLang)
108+
}
109+
110+
this.currentLanguage = initialLang
111+
this.updatePageTranslations()
112+
this.updateLanguageSelector()
113+
114+
const selector = document.getElementById('language-selector')
115+
if (selector) {
116+
selector.addEventListener('change', (e) => {
117+
this.setLanguage(e.target.value)
118+
})
119+
}
120+
}
121+
}
122+
123+
const i18n = new I18n()
124+
125+
if (document.readyState === 'loading') {
126+
document.addEventListener('DOMContentLoaded', () => i18n.init())
127+
} else {
128+
i18n.init()
129+
}
130+
131+
window.i18n = i18n

0 commit comments

Comments
 (0)