This commit is contained in:
StyleZhang 2024-11-06 11:55:19 +08:00
parent 0d08b6cf51
commit 7c2ab21c9c
14 changed files with 125 additions and 50 deletions

View File

@ -1,12 +1,14 @@
import { useTranslation } from 'react-i18next'
import type { TFunction } from 'i18next'
type Tag = {
name: string
label: string
}
export const useTags = () => {
const { t } = useTranslation()
export const useTags = (translateFromOut?: TFunction) => {
const { t: translation } = useTranslation()
const t = translateFromOut || translation
const tags = [
{

View File

@ -1,4 +1,13 @@
const Description = () => {
import { useTranslation as translate } from '@/i18n/server'
type DescriptionProps = {
locale?: string
}
const Description = async ({
locale = 'en-US',
}: DescriptionProps) => {
const { t } = await translate(locale, 'plugin')
return (
<>
<h1 className='mb-2 text-center title-4xl-semi-bold text-text-primary'>
@ -7,19 +16,19 @@ const Description = () => {
<h2 className='flex justify-center items-center text-center body-md-regular text-text-tertiary'>
Discover
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
models
{t('category.models')}
</span>
,
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
tools
{t('category.tools')}
</span>
,
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
extensions
{t('category.extensions')}
</span>
and
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
bundles
{t('category.bundles')}
</span>
in Dify Marketplace
</h2>

View File

@ -2,6 +2,8 @@ import {
useCallback,
useState,
} from 'react'
import i18n from 'i18next'
import { useTranslation } from 'react-i18next'
import { useDebounceFn } from 'ahooks'
import type { Plugin } from '../types'
import type {
@ -63,3 +65,14 @@ export const useMarketplacePlugins = () => {
setIsLoading,
}
}
export const useMixedTranslation = (localeFromOuter?: string) => {
let t = useTranslation().t
if (localeFromOuter)
t = i18n.getFixedT(localeFromOuter)
return {
t,
}
}

View File

@ -7,20 +7,23 @@ import ListWrapper from './list/list-wrapper'
import { getMarketplaceCollectionsAndPlugins } from './utils'
type MarketplaceProps = {
locale?: string
showInstallButton?: boolean
}
const Marketplace = async ({
locale,
showInstallButton = true,
}: MarketplaceProps) => {
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
return (
<MarketplaceContextProvider>
<Description />
<Description locale={locale} />
<IntersectionLine />
<SearchBoxWrapper />
<PluginTypeSwitch />
<SearchBoxWrapper locale={locale} />
<PluginTypeSwitch locale={locale} />
<ListWrapper
locale={locale}
marketplaceCollections={marketplaceCollections}
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
showInstallButton={showInstallButton}

View File

@ -1,21 +1,23 @@
'use client'
import { RiArrowRightUpLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Card from '@/app/components/plugins/card'
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
import type { Plugin } from '@/app/components/plugins/types'
import { MARKETPLACE_URL_PREFIX } from '@/config'
import Button from '@/app/components/base/button'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
type CardWrapperProps = {
plugin: Plugin
showInstallButton?: boolean
locale?: string
}
const CardWrapper = ({
plugin,
showInstallButton,
locale,
}: CardWrapperProps) => {
const { t } = useTranslation()
const { t } = useMixedTranslation(locale)
return (
<div className='group relative rounded-xl cursor-pointer'>
<Card

View File

@ -10,12 +10,14 @@ type ListProps = {
marketplaceCollectionPluginsMap: Record<string, Plugin[]>
plugins?: Plugin[]
showInstallButton?: boolean
locale?: string
}
const List = ({
marketplaceCollections,
marketplaceCollectionPluginsMap,
plugins,
showInstallButton,
locale,
}: ListProps) => {
return (
<>
@ -37,6 +39,7 @@ const List = ({
key={plugin.name}
plugin={plugin}
showInstallButton={showInstallButton}
locale={locale}
/>
))
}

View File

@ -9,11 +9,13 @@ type ListWrapperProps = {
marketplaceCollections: MarketplaceCollection[]
marketplaceCollectionPluginsMap: Record<string, Plugin[]>
showInstallButton?: boolean
locale?: string
}
const ListWrapper = ({
marketplaceCollections,
marketplaceCollectionPluginsMap,
showInstallButton,
locale,
}: ListWrapperProps) => {
const plugins = useMarketplaceContext(v => v.plugins)
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
@ -35,6 +37,7 @@ const ListWrapper = ({
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap}
plugins={plugins}
showInstallButton={showInstallButton}
locale={locale}
/>
</div>
)

View File

@ -1,5 +1,4 @@
'use client'
import {
RiArchive2Line,
RiBrain2Line,
@ -8,6 +7,7 @@ import {
} from '@remixicon/react'
import { PluginType } from '../types'
import { useMarketplaceContext } from './context'
import { useMixedTranslation } from './hooks'
import cn from '@/utils/classnames'
export const PLUGIN_TYPE_SEARCH_MAP = {
@ -17,37 +17,44 @@ export const PLUGIN_TYPE_SEARCH_MAP = {
extension: PluginType.extension,
bundle: 'bundle',
}
const options = [
{
value: PLUGIN_TYPE_SEARCH_MAP.all,
text: 'All',
icon: null,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.model,
text: 'Models',
icon: <RiBrain2Line className='mr-1.5 w-4 h-4' />,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.tool,
text: 'Tools',
icon: <RiHammerLine className='mr-1.5 w-4 h-4' />,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.extension,
text: 'Extensions',
icon: <RiPuzzle2Line className='mr-1.5 w-4 h-4' />,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.bundle,
text: 'Bundles',
icon: <RiArchive2Line className='mr-1.5 w-4 h-4' />,
},
]
const PluginTypeSwitch = () => {
type PluginTypeSwitchProps = {
locale?: string
}
const PluginTypeSwitch = ({
locale,
}: PluginTypeSwitchProps) => {
const { t } = useMixedTranslation(locale)
const activePluginType = useMarketplaceContext(s => s.activePluginType)
const handleActivePluginTypeChange = useMarketplaceContext(s => s.handleActivePluginTypeChange)
const options = [
{
value: PLUGIN_TYPE_SEARCH_MAP.all,
text: 'All',
icon: null,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.model,
text: t('plugin.category.models'),
icon: <RiBrain2Line className='mr-1.5 w-4 h-4' />,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.tool,
text: t('plugin.category.tools'),
icon: <RiHammerLine className='mr-1.5 w-4 h-4' />,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.extension,
text: t('plugin.category.extensions'),
icon: <RiPuzzle2Line className='mr-1.5 w-4 h-4' />,
},
{
value: PLUGIN_TYPE_SEARCH_MAP.bundle,
text: t('plugin.category.bundles'),
icon: <RiArchive2Line className='mr-1.5 w-4 h-4' />,
},
]
return (
<div className={cn(
'sticky top-[60px] flex items-center justify-center py-3 bg-background-body space-x-2 z-10',

View File

@ -12,6 +12,7 @@ type SearchBoxProps = {
onTagsChange: (tags: string[]) => void
size?: 'small' | 'large'
placeholder?: string
locale?: string
}
const SearchBox = ({
search,
@ -20,7 +21,8 @@ const SearchBox = ({
tags,
onTagsChange,
size = 'small',
placeholder = 'Search tools...',
placeholder = '',
locale,
}: SearchBoxProps) => {
return (
<div
@ -35,6 +37,7 @@ const SearchBox = ({
tags={tags}
onTagsChange={onTagsChange}
size={size}
locale={locale}
/>
<div className='mx-1 w-[1px] h-3.5 bg-divider-regular'></div>
<div className='grow flex items-center p-1 pl-2'>

View File

@ -1,9 +1,16 @@
'use client'
import { useMarketplaceContext } from '../context'
import { useMixedTranslation } from '../hooks'
import SearchBox from './index'
import cn from '@/utils/classnames'
const SearchBoxWrapper = () => {
type SearchBoxWrapperProps = {
locale?: string
}
const SearchBoxWrapper = ({
locale,
}: SearchBoxWrapperProps) => {
const { t } = useMixedTranslation(locale)
const intersected = useMarketplaceContext(v => v.intersected)
const searchPluginText = useMarketplaceContext(v => v.searchPluginText)
const handleSearchPluginTextChange = useMarketplaceContext(v => v.handleSearchPluginTextChange)
@ -21,6 +28,8 @@ const SearchBoxWrapper = () => {
tags={filterPluginTags}
onTagsChange={handleFilterPluginTagsChange}
size='large'
locale={locale}
placeholder={t('plugin.searchPlugins')}
/>
)
}

View File

@ -1,7 +1,6 @@
'use client'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowDownSLine,
RiCloseCircleFill,
@ -16,21 +15,24 @@ import Checkbox from '@/app/components/base/checkbox'
import cn from '@/utils/classnames'
import Input from '@/app/components/base/input'
import { useTags } from '@/app/components/plugins/hooks'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
type TagsFilterProps = {
tags: string[]
onTagsChange: (tags: string[]) => void
size: 'small' | 'large'
locale?: string
}
const TagsFilter = ({
tags,
onTagsChange,
size,
locale,
}: TagsFilterProps) => {
const { t } = useTranslation()
const { t } = useMixedTranslation(locale)
const [open, setOpen] = useState(false)
const [searchText, setSearchText] = useState('')
const { tags: options, tagsMap } = useTags()
const { tags: options, tagsMap } = useTags(t)
const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchText.toLowerCase()))
const handleCheck = (id: string) => {
if (tags.includes(id))

View File

@ -1,7 +1,9 @@
import { RiArrowUpDoubleLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useMarketplace } from './hooks'
import List from '@/app/components/plugins/marketplace/list'
import Loading from '@/app/components/base/loading'
import { getLocaleOnClient } from '@/i18n'
type MarketplaceProps = {
searchPluginText: string
@ -13,6 +15,8 @@ const Marketplace = ({
filterPluginTags,
onMarketplaceScroll,
}: MarketplaceProps) => {
const locale = getLocaleOnClient()
const { t } = useTranslation()
const {
isLoading,
marketplaceCollections,
@ -31,19 +35,19 @@ const Marketplace = ({
<div className='flex items-center text-center body-md-regular text-text-tertiary'>
Discover
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
models
{t('plugin.category.models')}
</span>
,
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
tools
{t('plugin.category.tools')}
</span>
,
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
extensions
{t('plugin.category.extensions')}
</span>
and
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
bundles
{t('plugin.category.bundles')}
</span>
in Dify Marketplace
</div>
@ -62,6 +66,7 @@ const Marketplace = ({
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}}
plugins={plugins}
showInstallButton
locale={locale}
/>
)
}

View File

@ -1,4 +1,11 @@
const translation = {
category: {
models: 'models',
tools: 'tools',
extensions: 'extensions',
bundles: 'bundles',
},
searchPlugins: 'Search plugins',
from: 'From',
findMoreInMarketplace: 'Find more in Marketplace',
searchInMarketplace: 'Search in Marketplace',

View File

@ -1,4 +1,11 @@
const translation = {
category: {
models: '模型',
tools: '工具',
extensions: '扩展',
bundles: '捆绑包',
},
searchPlugins: '搜索插件',
from: '来自',
findMoreInMarketplace: '在 Marketplace 中查找更多',
searchInMarketplace: '在 Marketplace 中搜索',