feat: locales and minor fixes

This commit is contained in:
2025-03-13 18:28:20 +07:00
parent cedcbbbf8b
commit 76ea3238e6
10 changed files with 84 additions and 37 deletions

View File

@@ -25,7 +25,7 @@ const exportFromExcel = async () => {
<template> <template>
<div class="flex w-screen h-screen items-center gap-5 justify-center"> <div class="flex w-screen h-screen items-center gap-5 justify-center">
<NavCard :title="'Пользователь'" :to="'/user'" :content="'Может кидать зигу и не может пользоваться туалетной бумагой'" /> <NavCard :title="'Пользователь'" :to="'/user'" :content="'Не может пользоваться туалетной бумагой'" />
</div> </div>
<footer class="fixed w-full bottom-10 flex items-center justify-center gap-2"> <footer class="fixed w-full bottom-10 flex items-center justify-center gap-2">
<Button severity="secondary" @click="importFromExcel">Импортировать данные</Button> <Button severity="secondary" @click="importFromExcel">Импортировать данные</Button>

8
go.mod
View File

@@ -1,13 +1,14 @@
module app module app
go 1.22.12 go 1.23.0
toolchain go1.23.4 toolchain go1.24.1
require ( require (
github.com/kuzgoga/fogg v0.1.2 github.com/kuzgoga/fogg v0.1.2
github.com/wailsapp/wails/v3 v3.0.0-alpha.9 github.com/wailsapp/wails/v3 v3.0.0-alpha.9
github.com/xuri/excelize/v2 v2.9.0 github.com/xuri/excelize/v2 v2.9.0
golang.org/x/text v0.23.0
gorm.io/driver/sqlite v1.5.7 gorm.io/driver/sqlite v1.5.7
gorm.io/gen v0.3.26 gorm.io/gen v0.3.26
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
@@ -62,9 +63,8 @@ require (
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.32.0 // indirect golang.org/x/net v0.32.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect golang.org/x/tools v0.28.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect

8
go.sum
View File

@@ -180,8 +180,8 @@ golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -216,8 +216,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"github.com/kuzgoga/fogg" "github.com/kuzgoga/fogg"
"github.com/xuri/excelize/v2" "github.com/xuri/excelize/v2"
"golang.org/x/text/language"
"golang.org/x/text/message"
"log/slog" "log/slog"
"reflect" "reflect"
"slices" "slices"
@@ -18,6 +20,8 @@ type TableHeaders struct {
IgnoredFieldsIndexes []int IgnoredFieldsIndexes []int
} }
var p = message.NewPrinter(language.Russian)
func isPrimitiveType(valueType reflect.Type) bool { func isPrimitiveType(valueType reflect.Type) bool {
switch valueType.Kind() { switch valueType.Kind() {
case reflect.Bool, case reflect.Bool,
@@ -48,7 +52,7 @@ func ExportEntitiesToSpreadsheet(filename string, exporters ...ExporterInterface
file := excelize.NewFile() file := excelize.NewFile()
defer func() { defer func() {
if err := file.Close(); err != nil { if err := file.Close(); err != nil {
slog.Error("Error while closing excel file: ", err) slog.Error(p.Sprintf("Error while closing excel file: %s", err))
} }
}() }()
@@ -125,7 +129,7 @@ func ExportEntityToSpreadsheet[T any](file *excelize.File, sheetName string, ent
err = file.SetCellValue(sheetName, cellName, fieldValue) err = file.SetCellValue(sheetName, cellName, fieldValue)
} }
slog.Info(fmt.Sprintf("Field %s value: %v, %s\n", cellName, fieldValue, datatype)) slog.Info(p.Sprintf("Field %s value: %v, %s\n", cellName, fieldValue, datatype))
if err != nil { if err != nil {
return err return err
@@ -176,7 +180,7 @@ func getNestedFieldValue(obj reflect.Value, path string) (any, error) {
val = val.FieldByName(field) val = val.FieldByName(field)
if !val.IsValid() { if !val.IsValid() {
return nil, fmt.Errorf("field %s not found", field) return nil, errors.New(p.Sprintf("field %s not found", field))
} }
} }
return val.Interface(), nil return val.Interface(), nil
@@ -193,7 +197,7 @@ func SerializeNestedField(fieldInfo reflect.StructField, fieldValue reflect.Valu
if tag.HasTag("ui") && tag.GetTag("ui").HasParam("field") { if tag.HasTag("ui") && tag.GetTag("ui").HasParam("field") {
uiTag = *tag.GetTag("ui") uiTag = *tag.GetTag("ui")
} else { } else {
slog.Error("Fail to extract Field tag") slog.Error(p.Sprintf("Fail to extract Field tag"))
return nil, nil return nil, nil
} }
@@ -254,7 +258,7 @@ func ExportHeaders(entity any) (TableHeaders, error) {
for i := range v.NumField() { for i := range v.NumField() {
tag, err := fogg.Parse(string(v.Field(i).Tag)) tag, err := fogg.Parse(string(v.Field(i).Tag))
if err != nil { if err != nil {
return headers, errors.New(fmt.Sprintf("Error occured while tag parsing `%s`: %s", err, string(v.Field(i).Tag))) return headers, errors.New(p.Sprintf("Error occurred while tag parsing `%s`: %s", err, string(v.Field(i).Tag)))
} }
uiTag := tag.GetTag("ui") uiTag := tag.GetTag("ui")
@@ -303,7 +307,7 @@ func WriteHeaders(sheetName string, entity any, file *excelize.File) (TableHeade
func GetStyleId(f *excelize.File, style *excelize.Style) (int, error) { func GetStyleId(f *excelize.File, style *excelize.Style) (int, error) {
styleId, err := f.NewStyle(style) styleId, err := f.NewStyle(style)
if err != nil { if err != nil {
return 0, fmt.Errorf("error occured while creating a style: %w", err) return 0, errors.New(p.Sprintf("error occurred while creating a style: %w", err))
} }
return styleId, nil return styleId, nil

View File

@@ -1,7 +1,7 @@
package excel package excel
import ( import (
"fmt" "errors"
"log/slog" "log/slog"
"github.com/xuri/excelize/v2" "github.com/xuri/excelize/v2"
@@ -15,7 +15,7 @@ type Importer struct {
func ImportEntitiesFromSpreadsheet(path string, importers ...Importer) error { func ImportEntitiesFromSpreadsheet(path string, importers ...Importer) error {
for _, importer := range importers { for _, importer := range importers {
err := ImportEntitiesFromSpreadsheet(path, importer) err := ImportFromSpreadsheet(path, importer)
if err != nil { if err != nil {
return err return err
} }
@@ -28,7 +28,7 @@ func ImportFromSpreadsheet(filepath string, importer Importer) error {
defer func() { defer func() {
err := f.Close() err := f.Close()
if err != nil { if err != nil {
slog.Error(fmt.Sprintf("Failed to close file: %s", err)) slog.Error(p.Sprintf("Failed to close file: %s", err))
} }
}() }()
@@ -36,6 +36,11 @@ func ImportFromSpreadsheet(filepath string, importer Importer) error {
return err return err
} }
sheetIndex, err := f.GetSheetIndex("MySheet")
if err != nil || sheetIndex == -1 {
return errors.New(p.Sprintf("Sheet `%s` not found", importer.SheetName))
}
rows, err := f.GetRows(importer.SheetName) rows, err := f.GetRows(importer.SheetName)
if err != nil { if err != nil {

View File

@@ -0,0 +1,25 @@
package excel
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func init() {
excelAddon()
}
func excelAddon() {
message.SetString(language.Russian, "Error while closing excel file: %v", "Ошибка при закрытии файла Excel: %v")
message.SetString(language.Russian, "Field %s not found", "Поле %s не найдено")
message.SetString(language.Russian, "Error occured while tag parsing `%s`: %s", "Произошла ошибка при разборе тега `%s`: %s")
message.SetString(language.Russian, "Fail to extract Field tag", "Не удалось извлечь тег поля")
message.SetString(language.Russian, "Error occured while creating a style: %w", "Произошла ошибка при создании стиля: %w")
message.SetString(language.Russian, "Error while setting cell value: %v", "Ошибка при установке значения ячейки: %v")
message.SetString(language.Russian, "Error while setting cell style: %v", "Ошибка при установке стиля ячейки: %v")
message.SetString(language.Russian, "Operation cancelled", "Операция отменена")
message.SetString(language.Russian, "error occurred while creating a style: %w", "Ошибка при создании стиля: %w")
message.SetString(language.Russian, "Failed to close file: %s", "Не удалось закрыть файл: %s")
message.SetString(language.Russian, "Error occurred while tag parsing `%s`: %s", "Ошибка при разборе тэга: `%s`: %s")
message.SetString(language.Russian, "Sheet `%s` not found", "Лист `%s` не найден")
}

View File

@@ -1,8 +1,6 @@
package dialogs package dialogs
import ( import (
"fmt"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/application"
) )
@@ -27,11 +25,6 @@ func SaveFileDialog(filename string) (string, error) {
} }
func OpenFileDialog(title string) (string, error) { func OpenFileDialog(title string) (string, error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
dialog := application.OpenFileDialog() dialog := application.OpenFileDialog()
dialog.SetTitle(title) dialog.SetTitle(title)
dialog.CanChooseDirectories(false) dialog.CanChooseDirectories(false)

View File

@@ -8,7 +8,7 @@ import (
func main() { func main() {
g := gen.NewGenerator(gen.Config{ g := gen.NewGenerator(gen.Config{
OutPath: "../dal", // output directory, default value is ./query OutPath: "../dal",
Mode: gen.WithDefaultQuery | gen.WithQueryInterface | gen.WithoutContext, Mode: gen.WithDefaultQuery | gen.WithQueryInterface | gen.WithoutContext,
FieldNullable: true, FieldNullable: true,
WithUnitTest: true, WithUnitTest: true,

20
internal/utils/locales.go Normal file
View File

@@ -0,0 +1,20 @@
package utils
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func init() {
sortingAddon()
}
func sortingAddon() {
message.SetString(language.Russian, "Field %s not found", "Поле %s не найдено")
message.SetString(language.Russian, "Field `%s` can only be sorted by ASC or DESC", "Поле `%s` может быть отсортировано только по ASC или DESC")
message.SetString(language.Russian, "Failed to parse tag for `%s` failed: %s", "Не удалось разобрать тег для `%s`: %s")
message.SetString(language.Russian, "Field `%s` doesn't have ui tag", "Поле `%s` не имеет ui тега")
message.SetString(language.Russian, "Field `%s` is array and cannot be used for sorting", "Поле `%s` является массивом и не может быть использовано для сортировки")
message.SetString(language.Russian, "Too complex fieldPath for structure `%s`", "Слишком сложный путь поля для структуры `%s`")
message.SetString(language.Russian, "Invalid field path for `%s` field", "Недопустимый путь поля для поля `%s`")
}

View File

@@ -5,15 +5,15 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/kuzgoga/fogg" "github.com/kuzgoga/fogg"
"golang.org/x/text/language"
"golang.org/x/text/message"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
"reflect" "reflect"
"strings" "strings"
) )
// SortByOrder Order items by specified field and a sort type var p = message.NewPrinter(language.Russian)
// Example: SortByOrder(map[string]string{"name": "ASC"}, &models.Post{})
// ASC - по возрастанию (или от А до Я)
// DESC - по убыванию (или от Я до А)
func SortByOrder[T any](fieldsSortOrder map[string]string, entity T) ([]*T, error) { func SortByOrder[T any](fieldsSortOrder map[string]string, entity T) ([]*T, error) {
var ( var (
orderQuery []string orderQuery []string
@@ -26,24 +26,24 @@ func SortByOrder[T any](fieldsSortOrder map[string]string, entity T) ([]*T, erro
field, fieldExist := structInfo.FieldByName(name) field, fieldExist := structInfo.FieldByName(name)
if !fieldExist { if !fieldExist {
return nil, errors.New(fmt.Sprintf("Field `%s` not found", name)) return nil, errors.New(p.Sprintf("Field %s not found", name))
} }
if strings.ToUpper(order) != "ASC" && strings.ToUpper(order) != "DESC" { if strings.ToUpper(order) != "ASC" && strings.ToUpper(order) != "DESC" {
return nil, errors.New(fmt.Sprintf("Field `%s` can only be sorted by ASC or DESC", name)) return nil, errors.New(p.Sprintf("Field `%s` can only be sorted by ASC or DESC", name))
} }
tag, err := fogg.Parse(string(field.Tag)) tag, err := fogg.Parse(string(field.Tag))
if err != nil { if err != nil {
return nil, errors.New(fmt.Sprintf("Failed to parse tag for `%s` failed: %s", name, err)) return nil, errors.New(p.Sprintf("Failed to parse tag for `%s` failed: %s", name, err))
} }
if !tag.HasTag("ui") { if !tag.HasTag("ui") {
return nil, errors.New(fmt.Sprintf("Field `%s` doesn't have ui tag", name)) return nil, errors.New(p.Sprintf("Field `%s` doesn't have ui tag", name))
} }
if field.Type.Kind() == reflect.Slice { if field.Type.Kind() == reflect.Slice {
return nil, errors.New(fmt.Sprintf("Field `%s` is array and cannot be used for sorting", name)) return nil, errors.New(p.Sprintf("Field `%s` is array and cannot be used for sorting", name))
} }
fieldPath := tag.GetTag("ui").GetParamOr("field", "") fieldPath := tag.GetTag("ui").GetParamOr("field", "")
@@ -53,11 +53,11 @@ func SortByOrder[T any](fieldsSortOrder map[string]string, entity T) ([]*T, erro
fieldsPathParts := strings.Split(fieldPath, ".") fieldsPathParts := strings.Split(fieldPath, ".")
if len(fieldsPathParts) > 1 { if len(fieldsPathParts) > 1 {
return nil, errors.New(fmt.Sprintf("Too complex fieldPath for structure `%s`", name)) return nil, errors.New(p.Sprintf("Too complex fieldPath for structure `%s`", name))
} }
if len(fieldsPathParts) == 0 { if len(fieldsPathParts) == 0 {
return nil, errors.New(fmt.Sprintf("Invalid field path for `%s` field", name)) return nil, errors.New(p.Sprintf("Invalid field path for `%s` field", name))
} }
joinPathParts := append([]string{field.Type.Name()}, fieldsPathParts...) joinPathParts := append([]string{field.Type.Name()}, fieldsPathParts...)