mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 03:32:23 +08:00
feat: add internationalization support for plugin categories and update translations
This commit is contained in:
parent
1573f6f6aa
commit
a1719c49b7
|
@ -87,3 +87,42 @@ export const useTags = (translateFromOut?: TFunction) => {
|
||||||
tagsMap,
|
tagsMap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Category = {
|
||||||
|
name: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useCategories = (translateFromOut?: TFunction) => {
|
||||||
|
const { t: translation } = useTranslation()
|
||||||
|
const t = translateFromOut || translation
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{
|
||||||
|
name: 'model',
|
||||||
|
label: t('pluginCategories.categories.model'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tool',
|
||||||
|
label: t('pluginCategories.categories.tool'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extension',
|
||||||
|
label: t('pluginCategories.categories.extension'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bundle',
|
||||||
|
label: t('pluginCategories.categories.bundle'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const categoriesMap = categories.reduce((acc, category) => {
|
||||||
|
acc[category.name] = category
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, Category>)
|
||||||
|
|
||||||
|
return {
|
||||||
|
categories,
|
||||||
|
categoriesMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ const PluginItem: FC<Props> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={cn('relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)}>
|
<div className={cn('relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)}>
|
||||||
<CornerMark text={category} />
|
<CornerMark text={t(`pluginCategories.categories.${category}`)} />
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className='flex items-center justify-center w-10 h-10 overflow-hidden border-components-panel-border-subtle border-[1px] rounded-xl'>
|
<div className='flex items-center justify-center w-10 h-10 overflow-hidden border-components-panel-border-subtle border-[1px] rounded-xl'>
|
||||||
|
|
|
@ -13,6 +13,8 @@ import {
|
||||||
import Checkbox from '@/app/components/base/checkbox'
|
import Checkbox from '@/app/components/base/checkbox'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
|
import { useCategories } from '../../hooks'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type CategoriesFilterProps = {
|
type CategoriesFilterProps = {
|
||||||
value: string[]
|
value: string[]
|
||||||
|
@ -22,27 +24,11 @@ const CategoriesFilter = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}: CategoriesFilterProps) => {
|
}: CategoriesFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
const options = [
|
const { categories: options, categoriesMap } = useCategories()
|
||||||
{
|
const filteredOptions = options.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase()))
|
||||||
value: 'model',
|
|
||||||
text: 'Model',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'tool',
|
|
||||||
text: 'Tool',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'extension',
|
|
||||||
text: 'Extension',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'bundle',
|
|
||||||
text: 'Bundle',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const filteredOptions = options.filter(option => option.text.toLowerCase().includes(searchText.toLowerCase()))
|
|
||||||
const handleCheck = (id: string) => {
|
const handleCheck = (id: string) => {
|
||||||
if (value.includes(id))
|
if (value.includes(id))
|
||||||
onChange(value.filter(tag => tag !== id))
|
onChange(value.filter(tag => tag !== id))
|
||||||
|
@ -70,10 +56,10 @@ const CategoriesFilter = ({
|
||||||
'flex items-center p-1 system-sm-medium',
|
'flex items-center p-1 system-sm-medium',
|
||||||
)}>
|
)}>
|
||||||
{
|
{
|
||||||
!selectedTagsLength && 'All Categories'
|
!selectedTagsLength && t('pluginCategories.allCategories')
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!!selectedTagsLength && value.slice(0, 2).join(',')
|
!!selectedTagsLength && value.map(val => categoriesMap[val].label).slice(0, 2).join(',')
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
selectedTagsLength > 2 && (
|
selectedTagsLength > 2 && (
|
||||||
|
@ -110,23 +96,23 @@ const CategoriesFilter = ({
|
||||||
showLeftIcon
|
showLeftIcon
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={e => setSearchText(e.target.value)}
|
onChange={e => setSearchText(e.target.value)}
|
||||||
placeholder='Search categories'
|
placeholder={t('pluginCategories.searchCategories')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='p-1 max-h-[448px] overflow-y-auto'>
|
<div className='p-1 max-h-[448px] overflow-y-auto'>
|
||||||
{
|
{
|
||||||
filteredOptions.map(option => (
|
filteredOptions.map(option => (
|
||||||
<div
|
<div
|
||||||
key={option.value}
|
key={option.name}
|
||||||
className='flex items-center px-2 py-1.5 h-7 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
className='flex items-center px-2 py-1.5 h-7 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||||
onClick={() => handleCheck(option.value)}
|
onClick={() => handleCheck(option.name)}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className='mr-1'
|
className='mr-1'
|
||||||
checked={value.includes(option.value)}
|
checked={value.includes(option.name)}
|
||||||
/>
|
/>
|
||||||
<div className='px-1 system-sm-medium text-text-secondary'>
|
<div className='px-1 system-sm-medium text-text-secondary'>
|
||||||
{option.text}
|
{option.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
type SearchBoxProps = {
|
type SearchBoxProps = {
|
||||||
searchQuery: string
|
searchQuery: string
|
||||||
onChange: (query: string) => void
|
onChange: (query: string) => void
|
||||||
|
@ -10,13 +11,15 @@ const SearchBox: React.FC<SearchBoxProps> = ({
|
||||||
searchQuery,
|
searchQuery,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
wrapperClassName='flex w-[200px] items-center rounded-lg'
|
wrapperClassName='flex w-[200px] items-center rounded-lg'
|
||||||
className='bg-components-input-bg-normal'
|
className='bg-components-input-bg-normal'
|
||||||
showLeftIcon
|
showLeftIcon
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
placeholder='Search'
|
placeholder={t('plugin.search')}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChange(e.target.value)
|
onChange(e.target.value)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -13,6 +13,8 @@ import {
|
||||||
import Checkbox from '@/app/components/base/checkbox'
|
import Checkbox from '@/app/components/base/checkbox'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
|
import { useTags } from '../../hooks'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type TagsFilterProps = {
|
type TagsFilterProps = {
|
||||||
value: string[]
|
value: string[]
|
||||||
|
@ -22,19 +24,11 @@ const TagsFilter = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}: TagsFilterProps) => {
|
}: TagsFilterProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [searchText, setSearchText] = useState('')
|
const [searchText, setSearchText] = useState('')
|
||||||
const options = [
|
const { tags: options, tagsMap } = useTags()
|
||||||
{
|
const filteredOptions = options.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase()))
|
||||||
value: 'search',
|
|
||||||
text: 'Search',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'image',
|
|
||||||
text: 'Image',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const filteredOptions = options.filter(option => option.text.toLowerCase().includes(searchText.toLowerCase()))
|
|
||||||
const handleCheck = (id: string) => {
|
const handleCheck = (id: string) => {
|
||||||
if (value.includes(id))
|
if (value.includes(id))
|
||||||
onChange(value.filter(tag => tag !== id))
|
onChange(value.filter(tag => tag !== id))
|
||||||
|
@ -62,10 +56,10 @@ const TagsFilter = ({
|
||||||
'flex items-center p-1 system-sm-medium',
|
'flex items-center p-1 system-sm-medium',
|
||||||
)}>
|
)}>
|
||||||
{
|
{
|
||||||
!selectedTagsLength && 'All Tags'
|
!selectedTagsLength && t('pluginTags.allTags')
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!!selectedTagsLength && value.slice(0, 2).join(',')
|
!!selectedTagsLength && value.map(val => tagsMap[val].label).slice(0, 2).join(',')
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
selectedTagsLength > 2 && (
|
selectedTagsLength > 2 && (
|
||||||
|
@ -97,23 +91,23 @@ const TagsFilter = ({
|
||||||
showLeftIcon
|
showLeftIcon
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={e => setSearchText(e.target.value)}
|
onChange={e => setSearchText(e.target.value)}
|
||||||
placeholder='Search tags'
|
placeholder={t('pluginTags.searchTags')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='p-1 max-h-[448px] overflow-y-auto'>
|
<div className='p-1 max-h-[448px] overflow-y-auto'>
|
||||||
{
|
{
|
||||||
filteredOptions.map(option => (
|
filteredOptions.map(option => (
|
||||||
<div
|
<div
|
||||||
key={option.value}
|
key={option.name}
|
||||||
className='flex items-center px-2 py-1.5 h-7 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
className='flex items-center px-2 py-1.5 h-7 rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||||
onClick={() => handleCheck(option.value)}
|
onClick={() => handleCheck(option.name)}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className='mr-1'
|
className='mr-1'
|
||||||
checked={value.includes(option.value)}
|
checked={value.includes(option.name)}
|
||||||
/>
|
/>
|
||||||
<div className='px-1 system-sm-medium text-text-secondary'>
|
<div className='px-1 system-sm-medium text-text-secondary'>
|
||||||
{option.text}
|
{option.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|
4
web/i18n/de-DE/plugin-categories.ts
Normal file
4
web/i18n/de-DE/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
12
web/i18n/en-US/plugin-categories.ts
Normal file
12
web/i18n/en-US/plugin-categories.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const translation = {
|
||||||
|
allCategories: 'All Categories',
|
||||||
|
searchCategories: 'Search categories',
|
||||||
|
categories: {
|
||||||
|
model: 'Model',
|
||||||
|
tool: 'Tool',
|
||||||
|
extension: 'Extension',
|
||||||
|
bundle: 'Bundle',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
|
@ -6,6 +6,7 @@ const translation = {
|
||||||
extensions: 'extensions',
|
extensions: 'extensions',
|
||||||
bundles: 'bundles',
|
bundles: 'bundles',
|
||||||
},
|
},
|
||||||
|
search: 'Search',
|
||||||
searchPlugins: 'Search plugins',
|
searchPlugins: 'Search plugins',
|
||||||
from: 'From',
|
from: 'From',
|
||||||
findMoreInMarketplace: 'Find more in Marketplace',
|
findMoreInMarketplace: 'Find more in Marketplace',
|
||||||
|
|
4
web/i18n/es-ES/plugin-categories.ts
Normal file
4
web/i18n/es-ES/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/fa-IR/plugin-categories.ts
Normal file
4
web/i18n/fa-IR/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/fr-FR/plugin-categories.ts
Normal file
4
web/i18n/fr-FR/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/hi-IN/plugin-categories.ts
Normal file
4
web/i18n/hi-IN/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
|
@ -30,6 +30,7 @@ const loadLangResources = (lang: string) => ({
|
||||||
runLog: require(`./${lang}/run-log`).default,
|
runLog: require(`./${lang}/run-log`).default,
|
||||||
plugin: require(`./${lang}/plugin`).default,
|
plugin: require(`./${lang}/plugin`).default,
|
||||||
pluginTags: require(`./${lang}/plugin-tags`).default,
|
pluginTags: require(`./${lang}/plugin-tags`).default,
|
||||||
|
pluginCategories: require(`./${lang}/plugin-categories`).default,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
4
web/i18n/it-IT/plugin-categories.ts
Normal file
4
web/i18n/it-IT/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/ja-JP/plugin-categories.ts
Normal file
4
web/i18n/ja-JP/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/ko-KR/plugin-categories.ts
Normal file
4
web/i18n/ko-KR/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/pl-PL/plugin-categories.ts
Normal file
4
web/i18n/pl-PL/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/pt-BR/plugin-categories.ts
Normal file
4
web/i18n/pt-BR/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/ro-RO/plugin-categories.ts
Normal file
4
web/i18n/ro-RO/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/ru-RU/plugin-categories.ts
Normal file
4
web/i18n/ru-RU/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/tr-TR/plugin-categories.ts
Normal file
4
web/i18n/tr-TR/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/uk-UA/plugin-categories.ts
Normal file
4
web/i18n/uk-UA/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
4
web/i18n/vi-VN/plugin-categories.ts
Normal file
4
web/i18n/vi-VN/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
12
web/i18n/zh-Hans/plugin-categories.ts
Normal file
12
web/i18n/zh-Hans/plugin-categories.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const translation = {
|
||||||
|
allCategories: '所有类型',
|
||||||
|
searchCategories: '搜索类型',
|
||||||
|
categories: {
|
||||||
|
model: '模型',
|
||||||
|
tool: '工具',
|
||||||
|
extension: '扩展',
|
||||||
|
bundle: '捆绑包',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
|
@ -6,6 +6,7 @@ const translation = {
|
||||||
extensions: '扩展',
|
extensions: '扩展',
|
||||||
bundles: '捆绑包',
|
bundles: '捆绑包',
|
||||||
},
|
},
|
||||||
|
search: '搜索',
|
||||||
searchPlugins: '搜索插件',
|
searchPlugins: '搜索插件',
|
||||||
from: '来自',
|
from: '来自',
|
||||||
findMoreInMarketplace: '在 Marketplace 中查找更多',
|
findMoreInMarketplace: '在 Marketplace 中查找更多',
|
||||||
|
|
4
web/i18n/zh-Hant/plugin-categories.ts
Normal file
4
web/i18n/zh-Hant/plugin-categories.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const translation = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translation
|
Loading…
Reference in New Issue
Block a user