diff --git a/.gitignore b/.gitignore index 533f297c1..e1ebe535b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ vendor # GraphQL generated output internal/api/generated_*.go +# Generated locale files +ui/login/locales/* + #### # Visual Studio #### diff --git a/Makefile b/Makefile index 088c1797d..b6d0a9e28 100644 --- a/Makefile +++ b/Makefile @@ -281,6 +281,10 @@ generate-ui: generate-backend: touch-ui go generate ./cmd/stash +.PHONY: generate-login-locale +generate-login-locale: + go generate ./ui + .PHONY: generate-dataloaders generate-dataloaders: go generate ./internal/api/loaders @@ -351,7 +355,10 @@ ifdef STASH_SOURCEMAPS endif .PHONY: ui -ui: ui-env +ui: ui-only generate-login-locale + +.PHONY: ui-only +ui-only: ui-env cd ui/v2.5 && yarn build .PHONY: zip-ui diff --git a/docker/build/x86_64/Dockerfile b/docker/build/x86_64/Dockerfile index b945c9c4a..2c09c5012 100644 --- a/docker/build/x86_64/Dockerfile +++ b/docker/build/x86_64/Dockerfile @@ -1,7 +1,7 @@ # This dockerfile should be built with `make docker-build` from the stash root. # Build Frontend -FROM node:alpine as frontend +FROM node:20-alpine AS frontend RUN apk add --no-cache make git ## cache node_modules separately COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/ @@ -13,19 +13,22 @@ RUN make pre-ui RUN make generate-ui ARG GITHASH ARG STASH_VERSION -RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui +RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only # Build Backend -FROM golang:1.22-alpine as backend +FROM golang:1.22.8-alpine AS backend RUN apk add --no-cache make alpine-sdk WORKDIR /stash COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/ +COPY ./graphql /stash/graphql/ COPY ./scripts /stash/scripts/ COPY ./pkg /stash/pkg/ -COPY ./cmd /stash/cmd -COPY ./internal /stash/internal +COPY ./cmd /stash/cmd/ +COPY ./internal /stash/internal/ +# needed for generate-login-locale +COPY ./ui /stash/ui/ +RUN make generate-backend generate-login-locale COPY --from=frontend /stash /stash/ -RUN make generate-backend ARG GITHASH ARG STASH_VERSION RUN make flags-release flags-pie stash diff --git a/docker/build/x86_64/Dockerfile-CUDA b/docker/build/x86_64/Dockerfile-CUDA index f76c6dea6..616e83a42 100644 --- a/docker/build/x86_64/Dockerfile-CUDA +++ b/docker/build/x86_64/Dockerfile-CUDA @@ -1,7 +1,7 @@ # This dockerfile should be built with `make docker-cuda-build` from the stash root. # Build Frontend -FROM node:alpine as frontend +FROM node:20-alpine AS frontend RUN apk add --no-cache make git ## cache node_modules separately COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/ @@ -13,19 +13,22 @@ RUN make pre-ui RUN make generate-ui ARG GITHASH ARG STASH_VERSION -RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui +RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only # Build Backend -FROM golang:1.22-bullseye as backend +FROM golang:1.22.8-bullseye AS backend RUN apt update && apt install -y build-essential golang WORKDIR /stash COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/ +COPY ./graphql /stash/graphql/ COPY ./scripts /stash/scripts/ COPY ./pkg /stash/pkg/ COPY ./cmd /stash/cmd COPY ./internal /stash/internal +# needed for generate-login-locale +COPY ./ui /stash/ui/ +RUN make generate-backend generate-login-locale COPY --from=frontend /stash /stash/ -RUN make generate-backend ARG GITHASH ARG STASH_VERSION RUN make flags-release flags-pie stash diff --git a/docker/build/x86_64/README.md b/docker/build/x86_64/README.md index f21253b0f..13e120465 100644 --- a/docker/build/x86_64/README.md +++ b/docker/build/x86_64/README.md @@ -4,7 +4,7 @@ This dockerfile is used to build a stash docker container using the current sour # Building the docker container -From the top-level directory (should contain `main.go` file): +From the top-level directory (should contain `tools.go` file): ``` make docker-build diff --git a/internal/api/server.go b/internal/api/server.go index bce8e6a07..c2c798be2 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -41,10 +41,11 @@ import ( ) const ( - loginEndpoint = "/login" - logoutEndpoint = "/logout" - gqlEndpoint = "/graphql" - playgroundEndpoint = "/playground" + loginEndpoint = "/login" + loginLocaleEndpoint = loginEndpoint + "/locale" + logoutEndpoint = "/logout" + gqlEndpoint = "/graphql" + playgroundEndpoint = "/playground" ) type Server struct { @@ -228,6 +229,7 @@ func Initialize() (*Server, error) { r.Get(loginEndpoint, handleLogin()) r.Post(loginEndpoint, handleLoginPost()) r.Get(logoutEndpoint, handleLogout()) + r.Get(loginLocaleEndpoint, handleLoginLocale(cfg)) r.HandleFunc(loginEndpoint+"/*", func(w http.ResponseWriter, r *http.Request) { r.URL.Path = strings.TrimPrefix(r.URL.Path, loginEndpoint) w.Header().Set("Cache-Control", "no-cache") diff --git a/internal/api/session.go b/internal/api/session.go index 6aa1c9567..5918cdd9b 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -17,7 +17,11 @@ import ( "github.com/stashapp/stash/ui" ) -const returnURLParam = "returnURL" +const ( + returnURLParam = "returnURL" + + defaultLocale = "en-GB" +) func getLoginPage() []byte { data, err := fs.ReadFile(ui.LoginUIBox, "login.html") @@ -58,6 +62,47 @@ func serveLoginPage(w http.ResponseWriter, r *http.Request, returnURL string, lo utils.ServeStaticContent(w, r, buffer.Bytes()) } +func handleLoginLocale(cfg *config.Config) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // get the locale from the config + lang := cfg.GetLanguage() + if lang == "" { + lang = defaultLocale + } + + data, err := getLoginLocale(lang) + if err != nil { + logger.Debugf("Failed to load login locale file for language %s: %v", lang, err) + // try again with the default language + if lang != defaultLocale { + data, err = getLoginLocale(defaultLocale) + if err != nil { + logger.Errorf("Failed to load login locale file for default language %s: %v", defaultLocale, err) + } + } + + // if there's still an error, response with an internal server error + if err != nil { + http.Error(w, "Failed to load login locale file", http.StatusInternalServerError) + return + } + } + + // write a script to set the locale string map as a global variable + localeScript := fmt.Sprintf("var localeStrings = %s;", data) + w.Header().Set("Content-Type", "application/javascript") + _, _ = w.Write([]byte(localeScript)) + } +} + +func getLoginLocale(lang string) ([]byte, error) { + data, err := fs.ReadFile(ui.LoginUIBox, "locales/"+lang+".json") + if err != nil { + return nil, err + } + return data, nil +} + func handleLogin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { returnURL := r.URL.Query().Get(returnURLParam) diff --git a/scripts/generateLoginLocales.go b/scripts/generateLoginLocales.go new file mode 100644 index 000000000..839bfccde --- /dev/null +++ b/scripts/generateLoginLocales.go @@ -0,0 +1,90 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "encoding/json" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + + "github.com/stashapp/stash/pkg/fsutil" + "github.com/stashapp/stash/pkg/utils" +) + +func main() { + verbose := len(os.Args) > 1 && os.Args[1] == "-v" + + fmt.Printf("Generating login locales\n") + + // read all json files in the locales directory + // and extract only the login part + + // assume running from ui directory + dirFS := os.DirFS(filepath.Join("v2.5", "src", "locales")) + + // ensure the login/locales directory exists + if err := fsutil.EnsureDir(filepath.Join("login", "locales")); err != nil { + panic(err) + } + + fs.WalkDir(dirFS, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + panic(err) + } + + if d.IsDir() { + return nil + } + + if filepath.Ext(path) != ".json" { + return nil + } + + // extract the login part + // from the json file + src, err := dirFS.Open(path) + if err != nil { + panic(err) + } + + defer src.Close() + data, err := io.ReadAll(src) + if err != nil { + panic(err) + } + + m := make(utils.NestedMap) + if err := json.Unmarshal(data, &m); err != nil { + panic(err) + } + + l, found := m.Get("login") + if !found { + // nothing to do + return nil + } + + // create new json file + // with only the login part + if verbose { + fmt.Printf("Writing %s\n", d.Name()) + } + + f, err := os.Create(filepath.Join("login", "locales", d.Name())) + if err != nil { + panic(err) + } + + defer f.Close() + e := json.NewEncoder(f) + if err := e.Encode(l); err != nil { + panic(err) + } + + return nil + }) +} diff --git a/ui/login/login.html b/ui/login/login.html index dce06a905..32787fc91 100644 --- a/ui/login/login.html +++ b/ui/login/login.html @@ -9,6 +9,18 @@ + + + +
-