From b7e54673690716c554b16ce1d7c847b121f1c07a Mon Sep 17 00:00:00 2001 From: cclohmar Date: Sun, 14 Jun 2026 18:21:54 +0000 Subject: [PATCH] feat(i18n): EN/DE translations, PageCtx, slide-out settings drawer --- src/core/i18n/i18n.go | 87 ++++++++++ src/core/ui/handler.go | 49 ++++-- src/core/ui/launcher.templ | 34 ++-- src/core/ui/launcher_templ.go | 290 +++++++++++++++++++++++----------- 4 files changed, 337 insertions(+), 123 deletions(-) create mode 100644 src/core/i18n/i18n.go diff --git a/src/core/i18n/i18n.go b/src/core/i18n/i18n.go new file mode 100644 index 0000000..4d62f45 --- /dev/null +++ b/src/core/i18n/i18n.go @@ -0,0 +1,87 @@ +package i18n + +// T holds all translation keys. +type T map[string]string + +var translations = map[string]T{ + "en": { + "greeting_morning": "Good morning", + "greeting_afternoon": "Good afternoon", + "greeting_evening": "Good evening", + "workspace_ready": "Your workspace is ready", + "install_app": "Install app", + "settings": "Settings", + "logout": "Logout", + "language": "Language", + "timezone": "Timezone", + "save": "Save", + "available": "Available", + "coming_soon": "Coming Soon", + "users_groups": "Users, groups & settings", + "storage_sharing": "Storage & sharing", + "email_client": "Email client", + "schedule_events": "Schedule & events", + "documents_sheets": "Documents & sheets", + "people_directory": "People & directory", + "project_management": "Project management", + "team_messaging": "Team messaging", + "pwa_desktop_title": "Desktop Chrome/Edge", + "pwa_desktop_step1": "Click the ⊕ icon in the address bar", + "pwa_desktop_step2": "Click Install", + "pwa_ios_title": "iOS Safari", + "pwa_ios_step1": "Tap Share ⎋", + "pwa_ios_step2": "Tap Add to Home Screen", + "pwa_android_title": "Android Chrome", + "pwa_android_step1": "Tap ⋮ menu", + "pwa_android_step2": "Tap Install app", + "pwa_got_it": "Got it", + "install_workspace": "Install Next Workspace", + }, + "de": { + "greeting_morning": "Guten Morgen", + "greeting_afternoon": "Guten Tag", + "greeting_evening": "Guten Abend", + "workspace_ready": "Ihr Arbeitsbereich ist bereit", + "install_app": "App installieren", + "settings": "Einstellungen", + "logout": "Abmelden", + "language": "Sprache", + "timezone": "Zeitzone", + "save": "Speichern", + "available": "Verfügbar", + "coming_soon": "Demnächst", + "users_groups": "Benutzer, Gruppen & Einstellungen", + "storage_sharing": "Speicher & Freigabe", + "email_client": "E-Mail-Client", + "schedule_events": "Termine & Ereignisse", + "documents_sheets": "Dokumente & Tabellen", + "people_directory": "Personen & Verzeichnis", + "project_management": "Projektmanagement", + "team_messaging": "Team-Chat", + "pwa_desktop_title": "Desktop Chrome/Edge", + "pwa_desktop_step1": "Klicken Sie auf das ⊕-Symbol in der Adressleiste", + "pwa_desktop_step2": "Klicken Sie auf Installieren", + "pwa_ios_title": "iOS Safari", + "pwa_ios_step1": "Tippen Sie auf Teilen ⎋", + "pwa_ios_step2": "Tippen Sie auf Zum Home-Bildschirm", + "pwa_android_title": "Android Chrome", + "pwa_android_step1": "Tippen Sie auf ⋮ Menü", + "pwa_android_step2": "Tippen Sie auf App installieren", + "pwa_got_it": "Verstanden", + "install_workspace": "Next Workspace installieren", + }, +} + +// Get returns the translation map for a language code. +func Get(lang string) T { + t, ok := translations[lang] + if !ok { + t = translations["en"] + } + return t +} + +// Supported returns the list of supported language codes. +func Supported() []string { + return []string{"en", "de"} +} diff --git a/src/core/ui/handler.go b/src/core/ui/handler.go index 063bec8..8f447af 100644 --- a/src/core/ui/handler.go +++ b/src/core/ui/handler.go @@ -3,21 +3,27 @@ package ui import ( "net/http" "path/filepath" + "strings" "git.lohmar.co.uk/lexton-it/NextWks/core/auth" + "git.lohmar.co.uk/lexton-it/NextWks/core/i18n" ) -// Handler serves the workspace launcher UI. +// PageCtx holds page-level data passed to templates. +type PageCtx struct { + UserID string + Role string + Locale i18n.T +} + type Handler struct { appDir string } -// NewHandler creates a UI handler that serves the launcher and static assets. func NewHandler(appDir string) *Handler { return &Handler{appDir: appDir} } -// RegisterRoutes mounts the public UI routes on the given mux. func (h *Handler) RegisterRoutes(mux *http.ServeMux, authGate func(http.Handler) http.Handler) { staticDir := filepath.Join(h.appDir, "static") mux.Handle("GET /static/", http.StripPrefix("/static", http.FileServer(http.Dir(staticDir)))) @@ -30,18 +36,35 @@ func (h *Handler) launcherPage(w http.ResponseWriter, r *http.Request) { return } - userID, _ := auth.GetUserID(r) - role := getUserRole(r) - apps := DefaultApps(role) - - component := LauncherPage(userID, role, apps) + ctx := getContext(r) + apps := DefaultApps(ctx.Role) + component := LauncherPage(ctx, apps) component.Render(r.Context(), w) } -// getUserRole extracts the user's role from the request context or session. -func getUserRole(r *http.Request) string { - if role, ok := r.Context().Value(auth.ContextRole).(string); ok { - return role +func getContext(r *http.Request) PageCtx { + userID, _ := auth.GetUserID(r) + role, _ := r.Context().Value(auth.ContextRole).(string) + if role == "" { + role = "user" + } + + // Detect language: cookie > header > default + lang := "en" + if cookie, err := r.Cookie("lang"); err == nil { + lang = cookie.Value + } else if al := r.Header.Get("Accept-Language"); al != "" { + for _, l := range i18n.Supported() { + if strings.HasPrefix(al, l) { + lang = l + break + } + } + } + + return PageCtx{ + UserID: userID, + Role: role, + Locale: i18n.Get(lang), } - return "user" } diff --git a/src/core/ui/launcher.templ b/src/core/ui/launcher.templ index 9858682..85b7634 100644 --- a/src/core/ui/launcher.templ +++ b/src/core/ui/launcher.templ @@ -2,7 +2,7 @@ package ui import appver "git.lohmar.co.uk/lexton-it/NextWks/core/version" -templ LauncherPage(userName string, role string, apps []AppTile) { +templ LauncherPage(c PageCtx, apps []AppTile) { @@ -18,9 +18,9 @@ templ LauncherPage(userName string, role string, apps []AppTile) {
- @topBar(userName, role) + @topBar(c)
- @greetingSection(userName) + @greetingSection(c) @AppGrid(apps)
@@ -45,19 +45,19 @@ templ LauncherPage(userName string, role string, apps []AppTile) {
@@ -75,37 +75,37 @@ templ LauncherPage(userName string, role string, apps []AppTile) { } -templ greetingSection(userName string) { +templ greetingSection(page PageCtx) {
-
{ initials(userName) }
+
{ initials(page.UserID) }
-

Good { timeOfDay() }, { userName }

-

Your workspace is ready

+

{ page.Locale["greeting_morning"] }, { page.UserID }

+

{ page.Locale["workspace_ready"] }

} -templ topBar(userName string, role string) { +templ topBar(page PageCtx) {
N
NextWks
-
-
{ initials(userName) }
+
{ initials(page.UserID) }
diff --git a/src/core/ui/launcher_templ.go b/src/core/ui/launcher_templ.go index e1d1cac..84b86ff 100644 --- a/src/core/ui/launcher_templ.go +++ b/src/core/ui/launcher_templ.go @@ -10,7 +10,7 @@ import templruntime "github.com/a-h/templ/runtime" import appver "git.lohmar.co.uk/lexton-it/NextWks/core/version" -func LauncherPage(userName string, role string, apps []AppTile) templ.Component { +func LauncherPage(c PageCtx, apps []AppTile) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -43,7 +43,7 @@ func LauncherPage(userName string, role string, apps []AppTile) templ.Component if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = topBar(userName, role).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = topBar(c).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -51,7 +51,7 @@ func LauncherPage(userName string, role string, apps []AppTile) templ.Component if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = greetingSection(userName).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = greetingSection(c).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -80,7 +80,59 @@ func LauncherPage(userName string, role string, apps []AppTile) templ.Component if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -88,7 +140,7 @@ func LauncherPage(userName string, role string, apps []AppTile) templ.Component if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -96,75 +148,7 @@ func LauncherPage(userName string, role string, apps []AppTile) templ.Component }) } -func greetingSection(userName string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var3 := templ.GetChildren(ctx) - if templ_7745c5c3_Var3 == nil { - templ_7745c5c3_Var3 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(initials(userName)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 80, Col: 42} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

Good ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(timeOfDay()) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 82, Col: 25} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, ", ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(userName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 82, Col: 39} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

Your workspace is ready

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func topBar(userName string, role string) templ.Component { +func greetingSection(page PageCtx) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -185,33 +169,153 @@ func topBar(userName string, role string) templ.Component { templ_7745c5c3_Var7 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
N
NextWks
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(initials(userName)) + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(initials(page.UserID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 99, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 80, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(userName) + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(page.Locale["greeting_morning"]) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 101, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 82, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Settings
Logout
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, ", ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(page.UserID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 82, Col: 57} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(page.Locale["workspace_ready"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 83, Col: 38} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func topBar(page PageCtx) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
N
NextWks
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(initials(page.UserID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 99, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(page.UserID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 101, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(page.Locale["settings"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 104, Col: 31} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(page.Locale["logout"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 108, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -235,12 +339,12 @@ func workspaceCSS() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var10 := templ.GetChildren(ctx) - if templ_7745c5c3_Var10 == nil { - templ_7745c5c3_Var10 = templ.NopComponent + templ_7745c5c3_Var18 := templ.GetChildren(ctx) + if templ_7745c5c3_Var18 == nil { + templ_7745c5c3_Var18 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -286,12 +390,12 @@ func pwaInstructions() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var11 := templ.GetChildren(ctx) - if templ_7745c5c3_Var11 == nil { - templ_7745c5c3_Var11 = templ.NopComponent + templ_7745c5c3_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Desktop Chrome/Edge

  1. Click the icon in the address bar
  2. Click Install

iOS Safari

  1. Tap Share
  2. Tap Add to Home Screen

Android Chrome

  1. Tap menu
  2. Tap Install app
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

Desktop Chrome/Edge

  1. Click the icon in the address bar
  2. Click Install

iOS Safari

  1. Tap Share
  2. Tap Add to Home Screen

Android Chrome

  1. Tap menu
  2. Tap Install app
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -316,12 +420,12 @@ func launcherJS() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var12 := templ.GetChildren(ctx) - if templ_7745c5c3_Var12 == nil { - templ_7745c5c3_Var12 = templ.NopComponent + templ_7745c5c3_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }