mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
445 lines
14 KiB
Go
445 lines
14 KiB
Go
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"})
|
|
}
|