feat: add internationalization support for plugin categories and update translations

This commit is contained in:
twwu 2024-11-13 16:55:43 +08:00
parent 1573f6f6aa
commit a1719c49b7
26 changed files with 159 additions and 46 deletions

View File

@ -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,
}
}

View File

@ -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'>

View File

@ -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>
)) ))

View File

@ -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)
}} }}

View File

@ -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>
)) ))

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View 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

View File

@ -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',

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -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,
}, },
}) })

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation

View File

@ -0,0 +1,12 @@
const translation = {
allCategories: '所有类型',
searchCategories: '搜索类型',
categories: {
model: '模型',
tool: '工具',
extension: '扩展',
bundle: '捆绑包',
},
}
export default translation

View File

@ -6,6 +6,7 @@ const translation = {
extensions: '扩展', extensions: '扩展',
bundles: '捆绑包', bundles: '捆绑包',
}, },
search: '搜索',
searchPlugins: '搜索插件', searchPlugins: '搜索插件',
from: '来自', from: '来自',
findMoreInMarketplace: '在 Marketplace 中查找更多', findMoreInMarketplace: '在 Marketplace 中查找更多',

View File

@ -0,0 +1,4 @@
const translation = {
}
export default translation