package handler import ( "bugulma/backend/internal/service" "fmt" "net/http" "github.com/gin-gonic/gin" ) // I18nHandler handles i18n API requests type I18nHandler struct { i18nService *service.I18nService } // NewI18nHandler creates a new i18n handler func NewI18nHandler(i18nService *service.I18nService) *I18nHandler { return &I18nHandler{ i18nService: i18nService, } } // GetUITranslations returns UI translations for a locale // @Summary Get UI translations // @Tags i18n // @Produce json // @Param locale path string true "Locale code (en, tt)" // @Success 200 {object} map[string]string // @Router /api/i18n/ui/{locale} [get] func (h *I18nHandler) GetUITranslations(c *gin.Context) { locale := c.Param("locale") if !h.i18nService.ValidateLocale(locale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid locale"}) return } ctx := c.Request.Context() // Get all UI translations for this locale from the database // UI translations are stored with entityType="ui", entityID="ui" localizations, err := h.i18nService.GetUITranslationsForLocale(ctx, locale) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to load UI translations: %v", err)}) return } c.JSON(http.StatusOK, gin.H{ "locale": locale, "translations": localizations, }) } // GetDataTranslation returns a data translation // @Summary Get data translation // @Tags i18n // @Produce json // @Param entityType path string true "Entity type" // @Param entityID path string true "Entity ID" // @Param field path string true "Field name" // @Param locale query string true "Locale code" // @Success 200 {object} map[string]string // @Router /api/i18n/data/{entityType}/{entityID}/{field} [get] func (h *I18nHandler) GetDataTranslation(c *gin.Context) { entityType := c.Param("entityType") entityID := c.Param("entityID") field := c.Param("field") locale := c.Query("locale") if locale == "" { locale = service.DefaultLocale } if !h.i18nService.ValidateLocale(locale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid locale"}) return } // GetDataTranslation returns only a string (no error), so we need to handle it differently // For now, we'll need the source text as fallback - this should come from the entity // In a real scenario, you'd fetch the entity first to get the source text translated := h.i18nService.GetDataTranslation( c.Request.Context(), entityType, entityID, field, locale, "", // Fallback would need to be provided from entity source ) c.JSON(http.StatusOK, gin.H{ "entity_type": entityType, "entity_id": entityID, "field": field, "locale": locale, "value": translated, }) } // GetSupportedLocales returns all supported locales // @Summary Get supported locales // @Tags i18n // @Produce json // @Success 200 {object} map[string]interface{} // @Router /api/i18n/locales [get] func (h *I18nHandler) GetSupportedLocales(c *gin.Context) { locales, err := h.i18nService.GetSupportedLocales(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "locales": locales, "default_locale": service.DefaultLocale, "supported": service.SupportedLocales, }) } // GetTranslationStats returns translation statistics // @Summary Get translation statistics // @Tags i18n // @Produce json // @Param entityType query string false "Entity type filter" // @Success 200 {object} map[string]interface{} // @Router /api/i18n/stats [get] func (h *I18nHandler) GetTranslationStats(c *gin.Context) { entityType := c.Query("entityType") stats, err := h.i18nService.GetTranslationStats(c.Request.Context(), entityType) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, stats) } // Admin endpoints for i18n management // UpdateUITranslation updates a UI translation (admin only) // @Summary Update UI translation // @Tags admin // @Accept json // @Produce json // @Param locale path string true "Locale code" // @Param key path string true "Translation key" // @Param request body UpdateUITranslationRequest true "Translation value" // @Success 200 {object} map[string]string // @Router /api/v1/admin/i18n/ui/:locale/:key [put] func (h *I18nHandler) UpdateUITranslation(c *gin.Context) { locale := c.Param("locale") key := c.Param("key") if !h.i18nService.ValidateLocale(locale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid locale"}) return } var req UpdateUITranslationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.i18nService.SetUITranslation(c.Request.Context(), key, locale, req.Value); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Translation updated"}) } type UpdateUITranslationRequest struct { Value string `json:"value" binding:"required"` } // BulkUpdateUITranslations updates multiple UI translations (admin only) // @Summary Bulk update UI translations // @Tags admin // @Accept json // @Produce json // @Param request body BulkUpdateUITranslationsRequest true "Bulk update request" // @Success 200 {object} map[string]string // @Router /api/v1/admin/i18n/ui/bulk-update [post] func (h *I18nHandler) BulkUpdateUITranslations(c *gin.Context) { var req BulkUpdateUITranslationsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updates := make(map[string]map[string]string) // locale -> key -> value for _, update := range req.Updates { if updates[update.Locale] == nil { updates[update.Locale] = make(map[string]string) } updates[update.Locale][update.Key] = update.Value } for locale, keys := range updates { if !h.i18nService.ValidateLocale(locale) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid locale: %s", locale)}) return } for key, value := range keys { if err := h.i18nService.SetUITranslation(c.Request.Context(), key, locale, value); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update %s:%s: %v", locale, key, err)}) return } } } c.JSON(http.StatusOK, gin.H{"message": "Translations updated"}) } type BulkUpdateUITranslationsRequest struct { Updates []TranslationUpdate `json:"updates" binding:"required"` } type TranslationUpdate struct { Locale string `json:"locale" binding:"required"` Key string `json:"key" binding:"required"` Value string `json:"value" binding:"required"` } // AutoTranslateMissing auto-translates missing UI keys (admin only) // @Summary Auto-translate missing keys // @Tags admin // @Accept json // @Produce json // @Param request body AutoTranslateRequest true "Auto-translate request" // @Success 200 {object} map[string]interface{} // @Router /api/v1/admin/i18n/ui/auto-translate [post] func (h *I18nHandler) AutoTranslateMissing(c *gin.Context) { var req AutoTranslateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if !h.i18nService.ValidateLocale(req.TargetLocale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid target locale"}) return } // Get all UI keys for source locale sourceTranslations, err := h.i18nService.GetUITranslationsForLocale(c.Request.Context(), req.SourceLocale) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Get existing target translations targetTranslations, err := h.i18nService.GetUITranslationsForLocale(c.Request.Context(), req.TargetLocale) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Find missing keys var keysToTranslate []string for key := range sourceTranslations { if _, exists := targetTranslations[key]; !exists { keysToTranslate = append(keysToTranslate, key) } } if len(keysToTranslate) == 0 { c.JSON(http.StatusOK, gin.H{"message": "No missing translations", "translated": 0}) return } // Batch translate translated, err := h.i18nService.BatchTranslateUI(c.Request.Context(), keysToTranslate, req.SourceLocale, req.TargetLocale) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "message": "Translations completed", "translated": len(translated), "results": translated, }) } type AutoTranslateRequest struct { SourceLocale string `json:"sourceLocale" binding:"required"` TargetLocale string `json:"targetLocale" binding:"required"` } // GetTranslationKeys returns all translation keys with status (admin only) // @Summary Get translation keys with status // @Tags admin // @Produce json // @Param locale path string true "Locale code" // @Success 200 {object} map[string]interface{} // @Router /api/v1/admin/i18n/ui/:locale/keys [get] func (h *I18nHandler) GetTranslationKeys(c *gin.Context) { locale := c.Param("locale") if !h.i18nService.ValidateLocale(locale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid locale"}) return } // Get source locale (default: ru) sourceLocale := service.DefaultLocale sourceTranslations, err := h.i18nService.GetUITranslationsForLocale(c.Request.Context(), sourceLocale) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Get target locale translations targetTranslations, err := h.i18nService.GetUITranslationsForLocale(c.Request.Context(), locale) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Build keys with status keys := make([]map[string]interface{}, 0) for key, sourceValue := range sourceTranslations { status := "missing" value := "" if targetValue, exists := targetTranslations[key]; exists { status = "translated" value = targetValue } keys = append(keys, map[string]interface{}{ "key": key, "source": sourceValue, "value": value, "status": status, }) } c.JSON(http.StatusOK, gin.H{ "locale": locale, "keys": keys, "total": len(keys), "translated": len(targetTranslations), "missing": len(sourceTranslations) - len(targetTranslations), }) } // UpdateDataTranslation updates a data translation (admin only) // @Summary Update data translation // @Tags admin // @Accept json // @Produce json // @Param entityType path string true "Entity type" // @Param entityID path string true "Entity ID" // @Param field path string true "Field name" // @Param locale path string true "Locale code" // @Param request body UpdateDataTranslationRequest true "Translation value" // @Success 200 {object} map[string]string // @Router /api/v1/admin/i18n/data/:entityType/:entityID/:field/:locale [put] func (h *I18nHandler) UpdateDataTranslation(c *gin.Context) { entityType := c.Param("entityType") entityID := c.Param("entityID") field := c.Param("field") locale := c.Param("locale") if !h.i18nService.ValidateLocale(locale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid locale"}) return } var req UpdateDataTranslationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.i18nService.SetDataTranslation(c.Request.Context(), entityType, entityID, field, locale, req.Value); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Translation updated"}) } type UpdateDataTranslationRequest struct { Value string `json:"value" binding:"required"` } // BulkTranslateData bulk translates entities (admin only) // @Summary Bulk translate data // @Tags admin // @Accept json // @Produce json // @Param request body BulkTranslateDataRequest true "Bulk translate request" // @Success 200 {object} map[string]interface{} // @Router /api/v1/admin/i18n/data/bulk-translate [post] func (h *I18nHandler) BulkTranslateData(c *gin.Context) { var req BulkTranslateDataRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if !h.i18nService.ValidateLocale(req.TargetLocale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid target locale"}) return } // TODO: Implement bulk data translation c.JSON(http.StatusNotImplemented, gin.H{"error": "Bulk data translation not yet implemented"}) } type BulkTranslateDataRequest struct { EntityType string `json:"entityType" binding:"required"` EntityIDs []string `json:"entityIDs" binding:"required"` TargetLocale string `json:"targetLocale" binding:"required"` Fields []string `json:"fields"` } // GetMissingTranslations returns entities with missing translations (admin only) // @Summary Get entities with missing translations // @Tags admin // @Produce json // @Param entityType path string true "Entity type" // @Param locale query string true "Locale code" // @Success 200 {object} map[string]interface{} // @Router /api/v1/admin/i18n/data/:entityType/missing [get] func (h *I18nHandler) GetMissingTranslations(c *gin.Context) { _ = c.Param("entityType") // Entity type - for future use locale := c.Query("locale") if locale == "" { locale = service.DefaultLocale } if !h.i18nService.ValidateLocale(locale) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid locale"}) return } // TODO: Implement missing translations detection c.JSON(http.StatusNotImplemented, gin.H{"error": "Missing translations detection not yet implemented"}) }