Browse Source

Feat/install process refinement (#3982)

crazywoola 11 months ago
parent
commit
c5e2659771

+ 248 - 248
web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx

@@ -1,248 +1,248 @@
-'use client'
-import type { FC, SVGProps } from 'react'
-import React, { useEffect } from 'react'
-import { usePathname } from 'next/navigation'
-import useSWR from 'swr'
-import { useTranslation } from 'react-i18next'
-import classNames from 'classnames'
-import { useBoolean } from 'ahooks'
-import {
-  Cog8ToothIcon,
-  // CommandLineIcon,
-  Squares2X2Icon,
-  // eslint-disable-next-line sort-imports
-  PuzzlePieceIcon,
-  DocumentTextIcon,
-  PaperClipIcon,
-  QuestionMarkCircleIcon,
-} from '@heroicons/react/24/outline'
-import {
-  Cog8ToothIcon as Cog8ToothSolidIcon,
-  // CommandLineIcon as CommandLineSolidIcon,
-  DocumentTextIcon as DocumentTextSolidIcon,
-} from '@heroicons/react/24/solid'
-import Link from 'next/link'
-import s from './style.module.css'
-import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
-import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
-import { getLocaleOnClient } from '@/i18n'
-import AppSideBar from '@/app/components/app-sidebar'
-import Divider from '@/app/components/base/divider'
-import AppIcon from '@/app/components/base/app-icon'
-import Loading from '@/app/components/base/loading'
-import FloatPopoverContainer from '@/app/components/base/float-popover-container'
-import DatasetDetailContext from '@/context/dataset-detail'
-import { DataSourceType } from '@/models/datasets'
-import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
-import { LanguagesSupported } from '@/i18n/language'
-import { useStore } from '@/app/components/app/store'
-import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
-import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
-
-export type IAppDetailLayoutProps = {
-  children: React.ReactNode
-  params: { datasetId: string }
-}
-
-type ILikedItemProps = {
-  type?: 'plugin' | 'app'
-  appStatus?: boolean
-  detail: RelatedApp
-  isMobile: boolean
-}
-
-const LikedItem = ({
-  type = 'app',
-  detail,
-  isMobile,
-}: ILikedItemProps) => {
-  return (
-    <Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
-      <div className={classNames(s.iconWrapper, 'mr-0')}>
-        <AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background} />
-        {type === 'app' && (
-          <span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
-            {detail.mode === 'advanced-chat' && (
-              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
-            )}
-            {detail.mode === 'agent-chat' && (
-              <CuteRobote className='w-2.5 h-2.5 text-indigo-600' />
-            )}
-            {detail.mode === 'chat' && (
-              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
-            )}
-            {detail.mode === 'completion' && (
-              <AiText className='w-2.5 h-2.5 text-[#0E9384]' />
-            )}
-            {detail.mode === 'workflow' && (
-              <Route className='w-2.5 h-2.5 text-[#f79009]' />
-            )}
-          </span>
-        )}
-      </div>
-      {!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
-    </Link>
-  )
-}
-
-const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
-  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <g clipPath="url(#clip0_4610_6951)">
-      <path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
-    </g>
-    <defs>
-      <clipPath id="clip0_4610_6951">
-        <rect width="16" height="16" fill="white" />
-      </clipPath>
-    </defs>
-  </svg>
-}
-
-const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
-  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
-    <path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
-    <path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
-  </svg>
-}
-
-const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
-  return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" />
-    <path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" />
-  </svg>
-}
-
-type IExtraInfoProps = {
-  isMobile: boolean
-  relatedApps?: RelatedAppResponse
-}
-
-const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
-  const locale = getLocaleOnClient()
-  const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
-  const { t } = useTranslation()
-
-  useEffect(() => {
-    setShowTips(!isMobile)
-  }, [isMobile, setShowTips])
-
-  return <div className='w-full flex flex-col items-center'>
-    <Divider className='mt-5' />
-    {(relatedApps?.data && relatedApps?.data?.length > 0) && (
-      <>
-        {!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
-        {isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
-          {relatedApps?.total || '--'}
-          <PaperClipIcon className='h-4 w-4 text-gray-700' />
-        </div>}
-        {relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
-      </>
-    )}
-    {!relatedApps?.data?.length && (
-      <FloatPopoverContainer
-        placement='bottom-start'
-        open={isShowTips}
-        toggle={toggleTips}
-        isMobile={isMobile}
-        triggerElement={
-          <div className={classNames('h-7 w-7 inline-flex justify-center items-center rounded-lg bg-transparent', isShowTips && '!bg-gray-50')}>
-            <QuestionMarkCircleIcon className='h-4 w-4 flex-shrink-0 text-gray-500' />
-          </div>
-        }
-      >
-        <div className={classNames('mt-5 p-3', isMobile && 'border-[0.5px] border-gray-200 shadow-lg rounded-lg bg-white w-[160px]')}>
-          <div className='flex items-center justify-start gap-2'>
-            <div className={s.emptyIconDiv}>
-              <Squares2X2Icon className='w-3 h-3 text-gray-500' />
-            </div>
-            <div className={s.emptyIconDiv}>
-              <PuzzlePieceIcon className='w-3 h-3 text-gray-500' />
-            </div>
-          </div>
-          <div className='text-xs text-gray-500 mt-2'>{t('common.datasetMenus.emptyTip')}</div>
-          <a
-            className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
-            href={
-              locale === LanguagesSupported[1]
-                ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
-                : 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
-            }
-            target='_blank' rel='noopener noreferrer'
-          >
-            <BookOpenIcon className='mr-1' />
-            {t('common.datasetMenus.viewDoc')}
-          </a>
-        </div>
-      </FloatPopoverContainer>
-    )}
-  </div>
-}
-
-const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
-  const {
-    children,
-    params: { datasetId },
-  } = props
-  const pathname = usePathname()
-  const hideSideBar = /documents\/create$/.test(pathname)
-  const { t } = useTranslation()
-
-  const media = useBreakpoints()
-  const isMobile = media === MediaType.mobile
-
-  const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
-    url: 'fetchDatasetDetail',
-    datasetId,
-  }, apiParams => fetchDatasetDetail(apiParams.datasetId))
-
-  const { data: relatedApps } = useSWR({
-    action: 'fetchDatasetRelatedApps',
-    datasetId,
-  }, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
-
-  const navigation = [
-    { name: t('common.datasetMenus.documents'), href: `/datasets/${datasetId}/documents`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
-    { name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
-    // { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
-    { name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
-  ]
-
-  useEffect(() => {
-    if (datasetRes)
-      document.title = `${datasetRes.name || 'Dataset'} - Dify`
-  }, [datasetRes])
-
-  const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
-
-  useEffect(() => {
-    const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
-    const mode = isMobile ? 'collapse' : 'expand'
-    setAppSiderbarExpand(isMobile ? mode : localeMode)
-  }, [isMobile, setAppSiderbarExpand])
-
-  if (!datasetRes && !error)
-    return <Loading />
-
-  return (
-    <div className='grow flex overflow-hidden'>
-      {!hideSideBar && <AppSideBar
-        title={datasetRes?.name || '--'}
-        icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
-        icon_background={datasetRes?.icon_background || '#F5F5F5'}
-        desc={datasetRes?.description || '--'}
-        navigation={navigation}
-        extraInfo={mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} />}
-        iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
-      />}
-      <DatasetDetailContext.Provider value={{
-        indexingTechnique: datasetRes?.indexing_technique,
-        dataset: datasetRes,
-        mutateDatasetRes: () => mutateDatasetRes(),
-      }}>
-        <div className="bg-white grow overflow-hidden">{children}</div>
-      </DatasetDetailContext.Provider>
-    </div>
-  )
-}
-export default React.memo(DatasetDetailLayout)
+'use client'
+import type { FC, SVGProps } from 'react'
+import React, { useEffect } from 'react'
+import { usePathname } from 'next/navigation'
+import useSWR from 'swr'
+import { useTranslation } from 'react-i18next'
+import classNames from 'classnames'
+import { useBoolean } from 'ahooks'
+import {
+  Cog8ToothIcon,
+  // CommandLineIcon,
+  Squares2X2Icon,
+  // eslint-disable-next-line sort-imports
+  PuzzlePieceIcon,
+  DocumentTextIcon,
+  PaperClipIcon,
+  QuestionMarkCircleIcon,
+} from '@heroicons/react/24/outline'
+import {
+  Cog8ToothIcon as Cog8ToothSolidIcon,
+  // CommandLineIcon as CommandLineSolidIcon,
+  DocumentTextIcon as DocumentTextSolidIcon,
+} from '@heroicons/react/24/solid'
+import Link from 'next/link'
+import s from './style.module.css'
+import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
+import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
+import AppSideBar from '@/app/components/app-sidebar'
+import Divider from '@/app/components/base/divider'
+import AppIcon from '@/app/components/base/app-icon'
+import Loading from '@/app/components/base/loading'
+import FloatPopoverContainer from '@/app/components/base/float-popover-container'
+import DatasetDetailContext from '@/context/dataset-detail'
+import { DataSourceType } from '@/models/datasets'
+import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
+import { LanguagesSupported } from '@/i18n/language'
+import { useStore } from '@/app/components/app/store'
+import { AiText, ChatBot, CuteRobote } from '@/app/components/base/icons/src/vender/solid/communication'
+import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel'
+import { getLocaleOnServer } from '@/i18n/server'
+
+export type IAppDetailLayoutProps = {
+  children: React.ReactNode
+  params: { datasetId: string }
+}
+
+type ILikedItemProps = {
+  type?: 'plugin' | 'app'
+  appStatus?: boolean
+  detail: RelatedApp
+  isMobile: boolean
+}
+
+const LikedItem = ({
+  type = 'app',
+  detail,
+  isMobile,
+}: ILikedItemProps) => {
+  return (
+    <Link className={classNames(s.itemWrapper, 'px-2', isMobile && 'justify-center')} href={`/app/${detail?.id}/overview`}>
+      <div className={classNames(s.iconWrapper, 'mr-0')}>
+        <AppIcon size='tiny' icon={detail?.icon} background={detail?.icon_background} />
+        {type === 'app' && (
+          <span className='absolute bottom-[-2px] right-[-2px] w-3.5 h-3.5 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
+            {detail.mode === 'advanced-chat' && (
+              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
+            )}
+            {detail.mode === 'agent-chat' && (
+              <CuteRobote className='w-2.5 h-2.5 text-indigo-600' />
+            )}
+            {detail.mode === 'chat' && (
+              <ChatBot className='w-2.5 h-2.5 text-[#1570EF]' />
+            )}
+            {detail.mode === 'completion' && (
+              <AiText className='w-2.5 h-2.5 text-[#0E9384]' />
+            )}
+            {detail.mode === 'workflow' && (
+              <Route className='w-2.5 h-2.5 text-[#f79009]' />
+            )}
+          </span>
+        )}
+      </div>
+      {!isMobile && <div className={classNames(s.appInfo, 'ml-2')}>{detail?.name || '--'}</div>}
+    </Link>
+  )
+}
+
+const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
+  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
+    <g clipPath="url(#clip0_4610_6951)">
+      <path d="M10.6666 5.33325V3.33325L12.6666 1.33325L13.3332 2.66659L14.6666 3.33325L12.6666 5.33325H10.6666ZM10.6666 5.33325L7.9999 7.99988M14.6666 7.99992C14.6666 11.6818 11.6818 14.6666 7.99992 14.6666C4.31802 14.6666 1.33325 11.6818 1.33325 7.99992C1.33325 4.31802 4.31802 1.33325 7.99992 1.33325M11.3333 7.99992C11.3333 9.84087 9.84087 11.3333 7.99992 11.3333C6.15897 11.3333 4.66659 9.84087 4.66659 7.99992C4.66659 6.15897 6.15897 4.66659 7.99992 4.66659" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
+    </g>
+    <defs>
+      <clipPath id="clip0_4610_6951">
+        <rect width="16" height="16" fill="white" />
+      </clipPath>
+    </defs>
+  </svg>
+}
+
+const TargetSolidIcon = ({ className }: SVGProps<SVGElement>) => {
+  return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
+    <path fillRule="evenodd" clipRule="evenodd" d="M12.7733 0.67512C12.9848 0.709447 13.1669 0.843364 13.2627 1.03504L13.83 2.16961L14.9646 2.73689C15.1563 2.83273 15.2902 3.01486 15.3245 3.22639C15.3588 3.43792 15.2894 3.65305 15.1379 3.80458L13.1379 5.80458C13.0128 5.92961 12.8433 5.99985 12.6665 5.99985H10.9426L8.47124 8.47124C8.21089 8.73159 7.78878 8.73159 7.52843 8.47124C7.26808 8.21089 7.26808 7.78878 7.52843 7.52843L9.9998 5.05707V3.33318C9.9998 3.15637 10.07 2.9868 10.1951 2.86177L12.1951 0.861774C12.3466 0.710244 12.5617 0.640794 12.7733 0.67512Z" fill="#155EEF" />
+    <path d="M1.99984 7.99984C1.99984 4.68613 4.68613 1.99984 7.99984 1.99984C8.36803 1.99984 8.6665 1.70136 8.6665 1.33317C8.6665 0.964981 8.36803 0.666504 7.99984 0.666504C3.94975 0.666504 0.666504 3.94975 0.666504 7.99984C0.666504 12.0499 3.94975 15.3332 7.99984 15.3332C12.0499 15.3332 15.3332 12.0499 15.3332 7.99984C15.3332 7.63165 15.0347 7.33317 14.6665 7.33317C14.2983 7.33317 13.9998 7.63165 13.9998 7.99984C13.9998 11.3135 11.3135 13.9998 7.99984 13.9998C4.68613 13.9998 1.99984 11.3135 1.99984 7.99984Z" fill="#155EEF" />
+    <path d="M5.33317 7.99984C5.33317 6.52708 6.52708 5.33317 7.99984 5.33317C8.36803 5.33317 8.6665 5.03469 8.6665 4.6665C8.6665 4.29831 8.36803 3.99984 7.99984 3.99984C5.7907 3.99984 3.99984 5.7907 3.99984 7.99984C3.99984 10.209 5.7907 11.9998 7.99984 11.9998C10.209 11.9998 11.9998 10.209 11.9998 7.99984C11.9998 7.63165 11.7014 7.33317 11.3332 7.33317C10.965 7.33317 10.6665 7.63165 10.6665 7.99984C10.6665 9.4726 9.4726 10.6665 7.99984 10.6665C6.52708 10.6665 5.33317 9.4726 5.33317 7.99984Z" fill="#155EEF" />
+  </svg>
+}
+
+const BookOpenIcon = ({ className }: SVGProps<SVGElement>) => {
+  return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
+    <path opacity="0.12" d="M1 3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7V10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1Z" fill="#155EEF" />
+    <path d="M6 10.5L5.94997 10.425C5.60265 9.90398 5.42899 9.64349 5.19955 9.45491C4.99643 9.28796 4.76238 9.1627 4.5108 9.0863C4.22663 9 3.91355 9 3.28741 9H2.6C2.03995 9 1.75992 9 1.54601 8.89101C1.35785 8.79513 1.20487 8.64215 1.10899 8.45399C1 8.24008 1 7.96005 1 7.4V3.1C1 2.53995 1 2.25992 1.10899 2.04601C1.20487 1.85785 1.35785 1.70487 1.54601 1.60899C1.75992 1.5 2.03995 1.5 2.6 1.5H2.8C3.9201 1.5 4.48016 1.5 4.90798 1.71799C5.28431 1.90973 5.59027 2.21569 5.78201 2.59202C6 3.01984 6 3.5799 6 4.7M6 10.5V4.7M6 10.5L6.05003 10.425C6.39735 9.90398 6.57101 9.64349 6.80045 9.45491C7.00357 9.28796 7.23762 9.1627 7.4892 9.0863C7.77337 9 8.08645 9 8.71259 9H9.4C9.96005 9 10.2401 9 10.454 8.89101C10.6422 8.79513 10.7951 8.64215 10.891 8.45399C11 8.24008 11 7.96005 11 7.4V3.1C11 2.53995 11 2.25992 10.891 2.04601C10.7951 1.85785 10.6422 1.70487 10.454 1.60899C10.2401 1.5 9.96005 1.5 9.4 1.5H9.2C8.07989 1.5 7.51984 1.5 7.09202 1.71799C6.71569 1.90973 6.40973 2.21569 6.21799 2.59202C6 3.01984 6 3.5799 6 4.7" stroke="#155EEF" strokeLinecap="round" strokeLinejoin="round" />
+  </svg>
+}
+
+type IExtraInfoProps = {
+  isMobile: boolean
+  relatedApps?: RelatedAppResponse
+}
+
+const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
+  const locale = getLocaleOnServer()
+  const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
+  const { t } = useTranslation()
+
+  useEffect(() => {
+    setShowTips(!isMobile)
+  }, [isMobile, setShowTips])
+
+  return <div className='w-full flex flex-col items-center'>
+    <Divider className='mt-5' />
+    {(relatedApps?.data && relatedApps?.data?.length > 0) && (
+      <>
+        {!isMobile && <div className='w-full px-2 pb-1 pt-4 uppercase text-xs text-gray-500 font-medium'>{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}</div>}
+        {isMobile && <div className={classNames(s.subTitle, 'flex items-center justify-center !px-0 gap-1')}>
+          {relatedApps?.total || '--'}
+          <PaperClipIcon className='h-4 w-4 text-gray-700' />
+        </div>}
+        {relatedApps?.data?.map((item, index) => (<LikedItem key={index} isMobile={isMobile} detail={item} />))}
+      </>
+    )}
+    {!relatedApps?.data?.length && (
+      <FloatPopoverContainer
+        placement='bottom-start'
+        open={isShowTips}
+        toggle={toggleTips}
+        isMobile={isMobile}
+        triggerElement={
+          <div className={classNames('h-7 w-7 inline-flex justify-center items-center rounded-lg bg-transparent', isShowTips && '!bg-gray-50')}>
+            <QuestionMarkCircleIcon className='h-4 w-4 flex-shrink-0 text-gray-500' />
+          </div>
+        }
+      >
+        <div className={classNames('mt-5 p-3', isMobile && 'border-[0.5px] border-gray-200 shadow-lg rounded-lg bg-white w-[160px]')}>
+          <div className='flex items-center justify-start gap-2'>
+            <div className={s.emptyIconDiv}>
+              <Squares2X2Icon className='w-3 h-3 text-gray-500' />
+            </div>
+            <div className={s.emptyIconDiv}>
+              <PuzzlePieceIcon className='w-3 h-3 text-gray-500' />
+            </div>
+          </div>
+          <div className='text-xs text-gray-500 mt-2'>{t('common.datasetMenus.emptyTip')}</div>
+          <a
+            className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
+            href={
+              locale === LanguagesSupported[1]
+                ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
+                : 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
+            }
+            target='_blank' rel='noopener noreferrer'
+          >
+            <BookOpenIcon className='mr-1' />
+            {t('common.datasetMenus.viewDoc')}
+          </a>
+        </div>
+      </FloatPopoverContainer>
+    )}
+  </div>
+}
+
+const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
+  const {
+    children,
+    params: { datasetId },
+  } = props
+  const pathname = usePathname()
+  const hideSideBar = /documents\/create$/.test(pathname)
+  const { t } = useTranslation()
+
+  const media = useBreakpoints()
+  const isMobile = media === MediaType.mobile
+
+  const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({
+    url: 'fetchDatasetDetail',
+    datasetId,
+  }, apiParams => fetchDatasetDetail(apiParams.datasetId))
+
+  const { data: relatedApps } = useSWR({
+    action: 'fetchDatasetRelatedApps',
+    datasetId,
+  }, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
+
+  const navigation = [
+    { name: t('common.datasetMenus.documents'), href: `/datasets/${datasetId}/documents`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
+    { name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
+    // { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
+    { name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
+  ]
+
+  useEffect(() => {
+    if (datasetRes)
+      document.title = `${datasetRes.name || 'Dataset'} - Dify`
+  }, [datasetRes])
+
+  const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand)
+
+  useEffect(() => {
+    const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand'
+    const mode = isMobile ? 'collapse' : 'expand'
+    setAppSiderbarExpand(isMobile ? mode : localeMode)
+  }, [isMobile, setAppSiderbarExpand])
+
+  if (!datasetRes && !error)
+    return <Loading />
+
+  return (
+    <div className='grow flex overflow-hidden'>
+      {!hideSideBar && <AppSideBar
+        title={datasetRes?.name || '--'}
+        icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
+        icon_background={datasetRes?.icon_background || '#F5F5F5'}
+        desc={datasetRes?.description || '--'}
+        navigation={navigation}
+        extraInfo={mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} />}
+        iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
+      />}
+      <DatasetDetailContext.Provider value={{
+        indexingTechnique: datasetRes?.indexing_technique,
+        dataset: datasetRes,
+        mutateDatasetRes: () => mutateDatasetRes(),
+      }}>
+        <div className="bg-white grow overflow-hidden">{children}</div>
+      </DatasetDetailContext.Provider>
+    </div>
+  )
+}
+export default React.memo(DatasetDetailLayout)

+ 1 - 1
web/app/activate/activateForm.tsx

@@ -86,7 +86,7 @@ const ActivateForm = () => {
           timezone,
         },
       })
-      setLocaleOnClient(language.startsWith('en') ? 'en' : 'zh-Hans', false)
+      setLocaleOnClient(language.startsWith('en') ? 'en-US' : 'zh-Hans', false)
       setShowSuccess(true)
     }
     catch {

+ 1 - 1
web/app/signin/enterpriseSSOForm.tsx

@@ -60,7 +60,7 @@ const EnterpriseSSOForm: FC<EnterpriseSSOFormProps> = ({
   return (
     <div className={
       cn(
-        'flex flex-col items-center w-full grow items-center justify-center',
+        'flex flex-col items-center w-full grow justify-center',
         'px-6',
         'md:px-[108px]',
       )

+ 1 - 1
web/app/signin/forms.tsx

@@ -20,7 +20,7 @@ const Forms = () => {
   }
   return <div className={
     cn(
-      'flex flex-col items-center w-full grow items-center justify-center',
+      'flex flex-col items-center w-full grow justify-center',
       'px-6',
       'md:px-[108px]',
     )

+ 10 - 1
web/app/signin/normalForm.tsx

@@ -245,7 +245,7 @@ const NormalForm = () => {
                       <span className='cursor-pointer text-primary-600'>{t('login.forget')}</span>
                     </Tooltip> */}
                   </label>
-                  <div className="relative mt-1 rounded-md shadow-sm">
+                  <div className="relative mt-1">
                     <input
                       id="password"
                       value={password}
@@ -300,6 +300,15 @@ const NormalForm = () => {
             >{t('login.pp')}</Link>
           </div>
 
+          {IS_CE_EDITION && <div className="w-hull text-center block mt-2 text-xs text-gray-600">
+            {t('login.goToInit')}
+            &nbsp;
+            <Link
+              className='text-primary-600'
+              href='/install'
+            >{t('login.setAdminAccount')}</Link>
+          </div>}
+
         </div>
       </div>
     </>

+ 1 - 1
web/app/signin/page.tsx

@@ -61,7 +61,7 @@ gtag('config', 'AW-11217955271"');
           {loading && (
             <div className={
               cn(
-                'flex flex-col items-center w-full grow items-center justify-center',
+                'flex flex-col items-center w-full grow justify-center',
                 'px-6',
                 'md:px-[108px]',
               )

+ 2 - 2
web/context/i18n.ts

@@ -8,13 +8,13 @@ import { getLanguage } from '@/i18n/language'
 type II18NContext = {
   locale: Locale
   i18n: Record<string, any>
-  setLocaleOnClient: (locale: Locale, reloadPage?: boolean) => void
+  setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => void
 }
 
 const I18NContext = createContext<II18NContext>({
   locale: 'en-US',
   i18n: {},
-  setLocaleOnClient: (lang: Locale, reloadPage?: boolean) => { },
+  setLocaleOnClient: (_lang: Locale, _reloadPage?: boolean) => { },
 })
 
 export const useI18N = () => useContext(I18NContext)

+ 524 - 522
web/i18n/de-DE/common.ts

@@ -1,522 +1,524 @@
-const translation = {
-  api: {
-    success: 'Erfolg',
-    actionSuccess: 'Aktion erfolgreich',
-    saved: 'Gespeichert',
-    create: 'Erstellt',
-    remove: 'Entfernt',
-  },
-  operation: {
-    create: 'Erstellen',
-    confirm: 'Bestätigen',
-    cancel: 'Abbrechen',
-    clear: 'Leeren',
-    save: 'Speichern',
-    edit: 'Bearbeiten',
-    add: 'Hinzufügen',
-    added: 'Hinzugefügt',
-    refresh: 'Neustart',
-    reset: 'Zurücksetzen',
-    search: 'Suchen',
-    change: 'Ändern',
-    remove: 'Entfernen',
-    send: 'Senden',
-    copy: 'Kopieren',
-    lineBreak: 'Zeilenumbruch',
-    sure: 'Ich bin sicher',
-    download: 'Herunterladen',
-    delete: 'Löschen',
-    settings: 'Einstellungen',
-    setup: 'Einrichten',
-    getForFree: 'Kostenlos erhalten',
-    reload: 'Neu laden',
-    ok: 'OK',
-    log: 'Protokoll',
-    learnMore: 'Mehr erfahren',
-    params: 'Parameter',
-    duplicate: 'Duplikat',
-    rename: 'Umbenennen',
-  },
-  placeholder: {
-    input: 'Bitte eingeben',
-    select: 'Bitte auswählen',
-  },
-  voice: {
-    language: {
-      zhHans: 'Chinesisch',
-      enUS: 'Englisch',
-      deDE: 'Deutsch',
-      frFR: 'Französisch',
-      esES: 'Spanisch',
-      itIT: 'Italienisch',
-      thTH: 'Thailändisch',
-      idID: 'Indonesisch',
-      jaJP: 'Japanisch',
-      koKR: 'Koreanisch',
-      ptBR: 'Portugiesisch',
-      ruRU: 'Russisch',
-      ukUA: 'Ukrainisch',
-    },
-  },
-  unit: {
-    char: 'Zeichen',
-  },
-  actionMsg: {
-    noModification: 'Im Moment keine Änderungen.',
-    modifiedSuccessfully: 'Erfolgreich geändert',
-    modifiedUnsuccessfully: 'Änderung nicht erfolgreich',
-    copySuccessfully: 'Erfolgreich kopiert',
-    paySucceeded: 'Zahlung erfolgreich',
-    payCancelled: 'Zahlung abgebrochen',
-    generatedSuccessfully: 'Erfolgreich generiert',
-    generatedUnsuccessfully: 'Generierung nicht erfolgreich',
-  },
-  model: {
-    params: {
-      temperature: 'Temperatur',
-      temperatureTip:
-        'Kontrolliert Zufälligkeit: Eine niedrigere Temperatur führt zu weniger zufälligen Ergebnissen. Nähert sich die Temperatur null, wird das Modell deterministisch und repetitiv.',
-      top_p: 'Top P',
-      top_pTip:
-        'Kontrolliert Diversität über Nukleus-Sampling: 0,5 bedeutet, dass die Hälfte aller wahrscheinlichkeitsgewichteten Optionen berücksichtigt wird.',
-      presence_penalty: 'Präsenz-Strafe',
-      presence_penaltyTip:
-        'Wie stark neue Tokens basierend darauf bestraft werden, ob sie bereits im Text erschienen sind.\nErhöht die Wahrscheinlichkeit des Modells, über neue Themen zu sprechen.',
-      frequency_penalty: 'Häufigkeitsstrafe',
-      frequency_penaltyTip:
-        'Wie stark neue Tokens basierend auf ihrer bisherigen Häufigkeit im Text bestraft werden.\nVerringert die Wahrscheinlichkeit des Modells, denselben Satz wortwörtlich zu wiederholen.',
-      max_tokens: 'Maximale Token',
-      max_tokensTip:
-        'Begrenzt die maximale Länge der Antwort in Token. \nGrößere Werte können den Platz für Eingabeaufforderungen, Chat-Logs und Wissen begrenzen. \nEs wird empfohlen, dies unter zwei Dritteln zu setzen\ngpt-4-1106-Vorschau, gpt-4-vision-Vorschau maximale Token (Eingabe 128k Ausgabe 4k)',
-      maxTokenSettingTip: 'Ihre Einstellung für maximale Token ist hoch, was den Platz für Eingabeaufforderungen, Abfragen und Daten potenziell begrenzen kann. Erwägen Sie, dies unter 2/3 zu setzen.',
-      setToCurrentModelMaxTokenTip: 'Maximale Token auf 80 % der maximalen Token des aktuellen Modells {{maxToken}} aktualisiert.',
-      stop_sequences: 'Stop-Sequenzen',
-      stop_sequencesTip: 'Bis zu vier Sequenzen, bei denen die API die Generierung weiterer Token stoppt. Der zurückgegebene Text wird die Stop-Sequenz nicht enthalten.',
-      stop_sequencesPlaceholder: 'Sequenz eingeben und Tab drücken',
-    },
-    tone: {
-      Creative: 'Kreativ',
-      Balanced: 'Ausgewogen',
-      Precise: 'Präzise',
-      Custom: 'Benutzerdefiniert',
-    },
-    addMoreModel: 'Gehen Sie zu den Einstellungen, um mehr Modelle hinzuzufügen',
-  },
-  menus: {
-    status: 'Beta',
-    explore: 'Erkunden',
-    apps: 'Studio',
-    plugins: 'Plugins',
-    pluginsTips: 'Integrieren Sie Plugins von Drittanbietern oder erstellen Sie ChatGPT-kompatible KI-Plugins.',
-    datasets: 'Wissen',
-    datasetsTips: 'BALD VERFÜGBAR: Importieren Sie Ihre eigenen Textdaten oder schreiben Sie Daten in Echtzeit über Webhook, um den LLM-Kontext zu verbessern.',
-    newApp: 'Neue App',
-    newDataset: 'Wissen erstellen',
-    tools: 'Werkzeuge',
-  },
-  userProfile: {
-    settings: 'Einstellungen',
-    workspace: 'Arbeitsbereich',
-    createWorkspace: 'Arbeitsbereich erstellen',
-    helpCenter: 'Hilfe',
-    roadmapAndFeedback: 'Feedback',
-    community: 'Gemeinschaft',
-    about: 'Über',
-    logout: 'Abmelden',
-  },
-  settings: {
-    accountGroup: 'KONTO',
-    workplaceGroup: 'ARBEITSBEREICH',
-    account: 'Mein Konto',
-    members: 'Mitglieder',
-    billing: 'Abrechnung',
-    integrations: 'Integrationen',
-    language: 'Sprache',
-    provider: 'Modellanbieter',
-    dataSource: 'Datenquelle',
-    plugin: 'Plugins',
-    apiBasedExtension: 'API-Erweiterung',
-  },
-  account: {
-    avatar: 'Avatar',
-    name: 'Name',
-    email: 'E-Mail',
-    password: 'Passwort',
-    passwordTip: 'Sie können ein dauerhaftes Passwort festlegen, wenn Sie keine temporären Anmeldecodes verwenden möchten',
-    setPassword: 'Ein Passwort festlegen',
-    resetPassword: 'Passwort zurücksetzen',
-    currentPassword: 'Aktuelles Passwort',
-    newPassword: 'Neues Passwort',
-    confirmPassword: 'Passwort bestätigen',
-    notEqual: 'Die Passwörter sind unterschiedlich.',
-    langGeniusAccount: 'Dify-Konto',
-    langGeniusAccountTip: 'Ihr Dify-Konto und zugehörige Benutzerdaten.',
-    editName: 'Namen bearbeiten',
-    showAppLength: '{{length}} Apps anzeigen',
-  },
-  members: {
-    team: 'Team',
-    invite: 'Hinzufügen',
-    name: 'NAME',
-    lastActive: 'ZULETZT AKTIV',
-    role: 'ROLLEN',
-    pending: 'Ausstehend...',
-    owner: 'Eigentümer',
-    admin: 'Admin',
-    adminTip: 'Kann Apps erstellen & Team-Einstellungen verwalten',
-    normal: 'Normal',
-    normalTip: 'Kann nur Apps verwenden, kann keine Apps erstellen',
-    inviteTeamMember: 'Teammitglied hinzufügen',
-    inviteTeamMemberTip: 'Sie können direkt nach der Anmeldung auf Ihre Teamdaten zugreifen.',
-    email: 'E-Mail',
-    emailInvalid: 'Ungültiges E-Mail-Format',
-    emailPlaceholder: 'Bitte E-Mails eingeben',
-    sendInvite: 'Einladung senden',
-    invitedAsRole: 'Eingeladen als {{role}}-Benutzer',
-    invitationSent: 'Einladung gesendet',
-    invitationSentTip: 'Einladung gesendet, und sie können sich bei Dify anmelden, um auf Ihre Teamdaten zuzugreifen.',
-    invitationLink: 'Einladungslink',
-    failedinvitationEmails: 'Die folgenden Benutzer wurden nicht erfolgreich eingeladen',
-    ok: 'OK',
-    removeFromTeam: 'Vom Team entfernen',
-    removeFromTeamTip: 'Wird den Teamzugang entfernen',
-    setAdmin: 'Als Administrator einstellen',
-    setMember: 'Als normales Mitglied einstellen',
-    disinvite: 'Einladung widerrufen',
-    deleteMember: 'Mitglied löschen',
-    you: '(Du)',
-  },
-  integrations: {
-    connected: 'Verbunden',
-    google: 'Google',
-    googleAccount: 'Mit Google-Konto anmelden',
-    github: 'GitHub',
-    githubAccount: 'Mit GitHub-Konto anmelden',
-    connect: 'Verbinden',
-  },
-  language: {
-    displayLanguage: 'Anzeigesprache',
-    timezone: 'Zeitzone',
-  },
-  provider: {
-    apiKey: 'API-Schlüssel',
-    enterYourKey: 'Geben Sie hier Ihren API-Schlüssel ein',
-    invalidKey: 'Ungültiger OpenAI API-Schlüssel',
-    validatedError: 'Validierung fehlgeschlagen: ',
-    validating: 'Schlüssel wird validiert...',
-    saveFailed: 'API-Schlüssel speichern fehlgeschlagen',
-    apiKeyExceedBill: 'Dieser API-SCHLÜSSEL verfügt über kein verfügbares Kontingent, bitte lesen',
-    addKey: 'Schlüssel hinzufügen',
-    comingSoon: 'Demnächst verfügbar',
-    editKey: 'Bearbeiten',
-    invalidApiKey: 'Ungültiger API-Schlüssel',
-    azure: {
-      apiBase: 'API-Basis',
-      apiBasePlaceholder: 'Die API-Basis-URL Ihres Azure OpenAI-Endpunkts.',
-      apiKey: 'API-Schlüssel',
-      apiKeyPlaceholder: 'Geben Sie hier Ihren API-Schlüssel ein',
-      helpTip: 'Azure OpenAI Service kennenlernen',
-    },
-    openaiHosted: {
-      openaiHosted: 'Gehostetes OpenAI',
-      onTrial: 'IN PROBE',
-      exhausted: 'KONTINGENT ERSCHÖPFT',
-      desc: 'Der OpenAI-Hostingdienst von Dify ermöglicht es Ihnen, Modelle wie GPT-3.5 zu verwenden. Bevor Ihr Probe-Kontingent aufgebraucht ist, müssen Sie andere Modellanbieter einrichten.',
-      callTimes: 'Anrufzeiten',
-      usedUp: 'Probe-Kontingent aufgebraucht. Eigenen Modellanbieter hinzufügen.',
-      useYourModel: 'Derzeit wird eigener Modellanbieter verwendet.',
-      close: 'Schließen',
-    },
-    anthropicHosted: {
-      anthropicHosted: 'Anthropic Claude',
-      onTrial: 'IN PROBE',
-      exhausted: 'KONTINGENT ERSCHÖPFT',
-      desc: 'Leistungsstarkes Modell, das bei einer Vielzahl von Aufgaben von anspruchsvollen Dialogen und kreativer Inhalteerstellung bis hin zu detaillierten Anweisungen hervorragend ist.',
-      callTimes: 'Anrufzeiten',
-      usedUp: 'Testkontingent aufgebraucht. Eigenen Modellanbieter hinzufügen.',
-      useYourModel: 'Derzeit wird eigener Modellanbieter verwendet.',
-      close: 'Schließen',
-    },
-    anthropic: {
-      using: 'Die Einbettungsfähigkeit verwendet',
-      enableTip: 'Um das Anthropische Modell zu aktivieren, müssen Sie sich zuerst mit OpenAI oder Azure OpenAI Service verbinden.',
-      notEnabled: 'Nicht aktiviert',
-      keyFrom: 'Holen Sie Ihren API-Schlüssel von Anthropic',
-    },
-    encrypted: {
-      front: 'Ihr API-SCHLÜSSEL wird verschlüsselt und mit',
-      back: ' Technologie gespeichert.',
-    },
-  },
-  modelProvider: {
-    notConfigured: 'Das Systemmodell wurde noch nicht vollständig konfiguriert, und einige Funktionen sind möglicherweise nicht verfügbar.',
-    systemModelSettings: 'Systemmodell-Einstellungen',
-    systemModelSettingsLink: 'Warum ist es notwendig, ein Systemmodell einzurichten?',
-    selectModel: 'Wählen Sie Ihr Modell',
-    setupModelFirst: 'Bitte richten Sie zuerst Ihr Modell ein',
-    systemReasoningModel: {
-      key: 'System-Reasoning-Modell',
-      tip: 'Legen Sie das Standardinferenzmodell fest, das für die Erstellung von Anwendungen verwendet wird, sowie Funktionen wie die Generierung von Dialognamen und die Vorschlagserstellung für die nächste Frage, die auch das Standardinferenzmodell verwenden.',
-    },
-    embeddingModel: {
-      key: 'Einbettungsmodell',
-      tip: 'Legen Sie das Standardmodell für die Dokumenteneinbettungsverarbeitung des Wissens fest, sowohl die Wiederherstellung als auch der Import des Wissens verwenden dieses Einbettungsmodell für die Vektorisierungsverarbeitung. Ein Wechsel wird dazu führen, dass die Vektordimension zwischen dem importierten Wissen und der Frage inkonsistent ist, was zu einem Wiederherstellungsfehler führt. Um einen Wiederherstellungsfehler zu vermeiden, wechseln Sie dieses Modell bitte nicht willkürlich.',
-      required: 'Einbettungsmodell ist erforderlich',
-    },
-    speechToTextModel: {
-      key: 'Sprach-zu-Text-Modell',
-      tip: 'Legen Sie das Standardmodell für die Spracheingabe in Konversationen fest.',
-    },
-    ttsModel: {
-      key: 'Text-zu-Sprache-Modell',
-      tip: 'Legen Sie das Standardmodell für die Text-zu-Sprache-Eingabe in Konversationen fest.',
-    },
-    rerankModel: {
-      key: 'Rerank-Modell',
-      tip: 'Rerank-Modell wird die Kandidatendokumentenliste basierend auf der semantischen Übereinstimmung mit der Benutzeranfrage neu ordnen und die Ergebnisse der semantischen Rangordnung verbessern',
-    },
-    quota: 'Kontingent',
-    searchModel: 'Suchmodell',
-    noModelFound: 'Kein Modell für {{model}} gefunden',
-    models: 'Modelle',
-    showMoreModelProvider: 'Zeige mehr Modellanbieter',
-    selector: {
-      tip: 'Dieses Modell wurde entfernt. Bitte fügen Sie ein Modell hinzu oder wählen Sie ein anderes Modell.',
-      emptyTip: 'Keine verfügbaren Modelle',
-      emptySetting: 'Bitte gehen Sie zu den Einstellungen, um zu konfigurieren',
-      rerankTip: 'Bitte richten Sie das Rerank-Modell ein',
-    },
-    card: {
-      quota: 'KONTINGENT',
-      onTrial: 'In Probe',
-      paid: 'Bezahlt',
-      quotaExhausted: 'Kontingent erschöpft',
-      callTimes: 'Anrufzeiten',
-      tokens: 'Token',
-      buyQuota: 'Kontingent kaufen',
-      priorityUse: 'Priorisierte Nutzung',
-      removeKey: 'API-Schlüssel entfernen',
-      tip: 'Der bezahlten Kontingent wird Vorrang gegeben. Das Testkontingent wird nach dem Verbrauch des bezahlten Kontingents verwendet.',
-    },
-    item: {
-      deleteDesc: '{{modelName}} werden als System-Reasoning-Modelle verwendet. Einige Funktionen stehen nach der Entfernung nicht zur Verfügung. Bitte bestätigen.',
-      freeQuota: 'KOSTENLOSES KONTINGENT',
-    },
-    addApiKey: 'Fügen Sie Ihren API-Schlüssel hinzu',
-    invalidApiKey: 'Ungültiger API-Schlüssel',
-    encrypted: {
-      front: 'Ihr API-SCHLÜSSEL wird verschlüsselt und mit',
-      back: ' Technologie gespeichert.',
-    },
-    freeQuota: {
-      howToEarn: 'Wie zu verdienen',
-    },
-    addMoreModelProvider: 'MEHR MODELLANBIETER HINZUFÜGEN',
-    addModel: 'Modell hinzufügen',
-    modelsNum: '{{num}} Modelle',
-    showModels: 'Modelle anzeigen',
-    showModelsNum: 'Zeige {{num}} Modelle',
-    collapse: 'Einklappen',
-    config: 'Konfigurieren',
-    modelAndParameters: 'Modell und Parameter',
-    model: 'Modell',
-    featureSupported: '{{feature}} unterstützt',
-    callTimes: 'Anrufzeiten',
-    credits: 'Nachrichtenguthaben',
-    buyQuota: 'Kontingent kaufen',
-    getFreeTokens: 'Kostenlose Token erhalten',
-    priorityUsing: 'Bevorzugte Nutzung',
-    deprecated: 'Veraltet',
-    confirmDelete: 'Löschung bestätigen?',
-    quotaTip: 'Verbleibende verfügbare kostenlose Token',
-    loadPresets: 'Voreinstellungen laden',
-    parameters: 'PARAMETER',
-  },
-  dataSource: {
-    add: 'Eine Datenquelle hinzufügen',
-    connect: 'Verbinden',
-    notion: {
-      title: 'Notion',
-      description: 'Notion als Datenquelle für das Wissen verwenden.',
-      connectedWorkspace: 'Verbundener Arbeitsbereich',
-      addWorkspace: 'Arbeitsbereich hinzufügen',
-      connected: 'Verbunden',
-      disconnected: 'Getrennt',
-      changeAuthorizedPages: 'Autorisierte Seiten ändern',
-      pagesAuthorized: 'Autorisierte Seiten',
-      sync: 'Synchronisieren',
-      remove: 'Entfernen',
-      selector: {
-        pageSelected: 'Ausgewählte Seiten',
-        searchPages: 'Seiten suchen...',
-        noSearchResult: 'Keine Suchergebnisse',
-        addPages: 'Seiten hinzufügen',
-        preview: 'VORSCHAU',
-      },
-    },
-  },
-  plugin: {
-    serpapi: {
-      apiKey: 'API-Schlüssel',
-      apiKeyPlaceholder: 'Geben Sie Ihren API-Schlüssel ein',
-      keyFrom: 'Holen Sie Ihren SerpAPI-Schlüssel von der SerpAPI-Kontoseite',
-    },
-  },
-  apiBasedExtension: {
-    title: 'API-Erweiterungen bieten zentralisiertes API-Management und vereinfachen die Konfiguration für eine einfache Verwendung in Difys Anwendungen.',
-    link: 'Erfahren Sie, wie Sie Ihre eigene API-Erweiterung entwickeln.',
-    linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension',
-    add: 'API-Erweiterung hinzufügen',
-    selector: {
-      title: 'API-Erweiterung',
-      placeholder: 'Bitte wählen Sie API-Erweiterung',
-      manage: 'API-Erweiterung verwalten',
-    },
-    modal: {
-      title: 'API-Erweiterung hinzufügen',
-      editTitle: 'API-Erweiterung bearbeiten',
-      name: {
-        title: 'Name',
-        placeholder: 'Bitte geben Sie den Namen ein',
-      },
-      apiEndpoint: {
-        title: 'API-Endpunkt',
-        placeholder: 'Bitte geben Sie den API-Endpunkt ein',
-      },
-      apiKey: {
-        title: 'API-Schlüssel',
-        placeholder: 'Bitte geben Sie den API-Schlüssel ein',
-        lengthError: 'Die Länge des API-Schlüssels darf nicht weniger als 5 Zeichen betragen',
-      },
-    },
-    type: 'Typ',
-  },
-  about: {
-    changeLog: 'Änderungsprotokoll',
-    updateNow: 'Jetzt aktualisieren',
-    nowAvailable: 'Dify {{version}} ist jetzt verfügbar.',
-    latestAvailable: 'Dify {{version}} ist die neueste verfügbare Version.',
-  },
-  appMenus: {
-    overview: 'Übersicht',
-    promptEng: 'Orchestrieren',
-    apiAccess: 'API-Zugriff',
-    logAndAnn: 'Protokolle & Ank.',
-  },
-  environment: {
-    testing: 'TESTEN',
-    development: 'ENTWICKLUNG',
-  },
-  appModes: {
-    completionApp: 'Textgenerator',
-    chatApp: 'Chat-App',
-  },
-  datasetMenus: {
-    documents: 'Dokumente',
-    hitTesting: 'Wiederherstellungstest',
-    settings: 'Einstellungen',
-    emptyTip: 'Das Wissen wurde nicht zugeordnet, bitte gehen Sie zur Anwendung oder zum Plug-in, um die Zuordnung abzuschließen.',
-    viewDoc: 'Dokumentation anzeigen',
-    relatedApp: 'verbundene Apps',
-  },
-  voiceInput: {
-    speaking: 'Sprechen Sie jetzt...',
-    converting: 'Umwandlung in Text...',
-    notAllow: 'Mikrofon nicht autorisiert',
-  },
-  modelName: {
-    'gpt-3.5-turbo': 'GPT-3.5-Turbo',
-    'gpt-3.5-turbo-16k': 'GPT-3.5-Turbo-16K',
-    'gpt-4': 'GPT-4',
-    'gpt-4-32k': 'GPT-4-32K',
-    'text-davinci-003': 'Text-Davinci-003',
-    'text-embedding-ada-002': 'Text-Embedding-Ada-002',
-    'whisper-1': 'Flüstern-1',
-    'claude-instant-1': 'Claude-Instant',
-    'claude-2': 'Claude-2',
-  },
-  chat: {
-    renameConversation: 'Konversation umbenennen',
-    conversationName: 'Konversationsname',
-    conversationNamePlaceholder: 'Bitte geben Sie den Konversationsnamen ein',
-    conversationNameCanNotEmpty: 'Konversationsname erforderlich',
-    citation: {
-      title: 'ZITIERUNGEN',
-      linkToDataset: 'Link zum Wissen',
-      characters: 'Zeichen:',
-      hitCount: 'Abrufanzahl:',
-      vectorHash: 'Vektorhash:',
-      hitScore: 'Abrufwertung:',
-    },
-  },
-  promptEditor: {
-    placeholder: 'Schreiben Sie hier Ihr Aufforderungswort, geben Sie \'{\' ein, um eine Variable einzufügen, geben Sie \'/\' ein, um einen Aufforderungs-Inhaltsblock einzufügen',
-    context: {
-      item: {
-        title: 'Kontext',
-        desc: 'Kontextvorlage einfügen',
-      },
-      modal: {
-        title: '{{num}} Wissen im Kontext',
-        add: 'Kontext hinzufügen',
-        footer: 'Sie können Kontexte im unten stehenden Kontextabschnitt verwalten.',
-      },
-    },
-    history: {
-      item: {
-        title: 'Konversationsgeschichte',
-        desc: 'Vorlage für historische Nachricht einfügen',
-      },
-      modal: {
-        title: 'BEISPIEL',
-        user: 'Hallo',
-        assistant: 'Hallo! Wie kann ich Ihnen heute helfen?',
-        edit: 'Konversationsrollennamen bearbeiten',
-      },
-    },
-    variable: {
-      item: {
-        title: 'Variablen & Externe Werkzeuge',
-        desc: 'Variablen & Externe Werkzeuge einfügen',
-      },
-      modal: {
-        add: 'Neue Variable',
-        addTool: 'Neues Werkzeug',
-      },
-    },
-    query: {
-      item: {
-        title: 'Abfrage',
-        desc: 'Benutzerabfragevorlage einfügen',
-      },
-    },
-    existed: 'Bereits im Aufforderungstext vorhanden',
-  },
-  imageUploader: {
-    uploadFromComputer: 'Vom Computer hochladen',
-    uploadFromComputerReadError: 'Bildlesung fehlgeschlagen, bitte versuchen Sie es erneut.',
-    uploadFromComputerUploadError: 'Bildupload fehlgeschlagen, bitte erneut hochladen.',
-    uploadFromComputerLimit: 'Hochgeladene Bilder dürfen {{size}} MB nicht überschreiten',
-    pasteImageLink: 'Bildlink einfügen',
-    pasteImageLinkInputPlaceholder: 'Bildlink hier einfügen',
-    pasteImageLinkInvalid: 'Ungültiger Bildlink',
-    imageUpload: 'Bild-Upload',
-  },
-  tag: {
-    placeholder: 'Alle Tags',
-    addNew: 'Neues Tag hinzufügen',
-    noTag: 'Keine Tags',
-    noTagYet: 'Noch keine Tags',
-    addTag: 'Tags hinzufügen',
-    editTag: 'Tags bearbeiten',
-    manageTags: 'Tags verwalten',
-    selectorPlaceholder: 'Typ zum Suchen oder Erstellen',
-    create: 'Erstellen',
-    delete: 'Tag löschen',
-    deleteTip: 'Das Tag wird verwendet, löschen?',
-    created: 'Tag erfolgreich erstellt',
-    failed: 'Tag-Erstellung fehlgeschlagen',
-  },
-}
-
-export default translation
+const translation = {
+  api: {
+    success: 'Erfolg',
+    actionSuccess: 'Aktion erfolgreich',
+    saved: 'Gespeichert',
+    create: 'Erstellt',
+    remove: 'Entfernt',
+  },
+  operation: {
+    create: 'Erstellen',
+    confirm: 'Bestätigen',
+    cancel: 'Abbrechen',
+    clear: 'Leeren',
+    save: 'Speichern',
+    edit: 'Bearbeiten',
+    add: 'Hinzufügen',
+    added: 'Hinzugefügt',
+    refresh: 'Neustart',
+    reset: 'Zurücksetzen',
+    search: 'Suchen',
+    change: 'Ändern',
+    remove: 'Entfernen',
+    send: 'Senden',
+    copy: 'Kopieren',
+    lineBreak: 'Zeilenumbruch',
+    sure: 'Ich bin sicher',
+    download: 'Herunterladen',
+    delete: 'Löschen',
+    settings: 'Einstellungen',
+    setup: 'Einrichten',
+    getForFree: 'Kostenlos erhalten',
+    reload: 'Neu laden',
+    ok: 'OK',
+    log: 'Protokoll',
+    learnMore: 'Mehr erfahren',
+    params: 'Parameter',
+    duplicate: 'Duplikat',
+    rename: 'Umbenennen',
+  },
+  placeholder: {
+    input: 'Bitte eingeben',
+    select: 'Bitte auswählen',
+  },
+  voice: {
+    language: {
+      zhHans: 'Chinesisch',
+      zhHant: 'Chinesisch (traditionell)',
+      enUS: 'Englisch',
+      deDE: 'Deutsch',
+      frFR: 'Französisch',
+      esES: 'Spanisch',
+      itIT: 'Italienisch',
+      thTH: 'Thailändisch',
+      idID: 'Indonesisch',
+      jaJP: 'Japanisch',
+      koKR: 'Koreanisch',
+      ptBR: 'Portugiesisch',
+      ruRU: 'Russisch',
+      ukUA: 'Ukrainisch',
+      viVN: 'Vietnamesisch',
+    },
+  },
+  unit: {
+    char: 'Zeichen',
+  },
+  actionMsg: {
+    noModification: 'Im Moment keine Änderungen.',
+    modifiedSuccessfully: 'Erfolgreich geändert',
+    modifiedUnsuccessfully: 'Änderung nicht erfolgreich',
+    copySuccessfully: 'Erfolgreich kopiert',
+    paySucceeded: 'Zahlung erfolgreich',
+    payCancelled: 'Zahlung abgebrochen',
+    generatedSuccessfully: 'Erfolgreich generiert',
+    generatedUnsuccessfully: 'Generierung nicht erfolgreich',
+  },
+  model: {
+    params: {
+      temperature: 'Temperatur',
+      temperatureTip:
+        'Kontrolliert Zufälligkeit: Eine niedrigere Temperatur führt zu weniger zufälligen Ergebnissen. Nähert sich die Temperatur null, wird das Modell deterministisch und repetitiv.',
+      top_p: 'Top P',
+      top_pTip:
+        'Kontrolliert Diversität über Nukleus-Sampling: 0,5 bedeutet, dass die Hälfte aller wahrscheinlichkeitsgewichteten Optionen berücksichtigt wird.',
+      presence_penalty: 'Präsenz-Strafe',
+      presence_penaltyTip:
+        'Wie stark neue Tokens basierend darauf bestraft werden, ob sie bereits im Text erschienen sind.\nErhöht die Wahrscheinlichkeit des Modells, über neue Themen zu sprechen.',
+      frequency_penalty: 'Häufigkeitsstrafe',
+      frequency_penaltyTip:
+        'Wie stark neue Tokens basierend auf ihrer bisherigen Häufigkeit im Text bestraft werden.\nVerringert die Wahrscheinlichkeit des Modells, denselben Satz wortwörtlich zu wiederholen.',
+      max_tokens: 'Maximale Token',
+      max_tokensTip:
+        'Begrenzt die maximale Länge der Antwort in Token. \nGrößere Werte können den Platz für Eingabeaufforderungen, Chat-Logs und Wissen begrenzen. \nEs wird empfohlen, dies unter zwei Dritteln zu setzen\ngpt-4-1106-Vorschau, gpt-4-vision-Vorschau maximale Token (Eingabe 128k Ausgabe 4k)',
+      maxTokenSettingTip: 'Ihre Einstellung für maximale Token ist hoch, was den Platz für Eingabeaufforderungen, Abfragen und Daten potenziell begrenzen kann. Erwägen Sie, dies unter 2/3 zu setzen.',
+      setToCurrentModelMaxTokenTip: 'Maximale Token auf 80 % der maximalen Token des aktuellen Modells {{maxToken}} aktualisiert.',
+      stop_sequences: 'Stop-Sequenzen',
+      stop_sequencesTip: 'Bis zu vier Sequenzen, bei denen die API die Generierung weiterer Token stoppt. Der zurückgegebene Text wird die Stop-Sequenz nicht enthalten.',
+      stop_sequencesPlaceholder: 'Sequenz eingeben und Tab drücken',
+    },
+    tone: {
+      Creative: 'Kreativ',
+      Balanced: 'Ausgewogen',
+      Precise: 'Präzise',
+      Custom: 'Benutzerdefiniert',
+    },
+    addMoreModel: 'Gehen Sie zu den Einstellungen, um mehr Modelle hinzuzufügen',
+  },
+  menus: {
+    status: 'Beta',
+    explore: 'Erkunden',
+    apps: 'Studio',
+    plugins: 'Plugins',
+    pluginsTips: 'Integrieren Sie Plugins von Drittanbietern oder erstellen Sie ChatGPT-kompatible KI-Plugins.',
+    datasets: 'Wissen',
+    datasetsTips: 'BALD VERFÜGBAR: Importieren Sie Ihre eigenen Textdaten oder schreiben Sie Daten in Echtzeit über Webhook, um den LLM-Kontext zu verbessern.',
+    newApp: 'Neue App',
+    newDataset: 'Wissen erstellen',
+    tools: 'Werkzeuge',
+  },
+  userProfile: {
+    settings: 'Einstellungen',
+    workspace: 'Arbeitsbereich',
+    createWorkspace: 'Arbeitsbereich erstellen',
+    helpCenter: 'Hilfe',
+    roadmapAndFeedback: 'Feedback',
+    community: 'Gemeinschaft',
+    about: 'Über',
+    logout: 'Abmelden',
+  },
+  settings: {
+    accountGroup: 'KONTO',
+    workplaceGroup: 'ARBEITSBEREICH',
+    account: 'Mein Konto',
+    members: 'Mitglieder',
+    billing: 'Abrechnung',
+    integrations: 'Integrationen',
+    language: 'Sprache',
+    provider: 'Modellanbieter',
+    dataSource: 'Datenquelle',
+    plugin: 'Plugins',
+    apiBasedExtension: 'API-Erweiterung',
+  },
+  account: {
+    avatar: 'Avatar',
+    name: 'Name',
+    email: 'E-Mail',
+    password: 'Passwort',
+    passwordTip: 'Sie können ein dauerhaftes Passwort festlegen, wenn Sie keine temporären Anmeldecodes verwenden möchten',
+    setPassword: 'Ein Passwort festlegen',
+    resetPassword: 'Passwort zurücksetzen',
+    currentPassword: 'Aktuelles Passwort',
+    newPassword: 'Neues Passwort',
+    confirmPassword: 'Passwort bestätigen',
+    notEqual: 'Die Passwörter sind unterschiedlich.',
+    langGeniusAccount: 'Dify-Konto',
+    langGeniusAccountTip: 'Ihr Dify-Konto und zugehörige Benutzerdaten.',
+    editName: 'Namen bearbeiten',
+    showAppLength: '{{length}} Apps anzeigen',
+  },
+  members: {
+    team: 'Team',
+    invite: 'Hinzufügen',
+    name: 'NAME',
+    lastActive: 'ZULETZT AKTIV',
+    role: 'ROLLEN',
+    pending: 'Ausstehend...',
+    owner: 'Eigentümer',
+    admin: 'Admin',
+    adminTip: 'Kann Apps erstellen & Team-Einstellungen verwalten',
+    normal: 'Normal',
+    normalTip: 'Kann nur Apps verwenden, kann keine Apps erstellen',
+    inviteTeamMember: 'Teammitglied hinzufügen',
+    inviteTeamMemberTip: 'Sie können direkt nach der Anmeldung auf Ihre Teamdaten zugreifen.',
+    email: 'E-Mail',
+    emailInvalid: 'Ungültiges E-Mail-Format',
+    emailPlaceholder: 'Bitte E-Mails eingeben',
+    sendInvite: 'Einladung senden',
+    invitedAsRole: 'Eingeladen als {{role}}-Benutzer',
+    invitationSent: 'Einladung gesendet',
+    invitationSentTip: 'Einladung gesendet, und sie können sich bei Dify anmelden, um auf Ihre Teamdaten zuzugreifen.',
+    invitationLink: 'Einladungslink',
+    failedinvitationEmails: 'Die folgenden Benutzer wurden nicht erfolgreich eingeladen',
+    ok: 'OK',
+    removeFromTeam: 'Vom Team entfernen',
+    removeFromTeamTip: 'Wird den Teamzugang entfernen',
+    setAdmin: 'Als Administrator einstellen',
+    setMember: 'Als normales Mitglied einstellen',
+    disinvite: 'Einladung widerrufen',
+    deleteMember: 'Mitglied löschen',
+    you: '(Du)',
+  },
+  integrations: {
+    connected: 'Verbunden',
+    google: 'Google',
+    googleAccount: 'Mit Google-Konto anmelden',
+    github: 'GitHub',
+    githubAccount: 'Mit GitHub-Konto anmelden',
+    connect: 'Verbinden',
+  },
+  language: {
+    displayLanguage: 'Anzeigesprache',
+    timezone: 'Zeitzone',
+  },
+  provider: {
+    apiKey: 'API-Schlüssel',
+    enterYourKey: 'Geben Sie hier Ihren API-Schlüssel ein',
+    invalidKey: 'Ungültiger OpenAI API-Schlüssel',
+    validatedError: 'Validierung fehlgeschlagen: ',
+    validating: 'Schlüssel wird validiert...',
+    saveFailed: 'API-Schlüssel speichern fehlgeschlagen',
+    apiKeyExceedBill: 'Dieser API-SCHLÜSSEL verfügt über kein verfügbares Kontingent, bitte lesen',
+    addKey: 'Schlüssel hinzufügen',
+    comingSoon: 'Demnächst verfügbar',
+    editKey: 'Bearbeiten',
+    invalidApiKey: 'Ungültiger API-Schlüssel',
+    azure: {
+      apiBase: 'API-Basis',
+      apiBasePlaceholder: 'Die API-Basis-URL Ihres Azure OpenAI-Endpunkts.',
+      apiKey: 'API-Schlüssel',
+      apiKeyPlaceholder: 'Geben Sie hier Ihren API-Schlüssel ein',
+      helpTip: 'Azure OpenAI Service kennenlernen',
+    },
+    openaiHosted: {
+      openaiHosted: 'Gehostetes OpenAI',
+      onTrial: 'IN PROBE',
+      exhausted: 'KONTINGENT ERSCHÖPFT',
+      desc: 'Der OpenAI-Hostingdienst von Dify ermöglicht es Ihnen, Modelle wie GPT-3.5 zu verwenden. Bevor Ihr Probe-Kontingent aufgebraucht ist, müssen Sie andere Modellanbieter einrichten.',
+      callTimes: 'Anrufzeiten',
+      usedUp: 'Probe-Kontingent aufgebraucht. Eigenen Modellanbieter hinzufügen.',
+      useYourModel: 'Derzeit wird eigener Modellanbieter verwendet.',
+      close: 'Schließen',
+    },
+    anthropicHosted: {
+      anthropicHosted: 'Anthropic Claude',
+      onTrial: 'IN PROBE',
+      exhausted: 'KONTINGENT ERSCHÖPFT',
+      desc: 'Leistungsstarkes Modell, das bei einer Vielzahl von Aufgaben von anspruchsvollen Dialogen und kreativer Inhalteerstellung bis hin zu detaillierten Anweisungen hervorragend ist.',
+      callTimes: 'Anrufzeiten',
+      usedUp: 'Testkontingent aufgebraucht. Eigenen Modellanbieter hinzufügen.',
+      useYourModel: 'Derzeit wird eigener Modellanbieter verwendet.',
+      close: 'Schließen',
+    },
+    anthropic: {
+      using: 'Die Einbettungsfähigkeit verwendet',
+      enableTip: 'Um das Anthropische Modell zu aktivieren, müssen Sie sich zuerst mit OpenAI oder Azure OpenAI Service verbinden.',
+      notEnabled: 'Nicht aktiviert',
+      keyFrom: 'Holen Sie Ihren API-Schlüssel von Anthropic',
+    },
+    encrypted: {
+      front: 'Ihr API-SCHLÜSSEL wird verschlüsselt und mit',
+      back: ' Technologie gespeichert.',
+    },
+  },
+  modelProvider: {
+    notConfigured: 'Das Systemmodell wurde noch nicht vollständig konfiguriert, und einige Funktionen sind möglicherweise nicht verfügbar.',
+    systemModelSettings: 'Systemmodell-Einstellungen',
+    systemModelSettingsLink: 'Warum ist es notwendig, ein Systemmodell einzurichten?',
+    selectModel: 'Wählen Sie Ihr Modell',
+    setupModelFirst: 'Bitte richten Sie zuerst Ihr Modell ein',
+    systemReasoningModel: {
+      key: 'System-Reasoning-Modell',
+      tip: 'Legen Sie das Standardinferenzmodell fest, das für die Erstellung von Anwendungen verwendet wird, sowie Funktionen wie die Generierung von Dialognamen und die Vorschlagserstellung für die nächste Frage, die auch das Standardinferenzmodell verwenden.',
+    },
+    embeddingModel: {
+      key: 'Einbettungsmodell',
+      tip: 'Legen Sie das Standardmodell für die Dokumenteneinbettungsverarbeitung des Wissens fest, sowohl die Wiederherstellung als auch der Import des Wissens verwenden dieses Einbettungsmodell für die Vektorisierungsverarbeitung. Ein Wechsel wird dazu führen, dass die Vektordimension zwischen dem importierten Wissen und der Frage inkonsistent ist, was zu einem Wiederherstellungsfehler führt. Um einen Wiederherstellungsfehler zu vermeiden, wechseln Sie dieses Modell bitte nicht willkürlich.',
+      required: 'Einbettungsmodell ist erforderlich',
+    },
+    speechToTextModel: {
+      key: 'Sprach-zu-Text-Modell',
+      tip: 'Legen Sie das Standardmodell für die Spracheingabe in Konversationen fest.',
+    },
+    ttsModel: {
+      key: 'Text-zu-Sprache-Modell',
+      tip: 'Legen Sie das Standardmodell für die Text-zu-Sprache-Eingabe in Konversationen fest.',
+    },
+    rerankModel: {
+      key: 'Rerank-Modell',
+      tip: 'Rerank-Modell wird die Kandidatendokumentenliste basierend auf der semantischen Übereinstimmung mit der Benutzeranfrage neu ordnen und die Ergebnisse der semantischen Rangordnung verbessern',
+    },
+    quota: 'Kontingent',
+    searchModel: 'Suchmodell',
+    noModelFound: 'Kein Modell für {{model}} gefunden',
+    models: 'Modelle',
+    showMoreModelProvider: 'Zeige mehr Modellanbieter',
+    selector: {
+      tip: 'Dieses Modell wurde entfernt. Bitte fügen Sie ein Modell hinzu oder wählen Sie ein anderes Modell.',
+      emptyTip: 'Keine verfügbaren Modelle',
+      emptySetting: 'Bitte gehen Sie zu den Einstellungen, um zu konfigurieren',
+      rerankTip: 'Bitte richten Sie das Rerank-Modell ein',
+    },
+    card: {
+      quota: 'KONTINGENT',
+      onTrial: 'In Probe',
+      paid: 'Bezahlt',
+      quotaExhausted: 'Kontingent erschöpft',
+      callTimes: 'Anrufzeiten',
+      tokens: 'Token',
+      buyQuota: 'Kontingent kaufen',
+      priorityUse: 'Priorisierte Nutzung',
+      removeKey: 'API-Schlüssel entfernen',
+      tip: 'Der bezahlten Kontingent wird Vorrang gegeben. Das Testkontingent wird nach dem Verbrauch des bezahlten Kontingents verwendet.',
+    },
+    item: {
+      deleteDesc: '{{modelName}} werden als System-Reasoning-Modelle verwendet. Einige Funktionen stehen nach der Entfernung nicht zur Verfügung. Bitte bestätigen.',
+      freeQuota: 'KOSTENLOSES KONTINGENT',
+    },
+    addApiKey: 'Fügen Sie Ihren API-Schlüssel hinzu',
+    invalidApiKey: 'Ungültiger API-Schlüssel',
+    encrypted: {
+      front: 'Ihr API-SCHLÜSSEL wird verschlüsselt und mit',
+      back: ' Technologie gespeichert.',
+    },
+    freeQuota: {
+      howToEarn: 'Wie zu verdienen',
+    },
+    addMoreModelProvider: 'MEHR MODELLANBIETER HINZUFÜGEN',
+    addModel: 'Modell hinzufügen',
+    modelsNum: '{{num}} Modelle',
+    showModels: 'Modelle anzeigen',
+    showModelsNum: 'Zeige {{num}} Modelle',
+    collapse: 'Einklappen',
+    config: 'Konfigurieren',
+    modelAndParameters: 'Modell und Parameter',
+    model: 'Modell',
+    featureSupported: '{{feature}} unterstützt',
+    callTimes: 'Anrufzeiten',
+    credits: 'Nachrichtenguthaben',
+    buyQuota: 'Kontingent kaufen',
+    getFreeTokens: 'Kostenlose Token erhalten',
+    priorityUsing: 'Bevorzugte Nutzung',
+    deprecated: 'Veraltet',
+    confirmDelete: 'Löschung bestätigen?',
+    quotaTip: 'Verbleibende verfügbare kostenlose Token',
+    loadPresets: 'Voreinstellungen laden',
+    parameters: 'PARAMETER',
+  },
+  dataSource: {
+    add: 'Eine Datenquelle hinzufügen',
+    connect: 'Verbinden',
+    notion: {
+      title: 'Notion',
+      description: 'Notion als Datenquelle für das Wissen verwenden.',
+      connectedWorkspace: 'Verbundener Arbeitsbereich',
+      addWorkspace: 'Arbeitsbereich hinzufügen',
+      connected: 'Verbunden',
+      disconnected: 'Getrennt',
+      changeAuthorizedPages: 'Autorisierte Seiten ändern',
+      pagesAuthorized: 'Autorisierte Seiten',
+      sync: 'Synchronisieren',
+      remove: 'Entfernen',
+      selector: {
+        pageSelected: 'Ausgewählte Seiten',
+        searchPages: 'Seiten suchen...',
+        noSearchResult: 'Keine Suchergebnisse',
+        addPages: 'Seiten hinzufügen',
+        preview: 'VORSCHAU',
+      },
+    },
+  },
+  plugin: {
+    serpapi: {
+      apiKey: 'API-Schlüssel',
+      apiKeyPlaceholder: 'Geben Sie Ihren API-Schlüssel ein',
+      keyFrom: 'Holen Sie Ihren SerpAPI-Schlüssel von der SerpAPI-Kontoseite',
+    },
+  },
+  apiBasedExtension: {
+    title: 'API-Erweiterungen bieten zentralisiertes API-Management und vereinfachen die Konfiguration für eine einfache Verwendung in Difys Anwendungen.',
+    link: 'Erfahren Sie, wie Sie Ihre eigene API-Erweiterung entwickeln.',
+    linkUrl: 'https://docs.dify.ai/features/extension/api_based_extension',
+    add: 'API-Erweiterung hinzufügen',
+    selector: {
+      title: 'API-Erweiterung',
+      placeholder: 'Bitte wählen Sie API-Erweiterung',
+      manage: 'API-Erweiterung verwalten',
+    },
+    modal: {
+      title: 'API-Erweiterung hinzufügen',
+      editTitle: 'API-Erweiterung bearbeiten',
+      name: {
+        title: 'Name',
+        placeholder: 'Bitte geben Sie den Namen ein',
+      },
+      apiEndpoint: {
+        title: 'API-Endpunkt',
+        placeholder: 'Bitte geben Sie den API-Endpunkt ein',
+      },
+      apiKey: {
+        title: 'API-Schlüssel',
+        placeholder: 'Bitte geben Sie den API-Schlüssel ein',
+        lengthError: 'Die Länge des API-Schlüssels darf nicht weniger als 5 Zeichen betragen',
+      },
+    },
+    type: 'Typ',
+  },
+  about: {
+    changeLog: 'Änderungsprotokoll',
+    updateNow: 'Jetzt aktualisieren',
+    nowAvailable: 'Dify {{version}} ist jetzt verfügbar.',
+    latestAvailable: 'Dify {{version}} ist die neueste verfügbare Version.',
+  },
+  appMenus: {
+    overview: 'Übersicht',
+    promptEng: 'Orchestrieren',
+    apiAccess: 'API-Zugriff',
+    logAndAnn: 'Protokolle & Ank.',
+  },
+  environment: {
+    testing: 'TESTEN',
+    development: 'ENTWICKLUNG',
+  },
+  appModes: {
+    completionApp: 'Textgenerator',
+    chatApp: 'Chat-App',
+  },
+  datasetMenus: {
+    documents: 'Dokumente',
+    hitTesting: 'Wiederherstellungstest',
+    settings: 'Einstellungen',
+    emptyTip: 'Das Wissen wurde nicht zugeordnet, bitte gehen Sie zur Anwendung oder zum Plug-in, um die Zuordnung abzuschließen.',
+    viewDoc: 'Dokumentation anzeigen',
+    relatedApp: 'verbundene Apps',
+  },
+  voiceInput: {
+    speaking: 'Sprechen Sie jetzt...',
+    converting: 'Umwandlung in Text...',
+    notAllow: 'Mikrofon nicht autorisiert',
+  },
+  modelName: {
+    'gpt-3.5-turbo': 'GPT-3.5-Turbo',
+    'gpt-3.5-turbo-16k': 'GPT-3.5-Turbo-16K',
+    'gpt-4': 'GPT-4',
+    'gpt-4-32k': 'GPT-4-32K',
+    'text-davinci-003': 'Text-Davinci-003',
+    'text-embedding-ada-002': 'Text-Embedding-Ada-002',
+    'whisper-1': 'Flüstern-1',
+    'claude-instant-1': 'Claude-Instant',
+    'claude-2': 'Claude-2',
+  },
+  chat: {
+    renameConversation: 'Konversation umbenennen',
+    conversationName: 'Konversationsname',
+    conversationNamePlaceholder: 'Bitte geben Sie den Konversationsnamen ein',
+    conversationNameCanNotEmpty: 'Konversationsname erforderlich',
+    citation: {
+      title: 'ZITIERUNGEN',
+      linkToDataset: 'Link zum Wissen',
+      characters: 'Zeichen:',
+      hitCount: 'Abrufanzahl:',
+      vectorHash: 'Vektorhash:',
+      hitScore: 'Abrufwertung:',
+    },
+  },
+  promptEditor: {
+    placeholder: 'Schreiben Sie hier Ihr Aufforderungswort, geben Sie \'{\' ein, um eine Variable einzufügen, geben Sie \'/\' ein, um einen Aufforderungs-Inhaltsblock einzufügen',
+    context: {
+      item: {
+        title: 'Kontext',
+        desc: 'Kontextvorlage einfügen',
+      },
+      modal: {
+        title: '{{num}} Wissen im Kontext',
+        add: 'Kontext hinzufügen',
+        footer: 'Sie können Kontexte im unten stehenden Kontextabschnitt verwalten.',
+      },
+    },
+    history: {
+      item: {
+        title: 'Konversationsgeschichte',
+        desc: 'Vorlage für historische Nachricht einfügen',
+      },
+      modal: {
+        title: 'BEISPIEL',
+        user: 'Hallo',
+        assistant: 'Hallo! Wie kann ich Ihnen heute helfen?',
+        edit: 'Konversationsrollennamen bearbeiten',
+      },
+    },
+    variable: {
+      item: {
+        title: 'Variablen & Externe Werkzeuge',
+        desc: 'Variablen & Externe Werkzeuge einfügen',
+      },
+      modal: {
+        add: 'Neue Variable',
+        addTool: 'Neues Werkzeug',
+      },
+    },
+    query: {
+      item: {
+        title: 'Abfrage',
+        desc: 'Benutzerabfragevorlage einfügen',
+      },
+    },
+    existed: 'Bereits im Aufforderungstext vorhanden',
+  },
+  imageUploader: {
+    uploadFromComputer: 'Vom Computer hochladen',
+    uploadFromComputerReadError: 'Bildlesung fehlgeschlagen, bitte versuchen Sie es erneut.',
+    uploadFromComputerUploadError: 'Bildupload fehlgeschlagen, bitte erneut hochladen.',
+    uploadFromComputerLimit: 'Hochgeladene Bilder dürfen {{size}} MB nicht überschreiten',
+    pasteImageLink: 'Bildlink einfügen',
+    pasteImageLinkInputPlaceholder: 'Bildlink hier einfügen',
+    pasteImageLinkInvalid: 'Ungültiger Bildlink',
+    imageUpload: 'Bild-Upload',
+  },
+  tag: {
+    placeholder: 'Alle Tags',
+    addNew: 'Neues Tag hinzufügen',
+    noTag: 'Keine Tags',
+    noTagYet: 'Noch keine Tags',
+    addTag: 'Tags hinzufügen',
+    editTag: 'Tags bearbeiten',
+    manageTags: 'Tags verwalten',
+    selectorPlaceholder: 'Typ zum Suchen oder Erstellen',
+    create: 'Erstellen',
+    delete: 'Tag löschen',
+    deleteTip: 'Das Tag wird verwendet, löschen?',
+    created: 'Tag erfolgreich erstellt',
+    failed: 'Tag-Erstellung fehlgeschlagen',
+  },
+}
+
+export default translation

+ 60 - 59
web/i18n/de-DE/login.ts

@@ -1,59 +1,60 @@
-const translation = {
-  pageTitle: 'Hey, lass uns anfangen!👋',
-  welcome: 'Willkommen bei Dify, bitte melde dich an, um fortzufahren.',
-  email: 'E-Mail-Adresse',
-  emailPlaceholder: 'Deine E-Mail',
-  password: 'Passwort',
-  passwordPlaceholder: 'Dein Passwort',
-  name: 'Benutzername',
-  namePlaceholder: 'Dein Benutzername',
-  forget: 'Passwort vergessen?',
-  signBtn: 'Anmelden',
-  installBtn: 'Einrichten',
-  setAdminAccount: 'Admin-Konto einrichten',
-  setAdminAccountDesc: 'Maximale Berechtigungen für das Admin-Konto, das verwendet werden kann, um Anwendungen zu erstellen und LLM-Anbieter usw. zu verwalten.',
-  createAndSignIn: 'Erstellen und anmelden',
-  oneMoreStep: 'Nur noch ein Schritt',
-  createSample: 'Basierend auf diesen Informationen erstellen wir eine Beispielanwendung für dich',
-  invitationCode: 'Einladungscode',
-  invitationCodePlaceholder: 'Dein Einladungscode',
-  interfaceLanguage: 'Oberflächensprache',
-  timezone: 'Zeitzone',
-  go: 'Zu Dify gehen',
-  sendUsMail: 'Sende uns deine Vorstellung per E-Mail, und wir bearbeiten die Einladungsanfrage.',
-  acceptPP: 'Ich habe die Datenschutzbestimmungen gelesen und akzeptiere sie',
-  reset: 'Bitte führe den folgenden Befehl aus, um dein Passwort zurückzusetzen',
-  withGitHub: 'Mit GitHub fortfahren',
-  withGoogle: 'Mit Google fortfahren',
-  rightTitle: 'Das volle Potenzial von LLM ausschöpfen',
-  rightDesc: 'Mühelos optisch ansprechende, bedienbare und verbesserbare KI-Anwendungen erstellen.',
-  tos: 'Nutzungsbedingungen',
-  pp: 'Datenschutzbestimmungen',
-  tosDesc: 'Mit der Anmeldung stimmst du unseren',
-  donthave: 'Hast du nicht?',
-  invalidInvitationCode: 'Ungültiger Einladungscode',
-  accountAlreadyInited: 'Konto bereits initialisiert',
-  error: {
-    emailEmpty: 'E-Mail-Adresse wird benötigt',
-    emailInValid: 'Bitte gib eine gültige E-Mail-Adresse ein',
-    nameEmpty: 'Name wird benötigt',
-    passwordEmpty: 'Passwort wird benötigt',
-    passwordInvalid: 'Das Passwort muss Buchstaben und Zahlen enthalten und länger als 8 Zeichen sein',
-  },
-  license: {
-    tip: 'Bevor du mit Dify Community Edition beginnst, lies die',
-    link: 'Open-Source-Lizenz',
-  },
-  join: 'Beitreten',
-  joinTipStart: 'Lade dich ein, dem',
-  joinTipEnd: 'Team auf Dify beizutreten',
-  invalid: 'Der Link ist abgelaufen',
-  explore: 'Dify erkunden',
-  activatedTipStart: 'Du bist dem',
-  activatedTipEnd: 'Team beigetreten',
-  activated: 'Jetzt anmelden',
-  adminInitPassword: 'Admin-Initialpasswort',
-  validate: 'Validieren',
-}
-
-export default translation
+const translation = {
+  pageTitle: 'Hey, lass uns anfangen!👋',
+  welcome: 'Willkommen bei Dify, bitte melde dich an, um fortzufahren.',
+  email: 'E-Mail-Adresse',
+  emailPlaceholder: 'Deine E-Mail',
+  password: 'Passwort',
+  passwordPlaceholder: 'Dein Passwort',
+  name: 'Benutzername',
+  namePlaceholder: 'Dein Benutzername',
+  forget: 'Passwort vergessen?',
+  signBtn: 'Anmelden',
+  installBtn: 'Einrichten',
+  setAdminAccount: 'Admin-Konto einrichten',
+  setAdminAccountDesc: 'Maximale Berechtigungen für das Admin-Konto, das verwendet werden kann, um Anwendungen zu erstellen und LLM-Anbieter usw. zu verwalten.',
+  createAndSignIn: 'Erstellen und anmelden',
+  oneMoreStep: 'Nur noch ein Schritt',
+  createSample: 'Basierend auf diesen Informationen erstellen wir eine Beispielanwendung für dich',
+  invitationCode: 'Einladungscode',
+  invitationCodePlaceholder: 'Dein Einladungscode',
+  interfaceLanguage: 'Oberflächensprache',
+  timezone: 'Zeitzone',
+  go: 'Zu Dify gehen',
+  sendUsMail: 'Sende uns deine Vorstellung per E-Mail, und wir bearbeiten die Einladungsanfrage.',
+  acceptPP: 'Ich habe die Datenschutzbestimmungen gelesen und akzeptiere sie',
+  reset: 'Bitte führe den folgenden Befehl aus, um dein Passwort zurückzusetzen',
+  withGitHub: 'Mit GitHub fortfahren',
+  withGoogle: 'Mit Google fortfahren',
+  rightTitle: 'Das volle Potenzial von LLM ausschöpfen',
+  rightDesc: 'Mühelos optisch ansprechende, bedienbare und verbesserbare KI-Anwendungen erstellen.',
+  tos: 'Nutzungsbedingungen',
+  pp: 'Datenschutzbestimmungen',
+  tosDesc: 'Mit der Anmeldung stimmst du unseren',
+  goToInit: 'Wenn du das Konto noch nicht initialisiert hast, gehe bitte zur Initialisierungsseite',
+  donthave: 'Hast du nicht?',
+  invalidInvitationCode: 'Ungültiger Einladungscode',
+  accountAlreadyInited: 'Konto bereits initialisiert',
+  error: {
+    emailEmpty: 'E-Mail-Adresse wird benötigt',
+    emailInValid: 'Bitte gib eine gültige E-Mail-Adresse ein',
+    nameEmpty: 'Name wird benötigt',
+    passwordEmpty: 'Passwort wird benötigt',
+    passwordInvalid: 'Das Passwort muss Buchstaben und Zahlen enthalten und länger als 8 Zeichen sein',
+  },
+  license: {
+    tip: 'Bevor du mit Dify Community Edition beginnst, lies die',
+    link: 'Open-Source-Lizenz',
+  },
+  join: 'Beitreten',
+  joinTipStart: 'Lade dich ein, dem',
+  joinTipEnd: 'Team auf Dify beizutreten',
+  invalid: 'Der Link ist abgelaufen',
+  explore: 'Dify erkunden',
+  activatedTipStart: 'Du bist dem',
+  activatedTipEnd: 'Team beigetreten',
+  activated: 'Jetzt anmelden',
+  adminInitPassword: 'Admin-Initialpasswort',
+  validate: 'Validieren',
+}
+
+export default translation

+ 2 - 0
web/i18n/en-US/common.ts

@@ -44,6 +44,7 @@ const translation = {
   voice: {
     language: {
       zhHans: 'Chinese',
+      zhHant: 'Traditional Chinese',
       enUS: 'English',
       deDE: 'German',
       frFR: 'French',
@@ -56,6 +57,7 @@ const translation = {
       ptBR: 'Portuguese',
       ruRU: 'Russian',
       ukUA: 'Ukrainian',
+      viVN: 'Vietnamese',
     },
   },
   unit: {

+ 1 - 0
web/i18n/en-US/login.ts

@@ -31,6 +31,7 @@ const translation = {
   tos: 'Terms of Service',
   pp: 'Privacy Policy',
   tosDesc: 'By signing up, you agree to our',
+  goToInit: 'If you have not initialized the account, please go to the initialization page',
   donthave: 'Don\'t have?',
   invalidInvitationCode: 'Invalid invitation code',
   accountAlreadyInited: 'Account already initialized',

+ 2 - 0
web/i18n/fr-FR/common.ts

@@ -44,6 +44,7 @@ const translation = {
   voice: {
     language: {
       zhHans: 'Chinois',
+      zhHant: 'Chinois (traditionnel)',
       enUS: 'Anglais',
       deDE: 'Allemand',
       frFR: 'Français',
@@ -56,6 +57,7 @@ const translation = {
       ptBR: 'Portugais',
       ruRU: 'Russe',
       ukUA: 'Ukrainien',
+      viVN: 'Vietnamien',
     },
   },
   unit: {

+ 1 - 0
web/i18n/fr-FR/login.ts

@@ -30,6 +30,7 @@ const translation = {
   tos: 'Conditions de Service',
   pp: 'Politique de Confidentialité',
   tosDesc: 'En vous inscrivant, vous acceptez nos',
+  goToInit: 'Si vous n\'avez pas initialisé le compte, veuillez vous rendre sur la page d\'initialisation',
   donthave: 'Vous n\'avez pas ?',
   invalidInvitationCode: 'Code d\'invitation invalide',
   accountAlreadyInited: 'Compte déjà initialisé',

+ 0 - 4
web/i18n/index.ts

@@ -11,10 +11,6 @@ export const i18n = {
 
 export type Locale = typeof i18n['locales'][number]
 
-export const getLocaleOnClient = (): Locale => {
-  return Cookies.get(LOCALE_COOKIE_NAME) as Locale || i18n.defaultLocale
-}
-
 export const setLocaleOnClient = (locale: Locale, reloadPage = true) => {
   Cookies.set(LOCALE_COOKIE_NAME, locale)
   changeLanguage(locale)

+ 2 - 0
web/i18n/ja-JP/common.ts

@@ -44,6 +44,7 @@ const translation = {
   voice: {
     language: {
       zhHans: '中国語',
+      zhHant: '繁体字中国語',
       enUS: '英語',
       deDE: 'ドイツ語',
       frFR: 'フランス語',
@@ -56,6 +57,7 @@ const translation = {
       ptBR: 'ポルトガル語',
       ruRU: 'ロシア語',
       ukUA: 'ウクライナ語',
+      viVN: 'ベトナム語',
     },
   },
   unit: {

+ 1 - 0
web/i18n/ja-JP/login.ts

@@ -30,6 +30,7 @@ const translation = {
   tos: '利用規約',
   pp: 'プライバシーポリシー',
   tosDesc: 'サインアップすることで、以下に同意するものとします',
+  goToInit: 'アカウントを初期化していない場合は、初期化ページに移動してください',
   donthave: 'お持ちでない場合',
   invalidInvitationCode: '無効な招待コード',
   accountAlreadyInited: 'アカウントは既に初期化されています',

+ 2 - 0
web/i18n/pt-BR/common.ts

@@ -44,6 +44,7 @@ const translation = {
   voice: {
     language: {
       zhHans: 'Chinês',
+      zhHant: 'Chinês Tradicional',
       enUS: 'Inglês',
       deDE: 'Alemão',
       frFR: 'Francês',
@@ -56,6 +57,7 @@ const translation = {
       ptBR: 'Português',
       ruRU: 'Russo',
       ukUA: 'Ucraniano',
+      viVN: 'Vietnamita',
     },
   },
   unit: {

+ 1 - 0
web/i18n/pt-BR/login.ts

@@ -30,6 +30,7 @@ const translation = {
   tos: 'Termos de Serviço',
   pp: 'Política de Privacidade',
   tosDesc: 'Ao se inscrever, você concorda com nossos',
+  goToInit: 'Se você não inicializou a conta, vá para a página de inicialização',
   donthave: 'Não tem?',
   invalidInvitationCode: 'Código de convite inválido',
   accountAlreadyInited: 'Conta já iniciada',

+ 2 - 0
web/i18n/uk-UA/common.ts

@@ -44,6 +44,7 @@ const translation = {
   voice: {
     language: {
       zhHans: 'Китайська',
+      zhHant: 'Китайська (традиційна)',
       enUS: 'Англійська',
       deDE: 'Німецька',
       frFR: 'Французька',
@@ -56,6 +57,7 @@ const translation = {
       ptBR: 'Португальська',
       ruRU: 'Російська',
       ukUA: 'Українська',
+      viVN: 'В\'є тнамська',
     },
   },
   unit: {

+ 1 - 0
web/i18n/uk-UA/login.ts

@@ -30,6 +30,7 @@ const translation = {
   tos: 'Умови обслуговування',
   pp: 'Політика конфіденційності',
   tosDesc: 'Реєструючись, ви приймаєте наші',
+  goToInit: 'Якщо ви ще не ініціалізували обліковий запис, перейдіть на сторінку ініціалізації',
   donthave: 'Не маєте?',
   invalidInvitationCode: 'Недійсний код запрошення',
   accountAlreadyInited: 'Обліковий запис уже ініціалізовано',

+ 1 - 0
web/i18n/vi-VN/common.ts

@@ -56,6 +56,7 @@ const translation = {
       ptBR: 'Tiếng Bồ Đào Nha',
       ruRU: 'Tiếng Nga',
       ukUA: 'Tiếng Ukraina',
+      viVN: 'Tiếng Việt',
     },
   },
   unit: {

+ 1 - 0
web/i18n/vi-VN/login.ts

@@ -30,6 +30,7 @@ const translation = {
   tos: 'Điều khoản dịch vụ',
   pp: 'Chính sách bảo mật',
   tosDesc: 'Bằng cách đăng ký, bạn đồng ý với',
+  goToInit: 'Nếu bạn chưa khởi tạo tài khoản, vui lòng đi đến trang khởi tạo',
   donthave: 'Chưa có?',
   invalidInvitationCode: 'Mã mời không hợp lệ',
   accountAlreadyInited: 'Tài khoản đã được khởi tạo',

+ 2 - 0
web/i18n/zh-Hans/common.ts

@@ -44,6 +44,7 @@ const translation = {
   voice: {
     language: {
       zhHans: '中文',
+      zhHant: '繁体中文',
       enUS: '英语',
       deDE: '德语',
       frFR: '法语',
@@ -56,6 +57,7 @@ const translation = {
       ptBR: '葡萄牙语',
       ruRU: '俄语',
       ukUA: '乌克兰语',
+      viVN: '越南语',
     },
   },
   unit: {

+ 1 - 0
web/i18n/zh-Hans/login.ts

@@ -30,6 +30,7 @@ const translation = {
   tos: '使用协议',
   pp: '隐私政策',
   tosDesc: '使用即代表你并同意我们的',
+  goToInit: '如果您还没有初始化账户,请前往初始化页面',
   donthave: '还没有邀请码?',
   invalidInvitationCode: '无效的邀请码',
   accountAlreadyInited: '账户已经初始化',

+ 2 - 0
web/i18n/zh-Hant/common.ts

@@ -44,6 +44,7 @@ const translation = {
   voice: {
     language: {
       zhHans: '中文',
+      zhHant: '繁體中文',
       enUS: '英語',
       deDE: '德語',
       frFR: '法語',
@@ -56,6 +57,7 @@ const translation = {
       ptBR: '葡萄牙語',
       ruRU: '俄語',
       ukUA: '烏克蘭語',
+      viVN: '越南語',
     },
   },
   unit: {

+ 1 - 0
web/i18n/zh-Hant/login.ts

@@ -30,6 +30,7 @@ const translation = {
   tos: '使用協議',
   pp: '隱私政策',
   tosDesc: '使用即代表你並同意我們的',
+  goToInit: '如果您還沒有初始化賬戶,請前往初始化頁面',
   donthave: '還沒有邀請碼?',
   invalidInvitationCode: '無效的邀請碼',
   accountAlreadyInited: '賬戶已經初始化',