turash/bugulma/backend/internal/service/translation_cache.go

203 lines
6.2 KiB
Go

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
}