SoFunction
Updated on 2025-04-13

Golang+vue's complete guide to creating an efficient multilingual blog system

Backend part (Go)

1. First create a data model related to the article

package model

import (
	"/gorm"
)

// Article Main articletype Article struct {
	
	Status     uint8  `json:"status" gorm:"default:1"` // Status: 0-Disable 1-Enable	Sort       int    `json:"sort" gorm:"default:0"`   // Sort	AuthorId   uint   `json:"authorId"`                // Author ID	CategoryId uint   `json:"categoryId"`              // Classification ID	Thumbnail  string `json:"thumbnail"`               // Thumbnail	
	// Related	Translations []ArticleTranslation `json:"translations"`
	Category     ArticleCategory      `json:"category"`
}

// ArticleTranslation Article Translation Tabletype ArticleTranslation struct {
	
	ArticleId   uint   `json:"articleId"`
	Lang        string `json:"lang" gorm:"size:5"`        // Language codes such as: zh-CN, en-US	Title       string `json:"title"`                     // Title	Description string `json:"description"`               // describe	Content     string `json:"content" gorm:"type:text"`  // content	Keywords    string `json:"keywords"`                  // SEO keywords	Slug        string `json:"slug" gorm:"uniqueIndex"`   // URL friendly title}

// ArticleCategory Article Categorytype ArticleCategory struct {
	
	ParentId     uint   `json:"parentId"`
	Status       uint8  `json:"status" gorm:"default:1"`
	Sort         int    `json:"sort" gorm:"default:0"`
	Translations []ArticleCategoryTranslation `json:"translations"`
}

// ArticleCategoryTranslation Classification Translation Tabletype ArticleCategoryTranslation struct {
	
	CategoryId  uint   `json:"categoryId"`
	Lang        string `json:"lang" gorm:"size:5"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Slug        string `json:"slug" gorm:"uniqueIndex"`
}

2. Create a service layer

package service

import (
	"dagisku-server/app/model"
	"dagisku-server/global"
)

type ArticleService struct{}

type ArticleListRequest struct {
	Page     int    `json:"page" form:"page"`
	PageSize int    `json:"pageSize" form:"pageSize"`
	Lang     string `json:"lang" form:"lang"`
	Status   *uint8 `json:"status" form:"status"`
	CategoryId *uint `json:"categoryId" form:"categoryId"`
}

func (s *ArticleService) GetList(req ArticleListRequest) (list [], total int64, err error) {
	limit := 
	offset :=  * ( - 1)
	
	db := (&{})
	
	// Build query conditions	if  != nil {
		db = ("status = ?", *)
	}
	if  != nil {
		db = ("category_id = ?", *)
	}
	
	// Preload translation data	db = ("Translations", "lang = ?", )
	db = ("Category").Preload("", "lang = ?", )
	
	err = (&total).Error
	if err != nil {
		return
	}
	
	err = ("sort desc, id desc").Limit(limit).Offset(offset).Find(&list).Error
	return
}

func (s *ArticleService) Create(article *) error {
	return (article).Error
}

func (s *ArticleService) Update(article *) error {
	return (article).Error
}

func (s *ArticleService) Delete(id uint) error {
	return (&{}, id).Error
}

3. Create a controller

package article

import (
	"dagisku-server/app/model"
	"dagisku-server/app/service"
	"dagisku-server/utils/response"
	"/gin-gonic/gin"
)

type ArticleApi struct{}

// List Get the article listfunc (api *ArticleApi) List(c *) {
	var req 
	if err := (&req); err != nil {
		((), c)
		return
	}
	
	if  == 0 {
		 = 1
	}
	if  == 0 {
		 = 10
	}
	
	service := {}
	list, total, err := (req)
	if err != nil {
		((), c)
		return
	}
	
	({
		"list": list,
		"total": total,
	}, c)
}

// Create Create articlefunc (api *ArticleApi) Create(c *) {
	var article 
	if err := (&article); err != nil {
		((), c)
		return
	}
	
	service := {}
	if err := (&article); err != nil {
		((), c)
		return
	}
	
	(c)
}

// Update Update articlefunc (api *ArticleApi) Update(c *) {
	var article 
	if err := (&article); err != nil {
		((), c)
		return
	}
	
	service := {}
	if err := (&article); err != nil {
		((), c)
		return
	}
	
	(c)
}

// Delete Delete Articlefunc (api *ArticleApi) Delete(c *) {
	id := ("id")
	service := {}
	if err := (uint(id)); err != nil {
		((), c)
		return
	}
	
	(c)
}

4. Register a route

// ... existing code ...

type ApiGroup struct {
	LoginApi
	
	
	// ... other existing APIs ...
	
	
}

// ... existing code ...
func InitAdminRouter(Router *) {
	adminRouter := ("admin")
	
	// ... existing routes ...
	
	// Article related routes	articleRouter := ("article")
	{
		("list", )
		("create", )
		("update", )
		("delete/:id", )
	}
}

Front-end part (Nuxt 3)

1. Create API Request

import { useFetch } from '#app'

export const useArticleApi = () => {
  const config = useRuntimeConfig()
  const baseURL = 
  
  const getList = async (params: {
    page: number
    pageSize: number
    lang: string
    status?: number
    categoryId?: number
  }) => {
    return await useFetch('/admin/article/list', {
      baseURL,
      method: 'GET',
      params
    })
  }
  
  const create = async (data: any) => {
    return await useFetch('/admin/article/create', {
      baseURL,
      method: 'POST',
      body: data
    })
  }
  
  const update = async (data: any) => {
    return await useFetch('/admin/article/update', {
      baseURL,
      method: 'PUT',
      body: data
    })
  }
  
  const remove = async (id: number) => {
    return await useFetch(`/admin/article/delete/${id}`, {
      baseURL,
      method: 'DELETE'
    })
  }
  
  return {
    getList,
    create,
    update,
    remove
  }
}

2. Create an article list page

<template>
  <div>
    <el-card>
      <!-- Search bar -->
      <el-form :inline="true" :model="searchForm">
        <el-form-item>
          <el-select v-model="" placeholder="Select a language">
            <el-option label="Chinese" value="zh-CN" />
            <el-option label="English" value="en-US" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="loadData">search</el-button>
          <el-button @click="handleAdd">New</el-button>
        </el-form-item>
      </el-form>
      
      <!-- Data table -->
      <el-table :data="tableData" v-loading="loading">
        <el-table-column prop="id" label="ID" width="80" />
        <el-table-column label="title">
          <template #default="{ row }">
            {{ ?.[0]?.title }}
          </template>
        </el-table-column>
        <el-table-column label="Classification">
          <template #default="{ row }">
            {{ ?.translations?.[0]?.name }}
          </template>
        </el-table-column>
        <el-table-column prop="status" label="state">
          <template #default="{ row }">
            <el-tag :type=" === 1 ? 'success' : 'info'">
              {{  === 1 ? 'Enable' : 'Disable' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="operate" width="200">
          <template #default="{ row }">
            <el-button type="primary" link @click="handleEdit(row)">edit</el-button>
            <el-button type="danger" link @click="handleDelete(row)">delete</el-button>
          </template>
        </el-table-column>
      </el-table>
      
      <!-- Pagination -->
      <div class="pagination-container">
        <el-pagination
          v-model:current-page="page"
          v-model:page-size="pageSize"
          :total="total"
          @current-change="loadData"
        />
      </div>
    </el-card>
    
    <!-- edit弹窗 -->
    <el-dialog
      :title="dialogTitle"
      v-model="dialogVisible"
      width="800px"
    >
      <article-form
        v-if="dialogVisible"
        :form-data="formData"
        @submit="handleSubmit"
        @cancel="dialogVisible = false"
      />
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
const { getList, remove } = useArticleApi()

// Status definitionconst searchForm = ref({
  lang: 'zh-CN'
})
const loading = ref(false)
const tableData = ref([])
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formData = ref<any>({})

// Load dataconst loadData = async () => {
   = true
  try {
    const { data } = await getList({
      page: ,
      pageSize: ,
      lang: 
    })
     = 
     = 
  } finally {
     = false
  }
}

// Process newconst handleAdd = () => {
   = {
    status: 1,
    translations: [{ lang:  }]
  }
   = 'Added Article'
   = true
}

// Processing Editconst handleEdit = (row: any) => {
   = { ...row }
   = 'Edit article'
   = true
}

// Handle deletionconst handleDelete = async (row: any) => {
  try {
    await ('Confirm to delete the article?  ')
    await remove()
    ('Delete successfully')
    loadData()
  } catch (err) {
    // No error is displayed when canceling deletion    if (err !== 'cancel') {
      ('Delete failed')
    }
  }
}

// Process form submissionconst handleSubmit = async (data: any) => {
  try {
    if () {
      await update(data)
    } else {
      await create(data)
    }
    ('Save successfully')
     = false
    loadData()
  } catch (err) {
    ('Save failed')
  }
}

// Initial loadingonMounted(() => {
  loadData()
})
</script>

<style scoped>
.pagination-container {
  margin-top: 20px;
  text-align: right;
}
</style>

3. Create article form components

<template>
  <el-form
    ref="formRef"
    :model="form"
    :rules="rules"
    label-width="100px"
  >
    <el-tabs v-model="activeLang">
      <el-tab-pane
        v-for="lang in languages"
        :key=""
        :label=""
        :name=""
      >
        <el-form-item
          :prop="`translations.${getTransIndex()}.title`"
          label="title"
        >
          <el-input
            v-model="getTranslation().title"
            placeholder="Please enter the title"
          />
        </el-form-item>
        
        <el-form-item
          :prop="`translations.${getTransIndex()}.description`"
          label="describe"
        >
          <el-input
            type="textarea"
            v-model="getTranslation().description"
            placeholder="Please enter a description"
          />
        </el-form-item>
        
        <el-form-item
          :prop="`translations.${getTransIndex()}.content`"
          label="content"
        >
          <editor
            v-model="getTranslation().content"
            :height="400"
          />
        </el-form-item>
      </el-tab-pane>
    </el-tabs>
    
    <el-form-item label="Classification" prop="categoryId">
      <el-select v-model="">
        <el-option
          v-for="item in categories"
          :key=""
          :label="[0].name"
          :value=""
        />
      </el-select>
    </el-form-item>
    
    <el-form-item label="Thumbnail" prop="thumbnail">
      <upload v-model="" />
    </el-form-item>
    
    <el-form-item label="state" prop="status">
      <el-switch
        v-model=""
        :active-value="1"
        :inactive-value="0"
      />
    </el-form-item>
    
    <el-form-item>
      <el-button type="primary" @click="handleSubmit">save</el-button>
      <el-button @click="$emit('cancel')">Cancel</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
const props = defineProps<{
  formData: any
}>()

const emit = defineEmits(['submit', 'cancel'])

// Form dataconst form = ref({
  ...,
  translations:  || []
})

// Language configurationconst languages = [
  { label: 'Chinese', value: 'zh-CN' },
  { label: 'English', value: 'en-US' }
]
const activeLang = ref(languages[0].value)

// Get translation indexconst getTransIndex = (lang: string) => {
  return ((t: any) =>  === lang)
}

// Get or create a translationconst getTranslation = (lang: string) => {
  let trans = ((t: any) =>  === lang)
  if (!trans) {
    trans = { lang }
    (trans)
  }
  return trans
}

// Form verification rulesconst rules = {
  categoryId: [{ required: true, message: 'Please select a category' }],
  translations: {
    title: [{ required: true, message: 'Please enter the title' }],
    content: [{ required: true, message: 'Please enter content' }]
  }
}

// Submit the formconst formRef = ref()
const handleSubmit = async () => {
  if (!) return
  await ()
  emit('submit', )
}
</script>

4. Multilingual configuration

import { createI18n } from 'vue-i18n'

export default defineNuxtPlugin(({ vueApp }) => {
  const i18n = createI18n({
    legacy: false,
    globalInjection: true,
    locale: 'zh-CN',
    messages: {
      'zh-CN': {
        article: {
          title: 'Article Management',
          list: 'Article List',
          add: 'Added Article',
          edit: 'Edit article',
          // ... Other translations        }
      },
      'en-US': {
        article: {
          title: 'Article Management',
          list: 'Article List',
          add: 'New Article',
          edit: 'Edit Article',
          // ... Other translations        }
      }
    }
  })

  (i18n)
})

5. Front-end routing configuration

export default defineNuxtRouteMiddleware((to) => {
  const token = useCookie('token')
  
  if (! && ('/admin')) {
    return navigateTo('/login')
  }
})

This implementation includes:

  • Complete CRUD interface on the backend
  • Multilingual support (Chinese and English)
  • Rich text editor support
  • Image upload function
  • Classification Management
  • Permission control

The above is the detailed content of the complete guide to creating an efficient multilingual blog system for golang+vue. For more information about govue multilingual blogs, please follow my other related articles!