diff --git a/web/app/components/plugins/card/index.tsx b/web/app/components/plugins/card/index.tsx index b9ff8ecfda..b262727506 100644 --- a/web/app/components/plugins/card/index.tsx +++ b/web/app/components/plugins/card/index.tsx @@ -10,6 +10,7 @@ import Description from './base/description' import Placeholder from './base/placeholder' import cn from '@/utils/classnames' import { useGetLanguage } from '@/context/i18n' +import { getLanguage } from '@/i18n/language' export type Props = { className?: string @@ -22,6 +23,7 @@ export type Props = { footer?: React.ReactNode isLoading?: boolean loadingFileName?: string + locale?: string } const Card = ({ @@ -35,8 +37,10 @@ const Card = ({ footer, isLoading = false, loadingFileName, + locale: localeFromProps, }: Props) => { - const locale = useGetLanguage() + const defaultLocale = useGetLanguage() + const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale const { type, name, org, label, brief, icon, verified } = payload diff --git a/web/app/components/plugins/hooks.ts b/web/app/components/plugins/hooks.ts index 0abadd7a90..484a7fbb5a 100644 --- a/web/app/components/plugins/hooks.ts +++ b/web/app/components/plugins/hooks.ts @@ -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 = [ { diff --git a/web/app/components/plugins/install-plugin/base/installed.tsx b/web/app/components/plugins/install-plugin/base/installed.tsx index 8322c3e5eb..442a61e372 100644 --- a/web/app/components/plugins/install-plugin/base/installed.tsx +++ b/web/app/components/plugins/install-plugin/base/installed.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import type { PluginDeclaration, PluginManifestInMarket } from '../../types' +import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types' import Card from '../../card' import Button from '@/app/components/base/button' import { pluginManifestInMarketToPluginProps, pluginManifestToCardPluginProps } from '../utils' @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next' import Badge, { BadgeState } from '@/app/components/base/badge/index' type Props = { - payload?: PluginDeclaration | PluginManifestInMarket | null + payload?: Plugin | PluginDeclaration | PluginManifestInMarket | null isMarketPayload?: boolean isFailed: boolean errMsg?: string | null diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx index 17eca59dc3..b721a84454 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react' import Modal from '@/app/components/base/modal' -import type { PluginManifestInMarket } from '../../types' +import type { Plugin, PluginManifestInMarket } from '../../types' import { InstallStep } from '../../types' import Install from './steps/install' import Installed from '../base/installed' @@ -12,7 +12,7 @@ const i18nPrefix = 'plugin.installModal' type InstallFromMarketplaceProps = { uniqueIdentifier: string - manifest: PluginManifestInMarket + manifest: PluginManifestInMarket | Plugin onSuccess: () => void onClose: () => void } @@ -36,7 +36,7 @@ const InstallFromMarketplace: React.FC = ({ if (step === InstallStep.installFailed) return t(`${i18nPrefix}.installFailed`) return t(`${i18nPrefix}.installPlugin`) - }, [step]) + }, [step, t]) const handleInstalled = useCallback(() => { setStep(InstallStep.installed) diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx index f1f5fecf93..bc32e642a5 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React, { useMemo } from 'react' import { RiInformation2Line } from '@remixicon/react' -import type { PluginManifestInMarket } from '../../../types' +import type { Plugin, PluginManifestInMarket } from '../../../types' import Card from '../../../card' import { pluginManifestInMarketToPluginProps } from '../../utils' import Button from '@/app/components/base/button' @@ -16,7 +16,7 @@ const i18nPrefix = 'plugin.installModal' type Props = { uniqueIdentifier: string - payload: PluginManifestInMarket + payload: PluginManifestInMarket | Plugin onCancel: () => void onStartToInstall?: () => void onInstalled: () => void @@ -104,7 +104,7 @@ const Installed: FC = ({
diff --git a/web/app/components/plugins/marketplace/description/index.tsx b/web/app/components/plugins/marketplace/description/index.tsx index 754a4b12a9..403478dfc7 100644 --- a/web/app/components/plugins/marketplace/description/index.tsx +++ b/web/app/components/plugins/marketplace/description/index.tsx @@ -1,4 +1,17 @@ -const Description = () => { +import { + getLocaleOnServer, + useTranslation as translate, +} from '@/i18n/server' + +type DescriptionProps = { + locale?: string +} +const Description = async ({ + locale: localeFromProps, +}: DescriptionProps) => { + const localeDefault = getLocaleOnServer() + const { t } = await translate(localeFromProps || localeDefault, 'plugin') + return ( <>

@@ -7,19 +20,19 @@ const Description = () => {

Discover - models + {t('category.models')} , - tools + {t('category.tools')} , - extensions + {t('category.extensions')} and - bundles + {t('category.bundles')} in Dify Marketplace

diff --git a/web/app/components/plugins/marketplace/hooks.ts b/web/app/components/plugins/marketplace/hooks.ts index 83b7ee5435..47ad603276 100644 --- a/web/app/components/plugins/marketplace/hooks.ts +++ b/web/app/components/plugins/marketplace/hooks.ts @@ -2,6 +2,7 @@ import { useCallback, useState, } from 'react' +import { useTranslation } from 'react-i18next' import { useDebounceFn } from 'ahooks' import type { Plugin } from '../types' import type { @@ -13,6 +14,7 @@ import { getMarketplaceCollectionsAndPlugins, getMarketplacePlugins, } from './utils' +import i18n from '@/i18n/i18next-config' export const useMarketplaceCollectionsAndPlugins = () => { const [isLoading, setIsLoading] = useState(false) @@ -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, + } +} diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 0c87cce924..742df86ea0 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -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 ( - + - - + + { - const { t } = useTranslation() + const { t } = useMixedTranslation(locale) + const [isShowInstallFromMarketplace, { + setTrue: showInstallFromMarketplace, + setFalse: hideInstallFromMarketplace, + }] = useBoolean(false) + return ( -
+
{ + if (!showInstallButton) + window.open(`${MARKETPLACE_URL_PREFIX}/plugin/${plugin.org}/${plugin.name}`) + }} + > {t('plugin.detailPanel.operation.install')} @@ -48,6 +65,16 @@ const CardWrapper = ({
) } + { + isShowInstallFromMarketplace && ( + + ) + }
) } diff --git a/web/app/components/plugins/marketplace/list/index.tsx b/web/app/components/plugins/marketplace/list/index.tsx index 7d1ace6297..1fe1e7306b 100644 --- a/web/app/components/plugins/marketplace/list/index.tsx +++ b/web/app/components/plugins/marketplace/list/index.tsx @@ -10,12 +10,14 @@ type ListProps = { marketplaceCollectionPluginsMap: Record plugins?: Plugin[] showInstallButton?: boolean + locale?: string } const List = ({ marketplaceCollections, marketplaceCollectionPluginsMap, plugins, showInstallButton, + locale, }: ListProps) => { return ( <> @@ -25,6 +27,7 @@ const List = ({ marketplaceCollections={marketplaceCollections} marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap} showInstallButton={showInstallButton} + locale={locale} /> ) } @@ -37,6 +40,7 @@ const List = ({ key={plugin.name} plugin={plugin} showInstallButton={showInstallButton} + locale={locale} /> )) } diff --git a/web/app/components/plugins/marketplace/list/list-with-collection.tsx b/web/app/components/plugins/marketplace/list/list-with-collection.tsx index 6d56380c52..57b087d1f5 100644 --- a/web/app/components/plugins/marketplace/list/list-with-collection.tsx +++ b/web/app/components/plugins/marketplace/list/list-with-collection.tsx @@ -7,11 +7,13 @@ type ListWithCollectionProps = { marketplaceCollections: MarketplaceCollection[] marketplaceCollectionPluginsMap: Record showInstallButton?: boolean + locale?: string } const ListWithCollection = ({ marketplaceCollections, marketplaceCollectionPluginsMap, showInstallButton, + locale, }: ListWithCollectionProps) => { return ( <> @@ -30,6 +32,7 @@ const ListWithCollection = ({ key={plugin.name} plugin={plugin} showInstallButton={showInstallButton} + locale={locale} /> )) } diff --git a/web/app/components/plugins/marketplace/list/list-wrapper.tsx b/web/app/components/plugins/marketplace/list/list-wrapper.tsx index bcb929ca2f..443b9ef516 100644 --- a/web/app/components/plugins/marketplace/list/list-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/list-wrapper.tsx @@ -9,11 +9,13 @@ type ListWrapperProps = { marketplaceCollections: MarketplaceCollection[] marketplaceCollectionPluginsMap: Record 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} /> ) diff --git a/web/app/components/plugins/marketplace/plugin-type-switch.tsx b/web/app/components/plugins/marketplace/plugin-type-switch.tsx index 35f5349343..c1469cf6bf 100644 --- a/web/app/components/plugins/marketplace/plugin-type-switch.tsx +++ b/web/app/components/plugins/marketplace/plugin-type-switch.tsx @@ -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: , - }, - { - value: PLUGIN_TYPE_SEARCH_MAP.tool, - text: 'Tools', - icon: , - }, - { - value: PLUGIN_TYPE_SEARCH_MAP.extension, - text: 'Extensions', - icon: , - }, - { - value: PLUGIN_TYPE_SEARCH_MAP.bundle, - text: 'Bundles', - icon: , - }, -] -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: , + }, + { + value: PLUGIN_TYPE_SEARCH_MAP.tool, + text: t('plugin.category.tools'), + icon: , + }, + { + value: PLUGIN_TYPE_SEARCH_MAP.extension, + text: t('plugin.category.extensions'), + icon: , + }, + { + value: PLUGIN_TYPE_SEARCH_MAP.bundle, + text: t('plugin.category.bundles'), + icon: , + }, + ] + return (
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 (
diff --git a/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx b/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx index a124d93eb4..dfdc699958 100644 --- a/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx +++ b/web/app/components/plugins/marketplace/search-box/search-box-wrapper.tsx @@ -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')} /> ) } diff --git a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx index 670d7af6ed..416cc99b91 100644 --- a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx +++ b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx @@ -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)) diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index 37831368e2..2f8ec889f4 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -52,13 +52,14 @@ const DetailHeader = ({ meta, } = detail const { author, name, label, description, icon, verified } = detail.declaration + const isFromGitHub = source === PluginSource.github // Only plugin installed from GitHub need to check if it's the new version const hasNewVersion = useMemo(() => { return source === PluginSource.github && latest_version !== version }, [source, latest_version, version]) // #plugin TODO# update plugin - const handleUpdate = () => {} + const handleUpdate = () => { } const [isShowPluginInfo, { setTrue: showPluginInfo, @@ -151,7 +152,7 @@ const DetailHeader = ({ {isShowPluginInfo && ( void } @@ -30,9 +30,9 @@ const PlugInfo: FC = ({ closable >
- - - + {repository && } + {release && } + {packageName && }
) diff --git a/web/app/components/plugins/plugin-page/plugins-panel.tsx b/web/app/components/plugins/plugin-page/plugins-panel.tsx index 11a8c3d198..6b8d2475fd 100644 --- a/web/app/components/plugins/plugin-page/plugins-panel.tsx +++ b/web/app/components/plugins/plugin-page/plugins-panel.tsx @@ -10,7 +10,7 @@ import { useDebounceFn } from 'ahooks' import Empty from './empty' const PluginsPanel = () => { - const [filters, setFilters] = usePluginPageContext(v => [v.filters, v.setFilters]) + const [filters, setFilters] = usePluginPageContext(v => [v.filters, v.setFilters]) as [FilterState, (filter: FilterState) => void] const pluginList = usePluginPageContext(v => v.installedPluginList) as PluginDetail[] const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList) @@ -19,11 +19,11 @@ const PluginsPanel = () => { }, { wait: 500 }) const filteredList = useMemo(() => { - // todo: filter by tags - const { categories, searchQuery } = filters + const { categories, searchQuery, tags } = filters const filteredList = pluginList.filter((plugin) => { return ( (categories.length === 0 || categories.includes(plugin.declaration.category)) + && (tags.length === 0 || tags.some(tag => plugin.declaration.tags.includes(tag))) && (searchQuery === '' || plugin.plugin_id.toLowerCase().includes(searchQuery.toLowerCase())) ) }) diff --git a/web/app/components/plugins/provider-card.tsx b/web/app/components/plugins/provider-card.tsx index 2a6ab0f132..5c8ab1891e 100644 --- a/web/app/components/plugins/provider-card.tsx +++ b/web/app/components/plugins/provider-card.tsx @@ -12,7 +12,9 @@ import DownloadCount from './card/base/download-count' import Button from '@/app/components/base/button' import { useGetLanguage } from '@/context/i18n' import { MARKETPLACE_URL_PREFIX } from '@/config' +import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' import cn from '@/utils/classnames' +import { useBoolean } from 'ahooks' type Props = { className?: string @@ -24,6 +26,10 @@ const ProviderCard: FC = ({ payload, }) => { const { t } = useTranslation() + const [isShowInstallFromMarketplace, { + setTrue: showInstallFromMarketplace, + setFalse: hideInstallFromMarketplace, + }] = useBoolean(false) const language = useGetLanguage() const { org, label } = payload @@ -58,6 +64,7 @@ const ProviderCard: FC = ({ @@ -71,6 +78,16 @@ const ProviderCard: FC = ({
+ { + isShowInstallFromMarketplace && ( + + ) + }
) } diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 83c3eca39c..f0f80a3e57 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -69,6 +69,7 @@ export type PluginDeclaration = { endpoint: PluginEndpointDeclaration tool: PluginToolDeclaration model: any // TODO + tags: string[] } export type PluginManifestInMarket = { @@ -110,6 +111,7 @@ export type Plugin = { plugin_id: string version: string latest_version: string + latest_package_identifier: string icon: string verified: boolean label: Record diff --git a/web/app/components/tools/marketplace/index.tsx b/web/app/components/tools/marketplace/index.tsx index fff22fedc5..f2092227a0 100644 --- a/web/app/components/tools/marketplace/index.tsx +++ b/web/app/components/tools/marketplace/index.tsx @@ -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 = ({
Discover - models + {t('plugin.category.models')} , - tools + {t('plugin.category.tools')} , - extensions + {t('plugin.category.extensions')} and - bundles + {t('plugin.category.bundles')} in Dify Marketplace
@@ -62,6 +66,7 @@ const Marketplace = ({ marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}} plugins={plugins} showInstallButton + locale={locale} /> ) } diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index c049653aac..6d6e0d57c0 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -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', diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index 1fc23ea1b0..8b13e63aee 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -1,4 +1,11 @@ const translation = { + category: { + models: '模型', + tools: '工具', + extensions: '扩展', + bundles: '捆绑包', + }, + searchPlugins: '搜索插件', from: '来自', findMoreInMarketplace: '在 Marketplace 中查找更多', searchInMarketplace: '在 Marketplace 中搜索',