mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
503 lines
15 KiB
Go
503 lines
15 KiB
Go
package handler
|
|
|
|
import (
|
|
"bugulma/backend/internal/service"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"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
|
|
}
|
|
|
|
// Perform bulk translation using service
|
|
results, err := h.i18nService.BulkTranslateData(c.Request.Context(), req.EntityType, req.EntityIDs, req.TargetLocale, req.Fields)
|
|
if err != nil {
|
|
// Return partial results if possible
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error(), "results": results})
|
|
return
|
|
}
|
|
|
|
// Compute translated count
|
|
count := 0
|
|
for _, fields := range results {
|
|
count += len(fields)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Bulk data translation completed",
|
|
"translated": count,
|
|
"results": results,
|
|
})
|
|
}
|
|
|
|
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) {
|
|
entityType := c.Param("entityType")
|
|
locale := c.Query("locale")
|
|
fieldsParam := c.Query("fields") // comma-separated list of fields
|
|
limitParam := c.DefaultQuery("limit", "100")
|
|
|
|
if locale == "" {
|
|
// default to English as target locale for missing translations
|
|
locale = "en"
|
|
}
|
|
|
|
if !h.i18nService.ValidateLocale(locale) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid locale"})
|
|
return
|
|
}
|
|
|
|
// Parse fields
|
|
var fields []string
|
|
if fieldsParam != "" {
|
|
for _, f := range strings.Split(fieldsParam, ",") {
|
|
f = strings.TrimSpace(f)
|
|
if f != "" {
|
|
fields = append(fields, f)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse limit
|
|
limit := 100
|
|
if lp, err := strconv.Atoi(limitParam); err == nil && lp > 0 {
|
|
limit = lp
|
|
}
|
|
|
|
results, err := h.i18nService.GetMissingTranslations(c.Request.Context(), entityType, locale, fields, limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Build summary
|
|
counts := make(map[string]int)
|
|
total := 0
|
|
for field, ids := range results {
|
|
counts[field] = len(ids)
|
|
total += len(ids)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"entity_type": entityType,
|
|
"locale": locale,
|
|
"total": total,
|
|
"counts": counts,
|
|
"results": results,
|
|
})
|
|
}
|