mirror of
https://github.com/YFGaia/dify-plus.git
synced 2026-06-14 20:41:21 +08:00
refactor(i18n): use JSON with flattened key and namespace (#30114)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -45,11 +45,11 @@ const AppCard = ({
|
||||
<div className="truncate" title={appBasicInfo.name}>{appBasicInfo.name}</div>
|
||||
</div>
|
||||
<div className="flex items-center text-[10px] font-medium leading-[18px] text-text-tertiary">
|
||||
{appBasicInfo.mode === AppModeEnum.ADVANCED_CHAT && <div className="truncate">{t('app.types.advanced').toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.CHAT && <div className="truncate">{t('app.types.chatbot').toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.AGENT_CHAT && <div className="truncate">{t('app.types.agent').toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.WORKFLOW && <div className="truncate">{t('app.types.workflow').toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.COMPLETION && <div className="truncate">{t('app.types.completion').toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.ADVANCED_CHAT && <div className="truncate">{t('types.advanced', { ns: 'app' }).toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.CHAT && <div className="truncate">{t('types.chatbot', { ns: 'app' }).toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.AGENT_CHAT && <div className="truncate">{t('types.agent', { ns: 'app' }).toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.WORKFLOW && <div className="truncate">{t('types.workflow', { ns: 'app' }).toUpperCase()}</div>}
|
||||
{appBasicInfo.mode === AppModeEnum.COMPLETION && <div className="truncate">{t('types.completion', { ns: 'app' }).toUpperCase()}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,7 +63,7 @@ const AppCard = ({
|
||||
<div className={cn('flex h-8 w-full items-center space-x-2')}>
|
||||
<Button variant="primary" className="h-7 grow" onClick={() => onCreate()}>
|
||||
<PlusIcon className="mr-1 h-4 w-4" />
|
||||
<span className="text-xs">{t('explore.appCard.addToWorkspace')}</span>
|
||||
<span className="text-xs">{t('appCard.addToWorkspace', { ns: 'explore' })}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@ const Apps = ({
|
||||
}: AppsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { hasEditPermission } = useContext(ExploreContext)
|
||||
const allCategoriesEn = t('explore.apps.allCategories', { lng: 'en' })
|
||||
const allCategoriesEn = t('apps.allCategories', { ns: 'explore', lng: 'en' })
|
||||
|
||||
const [keywords, setKeywords] = useState('')
|
||||
const [searchKeywords, setSearchKeywords] = useState('')
|
||||
@@ -139,8 +139,8 @@ const Apps = ({
|
||||
>
|
||||
|
||||
<div className="shrink-0 px-12 pt-6">
|
||||
<div className={`mb-1 ${s.textGradient} text-xl font-semibold`}>{t('explore.apps.title')}</div>
|
||||
<div className="text-sm text-text-tertiary">{t('explore.apps.description')}</div>
|
||||
<div className={`mb-1 ${s.textGradient} text-xl font-semibold`}>{t('apps.title', { ns: 'explore' })}</div>
|
||||
<div className="text-sm text-text-tertiary">{t('apps.description', { ns: 'explore' })}</div>
|
||||
</div>
|
||||
|
||||
<div className={cn(
|
||||
|
||||
@@ -4,11 +4,9 @@ import type { AppCategory } from '@/models/explore'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ThumbsUp } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import exploreI18n from '@/i18n/en-US/explore'
|
||||
import exploreI18n from '@/i18n/en-US/explore.json'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const categoryI18n = exploreI18n.category
|
||||
|
||||
export type ICategoryProps = {
|
||||
className?: string
|
||||
list: AppCategory[]
|
||||
@@ -42,7 +40,7 @@ const Category: FC<ICategoryProps> = ({
|
||||
onClick={() => onChange(allCategoriesEn)}
|
||||
>
|
||||
<ThumbsUp className="mr-1 h-3.5 w-3.5" />
|
||||
{t('explore.apps.allCategories')}
|
||||
{t('apps.allCategories', { ns: 'explore' })}
|
||||
</div>
|
||||
{list.filter(name => name !== allCategoriesEn).map(name => (
|
||||
<div
|
||||
@@ -50,7 +48,7 @@ const Category: FC<ICategoryProps> = ({
|
||||
className={itemClassName(name === value)}
|
||||
onClick={() => onChange(name)}
|
||||
>
|
||||
{(categoryI18n as any)[name] ? t(`explore.category.${name}` as any) as string : name}
|
||||
{`category.${name}` in exploreI18n ? t(`category.${name}`, { ns: 'explore' }) : name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -17,8 +17,12 @@ vi.mock('react-i18next', () => ({
|
||||
return override
|
||||
if (options?.returnObjects)
|
||||
return [`${key}-feature-1`, `${key}-feature-2`]
|
||||
if (options)
|
||||
return `${key}:${JSON.stringify(options)}`
|
||||
if (options) {
|
||||
const { ns, ...rest } = options
|
||||
const prefix = ns ? `${ns}.` : ''
|
||||
const suffix = Object.keys(rest).length > 0 ? `:${JSON.stringify(rest)}` : ''
|
||||
return `${prefix}${key}${suffix}`
|
||||
}
|
||||
return key
|
||||
},
|
||||
i18n: {
|
||||
@@ -192,8 +196,8 @@ describe('CreateAppModal', () => {
|
||||
|
||||
it('should fall back to empty placeholders when translations return empty string', () => {
|
||||
mockTranslationOverrides = {
|
||||
'app.newApp.appNamePlaceholder': '',
|
||||
'app.newApp.appDescriptionPlaceholder': '',
|
||||
'newApp.appNamePlaceholder': '',
|
||||
'newApp.appDescriptionPlaceholder': '',
|
||||
}
|
||||
|
||||
setup()
|
||||
|
||||
@@ -80,7 +80,7 @@ const CreateAppModal = ({
|
||||
|
||||
const submit = useCallback(() => {
|
||||
if (!name.trim()) {
|
||||
Toast.notify({ type: 'error', message: t('explore.appCustomize.nameRequired') })
|
||||
Toast.notify({ type: 'error', message: t('appCustomize.nameRequired', { ns: 'explore' }) })
|
||||
return
|
||||
}
|
||||
const isValid = maxActiveRequestsInput.trim() !== '' && !isNaN(Number(maxActiveRequestsInput))
|
||||
@@ -122,15 +122,15 @@ const CreateAppModal = ({
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
{isEditModal && (
|
||||
<div className="mb-9 text-xl font-semibold leading-[30px] text-text-primary">{t('app.editAppTitle')}</div>
|
||||
<div className="mb-9 text-xl font-semibold leading-[30px] text-text-primary">{t('editAppTitle', { ns: 'app' })}</div>
|
||||
)}
|
||||
{!isEditModal && (
|
||||
<div className="mb-9 text-xl font-semibold leading-[30px] text-text-primary">{t('explore.appCustomize.title', { name: appName })}</div>
|
||||
<div className="mb-9 text-xl font-semibold leading-[30px] text-text-primary">{t('appCustomize.title', { ns: 'explore', name: appName })}</div>
|
||||
)}
|
||||
<div className="mb-9">
|
||||
{/* icon & name */}
|
||||
<div className="pt-2">
|
||||
<div className="py-2 text-sm font-medium leading-[20px] text-text-primary">{t('app.newApp.captionName')}</div>
|
||||
<div className="py-2 text-sm font-medium leading-[20px] text-text-primary">{t('newApp.captionName', { ns: 'app' })}</div>
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<AppIcon
|
||||
size="large"
|
||||
@@ -144,17 +144,17 @@ const CreateAppModal = ({
|
||||
<Input
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder={t('app.newApp.appNamePlaceholder') || ''}
|
||||
placeholder={t('newApp.appNamePlaceholder', { ns: 'app' }) || ''}
|
||||
className="h-10 grow"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* description */}
|
||||
<div className="pt-2">
|
||||
<div className="py-2 text-sm font-medium leading-[20px] text-text-primary">{t('app.newApp.captionDescription')}</div>
|
||||
<div className="py-2 text-sm font-medium leading-[20px] text-text-primary">{t('newApp.captionDescription', { ns: 'app' })}</div>
|
||||
<Textarea
|
||||
className="resize-none"
|
||||
placeholder={t('app.newApp.appDescriptionPlaceholder') || ''}
|
||||
placeholder={t('newApp.appDescriptionPlaceholder', { ns: 'app' }) || ''}
|
||||
value={description}
|
||||
onChange={e => setDescription(e.target.value)}
|
||||
/>
|
||||
@@ -163,29 +163,29 @@ const CreateAppModal = ({
|
||||
{isEditModal && (appMode === AppModeEnum.CHAT || appMode === AppModeEnum.ADVANCED_CHAT || appMode === AppModeEnum.AGENT_CHAT) && (
|
||||
<div className="pt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="py-2 text-sm font-medium leading-[20px] text-text-primary">{t('app.answerIcon.title')}</div>
|
||||
<div className="py-2 text-sm font-medium leading-[20px] text-text-primary">{t('answerIcon.title', { ns: 'app' })}</div>
|
||||
<Switch
|
||||
defaultValue={useIconAsAnswerIcon}
|
||||
onChange={v => setUseIconAsAnswerIcon(v)}
|
||||
/>
|
||||
</div>
|
||||
<p className="body-xs-regular text-text-tertiary">{t('app.answerIcon.descriptionInExplore')}</p>
|
||||
<p className="body-xs-regular text-text-tertiary">{t('answerIcon.descriptionInExplore', { ns: 'app' })}</p>
|
||||
</div>
|
||||
)}
|
||||
{isEditModal && (
|
||||
<div className="pt-2">
|
||||
<div className="mb-2 mt-2 text-sm font-medium leading-[20px] text-text-primary">{t('app.maxActiveRequests')}</div>
|
||||
<div className="mb-2 mt-2 text-sm font-medium leading-[20px] text-text-primary">{t('maxActiveRequests', { ns: 'app' })}</div>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
placeholder={t('app.maxActiveRequestsPlaceholder')}
|
||||
placeholder={t('maxActiveRequestsPlaceholder', { ns: 'app' })}
|
||||
value={maxActiveRequestsInput}
|
||||
onChange={(e) => {
|
||||
setMaxActiveRequestsInput(e.target.value)
|
||||
}}
|
||||
className="h-10 w-full"
|
||||
/>
|
||||
<p className="body-xs-regular mb-0 mt-2 text-text-tertiary">{t('app.maxActiveRequestsTip')}</p>
|
||||
<p className="body-xs-regular mb-0 mt-2 text-text-tertiary">{t('maxActiveRequestsTip', { ns: 'app' })}</p>
|
||||
</div>
|
||||
)}
|
||||
{!isEditModal && isAppsFull && <AppsFull className="mt-4" loc="app-explore-create" />}
|
||||
@@ -197,13 +197,13 @@ const CreateAppModal = ({
|
||||
variant="primary"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<span>{!isEditModal ? t('common.operation.create') : t('common.operation.save')}</span>
|
||||
<span>{!isEditModal ? t('operation.create', { ns: 'common' }) : t('operation.save', { ns: 'common' })}</span>
|
||||
<div className="flex gap-0.5">
|
||||
<RiCommandLine size={14} className="system-kbd rounded-sm bg-components-kbd-bg-white p-0.5" />
|
||||
<RiCornerDownLeftLine size={14} className="system-kbd rounded-sm bg-components-kbd-bg-white p-0.5" />
|
||||
</div>
|
||||
</Button>
|
||||
<Button className="w-24" onClick={onHide}>{t('common.operation.cancel')}</Button>
|
||||
<Button className="w-24" onClick={onHide}>{t('operation.cancel', { ns: 'common' })}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
{showAppIconPicker && (
|
||||
|
||||
@@ -27,7 +27,7 @@ const Explore: FC<IExploreProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const { data: membersData } = useMembers()
|
||||
|
||||
useDocumentTitle(t('common.menus.explore'))
|
||||
useDocumentTitle(t('menus.explore', { ns: 'common' }))
|
||||
|
||||
useEffect(() => {
|
||||
if (!membersData?.accounts)
|
||||
|
||||
@@ -73,18 +73,18 @@ const ItemOperation: FC<IItemOperationProps> = ({
|
||||
>
|
||||
<div className={cn(s.actionItem, 'group hover:bg-state-base-hover')} onClick={togglePin}>
|
||||
<Pin02 className="h-4 w-4 shrink-0 text-text-secondary" />
|
||||
<span className={s.actionName}>{isPinned ? t('explore.sidebar.action.unpin') : t('explore.sidebar.action.pin')}</span>
|
||||
<span className={s.actionName}>{isPinned ? t('sidebar.action.unpin', { ns: 'explore' }) : t('sidebar.action.pin', { ns: 'explore' })}</span>
|
||||
</div>
|
||||
{isShowRenameConversation && (
|
||||
<div className={cn(s.actionItem, 'group hover:bg-state-base-hover')} onClick={onRenameConversation}>
|
||||
<RiEditLine className="h-4 w-4 shrink-0 text-text-secondary" />
|
||||
<span className={s.actionName}>{t('explore.sidebar.action.rename')}</span>
|
||||
<span className={s.actionName}>{t('sidebar.action.rename', { ns: 'explore' })}</span>
|
||||
</div>
|
||||
)}
|
||||
{isShowDelete && (
|
||||
<div className={cn(s.actionItem, s.deleteActionItem, 'group hover:bg-state-base-hover')} onClick={onDelete}>
|
||||
<RiDeleteBinLine className={cn(s.deleteActionItemChild, 'h-4 w-4 shrink-0 stroke-current stroke-2 text-text-secondary')} />
|
||||
<span className={cn(s.actionName, s.deleteActionItemChild)}>{t('explore.sidebar.action.delete')}</span>
|
||||
<span className={cn(s.actionName, s.deleteActionItemChild)}>{t('sidebar.action.delete', { ns: 'explore' })}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ const SideBar: FC<IExploreSideBarProps> = ({
|
||||
setShowConfirm(false)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.remove'),
|
||||
message: t('api.remove', { ns: 'common' }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ const SideBar: FC<IExploreSideBarProps> = ({
|
||||
await updatePinStatus({ appId: id, isPinned })
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.success'),
|
||||
message: t('api.success', { ns: 'common' }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,12 +92,12 @@ const SideBar: FC<IExploreSideBarProps> = ({
|
||||
style={isDiscoverySelected ? { boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)' } : {}}
|
||||
>
|
||||
{isDiscoverySelected ? <SelectedDiscoveryIcon /> : <DiscoveryIcon />}
|
||||
{!isMobile && <div className="text-sm">{t('explore.sidebar.discovery')}</div>}
|
||||
{!isMobile && <div className="text-sm">{t('sidebar.discovery', { ns: 'explore' })}</div>}
|
||||
</Link>
|
||||
</div>
|
||||
{installedApps.length > 0 && (
|
||||
<div className="mt-10">
|
||||
<p className="break-all pl-2 text-xs font-medium uppercase text-text-tertiary mobile:px-0">{t('explore.sidebar.workspace')}</p>
|
||||
<p className="break-all pl-2 text-xs font-medium uppercase text-text-tertiary mobile:px-0">{t('sidebar.workspace', { ns: 'explore' })}</p>
|
||||
<div
|
||||
className="mt-3 space-y-1 overflow-y-auto overflow-x-hidden"
|
||||
style={{
|
||||
@@ -131,8 +131,8 @@ const SideBar: FC<IExploreSideBarProps> = ({
|
||||
)}
|
||||
{showConfirm && (
|
||||
<Confirm
|
||||
title={t('explore.sidebar.delete.title')}
|
||||
content={t('explore.sidebar.delete.content')}
|
||||
title={t('sidebar.delete.title', { ns: 'explore' })}
|
||||
content={t('sidebar.delete.content', { ns: 'explore' })}
|
||||
isShow={showConfirm}
|
||||
onConfirm={handleDelete}
|
||||
onCancel={() => setShowConfirm(false)}
|
||||
|
||||
Reference in New Issue
Block a user