Compare commits
2 Commits
user_telep
...
translate_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7264a11ba6 | ||
| 04e238fd66 |
@@ -18,7 +18,7 @@ type ProductDescription struct {
|
||||
AvailableLater string `gorm:"column:available_later;type:varchar(255)" json:"available_later" form:"available_later"`
|
||||
DeliveryInStock string `gorm:"column:delivery_in_stock;type:varchar(255)" json:"delivery_in_stock" form:"delivery_in_stock"`
|
||||
DeliveryOutStock string `gorm:"column:delivery_out_stock;type:varchar(255)" json:"delivery_out_stock" form:"delivery_out_stock"`
|
||||
Usage string `gorm:"column:_usage_;type:text" json:"usage" form:"usage"`
|
||||
Usage string `gorm:"column:usage;type:text" json:"usage" form:"usage"`
|
||||
|
||||
ImageLink string `gorm:"column:image_link" json:"image_link"`
|
||||
ExistsInDatabase bool `gorm:"-" json:"exists_in_database"`
|
||||
|
||||
@@ -52,7 +52,7 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
|
||||
`+dbmodel.PsProductLangCols.AvailableLater.TabCol()+` AS available_later,
|
||||
`+dbmodel.PsProductLangCols.DeliveryInStock.TabCol()+` AS delivery_in_stock,
|
||||
`+dbmodel.PsProductLangCols.DeliveryOutStock.TabCol()+` AS delivery_out_stock,
|
||||
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS _usage_,
|
||||
`+dbmodel.PsProductLangCols.Usage.TabCol()+` AS `+"`usage`"+`,
|
||||
CONCAT(?, '/', `+dbmodel.PsImageShopCols.IDImage.TabCol()+`, '-large_default/', `+dbmodel.PsProductLangCols.LinkRewrite.TabCol()+`, '.webp') AS image_link
|
||||
`, config.Get().Image.ImagePrefix).
|
||||
Joins("JOIN " + dbmodel.TableNamePsImageShop +
|
||||
@@ -74,10 +74,10 @@ func (r *ProductDescriptionRepo) GetProductDescription(productID uint, productid
|
||||
|
||||
// If it doesn't exist, returns an error.
|
||||
func (r *ProductDescriptionRepo) CreateIfDoesNotExist(productID uint, productid_lang uint) error {
|
||||
record := model.ProductDescription{
|
||||
ProductID: productID,
|
||||
ShopID: constdata.SHOP_ID,
|
||||
LangID: productid_lang,
|
||||
record := dbmodel.PsProductLang{
|
||||
IDProduct: int32(productID),
|
||||
IDShop: int32(constdata.SHOP_ID),
|
||||
IDLang: int32(productid_lang),
|
||||
}
|
||||
|
||||
err := db.Get().
|
||||
|
||||
@@ -89,13 +89,24 @@ func (s *ProductTranslationService) GetProductDescription(userID uint, productID
|
||||
// Updates relevant fields with the "updates" map
|
||||
func (s *ProductTranslationService) SaveProductDescription(userID uint, productID uint, productLangID uint, updates map[string]string) error {
|
||||
// only some fields can be affected
|
||||
allowedFields := []string{"description", "description_short", "meta_description", "meta_title", "name", "available_now", "available_later", "usage"}
|
||||
allowedFields := []string{"description", "description_short", "link_rewrite", "meta_description", "meta_keywords", "meta_title", "name",
|
||||
"available_now", "available_later", "delivery_in_stock", "delivery_out_stock", "usage"}
|
||||
for key := range updates {
|
||||
if !slices.Contains(allowedFields, key) {
|
||||
return responseErrors.ErrBadField
|
||||
}
|
||||
}
|
||||
|
||||
if text, exists := updates["link_rewrite"]; exists {
|
||||
// sanitize and check that link_rewrite is a valid url slug
|
||||
sanitized := SanitizeSlug(text)
|
||||
if !IsValidSlug(sanitized) {
|
||||
return responseErrors.ErrInvalidURLSlug
|
||||
}
|
||||
|
||||
updates["link_rewrite"] = sanitized
|
||||
}
|
||||
|
||||
// check that fields description, description_short and usage, if they exist, have a valid html format
|
||||
mustBeHTML := []string{"description", "description_short", "usage"}
|
||||
for i := 0; i < len(mustBeHTML); i++ {
|
||||
@@ -136,20 +147,28 @@ func (s *ProductTranslationService) TranslateProductDescription(userID uint, pro
|
||||
|
||||
fields := []*string{&productDescription.Description,
|
||||
&productDescription.DescriptionShort,
|
||||
&productDescription.LinkRewrite,
|
||||
&productDescription.MetaDescription,
|
||||
&productDescription.MetaKeywords,
|
||||
&productDescription.MetaTitle,
|
||||
&productDescription.Name,
|
||||
&productDescription.AvailableNow,
|
||||
&productDescription.AvailableLater,
|
||||
&productDescription.DeliveryInStock,
|
||||
&productDescription.DeliveryOutStock,
|
||||
&productDescription.Usage,
|
||||
}
|
||||
keys := []string{"translation_of_product_description",
|
||||
"translation_of_product_short_description",
|
||||
"translation_of_product_url_link",
|
||||
"translation_of_product_meta_description",
|
||||
"translation_of_product_meta_keywords",
|
||||
"translation_of_product_meta_title",
|
||||
"translation_of_product_name",
|
||||
"translation_of_product_available_now",
|
||||
"translation_of_product_available_later",
|
||||
"translation_of_product_available_now_message",
|
||||
"translation_of_product_available_later_message",
|
||||
"translation_of_product_delivery_in_stock_message",
|
||||
"translation_of_product_delivery_out_stock_message",
|
||||
"translation_of_product_usage",
|
||||
}
|
||||
|
||||
|
||||
69
app/service/productTranslationService/sanitizeURLSlug.go
Normal file
69
app/service/productTranslationService/sanitizeURLSlug.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package productTranslationService
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
constdata "git.ma-al.com/goc_daniel/b2b/app/utils/const_data"
|
||||
"github.com/dlclark/regexp2"
|
||||
"golang.org/x/text/runes"
|
||||
"golang.org/x/text/transform"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
func IsValidSlug(s string) bool {
|
||||
var slug_regex2 = regexp2.MustCompile(constdata.SLUG_REGEX, regexp2.None)
|
||||
|
||||
ok, _ := slug_regex2.MatchString(s)
|
||||
return ok
|
||||
}
|
||||
|
||||
func SanitizeSlug(s string) string {
|
||||
s = strings.TrimSpace(strings.ToLower(s))
|
||||
|
||||
// First apply explicit transliteration for language-specific letters.
|
||||
s = transliterateWithTable(s)
|
||||
|
||||
// Then normalize and strip any remaining combining marks.
|
||||
s = removeDiacritics(s)
|
||||
|
||||
// Replace all non-alphanumeric runs with "-"
|
||||
var non_alphanum_regex2 = regexp2.MustCompile(constdata.NON_ALNUM_REGEX, regexp2.None)
|
||||
s, _ = non_alphanum_regex2.Replace(s, "-", -1, -1)
|
||||
|
||||
// Collapse repeated "-" and trim edges
|
||||
var multi_dash_regex2 = regexp2.MustCompile(constdata.MULTI_DASH_REGEX, regexp2.None)
|
||||
s, _ = multi_dash_regex2.Replace(s, "-", -1, -1)
|
||||
|
||||
s = strings.Trim(s, "-")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func transliterateWithTable(s string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
|
||||
for _, r := range s {
|
||||
if repl, ok := constdata.TRANSLITERATION_TABLE[r]; ok {
|
||||
b.WriteString(repl)
|
||||
} else {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func removeDiacritics(s string) string {
|
||||
t := transform.Chain(
|
||||
norm.NFD,
|
||||
runes.Remove(runes.In(unicode.Mn)),
|
||||
norm.NFC,
|
||||
)
|
||||
out, _, err := transform.String(t, s)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -12,3 +12,28 @@ const MAX_AMOUNT_OF_CARTS_PER_USER = 10
|
||||
const DEFAULT_NEW_CART_NAME = "new cart"
|
||||
|
||||
const USER_LOCALE = "user"
|
||||
|
||||
// Slug sanitization
|
||||
const NON_ALNUM_REGEX = `[^a-z0-9]+`
|
||||
const MULTI_DASH_REGEX = `-+`
|
||||
const SLUG_REGEX = `^[a-z0-9]+(?:-[a-z0-9]+)*$`
|
||||
|
||||
// Currently supports only German+Polish specific cases
|
||||
var TRANSLITERATION_TABLE = map[rune]string{
|
||||
// German
|
||||
'ä': "ae",
|
||||
'ö': "oe",
|
||||
'ü': "ue",
|
||||
'ß': "ss",
|
||||
|
||||
// Polish
|
||||
'ą': "a",
|
||||
'ć': "c",
|
||||
'ę': "e",
|
||||
'ł': "l",
|
||||
'ń': "n",
|
||||
'ó': "o",
|
||||
'ś': "s",
|
||||
'ż': "z",
|
||||
'ź': "z",
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ var (
|
||||
// Typed errors for product description handler
|
||||
ErrBadAttribute = errors.New("bad or missing attribute value in header")
|
||||
ErrBadField = errors.New("this field can not be updated")
|
||||
ErrInvalidURLSlug = errors.New("URL slug does not obey the industry standard")
|
||||
ErrInvalidXHTML = errors.New("text is not in xhtml format")
|
||||
ErrAIResponseFail = errors.New("AI responded with failure")
|
||||
ErrAIBadOutput = errors.New("AI response does not obey the format")
|
||||
@@ -136,6 +137,8 @@ func GetErrorCode(c fiber.Ctx, err error) string {
|
||||
return i18n.T_(c, "error.err_bad_attribute")
|
||||
case errors.Is(err, ErrBadField):
|
||||
return i18n.T_(c, "error.err_bad_field")
|
||||
case errors.Is(err, ErrInvalidURLSlug):
|
||||
return i18n.T_(c, "error.invalid_url_slug")
|
||||
case errors.Is(err, ErrInvalidXHTML):
|
||||
return i18n.T_(c, "error.err_invalid_html")
|
||||
case errors.Is(err, ErrAIResponseFail):
|
||||
@@ -195,6 +198,7 @@ func GetErrorStatus(err error) int {
|
||||
errors.Is(err, ErrInvalidPassword),
|
||||
errors.Is(err, ErrBadAttribute),
|
||||
errors.Is(err, ErrBadField),
|
||||
errors.Is(err, ErrInvalidURLSlug),
|
||||
errors.Is(err, ErrInvalidXHTML),
|
||||
errors.Is(err, ErrBadPaging),
|
||||
errors.Is(err, ErrNoRootFound),
|
||||
|
||||
39
bruno/b2b-daniel/save-product-description.yml
Normal file
39
bruno/b2b-daniel/save-product-description.yml
Normal file
File diff suppressed because one or more lines are too long
28
bruno/b2b-daniel/translate-product-description.yml
Normal file
28
bruno/b2b-daniel/translate-product-description.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
info:
|
||||
name: translate-product-description
|
||||
type: http
|
||||
seq: 20
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: http://localhost:3000/api/v1/restricted/product-translation/translate-product-description?productID=51&productFromLangID=1&productToLangID=3&model=Google
|
||||
params:
|
||||
- name: productID
|
||||
value: "51"
|
||||
type: query
|
||||
- name: productFromLangID
|
||||
value: "1"
|
||||
type: query
|
||||
- name: productToLangID
|
||||
value: "3"
|
||||
type: query
|
||||
- name: model
|
||||
value: Google
|
||||
type: query
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
Reference in New Issue
Block a user