package service import ( "bugulma/backend/internal/domain" "context" "fmt" "strings" "sync" ) // TranslationCache provides caching for translations to avoid redundant API calls // DEPRECATED: This cache is kept for backward compatibility but should not be used in new code. // Use TranslationCacheService instead, which provides better features (LRU eviction, stats, context support). // TranslationCache will be removed in a future version. type TranslationCache struct { repo domain.LocalizationRepository cache map[string]map[string]string // [russianText][targetLocale] -> translation mu sync.RWMutex commonTerms map[string]map[string]string // Pre-populated common terms } // NewTranslationCache creates a new translation cache func NewTranslationCache(repo domain.LocalizationRepository) *TranslationCache { cache := &TranslationCache{ repo: repo, cache: make(map[string]map[string]string), commonTerms: make(map[string]map[string]string), } cache.initCommonTerms() return cache } // initCommonTerms initializes common translation terms that appear frequently func (tc *TranslationCache) initCommonTerms() { // Common materials tc.commonTerms["кирпич"] = map[string]string{ "en": "brick", "tt": "кирпич", // Keep same for Tatar or translate if needed } // Common architectural styles tc.commonTerms["модерн"] = map[string]string{ "en": "modern", "tt": "модерн", } tc.commonTerms["эклектика"] = map[string]string{ "en": "eclecticism", "tt": "эклектика", } // Common building types tc.commonTerms["доходный дом"] = map[string]string{ "en": "income-producing house", "tt": "доходный дом", } tc.commonTerms["мельница"] = map[string]string{ "en": "mill", "tt": "тегермән", } // Common roles tc.commonTerms["купец"] = map[string]string{ "en": "merchant", "tt": "сәүдәгәр", } tc.commonTerms["архитектор"] = map[string]string{ "en": "architect", "tt": "архитектор", } } // GetCachedTranslation checks cache and database for existing translation // Returns (translation, found, error) func (tc *TranslationCache) GetCachedTranslation(ctx context.Context, russianText, targetLocale, entityType, field string) (string, bool, error) { if russianText == "" { return "", false, nil } // Normalize text for cache lookup (trim whitespace, lowercase for common terms) normalized := strings.TrimSpace(russianText) // Check in-memory cache first tc.mu.RLock() if localeMap, ok := tc.cache[normalized]; ok { if translation, found := localeMap[targetLocale]; found && translation != "" { tc.mu.RUnlock() return translation, true, nil } } tc.mu.RUnlock() // Check common terms (exact match) if localeMap, ok := tc.commonTerms[normalized]; ok { if translation, found := localeMap[targetLocale]; found && translation != "" { // Cache it tc.mu.Lock() if tc.cache[normalized] == nil { tc.cache[normalized] = make(map[string]string) } tc.cache[normalized][targetLocale] = translation tc.mu.Unlock() return translation, true, nil } } // Check database for existing translation of the same Russian text // Search for any entity with the same Russian source text translated to target locale existing, err := tc.findExistingTranslation(ctx, normalized, targetLocale, entityType, field) if err == nil && existing != "" { // Cache it tc.mu.Lock() if tc.cache[normalized] == nil { tc.cache[normalized] = make(map[string]string) } tc.cache[normalized][targetLocale] = existing tc.mu.Unlock() return existing, true, nil } return "", false, nil } // findExistingTranslation searches the database for an existing translation // by looking for the same Russian text pattern in source entities func (tc *TranslationCache) findExistingTranslation(ctx context.Context, russianText, targetLocale, entityType, field string) (string, error) { if russianText == "" { return "", nil } normalized := strings.TrimSpace(russianText) if normalized == "" { return "", nil } // Search for Russian localizations with matching text ruLocalizations, err := tc.repo.SearchLocalizations(ctx, normalized, "ru", 10) if err != nil { return "", err } // Find a matching Russian localization and check for target locale translation for _, ruLoc := range ruLocalizations { if ruLoc.EntityType == entityType && ruLoc.Field == field && ruLoc.Value == normalized { // Found matching Russian text, check for target locale translation targetLoc, err := tc.repo.GetByEntityAndField(ctx, ruLoc.EntityType, ruLoc.EntityID, ruLoc.Field, targetLocale) if err == nil && targetLoc != nil && targetLoc.Value != "" { return targetLoc.Value, nil } } } return "", nil } // SetCachedTranslation stores a translation in the cache func (tc *TranslationCache) SetCachedTranslation(russianText, targetLocale, translation string) { if russianText == "" || translation == "" { return } normalized := strings.TrimSpace(russianText) tc.mu.Lock() defer tc.mu.Unlock() if tc.cache[normalized] == nil { tc.cache[normalized] = make(map[string]string) } tc.cache[normalized][targetLocale] = translation } // ClearCache clears the in-memory cache func (tc *TranslationCache) ClearCache() { tc.mu.Lock() defer tc.mu.Unlock() tc.cache = make(map[string]map[string]string) } // PreloadCache preloads common translations from the database func (tc *TranslationCache) PreloadCache(ctx context.Context, entityType, field, targetLocale string) error { // Get all localizations for this entity type and locale localizations, err := tc.repo.GetByEntityTypeAndLocale(ctx, entityType, targetLocale) if err != nil { return fmt.Errorf("failed to get localizations: %w", err) } // Filter by field and cache translations with their Russian source for _, loc := range localizations { if loc.Field == field { // Get the Russian source text for this entity/field ruLoc, err := tc.repo.GetByEntityAndField(ctx, loc.EntityType, loc.EntityID, loc.Field, "ru") if err == nil && ruLoc != nil && ruLoc.Value != "" { // Cache the translation with its Russian source tc.SetCachedTranslation(ruLoc.Value, targetLocale, loc.Value) } } } return nil }