Skip to content

Frontend Translation Integration Guide

This guide explains how to integrate Category and Attribute translations into the admin dashboard and store dashboard frontends.

Overview

The API now supports multi-language translations for Categories and Attributes. Each category and attribute can have translations in 4 languages: - Azerbaijani (name_az) - English (name_en) - Spanish (name_es) - German (name_de)

API Changes Summary

Categories

Create/Update Endpoints: - POST /v1/products/category/ - PUT /v1/products/category/?id={id}

New Form Fields: - name_en (required, stored as fallback name) - name_az (optional) - name_es (optional) - name_de (optional) - name (backward compatibility, optional; API writes name_en into it)

Response Format: All GET endpoints now return translations:

{
  "id": 1,
  "name": "Elektronika",  // Backward compatibility
  "name_az": "Elektronika",
  "name_en": "Electronics",
  "name_es": "Electrónica",
  "name_de": "Elektronik",
  "image": "https://...",
  "parent_id": null
}

Attributes

Create/Update Endpoints: - POST /v1/products/attribute/ - PUT /v1/products/attribute/?id={id}

New Form Fields: - name_en (required, stored as fallback name) - name_az (optional) - name_es (optional) - name_de (optional) - name (backward compatibility, optional; API writes name_en into it)

Response Format: All GET endpoints now return translations:

{
  "id": 1,
  "name": "Rəng",  // Backward compatibility
  "name_az": "Rəng",
  "name_en": "Color",
  "name_es": "Color",
  "name_de": "Farbe",
  "type": "select",
  ...
}

Admin Dashboard Integration

1. Update Category Form Component

File: frontend/admin-dashboard/src/pages/categories/Categories.tsx

Changes Needed:

  1. Update formData state:

    const [formData, setFormData] = useState({
      name: "",        // Backward compatibility (API will fill from name_en)
      name_en: "",     // Required (stored as fallback name)
      name_az: "",
      name_es: "",
      name_de: "",
      image: null as File | null,
      preview: "" as string,
    });
    

  2. Add translation input fields in the form:

    <Input
      label="Name (English) *"
      value={formData.name_en}
      onChange={(e) => setFormData({ ...formData, name_en: e.target.value })}
      error={errors.name_en}
      required
    />
    
    <Input
      label="Name (Azerbaijani)"
      value={formData.name_az}
      onChange={(e) => setFormData({ ...formData, name_az: e.target.value })}
    />
    
    <Input
      label="Name (Spanish)"
      value={formData.name_es}
      onChange={(e) => setFormData({ ...formData, name_es: e.target.value })}
    />
    
    <Input
      label="Name (German)"
      value={formData.name_de}
      onChange={(e) => setFormData({ ...formData, name_de: e.target.value })}
    />
    

  3. Update handleSubmit function:

    const handleSubmit = async () => {
      if (!validateForm()) return;
      try {
        const data = new FormData();
    
      // English is required and becomes the fallback name on the API
      data.append("name_en", formData.name_en);
      if (formData.name_az) data.append("name_az", formData.name_az);
        if (formData.name_es) data.append("name_es", formData.name_es);
        if (formData.name_de) data.append("name_de", formData.name_de);
    
      // Optional: keep name field for backward compatibility (API fills from name_en anyway)
      if (formData.name) data.append("name", formData.name);
    
        if (formData.image) data.append("image", formData.image);
        if (parentCategory) {
          data.append("parent_id", parentCategory.id.toString());
        }
    
        if (editId) {
          await CategorisService.updateCategory(editId, data);
        } else {
          await CategorisService.createCategory(data);
        }
    
        setIsVisible(false);
        setFormData({ 
          name: "", 
          name_az: "", 
          name_en: "", 
          name_es: "", 
          name_de: "",
          image: null, 
          preview: "" 
        });
        setEditId(null);
        getData();
      } catch (error) {
        console.error(error);
      }
    };
    

  4. Update handleEdit function to populate translations:

    const handleEdit = async (id: number) => {
      try {
        const res = await CategorisService.getSingleCategory(id);
        const category = res.data[0]; // API returns tree structure
    
        setFormData({
          name: category.name || "",
          name_az: category.name_az || category.name || "",
          name_en: category.name_en || "",
          name_es: category.name_es || "",
          name_de: category.name_de || "",
          image: null,
          preview: category.image || "",
        });
        setEditId(id);
        setIsVisible(true);
      } catch (error) {
        console.error(error);
      }
    };
    

  5. Update validation:

    const validateForm = (): boolean => {
      const newErrors: Partial<Errors> = {};
      if (!formData.name_az?.trim() && !formData.name?.trim()) {
        newErrors.name_az = "Azerbaijani name is required";
      }
      setErrors(newErrors);
      return Object.keys(newErrors).length === 0;
    };
    

2. Update Attribute Form Component

File: frontend/admin-dashboard/src/pages/attributes/Attributes.tsx

Changes Needed:

  1. Update formData state:

    const [formData, setFormData] = useState<{
      name: string;        // Backward compatibility
      name_az: string;     // Required
      name_en: string;     // Optional
      name_es: string;     // Optional
      name_de: string;     // Optional
      is_filter: boolean;
      is_view: boolean;
      category_id: number | null;
      options_api?: string;
      values: { id?: number; name: string }[];
    }>({
      name: "",
      name_az: "",
      name_en: "",
      name_es: "",
      name_de: "",
      is_filter: false,
      is_view: false,
      category_id: null,
      options_api: "",
      values: [{ name: "" }],
    });
    

  2. Add translation input fields in the form:

    <Input
      label="Name (Azerbaijani) *"
      value={formData.name_az}
      onChange={(e) => setFormData({ ...formData, name_az: e.target.value })}
      error={errors.name_az}
      required
    />
    
    <Input
      label="Name (English)"
      value={formData.name_en}
      onChange={(e) => setFormData({ ...formData, name_en: e.target.value })}
    />
    
    <Input
      label="Name (Spanish)"
      value={formData.name_es}
      onChange={(e) => setFormData({ ...formData, name_es: e.target.value })}
    />
    
    <Input
      label="Name (German)"
      value={formData.name_de}
      onChange={(e) => setFormData({ ...formData, name_de: e.target.value })}
    />
    

  3. Update handleSubmit function:

    const handleSubmit = async () => {
      if (!validateForm()) return;
    
      try {
        const data = new FormData();
    
        if (editId && originalData) {
          // Update mode - only send changed fields
          if (formData.name_az !== originalData.name_az)
            data.append("name_az", formData.name_az);
          if (formData.name_en !== originalData.name_en)
            data.append("name_en", formData.name_en);
          if (formData.name_es !== originalData.name_es)
            data.append("name_es", formData.name_es);
          if (formData.name_de !== originalData.name_de)
            data.append("name_de", formData.name_de);
    
          // Other fields...
          if (formData.is_filter !== originalData.is_filter)
            data.append("is_filter", String(formData.is_filter));
          // ... rest of the fields
        } else {
          // Create mode
          data.append("name_az", formData.name_az);
          if (formData.name_en) data.append("name_en", formData.name_en);
          if (formData.name_es) data.append("name_es", formData.name_es);
          if (formData.name_de) data.append("name_de", formData.name_de);
    
          // Other fields...
          data.append("is_filter", String(formData.is_filter));
          // ... rest of the fields
        }
    
        if (editId) {
          await AttributesService.updateData(editId, data);
        } else {
          await AttributesService.createOne(data);
        }
    
        setIsVisible(false);
        setEditId(null);
        setFormData({
          name: "",
          name_az: "",
          name_en: "",
          name_es: "",
          name_de: "",
          category_id: null,
          options_api: "",
          values: [{ name: "" }],
          is_filter: false,
          is_view: false,
        });
        getData();
      } catch (error) {
        console.error(error);
      }
    };
    

  4. Update handleEdit function:

    const handleEdit = async (id: number) => {
      try {
        const res = await AttributesService.getById(id);
        const attribute = res.data;
    
        setOriginalData(attribute);
        setFormData({
          name: attribute.name || "",
          name_az: attribute.name_az || attribute.name || "",
          name_en: attribute.name_en || "",
          name_es: attribute.name_es || "",
          name_de: attribute.name_de || "",
          is_filter: attribute.is_filter || false,
          is_view: attribute.is_view || false,
          category_id: attribute.category_id || null,
          options_api: attribute.options_api || "",
          values: attribute.values || [{ name: "" }],
        });
        setEditId(id);
        setIsVisible(true);
      } catch (error) {
        console.error(error);
      }
    };
    

  5. Update validation:

    const validateForm = (): boolean => {
      const newErrors: Partial<Errors> = {};
      if (!formData.name_az?.trim() && !formData.name?.trim()) {
        newErrors.name_az = "Azerbaijani name is required";
      }
      if (!formData.category_id) {
        newErrors.category_id = "Category is required";
      }
      setErrors(newErrors);
      return Object.keys(newErrors).length === 0;
    };
    

3. Update TypeScript Types

File: frontend/admin-dashboard/src/api/types/products.types.ts

Add translation fields:

export interface CategoriesTypes {
  id: number;
  name: string;        // Backward compatibility
  name_az: string;
  name_en?: string;
  name_es?: string;
  name_de?: string;
  image: string;
  parent_id: number | null;
  children: CategoriesTypes[];
}

export interface AttributesTypes {
  id: number;
  name: string;        // Backward compatibility
  name_az: string;
  name_en?: string;
  name_es?: string;
  name_de?: string;
  type: string;
  values: Array<{ name: string }>;
  options_api?: string;
  category_id: number;
  category: CategoriesTypes;
  is_filter: boolean;
  is_view: boolean;
}

Store Dashboard Integration

Store owners typically don't create categories or attributes, but they may need to view them. If your store dashboard displays categories or attributes, update the display logic to use the appropriate translation based on the user's language preference.

Example:

const getCategoryName = (category: CategoriesTypes, language: string): string => {
  switch (language) {
    case "az": return category.name_az || category.name;
    case "en": return category.name_en || category.name_az || category.name;
    case "es": return category.name_es || category.name_en || category.name_az || category.name;
    case "de": return category.name_de || category.name_en || category.name_az || category.name;
    default: return category.name_az || category.name;
  }
};

Backward Compatibility

The API maintains backward compatibility: - If only name is provided, it will be used as the default for all languages - If name_az is provided, it takes precedence over name - The name field is still returned in all responses for backward compatibility - Existing categories/attributes without translations will use the name field as fallback

Testing Checklist

  • [ ] Create category with all 4 translations
  • [ ] Create category with only name_az (should work)
  • [ ] Create category with only name (should work, backward compatibility)
  • [ ] Update category translations individually
  • [ ] View category and verify all translations are returned
  • [ ] Create attribute with all 4 translations
  • [ ] Update attribute translations
  • [ ] Verify existing categories/attributes still work (backward compatibility)
  • [ ] Test form validation (name_az required)

UI/UX Recommendations

  1. Tab-based Translation Input: Consider using tabs or accordion to organize translation inputs:

    <Tabs>
      <Tab label="Azerbaijani (AZ)" required>
        <Input value={formData.name_az} ... />
      </Tab>
      <Tab label="English (EN)">
        <Input value={formData.name_en} ... />
      </Tab>
      <Tab label="Spanish (ES)">
        <Input value={formData.name_es} ... />
      </Tab>
      <Tab label="German (DE)">
        <Input value={formData.name_de} ... />
      </Tab>
    </Tabs>
    

  2. Auto-translation (Optional): You could add a button to auto-translate from Azerbaijani to other languages using a translation service, similar to how Product Variants work.

  3. Display Current Language: When viewing categories/attributes in lists, display the name in the user's current language preference.

Migration Notes

  • Existing categories and attributes will continue to work
  • The name field will be used as fallback for all languages until translations are added
  • No database migration is required for existing data - translations are optional
  • You can gradually add translations to existing categories/attributes through the update endpoints