Jelajahi Sumber

Feat/i18n restructure (#2529)

crazywoola 1 tahun lalu
induk
melakukan
9574730050
100 mengubah file dengan 385 tambahan dan 596 penghapusan
  1. 1 0
      web/.eslintrc.json
  2. 1 2
      web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx
  3. 1 2
      web/app/(commonLayout)/apps/page.tsx
  4. 4 5
      web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx
  5. 3 10
      web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx
  6. 2 3
      web/app/(commonLayout)/datasets/Doc.tsx
  7. 4 4
      web/app/activate/activateForm.tsx
  8. 1 1
      web/app/components/app-sidebar/basic.tsx
  9. 3 4
      web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx
  10. 4 5
      web/app/components/app/annotation/header-opts/index.tsx
  11. 2 3
      web/app/components/app/configuration/config-prompt/conversation-histroy/history-panel.tsx
  12. 1 1
      web/app/components/app/configuration/config-voice/param-config-content.tsx
  13. 2 3
      web/app/components/app/configuration/config/agent/agent-tools/choose-tool/index.tsx
  14. 2 2
      web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx
  15. 1 1
      web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx
  16. 2 3
      web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx
  17. 1 3
      web/app/components/app/configuration/toolbox/moderation/index.tsx
  18. 4 5
      web/app/components/app/configuration/toolbox/moderation/moderation-setting-modal.tsx
  19. 3 4
      web/app/components/app/configuration/tools/external-data-tool-modal.tsx
  20. 2 3
      web/app/components/app/overview/customize/index.tsx
  21. 2 2
      web/app/components/app/overview/settings/index.tsx
  22. 3 3
      web/app/components/billing/pricing/plan-item.tsx
  23. 2 3
      web/app/components/datasets/create/file-uploader/index.tsx
  24. 2 3
      web/app/components/datasets/create/step-two/index.tsx
  25. 2 3
      web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx
  26. 1 1
      web/app/components/datasets/documents/detail/embedding/index.tsx
  27. 1 1
      web/app/components/datasets/documents/index.tsx
  28. 4 4
      web/app/components/develop/doc.tsx
  29. 2 3
      web/app/components/develop/secret-key/secret-key-modal.tsx
  30. 1 1
      web/app/components/explore/category.tsx
  31. 3 4
      web/app/components/header/account-about/index.tsx
  32. 2 3
      web/app/components/header/account-dropdown/index.tsx
  33. 2 2
      web/app/components/header/account-setting/language-page/index.tsx
  34. 4 4
      web/app/components/header/account-setting/members-page/index.tsx
  35. 1 3
      web/app/components/header/account-setting/members-page/invite-modal/index.tsx
  36. 8 8
      web/app/components/header/account-setting/model-provider-page/declarations.ts
  37. 1 2
      web/app/components/header/account-setting/model-provider-page/hooks.ts
  38. 4 5
      web/app/components/header/maintenance-notice.tsx
  39. 1 1
      web/app/components/i18n.tsx
  40. 0 23
      web/app/components/locale-switcher.tsx
  41. 2 2
      web/app/components/tools/edit-custom-collection-modal/test-api.tsx
  42. 2 3
      web/app/components/tools/tool-list/header.tsx
  43. 3 3
      web/app/components/tools/tool-list/item.tsx
  44. 2 2
      web/app/components/tools/tool-nav-list/item.tsx
  45. 0 2
      web/app/install/installForm.tsx
  46. 2 2
      web/app/signin/_header.tsx
  47. 3 4
      web/app/signin/normalForm.tsx
  48. 2 2
      web/app/signin/oneMoreStep.tsx
  49. 1 3
      web/context/i18n.ts
  50. 175 0
      web/i18n/README.md
  51. 0 82
      web/i18n/README_CN.md
  52. 0 81
      web/i18n/README_EN.md
  53. 0 16
      web/i18n/client.ts
  54. 0 0
      web/i18n/en-US/app-annotation.ts
  55. 0 0
      web/i18n/en-US/app-api.ts
  56. 0 0
      web/i18n/en-US/app-debug.ts
  57. 0 0
      web/i18n/en-US/app-log.ts
  58. 0 0
      web/i18n/en-US/app-overview.ts
  59. 0 0
      web/i18n/en-US/app.ts
  60. 0 0
      web/i18n/en-US/billing.ts
  61. 0 0
      web/i18n/en-US/common.ts
  62. 0 0
      web/i18n/en-US/custom.ts
  63. 0 0
      web/i18n/en-US/dataset-creation.ts
  64. 0 0
      web/i18n/en-US/dataset-documents.ts
  65. 0 0
      web/i18n/en-US/dataset-hit-testing.ts
  66. 0 0
      web/i18n/en-US/dataset-settings.ts
  67. 0 0
      web/i18n/en-US/dataset.ts
  68. 0 0
      web/i18n/en-US/explore.ts
  69. 0 0
      web/i18n/en-US/layout.ts
  70. 0 0
      web/i18n/en-US/login.ts
  71. 0 0
      web/i18n/en-US/register.ts
  72. 0 0
      web/i18n/en-US/share-app.ts
  73. 0 0
      web/i18n/en-US/tools.ts
  74. 31 189
      web/i18n/i18next-config.ts
  75. 0 26
      web/i18n/i18next-serverside-config.ts
  76. 16 2
      web/i18n/index.ts
  77. 29 30
      web/i18n/language.ts
  78. 0 0
      web/i18n/pt-BR/app-annotation.ts
  79. 0 0
      web/i18n/pt-BR/app-api.ts
  80. 0 0
      web/i18n/pt-BR/app-debug.ts
  81. 0 0
      web/i18n/pt-BR/app-log.ts
  82. 0 0
      web/i18n/pt-BR/app-overview.ts
  83. 0 0
      web/i18n/pt-BR/app.ts
  84. 0 0
      web/i18n/pt-BR/billing.ts
  85. 0 0
      web/i18n/pt-BR/common.ts
  86. 0 0
      web/i18n/pt-BR/custom.ts
  87. 0 0
      web/i18n/pt-BR/dataset-creation.ts
  88. 0 0
      web/i18n/pt-BR/dataset-documents.ts
  89. 0 0
      web/i18n/pt-BR/dataset-hit-testing.ts
  90. 0 0
      web/i18n/pt-BR/dataset-settings.ts
  91. 0 0
      web/i18n/pt-BR/dataset.ts
  92. 0 0
      web/i18n/pt-BR/explore.ts
  93. 0 0
      web/i18n/pt-BR/layout.ts
  94. 0 0
      web/i18n/pt-BR/login.ts
  95. 0 0
      web/i18n/pt-BR/register.ts
  96. 0 0
      web/i18n/pt-BR/share-app.ts
  97. 0 0
      web/i18n/pt-BR/tools.ts
  98. 27 4
      web/i18n/server.ts
  99. 0 0
      web/i18n/uk-UA/app-annotation.ts
  100. 0 0
      web/i18n/uk-UA/app-api.ts

+ 1 - 0
web/.eslintrc.json

@@ -8,6 +8,7 @@
       "error",
       "error",
       "type"
       "type"
     ],
     ],
+    "@typescript-eslint/no-var-requires": "off",
     "no-console": "off",
     "no-console": "off",
     "indent": "off",
     "indent": "off",
     "@typescript-eslint/indent": [
     "@typescript-eslint/indent": [

+ 1 - 2
web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx

@@ -1,8 +1,7 @@
 import React from 'react'
 import React from 'react'
 import ChartView from './chartView'
 import ChartView from './chartView'
 import CardView from './cardView'
 import CardView from './cardView'
-import { getLocaleOnServer } from '@/i18n/server'
-import { useTranslation as translate } from '@/i18n/i18next-serverside-config'
+import { getLocaleOnServer, useTranslation as translate } from '@/i18n/server'
 import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel'
 import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel'
 
 
 export type IDevelopProps = {
 export type IDevelopProps = {

+ 1 - 2
web/app/(commonLayout)/apps/page.tsx

@@ -1,8 +1,7 @@
 import classNames from 'classnames'
 import classNames from 'classnames'
 import style from '../list.module.css'
 import style from '../list.module.css'
 import Apps from './Apps'
 import Apps from './Apps'
-import { getLocaleOnServer } from '@/i18n/server'
-import { useTranslation as translate } from '@/i18n/i18next-serverside-config'
+import { getLocaleOnServer, useTranslation as translate } from '@/i18n/server'
 
 
 const AppList = async () => {
 const AppList = async () => {
   const locale = getLocaleOnServer()
   const locale = getLocaleOnServer()

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

@@ -25,7 +25,7 @@ import Link from 'next/link'
 import s from './style.module.css'
 import s from './style.module.css'
 import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
 import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets'
 import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
 import type { RelatedApp, RelatedAppResponse } from '@/models/datasets'
-import { getLocaleOnClient } from '@/i18n/client'
+import { getLocaleOnClient } from '@/i18n'
 import AppSideBar from '@/app/components/app-sidebar'
 import AppSideBar from '@/app/components/app-sidebar'
 import Divider from '@/app/components/base/divider'
 import Divider from '@/app/components/base/divider'
 import Indicator from '@/app/components/header/indicator'
 import Indicator from '@/app/components/header/indicator'
@@ -35,7 +35,7 @@ import FloatPopoverContainer from '@/app/components/base/float-popover-container
 import DatasetDetailContext from '@/context/dataset-detail'
 import DatasetDetailContext from '@/context/dataset-detail'
 import { DataSourceType } from '@/models/datasets'
 import { DataSourceType } from '@/models/datasets'
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
 import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
-import { LanguagesSupported, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 export type IAppDetailLayoutProps = {
 export type IAppDetailLayoutProps = {
   children: React.ReactNode
   children: React.ReactNode
@@ -72,7 +72,7 @@ const LikedItem = ({
 
 
 const TargetIcon = ({ className }: SVGProps<SVGElement>) => {
 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 ?? ''}>
   return <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <g clip-path="url(#clip0_4610_6951)">
+    <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" />
       <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>
     </g>
     <defs>
     <defs>
@@ -105,7 +105,6 @@ type IExtraInfoProps = {
 
 
 const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
 const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
   const locale = getLocaleOnClient()
   const locale = getLocaleOnClient()
-  const language = getModelRuntimeSupported(locale)
   const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
   const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile)
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
@@ -150,7 +149,7 @@ const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => {
           <a
           <a
             className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
             className='inline-flex items-center text-xs text-primary-600 mt-2 cursor-pointer'
             href={
             href={
-              language === LanguagesSupported[1]
+              locale === LanguagesSupported[1]
                 ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
                 ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
                 : 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
                 : 'https://docs.dify.ai/user-guide/creating-dify-apps/prompt-engineering'
             }
             }

+ 3 - 10
web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/settings/page.tsx

@@ -1,15 +1,8 @@
 import React from 'react'
 import React from 'react'
-import { getLocaleOnServer } from '@/i18n/server'
-import { useTranslation as translate } from '@/i18n/i18next-serverside-config'
+import { getLocaleOnServer, useTranslation as translate } from '@/i18n/server'
 import Form from '@/app/components/datasets/settings/form'
 import Form from '@/app/components/datasets/settings/form'
 
 
-type Props = {
-  params: { datasetId: string }
-}
-
-const Settings = async ({
-  params: { datasetId },
-}: Props) => {
+const Settings = async () => {
   const locale = getLocaleOnServer()
   const locale = getLocaleOnServer()
   const { t } = await translate(locale, 'dataset-settings')
   const { t } = await translate(locale, 'dataset-settings')
 
 
@@ -19,7 +12,7 @@ const Settings = async ({
         <div className='mb-1 text-lg font-semibold text-gray-900'>{t('title')}</div>
         <div className='mb-1 text-lg font-semibold text-gray-900'>{t('title')}</div>
         <div className='text-sm text-gray-500'>{t('desc')}</div>
         <div className='text-sm text-gray-500'>{t('desc')}</div>
       </div>
       </div>
-      <Form datasetId={datasetId} />
+      <Form />
     </div>
     </div>
   )
   )
 }
 }

+ 2 - 3
web/app/(commonLayout)/datasets/Doc.tsx

@@ -5,7 +5,7 @@ import { useContext } from 'use-context-selector'
 import TemplateEn from './template/template.en.mdx'
 import TemplateEn from './template/template.en.mdx'
 import TemplateZh from './template/template.zh.mdx'
 import TemplateZh from './template/template.zh.mdx'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 type DocProps = {
 type DocProps = {
   apiBaseUrl: string
   apiBaseUrl: string
@@ -14,11 +14,10 @@ const Doc: FC<DocProps> = ({
   apiBaseUrl,
   apiBaseUrl,
 }) => {
 }) => {
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   return (
   return (
     <article className='mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'>
     <article className='mx-1 px-4 sm:mx-12 pt-16 bg-white rounded-t-xl prose prose-xl'>
       {
       {
-        language !== LanguagesSupportedUnderscore[1]
+        locale !== LanguagesSupported[1]
           ? <TemplateEn apiBaseUrl={apiBaseUrl} />
           ? <TemplateEn apiBaseUrl={apiBaseUrl} />
           : <TemplateZh apiBaseUrl={apiBaseUrl} />
           : <TemplateZh apiBaseUrl={apiBaseUrl} />
       }
       }

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

@@ -12,7 +12,7 @@ import Button from '@/app/components/base/button'
 
 
 import { SimpleSelect } from '@/app/components/base/select'
 import { SimpleSelect } from '@/app/components/base/select'
 import { timezones } from '@/utils/timezone'
 import { timezones } from '@/utils/timezone'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported, languages } from '@/utils/language'
+import { LanguagesSupported, languages } from '@/i18n/language'
 import { activateMember, invitationCheck } from '@/service/common'
 import { activateMember, invitationCheck } from '@/service/common'
 import Toast from '@/app/components/base/toast'
 import Toast from '@/app/components/base/toast'
 import Loading from '@/app/components/base/loading'
 import Loading from '@/app/components/base/loading'
@@ -42,9 +42,9 @@ const ActivateForm = () => {
   const [name, setName] = useState('')
   const [name, setName] = useState('')
   const [password, setPassword] = useState('')
   const [password, setPassword] = useState('')
   const [timezone, setTimezone] = useState('Asia/Shanghai')
   const [timezone, setTimezone] = useState('Asia/Shanghai')
-  const [language, setLanguage] = useState(getModelRuntimeSupported(locale))
+  const [language, setLanguage] = useState(locale)
   const [showSuccess, setShowSuccess] = useState(false)
   const [showSuccess, setShowSuccess] = useState(false)
-  const defaultLanguage = useCallback(() => (window.navigator.language.startsWith('zh') ? LanguagesSupportedUnderscore[1] : LanguagesSupportedUnderscore[0]) || LanguagesSupportedUnderscore[0], [])
+  const defaultLanguage = useCallback(() => (window.navigator.language.startsWith('zh') ? LanguagesSupported[1] : LanguagesSupported[0]) || LanguagesSupported[0], [])
 
 
   const showErrorMessage = useCallback((message: string) => {
   const showErrorMessage = useCallback((message: string) => {
     Toast.notify({
     Toast.notify({
@@ -207,7 +207,7 @@ const ActivateForm = () => {
                 <Link
                 <Link
                   className='text-primary-600'
                   className='text-primary-600'
                   target='_blank' rel='noopener noreferrer'
                   target='_blank' rel='noopener noreferrer'
-                  href={`https://docs.dify.ai/${language !== LanguagesSupportedUnderscore[1] ? 'user-agreement' : `v/${locale.toLowerCase()}/policies`}/open-source`}
+                  href={`https://docs.dify.ai/${language !== LanguagesSupported[1] ? 'user-agreement' : `v/${locale.toLowerCase()}/policies`}/open-source`}
                 >{t('login.license.link')}</Link>
                 >{t('login.license.link')}</Link>
               </div>
               </div>
             </div>
             </div>

+ 1 - 1
web/app/components/app-sidebar/basic.tsx

@@ -36,7 +36,7 @@ const WebappSvg = <svg width="16" height="18" viewBox="0 0 16 18" fill="none" xm
 </svg>
 </svg>
 
 
 const NotionSvg = <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
 const NotionSvg = <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <g clip-path="url(#clip0_6294_13848)">
+  <g clipPath="url(#clip0_6294_13848)">
     <path fill-rule="evenodd" clip-rule="evenodd" d="M4.287 21.9133L1.70748 18.6999C1.08685 17.9267 0.75 16.976 0.75 15.9974V4.36124C0.75 2.89548 1.92269 1.67923 3.43553 1.57594L15.3991 0.759137C16.2682 0.699797 17.1321 0.930818 17.8461 1.41353L22.0494 4.25543C22.8018 4.76414 23.25 5.59574 23.25 6.48319V19.7124C23.25 21.1468 22.0969 22.3345 20.6157 22.4256L7.3375 23.243C6.1555 23.3158 5.01299 22.8178 4.287 21.9133Z" fill="white" />
     <path fill-rule="evenodd" clip-rule="evenodd" d="M4.287 21.9133L1.70748 18.6999C1.08685 17.9267 0.75 16.976 0.75 15.9974V4.36124C0.75 2.89548 1.92269 1.67923 3.43553 1.57594L15.3991 0.759137C16.2682 0.699797 17.1321 0.930818 17.8461 1.41353L22.0494 4.25543C22.8018 4.76414 23.25 5.59574 23.25 6.48319V19.7124C23.25 21.1468 22.0969 22.3345 20.6157 22.4256L7.3375 23.243C6.1555 23.3158 5.01299 22.8178 4.287 21.9133Z" fill="white" />
     <path d="M8.43607 10.1842V10.0318C8.43607 9.64564 8.74535 9.32537 9.14397 9.29876L12.0475 9.10491L16.0628 15.0178V9.82823L15.0293 9.69046V9.6181C15.0293 9.22739 15.3456 8.90501 15.7493 8.88433L18.3912 8.74899V9.12918C18.3912 9.30765 18.2585 9.46031 18.0766 9.49108L17.4408 9.59861V18.0029L16.6429 18.2773C15.9764 18.5065 15.2343 18.2611 14.8527 17.6853L10.9545 11.803V17.4173L12.1544 17.647L12.1377 17.7583C12.0853 18.1069 11.7843 18.3705 11.4202 18.3867L8.43607 18.5195C8.39662 18.1447 8.67758 17.8093 9.06518 17.7686L9.45771 17.7273V10.2416L8.43607 10.1842Z" fill="black" />
     <path d="M8.43607 10.1842V10.0318C8.43607 9.64564 8.74535 9.32537 9.14397 9.29876L12.0475 9.10491L16.0628 15.0178V9.82823L15.0293 9.69046V9.6181C15.0293 9.22739 15.3456 8.90501 15.7493 8.88433L18.3912 8.74899V9.12918C18.3912 9.30765 18.2585 9.46031 18.0766 9.49108L17.4408 9.59861V18.0029L16.6429 18.2773C15.9764 18.5065 15.2343 18.2611 14.8527 17.6853L10.9545 11.803V17.4173L12.1544 17.647L12.1377 17.7583C12.0853 18.1069 11.7843 18.3705 11.4202 18.3867L8.43607 18.5195C8.39662 18.1447 8.67758 17.8093 9.06518 17.7686L9.45771 17.7273V10.2416L8.43607 10.1842Z" fill="black" />
     <path fill-rule="evenodd" clip-rule="evenodd" d="M15.5062 2.22521L3.5426 3.04201C2.82599 3.09094 2.27051 3.66706 2.27051 4.36136V15.9975C2.27051 16.6499 2.49507 17.2837 2.90883 17.7992L5.48835 21.0126C5.90541 21.5322 6.56174 21.8183 7.24076 21.7765L20.519 20.9591C21.1995 20.9172 21.7293 20.3716 21.7293 19.7125V6.48332C21.7293 6.07557 21.5234 5.69348 21.1777 5.45975L16.9743 2.61784C16.546 2.32822 16.0277 2.1896 15.5062 2.22521ZM4.13585 4.54287C3.96946 4.41968 4.04865 4.16303 4.25768 4.14804L15.5866 3.33545C15.9476 3.30956 16.3063 3.40896 16.5982 3.61578L18.8713 5.22622C18.9576 5.28736 18.9171 5.41935 18.8102 5.42516L6.8129 6.07764C6.44983 6.09739 6.09144 5.99073 5.80276 5.77699L4.13585 4.54287ZM6.25018 8.12315C6.25018 7.7334 6.56506 7.41145 6.9677 7.38952L19.6523 6.69871C20.0447 6.67734 20.375 6.97912 20.375 7.35898V18.8141C20.375 19.2031 20.0613 19.5247 19.6594 19.5476L7.05516 20.2648C6.61845 20.2896 6.25018 19.954 6.25018 19.5312V8.12315Z" fill="black" />
     <path fill-rule="evenodd" clip-rule="evenodd" d="M15.5062 2.22521L3.5426 3.04201C2.82599 3.09094 2.27051 3.66706 2.27051 4.36136V15.9975C2.27051 16.6499 2.49507 17.2837 2.90883 17.7992L5.48835 21.0126C5.90541 21.5322 6.56174 21.8183 7.24076 21.7765L20.519 20.9591C21.1995 20.9172 21.7293 20.3716 21.7293 19.7125V6.48332C21.7293 6.07557 21.5234 5.69348 21.1777 5.45975L16.9743 2.61784C16.546 2.32822 16.0277 2.1896 15.5062 2.22521ZM4.13585 4.54287C3.96946 4.41968 4.04865 4.16303 4.25768 4.14804L15.5866 3.33545C15.9476 3.30956 16.3063 3.40896 16.5982 3.61578L18.8713 5.22622C18.9576 5.28736 18.9171 5.41935 18.8102 5.42516L6.8129 6.07764C6.44983 6.09739 6.09144 5.99073 5.80276 5.77699L4.13585 4.54287ZM6.25018 8.12315C6.25018 7.7334 6.56506 7.41145 6.9677 7.38952L19.6523 6.69871C20.0447 6.67734 20.375 6.97912 20.375 7.35898V18.8141C20.375 19.2031 20.0613 19.5247 19.6594 19.5476L7.05516 20.2648C6.61845 20.2896 6.25018 19.954 6.25018 19.5312V8.12315Z" fill="black" />

+ 3 - 4
web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx

@@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next'
 import { useContext } from 'use-context-selector'
 import { useContext } from 'use-context-selector'
 import { Download02 as DownloadIcon } from '@/app/components/base/icons/src/vender/solid/general'
 import { Download02 as DownloadIcon } from '@/app/components/base/icons/src/vender/solid/general'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 const CSV_TEMPLATE_QA_EN = [
 const CSV_TEMPLATE_QA_EN = [
   ['question', 'answer'],
   ['question', 'answer'],
@@ -25,11 +25,10 @@ const CSVDownload: FC = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const { CSVDownloader, Type } = useCSVDownloader()
   const { CSVDownloader, Type } = useCSVDownloader()
 
 
   const getTemplate = () => {
   const getTemplate = () => {
-    return language !== LanguagesSupportedUnderscore[1] ? CSV_TEMPLATE_QA_EN : CSV_TEMPLATE_QA_CN
+    return locale !== LanguagesSupported[1] ? CSV_TEMPLATE_QA_EN : CSV_TEMPLATE_QA_CN
   }
   }
 
 
   return (
   return (
@@ -58,7 +57,7 @@ const CSVDownload: FC = () => {
       <CSVDownloader
       <CSVDownloader
         className="block mt-2 cursor-pointer"
         className="block mt-2 cursor-pointer"
         type={Type.Link}
         type={Type.Link}
-        filename={`template-${language}`}
+        filename={`template-${locale}`}
         bom={true}
         bom={true}
         data={getTemplate()}
         data={getTemplate()}
       >
       >

+ 4 - 5
web/app/components/app/annotation/header-opts/index.tsx

@@ -20,7 +20,7 @@ import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows
 
 
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import { fetchExportAnnotationList } from '@/service/annotation'
 import { fetchExportAnnotationList } from '@/service/annotation'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 const CSV_HEADER_QA_EN = ['Question', 'Answer']
 const CSV_HEADER_QA_EN = ['Question', 'Answer']
 const CSV_HEADER_QA_CN = ['问题', '答案']
 const CSV_HEADER_QA_CN = ['问题', '答案']
@@ -40,7 +40,6 @@ const HeaderOptions: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const { CSVDownloader, Type } = useCSVDownloader()
   const { CSVDownloader, Type } = useCSVDownloader()
   const [list, setList] = useState<AnnotationItemBasic[]>([])
   const [list, setList] = useState<AnnotationItemBasic[]>([])
 
 
@@ -56,7 +55,7 @@ const HeaderOptions: FC<Props> = ({
     const content = listTransformer(list).join('\n')
     const content = listTransformer(list).join('\n')
     const file = new Blob([content], { type: 'application/jsonl' })
     const file = new Blob([content], { type: 'application/jsonl' })
     a.href = URL.createObjectURL(file)
     a.href = URL.createObjectURL(file)
-    a.download = `annotations-${language}.jsonl`
+    a.download = `annotations-${locale}.jsonl`
     a.click()
     a.click()
   }
   }
 
 
@@ -110,10 +109,10 @@ const HeaderOptions: FC<Props> = ({
             >
             >
               <CSVDownloader
               <CSVDownloader
                 type={Type.Link}
                 type={Type.Link}
-                filename={`annotations-${language}`}
+                filename={`annotations-${locale}`}
                 bom={true}
                 bom={true}
                 data={[
                 data={[
-                  language !== LanguagesSupportedUnderscore[1] ? CSV_HEADER_QA_EN : CSV_HEADER_QA_CN,
+                  locale !== LanguagesSupported[1] ? CSV_HEADER_QA_EN : CSV_HEADER_QA_CN,
                   ...list.map(item => [item.question, item.answer]),
                   ...list.map(item => [item.question, item.answer]),
                 ]}
                 ]}
               >
               >

+ 2 - 3
web/app/components/app/configuration/config-prompt/conversation-histroy/history-panel.tsx

@@ -7,7 +7,7 @@ import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
 import Panel from '@/app/components/app/configuration/base/feature-panel'
 import Panel from '@/app/components/app/configuration/base/feature-panel'
 import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general'
 import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupported, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 type Props = {
 type Props = {
   showWarning: boolean
   showWarning: boolean
@@ -20,7 +20,6 @@ const HistoryPanel: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
 
 
   return (
   return (
     <Panel
     <Panel
@@ -46,7 +45,7 @@ const HistoryPanel: FC<Props> = ({
       {showWarning && (
       {showWarning && (
         <div className='flex justify-between py-2 px-3 rounded-b-xl bg-[#FFFAEB] text-xs text-gray-700'>
         <div className='flex justify-between py-2 px-3 rounded-b-xl bg-[#FFFAEB] text-xs text-gray-700'>
           <div>{t('appDebug.feature.conversationHistory.tip')}
           <div>{t('appDebug.feature.conversationHistory.tip')}
-            <a href={`${language === LanguagesSupported[1]
+            <a href={`${locale === LanguagesSupported[1]
               ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
               ? 'https://docs.dify.ai/v/zh-hans/guides/application-design/prompt-engineering'
               : 'https://docs.dify.ai/features/prompt-engineering'}`}
               : 'https://docs.dify.ai/features/prompt-engineering'}`}
             target='_blank' rel='noopener noreferrer'
             target='_blank' rel='noopener noreferrer'

+ 1 - 1
web/app/components/app/configuration/config-voice/param-config-content.tsx

@@ -13,7 +13,7 @@ import ConfigContext from '@/context/debug-configuration'
 import { fetchAppVoices } from '@/service/apps'
 import { fetchAppVoices } from '@/service/apps'
 import Tooltip from '@/app/components/base/tooltip'
 import Tooltip from '@/app/components/base/tooltip'
 import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
 import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
-import { languages } from '@/utils/language'
+import { languages } from '@/i18n/language'
 const VoiceParamConfig: FC = () => {
 const VoiceParamConfig: FC = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const pathname = usePathname()
   const pathname = usePathname()

+ 2 - 3
web/app/components/app/configuration/config/agent/agent-tools/choose-tool/index.tsx

@@ -10,7 +10,7 @@ import Drawer from '@/app/components/base/drawer-plus'
 import ConfigContext from '@/context/debug-configuration'
 import ConfigContext from '@/context/debug-configuration'
 import type { ModelConfig } from '@/models/debug'
 import type { ModelConfig } from '@/models/debug'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { getModelRuntimeSupported } from '@/utils/language'
+
 type Props = {
 type Props = {
   show: boolean
   show: boolean
   onHide: () => void
   onHide: () => void
@@ -24,7 +24,6 @@ const ChooseTool: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const {
   const {
     modelConfig,
     modelConfig,
     setModelConfig,
     setModelConfig,
@@ -60,7 +59,7 @@ const ChooseTool: FC<Props> = ({
                 provider_type: collection.type,
                 provider_type: collection.type,
                 provider_name: collection.name,
                 provider_name: collection.name,
                 tool_name: tool.name,
                 tool_name: tool.name,
-                tool_label: tool.label[language],
+                tool_label: tool.label[locale],
                 tool_parameters: parameters,
                 tool_parameters: parameters,
                 enabled: true,
                 enabled: true,
               })
               })

+ 2 - 2
web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx

@@ -13,7 +13,7 @@ import I18n from '@/context/i18n'
 import Button from '@/app/components/base/button'
 import Button from '@/app/components/base/button'
 import Loading from '@/app/components/base/loading'
 import Loading from '@/app/components/base/loading'
 import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common'
 import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common'
-import { getModelRuntimeSupported } from '@/utils/language'
+import { getLanguage } from '@/i18n/language'
 type Props = {
 type Props = {
   collection: Collection
   collection: Collection
   toolName: string
   toolName: string
@@ -32,7 +32,7 @@ const SettingBuiltInTool: FC<Props> = ({
   onSave,
   onSave,
 }) => {
 }) => {
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
+  const language = getLanguage(locale)
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
   const [isLoading, setIsLoading] = useState(true)
   const [isLoading, setIsLoading] = useState(true)

+ 1 - 1
web/app/components/app/configuration/features/chat-group/text-to-speech/index.tsx

@@ -7,7 +7,7 @@ import { usePathname } from 'next/navigation'
 import Panel from '@/app/components/app/configuration/base/feature-panel'
 import Panel from '@/app/components/app/configuration/base/feature-panel'
 import { Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
 import { Speaker } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
 import ConfigContext from '@/context/debug-configuration'
 import ConfigContext from '@/context/debug-configuration'
-import { languages } from '@/utils/language'
+import { languages } from '@/i18n/language'
 import { fetchAppVoices } from '@/service/apps'
 import { fetchAppVoices } from '@/service/apps'
 import AudioBtn from '@/app/components/base/audio-btn'
 import AudioBtn from '@/app/components/base/audio-btn'
 
 

+ 2 - 3
web/app/components/app/configuration/prompt-mode/advanced-mode-waring.tsx

@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
 import { useContext } from 'use-context-selector'
 import { useContext } from 'use-context-selector'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows'
 import { FlipBackward } from '@/app/components/base/icons/src/vender/line/arrows'
-import { LanguagesSupported, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 type Props = {
 type Props = {
   onReturnToSimpleMode: () => void
   onReturnToSimpleMode: () => void
 }
 }
@@ -15,7 +15,6 @@ const AdvancedModeWarning: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const [show, setShow] = React.useState(true)
   const [show, setShow] = React.useState(true)
   if (!show)
   if (!show)
     return null
     return null
@@ -27,7 +26,7 @@ const AdvancedModeWarning: FC<Props> = ({
           <span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span>
           <span className='text-gray-700'>{t('appDebug.promptMode.advancedWarning.description')}</span>
           <a
           <a
             className='font-medium text-[#155EEF]'
             className='font-medium text-[#155EEF]'
-            href={`https://docs.dify.ai/${language === LanguagesSupported[1] ? 'v/zh-hans/guides/application-design/prompt-engineering' : 'features/prompt-engineering'}`}
+            href={`https://docs.dify.ai/${locale === LanguagesSupported[1] ? 'v/zh-hans/guides/application-design/prompt-engineering' : 'features/prompt-engineering'}`}
             target='_blank' rel='noopener noreferrer'
             target='_blank' rel='noopener noreferrer'
           >
           >
             {t('appDebug.promptMode.advancedWarning.learnMore')}
             {t('appDebug.promptMode.advancedWarning.learnMore')}

+ 1 - 3
web/app/components/app/configuration/toolbox/moderation/index.tsx

@@ -7,12 +7,10 @@ import { useModalContext } from '@/context/modal-context'
 import ConfigContext from '@/context/debug-configuration'
 import ConfigContext from '@/context/debug-configuration'
 import { fetchCodeBasedExtensionList } from '@/service/common'
 import { fetchCodeBasedExtensionList } from '@/service/common'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { getModelRuntimeSupported } from '@/utils/language'
 const Moderation = () => {
 const Moderation = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { setShowModerationSettingModal } = useModalContext()
   const { setShowModerationSettingModal } = useModalContext()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const {
   const {
     moderationConfig,
     moderationConfig,
     setModerationConfig,
     setModerationConfig,
@@ -39,7 +37,7 @@ const Moderation = () => {
     else if (moderationConfig.type === 'api')
     else if (moderationConfig.type === 'api')
       prefix = t('common.apiBasedExtension.selector.title')
       prefix = t('common.apiBasedExtension.selector.title')
     else
     else
-      prefix = codeBasedExtensionList?.data.find(item => item.name === moderationConfig.type)?.label[language] || ''
+      prefix = codeBasedExtensionList?.data.find(item => item.name === moderationConfig.type)?.label[locale] || ''
 
 
     if (moderationConfig.config?.inputs_config?.enabled && moderationConfig.config?.outputs_config?.enabled)
     if (moderationConfig.config?.inputs_config?.enabled && moderationConfig.config?.outputs_config?.enabled)
       suffix = t('appDebug.feature.moderation.allEnabled')
       suffix = t('appDebug.feature.moderation.allEnabled')

+ 4 - 5
web/app/components/app/configuration/toolbox/moderation/moderation-setting-modal.tsx

@@ -17,7 +17,7 @@ import {
 } from '@/service/common'
 } from '@/service/common'
 import type { CodeBasedExtensionItem } from '@/models/common'
 import type { CodeBasedExtensionItem } from '@/models/common'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
 import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
 import { useModalContext } from '@/context/modal-context'
 import { useModalContext } from '@/context/modal-context'
 import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
 import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
@@ -44,7 +44,6 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { notify } = useToastContext()
   const { notify } = useToastContext()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const { data: modelProviders, isLoading, mutate } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
   const { data: modelProviders, isLoading, mutate } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
   const [localeData, setLocaleData] = useState<ModerationConfig>(data)
   const [localeData, setLocaleData] = useState<ModerationConfig>(data)
   const { setShowAccountSettingModal } = useModalContext()
   const { setShowAccountSettingModal } = useModalContext()
@@ -200,12 +199,12 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
     }
     }
 
 
     if (localeData.type === 'keywords' && !localeData.config.keywords) {
     if (localeData.type === 'keywords' && !localeData.config.keywords) {
-      notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: language !== LanguagesSupportedUnderscore[1] ? 'keywords' : '关键词' }) })
+      notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? 'keywords' : '关键词' }) })
       return
       return
     }
     }
 
 
     if (localeData.type === 'api' && !localeData.config.api_based_extension_id) {
     if (localeData.type === 'api' && !localeData.config.api_based_extension_id) {
-      notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: language !== LanguagesSupportedUnderscore[1] ? 'API Extension' : 'API 扩展' }) })
+      notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? 'API Extension' : 'API 扩展' }) })
       return
       return
     }
     }
 
 
@@ -214,7 +213,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
         if (!localeData.config?.[currentProvider.form_schema[i].variable] && currentProvider.form_schema[i].required) {
         if (!localeData.config?.[currentProvider.form_schema[i].variable] && currentProvider.form_schema[i].required) {
           notify({
           notify({
             type: 'error',
             type: 'error',
-            message: t('appDebug.errorMessage.valueOfVarRequired', { key: language !== LanguagesSupportedUnderscore[1] ? currentProvider.form_schema[i].label['en-US'] : currentProvider.form_schema[i].label['zh-Hans'] }),
+            message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? currentProvider.form_schema[i].label['en-US'] : currentProvider.form_schema[i].label['zh-Hans'] }),
           })
           })
           return
           return
         }
         }

+ 3 - 4
web/app/components/app/configuration/tools/external-data-tool-modal.tsx

@@ -12,7 +12,7 @@ import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/educatio
 import { fetchCodeBasedExtensionList } from '@/service/common'
 import { fetchCodeBasedExtensionList } from '@/service/common'
 import { SimpleSelect } from '@/app/components/base/select'
 import { SimpleSelect } from '@/app/components/base/select'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 import type {
 import type {
   CodeBasedExtensionItem,
   CodeBasedExtensionItem,
   ExternalDataTool,
   ExternalDataTool,
@@ -41,7 +41,6 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { notify } = useToastContext()
   const { notify } = useToastContext()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const [localeData, setLocaleData] = useState(data.type ? data : { ...data, type: 'api' })
   const [localeData, setLocaleData] = useState(data.type ? data : { ...data, type: 'api' })
   const [showEmojiPicker, setShowEmojiPicker] = useState(false)
   const [showEmojiPicker, setShowEmojiPicker] = useState(false)
   const { data: codeBasedExtensionList } = useSWR(
   const { data: codeBasedExtensionList } = useSWR(
@@ -157,7 +156,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
     }
     }
 
 
     if (localeData.type === 'api' && !localeData.config?.api_based_extension_id) {
     if (localeData.type === 'api' && !localeData.config?.api_based_extension_id) {
-      notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: language !== LanguagesSupportedUnderscore[1] ? 'API Extension' : 'API 扩展' }) })
+      notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? 'API Extension' : 'API 扩展' }) })
       return
       return
     }
     }
 
 
@@ -166,7 +165,7 @@ const ExternalDataToolModal: FC<ExternalDataToolModalProps> = ({
         if (!localeData.config?.[currentProvider.form_schema[i].variable] && currentProvider.form_schema[i].required) {
         if (!localeData.config?.[currentProvider.form_schema[i].variable] && currentProvider.form_schema[i].required) {
           notify({
           notify({
             type: 'error',
             type: 'error',
-            message: t('appDebug.errorMessage.valueOfVarRequired', { key: language !== LanguagesSupportedUnderscore[1] ? currentProvider.form_schema[i].label['en-US'] : currentProvider.form_schema[i].label['zh-Hans'] }),
+            message: t('appDebug.errorMessage.valueOfVarRequired', { key: locale !== LanguagesSupported[1] ? currentProvider.form_schema[i].label['en-US'] : currentProvider.form_schema[i].label['zh-Hans'] }),
           })
           })
           return
           return
         }
         }

+ 2 - 3
web/app/components/app/overview/customize/index.tsx

@@ -9,7 +9,7 @@ import I18n from '@/context/i18n'
 import Button from '@/app/components/base/button'
 import Button from '@/app/components/base/button'
 import Modal from '@/app/components/base/modal'
 import Modal from '@/app/components/base/modal'
 import Tag from '@/app/components/base/tag'
 import Tag from '@/app/components/base/tag'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 type IShareLinkProps = {
 type IShareLinkProps = {
   isShow: boolean
   isShow: boolean
@@ -44,7 +44,6 @@ const CustomizeModal: FC<IShareLinkProps> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const isChatApp = mode === 'chat'
   const isChatApp = mode === 'chat'
 
 
   return <Modal
   return <Modal
@@ -102,7 +101,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
         className='w-36 mt-2'
         className='w-36 mt-2'
         onClick={() =>
         onClick={() =>
           window.open(
           window.open(
-            `https://docs.dify.ai/${language !== LanguagesSupportedUnderscore[1]
+            `https://docs.dify.ai/${locale !== LanguagesSupported[1]
               ? 'user-guide/launching-dify-apps/developing-with-apis'
               ? 'user-guide/launching-dify-apps/developing-with-apis'
               : `v/${locale.toLowerCase()}/guides/application-publishing/developing-with-apis`
               : `v/${locale.toLowerCase()}/guides/application-publishing/developing-with-apis`
             }`,
             }`,

+ 2 - 2
web/app/components/app/overview/settings/index.tsx

@@ -13,7 +13,7 @@ import type { AppDetailResponse } from '@/models/app'
 import type { Language } from '@/types/app'
 import type { Language } from '@/types/app'
 import EmojiPicker from '@/app/components/base/emoji-picker'
 import EmojiPicker from '@/app/components/base/emoji-picker'
 
 
-import { languages } from '@/utils/language'
+import { languages } from '@/i18n/language'
 
 
 export type ISettingsModalProps = {
 export type ISettingsModalProps = {
   appInfo: AppDetailResponse
   appInfo: AppDetailResponse
@@ -122,7 +122,7 @@ const SettingsModal: FC<ISettingsModalProps> = ({
         />
         />
         <div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div>
         <div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div>
         <SimpleSelect
         <SimpleSelect
-          items={languages}
+          items={languages.filter(item => item.supported)}
           defaultValue={language}
           defaultValue={language}
           onSelect={item => setLanguage(item.value as Language)}
           onSelect={item => setLanguage(item.value as Language)}
         />
         />

+ 3 - 3
web/app/components/billing/pricing/plan-item.tsx

@@ -12,7 +12,7 @@ import { PlanRange } from './select-plan-range'
 import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
 import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
 import { useAppContext } from '@/context/app-context'
 import { useAppContext } from '@/context/app-context'
 import { fetchSubscriptionUrls } from '@/service/billing'
 import { fetchSubscriptionUrls } from '@/service/billing'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 
 
 type Props = {
 type Props = {
@@ -73,8 +73,8 @@ const PlanItem: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
-  const isZh = language === LanguagesSupportedUnderscore[1]
+
+  const isZh = locale === LanguagesSupported[1]
   const [loading, setLoading] = React.useState(false)
   const [loading, setLoading] = React.useState(false)
   const i18nPrefix = `billing.plans.${plan}`
   const i18nPrefix = `billing.plans.${plan}`
   const isFreePlan = plan === Plan.sandbox
   const isFreePlan = plan === Plan.sandbox

+ 2 - 3
web/app/components/datasets/create/file-uploader/index.tsx

@@ -12,7 +12,7 @@ import { upload } from '@/service/base'
 import { fetchFileUploadConfig } from '@/service/common'
 import { fetchFileUploadConfig } from '@/service/common'
 import { fetchSupportFileTypes } from '@/service/datasets'
 import { fetchSupportFileTypes } from '@/service/datasets'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 const FILES_NUMBER_LIMIT = 20
 const FILES_NUMBER_LIMIT = 20
 
 
@@ -36,7 +36,6 @@ const FileUploader = ({
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { notify } = useContext(ToastContext)
   const { notify } = useContext(ToastContext)
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const [dragging, setDragging] = useState(false)
   const [dragging, setDragging] = useState(false)
   const dropRef = useRef<HTMLDivElement>(null)
   const dropRef = useRef<HTMLDivElement>(null)
   const dragRef = useRef<HTMLDivElement>(null)
   const dragRef = useRef<HTMLDivElement>(null)
@@ -77,7 +76,7 @@ const FileUploader = ({
     res = res.map(item => item.toLowerCase())
     res = res.map(item => item.toLowerCase())
     res = res.filter((item, index, self) => self.indexOf(item) === index)
     res = res.filter((item, index, self) => self.indexOf(item) === index)
 
 
-    return res.map(item => item.toUpperCase()).join(language !== LanguagesSupportedUnderscore[1] ? ', ' : '、 ')
+    return res.map(item => item.toUpperCase()).join(locale !== LanguagesSupported[1] ? ', ' : '、 ')
   })()
   })()
   const ACCEPTS = supportTypes.map((ext: string) => `.${ext}`)
   const ACCEPTS = supportTypes.map((ext: string) => `.${ext}`)
   const fileUploadConfig = useMemo(() => fileUploadConfigResponse ?? {
   const fileUploadConfig = useMemo(() => fileUploadConfigResponse ?? {

+ 2 - 3
web/app/components/datasets/create/step-two/index.tsx

@@ -42,7 +42,7 @@ import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
 import Tooltip from '@/app/components/base/tooltip'
 import Tooltip from '@/app/components/base/tooltip'
 import TooltipPlus from '@/app/components/base/tooltip-plus'
 import TooltipPlus from '@/app/components/base/tooltip-plus'
 import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
 import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 type ValueOf<T> = T[keyof T]
 type ValueOf<T> = T[keyof T]
 type StepTwoProps = {
 type StepTwoProps = {
@@ -89,7 +89,6 @@ const StepTwo = ({
 }: StepTwoProps) => {
 }: StepTwoProps) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const media = useBreakpoints()
   const media = useBreakpoints()
   const isMobile = media === MediaType.mobile
   const isMobile = media === MediaType.mobile
 
 
@@ -114,7 +113,7 @@ const StepTwo = ({
   const [docForm, setDocForm] = useState<DocForm | string>(
   const [docForm, setDocForm] = useState<DocForm | string>(
     (datasetId && documentDetail) ? documentDetail.doc_form : DocForm.TEXT,
     (datasetId && documentDetail) ? documentDetail.doc_form : DocForm.TEXT,
   )
   )
-  const [docLanguage, setDocLanguage] = useState<string>(language !== LanguagesSupportedUnderscore[1] ? 'English' : 'Chinese')
+  const [docLanguage, setDocLanguage] = useState<string>(locale !== LanguagesSupported[1] ? 'English' : 'Chinese')
   const [QATipHide, setQATipHide] = useState(false)
   const [QATipHide, setQATipHide] = useState(false)
   const [previewSwitched, setPreviewSwitched] = useState(false)
   const [previewSwitched, setPreviewSwitched] = useState(false)
   const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean()
   const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean()

+ 2 - 3
web/app/components/datasets/documents/detail/batch-modal/csv-downloader.tsx

@@ -9,7 +9,7 @@ import { useContext } from 'use-context-selector'
 import { Download02 as DownloadIcon } from '@/app/components/base/icons/src/vender/solid/general'
 import { Download02 as DownloadIcon } from '@/app/components/base/icons/src/vender/solid/general'
 import { DocForm } from '@/models/datasets'
 import { DocForm } from '@/models/datasets'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 const CSV_TEMPLATE_QA_EN = [
 const CSV_TEMPLATE_QA_EN = [
   ['question', 'answer'],
   ['question', 'answer'],
@@ -35,11 +35,10 @@ const CSV_TEMPLATE_CN = [
 const CSVDownload: FC<{ docForm: DocForm }> = ({ docForm }) => {
 const CSVDownload: FC<{ docForm: DocForm }> = ({ docForm }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const { CSVDownloader, Type } = useCSVDownloader()
   const { CSVDownloader, Type } = useCSVDownloader()
 
 
   const getTemplate = () => {
   const getTemplate = () => {
-    if (language === LanguagesSupportedUnderscore[1]) {
+    if (locale === LanguagesSupported[1]) {
       if (docForm === DocForm.QA)
       if (docForm === DocForm.QA)
         return CSV_TEMPLATE_QA_CN
         return CSV_TEMPLATE_QA_CN
       return CSV_TEMPLATE_CN
       return CSV_TEMPLATE_CN

+ 1 - 1
web/app/components/datasets/documents/detail/embedding/index.tsx

@@ -35,7 +35,7 @@ type Props = {
 
 
 const StopIcon = ({ className }: SVGProps<SVGElement>) => {
 const StopIcon = ({ 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 ?? ''}>
   return <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <g clip-path="url(#clip0_2328_2798)">
+    <g clipPath="url(#clip0_2328_2798)">
       <path d="M1.5 3.9C1.5 3.05992 1.5 2.63988 1.66349 2.31901C1.8073 2.03677 2.03677 1.8073 2.31901 1.66349C2.63988 1.5 3.05992 1.5 3.9 1.5H8.1C8.94008 1.5 9.36012 1.5 9.68099 1.66349C9.96323 1.8073 10.1927 2.03677 10.3365 2.31901C10.5 2.63988 10.5 3.05992 10.5 3.9V8.1C10.5 8.94008 10.5 9.36012 10.3365 9.68099C10.1927 9.96323 9.96323 10.1927 9.68099 10.3365C9.36012 10.5 8.94008 10.5 8.1 10.5H3.9C3.05992 10.5 2.63988 10.5 2.31901 10.3365C2.03677 10.1927 1.8073 9.96323 1.66349 9.68099C1.5 9.36012 1.5 8.94008 1.5 8.1V3.9Z" stroke="#344054" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
       <path d="M1.5 3.9C1.5 3.05992 1.5 2.63988 1.66349 2.31901C1.8073 2.03677 2.03677 1.8073 2.31901 1.66349C2.63988 1.5 3.05992 1.5 3.9 1.5H8.1C8.94008 1.5 9.36012 1.5 9.68099 1.66349C9.96323 1.8073 10.1927 2.03677 10.3365 2.31901C10.5 2.63988 10.5 3.05992 10.5 3.9V8.1C10.5 8.94008 10.5 9.36012 10.3365 9.68099C10.1927 9.96323 9.96323 10.1927 9.68099 10.3365C9.36012 10.5 8.94008 10.5 8.1 10.5H3.9C3.05992 10.5 2.63988 10.5 2.31901 10.3365C2.03677 10.1927 1.8073 9.96323 1.66349 9.68099C1.5 9.36012 1.5 8.94008 1.5 8.1V3.9Z" stroke="#344054" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
     </g>
     </g>
     <defs>
     <defs>

+ 1 - 1
web/app/components/datasets/documents/index.tsx

@@ -38,7 +38,7 @@ const ThreeDotsIcon = ({ className }: React.SVGProps<SVGElement>) => {
 
 
 const NotionIcon = ({ className }: React.SVGProps<SVGElement>) => {
 const NotionIcon = ({ className }: React.SVGProps<SVGElement>) => {
   return <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
   return <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className={className ?? ''}>
-    <g clip-path="url(#clip0_2164_11263)">
+    <g clipPath="url(#clip0_2164_11263)">
       <path fillRule="evenodd" clipRule="evenodd" d="M3.5725 18.2611L1.4229 15.5832C0.905706 14.9389 0.625 14.1466 0.625 13.3312V3.63437C0.625 2.4129 1.60224 1.39936 2.86295 1.31328L12.8326 0.632614C13.5569 0.583164 14.2768 0.775682 14.8717 1.17794L18.3745 3.5462C19.0015 3.97012 19.375 4.66312 19.375 5.40266V16.427C19.375 17.6223 18.4141 18.6121 17.1798 18.688L6.11458 19.3692C5.12958 19.4298 4.17749 19.0148 3.5725 18.2611Z" fill="white" />
       <path fillRule="evenodd" clipRule="evenodd" d="M3.5725 18.2611L1.4229 15.5832C0.905706 14.9389 0.625 14.1466 0.625 13.3312V3.63437C0.625 2.4129 1.60224 1.39936 2.86295 1.31328L12.8326 0.632614C13.5569 0.583164 14.2768 0.775682 14.8717 1.17794L18.3745 3.5462C19.0015 3.97012 19.375 4.66312 19.375 5.40266V16.427C19.375 17.6223 18.4141 18.6121 17.1798 18.688L6.11458 19.3692C5.12958 19.4298 4.17749 19.0148 3.5725 18.2611Z" fill="white" />
       <path d="M7.03006 8.48669V8.35974C7.03006 8.03794 7.28779 7.77104 7.61997 7.74886L10.0396 7.58733L13.3857 12.5147V8.19009L12.5244 8.07528V8.01498C12.5244 7.68939 12.788 7.42074 13.1244 7.4035L15.326 7.29073V7.60755C15.326 7.75628 15.2154 7.88349 15.0638 7.90913L14.534 7.99874V15.0023L13.8691 15.231C13.3136 15.422 12.6952 15.2175 12.3772 14.7377L9.12879 9.83574V14.5144L10.1287 14.7057L10.1147 14.7985C10.0711 15.089 9.82028 15.3087 9.51687 15.3222L7.03006 15.4329C6.99718 15.1205 7.23132 14.841 7.55431 14.807L7.88143 14.7727V8.53453L7.03006 8.48669Z" fill="black" />
       <path d="M7.03006 8.48669V8.35974C7.03006 8.03794 7.28779 7.77104 7.61997 7.74886L10.0396 7.58733L13.3857 12.5147V8.19009L12.5244 8.07528V8.01498C12.5244 7.68939 12.788 7.42074 13.1244 7.4035L15.326 7.29073V7.60755C15.326 7.75628 15.2154 7.88349 15.0638 7.90913L14.534 7.99874V15.0023L13.8691 15.231C13.3136 15.422 12.6952 15.2175 12.3772 14.7377L9.12879 9.83574V14.5144L10.1287 14.7057L10.1147 14.7985C10.0711 15.089 9.82028 15.3087 9.51687 15.3222L7.03006 15.4329C6.99718 15.1205 7.23132 14.841 7.55431 14.807L7.88143 14.7727V8.53453L7.03006 8.48669Z" fill="black" />
       <path fillRule="evenodd" clipRule="evenodd" d="M12.9218 1.85424L2.95217 2.53491C2.35499 2.57568 1.89209 3.05578 1.89209 3.63437V13.3312C1.89209 13.8748 2.07923 14.403 2.42402 14.8325L4.57362 17.5104C4.92117 17.9434 5.46812 18.1818 6.03397 18.147L17.0991 17.4658C17.6663 17.4309 18.1078 16.9762 18.1078 16.427V5.40266C18.1078 5.06287 17.9362 4.74447 17.6481 4.54969L14.1453 2.18143C13.7883 1.94008 13.3564 1.82457 12.9218 1.85424ZM3.44654 3.78562C3.30788 3.68296 3.37387 3.46909 3.54806 3.4566L12.9889 2.77944C13.2897 2.75787 13.5886 2.8407 13.8318 3.01305L15.7261 4.35508C15.798 4.40603 15.7642 4.51602 15.6752 4.52086L5.67742 5.0646C5.37485 5.08106 5.0762 4.99217 4.83563 4.81406L3.44654 3.78562ZM5.20848 6.76919C5.20848 6.4444 5.47088 6.1761 5.80642 6.15783L16.3769 5.58216C16.7039 5.56435 16.9792 5.81583 16.9792 6.13239V15.6783C16.9792 16.0025 16.7177 16.2705 16.3829 16.2896L5.8793 16.8872C5.51537 16.9079 5.20848 16.6283 5.20848 16.2759V6.76919Z" fill="black" />
       <path fillRule="evenodd" clipRule="evenodd" d="M12.9218 1.85424L2.95217 2.53491C2.35499 2.57568 1.89209 3.05578 1.89209 3.63437V13.3312C1.89209 13.8748 2.07923 14.403 2.42402 14.8325L4.57362 17.5104C4.92117 17.9434 5.46812 18.1818 6.03397 18.147L17.0991 17.4658C17.6663 17.4309 18.1078 16.9762 18.1078 16.427V5.40266C18.1078 5.06287 17.9362 4.74447 17.6481 4.54969L14.1453 2.18143C13.7883 1.94008 13.3564 1.82457 12.9218 1.85424ZM3.44654 3.78562C3.30788 3.68296 3.37387 3.46909 3.54806 3.4566L12.9889 2.77944C13.2897 2.75787 13.5886 2.8407 13.8318 3.01305L15.7261 4.35508C15.798 4.40603 15.7642 4.51602 15.6752 4.52086L5.67742 5.0646C5.37485 5.08106 5.0762 4.99217 4.83563 4.81406L3.44654 3.78562ZM5.20848 6.76919C5.20848 6.4444 5.47088 6.1761 5.80642 6.15783L16.3769 5.58216C16.7039 5.56435 16.9792 5.81583 16.9792 6.13239V15.6783C16.9792 16.0025 16.7177 16.2705 16.3829 16.2896L5.8793 16.8872C5.51537 16.9079 5.20848 16.6283 5.20848 16.2759V6.76919Z" fill="black" />

+ 4 - 4
web/app/components/develop/doc.tsx

@@ -5,7 +5,7 @@ import TemplateZh from './template/template.zh.mdx'
 import TemplateChatEn from './template/template_chat.en.mdx'
 import TemplateChatEn from './template/template_chat.en.mdx'
 import TemplateChatZh from './template/template_chat.zh.mdx'
 import TemplateChatZh from './template/template_chat.zh.mdx'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 type IDocProps = {
 type IDocProps = {
   appDetail: any
   appDetail: any
@@ -13,7 +13,7 @@ type IDocProps = {
 
 
 const Doc = ({ appDetail }: IDocProps) => {
 const Doc = ({ appDetail }: IDocProps) => {
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
+
   const variables = appDetail?.model_config?.configs?.prompt_variables || []
   const variables = appDetail?.model_config?.configs?.prompt_variables || []
   const inputs = variables.reduce((res: any, variable: any) => {
   const inputs = variables.reduce((res: any, variable: any) => {
     res[variable.key] = variable.name || ''
     res[variable.key] = variable.name || ''
@@ -24,10 +24,10 @@ const Doc = ({ appDetail }: IDocProps) => {
     <article className="prose prose-xl" >
     <article className="prose prose-xl" >
       {appDetail?.mode === 'completion'
       {appDetail?.mode === 'completion'
         ? (
         ? (
-          language !== LanguagesSupportedUnderscore[1] ? <TemplateEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateZh appDetail={appDetail} variables={variables} inputs={inputs} />
+          locale !== LanguagesSupported[1] ? <TemplateEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateZh appDetail={appDetail} variables={variables} inputs={inputs} />
         )
         )
         : (
         : (
-          language !== LanguagesSupportedUnderscore[1] ? <TemplateChatEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
+          locale !== LanguagesSupported[1] ? <TemplateChatEn appDetail={appDetail} variables={variables} inputs={inputs} /> : <TemplateChatZh appDetail={appDetail} variables={variables} inputs={inputs} />
         )}
         )}
     </article>
     </article>
   )
   )

+ 2 - 3
web/app/components/develop/secret-key/secret-key-modal.tsx

@@ -27,7 +27,7 @@ import Tooltip from '@/app/components/base/tooltip'
 import Loading from '@/app/components/base/loading'
 import Loading from '@/app/components/base/loading'
 import Confirm from '@/app/components/base/confirm'
 import Confirm from '@/app/components/base/confirm'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupported, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 import { useAppContext } from '@/context/app-context'
 import { useAppContext } from '@/context/app-context'
 
 
 type ISecretKeyModalProps = {
 type ISecretKeyModalProps = {
@@ -56,7 +56,6 @@ const SecretKeyModal = ({
   const [delKeyID, setDelKeyId] = useState('')
   const [delKeyID, setDelKeyId] = useState('')
 
 
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
 
 
   // const [isCopied, setIsCopied] = useState(false)
   // const [isCopied, setIsCopied] = useState(false)
   const [copyValue, setCopyValue] = useState('')
   const [copyValue, setCopyValue] = useState('')
@@ -102,7 +101,7 @@ const SecretKeyModal = ({
   }
   }
 
 
   const formatDate = (timestamp: string) => {
   const formatDate = (timestamp: string) => {
-    if (language === LanguagesSupported[0])
+    if (locale === LanguagesSupported[0])
       return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format((+timestamp) * 1000)
       return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format((+timestamp) * 1000)
     else
     else
       return new Intl.DateTimeFormat('fr-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).format((+timestamp) * 1000)
       return new Intl.DateTimeFormat('fr-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).format((+timestamp) * 1000)

+ 1 - 1
web/app/components/explore/category.tsx

@@ -3,7 +3,7 @@ import type { FC } from 'react'
 import React from 'react'
 import React from 'react'
 import { useTranslation } from 'react-i18next'
 import { useTranslation } from 'react-i18next'
 import cn from 'classnames'
 import cn from 'classnames'
-import exploreI18n from '@/i18n/lang/explore.en'
+import exploreI18n from '@/i18n/en-US/explore'
 import type { AppCategory } from '@/models/explore'
 import type { AppCategory } from '@/models/explore'
 
 
 const categoryI18n = exploreI18n.category
 const categoryI18n = exploreI18n.category

+ 3 - 4
web/app/components/header/account-about/index.tsx

@@ -9,7 +9,7 @@ import { XClose } from '@/app/components/base/icons/src/vender/line/general'
 import type { LangGeniusVersionResponse } from '@/models/common'
 import type { LangGeniusVersionResponse } from '@/models/common'
 import { IS_CE_EDITION } from '@/config'
 import { IS_CE_EDITION } from '@/config'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 import LogoSite from '@/app/components/base/logo/logo-site'
 import LogoSite from '@/app/components/base/logo/logo-site'
 
 
 type IAccountSettingProps = {
 type IAccountSettingProps = {
@@ -26,7 +26,6 @@ export default function AccountAbout({
 }: IAccountSettingProps) {
 }: IAccountSettingProps) {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version
   const isLatest = langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version
 
 
   return (
   return (
@@ -49,8 +48,8 @@ export default function AccountAbout({
                 IS_CE_EDITION
                 IS_CE_EDITION
                   ? <Link href={'https://github.com/langgenius/dify/blob/main/LICENSE'} target='_blank' rel='noopener noreferrer'>Open Source License</Link>
                   ? <Link href={'https://github.com/langgenius/dify/blob/main/LICENSE'} target='_blank' rel='noopener noreferrer'>Open Source License</Link>
                   : <>
                   : <>
-                    <Link href={language !== LanguagesSupportedUnderscore[1] ? 'https://docs.dify.ai/user-agreement/privacy-policy' : 'https://docs.dify.ai/v/zh-hans/user-agreement/privacy-policy'} target='_blank' rel='noopener noreferrer'>Privacy Policy</Link>,
-                    <Link href={language !== LanguagesSupportedUnderscore[1] ? 'https://docs.dify.ai/user-agreement/terms-of-service' : 'https://docs.dify.ai/v/zh-hans/user-agreement/terms-of-service'} target='_blank' rel='noopener noreferrer'>Terms of Service</Link>
+                    <Link href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/privacy-policy' : 'https://docs.dify.ai/v/zh-hans/user-agreement/privacy-policy'} target='_blank' rel='noopener noreferrer'>Privacy Policy</Link>,
+                    <Link href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/terms-of-service' : 'https://docs.dify.ai/v/zh-hans/user-agreement/terms-of-service'} target='_blank' rel='noopener noreferrer'>Terms of Service</Link>
                   </>
                   </>
               }
               }
             </div>
             </div>

+ 2 - 3
web/app/components/header/account-dropdown/index.tsx

@@ -16,7 +16,7 @@ import { useAppContext } from '@/context/app-context'
 import { ArrowUpRight, ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
 import { ArrowUpRight, ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
 import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
 import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
 import { useModalContext } from '@/context/modal-context'
 import { useModalContext } from '@/context/modal-context'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 export type IAppSelecotr = {
 export type IAppSelecotr = {
   isMobile: boolean
   isMobile: boolean
 }
 }
@@ -30,7 +30,6 @@ export default function AppSelector({ isMobile }: IAppSelecotr) {
   const [aboutVisible, setAboutVisible] = useState(false)
   const [aboutVisible, setAboutVisible] = useState(false)
 
 
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { userProfile, langeniusVersionInfo } = useAppContext()
   const { userProfile, langeniusVersionInfo } = useAppContext()
   const { setShowAccountSettingModal } = useModalContext()
   const { setShowAccountSettingModal } = useModalContext()
@@ -123,7 +122,7 @@ export default function AppSelector({ isMobile }: IAppSelecotr) {
                       <Link
                       <Link
                         className={classNames(itemClassName, 'group justify-between')}
                         className={classNames(itemClassName, 'group justify-between')}
                         href={
                         href={
-                          language !== LanguagesSupportedUnderscore[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
+                          locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/' : `https://docs.dify.ai/v/${locale.toLowerCase()}/`
                         }
                         }
                         target='_blank' rel='noopener noreferrer'>
                         target='_blank' rel='noopener noreferrer'>
                         <div>{t('common.userProfile.helpCenter')}</div>
                         <div>{t('common.userProfile.helpCenter')}</div>

+ 2 - 2
web/app/components/header/account-setting/language-page/index.tsx

@@ -10,7 +10,7 @@ import { updateUserProfile } from '@/service/common'
 import { ToastContext } from '@/app/components/base/toast'
 import { ToastContext } from '@/app/components/base/toast'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import { timezones } from '@/utils/timezone'
 import { timezones } from '@/utils/timezone'
-import { languages } from '@/utils/language'
+import { languages } from '@/i18n/language'
 
 
 const titleClassName = `
 const titleClassName = `
   mb-2 text-sm font-medium text-gray-900
   mb-2 text-sm font-medium text-gray-900
@@ -53,7 +53,7 @@ export default function LanguagePage() {
         <div className={titleClassName}>{t('common.language.displayLanguage')}</div>
         <div className={titleClassName}>{t('common.language.displayLanguage')}</div>
         <SimpleSelect
         <SimpleSelect
           defaultValue={locale || userProfile.interface_language}
           defaultValue={locale || userProfile.interface_language}
-          items={languages}
+          items={languages.filter(item => item.supported)}
           onSelect={item => handleSelect('language', item)}
           onSelect={item => handleSelect('language', item)}
           disabled={editing}
           disabled={editing}
         />
         />

+ 4 - 4
web/app/components/header/account-setting/members-page/index.tsx

@@ -20,7 +20,7 @@ import { useProviderContext } from '@/context/provider-context'
 import { Plan } from '@/app/components/billing/type'
 import { Plan } from '@/app/components/billing/type'
 import UpgradeBtn from '@/app/components/billing/upgrade-btn'
 import UpgradeBtn from '@/app/components/billing/upgrade-btn'
 import { NUM_INFINITE } from '@/app/components/billing/config'
 import { NUM_INFINITE } from '@/app/components/billing/config'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 dayjs.extend(relativeTime)
 dayjs.extend(relativeTime)
 
 
 const MembersPage = () => {
 const MembersPage = () => {
@@ -31,7 +31,7 @@ const MembersPage = () => {
     normal: t('common.members.normal'),
     normal: t('common.members.normal'),
   }
   }
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
+
   const { userProfile, currentWorkspace, isCurrentWorkspaceManager } = useAppContext()
   const { userProfile, currentWorkspace, isCurrentWorkspaceManager } = useAppContext()
   const { data, mutate } = useSWR({ url: '/workspaces/current/members' }, fetchMembers)
   const { data, mutate } = useSWR({ url: '/workspaces/current/members' }, fetchMembers)
   const [inviteModalVisible, setInviteModalVisible] = useState(false)
   const [inviteModalVisible, setInviteModalVisible] = useState(false)
@@ -55,7 +55,7 @@ const MembersPage = () => {
                 {isNotUnlimitedMemberPlan
                 {isNotUnlimitedMemberPlan
                   ? (
                   ? (
                     <div className='flex space-x-1'>
                     <div className='flex space-x-1'>
-                      <div>{t('billing.plansCommon.member')}{language !== LanguagesSupportedUnderscore[1] && accounts.length > 1 && 's'}</div>
+                      <div>{t('billing.plansCommon.member')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}</div>
                       <div className='text-gray-700'>{accounts.length}</div>
                       <div className='text-gray-700'>{accounts.length}</div>
                       <div>/</div>
                       <div>/</div>
                       <div>{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}</div>
                       <div>{plan.total.teamMembers === NUM_INFINITE ? t('billing.plansCommon.unlimited') : plan.total.teamMembers}</div>
@@ -64,7 +64,7 @@ const MembersPage = () => {
                   : (
                   : (
                     <div className='flex space-x-1'>
                     <div className='flex space-x-1'>
                       <div>{accounts.length}</div>
                       <div>{accounts.length}</div>
-                      <div>{t('billing.plansCommon.memberAfter')}{language !== LanguagesSupportedUnderscore[1] && accounts.length > 1 && 's'}</div>
+                      <div>{t('billing.plansCommon.memberAfter')}{locale !== LanguagesSupported[1] && accounts.length > 1 && 's'}</div>
                     </div>
                     </div>
                   )}
                   )}
               </div>
               </div>

+ 1 - 3
web/app/components/header/account-setting/members-page/invite-modal/index.tsx

@@ -15,7 +15,6 @@ import { emailRegex } from '@/config'
 import { ToastContext } from '@/app/components/base/toast'
 import { ToastContext } from '@/app/components/base/toast'
 import type { InvitationResult } from '@/models/common'
 import type { InvitationResult } from '@/models/common'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { getModelRuntimeSupported } from '@/utils/language'
 
 
 import 'react-multi-email/dist/style.css'
 import 'react-multi-email/dist/style.css'
 type IInviteModalProps = {
 type IInviteModalProps = {
@@ -32,7 +31,6 @@ const InviteModal = ({
   const { notify } = useContext(ToastContext)
   const { notify } = useContext(ToastContext)
 
 
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
 
 
   const InvitingRoles = useMemo(() => [
   const InvitingRoles = useMemo(() => [
     {
     {
@@ -51,7 +49,7 @@ const InviteModal = ({
       try {
       try {
         const { result, invitation_results } = await inviteMember({
         const { result, invitation_results } = await inviteMember({
           url: '/workspaces/current/members/invite-email',
           url: '/workspaces/current/members/invite-email',
-          body: { emails, role: role.name, language },
+          body: { emails, role: role.name, language: locale },
         })
         })
 
 
         if (result === 'success') {
         if (result === 'success') {

+ 8 - 8
web/app/components/header/account-setting/model-provider-page/declarations.ts

@@ -1,8 +1,8 @@
 export type FormValue = Record<string, any>
 export type FormValue = Record<string, any>
 
 
 export type TypeWithI18N<T = string> = {
 export type TypeWithI18N<T = string> = {
-  'en_US': T
-  'zh_Hans': T
+  'en-US': T
+  'zh-Hans': T
   [key: string]: T
   [key: string]: T
 }
 }
 
 
@@ -67,16 +67,16 @@ export enum ModelStatusEnum {
 
 
 export const MODEL_STATUS_TEXT: { [k: string]: TypeWithI18N } = {
 export const MODEL_STATUS_TEXT: { [k: string]: TypeWithI18N } = {
   'no-configure': {
   'no-configure': {
-    en_US: 'No Configure',
-    zh_Hans: '未配置凭据',
+    'en-US': 'No Configure',
+    'zh-Hans': '未配置凭据',
   },
   },
   'quota-exceeded': {
   'quota-exceeded': {
-    en_US: 'Quota Exceeded',
-    zh_Hans: '额度不足',
+    'en-US': 'Quota Exceeded',
+    'zh-Hans': '额度不足',
   },
   },
   'no-permission': {
   'no-permission': {
-    en_US: 'No Permission',
-    zh_Hans: '无使用权限',
+    'en-US': 'No Permission',
+    'zh-Hans': '无使用权限',
   },
   },
 }
 }
 
 

+ 1 - 2
web/app/components/header/account-setting/model-provider-page/hooks.ts

@@ -16,7 +16,6 @@ import {
   ConfigurateMethodEnum,
   ConfigurateMethodEnum,
   ModelTypeEnum,
   ModelTypeEnum,
 } from './declarations'
 } from './declarations'
-import { getModelRuntimeSupported } from '@/utils/language'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import {
 import {
   fetchDefaultModal,
   fetchDefaultModal,
@@ -59,7 +58,7 @@ export const useSystemDefaultModelAndModelList: UseDefaultModelAndModelList = (
 
 
 export const useLanguage = () => {
 export const useLanguage = () => {
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  return getModelRuntimeSupported(locale)
+  return locale.replace('-', '_')
 }
 }
 
 
 export const useProviderCrenditialsFormSchemasValue = (
 export const useProviderCrenditialsFormSchemasValue = (

+ 4 - 5
web/app/components/header/maintenance-notice.tsx

@@ -2,11 +2,10 @@ import { useState } from 'react'
 import { useContext } from 'use-context-selector'
 import { useContext } from 'use-context-selector'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import { X } from '@/app/components/base/icons/src/vender/line/general'
 import { X } from '@/app/components/base/icons/src/vender/line/general'
-import { NOTICE_I18N, getModelRuntimeSupported } from '@/utils/language'
+import { NOTICE_I18N } from '@/i18n/language'
 
 
 const MaintenanceNotice = () => {
 const MaintenanceNotice = () => {
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
 
 
   const [showNotice, setShowNotice] = useState(localStorage.getItem('hide-maintenance-notice') !== '1')
   const [showNotice, setShowNotice] = useState(localStorage.getItem('hide-maintenance-notice') !== '1')
   const handleJumpNotice = () => {
   const handleJumpNotice = () => {
@@ -26,11 +25,11 @@ const MaintenanceNotice = () => {
 
 
   return (
   return (
     <div className='shrink-0 flex items-center px-4 h-[38px] bg-[#FFFAEB] border-b border-[0.5px] border-b-[#FEF0C7] z-20'>
     <div className='shrink-0 flex items-center px-4 h-[38px] bg-[#FFFAEB] border-b border-[0.5px] border-b-[#FEF0C7] z-20'>
-      <div className='shrink-0 flex items-center mr-2 px-2 h-[22px] bg-[#F79009] text-white text-[11px] font-medium rounded-xl'>{titleByLocale[language]}</div>
+      <div className='shrink-0 flex items-center mr-2 px-2 h-[22px] bg-[#F79009] text-white text-[11px] font-medium rounded-xl'>{titleByLocale[locale]}</div>
       {
       {
         (NOTICE_I18N.href && NOTICE_I18N.href !== '#')
         (NOTICE_I18N.href && NOTICE_I18N.href !== '#')
-          ? <div className='grow text-xs font-medium text-gray-700 cursor-pointer' onClick={handleJumpNotice}>{descByLocale[language]}</div>
-          : <div className='grow text-xs font-medium text-gray-700'>{descByLocale[language]}</div>
+          ? <div className='grow text-xs font-medium text-gray-700 cursor-pointer' onClick={handleJumpNotice}>{descByLocale[locale]}</div>
+          : <div className='grow text-xs font-medium text-gray-700'>{descByLocale[locale]}</div>
       }
       }
       <X className='shrink-0 w-4 h-4 text-gray-500 cursor-pointer' onClick={handleCloseNotice} />
       <X className='shrink-0 w-4 h-4 text-gray-500 cursor-pointer' onClick={handleCloseNotice} />
     </div>
     </div>

+ 1 - 1
web/app/components/i18n.tsx

@@ -5,7 +5,7 @@ import React, { useEffect } from 'react'
 import { changeLanguage } from '@/i18n/i18next-config'
 import { changeLanguage } from '@/i18n/i18next-config'
 import I18NContext from '@/context/i18n'
 import I18NContext from '@/context/i18n'
 import type { Locale } from '@/i18n'
 import type { Locale } from '@/i18n'
-import { setLocaleOnClient } from '@/i18n/client'
+import { setLocaleOnClient } from '@/i18n'
 
 
 export type II18nProps = {
 export type II18nProps = {
   locale: Locale
   locale: Locale

+ 0 - 23
web/app/components/locale-switcher.tsx

@@ -1,23 +0,0 @@
-'use client'
-
-import { i18n } from '@/i18n'
-import { setLocaleOnClient } from '@/i18n/client'
-
-const LocaleSwitcher = () => {
-  return (
-    <div className="mt-4">
-      <p>Locale switcher:</p>
-      <ul>
-        {i18n.locales.map((locale) => {
-          return (
-            <li key={locale}>
-              <div className='cursor-pointer ' onClick={() => setLocaleOnClient(locale)}>{locale}</div>
-            </li>
-          )
-        })}
-      </ul>
-    </div>
-  )
-}
-
-export default LocaleSwitcher

+ 2 - 2
web/app/components/tools/edit-custom-collection-modal/test-api.tsx

@@ -10,7 +10,7 @@ import Button from '@/app/components/base/button'
 import Drawer from '@/app/components/base/drawer-plus'
 import Drawer from '@/app/components/base/drawer-plus'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import { testAPIAvailable } from '@/service/tools'
 import { testAPIAvailable } from '@/service/tools'
-import { getModelRuntimeSupported } from '@/utils/language'
+import { getLanguage } from '@/i18n/language'
 
 
 type Props = {
 type Props = {
   customCollection: CustomCollectionBackend
   customCollection: CustomCollectionBackend
@@ -27,7 +27,7 @@ const TestApi: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
+  const language = getLanguage(locale)
   const [credentialsModalShow, setCredentialsModalShow] = useState(false)
   const [credentialsModalShow, setCredentialsModalShow] = useState(false)
   const [tempCredential, setTempCredential] = React.useState<Credential>(customCollection.credentials)
   const [tempCredential, setTempCredential] = React.useState<Credential>(customCollection.credentials)
   const [result, setResult] = useState<string>('')
   const [result, setResult] = useState<string>('')

+ 2 - 3
web/app/components/tools/tool-list/header.tsx

@@ -8,8 +8,7 @@ import type { Collection } from '../types'
 import { CollectionType, LOC } from '../types'
 import { CollectionType, LOC } from '../types'
 import { Settings01 } from '../../base/icons/src/vender/line/general'
 import { Settings01 } from '../../base/icons/src/vender/line/general'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { getModelRuntimeSupported } from '@/utils/language'
-
+import { getLanguage } from '@/i18n/language'
 type Props = {
 type Props = {
   icon: JSX.Element
   icon: JSX.Element
   collection: Collection
   collection: Collection
@@ -26,7 +25,7 @@ const Header: FC<Props> = ({
   onShowEditCustomCollection,
   onShowEditCustomCollection,
 }) => {
 }) => {
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
+  const language = getLanguage(locale)
   const { t } = useTranslation()
   const { t } = useTranslation()
   const isInToolsPage = loc === LOC.tools
   const isInToolsPage = loc === LOC.tools
   const isInDebugPage = !isInToolsPage
   const isInDebugPage = !isInToolsPage

+ 3 - 3
web/app/components/tools/tool-list/item.tsx

@@ -10,8 +10,7 @@ import { CollectionType } from '../types'
 import TooltipPlus from '../../base/tooltip-plus'
 import TooltipPlus from '../../base/tooltip-plus'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool'
 import SettingBuiltInTool from '@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool'
-import { getModelRuntimeSupported } from '@/utils/language'
-
+import { getLanguage } from '@/i18n/language'
 type Props = {
 type Props = {
   collection: Collection
   collection: Collection
   icon: JSX.Element
   icon: JSX.Element
@@ -33,7 +32,8 @@ const Item: FC<Props> = ({
 }) => {
 }) => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
+  const language = getLanguage(locale)
+
   const isBuiltIn = collection.type === CollectionType.builtIn
   const isBuiltIn = collection.type === CollectionType.builtIn
   const canShowDetail = !isBuiltIn || (isBuiltIn && isInToolsPage)
   const canShowDetail = !isBuiltIn || (isBuiltIn && isInToolsPage)
   const [showDetail, setShowDetail] = useState(false)
   const [showDetail, setShowDetail] = useState(false)

+ 2 - 2
web/app/components/tools/tool-nav-list/item.tsx

@@ -6,7 +6,7 @@ import cn from 'classnames'
 import AppIcon from '../../base/app-icon'
 import AppIcon from '../../base/app-icon'
 import type { Collection } from '@/app/components/tools/types'
 import type { Collection } from '@/app/components/tools/types'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { getModelRuntimeSupported } from '@/utils/language'
+import { getLanguage } from '@/i18n/language'
 
 
 type Props = {
 type Props = {
   isCurrent: boolean
   isCurrent: boolean
@@ -20,7 +20,7 @@ const Item: FC<Props> = ({
   onClick,
   onClick,
 }) => {
 }) => {
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
+  const language = getLanguage(locale)
   return (
   return (
     <div
     <div
       className={cn(isCurrent && 'bg-white shadow-xs rounded-lg', 'mt-1 flex h-9 items-center px-2 space-x-2 cursor-pointer')}
       className={cn(isCurrent && 'bg-white shadow-xs rounded-lg', 'mt-1 flex h-9 items-center px-2 space-x-2 cursor-pointer')}

+ 0 - 2
web/app/install/installForm.tsx

@@ -17,8 +17,6 @@ const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
 
 
 const InstallForm = () => {
 const InstallForm = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
-  // const { locale } = useContext(I18n)
-  // const language = getModelRuntimeSupported(locale)
   const router = useRouter()
   const router = useRouter()
 
 
   const [email, setEmail] = React.useState('')
   const [email, setEmail] = React.useState('')

+ 2 - 2
web/app/signin/_header.tsx

@@ -2,7 +2,7 @@
 import React from 'react'
 import React from 'react'
 import { useContext } from 'use-context-selector'
 import { useContext } from 'use-context-selector'
 import Select from '@/app/components/base/select/locale'
 import Select from '@/app/components/base/select/locale'
-import { languages } from '@/utils/language'
+import { languages } from '@/i18n/language'
 import { type Locale } from '@/i18n'
 import { type Locale } from '@/i18n'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
 import LogoSite from '@/app/components/base/logo/logo-site'
 import LogoSite from '@/app/components/base/logo/logo-site'
@@ -17,7 +17,7 @@ const Header = () => {
     <LogoSite />
     <LogoSite />
     <Select
     <Select
       value={locale}
       value={locale}
-      items={languages}
+      items={languages.filter(item => item.supported)}
       onChange={(value) => {
       onChange={(value) => {
         setLocaleOnClient(value as Locale)
         setLocaleOnClient(value as Locale)
       }}
       }}

+ 3 - 4
web/app/signin/normalForm.tsx

@@ -12,7 +12,7 @@ import { IS_CE_EDITION, apiPrefix } from '@/config'
 import Button from '@/app/components/base/button'
 import Button from '@/app/components/base/button'
 import { login, oauth } from '@/service/common'
 import { login, oauth } from '@/service/common'
 import I18n from '@/context/i18n'
 import I18n from '@/context/i18n'
-import { LanguagesSupportedUnderscore, getModelRuntimeSupported } from '@/utils/language'
+import { LanguagesSupported } from '@/i18n/language'
 import { getPurifyHref } from '@/utils'
 import { getPurifyHref } from '@/utils'
 const validEmailReg = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/
 const validEmailReg = /^[\w\.-]+@([\w-]+\.)+[\w-]{2,}$/
 
 
@@ -67,7 +67,6 @@ const NormalForm = () => {
   const { t } = useTranslation()
   const { t } = useTranslation()
   const router = useRouter()
   const router = useRouter()
   const { locale } = useContext(I18n)
   const { locale } = useContext(I18n)
-  const language = getModelRuntimeSupported(locale)
 
 
   const [state, dispatch] = useReducer(reducer, {
   const [state, dispatch] = useReducer(reducer, {
     formValid: false,
     formValid: false,
@@ -283,13 +282,13 @@ const NormalForm = () => {
             <Link
             <Link
               className='text-primary-600'
               className='text-primary-600'
               target='_blank' rel='noopener noreferrer'
               target='_blank' rel='noopener noreferrer'
-              href={language !== LanguagesSupportedUnderscore[1] ? 'https://docs.dify.ai/user-agreement/terms-of-service' : 'https://docs.dify.ai/v/zh-hans/user-agreement/terms-of-service'}
+              href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/terms-of-service' : 'https://docs.dify.ai/v/zh-hans/user-agreement/terms-of-service'}
             >{t('login.tos')}</Link>
             >{t('login.tos')}</Link>
             &nbsp;&&nbsp;
             &nbsp;&&nbsp;
             <Link
             <Link
               className='text-primary-600'
               className='text-primary-600'
               target='_blank' rel='noopener noreferrer'
               target='_blank' rel='noopener noreferrer'
-              href={language !== LanguagesSupportedUnderscore[1] ? 'https://docs.dify.ai/user-agreement/privacy-policy' : 'https://docs.dify.ai/v/zh-hans/user-agreement/privacy-policy'}
+              href={locale !== LanguagesSupported[1] ? 'https://docs.dify.ai/user-agreement/privacy-policy' : 'https://docs.dify.ai/v/zh-hans/user-agreement/privacy-policy'}
             >{t('login.pp')}</Link>
             >{t('login.pp')}</Link>
           </div>
           </div>
 
 

+ 2 - 2
web/app/signin/oneMoreStep.tsx

@@ -10,7 +10,7 @@ import Tooltip from '@/app/components/base/tooltip/index'
 
 
 import { SimpleSelect } from '@/app/components/base/select'
 import { SimpleSelect } from '@/app/components/base/select'
 import { timezones } from '@/utils/timezone'
 import { timezones } from '@/utils/timezone'
-import { LanguagesSupported, languages } from '@/utils/language'
+import { LanguagesSupported, languages } from '@/i18n/language'
 import { oneMoreStep } from '@/service/common'
 import { oneMoreStep } from '@/service/common'
 import Toast from '@/app/components/base/toast'
 import Toast from '@/app/components/base/toast'
 // import I18n from '@/context/i18n'
 // import I18n from '@/context/i18n'
@@ -122,7 +122,7 @@ const OneMoreStep = () => {
             <div className="relative mt-1 rounded-md shadow-sm">
             <div className="relative mt-1 rounded-md shadow-sm">
               <SimpleSelect
               <SimpleSelect
                 defaultValue={LanguagesSupported[0]}
                 defaultValue={LanguagesSupported[0]}
-                items={languages}
+                items={languages.filter(item => item.supported)}
                 onSelect={(item) => {
                 onSelect={(item) => {
                   dispatch({ type: 'interface_language', value: item.value })
                   dispatch({ type: 'interface_language', value: item.value })
                 }}
                 }}

+ 1 - 3
web/context/i18n.ts

@@ -5,14 +5,12 @@ type II18NContext = {
   locale: Locale
   locale: Locale
   i18n: Record<string, any>
   i18n: Record<string, any>
   setLocaleOnClient: (locale: Locale, reloadPage?: boolean) => void
   setLocaleOnClient: (locale: Locale, reloadPage?: boolean) => void
-  //   setI8N: (i18n: Record<string, string>) => void,
 }
 }
 
 
 const I18NContext = createContext<II18NContext>({
 const I18NContext = createContext<II18NContext>({
-  locale: 'en',
+  locale: 'en-US',
   i18n: {},
   i18n: {},
   setLocaleOnClient: (lang: Locale, reloadPage?: boolean) => { },
   setLocaleOnClient: (lang: Locale, reloadPage?: boolean) => { },
-  //   setI8N: () => {},
 })
 })
 
 
 export default I18NContext
 export default I18NContext

+ 175 - 0
web/i18n/README.md

@@ -0,0 +1,175 @@
+# Internationalization (i18n)
+
+## Introduction
+
+This directory contains the internationalization (i18n) files for this project.
+
+## File Structure
+
+```
+├── [  24]  README.md
+├── [   0]  README_CN.md
+├── [ 704]  en-US
+│   ├── [2.4K]  app-annotation.ts
+│   ├── [5.2K]  app-api.ts
+│   ├── [ 16K]  app-debug.ts
+│   ├── [2.1K]  app-log.ts
+│   ├── [5.3K]  app-overview.ts
+│   ├── [1.9K]  app.ts
+│   ├── [4.1K]  billing.ts
+│   ├── [ 17K]  common.ts
+│   ├── [ 859]  custom.ts
+│   ├── [5.7K]  dataset-creation.ts
+│   ├── [ 10K]  dataset-documents.ts
+│   ├── [ 761]  dataset-hit-testing.ts
+│   ├── [1.7K]  dataset-settings.ts
+│   ├── [2.0K]  dataset.ts
+│   ├── [ 941]  explore.ts
+│   ├── [  52]  layout.ts
+│   ├── [2.3K]  login.ts
+│   ├── [  52]  register.ts
+│   ├── [2.5K]  share-app.ts
+│   └── [2.8K]  tools.ts
+├── [1.6K]  i18next-config.ts
+├── [ 634]  index.ts
+├── [4.4K]  language.ts
+```
+
+We use English as the default language. The i18n files are organized by language and then by module. For example, the English translation for the `app` module is in `en-US/app.ts`.
+
+If you want to add a new language or modify an existing translation, you can create a new file for the language or modify the existing file. The file name should be the language code (e.g., `zh-CN` for Chinese) and the file extension should be `.ts`.
+
+For example, if you want to add french translation, you can create a new folder `fr-FR` and add the translation files in it.
+
+By default we will use `LanguagesSupported` to determine which languages are supported. For example, in login page and settings page, we will use `LanguagesSupported` to determine which languages are supported and display them in the language selection dropdown.
+
+## Example
+
+1. Create a new folder for the new language.
+
+```
+cp -r en-US fr-FR
+```
+
+2. Modify the translation files in the new folder.
+
+3. Add type to new language in the `language.ts` file.
+
+```typescript
+export type I18nText = {
+  'en-US': string
+  'zh-Hans': string
+  'pt-BR': string
+  'es-ES': string
+  'fr-FR': string
+  'de-DE': string
+  'ja-JP': string
+  'ko-KR': string
+  'ru-RU': string
+  'it-IT': string
+  'uk-UA': string
+  'YOUR_LANGUAGE_CODE': string
+}
+```
+
+4. Add the new language to the `language.ts` file.
+
+```typescript
+
+export const languages = [
+  {
+    value: 'en-US',
+    name: 'English(United States)',
+    example: 'Hello, Dify!',
+    supported: true,
+  },
+  {
+    value: 'zh-Hans',
+    name: '简体中文',
+    example: '你好,Dify!',
+    supported: true,
+  },
+  {
+    value: 'pt-BR',
+    name: 'Português(Brasil)',
+    example: 'Olá, Dify!',
+    supported: true,
+  },
+  {
+    value: 'es-ES',
+    name: 'Español(España)',
+    example: 'Saluton, Dify!',
+    supported: false,
+  },
+  {
+    value: 'fr-FR',
+    name: 'Français(France)',
+    example: 'Bonjour, Dify!',
+    supported: false,
+  },
+  {
+    value: 'de-DE',
+    name: 'Deutsch(Deutschland)',
+    example: 'Hallo, Dify!',
+    supported: false,
+  },
+  {
+    value: 'ja-JP',
+    name: '日本語(日本)',
+    example: 'こんにちは、Dify!',
+    supported: false,
+  },
+  {
+    value: 'ko-KR',
+    name: '한국어(대한민국)',
+    example: '안녕, Dify!',
+    supported: false,
+  },
+  {
+    value: 'ru-RU',
+    name: 'Русский(Россия)',
+    example: ' Привет, Dify!',
+    supported: false,
+  },
+  {
+    value: 'it-IT',
+    name: 'Italiano(Italia)',
+    example: 'Ciao, Dify!',
+    supported: false,
+  },
+  {
+    value: 'th-TH',
+    name: 'ไทย(ประเทศไทย)',
+    example: 'สวัสดี Dify!',
+    supported: false,
+  },
+  {
+    value: 'id-ID',
+    name: 'Bahasa Indonesia',
+    example: 'Saluto, Dify!',
+    supported: false,
+  },
+  {
+    value: 'uk-UA',
+    name: 'Українська(Україна)',
+    example: 'Привет, Dify!',
+    supported: true,
+  },
+  // Add your language here 👇
+  ...
+  // Add your language here 👆
+]
+```
+
+5. Don't forget to mark the supported field as `true` if the language is supported.
+
+6. Sometime you might need to do some changes in the server side. Please change this file as well. 👇
+https://github.com/langgenius/dify/blob/61e4bbabaf2758354db4073cbea09fdd21a5bec1/api/constants/languages.py#L5
+
+
+
+## Clean Up
+
+That's it! You have successfully added a new language to the project. If you want to remove a language, you can simply delete the folder and remove the language from the `language.ts` file.
+
+We have a list of languages that we support in the `language.ts` file. But some of them are not supported yet. So, they are marked as `false`. If you want to support a language, you can follow the steps above and mark the supported field as `true`.

+ 0 - 82
web/i18n/README_CN.md

@@ -1,82 +0,0 @@
-# 前端 i18n 修改
-
-## 后端多语言支持
-
-`api/libs/helper.py:117` 中添加对应的语言支持。如:
-```python
-def supported_language(lang):
-    if lang in ['en-US', 'zh-Hans', 'de', 'de-AT']:
-        return lang
-```
-
-
-## 添加多语言文件
-
-在 `web/i18n/lang` 下添加不同模块的多语言文件。文件命令为 模块名.{LANG}.ts。详细参考[LANG](https://www.venea.net/web/culture_code) 
-
-## 引入新添加的多语言文件
-在 `web/i18n/i18next-config.ts` 中 resources 对象中中引入新添加的多语言文件。如:
-
-```javascript
-const resources = {
-    'en': {...},
-    'zh-Hans': {...},
-    // 引入新添加的语言
-    'new LANG': {
-      translation: {
-        common: commonNewLan,
-        layout: layoutNewLan,
-        ...
-      }
-    }
-}
-```
-
-## 翻译过程中的改动
-
-### 日期格式化的多语言处理
-
-目前日期做多语言格式化的文件涉及到如下 2 个: 
-
-```javascript
-1. web/app/components/header/account-setting/members-page/index.tsx
-// Line: 78 
-{dayjs(Number((account.last_login_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}
-2. web/app/components/develop/secret-key/secret-key-modal.tsx
-// Line:82
-const formatDate = (timestamp: any) => {
-    if (locale === 'en') {
-      return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format((+timestamp) * 1000)
-    } else {
-      return new Intl.DateTimeFormat('fr-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).format((+timestamp) * 1000)
-    }
-  }
-```
-看需求做对应的改动。
-
-### 翻译中带变量的内容的处理
-
-翻译中会存在带变量的情况,变量的值会在运行时被替换。翻译中的变量会用{{ 和 }} 包裹。
-翻译带变量的内容时:
-  1. 不能改变量的名称。即:变量的名称不需要做翻译。
-  2. 确保变量填充后,语句仍保持通顺。
-
-查找所有翻译中带变量的方式:在 ./web/i18n/lang 下搜索:{{。
-
-### 翻译内容太长破坏 UI
-
-如果某个翻译的内容比其他语言的长很多,检查下是否会破坏 UI。
-
-## 帮助文档
-
-目前的帮助文档的调整逻辑是:中文跳转中文,其他语言跳英文。如果帮助文档也做了多语言。需要做这块的改动。
-
-## 验证
-
-新增语言包建议通过本地部署最新代码来验证,可参考:https://docs.dify.ai/getting-started/install-self-hosted/local-source-code
-验证点:
-1. 首次初始化安装是否存在新语言下拉选项,以及是否可以用新语言进行初始化
-2. 个人设置中是否存在新语言下拉选项,以及是否可以选择并保存新语言
-3. 界面各处文案是否使用新语言来展示,以及文案是否破坏 UI
-4. 从模板创建应用内容是否均为新语言
-5. (CLOUD 版)通过 OAuth 授权登录后,是否直接设置当前浏览器语言为界面语言

+ 0 - 81
web/i18n/README_EN.md

@@ -1,81 +0,0 @@
-# Frontend i18n modification
-
-## Backend i18n modification
-
-`api/libs/helper.py:117` Add corresponding language support. Such as:
-```python
-def supported_language(lang):
-    if lang in ['en-US', 'zh-Hans', 'de', 'de-AT']:
-        return lang
-```
-
-## Adding multiple language files
-
-Add multilingual files for different modules under web/i18n/lang. The file name is Module name.{LANG}.ts. Please refer [LANG](https://www.venea.net/web/culture_code) for details.
-
-## Introducing a newly added multilingual file 
-
-Introduce the newly added multilingual file in the resources object in web/i18n/i18next-config.ts. For example:
-
-```javascript
-const resources = {
-    'en': {...},
-    'zh-Hans': {...},  
-    _// Introduce the newly added language_
-    'new LANG': {  
-      translation: {  
-        common: commonNewLan,  
-        layout: layoutNewLan,  
-        ...  
-      }  
-    }
-}
-```
-## Changes in the translation process
-
-### Multi-language processing of date formatting
-
-Currently, two files are involved in date formatting in multiple languages:
-
-```javascript
-1. web/app/components/header/account-setting/members-page/index.tsx
-_// Line 78_   
-{dayjs(Number((account.last_login_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}  
-2. web/app/components/develop/secret-key/secret-key-modal.tsx  
-_// Line 82_
-const formatDate = (timestamp: any) => {
-    if (locale === 'en') {  
-      return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format((+timestamp) * 1000)  
-    } else {  
-      return new Intl.DateTimeFormat('fr-CA', { year: 'numeric', month: '2-digit', day: '2-digit' }).format((+timestamp) * 1000)  
-    }  
-  }
-```
-
-Make corresponding changes based on requirements.
-
-### Handling translation content with variables
-
-There will be variables in the translation, and the value of the variables will be replaced at runtime. Variables in translation will be wrapped in {{ and }}.
-When translating content with variables:
-  1. Do not change the variable name. That is: the variable name does not need to be translated. 
-  2. Ensure that the statement remains smooth after the variable is filled.
-Find all translations with variables: search for {{ under ./web/i18n/lang. 
-
-### Translation content is too long to destroy UI
-
-If a certain translation content is much longer than other languages, check if it will destroy the UI.
-
-## Help documentation
-
-The current logic for adjusting the help documentation is: Chinese jumps to Chinese, other languages jump to English. If the help documentation is also multilingual, changes need to be made in this area. 
-
-## Verification
-
-It is recommended to verify the newly added language pack through local deployment of the latest code. For reference: https://docs.dify.ai/getting-started/install-self-hosted/local-source-code
-Verification points:
-1. Whether the initial installation has new language drop-down options, and whether the new language can be used for initialization
-2. Whether there is a new language drop-down option in personal settings, and whether the new language can be selected and saved 
-3. Whether the text in the interface is displayed in the new language, and whether the text destroys the UI
-4. Whether the content created from the template is all in the new language
-5. (CLOUD version) After logging in through OAuth authorization, whether the current browser language is set directly as the interface language

+ 0 - 16
web/i18n/client.ts

@@ -1,16 +0,0 @@
-import Cookies from 'js-cookie'
-import type { Locale } from '.'
-import { i18n } from '.'
-import { LOCALE_COOKIE_NAME } from '@/config'
-import { changeLanguage } from '@/i18n/i18next-config'
-
-// same logic as server
-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)
-  reloadPage && location.reload()
-}

+ 0 - 0
web/i18n/lang/app-annotation.en.ts → web/i18n/en-US/app-annotation.ts


+ 0 - 0
web/i18n/lang/app-api.en.ts → web/i18n/en-US/app-api.ts


+ 0 - 0
web/i18n/lang/app-debug.en.ts → web/i18n/en-US/app-debug.ts


+ 0 - 0
web/i18n/lang/app-log.en.ts → web/i18n/en-US/app-log.ts


+ 0 - 0
web/i18n/lang/app-overview.en.ts → web/i18n/en-US/app-overview.ts


+ 0 - 0
web/i18n/lang/app.en.ts → web/i18n/en-US/app.ts


+ 0 - 0
web/i18n/lang/billing.en.ts → web/i18n/en-US/billing.ts


+ 0 - 0
web/i18n/lang/common.en.ts → web/i18n/en-US/common.ts


+ 0 - 0
web/i18n/lang/custom.en.ts → web/i18n/en-US/custom.ts


+ 0 - 0
web/i18n/lang/dataset-creation.en.ts → web/i18n/en-US/dataset-creation.ts


+ 0 - 0
web/i18n/lang/dataset-documents.en.ts → web/i18n/en-US/dataset-documents.ts


+ 0 - 0
web/i18n/lang/dataset-hit-testing.en.ts → web/i18n/en-US/dataset-hit-testing.ts


+ 0 - 0
web/i18n/lang/dataset-settings.en.ts → web/i18n/en-US/dataset-settings.ts


+ 0 - 0
web/i18n/lang/dataset.en.ts → web/i18n/en-US/dataset.ts


+ 0 - 0
web/i18n/lang/explore.en.ts → web/i18n/en-US/explore.ts


+ 0 - 0
web/i18n/lang/layout.en.ts → web/i18n/en-US/layout.ts


+ 0 - 0
web/i18n/lang/login.en.ts → web/i18n/en-US/login.ts


+ 0 - 0
web/i18n/lang/layout.pt.ts → web/i18n/en-US/register.ts


+ 0 - 0
web/i18n/lang/share-app.en.ts → web/i18n/en-US/share-app.ts


+ 0 - 0
web/i18n/lang/tools.en.ts → web/i18n/en-US/tools.ts


+ 31 - 189
web/i18n/i18next-config.ts

@@ -1,202 +1,44 @@
 'use client'
 'use client'
 import i18n from 'i18next'
 import i18n from 'i18next'
 import { initReactI18next } from 'react-i18next'
 import { initReactI18next } from 'react-i18next'
-import commonEn from './lang/common.en'
-import commonZh from './lang/common.zh'
-import commonUk from './lang/common.uk' // Ukrainian import
-import commonPt from './lang/common.pt' // Portuguese import
-import loginEn from './lang/login.en'
-import loginZh from './lang/login.zh'
-import loginPt from './lang/login.pt' // Portuguese import
-import loginUk from './lang/login.uk' // Ukrainian import
-import registerEn from './lang/register.en'
-import registerZh from './lang/register.zh'
-import registerPt from './lang/register.pt' // Portuguese import
-import registerUk from './lang/register.uk' // Ukrainian import
-import layoutEn from './lang/layout.en'
-import layoutZh from './lang/layout.zh'
-import layoutPt from './lang/layout.pt' // Portuguese import
-import layoutUk from './lang/layout.uk' // Ukrainian import
-import appEn from './lang/app.en'
-import appZh from './lang/app.zh'
-import appPt from './lang/app.pt' // Portuguese import
-import appUk from './lang/app.uk' // Ukrainian import
-import appOverviewEn from './lang/app-overview.en'
-import appOverviewZh from './lang/app-overview.zh'
-import appOverviewPt from './lang/app-overview.pt' // Portuguese import
-import appOverviewUk from './lang/app-overview.uk' // Ukrainian import
-import appDebugEn from './lang/app-debug.en'
-import appDebugZh from './lang/app-debug.zh'
-import appDebugPt from './lang/app-debug.pt' // Portuguese import
-import appDebugUk from './lang/app-debug.uk' // Ukrainian import
-import appApiEn from './lang/app-api.en'
-import appApiZh from './lang/app-api.zh'
-import appApiPt from './lang/app-api.pt' // Portuguese import
-import appApiUk from './lang/app-api.uk' // Ukrainian import
-import appLogEn from './lang/app-log.en'
-import appLogZh from './lang/app-log.zh'
-import appLogPt from './lang/app-log.pt' // Portuguese import
-import appLogUk from './lang/app-log.uk' // Ukrainian import
-import appAnnotationEn from './lang/app-annotation.en'
-import appAnnotationZh from './lang/app-annotation.zh'
-import appAnnotationPt from './lang/app-annotation.pt' // Portuguese import
-import appAnnotationUk from './lang/app-annotation.uk' // Ukrainian import
-import shareEn from './lang/share-app.en'
-import shareZh from './lang/share-app.zh'
-import sharePt from './lang/share-app.pt' // Portuguese import
-import shareUk from './lang/share-app.uk' // Ukrainian import
-import datasetEn from './lang/dataset.en'
-import datasetZh from './lang/dataset.zh'
-import datasetPt from './lang/dataset.pt' // Portuguese import
-import datasetUk from './lang/dataset.uk' // Ukrainian import
-import datasetDocumentsEn from './lang/dataset-documents.en'
-import datasetDocumentsZh from './lang/dataset-documents.zh'
-import datasetDocumentsPt from './lang/dataset-documents.pt' // Portuguese import
-import datasetDocumentsUk from './lang/dataset-documents.uk' // Ukrainian import
-import datasetHitTestingEn from './lang/dataset-hit-testing.en'
-import datasetHitTestingZh from './lang/dataset-hit-testing.zh'
-import datasetHitTestingPt from './lang/dataset-hit-testing.pt' // Portuguese import
-import datasetHitTestingUk from './lang/dataset-hit-testing.uk' // Ukrainian import
-import datasetSettingsEn from './lang/dataset-settings.en'
-import datasetSettingsZh from './lang/dataset-settings.zh'
-import datasetSettingsPt from './lang/dataset-settings.pt' // Portuguese import
-import datasetSettingsUk from './lang/dataset-settings.uk' // Ukrainian import
-import datasetCreationEn from './lang/dataset-creation.en'
-import datasetCreationZh from './lang/dataset-creation.zh'
-import datasetCreationPt from './lang/dataset-creation.pt' // Portuguese import
-import datasetCreationUk from './lang/dataset-creation.uk' // Ukrainian import
-import exploreEn from './lang/explore.en'
-import exploreZh from './lang/explore.zh'
-import explorePt from './lang/explore.pt' // Portuguese import
-import exploreUk from './lang/explore.uk' // Ukrainian import
-import billingEn from './lang/billing.en'
-import billingZh from './lang/billing.zh'
-import billingPt from './lang/billing.pt' // Portuguese import
-import billingUk from './lang/billing.uk' // Ukrainian import
-import customEn from './lang/custom.en'
-import customZh from './lang/custom.zh'
-import customPt from './lang/custom.pt' // Portuguese import
-import customUk from './lang/custom.uk' // Ukrainian import
-import toolsEn from './lang/tools.en'
-import toolsZh from './lang/tools.zh'
-import toolsPt from './lang/tools.pt' // Portuguese import
-import toolsUk from './lang/tools.uk' // Ukrainian import
 
 
-const resources = {
-  'en-US': {
-    translation: {
-      common: commonEn,
-      layout: layoutEn, // page layout
-      login: loginEn,
-      register: registerEn,
-      // app
-      app: appEn,
-      appOverview: appOverviewEn,
-      appDebug: appDebugEn,
-      appApi: appApiEn,
-      appLog: appLogEn,
-      appAnnotation: appAnnotationEn,
-      // share
-      share: shareEn,
-      dataset: datasetEn,
-      datasetDocuments: datasetDocumentsEn,
-      datasetHitTesting: datasetHitTestingEn,
-      datasetSettings: datasetSettingsEn,
-      datasetCreation: datasetCreationEn,
-      explore: exploreEn,
-      // billing
-      billing: billingEn,
-      custom: customEn,
-      // tools
-      tools: toolsEn,
-    },
-  },
-  'zh-Hans': {
-    translation: {
-      common: commonZh,
-      layout: layoutZh,
-      login: loginZh,
-      register: registerZh,
-      // app
-      app: appZh,
-      appOverview: appOverviewZh,
-      appDebug: appDebugZh,
-      appApi: appApiZh,
-      appLog: appLogZh,
-      appAnnotation: appAnnotationZh,
-      // share
-      share: shareZh,
-      dataset: datasetZh,
-      datasetDocuments: datasetDocumentsZh,
-      datasetHitTesting: datasetHitTestingZh,
-      datasetSettings: datasetSettingsZh,
-      datasetCreation: datasetCreationZh,
-      explore: exploreZh,
-      billing: billingZh,
-      custom: customZh,
-      // tools
-      tools: toolsZh,
-    },
-  },
-  'pt-BR': {
-    translation: {
-      common: commonPt,
-      layout: layoutPt,
-      login: loginPt,
-      register: registerPt,
-      // app
-      app: appPt,
-      appOverview: appOverviewPt,
-      appDebug: appDebugPt,
-      appApi: appApiPt,
-      appLog: appLogPt,
-      appAnnotation: appAnnotationPt,
-      // share
-      share: sharePt,
-      dataset: datasetPt,
-      datasetDocuments: datasetDocumentsPt,
-      datasetHitTesting: datasetHitTestingPt,
-      datasetSettings: datasetSettingsPt,
-      datasetCreation: datasetCreationPt,
-      explore: explorePt,
-      billing: billingPt,
-      custom: customPt,
-      tools: toolsPt,
-    },
-  },
-  'uk-UA': {
-    translation: {
-      common: commonUk,
-      layout: layoutUk,
-      login: loginUk,
-      register: registerUk,
-      app: appUk,
-      appOverview: appOverviewUk,
-      appDebug: appDebugUk,
-      appApi: appApiUk,
-      appLog: appLogUk,
-      appAnnotation: appAnnotationUk,
-      share: shareUk,
-      dataset: datasetUk,
-      datasetDocuments: datasetDocumentsUk,
-      datasetHitTesting: datasetHitTestingUk,
-      datasetSettings: datasetSettingsUk,
-      datasetCreation: datasetCreationUk,
-      explore: exploreUk,
-      billing: billingUk,
-      custom: customUk,
-      tools: toolsUk,
-    },
+import { LanguagesSupported } from '@/i18n/language'
+
+const loadLangResources = (lang: string) => ({
+  translation: {
+    common: require(`./${lang}/common`).default,
+    layout: require(`./${lang}/layout`).default,
+    login: require(`./${lang}/login`).default,
+    register: require(`./${lang}/register`).default,
+    app: require(`./${lang}/app`).default,
+    appOverview: require(`./${lang}/app-overview`).default,
+    appDebug: require(`./${lang}/app-debug`).default,
+    appApi: require(`./${lang}/app-api`).default,
+    appLog: require(`./${lang}/app-log`).default,
+    appAnnotation: require(`./${lang}/app-annotation`).default,
+    share: require(`./${lang}/share-app`).default,
+    dataset: require(`./${lang}/dataset`).default,
+    datasetDocuments: require(`./${lang}/dataset-documents`).default,
+    datasetHitTesting: require(`./${lang}/dataset-hit-testing`).default,
+    datasetSettings: require(`./${lang}/dataset-settings`).default,
+    datasetCreation: require(`./${lang}/dataset-creation`).default,
+    explore: require(`./${lang}/explore`).default,
+    billing: require(`./${lang}/billing`).default,
+    custom: require(`./${lang}/custom`).default,
+    tools: require(`./${lang}/tools`).default,
   },
   },
-}
+})
+
+// Automatically generate the resources object
+const resources = LanguagesSupported.reduce((acc: any, lang: string) => {
+  acc[lang] = loadLangResources(lang)
+  return acc
+}, {})
 
 
 i18n.use(initReactI18next)
 i18n.use(initReactI18next)
-  // init i18next
-  // for all options read: https://www.i18next.com/overview/configuration-options
   .init({
   .init({
     lng: undefined,
     lng: undefined,
     fallbackLng: 'en-US',
     fallbackLng: 'en-US',
-    // debug: true,
     resources,
     resources,
   })
   })
 
 

+ 0 - 26
web/i18n/i18next-serverside-config.ts

@@ -1,26 +0,0 @@
-import { createInstance } from 'i18next'
-import resourcesToBackend from 'i18next-resources-to-backend'
-import { initReactI18next } from 'react-i18next/initReactI18next'
-import type { Locale } from '.'
-
-// https://locize.com/blog/next-13-app-dir-i18n/
-const initI18next = async (lng: Locale, ns: string) => {
-  const i18nInstance = createInstance()
-  await i18nInstance
-    .use(initReactI18next)
-    .use(resourcesToBackend((language: string, namespace: string) => import(`./lang/${namespace}.${language}.ts`)))
-    .init({
-      lng: lng === 'zh-Hans' ? 'zh' : lng,
-      ns,
-      fallbackLng: 'en',
-    })
-  return i18nInstance
-}
-
-export async function useTranslation(lng: Locale, ns = '', options: Record<string, any> = {}) {
-  const i18nextInstance = await initI18next(lng, ns)
-  return {
-    t: i18nextInstance.getFixedT(lng, ns, options.keyPrefix),
-    i18n: i18nextInstance,
-  }
-}

+ 16 - 2
web/i18n/index.ts

@@ -1,8 +1,22 @@
-import { LanguagesSupported } from '@/utils/language'
+import Cookies from 'js-cookie'
+
+import { changeLanguage } from '@/i18n/i18next-config'
+import { LOCALE_COOKIE_NAME } from '@/config'
+import { LanguagesSupported } from '@/i18n/language'
 
 
 export const i18n = {
 export const i18n = {
-  defaultLocale: 'en',
+  defaultLocale: 'en-US',
   locales: LanguagesSupported,
   locales: LanguagesSupported,
 } as const
 } as const
 
 
 export type Locale = typeof i18n['locales'][number]
 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)
+  reloadPage && location.reload()
+}

+ 29 - 30
web/utils/language.ts → web/i18n/language.ts

@@ -4,110 +4,109 @@ export type Item = {
   example: string
   example: string
 }
 }
 
 
-export const LanguagesSupported = ['en-US', 'zh-Hans', 'pt-BR', 'es-ES', 'fr-FR', 'de-DE', 'ja-JP', 'ko-KR', 'ru-RU', 'it-IT', 'th-TH', 'id-ID', 'uk-UA']
-export const LanguagesSupportedUnderscore = ['en_US', 'zh_Hans', 'pt_BR', 'es_ES', 'fr_FR', 'de_DE', 'ja_JP', 'ko_KR', 'ru_RU', 'it_IT', 'th_TH', 'id_ID', 'uk_UA']
+export type I18nText = {
+  'en-US': string
+  'zh-Hans': string
+  'pt-BR': string
+  'es-ES': string
+  'fr-FR': string
+  'de-DE': string
+  'ja-JP': string
+  'ko-KR': string
+  'ru-RU': string
+  'it-IT': string
+  'uk-UA': string
+}
 
 
 export const languages = [
 export const languages = [
   {
   {
     value: 'en-US',
     value: 'en-US',
     name: 'English(United States)',
     name: 'English(United States)',
     example: 'Hello, Dify!',
     example: 'Hello, Dify!',
+    supported: true,
   },
   },
   {
   {
     value: 'zh-Hans',
     value: 'zh-Hans',
     name: '简体中文',
     name: '简体中文',
     example: '你好,Dify!',
     example: '你好,Dify!',
+    supported: true,
   },
   },
   {
   {
     value: 'pt-BR',
     value: 'pt-BR',
     name: 'Português(Brasil)',
     name: 'Português(Brasil)',
     example: 'Olá, Dify!',
     example: 'Olá, Dify!',
+    supported: true,
   },
   },
   {
   {
     value: 'es-ES',
     value: 'es-ES',
     name: 'Español(España)',
     name: 'Español(España)',
     example: 'Saluton, Dify!',
     example: 'Saluton, Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'fr-FR',
     value: 'fr-FR',
     name: 'Français(France)',
     name: 'Français(France)',
     example: 'Bonjour, Dify!',
     example: 'Bonjour, Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'de-DE',
     value: 'de-DE',
     name: 'Deutsch(Deutschland)',
     name: 'Deutsch(Deutschland)',
     example: 'Hallo, Dify!',
     example: 'Hallo, Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'ja-JP',
     value: 'ja-JP',
     name: '日本語(日本)',
     name: '日本語(日本)',
     example: 'こんにちは、Dify!',
     example: 'こんにちは、Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'ko-KR',
     value: 'ko-KR',
     name: '한국어(대한민국)',
     name: '한국어(대한민국)',
     example: '안녕, Dify!',
     example: '안녕, Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'ru-RU',
     value: 'ru-RU',
     name: 'Русский(Россия)',
     name: 'Русский(Россия)',
     example: ' Привет, Dify!',
     example: ' Привет, Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'it-IT',
     value: 'it-IT',
     name: 'Italiano(Italia)',
     name: 'Italiano(Italia)',
     example: 'Ciao, Dify!',
     example: 'Ciao, Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'th-TH',
     value: 'th-TH',
     name: 'ไทย(ประเทศไทย)',
     name: 'ไทย(ประเทศไทย)',
     example: 'สวัสดี Dify!',
     example: 'สวัสดี Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'id-ID',
     value: 'id-ID',
     name: 'Bahasa Indonesia',
     name: 'Bahasa Indonesia',
     example: 'Saluto, Dify!',
     example: 'Saluto, Dify!',
+    supported: false,
   },
   },
   {
   {
     value: 'uk-UA',
     value: 'uk-UA',
     name: 'Українська(Україна)',
     name: 'Українська(Україна)',
     example: 'Привет, Dify!',
     example: 'Привет, Dify!',
+    supported: true,
   },
   },
 ]
 ]
 
 
-export const getModelRuntimeSupported = (locale: string) => {
+export const LanguagesSupported = languages.filter(item => item.supported).map(item => item.value)
+
+export const getLanguage = (locale: string) => {
   if (locale === 'zh-Hans')
   if (locale === 'zh-Hans')
     return locale.replace('-', '_')
     return locale.replace('-', '_')
 
 
   return LanguagesSupported[0].replace('-', '_')
   return LanguagesSupported[0].replace('-', '_')
 }
 }
-export const languageMaps = {
-  'en-US': 'en-US',
-  'zh-Hans': 'zh-Hans',
-  'pt-BR': 'pt-BR',
-  'es-ES': 'es-ES',
-  'fr-FR': 'fr-FR',
-  'de-DE': 'de-DE',
-  'ja-JP': 'ja-JP',
-  'ko-KR': 'ko-KR',
-  'ru-RU': 'ru-RU',
-  'it-IT': 'it-IT',
-  'uk-UA': 'uk-UA',
-}
-
-export type I18nText = {
-  'en-US': string
-  'zh-Hans': string
-  'pt-BR': string
-  'es-ES': string
-  'fr-FR': string
-  'de-DE': string
-  'ja-JP': string
-  'ko-KR': string
-  'ru-RU': string
-  'it-IT': string
-  'uk-UA': string
-}
 
 
 export const NOTICE_I18N = {
 export const NOTICE_I18N = {
   title: {
   title: {

+ 0 - 0
web/i18n/lang/app-annotation.pt.ts → web/i18n/pt-BR/app-annotation.ts


+ 0 - 0
web/i18n/lang/app-api.pt.ts → web/i18n/pt-BR/app-api.ts


+ 0 - 0
web/i18n/lang/app-debug.pt.ts → web/i18n/pt-BR/app-debug.ts


+ 0 - 0
web/i18n/lang/app-log.pt.ts → web/i18n/pt-BR/app-log.ts


+ 0 - 0
web/i18n/lang/app-overview.pt.ts → web/i18n/pt-BR/app-overview.ts


+ 0 - 0
web/i18n/lang/app.pt.ts → web/i18n/pt-BR/app.ts


+ 0 - 0
web/i18n/lang/billing.pt.ts → web/i18n/pt-BR/billing.ts


+ 0 - 0
web/i18n/lang/common.pt.ts → web/i18n/pt-BR/common.ts


+ 0 - 0
web/i18n/lang/custom.pt.ts → web/i18n/pt-BR/custom.ts


+ 0 - 0
web/i18n/lang/dataset-creation.pt.ts → web/i18n/pt-BR/dataset-creation.ts


+ 0 - 0
web/i18n/lang/dataset-documents.pt.ts → web/i18n/pt-BR/dataset-documents.ts


+ 0 - 0
web/i18n/lang/dataset-hit-testing.pt.ts → web/i18n/pt-BR/dataset-hit-testing.ts


+ 0 - 0
web/i18n/lang/dataset-settings.pt.ts → web/i18n/pt-BR/dataset-settings.ts


+ 0 - 0
web/i18n/lang/dataset.pt.ts → web/i18n/pt-BR/dataset.ts


+ 0 - 0
web/i18n/lang/explore.pt.ts → web/i18n/pt-BR/explore.ts


+ 0 - 0
web/i18n/lang/layout.uk.ts → web/i18n/pt-BR/layout.ts


+ 0 - 0
web/i18n/lang/login.pt.ts → web/i18n/pt-BR/login.ts


+ 0 - 0
web/i18n/lang/layout.zh.ts → web/i18n/pt-BR/register.ts


+ 0 - 0
web/i18n/lang/share-app.pt.ts → web/i18n/pt-BR/share-app.ts


+ 0 - 0
web/i18n/lang/tools.pt.ts → web/i18n/pt-BR/tools.ts


+ 27 - 4
web/i18n/server.ts

@@ -1,13 +1,36 @@
-import 'server-only'
-
 import { cookies, headers } from 'next/headers'
 import { cookies, headers } from 'next/headers'
 import Negotiator from 'negotiator'
 import Negotiator from 'negotiator'
 import { match } from '@formatjs/intl-localematcher'
 import { match } from '@formatjs/intl-localematcher'
-import type { Locale } from '.'
+
+import { createInstance } from 'i18next'
+import resourcesToBackend from 'i18next-resources-to-backend'
+import { initReactI18next } from 'react-i18next/initReactI18next'
 import { i18n } from '.'
 import { i18n } from '.'
+import type { Locale } from '.'
+
+// https://locize.com/blog/next-13-app-dir-i18n/
+const initI18next = async (lng: Locale, ns: string) => {
+  const i18nInstance = createInstance()
+  await i18nInstance
+    .use(initReactI18next)
+    .use(resourcesToBackend((language: string, namespace: string) => import(`./${language}/${namespace}.ts`)))
+    .init({
+      lng: lng === 'zh-Hans' ? 'zh-Hans' : lng,
+      ns,
+      fallbackLng: 'en-US',
+    })
+  return i18nInstance
+}
+
+export async function useTranslation(lng: Locale, ns = '', options: Record<string, any> = {}) {
+  const i18nextInstance = await initI18next(lng, ns)
+  return {
+    t: i18nextInstance.getFixedT(lng, ns, options.keyPrefix),
+    i18n: i18nextInstance,
+  }
+}
 
 
 export const getLocaleOnServer = (): Locale => {
 export const getLocaleOnServer = (): Locale => {
-  // @ts-expect-error locales are readonly
   const locales: string[] = i18n.locales
   const locales: string[] = i18n.locales
 
 
   let languages: string[] | undefined
   let languages: string[] | undefined

+ 0 - 0
web/i18n/lang/app-annotation.uk.ts → web/i18n/uk-UA/app-annotation.ts


+ 0 - 0
web/i18n/lang/app-api.uk.ts → web/i18n/uk-UA/app-api.ts


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini