Skip to content

Commit 712d247

Browse files
ebub first works
1 parent fd2ac41 commit 712d247

8 files changed

Lines changed: 1907 additions & 241 deletions

File tree

dist/index.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/server/public/app.js

Lines changed: 671 additions & 1 deletion
Large diffs are not rendered by default.

dist/server/public/i18n.js

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,169 @@
1-
class I18n{constructor(){this.currentLanguage="en",this.translations={},this.fallbackLanguage="en"}async loadLanguage(t){try{const e=await fetch(`/locales/${t}.json`);if(!e.ok)throw new Error(`Failed to load language file: ${t}`);return this.translations[t]=await e.json(),!0}catch(e){return console.error(`Error loading language ${t}:`,e),!1}}async setLanguage(t){if(!this.translations[t]){await this.loadLanguage(t)||t===this.fallbackLanguage||(await this.loadLanguage(this.fallbackLanguage),t=this.fallbackLanguage)}this.currentLanguage=t,localStorage.setItem("language",t),this.updatePageTranslations(),this.updateLanguageSelector(),this.updateDynamicContent()}t(t,e=""){return this.translations[this.currentLanguage]?.[t]||this.translations[this.fallbackLanguage]?.[t]||e||t}updatePageTranslations(){document.querySelectorAll("[data-i18n]").forEach((t=>{const e=t.getAttribute("data-i18n"),a=this.t(e);a.includes("<")&&a.includes(">")?t.innerHTML=a:t.textContent=a})),document.querySelectorAll("[data-i18n-placeholder]").forEach((t=>{const e=t.getAttribute("data-i18n-placeholder");t.placeholder=this.t(e)})),document.querySelectorAll("[data-i18n-title]").forEach((t=>{const e=t.getAttribute("data-i18n-title");t.title=this.t(e)})),document.querySelectorAll("[data-i18n-html]").forEach((t=>{const e=t.getAttribute("data-i18n-html");t.innerHTML=this.t(e)})),document.querySelectorAll("[data-i18n-description]").forEach((t=>{const e=t.getAttribute("data-i18n-description"),a=this.t(e);t.setAttribute("data-description",a)}))}updateDynamicContent(){const t=document.querySelector('input[name="preset"]:checked');if(t&&t.dataset.descriptionKey){const e=document.getElementById("preset-description");if(e&&"none"!==e.style.display){const a=e.querySelector("p");if(a){const e=this.t(t.dataset.descriptionKey);a.innerHTML=e}}}const e=document.querySelector('input[name="format"]:checked');if(e&&e.dataset.description){const t=document.getElementById("format-description");if(t&&"none"!==t.style.display){const a=t.querySelector("p");if(a){const t=e.dataset.description;a.innerHTML=t}}}const a=document.querySelectorAll(".remove-file");if(a.length>0){const t=this.t("files.remove");a.forEach((e=>{e.title=t}))}}updateLanguageSelector(){const t=document.getElementById("language-selector");t&&(t.value=this.currentLanguage)}async init(){let t=localStorage.getItem("language")||navigator.language.split("-")[0]||"en";["en","de"].includes(t)||(t="en"),await this.loadLanguage(this.fallbackLanguage),t!==this.fallbackLanguage&&await this.loadLanguage(t),this.currentLanguage=t,this.updatePageTranslations(),this.updateLanguageSelector();const e=document.getElementById("language-selector");e&&e.addEventListener("change",(t=>{this.setLanguage(t.target.value)}))}}const i18n=new I18n;"loading"===document.readyState?document.addEventListener("DOMContentLoaded",(()=>i18n.init())):i18n.init(),window.i18n=i18n;
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+
this.updateDynamicContent()
43+
}
44+
45+
t(key, fallback = '') {
46+
const translation = this.translations[this.currentLanguage]?.[key] ||
47+
this.translations[this.fallbackLanguage]?.[key] ||
48+
fallback ||
49+
key
50+
return translation
51+
}
52+
53+
// Update all elements with data-i18n attribute
54+
updatePageTranslations() {
55+
document.querySelectorAll('[data-i18n]').forEach(element => {
56+
const key = element.getAttribute('data-i18n')
57+
const translation = this.t(key)
58+
59+
if (translation.includes('<') && translation.includes('>')) {
60+
element.innerHTML = translation
61+
} else {
62+
element.textContent = translation
63+
}
64+
})
65+
66+
document.querySelectorAll('[data-i18n-placeholder]').forEach(element => {
67+
const key = element.getAttribute('data-i18n-placeholder')
68+
element.placeholder = this.t(key)
69+
})
70+
71+
document.querySelectorAll('[data-i18n-title]').forEach(element => {
72+
const key = element.getAttribute('data-i18n-title')
73+
element.title = this.t(key)
74+
})
75+
76+
document.querySelectorAll('[data-i18n-html]').forEach(element => {
77+
const key = element.getAttribute('data-i18n-html')
78+
element.innerHTML = this.t(key)
79+
})
80+
81+
document.querySelectorAll('[data-i18n-description]').forEach(element => {
82+
const key = element.getAttribute('data-i18n-description')
83+
const translation = this.t(key)
84+
element.setAttribute('data-description', translation)
85+
})
86+
}
87+
88+
updateDynamicContent() {
89+
// Update preset description if one is currently selected
90+
const checkedPreset = document.querySelector('input[name="preset"]:checked')
91+
if (checkedPreset && checkedPreset.dataset.descriptionKey) {
92+
const descriptionBox = document.getElementById('preset-description')
93+
if (descriptionBox && descriptionBox.style.display !== 'none') {
94+
const descriptionText = descriptionBox.querySelector('p')
95+
if (descriptionText) {
96+
const description = this.t(checkedPreset.dataset.descriptionKey)
97+
descriptionText.innerHTML = description
98+
}
99+
}
100+
}
101+
102+
// Update format description if one is currently selected
103+
const checkedFormat = document.querySelector('input[name="format"]:checked')
104+
if (checkedFormat && checkedFormat.dataset.description) {
105+
const descriptionBox = document.getElementById('format-description')
106+
if (descriptionBox && descriptionBox.style.display !== 'none') {
107+
const descriptionText = descriptionBox.querySelector('p')
108+
if (descriptionText) {
109+
const description = checkedFormat.dataset.description
110+
descriptionText.innerHTML = description
111+
}
112+
}
113+
}
114+
115+
// Update file list remove button titles
116+
const removeButtons = document.querySelectorAll('.remove-file')
117+
if (removeButtons.length > 0) {
118+
const removeTitle = this.t('files.remove')
119+
removeButtons.forEach(btn => {
120+
btn.title = removeTitle
121+
})
122+
}
123+
}
124+
125+
updateLanguageSelector() {
126+
const selector = document.getElementById('language-selector')
127+
if (selector) {
128+
selector.value = this.currentLanguage
129+
}
130+
}
131+
132+
async init() {
133+
const savedLang = localStorage.getItem('language')
134+
135+
let initialLang = savedLang || navigator.language.split('-')[0] || 'en'
136+
137+
const supportedLanguages = ['en', 'de']
138+
if (!supportedLanguages.includes(initialLang)) {
139+
initialLang = 'en'
140+
}
141+
142+
await this.loadLanguage(this.fallbackLanguage)
143+
144+
if (initialLang !== this.fallbackLanguage) {
145+
await this.loadLanguage(initialLang)
146+
}
147+
148+
this.currentLanguage = initialLang
149+
this.updatePageTranslations()
150+
this.updateLanguageSelector()
151+
152+
const selector = document.getElementById('language-selector')
153+
if (selector) {
154+
selector.addEventListener('change', (e) => {
155+
this.setLanguage(e.target.value)
156+
})
157+
}
158+
}
159+
}
160+
161+
const i18n = new I18n()
162+
163+
if (document.readyState === 'loading') {
164+
document.addEventListener('DOMContentLoaded', () => i18n.init())
165+
} else {
166+
i18n.init()
167+
}
168+
169+
window.i18n = i18n

0 commit comments

Comments
 (0)