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:
parent
04a1ec9a9a
commit
ca0dc71338
@ -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
|
||||||
|
@ -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"`
|
||||||
|
@ -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"`
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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('');
|
||||||
|
@ -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 {
|
||||||
|
@ -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: '',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user