Skip to content

Commit f1bf3e9

Browse files
feat: add open to open browser directly
1 parent 2c6a4af commit f1bf3e9

9 files changed

Lines changed: 1094 additions & 10 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ fi\n\
121121
\n\
122122
# Default to serve if no arguments provided\n\
123123
if [ $# -eq 0 ]; then\n\
124-
set -- serve --port "${PORT:-4000}"\n\
124+
set -- serve --port "${PORT:-4000} --no-browser"\n\
125125
fi\n\
126126
\n\
127127
# Execute the main application\n\

dist/index.js

Lines changed: 6 additions & 6 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: 719 additions & 1 deletion
Large diffs are not rendered by default.

dist/server/public/i18n.js

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

0 commit comments

Comments
 (0)