turash/bugulma/backend/internal/domain/localization.go
2025-12-15 10:06:41 +01:00

165 lines
6.3 KiB
Go

package domain
import (
"context"
"time"
"gorm.io/gorm"
)
// Localizable defines an interface for entities that support localization
type Localizable interface {
GetEntityType() string
GetEntityID() string
}
// EntityLoadOptions contains options for loading entities for localization
type EntityLoadOptions struct {
IncludeAllSites bool
}
// EntityHandler defines the interface for handling entity-specific operations for localization
// This interface is implemented by handlers in the localization/handlers package
type EntityHandler[T any] interface {
// GetEntityID returns the unique identifier for an entity
GetEntityID(entity T) string
// GetFieldValue returns the value of a specific field from an entity
GetFieldValue(entity T, field string) string
// GetLocalizableFields returns the list of fields that can be localized
GetLocalizableFields() []string
// LoadEntities loads entities from the database with optional filtering
LoadEntities(db *gorm.DB, options EntityLoadOptions) ([]T, error)
// BuildFieldQuery builds a GORM query for finding entities with specific field values
BuildFieldQuery(db *gorm.DB, field, value string) *gorm.DB
// GetEntityType returns the entity type string
GetEntityType() string
}
// Localization represents localized strings for any entity and field
type Localization struct {
ID string `gorm:"primaryKey;type:text"`
EntityType string `gorm:"type:varchar(50);index"` // 'site', 'organization', 'business', 'user'
EntityID string `gorm:"type:text;index"`
Field string `gorm:"type:varchar(50);index"` // 'name', 'description'
Locale string `gorm:"type:varchar(10);index"` // 'ru', 'en', 'tt'
Value string `gorm:"type:text"`
// Timestamps
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
// TableName specifies the table name for GORM
func (Localization) TableName() string {
return "localizations"
}
// LocalizationService provides methods for managing localized content
type LocalizationService interface {
GetLocalizedValue(entityType, entityID, field, locale string) (string, error)
SetLocalizedValue(entityType, entityID, field, locale, value string) error
GetAllLocalizedValues(entityType, entityID string) (map[string]map[string]string, error) // field -> locale -> value
GetLocalizedEntity(entityType, entityID, locale string) (map[string]string, error)
GetSupportedLocalesForEntity(entityType, entityID string) ([]string, error)
DeleteLocalizedValue(entityType, entityID, field, locale string) error
BulkSetLocalizedValues(entityType, entityID string, values map[string]map[string]string) error
GetAllLocales() ([]string, error)
SearchLocalizations(query, locale string, limit int) ([]*Localization, error)
ApplyLocalizationToEntity(entity Localizable, locale string) error
}
// Helper methods for models to implement Localizable
func (s *Site) GetEntityType() string { return "site" }
func (s *Site) GetEntityID() string { return s.ID }
func (o *Organization) GetEntityType() string { return "organization" }
func (o *Organization) GetEntityID() string { return o.ID }
func (u *User) GetEntityType() string { return "user" }
func (u *User) GetEntityID() string { return u.ID }
// GetLocalizedName retrieves the localized name for an entity, falling back to primary name
func GetLocalizedName(entity Localizable, locale string, service LocalizationService) string {
if locale == "ru" {
// Return primary name for Russian
switch e := entity.(type) {
case *Site:
return e.Name
case *Organization:
return e.Name
case *User:
return e.Name
}
}
// Try to get localized value
value, err := service.GetLocalizedValue(entity.GetEntityType(), entity.GetEntityID(), "name", locale)
if err != nil || value == "" {
// Fallback to primary name
switch e := entity.(type) {
case *Site:
return e.Name
case *Organization:
return e.Name
case *User:
return e.Name
}
}
return value
}
// SetLocalizedName sets the localized name for an entity
func SetLocalizedName(entity Localizable, locale, value string, service LocalizationService) error {
return service.SetLocalizedValue(entity.GetEntityType(), entity.GetEntityID(), "name", locale, value)
}
// TranslationStats represents overall translation statistics
type TranslationStats struct {
TotalEntities int
TotalFields int
TotalTranslations int
EntityStats map[string]*EntityTranslationStats
}
// EntityTranslationStats represents translation statistics for a specific entity type
type EntityTranslationStats struct {
EntityType string
TotalEntities int
TotalFields int
RussianCount int
EnglishCount int
TatarCount int
TotalTranslations int
}
type LocalizationRepository interface {
Create(ctx context.Context, loc *Localization) error
GetByEntityAndField(ctx context.Context, entityType, entityID, field, locale string) (*Localization, error)
GetAllByEntity(ctx context.Context, entityType, entityID string) ([]*Localization, error)
Update(ctx context.Context, loc *Localization) error
Delete(ctx context.Context, id string) error
GetByEntityTypeAndLocale(ctx context.Context, entityType, locale string) ([]*Localization, error)
GetAllLocales(ctx context.Context) ([]string, error)
GetSupportedLocalesForEntity(ctx context.Context, entityType, entityID string) ([]string, error)
BulkCreate(ctx context.Context, localizations []*Localization) error
BulkDelete(ctx context.Context, ids []string) error
SearchLocalizations(ctx context.Context, query string, locale string, limit int) ([]*Localization, error)
GetTranslationReuseCandidates(ctx context.Context, entityType, field, locale string) ([]ReuseCandidate, error)
GetEntitiesNeedingTranslation(ctx context.Context, entityType, field, targetLocale string, limit int) ([]string, error)
FindExistingTranslationByRussianText(ctx context.Context, entityType, field, targetLocale, russianText string) (string, error)
GetTranslationCountsByEntity(ctx context.Context) (map[string]map[string]int, error)
// GetEntityFieldValue returns the raw value of a field from the entity table (e.g., name, description)
GetEntityFieldValue(ctx context.Context, entityType, entityID, field string) (string, error)
}
// ReuseCandidate represents a piece of Russian text that appears in multiple entities
type ReuseCandidate struct {
RussianValue string
EntityCount int
}