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:
Stephen Zhou
2025-12-29 14:52:32 +08:00
committed by GitHub
parent 09be869f58
commit 6d0e36479b
2552 changed files with 111159 additions and 142972 deletions
@@ -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(
+3 -5
View File
@@ -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 && (
+1 -1
View File
@@ -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>
+6 -6
View File
@@ -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)}