mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
227 lines
6.8 KiB
Go
227 lines
6.8 KiB
Go
package service
|
|
|
|
import (
|
|
"bugulma/backend/internal/domain"
|
|
"context"
|
|
"crypto/md5"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TranslationCacheEntry represents a cached translation
|
|
type TranslationCacheEntry struct {
|
|
EntityType string
|
|
Field string
|
|
TargetLocale string
|
|
RussianText string
|
|
Translated string
|
|
UseCount int
|
|
LastUsed int64
|
|
}
|
|
|
|
// TranslationCacheService manages translation caching and reuse
|
|
type TranslationCacheService struct {
|
|
cache map[string]*TranslationCacheEntry
|
|
locRepo domain.LocalizationRepository
|
|
locService domain.LocalizationService
|
|
mutex sync.RWMutex
|
|
maxSize int
|
|
}
|
|
|
|
// NewTranslationCacheService creates a new translation cache service
|
|
func NewTranslationCacheService(locRepo domain.LocalizationRepository, locService domain.LocalizationService) *TranslationCacheService {
|
|
return &TranslationCacheService{
|
|
cache: make(map[string]*TranslationCacheEntry),
|
|
locRepo: locRepo,
|
|
locService: locService,
|
|
maxSize: 10000, // Configurable cache size
|
|
}
|
|
}
|
|
|
|
// FindCachedTranslation finds a cached translation for the given parameters
|
|
// Returns (translation, found, error) for consistency with TranslationCache API
|
|
func (s *TranslationCacheService) FindCachedTranslation(ctx context.Context, entityType, field, targetLocale, russianText string) (string, bool, error) {
|
|
if russianText == "" {
|
|
return "", false, nil
|
|
}
|
|
|
|
s.mutex.RLock()
|
|
key := s.generateCacheKey(entityType, field, targetLocale, russianText)
|
|
if entry, exists := s.cache[key]; exists {
|
|
entry.UseCount++
|
|
entry.LastUsed = time.Now().Unix()
|
|
s.mutex.RUnlock()
|
|
return entry.Translated, true, nil
|
|
}
|
|
s.mutex.RUnlock()
|
|
|
|
// Try to find in database (slower but persistent)
|
|
if translation := s.findInDatabase(ctx, entityType, field, targetLocale, russianText); translation != "" {
|
|
// Add to cache
|
|
s.AddToCache(entityType, field, targetLocale, russianText, translation)
|
|
return translation, true, nil
|
|
}
|
|
|
|
return "", false, nil
|
|
}
|
|
|
|
// FindCachedTranslationLegacy is the legacy API for backward compatibility
|
|
// Deprecated: Use FindCachedTranslation with context instead
|
|
func (s *TranslationCacheService) FindCachedTranslationLegacy(entityType, field, targetLocale, russianText string) string {
|
|
ctx := context.Background()
|
|
translation, found, _ := s.FindCachedTranslation(ctx, entityType, field, targetLocale, russianText)
|
|
if found {
|
|
return translation
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// AddToCache adds a translation to the cache
|
|
func (s *TranslationCacheService) AddToCache(entityType, field, targetLocale, russianText, translated string) {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
s.addToCache(entityType, field, targetLocale, russianText, translated)
|
|
}
|
|
|
|
// addToCache internal method to add to cache (assumes mutex is held)
|
|
func (s *TranslationCacheService) addToCache(entityType, field, targetLocale, russianText, translated string) {
|
|
key := s.generateCacheKey(entityType, field, targetLocale, russianText)
|
|
entry := &TranslationCacheEntry{
|
|
EntityType: entityType,
|
|
Field: field,
|
|
TargetLocale: targetLocale,
|
|
RussianText: russianText,
|
|
Translated: translated,
|
|
UseCount: 1,
|
|
LastUsed: time.Now().Unix(),
|
|
}
|
|
|
|
// Evict least recently used if cache is full
|
|
if len(s.cache) >= s.maxSize {
|
|
s.evictLRU()
|
|
}
|
|
|
|
s.cache[key] = entry
|
|
}
|
|
|
|
// findInDatabase searches the database for existing translations of the same Russian text
|
|
func (s *TranslationCacheService) findInDatabase(ctx context.Context, entityType, field, targetLocale, russianText string) string {
|
|
if russianText == "" {
|
|
return ""
|
|
}
|
|
|
|
normalized := strings.TrimSpace(russianText)
|
|
if normalized == "" {
|
|
return ""
|
|
}
|
|
|
|
// Search for localizations with matching Russian source text
|
|
localizations, err := s.locRepo.SearchLocalizations(ctx, normalized, "ru", 10)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Find a matching Russian localization and check if it has a target locale translation
|
|
for _, loc := range localizations {
|
|
if loc.EntityType == entityType && loc.Field == field && loc.Value == normalized {
|
|
// Found matching Russian text, now check for target locale translation
|
|
targetLoc, err := s.locRepo.GetByEntityAndField(ctx, loc.EntityType, loc.EntityID, loc.Field, targetLocale)
|
|
if err == nil && targetLoc != nil && targetLoc.Value != "" {
|
|
return targetLoc.Value
|
|
}
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// generateCacheKey generates a unique key for cache entries
|
|
func (s *TranslationCacheService) generateCacheKey(entityType, field, targetLocale, russianText string) string {
|
|
// Create a hash of the Russian text to handle long texts
|
|
textHash := fmt.Sprintf("%x", md5.Sum([]byte(russianText)))
|
|
return fmt.Sprintf("%s:%s:%s:%s", entityType, field, targetLocale, textHash)
|
|
}
|
|
|
|
// evictLRU evicts the least recently used entry from the cache
|
|
func (s *TranslationCacheService) evictLRU() {
|
|
var oldestKey string
|
|
var oldestTime int64 = -1
|
|
|
|
for key, entry := range s.cache {
|
|
if oldestTime == -1 || entry.LastUsed < oldestTime {
|
|
oldestTime = entry.LastUsed
|
|
oldestKey = key
|
|
}
|
|
}
|
|
|
|
if oldestKey != "" {
|
|
delete(s.cache, oldestKey)
|
|
}
|
|
}
|
|
|
|
// GetCacheStats returns statistics about the cache
|
|
func (s *TranslationCacheService) GetCacheStats() map[string]interface{} {
|
|
s.mutex.RLock()
|
|
defer s.mutex.RUnlock()
|
|
|
|
totalEntries := len(s.cache)
|
|
totalUses := 0
|
|
avgUses := 0.0
|
|
|
|
if totalEntries > 0 {
|
|
for _, entry := range s.cache {
|
|
totalUses += entry.UseCount
|
|
}
|
|
avgUses = float64(totalUses) / float64(totalEntries)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"total_entries": totalEntries,
|
|
"max_size": s.maxSize,
|
|
"total_uses": totalUses,
|
|
"avg_uses": avgUses,
|
|
"utilization": float64(totalEntries) / float64(s.maxSize) * 100,
|
|
}
|
|
}
|
|
|
|
// ClearCache clears all entries from the cache
|
|
func (s *TranslationCacheService) ClearCache() {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
s.cache = make(map[string]*TranslationCacheEntry)
|
|
}
|
|
|
|
// PreloadCache preloads the cache with commonly used translations
|
|
func (s *TranslationCacheService) PreloadCache(ctx context.Context, entityType, field, targetLocale string) error {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
// Get translation reuse candidates (Russian text that appears in multiple entities)
|
|
candidates, err := s.locRepo.GetTranslationReuseCandidates(ctx, entityType, field, "ru")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get reuse candidates: %w", err)
|
|
}
|
|
|
|
// For each candidate, find existing translations and cache them
|
|
for _, candidate := range candidates {
|
|
// Find any translation of this Russian text to the target locale
|
|
localizations, err := s.locRepo.SearchLocalizations(ctx, candidate.RussianValue, targetLocale, 1)
|
|
if err != nil || len(localizations) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Use the first matching translation
|
|
translation := localizations[0].Value
|
|
if translation != "" {
|
|
s.AddToCache(entityType, field, targetLocale, candidate.RussianValue, translation)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|