From 97636b23f50d19fcad9f58310858e75982d5b1f4 Mon Sep 17 00:00:00 2001 From: Thomas Miceli <27960254+thomiceli@users.noreply.github.com> Date: Sat, 11 May 2024 21:02:57 +0200 Subject: [PATCH 1/2] Check translations keys in CI (#279) --- .github/workflows/go.yml | 5 ++- Makefile | 5 ++- .../i18n/locales/{de_DE.yml => de-DE.yml} | 3 -- internal/i18n/locales/es-ES.yml | 1 - internal/i18n/locales/fr-FR.yml | 1 - internal/i18n/locales/pt-BR.yml | 3 -- scripts/check-translations.sh | 39 +++++++++++++++++++ 7 files changed, 47 insertions(+), 10 deletions(-) rename internal/i18n/locales/{de_DE.yml => de-DE.yml} (98%) create mode 100755 scripts/check-translations.sh diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cb055515..cb383287 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -41,9 +41,12 @@ jobs: with: go-version: "1.22" - - name: Check + - name: Check Go modules run: make go_mod check_changes + - name: Check translations + run: make check-tr + test: strategy: fail-fast: false diff --git a/Makefile b/Makefile index 3fe87043..0f0e2cc1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all all_crosscompile install build_frontend build_backend build build_crosscompile build_docker build_dev_docker run_dev_docker watch_frontend watch_backend watch clean clean_docker check_changes go_mod fmt test +.PHONY: all all_crosscompile install build_frontend build_backend build build_crosscompile build_docker build_dev_docker run_dev_docker watch_frontend watch_backend watch clean clean_docker check_changes go_mod fmt test check-tr # Specify the name of your Go binary output BINARY_NAME := opengist @@ -73,3 +73,6 @@ fmt: test: @go test ./... -p 1 + +check-tr: + @bash ./scripts/check-translations.sh \ No newline at end of file diff --git a/internal/i18n/locales/de_DE.yml b/internal/i18n/locales/de-DE.yml similarity index 98% rename from internal/i18n/locales/de_DE.yml rename to internal/i18n/locales/de-DE.yml index 1e2e83ba..60560523 100644 --- a/internal/i18n/locales/de_DE.yml +++ b/internal/i18n/locales/de-DE.yml @@ -132,9 +132,6 @@ auth.username: 'Benutzername' auth.password: 'Passwort' auth.register-instead: 'Stattdessen registrieren' auth.login-instead: 'Stattdessen anmelden' -auth.github-oauth: 'Mit GitHub-Account fortfahren' -auth.gitlab-oauth: 'Mit GitLab-Account fortfahren' -auth.gitea-oauth: 'Mit Gitea-Account fortfahren' error: 'Fehler' diff --git a/internal/i18n/locales/es-ES.yml b/internal/i18n/locales/es-ES.yml index 244fd179..1fd70b2f 100644 --- a/internal/i18n/locales/es-ES.yml +++ b/internal/i18n/locales/es-ES.yml @@ -157,7 +157,6 @@ admin.delete: Eliminar admin.created_at: Creado admin.config-link: Esta configuración puede ser %s por un archivo de configuración YAML y/o variables de entorno. -admin.config-link-overridden: sobrescrito admin.disable-signup: Deshabilitar registro admin.disable-signup_help: Prohibir la creación de nuevas cuentas. admin.require-login: Requerir inicio de sesión diff --git a/internal/i18n/locales/fr-FR.yml b/internal/i18n/locales/fr-FR.yml index f2682495..d6716f8b 100644 --- a/internal/i18n/locales/fr-FR.yml +++ b/internal/i18n/locales/fr-FR.yml @@ -187,7 +187,6 @@ settings.create-password-help: Créer un mot de passe pour se connecter à Openg settings.change-password: Changer le mot de passe settings.change-password-help: Changer le mot de passe pour se connecter à Opengist via HTTP settings.password-label-title: Mot de passe -auth.gitlab-oauth: Continuer avec un compte GitLab admin.actions.sync-previews: Synchroniser l'aperçu des gists admin.actions.reset-hooks: Réinitialiser les hooks de Git pour tous les dépôts gist.new.url: URL diff --git a/internal/i18n/locales/pt-BR.yml b/internal/i18n/locales/pt-BR.yml index 2eee31ef..4e71c5bc 100644 --- a/internal/i18n/locales/pt-BR.yml +++ b/internal/i18n/locales/pt-BR.yml @@ -17,8 +17,6 @@ gist.header.clone-http: Clonar via %s gist.header.clone-http-help: Clonar com Git usando autenticação básica HTTP. gist.header.clone-ssh: Clonar via SSH gist.header.clone-ssh-help: Clonar com Git usando uma chave SSH. -gist.header.share: Compartilhar -gist.header.share-help: Copiar link para compartilhar este gist. gist.header.download-zip: Baixar ZIP gist.raw: Bruto @@ -157,7 +155,6 @@ admin.delete: Excluir admin.created_at: Criado admin.config-link: Esta configuração pode ser %s por um arquivo de configuração YAML e/ou variáveis de ambiente. -admin.config-link-overridden: sobrescrito admin.disable-signup: Desabilitar registro admin.disable-signup_help: Proibir a criação de novas contas. admin.require-login: Exigir login diff --git a/scripts/check-translations.sh b/scripts/check-translations.sh new file mode 100755 index 00000000..bfe88915 --- /dev/null +++ b/scripts/check-translations.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +differences_found=0 + +# Extract keys from the reference file and sort them +sort <(awk -F':' '{print $1}' internal/i18n/locales/en-US.yml) > sorted_reference_keys.txt +sed -i '/^\s*$/d' sorted_reference_keys.txt + +for new_file in internal/i18n/locales/*.yml; do + filename=$(basename $new_file) + echo "" + echo "Checking $filename..." + + # Extract keys from the current file and sort them + sort <(awk -F':' '{print $1}' $new_file) > sorted_new_keys.txt + sed -i '/^\s*$/d' sorted_new_keys.txt + + comm -3 sorted_reference_keys.txt sorted_new_keys.txt > differences.txt + + if [ -s differences.txt ]; then + echo "Error in $filename: The YAML file has differences in keys." + while IFS= read -r line; do + if [[ $line == $'\t'* ]]; then + echo "+ Additional key in $filename: $(echo $line | awk '{$1=$1; print}')" + else + echo "- Missing key in $filename: $(echo $line | awk '{$1=$1; print}')" + fi + done < differences.txt + differences_found=1 + else + echo "All keys in $filename match perfectly." + fi + + rm sorted_new_keys.txt +done + +rm sorted_reference_keys.txt differences.txt + +exit $differences_found From 2fd053a077e0502956b188dc3e3e4a3828b8b103 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Sat, 11 May 2024 14:03:25 -0500 Subject: [PATCH 2/2] feat: make edit visibility a toggle (#277) * feat: make edit visibility a toggle Signed-off-by: jolheiser * Tweak SVG dropdown icon size & color --------- Signed-off-by: jolheiser Co-authored-by: Thomas Miceli --- internal/db/gist.go | 18 ++++++---- internal/web/gist.go | 23 +++++++----- internal/web/server.go | 9 ++--- internal/web/test/gist_test.go | 65 +++++++++++++++++++++------------- internal/web/test/server.go | 23 +++++++----- templates/pages/edit.html | 30 +++++++++------- 6 files changed, 102 insertions(+), 66 deletions(-) diff --git a/internal/db/gist.go b/internal/db/gist.go index 276dcad8..5b269342 100644 --- a/internal/db/gist.go +++ b/internal/db/gist.go @@ -538,13 +538,17 @@ func (gist *Gist) GetLanguagesFromFiles() ([]string, error) { // -- DTO -- // type GistDTO struct { - Title string `validate:"max=250" form:"title"` - Description string `validate:"max=1000" form:"description"` - URL string `validate:"max=32,alphanumdashorempty" form:"url"` - Private Visibility `validate:"number,min=0,max=2" form:"private"` - Files []FileDTO `validate:"min=1,dive"` - Name []string `form:"name"` - Content []string `form:"content"` + Title string `validate:"max=250" form:"title"` + Description string `validate:"max=1000" form:"description"` + URL string `validate:"max=32,alphanumdashorempty" form:"url"` + Files []FileDTO `validate:"min=1,dive"` + Name []string `form:"name"` + Content []string `form:"content"` + VisibilityDTO +} + +type VisibilityDTO struct { + Private Visibility `validate:"number,min=0,max=2" form:"private"` } type FileDTO struct { diff --git a/internal/web/gist.go b/internal/web/gist.go index 4161d595..11784374 100644 --- a/internal/web/gist.go +++ b/internal/web/gist.go @@ -6,12 +6,6 @@ import ( "bytes" "errors" "fmt" - "github.com/rs/zerolog/log" - "github.com/thomiceli/opengist/internal/git" - "github.com/thomiceli/opengist/internal/i18n" - "github.com/thomiceli/opengist/internal/index" - "github.com/thomiceli/opengist/internal/render" - "github.com/thomiceli/opengist/internal/utils" "html/template" "net/url" "path/filepath" @@ -20,6 +14,13 @@ import ( "strings" "time" + "github.com/rs/zerolog/log" + "github.com/thomiceli/opengist/internal/git" + "github.com/thomiceli/opengist/internal/i18n" + "github.com/thomiceli/opengist/internal/index" + "github.com/thomiceli/opengist/internal/render" + "github.com/thomiceli/opengist/internal/utils" + "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/thomiceli/opengist/internal/config" @@ -603,10 +604,15 @@ func processCreate(ctx echo.Context) error { return redirect(ctx, "/"+user.Username+"/"+gist.Identifier()) } -func toggleVisibility(ctx echo.Context) error { +func editVisibility(ctx echo.Context) error { gist := getData(ctx, "gist").(*db.Gist) - gist.Private = (gist.Private + 1) % 3 + dto := new(db.VisibilityDTO) + if err := ctx.Bind(dto); err != nil { + return errorRes(400, tr(ctx, "error.cannot-bind-data"), err) + } + + gist.Private = dto.Private if err := gist.UpdateNoTimestamps(); err != nil { return errorRes(500, "Error updating this gist", err) } @@ -733,7 +739,6 @@ func downloadFile(ctx echo.Context) error { ctx.Response().Header().Set("Content-Disposition", "attachment; filename="+file.Filename) ctx.Response().Header().Set("Content-Length", strconv.Itoa(len(file.Content))) _, err = ctx.Response().Write([]byte(file.Content)) - if err != nil { return errorRes(500, "Error downloading the file", err) } diff --git a/internal/web/server.go b/internal/web/server.go index 384fe811..3d3ad7d6 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -5,9 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/thomiceli/opengist/internal/index" - "github.com/thomiceli/opengist/internal/utils" - "github.com/thomiceli/opengist/templates" htmlpkg "html" "html/template" "io" @@ -21,6 +18,10 @@ import ( "strings" "time" + "github.com/thomiceli/opengist/internal/index" + "github.com/thomiceli/opengist/internal/utils" + "github.com/thomiceli/opengist/templates" + "github.com/gorilla/sessions" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -313,7 +314,7 @@ func NewServer(isDev bool) *Server { g3.GET("/rev/:revision", gistIndex) g3.GET("/revisions", revisions) g3.GET("/archive/:revision", downloadZip) - g3.POST("/visibility", toggleVisibility, logged, writePermission) + g3.POST("/visibility", editVisibility, logged, writePermission) g3.POST("/delete", deleteGist, logged, writePermission) g3.GET("/raw/:revision/:file", rawFile) g3.GET("/download/:revision/:file", downloadFile) diff --git a/internal/web/test/gist_test.go b/internal/web/test/gist_test.go index a04f906e..840753bc 100644 --- a/internal/web/test/gist_test.go +++ b/internal/web/test/gist_test.go @@ -1,10 +1,11 @@ package test import ( + "testing" + "github.com/stretchr/testify/require" "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/git" - "testing" ) func TestGists(t *testing.T) { @@ -28,9 +29,11 @@ func TestGists(t *testing.T) { gist1 := db.GistDTO{ Title: "gist1", Description: "my first gist", - Private: 0, - Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"}, - Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"}, + VisibilityDTO: db.VisibilityDTO{ + Private: 0, + }, + Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"}, + Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"}, } err = s.request("POST", "/", gist1, 302) require.NoError(t, err) @@ -57,9 +60,11 @@ func TestGists(t *testing.T) { gist2 := db.GistDTO{ Title: "gist2", Description: "my second gist", - Private: 0, - Name: []string{"", "gist2.txt", "gist3.txt"}, - Content: []string{"", "yeah\ncool", "yeah\ncool gist actually"}, + VisibilityDTO: db.VisibilityDTO{ + Private: 0, + }, + Name: []string{"", "gist2.txt", "gist3.txt"}, + Content: []string{"", "yeah\ncool", "yeah\ncool gist actually"}, } err = s.request("POST", "/", gist2, 200) require.NoError(t, err) @@ -67,9 +72,11 @@ func TestGists(t *testing.T) { gist3 := db.GistDTO{ Title: "gist3", Description: "my third gist", - Private: 0, - Name: []string{""}, - Content: []string{"yeah"}, + VisibilityDTO: db.VisibilityDTO{ + Private: 0, + }, + Name: []string{""}, + Content: []string{"yeah"}, } err = s.request("POST", "/", gist3, 302) require.NoError(t, err) @@ -110,9 +117,11 @@ func TestVisibility(t *testing.T) { gist1 := db.GistDTO{ Title: "gist1", Description: "my first gist", - Private: db.UnlistedVisibility, - Name: []string{""}, - Content: []string{"yeah"}, + VisibilityDTO: db.VisibilityDTO{ + Private: db.UnlistedVisibility, + }, + Name: []string{""}, + Content: []string{"yeah"}, } err = s.request("POST", "/", gist1, 302) require.NoError(t, err) @@ -121,19 +130,19 @@ func TestVisibility(t *testing.T) { require.NoError(t, err) require.Equal(t, db.UnlistedVisibility, gist1db.Private) - err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302) + err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", db.VisibilityDTO{Private: db.PrivateVisibility}, 302) require.NoError(t, err) gist1db, err = db.GetGistByID("1") require.NoError(t, err) require.Equal(t, db.PrivateVisibility, gist1db.Private) - err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302) + err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", db.VisibilityDTO{Private: db.PublicVisibility}, 302) require.NoError(t, err) gist1db, err = db.GetGistByID("1") require.NoError(t, err) require.Equal(t, db.PublicVisibility, gist1db.Private) - err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", nil, 302) + err = s.request("POST", "/"+gist1db.User.Username+"/"+gist1db.Uuid+"/visibility", db.VisibilityDTO{Private: db.UnlistedVisibility}, 302) require.NoError(t, err) gist1db, err = db.GetGistByID("1") require.NoError(t, err) @@ -152,9 +161,11 @@ func TestLikeFork(t *testing.T) { gist1 := db.GistDTO{ Title: "gist1", Description: "my first gist", - Private: 1, - Name: []string{""}, - Content: []string{"yeah"}, + VisibilityDTO: db.VisibilityDTO{ + Private: 1, + }, + Name: []string{""}, + Content: []string{"yeah"}, } err = s.request("POST", "/", gist1, 302) require.NoError(t, err) @@ -212,9 +223,11 @@ func TestCustomUrl(t *testing.T) { Title: "gist1", URL: "my-gist", Description: "my first gist", - Private: 0, - Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"}, - Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"}, + VisibilityDTO: db.VisibilityDTO{ + Private: 0, + }, + Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"}, + Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"}, } err = s.request("POST", "/", gist1, 302) require.NoError(t, err) @@ -241,9 +254,11 @@ func TestCustomUrl(t *testing.T) { gist2 := db.GistDTO{ Title: "gist2", Description: "my second gist", - Private: 0, - Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"}, - Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"}, + VisibilityDTO: db.VisibilityDTO{ + Private: 0, + }, + Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"}, + Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"}, } err = s.request("POST", "/", gist2, 302) require.NoError(t, err) diff --git a/internal/web/test/server.go b/internal/web/test/server.go index 5bcf2243..5ef8d252 100644 --- a/internal/web/test/server.go +++ b/internal/web/test/server.go @@ -3,13 +3,6 @@ package test import ( "errors" "fmt" - "github.com/rs/zerolog/log" - "github.com/stretchr/testify/require" - "github.com/thomiceli/opengist/internal/config" - "github.com/thomiceli/opengist/internal/db" - "github.com/thomiceli/opengist/internal/git" - "github.com/thomiceli/opengist/internal/memdb" - "github.com/thomiceli/opengist/internal/web" "io" "net/http" "net/http/httptest" @@ -21,6 +14,14 @@ import ( "strconv" "strings" "testing" + + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + "github.com/thomiceli/opengist/internal/config" + "github.com/thomiceli/opengist/internal/db" + "github.com/thomiceli/opengist/internal/git" + "github.com/thomiceli/opengist/internal/memdb" + "github.com/thomiceli/opengist/internal/web" ) type testServer struct { @@ -106,7 +107,7 @@ func structToURLValues(s interface{}) url.Values { for i := 0; i < rValue.NumField(); i++ { field := rValue.Type().Field(i) tag := field.Tag.Get("form") - if tag != "" { + if tag != "" || field.Anonymous { if field.Type.Kind() == reflect.Int { fieldValue := rValue.Field(i).Int() v.Add(tag, strconv.FormatInt(fieldValue, 10)) @@ -115,6 +116,12 @@ func structToURLValues(s interface{}) url.Values { for _, va := range fieldValue { v.Add(tag, va) } + } else if field.Type.Kind() == reflect.Struct { + for key, val := range structToURLValues(rValue.Field(i).Interface()) { + for _, vv := range val { + v.Add(key, vv) + } + } } else { fieldValue := rValue.Field(i).String() v.Add(tag, fieldValue) diff --git a/templates/pages/edit.html b/templates/pages/edit.html index c80f7691..a8f92d64 100644 --- a/templates/pages/edit.html +++ b/templates/pages/edit.html @@ -10,19 +10,23 @@

{{ .csrfHtml }} - +
+ +
+ + +
+
{{ .csrfHtml }}