mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
371 lines
13 KiB
Go
371 lines
13 KiB
Go
package service
|
|
|
|
import (
|
|
"bugulma/backend/internal/domain"
|
|
"bugulma/backend/internal/localization"
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Note: SupportedLocales and DefaultLocale are defined in i18n_service.go
|
|
// They are shared constants used across all i18n-related services
|
|
|
|
// LocalizationService implements the domain.LocalizationService interface
|
|
type LocalizationService struct {
|
|
repo domain.LocalizationRepository
|
|
}
|
|
|
|
// NewLocalizationService creates a new localization service
|
|
func NewLocalizationService(repo domain.LocalizationRepository) domain.LocalizationService {
|
|
return &LocalizationService{repo: repo}
|
|
}
|
|
|
|
// GetLocalizedValue retrieves a localized value for a specific entity field and locale
|
|
func (s *LocalizationService) GetLocalizedValue(entityType, entityID, field, locale string) (string, error) {
|
|
return s.GetLocalizedValueWithContext(context.Background(), entityType, entityID, field, locale)
|
|
}
|
|
|
|
// GetLocalizedValueWithContext retrieves a localized value with context support
|
|
func (s *LocalizationService) GetLocalizedValueWithContext(ctx context.Context, entityType, entityID, field, locale string) (string, error) {
|
|
if entityType == "" || entityID == "" || field == "" || locale == "" {
|
|
return "", fmt.Errorf("all parameters (entityType, entityID, field, locale) are required")
|
|
}
|
|
|
|
// Validate locale
|
|
if !s.isValidLocale(locale) {
|
|
return "", fmt.Errorf("invalid locale: %s. Supported locales: ru, en, tt", locale)
|
|
}
|
|
|
|
loc, err := s.repo.GetByEntityAndField(ctx, entityType, entityID, field, locale)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to retrieve localization: %w", err)
|
|
}
|
|
|
|
if loc == nil {
|
|
return "", nil // No localization found
|
|
}
|
|
|
|
return loc.Value, nil
|
|
}
|
|
|
|
// SetLocalizedValue sets or updates a localized value for a specific entity field and locale
|
|
func (s *LocalizationService) SetLocalizedValue(entityType, entityID, field, locale, value string) error {
|
|
return s.SetLocalizedValueWithContext(context.Background(), entityType, entityID, field, locale, value)
|
|
}
|
|
|
|
// SetLocalizedValueWithContext sets or updates a localized value with context support
|
|
func (s *LocalizationService) SetLocalizedValueWithContext(ctx context.Context, entityType, entityID, field, locale, value string) error {
|
|
if entityType == "" || entityID == "" || field == "" || locale == "" {
|
|
return fmt.Errorf("all parameters (entityType, entityID, field, locale) are required")
|
|
}
|
|
|
|
// Validate locale
|
|
if !s.isValidLocale(locale) {
|
|
return fmt.Errorf("invalid locale: %s. Supported locales: ru, en, tt", locale)
|
|
}
|
|
|
|
// Validate entity type and field combination
|
|
if !s.isValidEntityField(entityType, field) {
|
|
return fmt.Errorf("invalid field '%s' for entity type '%s'", field, entityType)
|
|
}
|
|
|
|
// Check if localization already exists
|
|
existing, err := s.repo.GetByEntityAndField(ctx, entityType, entityID, field, locale)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check existing localization: %w", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
|
|
if existing != nil {
|
|
// Update existing
|
|
existing.Value = value
|
|
existing.UpdatedAt = now
|
|
return s.repo.Update(ctx, existing)
|
|
} else {
|
|
// Create new
|
|
loc := &domain.Localization{
|
|
EntityType: entityType,
|
|
EntityID: entityID,
|
|
Field: field,
|
|
Locale: locale,
|
|
Value: value,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
return s.repo.Create(ctx, loc)
|
|
}
|
|
}
|
|
|
|
// GetAllLocalizedValues retrieves all localized values for a specific entity
|
|
func (s *LocalizationService) GetAllLocalizedValues(entityType, entityID string) (map[string]map[string]string, error) {
|
|
return s.GetAllLocalizedValuesWithContext(context.Background(), entityType, entityID)
|
|
}
|
|
|
|
// GetAllLocalizedValuesWithContext retrieves all localized values with context support
|
|
func (s *LocalizationService) GetAllLocalizedValuesWithContext(ctx context.Context, entityType, entityID string) (map[string]map[string]string, error) {
|
|
if entityType == "" || entityID == "" {
|
|
return nil, fmt.Errorf("entityType and entityID are required")
|
|
}
|
|
|
|
localizations, err := s.repo.GetAllByEntity(ctx, entityType, entityID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve localizations: %w", err)
|
|
}
|
|
|
|
result := make(map[string]map[string]string)
|
|
for _, loc := range localizations {
|
|
if result[loc.Field] == nil {
|
|
result[loc.Field] = make(map[string]string)
|
|
}
|
|
result[loc.Field][loc.Locale] = loc.Value
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetLocalizedEntity retrieves an entity with all its fields localized for a specific locale
|
|
func (s *LocalizationService) GetLocalizedEntity(entityType, entityID, locale string) (map[string]string, error) {
|
|
return s.GetLocalizedEntityWithContext(context.Background(), entityType, entityID, locale)
|
|
}
|
|
|
|
// GetLocalizedEntityWithContext retrieves an entity with all its fields localized with context support
|
|
func (s *LocalizationService) GetLocalizedEntityWithContext(ctx context.Context, entityType, entityID, locale string) (map[string]string, error) {
|
|
if entityType == "" || entityID == "" || locale == "" {
|
|
return nil, fmt.Errorf("all parameters (entityType, entityID, locale) are required")
|
|
}
|
|
|
|
if !s.isValidLocale(locale) {
|
|
return nil, fmt.Errorf("invalid locale: %s. Supported locales: ru, en, tt", locale)
|
|
}
|
|
|
|
localizations, err := s.repo.GetAllByEntity(ctx, entityType, entityID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve localizations: %w", err)
|
|
}
|
|
|
|
result := make(map[string]string)
|
|
for _, loc := range localizations {
|
|
if loc.Locale == locale {
|
|
result[loc.Field] = loc.Value
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GetSupportedLocalesForEntity returns all locales that have translations for a specific entity
|
|
func (s *LocalizationService) GetSupportedLocalesForEntity(entityType, entityID string) ([]string, error) {
|
|
return s.GetSupportedLocalesForEntityWithContext(context.Background(), entityType, entityID)
|
|
}
|
|
|
|
// GetSupportedLocalesForEntityWithContext returns all locales with context support
|
|
func (s *LocalizationService) GetSupportedLocalesForEntityWithContext(ctx context.Context, entityType, entityID string) ([]string, error) {
|
|
if entityType == "" || entityID == "" {
|
|
return nil, fmt.Errorf("entityType and entityID are required")
|
|
}
|
|
|
|
return s.repo.GetSupportedLocalesForEntity(ctx, entityType, entityID)
|
|
}
|
|
|
|
// DeleteLocalizedValue deletes a specific localization
|
|
func (s *LocalizationService) DeleteLocalizedValue(entityType, entityID, field, locale string) error {
|
|
return s.DeleteLocalizedValueWithContext(context.Background(), entityType, entityID, field, locale)
|
|
}
|
|
|
|
// DeleteLocalizedValueWithContext deletes a specific localization with context support
|
|
func (s *LocalizationService) DeleteLocalizedValueWithContext(ctx context.Context, entityType, entityID, field, locale string) error {
|
|
if entityType == "" || entityID == "" || field == "" || locale == "" {
|
|
return fmt.Errorf("all parameters (entityType, entityID, field, locale) are required")
|
|
}
|
|
|
|
loc, err := s.repo.GetByEntityAndField(ctx, entityType, entityID, field, locale)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find localization: %w", err)
|
|
}
|
|
|
|
if loc == nil {
|
|
return fmt.Errorf("localization not found")
|
|
}
|
|
|
|
return s.repo.Delete(ctx, loc.ID)
|
|
}
|
|
|
|
// BulkSetLocalizedValues sets multiple localized values in a single operation
|
|
func (s *LocalizationService) BulkSetLocalizedValues(entityType, entityID string, values map[string]map[string]string) error {
|
|
return s.BulkSetLocalizedValuesWithContext(context.Background(), entityType, entityID, values)
|
|
}
|
|
|
|
// BulkSetLocalizedValuesWithContext sets multiple localized values with context support
|
|
func (s *LocalizationService) BulkSetLocalizedValuesWithContext(ctx context.Context, entityType, entityID string, values map[string]map[string]string) error {
|
|
if entityType == "" || entityID == "" {
|
|
return fmt.Errorf("entityType and entityID are required")
|
|
}
|
|
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var localizations []*domain.Localization
|
|
now := time.Now()
|
|
|
|
for field, localeValues := range values {
|
|
for locale, value := range localeValues {
|
|
if !s.isValidLocale(locale) {
|
|
return fmt.Errorf("invalid locale: %s for field %s", locale, field)
|
|
}
|
|
if !s.isValidEntityField(entityType, field) {
|
|
return fmt.Errorf("invalid field '%s' for entity type '%s'", field, entityType)
|
|
}
|
|
|
|
loc := &domain.Localization{
|
|
EntityType: entityType,
|
|
EntityID: entityID,
|
|
Field: field,
|
|
Locale: locale,
|
|
Value: value,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
localizations = append(localizations, loc)
|
|
}
|
|
}
|
|
|
|
// First delete existing localizations for these fields/locales
|
|
for _, loc := range localizations {
|
|
existing, _ := s.repo.GetByEntityAndField(ctx, loc.EntityType, loc.EntityID, loc.Field, loc.Locale)
|
|
if existing != nil {
|
|
s.repo.Delete(ctx, existing.ID) // Ignore errors, continue with creation
|
|
}
|
|
}
|
|
|
|
return s.repo.BulkCreate(ctx, localizations)
|
|
}
|
|
|
|
// GetAllLocales returns all available locales in the system
|
|
func (s *LocalizationService) GetAllLocales() ([]string, error) {
|
|
return s.GetAllLocalesWithContext(context.Background())
|
|
}
|
|
|
|
// GetAllLocalesWithContext returns all available locales with context support
|
|
func (s *LocalizationService) GetAllLocalesWithContext(ctx context.Context) ([]string, error) {
|
|
return s.repo.GetAllLocales(ctx)
|
|
}
|
|
|
|
// SearchLocalizations searches for localizations containing specific text
|
|
func (s *LocalizationService) SearchLocalizations(query, locale string, limit int) ([]*domain.Localization, error) {
|
|
return s.SearchLocalizationsWithContext(context.Background(), query, locale, limit)
|
|
}
|
|
|
|
// SearchLocalizationsWithContext searches for localizations with context support
|
|
func (s *LocalizationService) SearchLocalizationsWithContext(ctx context.Context, query, locale string, limit int) ([]*domain.Localization, error) {
|
|
if query == "" {
|
|
return nil, fmt.Errorf("search query cannot be empty")
|
|
}
|
|
|
|
if locale != "" && !s.isValidLocale(locale) {
|
|
return nil, fmt.Errorf("invalid locale: %s", locale)
|
|
}
|
|
|
|
if limit <= 0 {
|
|
limit = 50 // Default limit
|
|
}
|
|
if limit > 1000 {
|
|
limit = 1000 // Max limit
|
|
}
|
|
|
|
return s.repo.SearchLocalizations(ctx, query, locale, limit)
|
|
}
|
|
|
|
// Helper methods for validation
|
|
|
|
func (s *LocalizationService) isValidLocale(locale string) bool {
|
|
// Use the centralized locale constants from I18nService
|
|
for _, supported := range SupportedLocales {
|
|
if supported == locale {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *LocalizationService) isValidEntityField(entityType, field string) bool {
|
|
// Use registry to get valid fields for the entity type
|
|
fields := localization.GetFieldsForEntityType(entityType)
|
|
for _, f := range fields {
|
|
if f == field {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ApplyLocalizationToEntity applies localization to a localizable entity for a specific locale
|
|
func (s *LocalizationService) ApplyLocalizationToEntity(entity domain.Localizable, locale string) error {
|
|
return s.ApplyLocalizationToEntityWithContext(context.Background(), entity, locale)
|
|
}
|
|
|
|
// ApplyLocalizationToEntityWithContext applies localization with context support
|
|
func (s *LocalizationService) ApplyLocalizationToEntityWithContext(ctx context.Context, entity domain.Localizable, locale string) error {
|
|
if entity == nil {
|
|
return fmt.Errorf("entity cannot be nil")
|
|
}
|
|
|
|
if locale == "ru" {
|
|
// Russian is the primary language, no localization needed
|
|
return nil
|
|
}
|
|
|
|
if !s.isValidLocale(locale) {
|
|
return fmt.Errorf("invalid locale: %s", locale)
|
|
}
|
|
|
|
localizations, err := s.GetAllLocalizedValuesWithContext(ctx, entity.GetEntityType(), entity.GetEntityID())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get localizations: %w", err)
|
|
}
|
|
|
|
// Apply localizations based on entity type
|
|
switch e := entity.(type) {
|
|
case *domain.Site:
|
|
if name, ok := localizations["name"][locale]; ok && name != "" {
|
|
e.Name = name
|
|
}
|
|
if notes, ok := localizations["notes"][locale]; ok && notes != "" {
|
|
e.Notes = notes
|
|
}
|
|
if builderOwner, ok := localizations["builder_owner"][locale]; ok && builderOwner != "" {
|
|
e.BuilderOwner = builderOwner
|
|
}
|
|
if architect, ok := localizations["architect"][locale]; ok && architect != "" {
|
|
e.Architect = architect
|
|
}
|
|
if originalPurpose, ok := localizations["original_purpose"][locale]; ok && originalPurpose != "" {
|
|
e.OriginalPurpose = originalPurpose
|
|
}
|
|
if currentUse, ok := localizations["current_use"][locale]; ok && currentUse != "" {
|
|
e.CurrentUse = currentUse
|
|
}
|
|
if style, ok := localizations["style"][locale]; ok && style != "" {
|
|
e.Style = style
|
|
}
|
|
if materials, ok := localizations["materials"][locale]; ok && materials != "" {
|
|
e.Materials = materials
|
|
}
|
|
case *domain.Organization:
|
|
if name, ok := localizations["name"][locale]; ok && name != "" {
|
|
e.Name = name
|
|
}
|
|
if description, ok := localizations["description"][locale]; ok && description != "" {
|
|
e.Description = description
|
|
}
|
|
case *domain.User:
|
|
if name, ok := localizations["name"][locale]; ok && name != "" {
|
|
e.Name = name
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|