1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 00:09:16 +08:00

feat: Enable Multi-Language Support for Application Installation Forms (#7717)

This commit is contained in:
zhengkunwang 2025-01-14 19:03:22 +08:00 committed by GitHub
parent 04a1ec9a9a
commit ca0dc71338
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 91 additions and 45 deletions

View File

@ -75,7 +75,7 @@ func (b *BaseApi) GetApp(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return return
} }
appDTO, err := appService.GetApp(appKey) appDTO, err := appService.GetApp(c, appKey)
if err != nil { if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return return

View File

@ -1,9 +1,5 @@
package dto package dto
import (
"github.com/1Panel-dev/1Panel/backend/app/model"
)
type AppDatabase struct { type AppDatabase struct {
ServiceName string `json:"PANEL_DB_HOST"` ServiceName string `json:"PANEL_DB_HOST"`
DbName string `json:"PANEL_DB_NAME"` DbName string `json:"PANEL_DB_NAME"`
@ -62,7 +58,7 @@ type AppDefine struct {
} }
type LocalAppAppDefine struct { type LocalAppAppDefine struct {
AppProperty model.App `json:"additionalProperties" yaml:"additionalProperties"` AppProperty AppProperty `json:"additionalProperties" yaml:"additionalProperties"`
} }
type LocalAppParam struct { type LocalAppParam struct {
@ -84,6 +80,7 @@ type AppProperty struct {
Tags []string `json:"tags"` Tags []string `json:"tags"`
ShortDescZh string `json:"shortDescZh"` ShortDescZh string `json:"shortDescZh"`
ShortDescEn string `json:"shortDescEn"` ShortDescEn string `json:"shortDescEn"`
Description Locale `json:"description"`
Key string `json:"key"` Key string `json:"key"`
Required []string `json:"Required"` Required []string `json:"Required"`
CrossVersionUpdate bool `json:"crossVersionUpdate"` CrossVersionUpdate bool `json:"crossVersionUpdate"`
@ -114,9 +111,9 @@ type Locale struct {
En string `json:"en"` En string `json:"en"`
Ja string `json:"ja"` Ja string `json:"ja"`
Ms string `json:"ms"` Ms string `json:"ms"`
PtBr string `json:"pt-br"` PtBr string `json:"pt-br" yaml:"pt-br"`
Ru string `json:"ru"` Ru string `json:"ru"`
ZhHant string `json:"zh-hant"` ZhHant string `json:"zh-hant" yaml:"zh-hant"`
Zh string `json:"zh"` Zh string `json:"zh"`
} }
@ -129,6 +126,7 @@ type AppFormFields struct {
Type string `json:"type"` Type string `json:"type"`
LabelZh string `json:"labelZh"` LabelZh string `json:"labelZh"`
LabelEn string `json:"labelEn"` LabelEn string `json:"labelEn"`
Label Locale `json:"label"`
Required bool `json:"required"` Required bool `json:"required"`
Default interface{} `json:"default"` Default interface{} `json:"default"`
EnvKey string `json:"envKey"` EnvKey string `json:"envKey"`

View File

@ -32,8 +32,7 @@ type AppItem struct {
Name string `json:"name"` Name string `json:"name"`
Key string `json:"key"` Key string `json:"key"`
ID uint `json:"id"` ID uint `json:"id"`
ShortDescZh string `json:"shortDescZh"` Description string `json:"description"`
ShortDescEn string `json:"shortDescEn"`
Icon string `json:"icon"` Icon string `json:"icon"`
Type string `json:"type"` Type string `json:"type"`
Status string `json:"status"` Status string `json:"status"`

View File

@ -1,7 +1,10 @@
package model package model
import ( import (
"encoding/json"
"github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/gin-gonic/gin"
"path/filepath" "path/filepath"
"strings" "strings"
) )
@ -12,6 +15,7 @@ type App struct {
Key string `json:"key" gorm:"type:varchar(64);not null;"` Key string `json:"key" gorm:"type:varchar(64);not null;"`
ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh" gorm:"type:longtext;"` ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh" gorm:"type:longtext;"`
ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn" gorm:"type:longtext;"` ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn" gorm:"type:longtext;"`
Description string `json:"description"`
Icon string `json:"icon" gorm:"type:longtext;"` Icon string `json:"icon" gorm:"type:longtext;"`
Type string `json:"type" gorm:"type:varchar(64);not null"` Type string `json:"type" gorm:"type:varchar(64);not null"`
Status string `json:"status" gorm:"type:varchar(64);not null"` Status string `json:"status" gorm:"type:varchar(64);not null"`
@ -36,8 +40,20 @@ func (i *App) IsLocalApp() bool {
} }
func (i *App) GetAppResourcePath() string { func (i *App) GetAppResourcePath() string {
if i.IsLocalApp() { if i.IsLocalApp() {
//这里要去掉本地应用的local前缀
return filepath.Join(constant.LocalAppResourceDir, strings.TrimPrefix(i.Key, "local")) return filepath.Join(constant.LocalAppResourceDir, strings.TrimPrefix(i.Key, "local"))
} }
return filepath.Join(constant.RemoteAppResourceDir, i.Key) return filepath.Join(constant.RemoteAppResourceDir, i.Key)
} }
func (i *App) GetDescription(ctx *gin.Context) string {
var translations = make(map[string]string)
_ = json.Unmarshal([]byte(i.Description), &translations)
lang := strings.ToLower(common.GetLang(ctx))
if desc, ok := translations[lang]; ok {
return desc
}
if lang == "zh" {
return i.ShortDescZh
}
return i.ShortDescEn
}

View File

@ -37,7 +37,7 @@ type AppService struct {
type IAppService interface { type IAppService interface {
PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error) PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error)
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error) GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
GetApp(key string) (*response.AppDTO, error) GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error) Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
SyncAppListFromRemote() error SyncAppListFromRemote() error
@ -94,16 +94,15 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
lang := strings.ToLower(common.GetLang(ctx)) lang := strings.ToLower(common.GetLang(ctx))
for _, ap := range apps { for _, ap := range apps {
appDTO := &response.AppItem{ appDTO := &response.AppItem{
ID: ap.ID, ID: ap.ID,
Name: ap.Name, Name: ap.Name,
Key: ap.Key, Key: ap.Key,
Type: ap.Type, Type: ap.Type,
Icon: ap.Icon, Icon: ap.Icon,
ShortDescZh: ap.ShortDescZh, Resource: ap.Resource,
ShortDescEn: ap.ShortDescEn, Limit: ap.Limit,
Resource: ap.Resource,
Limit: ap.Limit,
} }
appDTO.Description = ap.GetDescription(ctx)
appDTOs = append(appDTOs, appDTO) appDTOs = append(appDTOs, appDTO)
appTags, err := appTagRepo.GetByAppId(ap.ID) appTags, err := appTagRepo.GetByAppId(ap.ID)
if err != nil { if err != nil {
@ -166,7 +165,7 @@ func (a AppService) GetAppTags(ctx *gin.Context) ([]response.TagDTO, error) {
return res, nil return res, nil
} }
func (a AppService) GetApp(key string) (*response.AppDTO, error) { func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, error) {
var appDTO response.AppDTO var appDTO response.AppDTO
if key == "postgres" { if key == "postgres" {
key = "postgresql" key = "postgresql"
@ -176,6 +175,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
return nil, err return nil, err
} }
appDTO.App = app appDTO.App = app
appDTO.App.Description = app.GetDescription(ctx)
details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID)) details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID))
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1051,6 +1051,8 @@ func getApps(oldApps []model.App, items []dto.AppDefine) map[string]model.App {
app.Key = key app.Key = key
app.ShortDescZh = config.ShortDescZh app.ShortDescZh = config.ShortDescZh
app.ShortDescEn = config.ShortDescEn app.ShortDescEn = config.ShortDescEn
description, _ := json.Marshal(config.Description)
app.Description = string(description)
app.Website = config.Website app.Website = config.Website
app.Document = config.Document app.Document = config.Document
app.Github = config.Github app.Github = config.Github
@ -1150,14 +1152,32 @@ func handleLocalApp(appDir string) (app *model.App, err error) {
err = buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err) err = buserr.WithMap(constant.ErrFileParseApp, map[string]interface{}{"name": "data.yml", "err": err.Error()}, err)
return return
} }
app = &localAppDefine.AppProperty appDefine := localAppDefine.AppProperty
app = &model.App{}
app.Name = appDefine.Name
app.TagsKey = append(appDefine.Tags, "Local")
app.Type = appDefine.Type
app.CrossVersionUpdate = appDefine.CrossVersionUpdate
app.Limit = appDefine.Limit
app.Recommend = appDefine.Recommend
app.Website = appDefine.Website
app.Github = appDefine.Github
app.Document = appDefine.Document
if appDefine.ShortDescZh != "" {
appDefine.Description.Zh = appDefine.ShortDescZh
}
if appDefine.ShortDescEn != "" {
appDefine.Description.En = appDefine.ShortDescEn
}
desc, _ := json.Marshal(appDefine.Description)
app.Description = string(desc)
app.Key = "local" + appDefine.Key
app.Resource = constant.AppResourceLocal app.Resource = constant.AppResourceLocal
app.Status = constant.AppNormal app.Status = constant.AppNormal
app.Recommend = 9999 app.Recommend = 9999
app.TagsKey = append(app.TagsKey, "Local") readMeByte, err := fileOp.GetContent(path.Join(appDir, "README.md"))
app.Key = "local" + app.Key
readMePath := path.Join(appDir, "README.md")
readMeByte, err := fileOp.GetContent(readMePath)
if err == nil { if err == nil {
app.ReadMe = string(readMeByte) app.ReadMe = string(readMeByte)
} }

View File

@ -101,6 +101,7 @@ func Init() {
migrations.AddApiKeyValidityTime, migrations.AddApiKeyValidityTime,
migrations.UpdateAppTag, migrations.UpdateAppTag,
migrations.UpdateApp,
}) })
if err := m.Migrate(); err != nil { if err := m.Migrate(); err != nil {
global.LOG.Error(err) global.LOG.Error(err)

View File

@ -362,7 +362,7 @@ var AddApiKeyValidityTime = &gormigrate.Migration{
} }
var UpdateAppTag = &gormigrate.Migration{ var UpdateAppTag = &gormigrate.Migration{
ID: "20241226-update-app-tag", ID: "20250114-update-app-tag",
Migrate: func(tx *gorm.DB) error { Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.Tag{}); err != nil { if err := tx.AutoMigrate(&model.Tag{}); err != nil {
return err return err
@ -370,3 +370,13 @@ var UpdateAppTag = &gormigrate.Migration{
return nil return nil
}, },
} }
var UpdateApp = &gormigrate.Migration{
ID: "20250114-update-app",
Migrate: func(tx *gorm.DB) error {
if err := tx.AutoMigrate(&model.App{}); err != nil {
return err
}
return nil
},
}

View File

@ -8,6 +8,7 @@ export namespace App {
tags: Tag[]; tags: Tag[];
shortDescZh: string; shortDescZh: string;
shortDescEn: string; shortDescEn: string;
description: string;
author: string; author: string;
source: string; source: string;
type: string; type: string;
@ -58,10 +59,21 @@ export namespace App {
formFields: FromField[]; formFields: FromField[];
} }
interface Locale {
zh: string;
en: string;
'zh-Hant': string;
ja: string;
ms: string;
'pt-br': string;
ru: string;
}
export interface FromField { export interface FromField {
type: string; type: string;
labelZh: string; labelZh: string;
labelEn: string; labelEn: string;
label: Locale;
required: boolean; required: boolean;
default: any; default: any;
envKey: string; envKey: string;

View File

@ -129,11 +129,7 @@
</div> </div>
<div class="app-desc"> <div class="app-desc">
<span class="desc"> <span class="desc">
{{ {{ app.description }}
language == 'zh' || language == 'tw'
? app.shortDescZh
: app.shortDescEn
}}
</span> </span>
</div> </div>
<div class="app-tag"> <div class="app-tag">
@ -177,16 +173,11 @@ import Detail from '../detail/index.vue';
import Install from '../detail/install/index.vue'; import Install from '../detail/install/index.vue';
import router from '@/routers'; import router from '@/routers';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { getLanguage } from '@/utils/util';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
const mobile = computed(() => { const mobile = computed(() => {
return globalStore.isMobile(); return globalStore.isMobile();
}); });
const language = getLanguage();
const paginationConfig = reactive({ const paginationConfig = reactive({
cacheSizeKey: 'app-page-size', cacheSizeKey: 'app-page-size',
currentPage: 1, currentPage: 1,

View File

@ -20,7 +20,7 @@
</div> </div>
<div class="description mb-4"> <div class="description mb-4">
<span> <span>
{{ language == 'zh' || language == 'tw' ? app.shortDescZh : app.shortDescEn }} {{ app.description }}
</span> </span>
</div> </div>
<br /> <br />
@ -84,13 +84,10 @@ import { ref } from 'vue';
import Install from './install/index.vue'; import Install from './install/index.vue';
import router from '@/routers'; import router from '@/routers';
import { GlobalStore } from '@/store'; import { GlobalStore } from '@/store';
import { getLanguage } from '@/utils/util';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
const { isDarkTheme } = storeToRefs(globalStore); const { isDarkTheme } = storeToRefs(globalStore);
const language = getLanguage();
const app = ref<any>({}); const app = ref<any>({});
const appDetail = ref<any>({}); const appDetail = ref<any>({});
const version = ref(''); const version = ref('');

View File

@ -248,6 +248,10 @@ const changeService = (value: string, services: App.AppService[]) => {
const getLabel = (row: ParamObj): string => { const getLabel = (row: ParamObj): string => {
const language = localStorage.getItem('lang') || 'zh'; const language = localStorage.getItem('lang') || 'zh';
let lang = language == 'tw' ? 'zh-Hant' : language;
if (row.label && row.label[lang] != '') {
return row.label[lang];
}
if (language == 'zh' || language == 'tw') { if (language == 'zh' || language == 'tw') {
return row.labelZh; return row.labelZh;
} else { } else {

View File

@ -10,7 +10,7 @@
<span class="h-app-title">{{ app.name }}</span> <span class="h-app-title">{{ app.name }}</span>
<div class="h-app-desc"> <div class="h-app-desc">
<span> <span>
{{ language == 'zh' || language == 'tw' ? app.shortDescZh : app.shortDescEn }} {{ app.description }}
</span> </span>
</div> </div>
</div> </div>
@ -37,11 +37,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import { SearchApp } from '@/api/modules/app'; import { SearchApp } from '@/api/modules/app';
import { getLanguage } from '@/utils/util';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
const router = useRouter(); const router = useRouter();
const language = getLanguage();
let req = reactive({ let req = reactive({
name: '', name: '',