feat: i18n Setting

feat: i18n setting
This commit is contained in:
Quicy
2026-01-26 14:14:30 +08:00
parent e3c9a13493
commit e8b690b174
16 changed files with 250 additions and 99 deletions
+34 -1
View File
@@ -1,8 +1,41 @@
<script setup lang="ts">
import { RouterView } from 'vue-router'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@memoh/ui'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiTranslate } from '@mdi/js'
import i18n from './i18n'
console.log(i18n.global.locale)
</script>
<template>
<RouterView />
<section>
<div
class="fixed top-8 right-2 z-9999 [&:is(:has([data-state=open]),:hover)_.translate-icon]:opacity-100"
>
<DropdownMenu>
<DropdownMenuTrigger class="ml-auto mr-4 cursor-pointer">
<svg-icon
type="mdi"
:path="mdiTranslate"
class="translate-icon opacity-30"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="$i18n.locale= 'zh'">
中文
</DropdownMenuItem>
<DropdownMenuItem @click="$i18n.locale = 'en'">
English
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<RouterView />
</section>
</template>
+13 -11
View File
@@ -6,13 +6,13 @@
variant="default"
class="ml-auto my-4"
>
添加MCP
{{ $t("button.add",{msg:"MCP"}) }}
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-106.25">
<form @submit="createMCP">
<DialogHeader>
<DialogTitle>添加MCP</DialogTitle>
<DialogTitle> {{ $t("button.add", { msg: "MCP" }) }}</DialogTitle>
<DialogDescription class="mb-4">
添加MCP完成操作
</DialogDescription>
@@ -29,7 +29,7 @@
<FormControl>
<Input
type="text"
placeholder="请输入Name"
:placeholder="$t('prompt.enter', { msg: 'Name' })"
v-bind="componentField"
autocomplete="name"
/>
@@ -50,7 +50,9 @@
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择 Type" />
<SelectValue
:placeholder="$t('prompt.select', { msg: 'Type' })"
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -77,7 +79,7 @@
<FormControl>
<Input
type="text"
placeholder="请输入cwd"
:placeholder="$t('prompt.enter', { msg: 'cwd' })"
v-bind="componentField"
autocomplete="cwd"
/>
@@ -97,7 +99,7 @@
</FormLabel>
<FormControl>
<Input
placeholder="请输入Command"
:placeholder="$t('prompt.enter', { msg: 'Command' })"
v-bind="componentField"
/>
</FormControl>
@@ -129,8 +131,8 @@
<TagsInputItemText />
<TagsInputItemDelete />
</TagsInputItem>
<TagsInputInput
placeholder="请输入Arguments"
<TagsInputInput
:placeholder="$t('prompt.enter', { msg: 'Arguments' })"
class="w-full py-1"
/>
</TagsInput>
@@ -179,7 +181,7 @@
<TagsInputItemDelete />
</TagsInputItem>
<TagsInputInput
placeholder="请输入Env"
:placeholder="$t('prompt.enter', { msg: 'Env' })"
class="w-full py-1"
/>
</TagsInput>
@@ -196,7 +198,7 @@
<FormItem>
<FormControl>
<section class="flex gap-4">
<Label for="airplane-mode">开启</Label>
<Label for="airplane-mode">{{ $t('state.open') }}</Label>
<Switch
id="airplane-mode"
:model-value="componentField.modelValue"
@@ -217,7 +219,7 @@
</Button>
</DialogClose>
<Button type="submit">
添加MCP
{{ $t("button.add", { msg: "MCP" }) }}
</Button>
</DialogFooter>
</form>
@@ -3,13 +3,13 @@
<Dialog v-model:open="open">
<DialogTrigger as-child>
<Button variant="default">
添加Model
{{ $t("button.add",{msg:"Model"}) }}
</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-106.25">
<form @submit="addModel">
<DialogHeader>
<DialogTitle>添加Model</DialogTitle>
<DialogTitle> {{ $t("button.add", { msg: "Model" }) }}</DialogTitle>
<DialogDescription class="mb-4">
使用不用厂商的大模型
</DialogDescription>
@@ -26,7 +26,7 @@
<FormControl>
<Input
type="text"
placeholder="请输入Model Name"
:placeholder="$t('prompt.enter',{msg:'Model Name'})"
v-bind="componentField"
autocomplete="modelId"
/>
@@ -46,8 +46,8 @@
</FormLabel>
<FormControl>
<Input
type="text"
placeholder="请输入Base Url"
type="text"
:placeholder="$t('prompt.enter', { msg: 'Base Url' })"
v-bind="componentField"
autocomplete="baseurl"
/>
@@ -67,7 +67,7 @@
</FormLabel>
<FormControl>
<Input
placeholder="请输入Api Key"
:placeholder="$t('prompt.enter', { msg: 'Api Key' })"
autocomplete="apiKey"
v-bind="componentField"
/>
@@ -88,7 +88,7 @@
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择Client Type" />
<SelectValue :placeholder="$t('prompt.select',{msg:'Client Type'})" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -120,7 +120,7 @@
</FormLabel>
<FormControl>
<Input
placeholder="请输入Api Key"
:placeholder="$t('prompt.enter', { msg: 'Display Name' })"
autocomplete="name"
v-bind="componentField"
/>
@@ -141,7 +141,7 @@
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择Role" />
<SelectValue :placeholder="$t('prompt.select', { msg: 'Role' })" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -168,7 +168,7 @@
</Button>
</DialogClose>
<Button type="submit">
添加Model
{{ $t("button.add", { msg: "Model" }) }}
</Button>
</DialogFooter>
</form>
@@ -12,10 +12,10 @@
<Breadcrumb>
<BreadcrumbList>
<template
v-for="(breadcrumbItem,index) in curBreadcrumb"
v-for="(breadcrumbItem, index) in curBreadcrumb"
:key="breadcrumbItem"
>
<template v-if="(index+1)!==curBreadcrumb.length">
<template v-if="(index + 1) !== curBreadcrumb.length">
<BreadcrumbItem class="hidden md:block">
<BreadcrumbLink :href="breadcrumbItem.path">
{{ breadcrumbItem.breadcrumb }}
@@ -23,7 +23,7 @@
</BreadcrumbItem>
<BreadcrumbSeparator />
</template>
<BreadcrumbItem v-else>
<BreadcrumbPage>
{{ breadcrumbItem.breadcrumb }}
@@ -52,17 +52,24 @@ import {
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
Separator
Separator,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@memoh/ui'
import {useRoute} from 'vue-router'
import { useRoute } from 'vue-router'
import { computed } from 'vue'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiTranslate } from '@mdi/js'
const route = useRoute()
const curBreadcrumb = computed(() => {
const curBreadcrumb = computed(() => {
return route.matched.map(routeItem => ({
path: routeItem.path,
breadcrumb: routeItem.meta['breadcrumb']
}))
}))
})
</script>
+37 -32
View File
@@ -56,7 +56,7 @@
class="flex-[0.7] mb-10"
@click="exit"
>
退出登录
{{ $t("login.exit") }}
</Button>
</SidebarMenuItem>
</SidebarMenu>
@@ -81,45 +81,50 @@ import {
Collapsible,
Button
} from '@memoh/ui'
import { reactive } from 'vue'
import { computed } from 'vue'
import SvgIcon from '@jamescoyle/vue-icon'
import { mdiRobot, mdiChatOutline, mdiCogBox, mdiListBox, mdiHome, mdiBookArrowDown } from '@mdi/js'
import { useRouter } from 'vue-router'
import {useUserStore} from '@/store/User.ts'
import { useUserStore } from '@/store/User.ts'
import i18n from '@/i18n'
const router=useRouter()
const router = useRouter()
const { t } = i18n.global
const sidebarInfo = reactive([{
title: '创建对话',
name: 'chat',
icon: mdiChatOutline
}, {
title: '主页',
name: 'home',
icon: mdiHome
},
{
title: '模型配置',
name: 'models',
icon: mdiRobot
}, {
title: '设置',
name: 'settings',
icon: mdiCogBox
}, {
title: 'MCP',
name: 'mcp',
icon: mdiListBox
}, {
title: '平台',
name: 'platform',
icon: mdiBookArrowDown
}])
const sidebarInfo = computed(() => [
{
title: t('slidebar.chat'),
name: 'chat',
icon: mdiChatOutline
},
{
title: t('slidebar.home'),
name: 'home',
icon: mdiHome
},
{
title: t('slidebar.model_setting'),
name: 'models',
icon: mdiRobot
}, {
title: t('slidebar.setting'),
name: 'settings',
icon: mdiCogBox
}, {
title: 'MCP',
name: 'mcp',
icon: mdiListBox
}, {
title: t('slidebar.platform'),
name: 'platform',
icon: mdiBookArrowDown
}
])
const {exitLogin}=useUserStore()
const { exitLogin } = useUserStore()
const exit = () => {
exitLogin()
router.replace({name:'Login'})
router.replace({ name: 'Login' })
}
</script>
+25 -3
View File
@@ -1,7 +1,29 @@
import { createI18n } from 'vue-i18n'
import en from '@/i18n/locales/en.json'
import zh from '@/i18n/locales/zh.json'
import { computed } from 'vue'
const i18n = createI18n({
locale: 'en',
type enMessageSchema = typeof en
type zhMessageSchema = typeof zh;
const i18n = createI18n<[enMessageSchema, zhMessageSchema], 'en' | 'zh'>({
locale: 'zh',
legacy: false,
fallbackLocale: 'en',
messages: {
en,
zh
}
})
export default i18n
export default i18n
const t = i18n.global.t
export const i18nRef = (arg:string) => {
return computed(() => {
return t(arg)
})
}
+39
View File
@@ -0,0 +1,39 @@
{
"login": {
"username": "Username",
"password": "Password",
"login": "Login",
"register": "Register",
"forget": "Forgot your password?",
"exit": "Sign Out"
},
"prompt": {
"enter": "Please enter {msg}",
"select": "Please select {msg}"
},
"slidebar": {
"setting": "Settings",
"platform": "Platform",
"chat": "Create Chat",
"model_setting": "Model Settings",
"home": "Home"
},
"desc": {
"question": "your question"
},
"chat": {
"send": "Send",
"chat": "Chat"
},
"breadcrumb": {
"main": "Main"
},
"button": {
"edit": "Edit",
"delete": "Delete",
"add": "Add {msg}"
},
"state":{
"open":"Open"
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"login": {
"username": "用户名",
"password": "密码",
"login": "登录",
"register": "注册",
"forget": "忘记密码?",
"exit": "退出登录"
},
"desc": {
"question": "您的问题"
},
"prompt": {
"enter": "请输入{msg}",
"select": "请选择{msg}"
},
"slidebar": {
"setting": "设置",
"platform": "平台",
"chat": "创建对话",
"model_setting": "模型配置",
"home": "主页"
},
"chat": {
"send": "发送",
"chat": "对话"
},
"breadcrumb": {
"main": "主菜单"
},
"button": {
"edit": "编辑",
"delete": "删除",
"add": "添加{msg}"
},
"state": {
"open": "Open"
}
}
+1 -1
View File
@@ -13,4 +13,4 @@ createApp(App)
.use(PiniaColada)
.use(router)
.use(i18n)
.mount('#app')
.mount('#app')
+2 -2
View File
@@ -23,7 +23,7 @@
<section class="flex-none relative">
<Textarea
class="pb-16 pt-4"
placeholder="请输入想要获取的内容"
:placeholder="$t('prompt.enter',{msg:$t('desc.question')})"
/>
<section
class="absolute bottom-0 h-14 px-2 inset-x-0 flex items-center"
@@ -32,7 +32,7 @@
variant="default"
class="ml-auto"
>
发送
{{ $t('chat.send') }}
<svg-icon
type="mdi"
:path="mdiSendOutline"
+7 -9
View File
@@ -24,12 +24,12 @@
>
<FormItem>
<FormLabel class="mb-2">
Username
{{ $t("login.username") }}
</FormLabel>
<FormControl>
<Input
type="text"
placeholder="请输入用户名"
:placeholder="$t('prompt.enter', { msg: $t(`login.username`).toLocaleLowerCase() })"
v-bind="componentField"
autocomplete="username"
/>
@@ -45,12 +45,12 @@
>
<FormItem>
<FormLabel class="mb-2">
Password
{{ $t('login.password') }}
</FormLabel>
<FormControl>
<Input
type="password"
placeholder="请输入密码"
:placeholder="$t('prompt.enter',{msg:$t(`login.password`).toLocaleLowerCase()})"
autocomplete="password"
v-bind="componentField"
/>
@@ -65,7 +65,7 @@
href="#"
class="ml-auto inline-block text-sm underline mt-2"
>
Forgot your password?
{{ $t('login.forget') }}
</a>
</div>
</CardContent>
@@ -75,13 +75,13 @@
type="submit"
@click="login"
>
登录
{{ $t("login.login") }}
</Button>
<Button
variant="outline"
class="w-full"
>
注册
{{ $t("login.register") }}
</Button>
</CardFooter>
</Card>
@@ -153,8 +153,6 @@ const login = form.handleSubmit(async (values) => {
} finally {
loading.value=false
}
})
+4 -3
View File
@@ -19,7 +19,8 @@ import {
Badge,
Button
} from '@memoh/ui'
import { type MCPListItem as MCPType } from '@memoh/shared'
import { type MCPListItem as MCPType } from '@memoh/shared'
import { i18nRef } from '@/i18n'
const open = ref(false)
@@ -94,7 +95,7 @@ const columns:ColumnDef<MCPType>[] = [
}
open.value=true
}
}, ()=>'编辑'),
}, ()=>i18nRef('button.edit').value),
h(Button, {
variant: 'destructive',
async onClick() {
@@ -104,7 +105,7 @@ const columns:ColumnDef<MCPType>[] = [
return
}
}
},()=>'删除')
},()=>i18nRef('button.delete').value)
])
}
]
+9 -9
View File
@@ -5,19 +5,19 @@ import CreateModel from '@/components/CreateModel/index.vue'
import { useQuery, useMutation, useQueryCache } from '@pinia/colada'
import {
Button,
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationNext,
PaginationPrevious,
// Pagination,
// PaginationContent,
// PaginationEllipsis,
// PaginationItem,
// PaginationNext,
// PaginationPrevious,
Checkbox
} from '@memoh/ui'
import DataTable from '@/components/DataTable/index.vue'
import request from '@/utils/request'
import { type ColumnDef } from '@tanstack/vue-table'
import {type ModelTable as ModelType} from '@memoh/shared'
import { i18nRef } from '@/i18n'
const openDialogModel = ref(false)
const editModelInfo = ref<ModelType & { id: string } | null>(null)
@@ -131,11 +131,11 @@ const columns: ComputedRef<ColumnDef<ModelType>[]> = computed(() => [
editModelInfo.value = row.original
openDialogModel.value = true
}
}, () => '编辑'), h(Button, {
}, () => i18nRef('button.edit').value), h(Button, {
variant: 'destructive', onClick() {
deleteModel(row.original.id)
}
}, () => '删除')])
}, () => i18nRef('button.delete').value)])
}
])
+7 -5
View File
@@ -23,7 +23,7 @@
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择Client Type" />
<SelectValue :placeholder="$t('prompt.select',{msg:'Client Type'})" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -54,7 +54,7 @@
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择Embedding Type" />
<SelectValue :placeholder="$t('prompt.select',{msg:'Embedding Type'})" />
</SelectTrigger>
<SelectContent v-if="modelType.embedding.length > 0">
<SelectGroup>
@@ -88,7 +88,7 @@
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择Summary Type" />
<SelectValue :placeholder="$t('prompt.select', { msg: 'Summary Type' })" />
</SelectTrigger>
<SelectContent v-if="modelType.embedding.length > 0">
<SelectGroup>
@@ -119,7 +119,9 @@
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择Language" />
<SelectValue
:placeholder="$t('prompt.select', { msg: 'Language' })"
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -149,7 +151,7 @@
</FormLabel>
<FormControl>
<Input
placeholder="请输入超时时间"
:placeholder="$t('prompt.enter',{msg:'Timeout'})"
v-bind="componentField"
/>
</FormControl>
+8 -5
View File
@@ -1,4 +1,7 @@
import { computed } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import i18n from './i18n'
import { i18nRef } from './i18n'
const routes = [
{
@@ -15,14 +18,14 @@ const routes = [
path: '/main',
redirect: '/main/chat',
meta: {
breadcrumb: '主菜单'
breadcrumb: i18nRef('breadcrumb.main')
},
children: [{
name: 'chat',
path: 'chat',
component: () => import('@/pages/chat/index.vue'),
meta: {
breadcrumb: '对话'
breadcrumb: i18nRef('chat.chat')
}
}, {
name: 'home',
@@ -36,14 +39,14 @@ const routes = [
path: 'models',
component: () => import('@/pages/models/index.vue'),
meta: {
breadcrumb: '模型管理'
breadcrumb: i18nRef('slidebar.model_setting')
}
}, {
name: 'settings',
path: 'settings',
component: () => import('@/pages/settings/index.vue'),
meta: {
breadcrumb: '设置'
breadcrumb: i18nRef('slidebar.setting')
}
}, {
name: 'mcp',
@@ -57,7 +60,7 @@ const routes = [
path: 'platform',
component: () => import('@/pages/platform/index.vue'),
meta: {
breadcrumb: '平台'
breadcrumb: i18nRef('slidebar.platform')
}
}]
}
+1 -1
View File
@@ -12,7 +12,7 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"typeRoots": ["./node_modules/@types", "./type.d.ts"],
"baseUrl": ".",
"rootDir": ".",
"paths": {
"@/*": ["./src/*"]
}