Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Co-authored-by: twwu <twwu@dify.ai>tags/1.7.0
| latest_package_identifier: str = Field( | latest_package_identifier: str = Field( | ||||
| ..., description="Unique identifier for the latest package release of the plugin" | ..., description="Unique identifier for the latest package release of the plugin" | ||||
| ) | ) | ||||
| status: str = Field(..., description="Indicate the status of marketplace plugin, enum from `active` `deleted`") | |||||
| deprecated_reason: str = Field( | |||||
| ..., description="Not empty when status='deleted', indicates the reason why this plugin is deleted(deprecated)" | |||||
| ) | |||||
| alternative_plugin_id: str = Field( | |||||
| ..., description="Optional, indicates the alternative plugin for user to switch to" | |||||
| ) | |||||
| @model_validator(mode="before") | @model_validator(mode="before") | ||||
| @classmethod | @classmethod | 
| plugin_id: str | plugin_id: str | ||||
| version: str | version: str | ||||
| unique_identifier: str | unique_identifier: str | ||||
| status: str | |||||
| deprecated_reason: str | |||||
| alternative_plugin_id: str | |||||
| REDIS_KEY_PREFIX = "plugin_service:latest_plugin:" | REDIS_KEY_PREFIX = "plugin_service:latest_plugin:" | ||||
| REDIS_TTL = 60 * 5 # 5 minutes | REDIS_TTL = 60 * 5 # 5 minutes | ||||
| plugin_id=plugin_id, | plugin_id=plugin_id, | ||||
| version=manifest.latest_version, | version=manifest.latest_version, | ||||
| unique_identifier=manifest.latest_package_identifier, | unique_identifier=manifest.latest_package_identifier, | ||||
| status=manifest.status, | |||||
| deprecated_reason=manifest.deprecated_reason, | |||||
| alternative_plugin_id=manifest.alternative_plugin_id, | |||||
| ) | ) | ||||
| # Store in Redis | # Store in Redis | 
| import React, { useMemo } from 'react' | |||||
| import type { FC } from 'react' | |||||
| import Link from 'next/link' | |||||
| import cn from '@/utils/classnames' | |||||
| import { RiAlertFill } from '@remixicon/react' | |||||
| import { Trans } from 'react-i18next' | |||||
| import { snakeCase2CamelCase } from '@/utils/format' | |||||
| import { useMixedTranslation } from '../marketplace/hooks' | |||||
| type DeprecationNoticeProps = { | |||||
| status: 'deleted' | 'active' | |||||
| deprecatedReason: string | |||||
| alternativePluginId: string | |||||
| alternativePluginURL: string | |||||
| locale?: string | |||||
| className?: string | |||||
| innerWrapperClassName?: string | |||||
| iconWrapperClassName?: string | |||||
| textClassName?: string | |||||
| } | |||||
| const i18nPrefix = 'plugin.detailPanel.deprecation' | |||||
| const DeprecationNotice: FC<DeprecationNoticeProps> = ({ | |||||
| status, | |||||
| deprecatedReason, | |||||
| alternativePluginId, | |||||
| alternativePluginURL, | |||||
| locale, | |||||
| className, | |||||
| innerWrapperClassName, | |||||
| iconWrapperClassName, | |||||
| textClassName, | |||||
| }) => { | |||||
| const { t } = useMixedTranslation(locale) | |||||
| const deprecatedReasonKey = useMemo(() => { | |||||
| if (!deprecatedReason) return '' | |||||
| return snakeCase2CamelCase(deprecatedReason) | |||||
| }, [deprecatedReason]) | |||||
| // Check if the deprecatedReasonKey exists in i18n | |||||
| const hasValidDeprecatedReason = useMemo(() => { | |||||
| if (!deprecatedReason || !deprecatedReasonKey) return false | |||||
| // Define valid reason keys that exist in i18n | |||||
| const validReasonKeys = ['businessAdjustments', 'ownershipTransferred', 'noMaintainer'] | |||||
| return validReasonKeys.includes(deprecatedReasonKey) | |||||
| }, [deprecatedReason, deprecatedReasonKey]) | |||||
| if (status !== 'deleted') | |||||
| return null | |||||
| return ( | |||||
| <div className={cn('w-full', className)}> | |||||
| <div className={cn( | |||||
| 'relative flex items-start gap-x-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs shadow-shadow-shadow-3 backdrop-blur-[5px]', | |||||
| innerWrapperClassName, | |||||
| )}> | |||||
| <div className='absolute left-0 top-0 -z-10 h-full w-full bg-toast-warning-bg opacity-40' /> | |||||
| <div className={cn('flex size-6 shrink-0 items-center justify-center', iconWrapperClassName)}> | |||||
| <RiAlertFill className='size-4 text-text-warning-secondary' /> | |||||
| </div> | |||||
| <div className={cn('system-xs-regular grow py-1 text-text-primary', textClassName)}> | |||||
| { | |||||
| hasValidDeprecatedReason && alternativePluginId && ( | |||||
| <Trans | |||||
| i18nKey={`${i18nPrefix}.fullMessage`} | |||||
| components={{ | |||||
| CustomLink: ( | |||||
| <Link | |||||
| href={alternativePluginURL} | |||||
| target='_blank' | |||||
| rel='noopener noreferrer' | |||||
| className='underline' | |||||
| /> | |||||
| ), | |||||
| }} | |||||
| values={{ | |||||
| deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`), | |||||
| alternativePluginId, | |||||
| }} | |||||
| /> | |||||
| ) | |||||
| } | |||||
| { | |||||
| hasValidDeprecatedReason && !alternativePluginId && ( | |||||
| <span> | |||||
| {t(`${i18nPrefix}.onlyReason`, { deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`) })} | |||||
| </span> | |||||
| ) | |||||
| } | |||||
| { | |||||
| !hasValidDeprecatedReason && ( | |||||
| <span>{t(`${i18nPrefix}.noReason`)}</span> | |||||
| ) | |||||
| } | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ) | |||||
| } | |||||
| export default React.memo(DeprecationNotice) | 
| import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin' | import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin' | ||||
| import { Github } from '@/app/components/base/icons/src/public/common' | import { Github } from '@/app/components/base/icons/src/public/common' | ||||
| import { uninstallPlugin } from '@/service/plugins' | import { uninstallPlugin } from '@/service/plugins' | ||||
| import { useGetLanguage } from '@/context/i18n' | |||||
| import { useGetLanguage, useI18N } from '@/context/i18n' | |||||
| import { useModalContext } from '@/context/modal-context' | import { useModalContext } from '@/context/modal-context' | ||||
| import { useProviderContext } from '@/context/provider-context' | import { useProviderContext } from '@/context/provider-context' | ||||
| import { useInvalidateAllToolProviders } from '@/service/use-tools' | import { useInvalidateAllToolProviders } from '@/service/use-tools' | ||||
| import { PluginAuth } from '@/app/components/plugins/plugin-auth' | import { PluginAuth } from '@/app/components/plugins/plugin-auth' | ||||
| import { AuthCategory } from '@/app/components/plugins/plugin-auth' | import { AuthCategory } from '@/app/components/plugins/plugin-auth' | ||||
| import { useAllToolProviders } from '@/service/use-tools' | import { useAllToolProviders } from '@/service/use-tools' | ||||
| import DeprecationNotice from '../base/deprecation-notice' | |||||
| const i18nPrefix = 'plugin.action' | const i18nPrefix = 'plugin.action' | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const { theme } = useTheme() | const { theme } = useTheme() | ||||
| const locale = useGetLanguage() | const locale = useGetLanguage() | ||||
| const { locale: currentLocale } = useI18N() | |||||
| const { checkForUpdates, fetchReleases } = useGitHubReleases() | const { checkForUpdates, fetchReleases } = useGitHubReleases() | ||||
| const { setShowUpdatePluginModal } = useModalContext() | const { setShowUpdatePluginModal } = useModalContext() | ||||
| const { refreshModelProviders } = useProviderContext() | const { refreshModelProviders } = useProviderContext() | ||||
| latest_version, | latest_version, | ||||
| meta, | meta, | ||||
| plugin_id, | plugin_id, | ||||
| status, | |||||
| deprecated_reason, | |||||
| alternative_plugin_id, | |||||
| } = detail | } = detail | ||||
| const { author, category, name, label, description, icon, verified, tool } = detail.declaration | const { author, category, name, label, description, icon, verified, tool } = detail.declaration | ||||
| const isTool = category === PluginType.tool | const isTool = category === PluginType.tool | ||||
| if (isFromGitHub) | if (isFromGitHub) | ||||
| return `https://github.com/${meta!.repo}` | return `https://github.com/${meta!.repo}` | ||||
| if (isFromMarketplace) | if (isFromMarketplace) | ||||
| return getMarketplaceUrl(`/plugins/${author}/${name}`, { theme }) | |||||
| return getMarketplaceUrl(`/plugins/${author}/${name}`, { language: currentLocale, theme }) | |||||
| return '' | return '' | ||||
| }, [author, isFromGitHub, isFromMarketplace, meta, name, theme]) | }, [author, isFromGitHub, isFromMarketplace, meta, name, theme]) | ||||
| </ActionButton> | </ActionButton> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {isFromMarketplace && ( | |||||
| <DeprecationNotice | |||||
| status={status} | |||||
| deprecatedReason={deprecated_reason} | |||||
| alternativePluginId={alternative_plugin_id} | |||||
| alternativePluginURL={getMarketplaceUrl(`/plugins/${alternative_plugin_id}`, { language: currentLocale, theme })} | |||||
| className='mt-3' | |||||
| /> | |||||
| )} | |||||
| <Description className='mb-2 mt-3 h-auto' text={description[locale]} descriptionLineRows={2}></Description> | <Description className='mb-2 mt-3 h-auto' text={description[locale]} descriptionLineRows={2}></Description> | ||||
| { | { | ||||
| category === PluginType.tool && ( | category === PluginType.tool && ( | 
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React, { useMemo } from 'react' | |||||
| import React, { useCallback, useMemo } from 'react' | |||||
| import { useTheme } from 'next-themes' | import { useTheme } from 'next-themes' | ||||
| import { | import { | ||||
| RiArrowRightUpLine, | RiArrowRightUpLine, | ||||
| endpoints_active, | endpoints_active, | ||||
| meta, | meta, | ||||
| plugin_id, | plugin_id, | ||||
| status, | |||||
| deprecated_reason, | |||||
| } = plugin | } = plugin | ||||
| const { category, author, name, label, description, icon, verified, meta: declarationMeta } = plugin.declaration | const { category, author, name, label, description, icon, verified, meta: declarationMeta } = plugin.declaration | ||||
| return gte(langGeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0') | return gte(langGeniusVersionInfo.current_version, declarationMeta.minimum_dify_version ?? '0.0.0') | ||||
| }, [declarationMeta.minimum_dify_version, langGeniusVersionInfo.current_version]) | }, [declarationMeta.minimum_dify_version, langGeniusVersionInfo.current_version]) | ||||
| const handleDelete = () => { | |||||
| const isDeprecated = useMemo(() => { | |||||
| return status === 'deleted' && !!deprecated_reason | |||||
| }, [status, deprecated_reason]) | |||||
| const handleDelete = useCallback(() => { | |||||
| refreshPluginList({ category } as any) | refreshPluginList({ category } as any) | ||||
| } | |||||
| }, [category, refreshPluginList]) | |||||
| const getValueFromI18nObject = useRenderI18nObject() | const getValueFromI18nObject = useRenderI18nObject() | ||||
| const title = getValueFromI18nObject(label) | const title = getValueFromI18nObject(label) | ||||
| const descriptionText = getValueFromI18nObject(description) | const descriptionText = getValueFromI18nObject(description) | ||||
| return ( | return ( | ||||
| <div | <div | ||||
| className={cn( | className={cn( | ||||
| 'rounded-xl border-[1.5px] border-background-section-burn p-1', | |||||
| 'relative overflow-hidden rounded-xl border-[1.5px] border-background-section-burn p-1', | |||||
| currentPluginID === plugin_id && 'border-components-option-card-option-selected-border', | currentPluginID === plugin_id && 'border-components-option-card-option-selected-border', | ||||
| source === PluginSource.debugging | source === PluginSource.debugging | ||||
| ? 'bg-[repeating-linear-gradient(-45deg,rgba(16,24,40,0.04),rgba(16,24,40,0.04)_5px,rgba(0,0,0,0.02)_5px,rgba(0,0,0,0.02)_10px)]' | ? 'bg-[repeating-linear-gradient(-45deg,rgba(16,24,40,0.04),rgba(16,24,40,0.04)_5px,rgba(0,0,0,0.02)_5px,rgba(0,0,0,0.02)_10px)]' | ||||
| setCurrentPluginID(plugin.plugin_id) | setCurrentPluginID(plugin.plugin_id) | ||||
| }} | }} | ||||
| > | > | ||||
| <div className={cn('hover-bg-components-panel-on-panel-item-bg relative rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 pb-3 shadow-xs', className)}> | |||||
| <div className={cn('hover-bg-components-panel-on-panel-item-bg relative z-10 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 pb-3 shadow-xs', className)}> | |||||
| <CornerMark text={categoriesMap[category].label} /> | <CornerMark text={categoriesMap[category].label} /> | ||||
| {/* Header */} | {/* Header */} | ||||
| <div className="flex"> | |||||
| <div className='flex'> | |||||
| <div className='flex h-10 w-10 items-center justify-center overflow-hidden rounded-xl border-[1px] border-components-panel-border-subtle'> | <div className='flex h-10 w-10 items-center justify-center overflow-hidden rounded-xl border-[1px] border-components-panel-border-subtle'> | ||||
| <img | <img | ||||
| className='h-full w-full' | className='h-full w-full' | ||||
| alt={`plugin-${plugin_unique_identifier}-logo`} | alt={`plugin-${plugin_unique_identifier}-logo`} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| <div className="ml-3 w-0 grow"> | |||||
| <div className="flex h-5 items-center"> | |||||
| <div className='ml-3 w-0 grow'> | |||||
| <div className='flex h-5 items-center'> | |||||
| <Title title={title} /> | <Title title={title} /> | ||||
| {verified && <RiVerifiedBadgeLine className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" />} | |||||
| {verified && <RiVerifiedBadgeLine className='ml-0.5 h-4 w-4 shrink-0 text-text-accent' />} | |||||
| {!isDifyVersionCompatible && <Tooltip popupContent={ | {!isDifyVersionCompatible && <Tooltip popupContent={ | ||||
| t('plugin.difyVersionNotCompatible', { minimalDifyVersion: declarationMeta.minimum_dify_version }) | t('plugin.difyVersionNotCompatible', { minimalDifyVersion: declarationMeta.minimum_dify_version }) | ||||
| }><RiErrorWarningLine color='red' className="ml-0.5 h-4 w-4 shrink-0 text-text-accent" /></Tooltip>} | |||||
| }><RiErrorWarningLine color='red' className='ml-0.5 h-4 w-4 shrink-0 text-text-accent' /></Tooltip>} | |||||
| <Badge className='ml-1 shrink-0' | <Badge className='ml-1 shrink-0' | ||||
| text={source === PluginSource.github ? plugin.meta!.version : plugin.version} | text={source === PluginSource.github ? plugin.meta!.version : plugin.version} | ||||
| hasRedCornerMark={(source === PluginSource.marketplace) && !!plugin.latest_version && plugin.latest_version !== plugin.version} | hasRedCornerMark={(source === PluginSource.marketplace) && !!plugin.latest_version && plugin.latest_version !== plugin.version} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className='mb-1 mt-1.5 flex h-4 items-center justify-between px-4'> | |||||
| <div className='flex items-center'> | |||||
| <div className='mb-1 mt-1.5 flex h-4 items-center gap-x-2 px-4'> | |||||
| {/* Organization & Name */} | |||||
| <div className='flex grow items-center overflow-hidden'> | |||||
| <OrgInfo | <OrgInfo | ||||
| className="mt-0.5" | |||||
| className='mt-0.5' | |||||
| orgName={orgName} | orgName={orgName} | ||||
| packageName={name} | packageName={name} | ||||
| packageNameClassName='w-auto max-w-[150px]' | packageNameClassName='w-auto max-w-[150px]' | ||||
| {category === PluginType.extension && ( | {category === PluginType.extension && ( | ||||
| <> | <> | ||||
| <div className='system-xs-regular mx-2 text-text-quaternary'>·</div> | <div className='system-xs-regular mx-2 text-text-quaternary'>·</div> | ||||
| <div className='system-xs-regular flex space-x-1 text-text-tertiary'> | |||||
| <RiLoginCircleLine className='h-4 w-4' /> | |||||
| <span>{t('plugin.endpointsEnabled', { num: endpoints_active })}</span> | |||||
| <div className='system-xs-regular flex space-x-1 overflow-hidden text-text-tertiary'> | |||||
| <RiLoginCircleLine className='h-4 w-4 shrink-0' /> | |||||
| <span | |||||
| className='truncate' | |||||
| title={t('plugin.endpointsEnabled', { num: endpoints_active })} | |||||
| > | |||||
| {t('plugin.endpointsEnabled', { num: endpoints_active })} | |||||
| </span> | |||||
| </div> | </div> | ||||
| </> | </> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| <div className='flex items-center'> | |||||
| {/* Source */} | |||||
| <div className='flex shrink-0 items-center'> | |||||
| {source === PluginSource.github | {source === PluginSource.github | ||||
| && <> | && <> | ||||
| <a href={`https://github.com/${meta!.repo}`} target='_blank' className='flex items-center gap-1'> | <a href={`https://github.com/${meta!.repo}`} target='_blank' className='flex items-center gap-1'> | ||||
| </> | </> | ||||
| } | } | ||||
| </div> | </div> | ||||
| {/* Deprecated */} | |||||
| {source === PluginSource.marketplace && enable_marketplace && isDeprecated && ( | |||||
| <div className='system-2xs-medium-uppercase flex shrink-0 items-center gap-x-2'> | |||||
| <span className='text-text-tertiary'>·</span> | |||||
| <span className='text-text-warning'> | |||||
| {t('plugin.deprecated')} | |||||
| </span> | |||||
| </div> | |||||
| )} | |||||
| </div> | </div> | ||||
| {/* BG Effect for Deprecated Plugin */} | |||||
| {source === PluginSource.marketplace && enable_marketplace && isDeprecated && ( | |||||
| <div className='absolute bottom-[-71px] right-[-45px] z-0 size-40 bg-components-badge-status-light-warning-halo opacity-60 blur-[120px]' /> | |||||
| )} | |||||
| </div> | </div> | ||||
| ) | ) | ||||
| } | } | 
| ...plugin, | ...plugin, | ||||
| latest_version: installedLatestVersion?.versions[plugin.plugin_id]?.version ?? '', | latest_version: installedLatestVersion?.versions[plugin.plugin_id]?.version ?? '', | ||||
| latest_unique_identifier: installedLatestVersion?.versions[plugin.plugin_id]?.unique_identifier ?? '', | latest_unique_identifier: installedLatestVersion?.versions[plugin.plugin_id]?.unique_identifier ?? '', | ||||
| status: installedLatestVersion?.versions[plugin.plugin_id]?.status ?? 'active', | |||||
| deprecated_reason: installedLatestVersion?.versions[plugin.plugin_id]?.deprecated_reason ?? '', | |||||
| alternative_plugin_id: installedLatestVersion?.versions[plugin.plugin_id]?.alternative_plugin_id ?? '', | |||||
| })) || [] | })) || [] | ||||
| }, [pluginList, installedLatestVersion]) | }, [pluginList, installedLatestVersion]) | ||||
| onFilterChange={handleFilterChange} | onFilterChange={handleFilterChange} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| {isPluginListLoading ? <Loading type='app' /> : (filteredList?.length ?? 0) > 0 ? ( | |||||
| <div className='flex grow flex-wrap content-start items-start justify-center gap-2 self-stretch px-12'> | |||||
| <div className='w-full'> | |||||
| <List pluginList={filteredList || []} /> | |||||
| </div> | |||||
| {!isLastPage && !isFetching && ( | |||||
| <Button onClick={loadNextPage}> | |||||
| {t('workflow.common.loadMore')} | |||||
| </Button> | |||||
| {isPluginListLoading && <Loading type='app' />} | |||||
| {!isPluginListLoading && ( | |||||
| <> | |||||
| {(filteredList?.length ?? 0) > 0 ? ( | |||||
| <div className='flex grow flex-wrap content-start items-start justify-center gap-2 self-stretch px-12'> | |||||
| <div className='w-full'> | |||||
| <List pluginList={filteredList || []} /> | |||||
| </div> | |||||
| {!isLastPage && !isFetching && ( | |||||
| <Button onClick={loadNextPage}> | |||||
| {t('workflow.common.loadMore')} | |||||
| </Button> | |||||
| )} | |||||
| {isFetching && <div className='system-md-semibold text-text-secondary'>{t('appLog.detail.loading')}</div>} | |||||
| </div> | |||||
| ) : ( | |||||
| <Empty /> | |||||
| )} | )} | ||||
| {isFetching && <div className='system-md-semibold text-text-secondary'>{t('appLog.detail.loading')}</div>} | |||||
| </div> | |||||
| ) : ( | |||||
| <Empty /> | |||||
| </> | |||||
| )} | )} | ||||
| <PluginDetailPanel | <PluginDetailPanel | ||||
| detail={currentPluginDetail} | detail={currentPluginDetail} | 
| latest_unique_identifier: string | latest_unique_identifier: string | ||||
| source: PluginSource | source: PluginSource | ||||
| meta?: MetaData | meta?: MetaData | ||||
| status: 'active' | 'deleted' | |||||
| deprecated_reason: string | |||||
| alternative_plugin_id: string | |||||
| } | } | ||||
| export type PluginInfoFromMarketPlace = { | export type PluginInfoFromMarketPlace = { | ||||
| [plugin_id: string]: { | [plugin_id: string]: { | ||||
| unique_identifier: string | unique_identifier: string | ||||
| version: string | version: string | ||||
| status: 'active' | 'deleted' | |||||
| deprecated_reason: string | |||||
| alternative_plugin_id: string | |||||
| } | null | } | null | ||||
| } | } | ||||
| } | } | 
| searchTools: 'Search tools...', | searchTools: 'Search tools...', | ||||
| installPlugin: 'Install plugin', | installPlugin: 'Install plugin', | ||||
| installFrom: 'INSTALL FROM', | installFrom: 'INSTALL FROM', | ||||
| deprecated: 'Deprecated', | |||||
| list: { | list: { | ||||
| noInstalled: 'No plugins installed', | noInstalled: 'No plugins installed', | ||||
| notFound: 'No plugins found', | notFound: 'No plugins found', | ||||
| configureApp: 'Configure App', | configureApp: 'Configure App', | ||||
| configureModel: 'Configure model', | configureModel: 'Configure model', | ||||
| configureTool: 'Configure tool', | configureTool: 'Configure tool', | ||||
| deprecation: { | |||||
| fullMessage: 'This plugin has been deprecated due to {{deprecatedReason}}, and will no longer be updated. Please use <CustomLink href=\'https://example.com/\'>{{-alternativePluginId}}</CustomLink> instead.', | |||||
| onlyReason: 'This plugin has been deprecated due to {{deprecatedReason}} and will no longer be updated.', | |||||
| noReason: 'This plugin has been deprecated and will no longer be updated.', | |||||
| reason: { | |||||
| businessAdjustments: 'business adjustments', | |||||
| ownershipTransferred: 'ownership transferred', | |||||
| noMaintainer: 'no maintainer', | |||||
| }, | |||||
| }, | |||||
| }, | }, | ||||
| install: '{{num}} installs', | install: '{{num}} installs', | ||||
| installAction: 'Install', | installAction: 'Install', | 
| actionNum: '{{num}} {{action}} が含まれています', | actionNum: '{{num}} {{action}} が含まれています', | ||||
| endpointsDocLink: 'ドキュメントを表示する', | endpointsDocLink: 'ドキュメントを表示する', | ||||
| switchVersion: 'バージョンの切り替え', | switchVersion: 'バージョンの切り替え', | ||||
| deprecation: { | |||||
| fullMessage: 'このプラグインは{{deprecatedReason}}のため非推奨となり、新しいバージョンはリリースされません。代わりに<CustomLink href=\'https://example.com/\'>{{-alternativePluginId}}</CustomLink>をご利用ください。', | |||||
| onlyReason: 'このプラグインは{{deprecatedReason}}のため非推奨となり、新しいバージョンはリリースされません。', | |||||
| noReason: 'このプラグインは廃止されており、今後更新されることはありません。', | |||||
| reason: { | |||||
| businessAdjustments: '事業調整', | |||||
| ownershipTransferred: '所有権移転', | |||||
| noMaintainer: 'メンテナーの不足', | |||||
| }, | |||||
| }, | |||||
| }, | }, | ||||
| debugInfo: { | debugInfo: { | ||||
| title: 'デバッグ', | title: 'デバッグ', | ||||
| install: '{{num}} インストール', | install: '{{num}} インストール', | ||||
| installAction: 'インストール', | installAction: 'インストール', | ||||
| installFrom: 'インストール元', | installFrom: 'インストール元', | ||||
| deprecated: '非推奨', | |||||
| searchPlugins: '検索プラグイン', | searchPlugins: '検索プラグイン', | ||||
| search: '検索', | search: '検索', | ||||
| endpointsEnabled: '{{num}} セットのエンドポイントが有効になりました', | endpointsEnabled: '{{num}} セットのエンドポイントが有効になりました', | 
| searchTools: '搜索工具...', | searchTools: '搜索工具...', | ||||
| installPlugin: '安装插件', | installPlugin: '安装插件', | ||||
| installFrom: '安装源', | installFrom: '安装源', | ||||
| deprecated: '已弃用', | |||||
| list: { | list: { | ||||
| noInstalled: '无已安装的插件', | noInstalled: '无已安装的插件', | ||||
| notFound: '未找到插件', | notFound: '未找到插件', | ||||
| configureApp: '应用设置', | configureApp: '应用设置', | ||||
| configureModel: '模型设置', | configureModel: '模型设置', | ||||
| configureTool: '工具设置', | configureTool: '工具设置', | ||||
| deprecation: { | |||||
| fullMessage: '由于{{deprecatedReason}},此插件已被弃用,将不再发布新版本。请使用<CustomLink href=\'https://example.com/\'>{{-alternativePluginId}}</CustomLink>替代。', | |||||
| onlyReason: '由于{{deprecatedReason}},此插件已被弃用,将不再发布新版本。', | |||||
| noReason: '此插件已被弃用,将不再发布新版本。', | |||||
| reason: { | |||||
| businessAdjustments: '业务调整', | |||||
| ownershipTransferred: '所有权转移', | |||||
| noMaintainer: '无人维护', | |||||
| }, | |||||
| }, | |||||
| }, | }, | ||||
| install: '{{num}} 次安装', | install: '{{num}} 次安装', | ||||
| installAction: '安装', | installAction: '安装', | 
| a.remove() | a.remove() | ||||
| window.URL.revokeObjectURL(url) | window.URL.revokeObjectURL(url) | ||||
| } | } | ||||
| export const snakeCase2CamelCase = (input: string): string => { | |||||
| return input.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) | |||||
| } |