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) } // ReuseCandidate represents a piece of Russian text that appears in multiple entities type ReuseCandidate struct { RussianValue string EntityCount int }