detached d517dee

Fix and refactor code to manage localization more efficiently.

- Added `StringsYML` field in `App` struct to store content of `strings.yml`.
    - Modified `NewApp` to read `strings.yml` during app initialization.
    - Refactored to use a new `GetLocalizedData` function to get language-specific data.
    - Adopted the `GetLocalizedData` function for language data.
    - Added dynamic localization using `Lang` map for dashboard title.
commit
d517deef2cea2fcf3f945c21114f85c25f51729a
author
bnema · · 2023-10-04 07:31 UTC
parent
306cd6f
signature
unsigned
10 files · +185 -126
M internal/app/app.go +17 -0
@@ -3,6 +3,7 @@ package app
33 import (
44 "database/sql"
55 "fmt"
6+ "io"
67 "io/fs"
78 "log"
89 "os"
@@ -22,6 +23,7 @@ var (
2223 type App struct {
2324 TemplateFS fs.FS
2425 PublicFS fs.FS
26+ StringsYML []byte // strings.yml contains the strings for the current language
2527 DBDir string
2628 DBFilename string
2729 DBPath string
@@ -97,10 +99,25 @@ func NewApp() *App {
9799 log.Fatalf("Failed to load configuration: %v", err)
98100 }
99101
102+ // Open the strings.yml file containing the strings for the current language
103+ file, err := templates.TemplateFS.Open("strings.yml")
104+ if err != nil {
105+ log.Fatalf("Failed to open strings.yml: %v", err)
106+ }
107+
108+ // Read the file content into a byte slice
109+ bytes, err := io.ReadAll(file)
110+ if err != nil {
111+ log.Fatalf("Failed to read strings.yml: %v", err)
112+ }
113+
114+ file.Close()
115+
100116 // Initialize App
101117 a := &App{
102118 TemplateFS: templates.TemplateFS,
103119 PublicFS: webui.PublicFS,
120+ StringsYML: bytes,
104121 DBDir: DBDir,
105122 DBFilename: DBFilename,
106123 Config: config,
@@ -3,6 +3,7 @@ package app
3 import (3 import (
4 "database/sql"4 "database/sql"
5 "fmt"5 "fmt"
6+ "io"
6 "io/fs"7 "io/fs"
7 "log"8 "log"
8 "os"9 "os"
@@ -22,6 +23,7 @@ var (
22 type App struct {23 type App struct {
23 TemplateFS fs.FS24 TemplateFS fs.FS
24 PublicFS fs.FS25 PublicFS fs.FS
26+ StringsYML []byte // strings.yml contains the strings for the current language
25 DBDir string27 DBDir string
26 DBFilename string28 DBFilename string
27 DBPath string29 DBPath string
@@ -97,10 +99,25 @@ func NewApp() *App {
97 log.Fatalf("Failed to load configuration: %v", err)99 log.Fatalf("Failed to load configuration: %v", err)
98 }100 }
99 101
102+ // Open the strings.yml file containing the strings for the current language
103+ file, err := templates.TemplateFS.Open("strings.yml")
104+ if err != nil {
105+ log.Fatalf("Failed to open strings.yml: %v", err)
106+ }
107+
108+ // Read the file content into a byte slice
109+ bytes, err := io.ReadAll(file)
110+ if err != nil {
111+ log.Fatalf("Failed to read strings.yml: %v", err)
112+ }
113+
114+ file.Close()
115+
100 // Initialize App116 // Initialize App
101 a := &App{117 a := &App{
102 TemplateFS: templates.TemplateFS,118 TemplateFS: templates.TemplateFS,
103 PublicFS: webui.PublicFS,119 PublicFS: webui.PublicFS,
120+ StringsYML: bytes,
104 DBDir: DBDir,121 DBDir: DBDir,
105 DBFilename: DBFilename,122 DBFilename: DBFilename,
106 Config: config,123 Config: config,
M internal/httpserve/handler/adminroutes.go +19 -20
@@ -2,12 +2,10 @@ package handler
22
33 import (
44 "fmt"
5- "net/http"
65
76 "github.com/bnema/gordon/internal/app"
87 "github.com/bnema/gordon/internal/httpserve/middleware"
98 "github.com/bnema/gordon/internal/templates/render"
10- "github.com/bnema/gordon/internal/webui"
119 "github.com/labstack/echo/v4"
1210 )
1311
@@ -16,27 +14,34 @@ var (
1614 fragmentsPath = "html/fragments"
1715 )
1816
17+func GetLocalizedData(c echo.Context, a *app.App) (map[string]interface{}, error) {
18+ lang := c.Get(middleware.LangKey)
19+
20+ if lang == nil {
21+ return nil, fmt.Errorf("LangKey not found in context")
22+ }
23+
24+ yamlData, err := render.GetLocalization(lang.(string), a)
25+ if err != nil {
26+ return nil, fmt.Errorf("failed to get localization: %w", err)
27+ }
28+ return map[string]interface{}{
29+ "Lang": yamlData,
30+ }, nil
31+}
32+
1933 // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml
2034 func AdminRoute(c echo.Context, a *app.App) error {
21- lang := c.Get(middleware.LangKey).(string)
22- yamlData := webui.StringsYamlData{}
23- err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
35+ data, err := GetLocalizedData(c, a)
2436 if err != nil {
2537 return err
2638 }
27-
2839 // Navigate inside the fs.FS to get the template
2940 rendererData, err := render.GetHTMLRenderer(mainPath, "index.gohtml", a.TemplateFS, a, fragmentsPath)
3041 if err != nil {
3142 return err
3243 }
3344
34- // Create a data map to pass to the renderer
35- data := map[string]interface{}{
36- "CurrentLang": yamlData.CurrentLang,
37- // "BuildVersion" will be automatically added in the renderer
38- }
39-
4045 renderedHTML, err := rendererData.Render(data, a)
4146 if err != nil {
4247 return err
@@ -46,12 +51,9 @@ func AdminRoute(c echo.Context, a *app.App) error {
4651 }
4752
4853 func AdminManagerRoute(c echo.Context, a *app.App) error {
49- lang := c.Get(middleware.LangKey).(string)
50- yamlData := webui.StringsYamlData{}
51- err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
54+ data, err := GetLocalizedData(c, a)
5255 if err != nil {
53- fmt.Printf("Error reading YAML: %v\n", err)
54- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to load language data")
56+ return err
5557 }
5658
5759 // Navigate inside the fs.FS to get the template
@@ -59,9 +61,6 @@ func AdminManagerRoute(c echo.Context, a *app.App) error {
5961 if err != nil {
6062 return err
6163 }
62- data := map[string]interface{}{
63- "CurrentLang": yamlData.CurrentLang,
64- }
6564
6665 renderedHTML, err := rendererData.Render(data, a)
6766 if err != nil {
@@ -2,12 +2,10 @@ package handler
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "net/http"
6 5
7 "github.com/bnema/gordon/internal/app"6 "github.com/bnema/gordon/internal/app"
8 "github.com/bnema/gordon/internal/httpserve/middleware"7 "github.com/bnema/gordon/internal/httpserve/middleware"
9 "github.com/bnema/gordon/internal/templates/render"8 "github.com/bnema/gordon/internal/templates/render"
10- "github.com/bnema/gordon/internal/webui"
11 "github.com/labstack/echo/v4"9 "github.com/labstack/echo/v4"
12 )10 )
13 11
@@ -16,27 +14,34 @@ var (
16 fragmentsPath = "html/fragments"14 fragmentsPath = "html/fragments"
17 )15 )
18 16
17+func GetLocalizedData(c echo.Context, a *app.App) (map[string]interface{}, error) {
18+ lang := c.Get(middleware.LangKey)
19+
20+ if lang == nil {
21+ return nil, fmt.Errorf("LangKey not found in context")
22+ }
23+
24+ yamlData, err := render.GetLocalization(lang.(string), a)
25+ if err != nil {
26+ return nil, fmt.Errorf("failed to get localization: %w", err)
27+ }
28+ return map[string]interface{}{
29+ "Lang": yamlData,
30+ }, nil
31+}
32+
19 // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml33 // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml
20 func AdminRoute(c echo.Context, a *app.App) error {34 func AdminRoute(c echo.Context, a *app.App) error {
21- lang := c.Get(middleware.LangKey).(string)35+ data, err := GetLocalizedData(c, a)
22- yamlData := webui.StringsYamlData{}
23- err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
24 if err != nil {36 if err != nil {
25 return err37 return err
26 }38 }
27-
28 // Navigate inside the fs.FS to get the template39 // Navigate inside the fs.FS to get the template
29 rendererData, err := render.GetHTMLRenderer(mainPath, "index.gohtml", a.TemplateFS, a, fragmentsPath)40 rendererData, err := render.GetHTMLRenderer(mainPath, "index.gohtml", a.TemplateFS, a, fragmentsPath)
30 if err != nil {41 if err != nil {
31 return err42 return err
32 }43 }
33 44
34- // Create a data map to pass to the renderer
35- data := map[string]interface{}{
36- "CurrentLang": yamlData.CurrentLang,
37- // "BuildVersion" will be automatically added in the renderer
38- }
39-
40 renderedHTML, err := rendererData.Render(data, a)45 renderedHTML, err := rendererData.Render(data, a)
41 if err != nil {46 if err != nil {
42 return err47 return err
@@ -46,12 +51,9 @@ func AdminRoute(c echo.Context, a *app.App) error {
46 }51 }
47 52
48 func AdminManagerRoute(c echo.Context, a *app.App) error {53 func AdminManagerRoute(c echo.Context, a *app.App) error {
49- lang := c.Get(middleware.LangKey).(string)54+ data, err := GetLocalizedData(c, a)
50- yamlData := webui.StringsYamlData{}
51- err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
52 if err != nil {55 if err != nil {
53- fmt.Printf("Error reading YAML: %v\n", err)56+ return err
54- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to load language data")
55 }57 }
56 58
57 // Navigate inside the fs.FS to get the template59 // Navigate inside the fs.FS to get the template
@@ -59,9 +61,6 @@ func AdminManagerRoute(c echo.Context, a *app.App) error {
59 if err != nil {61 if err != nil {
60 return err62 return err
61 }63 }
62- data := map[string]interface{}{
63- "CurrentLang": yamlData.CurrentLang,
64- }
65 64
66 renderedHTML, err := rendererData.Render(data, a)65 renderedHTML, err := rendererData.Render(data, a)
67 if err != nil {66 if err != nil {
M internal/httpserve/handler/oauth.go +2 -11
@@ -8,9 +8,7 @@ import (
88 "strings"
99
1010 "github.com/bnema/gordon/internal/app"
11- "github.com/bnema/gordon/internal/httpserve/middleware"
1211 "github.com/bnema/gordon/internal/templates/render"
13- "github.com/bnema/gordon/internal/webui"
1412 _ "github.com/joho/godotenv/autoload"
1513 "github.com/labstack/echo-contrib/session"
1614 "github.com/labstack/echo/v4"
@@ -19,12 +17,11 @@ import (
1917
2018 // RenderLoginPage renders the login.html template
2119 func RenderLoginPage(c echo.Context, a *app.App) error {
22- lang := c.Get(middleware.LangKey).(string)
23- yamlData := webui.StringsYamlData{}
24- err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
20+ data, err := GetLocalizedData(c, a)
2521 if err != nil {
2622 return err
2723 }
24+
2825 // Navigate inside the fs.FS to get the template
2926 path := "html/login"
3027 rendererData, err := render.GetHTMLRenderer(path, "index.gohtml", a.TemplateFS, a)
@@ -32,12 +29,6 @@ func RenderLoginPage(c echo.Context, a *app.App) error {
3229 return err
3330 }
3431
35- // Create a data map to pass to the renderer
36- data := map[string]interface{}{
37- "CurrentLang": yamlData.CurrentLang,
38- // "BuildVersion" will be automatically added in the renderer
39- }
40-
4132 renderedHTML, err := rendererData.Render(data, a)
4233 if err != nil {
4334 return err
@@ -8,9 +8,7 @@ import (
8 "strings"8 "strings"
9 9
10 "github.com/bnema/gordon/internal/app"10 "github.com/bnema/gordon/internal/app"
11- "github.com/bnema/gordon/internal/httpserve/middleware"
12 "github.com/bnema/gordon/internal/templates/render"11 "github.com/bnema/gordon/internal/templates/render"
13- "github.com/bnema/gordon/internal/webui"
14 _ "github.com/joho/godotenv/autoload"12 _ "github.com/joho/godotenv/autoload"
15 "github.com/labstack/echo-contrib/session"13 "github.com/labstack/echo-contrib/session"
16 "github.com/labstack/echo/v4"14 "github.com/labstack/echo/v4"
@@ -19,12 +17,11 @@ import (
19 17
20 // RenderLoginPage renders the login.html template18 // RenderLoginPage renders the login.html template
21 func RenderLoginPage(c echo.Context, a *app.App) error {19 func RenderLoginPage(c echo.Context, a *app.App) error {
22- lang := c.Get(middleware.LangKey).(string)20+ data, err := GetLocalizedData(c, a)
23- yamlData := webui.StringsYamlData{}
24- err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &yamlData)
25 if err != nil {21 if err != nil {
26 return err22 return err
27 }23 }
24+
28 // Navigate inside the fs.FS to get the template25 // Navigate inside the fs.FS to get the template
29 path := "html/login"26 path := "html/login"
30 rendererData, err := render.GetHTMLRenderer(path, "index.gohtml", a.TemplateFS, a)27 rendererData, err := render.GetHTMLRenderer(path, "index.gohtml", a.TemplateFS, a)
@@ -32,12 +29,6 @@ func RenderLoginPage(c echo.Context, a *app.App) error {
32 return err29 return err
33 }30 }
34 31
35- // Create a data map to pass to the renderer
36- data := map[string]interface{}{
37- "CurrentLang": yamlData.CurrentLang,
38- // "BuildVersion" will be automatically added in the renderer
39- }
40-
41 renderedHTML, err := rendererData.Render(data, a)32 renderedHTML, err := rendererData.Render(data, a)
42 if err != nil {33 if err != nil {
43 return err34 return err
M internal/httpserve/router.go +4 -0
@@ -38,12 +38,14 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
3838 return e
3939 }
4040
41+// bindStaticRoute bind static path
4142 func bindStaticRoute(e *echo.Echo, a *app.App, path string) {
4243 e.GET(path, func(c echo.Context) error {
4344 return handler.StaticRoute(c, a)
4445 })
4546 }
4647
48+// bindAdminRoute binds all admin routes
4749 func bindAdminRoute(e *echo.Echo, a *app.App, adminPath string) {
4850 e.GET(adminPath, func(c echo.Context) error {
4951 return handler.AdminRoute(c, a)
@@ -54,6 +56,7 @@ func bindAdminRoute(e *echo.Echo, a *app.App, adminPath string) {
5456 })
5557 }
5658
59+// bindLoginRoute binds all login routes
5760 func bindLoginRoute(e *echo.Echo, a *app.App, adminPath string) {
5861 e.GET(adminPath+"/login", func(c echo.Context) error {
5962 return handler.RenderLoginPage(c, a)
@@ -69,6 +72,7 @@ func bindLoginRoute(e *echo.Echo, a *app.App, adminPath string) {
6972 })
7073 }
7174
75+// bindHTMXEndpoints binds all HTMX endpoints
7276 func bindHTMXEndpoints(e *echo.Echo, a *app.App) {
7377 // Create a group for /htmx endpoints
7478 htmxGroup := e.Group("/htmx")
@@ -38,12 +38,14 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
38 return e38 return e
39 }39 }
40 40
41+// bindStaticRoute bind static path
41 func bindStaticRoute(e *echo.Echo, a *app.App, path string) {42 func bindStaticRoute(e *echo.Echo, a *app.App, path string) {
42 e.GET(path, func(c echo.Context) error {43 e.GET(path, func(c echo.Context) error {
43 return handler.StaticRoute(c, a)44 return handler.StaticRoute(c, a)
44 })45 })
45 }46 }
46 47
48+// bindAdminRoute binds all admin routes
47 func bindAdminRoute(e *echo.Echo, a *app.App, adminPath string) {49 func bindAdminRoute(e *echo.Echo, a *app.App, adminPath string) {
48 e.GET(adminPath, func(c echo.Context) error {50 e.GET(adminPath, func(c echo.Context) error {
49 return handler.AdminRoute(c, a)51 return handler.AdminRoute(c, a)
@@ -54,6 +56,7 @@ func bindAdminRoute(e *echo.Echo, a *app.App, adminPath string) {
54 })56 })
55 }57 }
56 58
59+// bindLoginRoute binds all login routes
57 func bindLoginRoute(e *echo.Echo, a *app.App, adminPath string) {60 func bindLoginRoute(e *echo.Echo, a *app.App, adminPath string) {
58 e.GET(adminPath+"/login", func(c echo.Context) error {61 e.GET(adminPath+"/login", func(c echo.Context) error {
59 return handler.RenderLoginPage(c, a)62 return handler.RenderLoginPage(c, a)
@@ -69,6 +72,7 @@ func bindLoginRoute(e *echo.Echo, a *app.App, adminPath string) {
69 })72 })
70 }73 }
71 74
75+// bindHTMXEndpoints binds all HTMX endpoints
72 func bindHTMXEndpoints(e *echo.Echo, a *app.App) {76 func bindHTMXEndpoints(e *echo.Echo, a *app.App) {
73 // Create a group for /htmx endpoints77 // Create a group for /htmx endpoints
74 htmxGroup := e.Group("/htmx")78 htmxGroup := e.Group("/htmx")
M internal/templates/models/html/admin/index.gohtml +33 -31
@@ -1,40 +1,42 @@
11 <!DOCTYPE html>
22 <html lang="en">
3+ {{template "header.gohtml" .}}
34
4-{{template "header.gohtml" .}}
5-
6-<body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0">
7-
8- {{template "leftmenu.gohtml" .}}
9- <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4">
10- <div class="hidden md:flex mb-4 justify-end">
11- <div class="w-72 h-10 bg-gray-300 rounded-lg">
12- <div class="flex justify-between items-center h-full px-2">
13- <input type="text" class="w-full h-full bg-transparent outline-none" placeholder="Search" />
14- <i class="iconoir-search"></i>
5+ <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0">
6+ {{template "leftmenu.gohtml" .}}
7+ <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4">
8+ <div class="hidden md:flex mb-4 justify-end">
9+ <div class="w-72 h-10 bg-gray-300 rounded-lg">
10+ <div class="flex justify-between items-center h-full px-2">
11+ <input
12+ type="text"
13+ class="w-full h-full bg-transparent outline-none"
14+ placeholder="Search"
15+ />
16+ <i class="iconoir-search"></i>
17+ </div>
1518 </div>
1619 </div>
17- </div>
18- <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow">
19- <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
20- <h2>Dashboard</h2>
21- </div>
22- <div class="w-full md:w-2/6 flex flex-col space-y-4">
23- <div class="bg-white p-4 rounded-md shadow mb-4">
24- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
20+ <div
21+ class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
22+ >
23+ <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
24+ <h2>{{index .Lang "dashboard" "content" "title"}}</h2>
2525 </div>
26- <div class="bg-white p-4 rounded-md shadow mb-4">
27- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
28- </div>
29- <div class="bg-white p-4 rounded-md shadow mb-4">
30- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
26+ <div class="w-full md:w-2/6 flex flex-col space-y-4">
27+ <div class="bg-white p-4 rounded-md shadow mb-4">
28+ <div class="w-full h-44 rounded-sm bg-gray-300"></div>
29+ </div>
30+ <div class="bg-white p-4 rounded-md shadow mb-4">
31+ <div class="w-full h-44 rounded-sm bg-gray-300"></div>
32+ </div>
33+ <div class="bg-white p-4 rounded-md shadow mb-4">
34+ <div class="w-full h-44 rounded-sm bg-gray-300"></div>
35+ </div>
3136 </div>
3237 </div>
3338 </div>
34- </div>
35- <!-- footer -->
36- {{template "footer.gohtml" .}}
37-
38-</body>
39-
40-</html>⏎̸
39+ <!-- footer -->
40+ {{template "footer.gohtml" .}}
41+ </body>
42+</html>
@@ -1,40 +1,42 @@
1 <!DOCTYPE html>1 <!DOCTYPE html>
2 <html lang="en">2 <html lang="en">
3+ {{template "header.gohtml" .}}
3 4
4-{{template "header.gohtml" .}}5+ <body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0">
5-6+ {{template "leftmenu.gohtml" .}}
6-<body class="bg-zinc-200 flex flex-col md:flex-row p-4 md:p-8 p-0">7+ <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4">
7-8+ <div class="hidden md:flex mb-4 justify-end">
8- {{template "leftmenu.gohtml" .}}9+ <div class="w-72 h-10 bg-gray-300 rounded-lg">
9- <div class="flex-1 flex flex-col md:pt-10 space-y-4 p-4 md:ml-4">10+ <div class="flex justify-between items-center h-full px-2">
10- <div class="hidden md:flex mb-4 justify-end">11+ <input
11- <div class="w-72 h-10 bg-gray-300 rounded-lg">12+ type="text"
12- <div class="flex justify-between items-center h-full px-2">13+ class="w-full h-full bg-transparent outline-none"
13- <input type="text" class="w-full h-full bg-transparent outline-none" placeholder="Search" />14+ placeholder="Search"
14- <i class="iconoir-search"></i>15+ />
16+ <i class="iconoir-search"></i>
17+ </div>
15 </div>18 </div>
16 </div>19 </div>
17- </div>20+ <div
18- <div class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow">21+ class="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4 flex-grow"
19- <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">22+ >
20- <h2>Dashboard</h2>23+ <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
21- </div>24+ <h2>{{index .Lang "dashboard" "content" "title"}}</h2>
22- <div class="w-full md:w-2/6 flex flex-col space-y-4">
23- <div class="bg-white p-4 rounded-md shadow mb-4">
24- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
25 </div>25 </div>
26- <div class="bg-white p-4 rounded-md shadow mb-4">26+ <div class="w-full md:w-2/6 flex flex-col space-y-4">
27- <div class="w-full h-44 rounded-sm bg-gray-300"></div>27+ <div class="bg-white p-4 rounded-md shadow mb-4">
28- </div>28+ <div class="w-full h-44 rounded-sm bg-gray-300"></div>
29- <div class="bg-white p-4 rounded-md shadow mb-4">29+ </div>
30- <div class="w-full h-44 rounded-sm bg-gray-300"></div>30+ <div class="bg-white p-4 rounded-md shadow mb-4">
31+ <div class="w-full h-44 rounded-sm bg-gray-300"></div>
32+ </div>
33+ <div class="bg-white p-4 rounded-md shadow mb-4">
34+ <div class="w-full h-44 rounded-sm bg-gray-300"></div>
35+ </div>
31 </div>36 </div>
32 </div>37 </div>
33 </div>38 </div>
34- </div>39+ <!-- footer -->
35- <!-- footer -->40+ {{template "footer.gohtml" .}}
36- {{template "footer.gohtml" .}}41+ </body>
37-42+</html>
38-</body>
39-
40-</html>⏎̸
M internal/templates/models/html/admin/manager.gohtml +1 -1
@@ -9,7 +9,7 @@
99 <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4">
1010 <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24">
1111 <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
12- <h1 class="text-lg md:text-2xl font-bold">Container Engine Manager</h1>
12+ <h1 class="text-lg md:text-2xl font-bold">{{index .Lang "manager" "content" "title"}}</h1>
1313 <!-- List all the containers -->
1414 <div
1515 id="container-manager"
@@ -9,7 +9,7 @@
9 <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4">9 <div class="flex-1 flex flex-col space-y-4 p-4 md:ml-4">
10 <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24">10 <div class="flex flex-col md:flex-row w-full flex-grow md:mt-24">
11 <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">11 <div class="flex-grow bg-white p-4 rounded-md shadow mb-4">
12- <h1 class="text-lg md:text-2xl font-bold">Container Engine Manager</h1>12+ <h1 class="text-lg md:text-2xl font-bold">{{index .Lang "manager" "content" "title"}}</h1>
13 <!-- List all the containers -->13 <!-- List all the containers -->
14 <div14 <div
15 id="container-manager"15 id="container-manager"
M internal/templates/models/strings.yml +48 -16
@@ -1,23 +1,55 @@
11 en:
2- header:
2+ common:
33 meta:
4- title: Gordon - Admin Dashboard
5- description: Admin dashboard for Gordon
6- keywords: gordon, admin, dashboard
7- body:
4+ description: Admin interface for Gordon
5+ keywords: gordon, admin, interface
86 header:
9- helloworld: Hello World!
10- div:
11- hello: Hello component loaded from a template!
7+ someKey: Hello World!
8+ navmenu:
9+ dashboard: Dashboard
10+ manager: Manager
11+ settings: Settings
12+ logout: Logout
13+ dashboard:
14+ meta:
15+ title: Gordon - Admin Dashboard
16+ content:
17+ title: Dashboard
18+ manager:
19+ meta:
20+ title: Gordon - Container Engine Manager
21+ content:
22+ title: Container Engine Manager
23+ settings:
24+ meta:
25+ title: Gordon - Settings
26+ content:
27+ title: Settings
1228
1329 fr:
14- header:
30+ common:
1531 meta:
16- title: Gordon - Tableau de bord administrateur
17- description: Tableau de bord administrateur pour Gordon
18- keywords: gordon, admin, dashboard
19- body:
32+ description: Interface d'administration pour Gordon
33+ keywords: gordon, admin, interface
2034 header:
21- helloworld: Bonjour le monde!
22- div:
23- hello: Composant de salut chargé à partir d'un modèle!
35+ someKey: Bonjour le monde!
36+ navmenu:
37+ dashboard: Tableau de bord
38+ manager: Gestionnaire
39+ settings: Paramètres
40+ logout: Déconnexion
41+ dashboard:
42+ meta:
43+ title: Gordon - Tableau de bord
44+ content:
45+ title: Tableau de bord
46+ manager:
47+ meta:
48+ title: Gordon - Gestionnaire de moteur de conteneur
49+ content:
50+ title: Gestionnaire de moteur de conteneur
51+ settings:
52+ meta:
53+ title: Gordon - Paramètres
54+ content:
55+ title: Paramètres
@@ -1,23 +1,55 @@
1 en:1 en:
2- header:2+ common:
3 meta:3 meta:
4- title: Gordon - Admin Dashboard4+ description: Admin interface for Gordon
5- description: Admin dashboard for Gordon5+ keywords: gordon, admin, interface
6- keywords: gordon, admin, dashboard
7- body:
8 header:6 header:
9- helloworld: Hello World!7+ someKey: Hello World!
10- div:8+ navmenu:
11- hello: Hello component loaded from a template!9+ dashboard: Dashboard
10+ manager: Manager
11+ settings: Settings
12+ logout: Logout
13+ dashboard:
14+ meta:
15+ title: Gordon - Admin Dashboard
16+ content:
17+ title: Dashboard
18+ manager:
19+ meta:
20+ title: Gordon - Container Engine Manager
21+ content:
22+ title: Container Engine Manager
23+ settings:
24+ meta:
25+ title: Gordon - Settings
26+ content:
27+ title: Settings
12 28
13 fr:29 fr:
14- header:30+ common:
15 meta:31 meta:
16- title: Gordon - Tableau de bord administrateur32+ description: Interface d'administration pour Gordon
17- description: Tableau de bord administrateur pour Gordon33+ keywords: gordon, admin, interface
18- keywords: gordon, admin, dashboard
19- body:
20 header:34 header:
21- helloworld: Bonjour le monde!35+ someKey: Bonjour le monde!
22- div:36+ navmenu:
23- hello: Composant de salut chargé à partir d'un modèle!37+ dashboard: Tableau de bord
38+ manager: Gestionnaire
39+ settings: Paramètres
40+ logout: Déconnexion
41+ dashboard:
42+ meta:
43+ title: Gordon - Tableau de bord
44+ content:
45+ title: Tableau de bord
46+ manager:
47+ meta:
48+ title: Gordon - Gestionnaire de moteur de conteneur
49+ content:
50+ title: Gestionnaire de moteur de conteneur
51+ settings:
52+ meta:
53+ title: Gordon - Paramètres
54+ content:
55+ title: Paramètres
A internal/templates/render/localization.go +60 -0
@@ -0,0 +1,60 @@
1+package render
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "gopkg.in/yaml.v3"
8+)
9+
10+type Localizations map[string]map[string]map[string]interface{}
11+
12+// GetLocalization function returns the localization for the given language
13+func GetLocalization(lang string, a *app.App) (map[string]interface{}, error) {
14+ // 1. Check if the language is supported
15+ supportedLanguages := []string{"en", "fr"} // Add more languages here
16+ if !contains(supportedLanguages, lang) {
17+ return nil, fmt.Errorf("language %s is not supported", lang)
18+ }
19+
20+ fmt.Println("Language:", lang)
21+
22+ // 2. Get the localizations from the strings.yml file
23+ localizations, err := getLocalizations(a)
24+ if err != nil {
25+ return nil, fmt.Errorf("failed to get localizations: %w", err)
26+ }
27+
28+ // 3. Return the localizations for the given language
29+ if localization, ok := localizations[lang]; ok {
30+ result := make(map[string]interface{})
31+ for k, v := range localization {
32+ result[k] = v
33+ }
34+ return result, nil
35+ }
36+
37+ return nil, fmt.Errorf("language %s is not available", lang)
38+}
39+
40+// getLocalizations function returns the localizations from the strings.yml file
41+func getLocalizations(a *app.App) (Localizations, error) {
42+ var loc Localizations
43+
44+ err := yaml.Unmarshal(a.StringsYML, &loc)
45+ if err != nil {
46+ return loc, fmt.Errorf("failed to unmarshal strings.yml: %w", err)
47+ }
48+
49+ return loc, nil
50+}
51+
52+// Helper function to check if a slice contains a string
53+func contains(slice []string, str string) bool {
54+ for _, v := range slice {
55+ if v == str {
56+ return true
57+ }
58+ }
59+ return false
60+}
@@ -0,0 +1,60 @@
1+package render
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "gopkg.in/yaml.v3"
8+)
9+
10+type Localizations map[string]map[string]map[string]interface{}
11+
12+// GetLocalization function returns the localization for the given language
13+func GetLocalization(lang string, a *app.App) (map[string]interface{}, error) {
14+ // 1. Check if the language is supported
15+ supportedLanguages := []string{"en", "fr"} // Add more languages here
16+ if !contains(supportedLanguages, lang) {
17+ return nil, fmt.Errorf("language %s is not supported", lang)
18+ }
19+
20+ fmt.Println("Language:", lang)
21+
22+ // 2. Get the localizations from the strings.yml file
23+ localizations, err := getLocalizations(a)
24+ if err != nil {
25+ return nil, fmt.Errorf("failed to get localizations: %w", err)
26+ }
27+
28+ // 3. Return the localizations for the given language
29+ if localization, ok := localizations[lang]; ok {
30+ result := make(map[string]interface{})
31+ for k, v := range localization {
32+ result[k] = v
33+ }
34+ return result, nil
35+ }
36+
37+ return nil, fmt.Errorf("language %s is not available", lang)
38+}
39+
40+// getLocalizations function returns the localizations from the strings.yml file
41+func getLocalizations(a *app.App) (Localizations, error) {
42+ var loc Localizations
43+
44+ err := yaml.Unmarshal(a.StringsYML, &loc)
45+ if err != nil {
46+ return loc, fmt.Errorf("failed to unmarshal strings.yml: %w", err)
47+ }
48+
49+ return loc, nil
50+}
51+
52+// Helper function to check if a slice contains a string
53+func contains(slice []string, str string) bool {
54+ for _, v := range slice {
55+ if v == str {
56+ return true
57+ }
58+ }
59+ return false
60+}
D internal/webui/strings.go +0 -46
@@ -1,46 +0,0 @@
1-package webui
2-
3-import (
4- "fmt"
5- "io/fs"
6-
7- "github.com/bnema/gordon/pkg/parser"
8-)
9-
10-type LangData map[string]interface{}
11-
12-type StringsModel struct {
13- Header string `yaml:"header"`
14- Subheader string `yaml:"subheader"`
15-}
16-type StringsYamlData struct {
17- StringsModel StringsModel `yaml:"strings_model"`
18- EN LangData `yaml:"en"`
19- FR LangData `yaml:"fr"`
20- CurrentLang LangData
21-}
22-
23-// ReadStringsDataFromYAML reads the strings.yaml file and unmarshals it into the out interface
24-func ReadStringsDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
25-
26- err := parser.ParseYAMLFile(fs, filename, out)
27- if err != nil {
28- return err
29- }
30-
31- // If the type assertion succeeds, we set the appropriate language data
32- if data, ok := out.(*StringsYamlData); ok {
33- switch lang {
34- case "en":
35- data.CurrentLang = data.EN
36- case "fr":
37- data.CurrentLang = data.FR
38- default: // default to English if no match
39- data.CurrentLang = data.EN
40- }
41- } else {
42- return fmt.Errorf("failed to assert type of out interface: %w", err)
43- }
44-
45- return nil
46-}
@@ -1,46 +0,0 @@
1-package webui
2-
3-import (
4- "fmt"
5- "io/fs"
6-
7- "github.com/bnema/gordon/pkg/parser"
8-)
9-
10-type LangData map[string]interface{}
11-
12-type StringsModel struct {
13- Header string `yaml:"header"`
14- Subheader string `yaml:"subheader"`
15-}
16-type StringsYamlData struct {
17- StringsModel StringsModel `yaml:"strings_model"`
18- EN LangData `yaml:"en"`
19- FR LangData `yaml:"fr"`
20- CurrentLang LangData
21-}
22-
23-// ReadStringsDataFromYAML reads the strings.yaml file and unmarshals it into the out interface
24-func ReadStringsDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
25-
26- err := parser.ParseYAMLFile(fs, filename, out)
27- if err != nil {
28- return err
29- }
30-
31- // If the type assertion succeeds, we set the appropriate language data
32- if data, ok := out.(*StringsYamlData); ok {
33- switch lang {
34- case "en":
35- data.CurrentLang = data.EN
36- case "fr":
37- data.CurrentLang = data.FR
38- default: // default to English if no match
39- data.CurrentLang = data.EN
40- }
41- } else {
42- return fmt.Errorf("failed to assert type of out interface: %w", err)
43- }
44-
45- return nil
46-}
M pkg/htmx/htmlfragments.go +1 -1
@@ -6,7 +6,7 @@ import (
66
77 "github.com/PuerkitoBio/goquery"
88 "github.com/bnema/gordon/internal/app"
9- "github.com/bnema/gordon/internal/gotemplate/render"
9+ "github.com/bnema/gordon/internal/templates/render"
1010 )
1111
1212 // GetHTMLFragmentByID returns the HTML fragment with the specified id
@@ -6,7 +6,7 @@ import (
6 6
7 "github.com/PuerkitoBio/goquery"7 "github.com/PuerkitoBio/goquery"
8 "github.com/bnema/gordon/internal/app"8 "github.com/bnema/gordon/internal/app"
9- "github.com/bnema/gordon/internal/gotemplate/render"9+ "github.com/bnema/gordon/internal/templates/render"
10 )10 )
11 11
12 // GetHTMLFragmentByID returns the HTML fragment with the specified id12 // GetHTMLFragmentByID returns the HTML fragment with the specified id

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close