prepare for google translate

This commit is contained in:
Daniel Goc
2026-03-16 08:51:35 +01:00
parent 6ff47ce1c4
commit a44885faa5
8 changed files with 198 additions and 28 deletions

View File

@@ -13,14 +13,15 @@ import (
)
type Config struct {
Server ServerConfig
Database DatabaseConfig
Auth AuthConfig
OAuth OAuthConfig
App AppConfig
Email EmailConfig
I18n I18n
Pdf PdfPrinter
Server ServerConfig
Database DatabaseConfig
Auth AuthConfig
OAuth OAuthConfig
App AppConfig
Email EmailConfig
I18n I18n
Pdf PdfPrinter
GoogleTranslate GoogleTranslateConfig
}
type I18n struct {
@@ -82,6 +83,14 @@ type PdfPrinter struct {
ServerUrl string `env:"PDF_SERVER_URL,http://localhost:8000"`
}
// GoogleTranslateConfig holds configuration for the Google Cloud Translation API.
// CredentialsFile should point to a service account JSON key file.
// ProjectID is your Google Cloud project ID (e.g. "my-project-123").
type GoogleTranslateConfig struct {
CredentialsFile string `env:"GOOGLE_APPLICATION_CREDENTIALS"`
ProjectID string `env:"GOOGLE_CLOUD_PROJECT_ID"`
}
var cfg *Config
func init() {
@@ -153,6 +162,11 @@ func load() *Config {
slog.Error("not possible to load env variables for email : ", err.Error(), "")
}
err = loadEnv(&cfg.GoogleTranslate)
if err != nil {
slog.Error("not possible to load env variables for google translate : ", err.Error(), "")
}
return cfg
}

View File

@@ -5,32 +5,76 @@ import (
"encoding/xml"
"fmt"
"io"
"net/http"
"log"
"os"
"slices"
"strings"
"time"
"git.ma-al.com/goc_daniel/b2b/app/config"
"git.ma-al.com/goc_daniel/b2b/app/db"
"git.ma-al.com/goc_daniel/b2b/app/model"
"git.ma-al.com/goc_daniel/b2b/app/service/langsService"
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
"google.golang.org/api/option"
"gorm.io/gorm"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/option"
"github.com/openai/openai-go/v3/responses"
// [START translate_v3_import_client_library]
"cloud.google.com/go/auth/credentials"
translate "cloud.google.com/go/translate/apiv3"
"cloud.google.com/go/translate/apiv3/translatepb"
// [END translate_v3_import_client_library]
)
type ProductDescriptionService struct {
db *gorm.DB
client openai.Client
db *gorm.DB
ctx context.Context
googleCli translate.TranslationClient
// projectID is the Google Cloud project ID used as the "parent" in API calls,
// e.g. "projects/my-project-123/locations/global"
projectID string
}
// New creates a ProductDescriptionService and authenticates against the
// Google Cloud Translation API using a service account key file.
//
// Required configuration (set in .env or environment):
//
// GOOGLE_APPLICATION_CREDENTIALS absolute path to the service account JSON key file
// GOOGLE_CLOUD_PROJECT_ID your Google Cloud project ID
//
// The service account must have the "Cloud Translation API User" role
// (roles/cloudtranslate.user) granted in Google Cloud IAM.
func New() *ProductDescriptionService {
ctx := context.Background()
cfg := config.Get()
// Read the service account key file whose path comes from config / env.
data, err := os.ReadFile(cfg.GoogleTranslate.CredentialsFile)
if err != nil {
log.Fatalf("productDescriptionService: cannot read credentials file %q: %v",
cfg.GoogleTranslate.CredentialsFile, err)
}
// Build OAuth2 credentials scoped to the Cloud Translation API.
// The correct scope for Cloud Translation v3 is "cloud-translation".
creds, err := credentials.DetectDefault(&credentials.DetectOptions{
Scopes: []string{"https://www.googleapis.com/auth/cloud-translation"},
CredentialsJSON: data,
})
if err != nil {
log.Fatalf("productDescriptionService: cannot build Google credentials: %v", err)
}
googleCli, err := translate.NewTranslationClient(ctx, option.WithAuthCredentials(creds))
if err != nil {
log.Fatalf("productDescriptionService: cannot create Translation client: %v", err)
}
return &ProductDescriptionService{
db: db.Get(),
client: openai.NewClient(option.WithAPIKey("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"),
option.WithHTTPClient(&http.Client{Timeout: 300 * time.Second})),
db: db.Get(),
ctx: ctx,
googleCli: *googleCli,
projectID: cfg.GoogleTranslate.ProjectID,
}
}
@@ -102,7 +146,12 @@ func (s *ProductDescriptionService) SaveProductDescription(userID uint, productI
return nil
}
// Updates relevant fields with the "updates" map
// TranslateProductDescription fetches the product description for productFromLangID,
// translates every text field into productToLangID using the Google Cloud
// Translation API (v3 TranslateText), and returns the translated record.
//
// The Google Cloud project must have the Cloud Translation API enabled and the
// service account must hold the "Cloud Translation API User" role.
func (s *ProductDescriptionService) TranslateProductDescription(userID uint, productID uint, productShopID uint, productFromLangID uint, productToLangID uint) (*model.ProductDescription, error) {
var ProductDescription model.ProductDescription
@@ -148,21 +197,36 @@ func (s *ProductDescriptionService) TranslateProductDescription(userID uint, pro
}
request = cleanForPrompt(request)
openai_response, _ := s.client.Responses.New(context.Background(), responses.ResponseNewParams{
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(request)},
Model: openai.ChatModelGPT4_1Mini,
// Model: openai.ChatModelGPT4_1Nano,
})
if openai_response.Status != "completed" {
return nil, responseErrors.ErrOpenAIResponseFail
// TranslateText is the standard Cloud Translation v3 endpoint.
// "parent" must be "projects/<PROJECT_ID>/locations/global" (or a specific region).
// MimeType "text/plain" is used because cleanForPrompt strips HTML attributes;
// switch to "text/html" if you want Google to preserve HTML tags automatically.
req := &translatepb.TranslateTextRequest{
Parent: fmt.Sprintf("projects/%s/locations/global", s.projectID),
TargetLanguageCode: lang.ISOCode,
MimeType: "text/plain",
Contents: []string{request},
}
responseGoogle, err := s.googleCli.TranslateText(s.ctx, req)
if err != nil {
fmt.Println(err)
return nil, err
}
// TranslateText returns one Translation per input string.
if len(responseGoogle.GetTranslations()) == 0 {
return nil, responseErrors.ErrOpenAIBadOutput
}
response := responseGoogle.GetTranslations()[0].GetTranslatedText()
for i := 0; i < len(keys); i++ {
success, resolution := resolveResponse(*fields[i], openai_response.OutputText(), keys[i])
success, resolution := resolveResponse(*fields[i], response, keys[i])
if !success {
return nil, responseErrors.ErrOpenAIBadOutput
}
*fields[i] = resolution
fmt.Println("resolution: ", resolution)
}
return &ProductDescription, nil