mirror of
https://github.com/langgenius/dify.git
synced 2026-10-04 13:47:55 +08:00
perf(web): split shared translation namespaces by feature (#42874)
This commit is contained in:
1 parent
b9f1ddaff4
commit
ddb9f1ba8c
446 files changed
+12670
-11952
No files matched your search
@@ -95,7 +95,7 @@ describe('AppDetailLayout', () => {
|
||||
['/app/app-1/logs', 'common.appMenus.logs', AppModeEnum.WORKFLOW],
|
||||
['/app/app-1/annotations', 'common.appMenus.annotations', AppModeEnum.CHAT],
|
||||
['/app/app-1/overview', 'common.appMenus.overview', AppModeEnum.WORKFLOW],
|
||||
['/app/app-1/access-config', 'common.settings.resourceAccess', AppModeEnum.WORKFLOW],
|
||||
['/app/app-1/access-config', 'navigation.settings.resourceAccess', AppModeEnum.WORKFLOW],
|
||||
])('identifies the current detail page for %s', async (pathname, pageTitle, mode) => {
|
||||
mockPathname = pathname
|
||||
mockFetchAppDetailDirect.mockResolvedValue(
|
||||
|
||||
@@ -43,9 +43,9 @@ const appDetailPageTitle = (pathname: string, t: ReturnType<typeof useTranslatio
|
||||
return t(($) => $['appMenus.annotations'], { ns: 'common' })
|
||||
if (pathname.endsWith('/overview')) return t(($) => $['appMenus.overview'], { ns: 'common' })
|
||||
if (pathname.endsWith('/access-config'))
|
||||
return t(($) => $['settings.resourceAccess'], { ns: 'common' })
|
||||
return t(($) => $['settings.resourceAccess'], { ns: 'navigation' })
|
||||
|
||||
return t(($) => $['menus.appDetail'], { ns: 'common' })
|
||||
return t(($) => $['menus.appDetail'], { ns: 'navigation' })
|
||||
}
|
||||
|
||||
const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
@@ -53,7 +53,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
children,
|
||||
appId, // get appId in path
|
||||
} = props
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'navigation'])
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
@@ -95,7 +95,9 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
: false
|
||||
const shouldBlockAccessPointAccess = pathname.endsWith('/access-point') && !canViewAccessPoint
|
||||
|
||||
useDocumentTitle(`${pageTitle} · ${appName || t(($) => $['menus.appDetail'], { ns: 'common' })}`)
|
||||
useDocumentTitle(
|
||||
`${pageTitle} · ${appName || t(($) => $['menus.appDetail'], { ns: 'navigation' })}`,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let ignore = false
|
||||
|
||||
+3
-3
@@ -145,7 +145,7 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
onSaved,
|
||||
onChosen,
|
||||
}) => {
|
||||
const { t } = useTranslation(['app', 'common'])
|
||||
const { t } = useTranslation(['app', 'common', 'modelProvider'])
|
||||
const isEdit = !!payload
|
||||
const isAdd = !isEdit
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
@@ -825,7 +825,7 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
aria-hidden
|
||||
className="mr-1 i-custom-vender-solid-security-lock-01 size-3 text-text-tertiary"
|
||||
/>
|
||||
{t(($) => $['modelProvider.encrypted.front'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.encrypted.front'], { ns: 'modelProvider' })}
|
||||
<a
|
||||
className="mx-1 text-primary-600"
|
||||
target="_blank"
|
||||
@@ -834,7 +834,7 @@ const ProviderConfigModal: FC<Props> = ({
|
||||
>
|
||||
PKCS1_OAEP
|
||||
</a>
|
||||
{t(($) => $['modelProvider.encrypted.back'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.encrypted.back'], { ns: 'modelProvider' })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Apps } from '@/app/components/apps'
|
||||
import { getRouteMetadata } from '@/app/route-metadata'
|
||||
|
||||
export function generateMetadata() {
|
||||
return getRouteMetadata('common', ($) => $['menus.apps'])
|
||||
return getRouteMetadata('navigation', ($) => $['menus.apps'])
|
||||
}
|
||||
|
||||
export default function AppsPage() {
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ describe('DatasetDetailLayout', () => {
|
||||
['/datasets/dataset-1/pipeline', 'common.datasetMenus.pipeline'],
|
||||
['/datasets/dataset-1/hitTesting', 'common.datasetMenus.hitTesting'],
|
||||
['/datasets/dataset-1/settings', 'common.datasetMenus.settings'],
|
||||
['/datasets/dataset-1/access-config', 'common.settings.resourceAccess'],
|
||||
['/datasets/dataset-1/access-config', 'navigation.settings.resourceAccess'],
|
||||
['/datasets/dataset-1/api', 'common.appMenus.apiAccess'],
|
||||
])('identifies the current detail page for %s', async (pathname, pageTitle) => {
|
||||
mockUsePathname.mockReturnValue(pathname)
|
||||
|
||||
@@ -51,10 +51,10 @@ const datasetDetailPageTitle = (pathname: string, t: ReturnType<typeof useTransl
|
||||
return t(($) => $['datasetMenus.hitTesting'], { ns: 'common' })
|
||||
if (pathname.endsWith('/settings')) return t(($) => $['datasetMenus.settings'], { ns: 'common' })
|
||||
if (pathname.endsWith('/access-config'))
|
||||
return t(($) => $['settings.resourceAccess'], { ns: 'common' })
|
||||
return t(($) => $['settings.resourceAccess'], { ns: 'navigation' })
|
||||
if (pathname.endsWith('/api')) return t(($) => $['appMenus.apiAccess'], { ns: 'common' })
|
||||
|
||||
return t(($) => $['menus.datasets'], { ns: 'common' })
|
||||
return t(($) => $['menus.datasets'], { ns: 'navigation' })
|
||||
}
|
||||
|
||||
const isDocumentDetailPath = (pathname: string) =>
|
||||
@@ -86,7 +86,7 @@ const getDatasetRedirectionPath = (
|
||||
|
||||
const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const { children, datasetId } = props
|
||||
const { t } = useTranslation(['common', 'datasetPipeline'])
|
||||
const { t } = useTranslation(['common', 'datasetPipeline', 'navigation'])
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
|
||||
@@ -132,7 +132,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
((isAccessConfigPath && !datasetACLCapabilities.canAccessConfig) ||
|
||||
(isHitTestingPath && !datasetACLCapabilities.canRetrievalRecall))
|
||||
const pageTitle = datasetDetailPageTitle(pathname, t)
|
||||
const documentTitle = `${pageTitle} · ${datasetRes?.name || t(($) => $['menus.datasets'], { ns: 'common' })}`
|
||||
const documentTitle = `${pageTitle} · ${datasetRes?.name || t(($) => $['menus.datasets'], { ns: 'navigation' })}`
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldRedirect) router.replace('/datasets')
|
||||
|
||||
@@ -2,7 +2,10 @@ import ExternalKnowledgeBaseConnector from '@/app/components/datasets/external-k
|
||||
import { getRouteMetadata } from '@/app/route-metadata'
|
||||
|
||||
export function generateMetadata() {
|
||||
return getRouteMetadata('common', ($) => $['stepByStepTour.guides.knowledge.empty.connect.title'])
|
||||
return getRouteMetadata(
|
||||
'onboarding',
|
||||
($) => $['stepByStepTour.guides.knowledge.empty.connect.title'],
|
||||
)
|
||||
}
|
||||
|
||||
const ExternalKnowledgeBaseCreation = () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getRouteMetadata } from '@/app/route-metadata'
|
||||
|
||||
export function generateMetadata() {
|
||||
return getRouteMetadata(
|
||||
'common',
|
||||
'onboarding',
|
||||
($) => $['stepByStepTour.guides.knowledge.empty.pipeline.title'],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import DatasetUpdateForm from '@/app/components/datasets/create'
|
||||
import { getRouteMetadata } from '@/app/route-metadata'
|
||||
|
||||
export function generateMetadata() {
|
||||
return getRouteMetadata('common', ($) => $['stepByStepTour.guides.knowledge.empty.create.title'])
|
||||
return getRouteMetadata(
|
||||
'onboarding',
|
||||
($) => $['stepByStepTour.guides.knowledge.empty.create.title'],
|
||||
)
|
||||
}
|
||||
|
||||
const DatasetCreation = () => {
|
||||
|
||||
@@ -15,8 +15,8 @@ const getIntegrationSectionTitle = async (section: IntegrationSection) => {
|
||||
|
||||
if (section === 'mcp') return 'MCP'
|
||||
if (section === 'workflow-tool') {
|
||||
const { t } = await getTranslation(locale, 'workflow')
|
||||
return t(($) => $['common.workflowAsTool'], { ns: 'workflow' })
|
||||
const { t } = await getTranslation(locale, 'navigation')
|
||||
return t(($) => $['common.workflowAsTool'], { ns: 'navigation' })
|
||||
}
|
||||
if (section === 'trigger' || section === 'agent-strategy' || section === 'extension') {
|
||||
const { t } = await getTranslation(locale, 'plugin')
|
||||
@@ -25,12 +25,13 @@ const getIntegrationSectionTitle = async (section: IntegrationSection) => {
|
||||
return t(($) => $['categorySingle.extension'], { ns: 'plugin' })
|
||||
}
|
||||
|
||||
const { t } = await getTranslation(locale, 'common')
|
||||
if (section === 'provider') return t(($) => $['settings.provider'], { ns: 'common' })
|
||||
if (section === 'builtin') return t(($) => $['toolsPage.toolPlugin'], { ns: 'common' })
|
||||
if (section === 'custom-tool') return t(($) => $['settings.swaggerAPIAsTool'], { ns: 'common' })
|
||||
if (section === 'data-source') return t(($) => $['settings.dataSource'], { ns: 'common' })
|
||||
return t(($) => $['settings.customEndpoint'], { ns: 'common' })
|
||||
const { t } = await getTranslation(locale, 'navigation')
|
||||
if (section === 'provider') return t(($) => $['settings.provider'], { ns: 'navigation' })
|
||||
if (section === 'builtin') return t(($) => $['toolsPage.toolPlugin'], { ns: 'navigation' })
|
||||
if (section === 'custom-tool')
|
||||
return t(($) => $['settings.swaggerAPIAsTool'], { ns: 'navigation' })
|
||||
if (section === 'data-source') return t(($) => $['settings.dataSource'], { ns: 'navigation' })
|
||||
return t(($) => $['settings.customEndpoint'], { ns: 'navigation' })
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
@@ -39,8 +40,8 @@ export async function generateMetadata({
|
||||
const { slug } = await params
|
||||
const target = getIntegrationRouteTargetBySlug(slug)
|
||||
const locale = await getLocaleOnServer()
|
||||
const { t } = await getTranslation(locale, 'common')
|
||||
const integrationsTitle = t(($) => $['mainNav.integrations'], { ns: 'common' })
|
||||
const { t } = await getTranslation(locale, 'navigation')
|
||||
const integrationsTitle = t(($) => $['mainNav.integrations'], { ns: 'navigation' })
|
||||
|
||||
if (target.type !== 'section') return { title: integrationsTitle }
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
|
||||
const MarketplaceDocumentTitle = () => {
|
||||
const { t } = useTranslation(['common'])
|
||||
useDocumentTitle(t(($) => $['mainNav.marketplace'], { ns: 'common' }))
|
||||
const { t } = useTranslation(['navigation'])
|
||||
useDocumentTitle(t(($) => $['mainNav.marketplace'], { ns: 'navigation' }))
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getRouteMetadata } from '@/app/route-metadata'
|
||||
import { HomePage } from '@/features/home/page'
|
||||
|
||||
export function generateMetadata() {
|
||||
return getRouteMetadata('common', ($) => $['mainNav.home'])
|
||||
return getRouteMetadata('navigation', ($) => $['mainNav.home'])
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
|
||||
@@ -42,10 +42,10 @@ describe('Webapp Reset Password Set Password Page', () => {
|
||||
|
||||
expect(mockUseDocumentTitle).toHaveBeenCalledWith('login.changePassword')
|
||||
|
||||
fireEvent.change(screen.getByLabelText('common.account.newPassword'), {
|
||||
fireEvent.change(screen.getByLabelText('accountSettings.account.newPassword'), {
|
||||
target: { value: 'ValidPass123!' },
|
||||
})
|
||||
fireEvent.change(screen.getByLabelText('common.account.confirmPassword'), {
|
||||
fireEvent.change(screen.getByLabelText('accountSettings.account.confirmPassword'), {
|
||||
target: { value: 'ValidPass123!' },
|
||||
})
|
||||
fireEvent.click(screen.getByRole('button', { name: 'login.changePasswordBtn' }))
|
||||
|
||||
@@ -17,7 +17,7 @@ import { useRouter, useSearchParams } from '@/next/navigation'
|
||||
import { changeWebAppPasswordWithToken } from '@/service/common'
|
||||
|
||||
const ChangePasswordForm = () => {
|
||||
const { t } = useTranslation(['common', 'login'])
|
||||
const { t } = useTranslation(['common', 'login', 'accountSettings'])
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const token = decodeURIComponent(searchParams.get('token') || '')
|
||||
@@ -60,7 +60,7 @@ const ChangePasswordForm = () => {
|
||||
return false
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
showErrorMessage(t(($) => $['account.notEqual'], { ns: 'common' }))
|
||||
showErrorMessage(t(($) => $['account.notEqual'], { ns: 'accountSettings' }))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -104,7 +104,7 @@ const ChangePasswordForm = () => {
|
||||
<Field name="password" className="mb-5">
|
||||
<div className="grid gap-0">
|
||||
<FieldLabel className="system-md-semibold">
|
||||
{t(($) => $['account.newPassword'], { ns: 'common' })}
|
||||
{t(($) => $['account.newPassword'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
@@ -138,7 +138,7 @@ const ChangePasswordForm = () => {
|
||||
</Field>
|
||||
<Field name="confirmPassword" className="mb-5 gap-0">
|
||||
<FieldLabel className="system-md-semibold">
|
||||
{t(($) => $['account.confirmPassword'], { ns: 'common' })}
|
||||
{t(($) => $['account.confirmPassword'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
|
||||
@@ -34,10 +34,10 @@ describe('EmailChangeModal', () => {
|
||||
render(<EmailChangeModal email="old@example.com" onClose={vi.fn()} />)
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'common.account.changeEmail.sendVerifyCode' }),
|
||||
screen.getByRole('button', { name: 'accountSettings.account.changeEmail.sendVerifyCode' }),
|
||||
)
|
||||
const originCode = await screen.findByRole('textbox', {
|
||||
name: 'common.account.changeEmail.codeLabel',
|
||||
name: 'accountSettings.account.changeEmail.codeLabel',
|
||||
})
|
||||
await user.type(originCode, '123456{Enter}')
|
||||
expect(verifyEmail).toHaveBeenCalledWith({
|
||||
@@ -47,12 +47,12 @@ describe('EmailChangeModal', () => {
|
||||
})
|
||||
|
||||
const email = await screen.findByRole('textbox', {
|
||||
name: 'common.account.changeEmail.emailLabel',
|
||||
name: 'accountSettings.account.changeEmail.emailLabel',
|
||||
})
|
||||
await user.type(email, 'new@example.com')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.account.changeEmail.sendVerifyCode' }),
|
||||
screen.getByRole('button', { name: 'accountSettings.account.changeEmail.sendVerifyCode' }),
|
||||
).toBeEnabled(),
|
||||
)
|
||||
await user.type(email, '{Enter}')
|
||||
@@ -63,7 +63,7 @@ describe('EmailChangeModal', () => {
|
||||
})
|
||||
|
||||
const newCode = await screen.findByRole('textbox', {
|
||||
name: 'common.account.changeEmail.codeLabel',
|
||||
name: 'accountSettings.account.changeEmail.codeLabel',
|
||||
})
|
||||
expect(newCode).toHaveValue('')
|
||||
await user.type(newCode, '654321{Enter}')
|
||||
@@ -84,22 +84,24 @@ describe('EmailChangeModal', () => {
|
||||
render(<EmailChangeModal email="old@example.com" onClose={vi.fn()} />)
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: 'common.account.changeEmail.sendVerifyCode' }),
|
||||
screen.getByRole('button', { name: 'accountSettings.account.changeEmail.sendVerifyCode' }),
|
||||
)
|
||||
await user.type(
|
||||
await screen.findByRole('textbox', { name: 'common.account.changeEmail.codeLabel' }),
|
||||
await screen.findByRole('textbox', { name: 'accountSettings.account.changeEmail.codeLabel' }),
|
||||
'123456{Enter}',
|
||||
)
|
||||
const email = await screen.findByRole('textbox', {
|
||||
name: 'common.account.changeEmail.emailLabel',
|
||||
name: 'accountSettings.account.changeEmail.emailLabel',
|
||||
})
|
||||
await user.type(email, 'used@example.com')
|
||||
|
||||
await screen.findByText('common.account.changeEmail.existingEmail')
|
||||
await screen.findByText('accountSettings.account.changeEmail.existingEmail')
|
||||
expect(email).toBeInvalid()
|
||||
expect(email).toHaveAccessibleDescription('common.account.changeEmail.existingEmail')
|
||||
expect(email).toHaveAccessibleDescription('accountSettings.account.changeEmail.existingEmail')
|
||||
await user.clear(email)
|
||||
expect(email).not.toBeInvalid()
|
||||
expect(screen.queryByText('common.account.changeEmail.existingEmail')).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('accountSettings.account.changeEmail.existingEmail'),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -68,7 +68,7 @@ describe('AccountPage', () => {
|
||||
renderPage()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.edit' }))
|
||||
const nameInput = await screen.findByRole('textbox', { name: 'common.account.name' })
|
||||
const nameInput = await screen.findByRole('textbox', { name: 'accountSettings.account.name' })
|
||||
await user.clear(nameInput)
|
||||
await user.type(nameInput, 'Alice Cooper')
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
||||
|
||||
@@ -52,7 +52,7 @@ function isFetchResponseError(error: unknown): error is FetchResponseError {
|
||||
}
|
||||
|
||||
const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'accountSettings'])
|
||||
const router = useRouter()
|
||||
const [step, setStep] = useState<Step>(STEP.start)
|
||||
const [code, setCode] = useState<string>('')
|
||||
@@ -245,16 +245,16 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
{step === STEP.start && (
|
||||
<>
|
||||
<DialogTitle className="pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.changeEmail.title'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.title'], { ns: 'accountSettings' })}
|
||||
</DialogTitle>
|
||||
<div className="space-y-0.5 pt-1 pb-2">
|
||||
<div className="body-md-medium text-text-warning">
|
||||
{t(($) => $['account.changeEmail.authTip'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.authTip'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<div className="body-md-regular text-text-secondary">
|
||||
<Trans
|
||||
i18nKey={($) => $['account.changeEmail.content1']}
|
||||
ns="common"
|
||||
ns="accountSettings"
|
||||
components={{
|
||||
email: <span className="body-md-medium text-text-primary"></span>,
|
||||
}}
|
||||
@@ -265,7 +265,7 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
<div className="pt-3"></div>
|
||||
<div className="space-y-2">
|
||||
<Button className="w-full!" variant="primary" type="submit">
|
||||
{t(($) => $['account.changeEmail.sendVerifyCode'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.sendVerifyCode'], { ns: 'accountSettings' })}
|
||||
</Button>
|
||||
<Button className="w-full!" onClick={onClose}>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
@@ -276,13 +276,13 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
{step === STEP.verifyOrigin && (
|
||||
<>
|
||||
<DialogTitle className="pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.changeEmail.verifyEmail'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.verifyEmail'], { ns: 'accountSettings' })}
|
||||
</DialogTitle>
|
||||
<div className="space-y-0.5 pt-1 pb-2">
|
||||
<div className="body-md-regular text-text-secondary">
|
||||
<Trans
|
||||
i18nKey={($) => $['account.changeEmail.content2']}
|
||||
ns="common"
|
||||
ns="accountSettings"
|
||||
components={{
|
||||
email: <span className="body-md-medium text-text-primary"></span>,
|
||||
}}
|
||||
@@ -292,11 +292,13 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
</div>
|
||||
<Field name="code" className="pt-3">
|
||||
<FieldLabel>
|
||||
{t(($) => $['account.changeEmail.codeLabel'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.codeLabel'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
className="w-full!"
|
||||
placeholder={t(($) => $['account.changeEmail.codePlaceholder'], { ns: 'common' })}
|
||||
placeholder={t(($) => $['account.changeEmail.codePlaceholder'], {
|
||||
ns: 'accountSettings',
|
||||
})}
|
||||
value={code}
|
||||
onValueChange={(value) => setCode(value)}
|
||||
autoComplete="one-time-code"
|
||||
@@ -310,17 +312,22 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
{t(($) => $['account.changeEmail.continue'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.continue'], { ns: 'accountSettings' })}
|
||||
</Button>
|
||||
<Button className="w-full!" onClick={onClose}>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center gap-1 system-xs-regular text-text-tertiary">
|
||||
<span>{t(($) => $['account.changeEmail.resendTip'], { ns: 'common' })}</span>
|
||||
<span>
|
||||
{t(($) => $['account.changeEmail.resendTip'], { ns: 'accountSettings' })}
|
||||
</span>
|
||||
{time > 0 && (
|
||||
<span>
|
||||
{t(($) => $['account.changeEmail.resendCount'], { ns: 'common', count: time })}
|
||||
{t(($) => $['account.changeEmail.resendCount'], {
|
||||
ns: 'accountSettings',
|
||||
count: time,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
{!time && (
|
||||
@@ -329,7 +336,7 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
onClick={sendCodeToOriginEmail}
|
||||
className="cursor-pointer rounded-sm system-xs-medium text-text-accent-secondary outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
{t(($) => $['account.changeEmail.resend'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.resend'], { ns: 'accountSettings' })}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -338,21 +345,21 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
{step === STEP.newEmail && (
|
||||
<>
|
||||
<DialogTitle className="pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.changeEmail.newEmail'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.newEmail'], { ns: 'accountSettings' })}
|
||||
</DialogTitle>
|
||||
<div className="space-y-0.5 pt-1 pb-2">
|
||||
<div className="body-md-regular text-text-secondary">
|
||||
{t(($) => $['account.changeEmail.content3'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.content3'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
</div>
|
||||
<Field name="email" invalid={newEmailExited || unAvailableEmail} className="pt-3">
|
||||
<FieldLabel>
|
||||
{t(($) => $['account.changeEmail.emailLabel'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.emailLabel'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
className="w-full!"
|
||||
placeholder={t(($) => $['account.changeEmail.emailPlaceholder'], {
|
||||
ns: 'common',
|
||||
ns: 'accountSettings',
|
||||
})}
|
||||
value={mail}
|
||||
onValueChange={(value) => handleNewEmailValueChange(value)}
|
||||
@@ -361,12 +368,12 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
/>
|
||||
{newEmailExited && (
|
||||
<FieldError match>
|
||||
{t(($) => $['account.changeEmail.existingEmail'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.existingEmail'], { ns: 'accountSettings' })}
|
||||
</FieldError>
|
||||
)}
|
||||
{unAvailableEmail && (
|
||||
<FieldError match>
|
||||
{t(($) => $['account.changeEmail.unAvailableEmail'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.unAvailableEmail'], { ns: 'accountSettings' })}
|
||||
</FieldError>
|
||||
)}
|
||||
</Field>
|
||||
@@ -377,7 +384,7 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
{t(($) => $['account.changeEmail.sendVerifyCode'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.sendVerifyCode'], { ns: 'accountSettings' })}
|
||||
</Button>
|
||||
<Button className="w-full!" onClick={onClose}>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
@@ -388,13 +395,13 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
{step === STEP.verifyNew && (
|
||||
<>
|
||||
<DialogTitle className="pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.changeEmail.verifyNew'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.verifyNew'], { ns: 'accountSettings' })}
|
||||
</DialogTitle>
|
||||
<div className="space-y-0.5 pt-1 pb-2">
|
||||
<div className="body-md-regular text-text-secondary">
|
||||
<Trans
|
||||
i18nKey={($) => $['account.changeEmail.content4']}
|
||||
ns="common"
|
||||
ns="accountSettings"
|
||||
components={{
|
||||
email: <span className="body-md-medium text-text-primary"></span>,
|
||||
}}
|
||||
@@ -404,11 +411,13 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
</div>
|
||||
<Field name="code" className="pt-3">
|
||||
<FieldLabel>
|
||||
{t(($) => $['account.changeEmail.codeLabel'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.codeLabel'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
className="w-full!"
|
||||
placeholder={t(($) => $['account.changeEmail.codePlaceholder'], { ns: 'common' })}
|
||||
placeholder={t(($) => $['account.changeEmail.codePlaceholder'], {
|
||||
ns: 'accountSettings',
|
||||
})}
|
||||
value={code}
|
||||
onValueChange={(value) => setCode(value)}
|
||||
autoComplete="one-time-code"
|
||||
@@ -422,17 +431,25 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
{t(($) => $['account.changeEmail.changeTo'], { ns: 'common', email: mail })}
|
||||
{t(($) => $['account.changeEmail.changeTo'], {
|
||||
ns: 'accountSettings',
|
||||
email: mail,
|
||||
})}
|
||||
</Button>
|
||||
<Button className="w-full!" onClick={onClose}>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-3 flex items-center gap-1 system-xs-regular text-text-tertiary">
|
||||
<span>{t(($) => $['account.changeEmail.resendTip'], { ns: 'common' })}</span>
|
||||
<span>
|
||||
{t(($) => $['account.changeEmail.resendTip'], { ns: 'accountSettings' })}
|
||||
</span>
|
||||
{time > 0 && (
|
||||
<span>
|
||||
{t(($) => $['account.changeEmail.resendCount'], { ns: 'common', count: time })}
|
||||
{t(($) => $['account.changeEmail.resendCount'], {
|
||||
ns: 'accountSettings',
|
||||
count: time,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
{!time && (
|
||||
@@ -441,7 +458,7 @@ const EmailChangeModal = ({ onClose, email }: Props) => {
|
||||
onClick={sendCodeToNewEmail}
|
||||
className="cursor-pointer rounded-sm system-xs-medium text-text-accent-secondary outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
{t(($) => $['account.changeEmail.resend'], { ns: 'common' })}
|
||||
{t(($) => $['account.changeEmail.resend'], { ns: 'accountSettings' })}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@ const descriptionClassName = `
|
||||
type AccountAppItem = AppPartial & IItem
|
||||
|
||||
export default function AccountPage() {
|
||||
const { t } = useTranslation(['common', 'login'])
|
||||
const { t } = useTranslation(['common', 'login', 'accountSettings'])
|
||||
const editNameInputId = useId()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { data: appList } = useQuery(
|
||||
@@ -111,7 +111,7 @@ export default function AccountPage() {
|
||||
return false
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
showErrorMessage(t(($) => $['account.notEqual'], { ns: 'common' }))
|
||||
showErrorMessage(t(($) => $['account.notEqual'], { ns: 'accountSettings' }))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ export default function AccountPage() {
|
||||
<>
|
||||
<div className="pt-2 pb-3">
|
||||
<h4 className="title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.myAccount'], { ns: 'common' })}
|
||||
{t(($) => $['account.myAccount'], { ns: 'accountSettings' })}
|
||||
</h4>
|
||||
</div>
|
||||
<div className="mb-8 flex items-center rounded-xl bg-linear-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1 p-6">
|
||||
@@ -187,7 +187,9 @@ export default function AccountPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<div className={titleClassName}>{t(($) => $['account.name'], { ns: 'common' })}</div>
|
||||
<div className={titleClassName}>
|
||||
{t(($) => $['account.name'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<div className="mt-2 flex w-full items-center justify-between gap-2">
|
||||
<div className="flex-1 rounded-lg bg-components-input-bg-normal p-2 system-sm-regular text-components-input-text-filled">
|
||||
<span className="pl-1">{userProfile.name}</span>
|
||||
@@ -202,7 +204,9 @@ export default function AccountPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<div className={titleClassName}>{t(($) => $['account.email'], { ns: 'common' })}</div>
|
||||
<div className={titleClassName}>
|
||||
{t(($) => $['account.email'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<div className="mt-2 flex w-full items-center justify-between gap-2">
|
||||
<div className="flex-1 rounded-lg bg-components-input-bg-normal p-2 system-sm-regular text-components-input-text-filled">
|
||||
<span className="pl-1">{userProfile.email}</span>
|
||||
@@ -222,30 +226,30 @@ export default function AccountPage() {
|
||||
<div className="mb-8 flex justify-between gap-2">
|
||||
<div>
|
||||
<div className="mb-1 system-sm-semibold text-text-secondary">
|
||||
{t(($) => $['account.password'], { ns: 'common' })}
|
||||
{t(($) => $['account.password'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<div className="mb-2 body-xs-regular text-text-tertiary">
|
||||
{t(($) => $['account.passwordTip'], { ns: 'common' })}
|
||||
{t(($) => $['account.passwordTip'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={() => setEditPasswordModalVisible(true)}>
|
||||
{userProfile.is_password_set
|
||||
? t(($) => $['account.resetPassword'], { ns: 'common' })
|
||||
: t(($) => $['account.setPassword'], { ns: 'common' })}
|
||||
? t(($) => $['account.resetPassword'], { ns: 'accountSettings' })
|
||||
: t(($) => $['account.setPassword'], { ns: 'accountSettings' })}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-6 border border-divider-subtle" />
|
||||
<div className="mb-8">
|
||||
<div className={titleClassName}>
|
||||
{t(($) => $['account.langGeniusAccount'], { ns: 'common' })}
|
||||
{t(($) => $['account.langGeniusAccount'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<div className={descriptionClassName}>
|
||||
{t(($) => $['account.langGeniusAccountTip'], { ns: 'common' })}
|
||||
{t(($) => $['account.langGeniusAccountTip'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
{!!apps.length && (
|
||||
<Collapse
|
||||
title={`${t(($) => $['account.showAppLength'], { ns: 'common', length: apps.length })}`}
|
||||
title={`${t(($) => $['account.showAppLength'], { ns: 'accountSettings', length: apps.length })}`}
|
||||
items={apps.map((app) => ({ ...app, key: app.id, name: app.name }))}
|
||||
renderItem={renderAppItem}
|
||||
wrapperClassName="mt-2"
|
||||
@@ -256,7 +260,7 @@ export default function AccountPage() {
|
||||
className="mt-2 text-components-button-destructive-secondary-text"
|
||||
onClick={() => setShowDeleteAccountModal(true)}
|
||||
>
|
||||
{t(($) => $['account.delete'], { ns: 'common' })}
|
||||
{t(($) => $['account.delete'], { ns: 'accountSettings' })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -266,10 +270,10 @@ export default function AccountPage() {
|
||||
>
|
||||
<DialogContent className="w-105 p-6">
|
||||
<div className="mb-6 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.editName'], { ns: 'common' })}
|
||||
{t(($) => $['account.editName'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<label htmlFor={editNameInputId} className={`block ${titleClassName}`}>
|
||||
{t(($) => $['account.name'], { ns: 'common' })}
|
||||
{t(($) => $['account.name'], { ns: 'accountSettings' })}
|
||||
</label>
|
||||
<Input
|
||||
id={editNameInputId}
|
||||
@@ -294,13 +298,13 @@ export default function AccountPage() {
|
||||
<DialogContent className="w-105! p-6!">
|
||||
<div className="mb-6 title-2xl-semi-bold text-text-primary">
|
||||
{userProfile.is_password_set
|
||||
? t(($) => $['account.resetPassword'], { ns: 'common' })
|
||||
: t(($) => $['account.setPassword'], { ns: 'common' })}
|
||||
? t(($) => $['account.resetPassword'], { ns: 'accountSettings' })
|
||||
: t(($) => $['account.setPassword'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
{userProfile.is_password_set && (
|
||||
<Field name="current-password">
|
||||
<FieldLabel className="system-sm-semibold">
|
||||
{t(($) => $['account.currentPassword'], { ns: 'common' })}
|
||||
{t(($) => $['account.currentPassword'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
@@ -327,8 +331,8 @@ export default function AccountPage() {
|
||||
<Field name="new-password" className="mt-8">
|
||||
<FieldLabel className="system-sm-semibold">
|
||||
{userProfile.is_password_set
|
||||
? t(($) => $['account.newPassword'], { ns: 'common' })
|
||||
: t(($) => $['account.password'], { ns: 'common' })}
|
||||
? t(($) => $['account.newPassword'], { ns: 'accountSettings' })
|
||||
: t(($) => $['account.password'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
@@ -353,7 +357,7 @@ export default function AccountPage() {
|
||||
</Field>
|
||||
<Field name="confirm-password" className="mt-8">
|
||||
<FieldLabel className="system-sm-semibold">
|
||||
{t(($) => $['account.confirmPassword'], { ns: 'common' })}
|
||||
{t(($) => $['account.confirmPassword'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupInput
|
||||
|
||||
+2
-2
@@ -37,9 +37,9 @@ describe('CheckEmail', () => {
|
||||
|
||||
render(<CheckEmail onCancel={vi.fn()} onConfirm={onConfirm} />)
|
||||
|
||||
const input = screen.getByRole('textbox', { name: 'common.account.deleteLabel' })
|
||||
const input = screen.getByRole('textbox', { name: 'accountSettings.account.deleteLabel' })
|
||||
const submitButton = screen.getByRole('button', {
|
||||
name: 'common.account.sendVerificationButton',
|
||||
name: 'accountSettings.account.sendVerificationButton',
|
||||
})
|
||||
|
||||
await user.type(input, 'someone@example.com{Enter}')
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ describe('FeedBack', () => {
|
||||
|
||||
render(<FeedBack onCancel={vi.fn()} onConfirm={onConfirm} />)
|
||||
|
||||
const feedback = screen.getByRole('textbox', { name: 'common.account.feedbackLabel' })
|
||||
const feedback = screen.getByRole('textbox', { name: 'accountSettings.account.feedbackLabel' })
|
||||
await user.type(feedback, 'The workspace is no longer needed.')
|
||||
await user.click(screen.getByRole('button', { name: 'common.operation.submit' }))
|
||||
|
||||
|
||||
+4
-4
@@ -31,18 +31,18 @@ describe('VerifyEmail', () => {
|
||||
|
||||
render(<VerifyEmail onCancel={vi.fn()} onConfirm={onConfirm} />)
|
||||
|
||||
const input = screen.getByLabelText('common.account.verificationLabel')
|
||||
const input = screen.getByLabelText('accountSettings.account.verificationLabel')
|
||||
const submitButton = screen.getByRole('button', {
|
||||
name: 'common.account.permanentlyDeleteButton',
|
||||
name: 'accountSettings.account.permanentlyDeleteButton',
|
||||
})
|
||||
|
||||
expect(submitButton).toBeEnabled()
|
||||
await user.click(submitButton)
|
||||
|
||||
const errors = await screen.findAllByText('common.account.verificationPlaceholder')
|
||||
const errors = await screen.findAllByText('accountSettings.account.verificationPlaceholder')
|
||||
expect(errors).toHaveLength(1)
|
||||
expect(input).toHaveAttribute('aria-invalid', 'true')
|
||||
expect(input).toHaveAccessibleDescription('common.account.verificationPlaceholder')
|
||||
expect(input).toHaveAccessibleDescription('accountSettings.account.verificationPlaceholder')
|
||||
expect(input).toHaveFocus()
|
||||
expect(mockConfirmDeleteAccount).not.toHaveBeenCalled()
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type DeleteAccountProps = {
|
||||
}
|
||||
|
||||
export default function CheckEmail(props: DeleteAccountProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'accountSettings'])
|
||||
const { data: userProfileEmail } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.profile.email,
|
||||
@@ -44,20 +44,22 @@ export default function CheckEmail(props: DeleteAccountProps) {
|
||||
}}
|
||||
>
|
||||
<div className="py-1 body-md-medium text-text-destructive">
|
||||
{t(($) => $['account.deleteTip'], { ns: 'common' })}
|
||||
{t(($) => $['account.deleteTip'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<div className="pt-1 pb-2 body-md-regular text-text-secondary">
|
||||
{t(($) => $['account.deletePrivacyLinkTip'], { ns: 'common' })}
|
||||
{t(($) => $['account.deletePrivacyLinkTip'], { ns: 'accountSettings' })}
|
||||
<Link href="https://dify.ai/privacy" className="text-text-accent">
|
||||
{t(($) => $['account.deletePrivacyLink'], { ns: 'common' })}
|
||||
{t(($) => $['account.deletePrivacyLink'], { ns: 'accountSettings' })}
|
||||
</Link>
|
||||
</div>
|
||||
<Field name="email" className="mt-3">
|
||||
<FieldLabel className="system-sm-semibold">
|
||||
{t(($) => $['account.deleteLabel'], { ns: 'common' })}
|
||||
{t(($) => $['account.deleteLabel'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
placeholder={t(($) => $['account.deletePlaceholder'], { ns: 'common' }) as string}
|
||||
placeholder={
|
||||
t(($) => $['account.deletePlaceholder'], { ns: 'accountSettings' }) as string
|
||||
}
|
||||
value={userInputEmail}
|
||||
onValueChange={setUserInputEmail}
|
||||
/>
|
||||
@@ -70,7 +72,7 @@ export default function CheckEmail(props: DeleteAccountProps) {
|
||||
loading={isSendingEmail}
|
||||
variant="primary"
|
||||
>
|
||||
{t(($) => $['account.sendVerificationButton'], { ns: 'common' })}
|
||||
{t(($) => $['account.sendVerificationButton'], { ns: 'accountSettings' })}
|
||||
</Button>
|
||||
<Button type="button" className="w-full" onClick={props.onCancel}>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
|
||||
@@ -23,7 +23,7 @@ type FeedbackFormValues = {
|
||||
}
|
||||
|
||||
export default function FeedBack(props: DeleteAccountProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'accountSettings'])
|
||||
const { data: userProfileEmail } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.profile.email,
|
||||
@@ -37,7 +37,7 @@ export default function FeedBack(props: DeleteAccountProps) {
|
||||
await logout()
|
||||
// Tokens are now stored in cookies and cleared by backend
|
||||
router.push('/signin')
|
||||
toast.info(t(($) => $['account.deleteSuccessTip'], { ns: 'common' }))
|
||||
toast.info(t(($) => $['account.deleteSuccessTip'], { ns: 'accountSettings' }))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export default function FeedBack(props: DeleteAccountProps) {
|
||||
backdropProps={{ className: 'bg-background-overlay-backdrop backdrop-blur-[6px]' }}
|
||||
>
|
||||
<DialogTitle className="pr-8 pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.feedbackTitle'], { ns: 'common' })}
|
||||
{t(($) => $['account.feedbackTitle'], { ns: 'accountSettings' })}
|
||||
</DialogTitle>
|
||||
<Form<FeedbackFormValues>
|
||||
onFormSubmit={({ feedback }) => {
|
||||
@@ -81,11 +81,13 @@ export default function FeedBack(props: DeleteAccountProps) {
|
||||
>
|
||||
<Field name="feedback" className="mt-3 gap-0">
|
||||
<FieldLabel className="system-sm-semibold">
|
||||
{t(($) => $['account.feedbackLabel'], { ns: 'common' })}
|
||||
{t(($) => $['account.feedbackLabel'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
rows={6}
|
||||
placeholder={t(($) => $['account.feedbackPlaceholder'], { ns: 'common' }) as string}
|
||||
placeholder={
|
||||
t(($) => $['account.feedbackPlaceholder'], { ns: 'accountSettings' }) as string
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<div className="mt-3 flex w-full flex-col gap-2">
|
||||
|
||||
@@ -19,7 +19,7 @@ type VerifyEmailFormValues = {
|
||||
}
|
||||
|
||||
export default function VerifyEmail(props: DeleteAccountProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'accountSettings'])
|
||||
const emailToken = useAccountDeleteStore((state) => state.sendEmailToken)
|
||||
const { mutate: sendEmail } = useSendDeleteAccountEmail()
|
||||
const { isPending: isDeleting, mutateAsync: confirmDeleteAccount } = useConfirmDeleteAccount()
|
||||
@@ -44,17 +44,17 @@ export default function VerifyEmail(props: DeleteAccountProps) {
|
||||
}}
|
||||
>
|
||||
<div className="pt-1 body-md-medium text-text-destructive">
|
||||
{t(($) => $['account.deleteTip'], { ns: 'common' })}
|
||||
{t(($) => $['account.deleteTip'], { ns: 'accountSettings' })}
|
||||
</div>
|
||||
<div className="pt-1 pb-2 body-md-regular text-text-secondary">
|
||||
{t(($) => $['account.deletePrivacyLinkTip'], { ns: 'common' })}
|
||||
{t(($) => $['account.deletePrivacyLinkTip'], { ns: 'accountSettings' })}
|
||||
<Link href="https://dify.ai/privacy" className="text-text-accent">
|
||||
{t(($) => $['account.deletePrivacyLink'], { ns: 'common' })}
|
||||
{t(($) => $['account.deletePrivacyLink'], { ns: 'accountSettings' })}
|
||||
</Link>
|
||||
</div>
|
||||
<Field name="verificationCode" className="mt-3">
|
||||
<FieldLabel className="system-sm-semibold">
|
||||
{t(($) => $['account.verificationLabel'], { ns: 'common' })}
|
||||
{t(($) => $['account.verificationLabel'], { ns: 'accountSettings' })}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
autoComplete="one-time-code"
|
||||
@@ -63,13 +63,15 @@ export default function VerifyEmail(props: DeleteAccountProps) {
|
||||
pattern="[0-9]{6}"
|
||||
minLength={6}
|
||||
maxLength={6}
|
||||
placeholder={t(($) => $['account.verificationPlaceholder'], { ns: 'common' }) as string}
|
||||
placeholder={
|
||||
t(($) => $['account.verificationPlaceholder'], { ns: 'accountSettings' }) as string
|
||||
}
|
||||
/>
|
||||
<FieldError match="valueMissing">
|
||||
{t(($) => $['account.verificationPlaceholder'], { ns: 'common' })}
|
||||
{t(($) => $['account.verificationPlaceholder'], { ns: 'accountSettings' })}
|
||||
</FieldError>
|
||||
<FieldError match="patternMismatch">
|
||||
{t(($) => $['account.verificationPlaceholder'], { ns: 'common' })}
|
||||
{t(($) => $['account.verificationPlaceholder'], { ns: 'accountSettings' })}
|
||||
</FieldError>
|
||||
</Field>
|
||||
<div className="mt-3 flex w-full flex-col gap-2">
|
||||
@@ -80,7 +82,7 @@ export default function VerifyEmail(props: DeleteAccountProps) {
|
||||
variant="primary"
|
||||
tone="destructive"
|
||||
>
|
||||
{t(($) => $['account.permanentlyDeleteButton'], { ns: 'common' })}
|
||||
{t(($) => $['account.permanentlyDeleteButton'], { ns: 'accountSettings' })}
|
||||
</Button>
|
||||
<Button className="w-full" onClick={props.onCancel}>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
|
||||
@@ -13,7 +13,7 @@ type DeleteAccountProps = {
|
||||
}
|
||||
|
||||
export default function DeleteAccount(props: DeleteAccountProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['accountSettings'])
|
||||
const setCountdownLeftTime = useSetCountdownLeftTime()
|
||||
|
||||
const [showVerifyEmail, setShowVerifyEmail] = useState(false)
|
||||
@@ -42,7 +42,7 @@ export default function DeleteAccount(props: DeleteAccountProps) {
|
||||
backdropProps={{ className: 'bg-background-overlay-backdrop backdrop-blur-[6px]' }}
|
||||
>
|
||||
<DialogTitle className="pr-8 pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.delete'], { ns: 'common' })}
|
||||
{t(($) => $['account.delete'], { ns: 'accountSettings' })}
|
||||
</DialogTitle>
|
||||
{!showVerifyEmail && (
|
||||
<CheckEmail onCancel={props.onCancel} onConfirm={handleEmailCheckSuccess} />
|
||||
|
||||
@@ -9,7 +9,7 @@ import Link from '@/next/link'
|
||||
import Avatar from './avatar'
|
||||
|
||||
const Header = () => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['accountSettings', 'navigation'])
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const logoLabel =
|
||||
systemFeatures.branding.enabled && systemFeatures.branding.application_title
|
||||
@@ -36,13 +36,13 @@ const Header = () => {
|
||||
</Link>
|
||||
<div className="h-4 w-px origin-center rotate-[11.31deg] bg-divider-regular" />
|
||||
<p className="relative -mt-0.5 title-3xl-semi-bold text-text-primary">
|
||||
{t(($) => $['account.account'], { ns: 'common' })}
|
||||
{t(($) => $['account.account'], { ns: 'accountSettings' })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-3">
|
||||
<Link href="/" className={cn(buttonVariants(), 'px-3 py-2 system-sm-medium')}>
|
||||
<span aria-hidden className="i-custom-vender-main-nav-home size-4" />
|
||||
<p>{t(($) => $['mainNav.home'], { ns: 'common' })}</p>
|
||||
<p>{t(($) => $['mainNav.home'], { ns: 'navigation' })}</p>
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-4" />
|
||||
</Link>
|
||||
<div className="h-4 w-px bg-divider-regular" />
|
||||
|
||||
@@ -4,8 +4,8 @@ import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import AccountPage from './account-page'
|
||||
|
||||
export default function Account() {
|
||||
const { t } = useTranslation(['common'])
|
||||
useDocumentTitle(t(($) => $['menus.account'], { ns: 'common' }))
|
||||
const { t } = useTranslation(['navigation'])
|
||||
useDocumentTitle(t(($) => $['menus.account'], { ns: 'navigation' }))
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-160 px-6 pt-12">
|
||||
<AccountPage />
|
||||
|
||||
@@ -257,7 +257,7 @@ describe('AppDetailSection', () => {
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.getByRole('link', { name: 'common.settings.resourceAccess' }),
|
||||
screen.getByRole('link', { name: 'navigation.settings.resourceAccess' }),
|
||||
).toHaveAttribute('href', '/app/app-1/access-config')
|
||||
expect(
|
||||
screen.queryByRole('link', { name: 'common.appMenus.overview' }),
|
||||
@@ -275,7 +275,7 @@ describe('AppDetailSection', () => {
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.queryByRole('link', { name: 'common.settings.resourceAccess' }),
|
||||
screen.queryByRole('link', { name: 'navigation.settings.resourceAccess' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -285,7 +285,7 @@ describe('AppDetailSection', () => {
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.queryByRole('link', { name: 'common.settings.resourceAccess' }),
|
||||
screen.queryByRole('link', { name: 'navigation.settings.resourceAccess' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -299,7 +299,7 @@ describe('AppDetailSection', () => {
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.queryByRole('link', { name: 'common.settings.resourceAccess' }),
|
||||
screen.queryByRole('link', { name: 'navigation.settings.resourceAccess' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,14 +24,20 @@ describe('AppDetailTop', () => {
|
||||
it('links the combined home control to home', () => {
|
||||
render(<AppDetailTop />)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'common.mainNav.home' })).toHaveAttribute('href', '/')
|
||||
expect(screen.getByRole('link', { name: 'navigation.mainNav.home' })).toHaveAttribute(
|
||||
'href',
|
||||
'/',
|
||||
)
|
||||
expect(screen.queryByRole('button', { name: 'common.operation.back' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('links the Studio breadcrumb to the Studio page', () => {
|
||||
render(<AppDetailTop />)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'common.menus.apps' })).toHaveAttribute('href', '/apps')
|
||||
expect(screen.getByRole('link', { name: 'navigation.menus.apps' })).toHaveAttribute(
|
||||
'href',
|
||||
'/apps',
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the quick search action', () => {
|
||||
|
||||
@@ -113,17 +113,16 @@ describe('DatasetDetailSection', () => {
|
||||
|
||||
render(<DatasetDetailSection expand />)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'common.settings.resourceAccess' })).toHaveAttribute(
|
||||
'href',
|
||||
'/datasets/dataset-1/access-config',
|
||||
)
|
||||
expect(
|
||||
screen.getByRole('link', { name: 'navigation.settings.resourceAccess' }),
|
||||
).toHaveAttribute('href', '/datasets/dataset-1/access-config')
|
||||
})
|
||||
|
||||
it('should hide resource access navigation when dataset access config permission is missing', () => {
|
||||
render(<DatasetDetailSection expand />)
|
||||
|
||||
expect(
|
||||
screen.queryByRole('link', { name: 'common.settings.resourceAccess' }),
|
||||
screen.queryByRole('link', { name: 'navigation.settings.resourceAccess' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -136,7 +135,7 @@ describe('DatasetDetailSection', () => {
|
||||
render(<DatasetDetailSection expand />)
|
||||
|
||||
expect(
|
||||
screen.queryByRole('link', { name: 'common.settings.resourceAccess' }),
|
||||
screen.queryByRole('link', { name: 'navigation.settings.resourceAccess' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@@ -24,8 +24,11 @@ describe('DatasetDetailTop', () => {
|
||||
it('links the combined home control to home and labels the breadcrumb as datasets', () => {
|
||||
render(<DatasetDetailTop />)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'common.mainNav.home' })).toHaveAttribute('href', '/')
|
||||
expect(screen.getByRole('link', { name: 'common.menus.datasets' })).toHaveAttribute(
|
||||
expect(screen.getByRole('link', { name: 'navigation.mainNav.home' })).toHaveAttribute(
|
||||
'href',
|
||||
'/',
|
||||
)
|
||||
expect(screen.getByRole('link', { name: 'navigation.menus.datasets' })).toHaveAttribute(
|
||||
'href',
|
||||
'/datasets',
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ type AppDetailSectionProps = {
|
||||
}
|
||||
|
||||
const AppDetailSection = ({ expand = true }: AppDetailSectionProps) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'navigation'])
|
||||
const pathname = usePathname()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { data: currentUserId } = useSuspenseQuery({
|
||||
@@ -136,7 +136,7 @@ const AppDetailSection = ({ expand = true }: AppDetailSectionProps) => {
|
||||
...(supportsResourceAccess && appACLCapabilities.canAccessConfig
|
||||
? [
|
||||
{
|
||||
name: t(($) => $['settings.resourceAccess'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.resourceAccess'], { ns: 'navigation' }),
|
||||
href: `/app/${appId}/access-config`,
|
||||
icon: 'i-ri-lock-2-line',
|
||||
selectedIcon: 'i-ri-lock-2-fill',
|
||||
|
||||
@@ -23,7 +23,7 @@ type AppDetailTopProps = {
|
||||
}
|
||||
|
||||
export function AppDetailTop({ expand = true, onToggle }: AppDetailTopProps) {
|
||||
const { t } = useTranslation(['app', 'common'])
|
||||
const { t } = useTranslation(['app', 'navigation'])
|
||||
|
||||
if (!expand) {
|
||||
return (
|
||||
@@ -43,12 +43,12 @@ export function AppDetailTop({ expand = true, onToggle }: AppDetailTopProps) {
|
||||
|
||||
return (
|
||||
<div className="flex items-center py-2 pr-2 pl-1">
|
||||
<Breadcrumb aria-label={t(($) => $['menus.apps'], { ns: 'common' })} className="flex-1">
|
||||
<Breadcrumb aria-label={t(($) => $['menus.apps'], { ns: 'navigation' })} className="flex-1">
|
||||
<BreadcrumbList className="gap-px">
|
||||
<BreadcrumbItem className="shrink-0">
|
||||
<BreadcrumbLink
|
||||
render={<Link href="/" />}
|
||||
aria-label={t(($) => $['mainNav.home'], { ns: 'common' })}
|
||||
aria-label={t(($) => $['mainNav.home'], { ns: 'navigation' })}
|
||||
className="gap-0 rounded-lg py-2 pr-1.5 pl-0.5 hover:bg-background-default-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-arrow-left-s-line size-4" />
|
||||
@@ -63,7 +63,7 @@ export function AppDetailTop({ expand = true, onToggle }: AppDetailTopProps) {
|
||||
render={<Link href="/apps" />}
|
||||
className="rounded-lg px-1.5 py-2 system-sm-semibold-uppercase text-text-secondary hover:bg-background-default-hover hover:text-text-primary"
|
||||
>
|
||||
{t(($) => $['menus.apps'], { ns: 'common' })}
|
||||
{t(($) => $['menus.apps'], { ns: 'navigation' })}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</>
|
||||
|
||||
@@ -27,7 +27,7 @@ type DatasetDetailSectionProps = {
|
||||
}
|
||||
|
||||
const DatasetDetailSection = ({ expand = true }: DatasetDetailSectionProps) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'navigation'])
|
||||
const pathname = usePathname()
|
||||
const datasetId = getDatasetIdFromPathname(pathname)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
@@ -92,7 +92,7 @@ const DatasetDetailSection = ({ expand = true }: DatasetDetailSectionProps) => {
|
||||
...(datasetACLCapabilities.canAccessConfig
|
||||
? [
|
||||
{
|
||||
name: t(($) => $['settings.resourceAccess'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.resourceAccess'], { ns: 'navigation' }),
|
||||
href: `/datasets/${datasetId}/access-config`,
|
||||
icon: 'i-ri-lock-2-line',
|
||||
selectedIcon: 'i-ri-lock-2-fill',
|
||||
|
||||
@@ -23,7 +23,7 @@ type DatasetDetailTopProps = {
|
||||
}
|
||||
|
||||
export function DatasetDetailTop({ expand = true, onToggle }: DatasetDetailTopProps) {
|
||||
const { t } = useTranslation(['app', 'common'])
|
||||
const { t } = useTranslation(['app', 'navigation'])
|
||||
|
||||
if (!expand) {
|
||||
return (
|
||||
@@ -43,12 +43,15 @@ export function DatasetDetailTop({ expand = true, onToggle }: DatasetDetailTopPr
|
||||
|
||||
return (
|
||||
<div className="flex items-center py-2 pr-2 pl-1">
|
||||
<Breadcrumb aria-label={t(($) => $['menus.datasets'], { ns: 'common' })} className="flex-1">
|
||||
<Breadcrumb
|
||||
aria-label={t(($) => $['menus.datasets'], { ns: 'navigation' })}
|
||||
className="flex-1"
|
||||
>
|
||||
<BreadcrumbList className="gap-px">
|
||||
<BreadcrumbItem className="shrink-0">
|
||||
<BreadcrumbLink
|
||||
render={<Link href="/" />}
|
||||
aria-label={t(($) => $['mainNav.home'], { ns: 'common' })}
|
||||
aria-label={t(($) => $['mainNav.home'], { ns: 'navigation' })}
|
||||
className="gap-0 rounded-lg py-2 pr-1.5 pl-0.5 hover:bg-background-default-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-arrow-left-s-line size-4" />
|
||||
@@ -63,7 +66,7 @@ export function DatasetDetailTop({ expand = true, onToggle }: DatasetDetailTopPr
|
||||
render={<Link href="/datasets" />}
|
||||
className="rounded-lg px-1.5 py-2 system-sm-semibold-uppercase text-text-secondary hover:bg-background-default-hover hover:text-text-primary"
|
||||
>
|
||||
{t(($) => $['menus.datasets'], { ns: 'common' })}
|
||||
{t(($) => $['menus.datasets'], { ns: 'navigation' })}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</>
|
||||
|
||||
@@ -321,7 +321,7 @@ describe('Menu', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.settings.resourceAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.resourceAccess')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide export and delete options when not rag pipeline and not deletable', () => {
|
||||
@@ -400,7 +400,7 @@ describe('Menu', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
await user.click(screen.getByText('common.settings.resourceAccess'))
|
||||
await user.click(screen.getByText('navigation.settings.resourceAccess'))
|
||||
|
||||
expect(openAccessConfig).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -461,7 +461,7 @@ describe('Dropdown', () => {
|
||||
await openMenu(user)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('common.settings.resourceAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.resourceAccess')).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.operation.edit')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.operation.delete')).not.toBeInTheDocument()
|
||||
})
|
||||
@@ -481,7 +481,7 @@ describe('Dropdown', () => {
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('common.operation.delete')).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.resourceAccess')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.resourceAccess')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -566,7 +566,7 @@ describe('Dropdown', () => {
|
||||
|
||||
// Act
|
||||
await openMenu(user)
|
||||
await user.click(screen.getByText('common.settings.resourceAccess'))
|
||||
await user.click(screen.getByText('navigation.settings.resourceAccess'))
|
||||
|
||||
// Assert
|
||||
expect(mockPush).toHaveBeenCalledWith('/datasets/dataset-1/access-config')
|
||||
|
||||
@@ -26,7 +26,7 @@ const Menu = ({
|
||||
detectIsUsedByApp,
|
||||
openAccessConfig,
|
||||
}: MenuProps) => {
|
||||
const { t } = useTranslation(['common', 'datasetPipeline'])
|
||||
const { t } = useTranslation(['common', 'datasetPipeline', 'navigation'])
|
||||
const runtimeMode = useDatasetDetailContextWithSelector((state) => state.dataset?.runtime_mode)
|
||||
|
||||
return (
|
||||
@@ -49,7 +49,7 @@ const Menu = ({
|
||||
{showAccessConfig && (
|
||||
<MenuItem
|
||||
Icon={RiLock2Line}
|
||||
name={t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
name={t(($) => $['settings.resourceAccess'], { ns: 'navigation' })}
|
||||
handleClick={openAccessConfig}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -169,7 +169,7 @@ describe('AppAccessConfigPage', () => {
|
||||
render(<AppAccessConfigPage appId="app-1" />)
|
||||
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'common.settings.resourceAccess' }),
|
||||
screen.getByRole('heading', { name: 'navigation.settings.resourceAccess' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByTestId('access-rules-editor')).toBeInTheDocument()
|
||||
expect(useAppUserAccessSettings).toHaveBeenCalledWith('app-1', expect.any(String), 1, 10)
|
||||
|
||||
@@ -35,7 +35,7 @@ type AppAccessConfigContentProps = {
|
||||
}
|
||||
|
||||
const AppAccessConfigContent = ({ appId, maintainerId }: AppAccessConfigContentProps) => {
|
||||
const { t } = useTranslation(['common', 'permission'])
|
||||
const { t } = useTranslation(['permission', 'navigation'])
|
||||
const locale = useLocale()
|
||||
const language = useMemo(() => getAccessControlTemplateLanguage(locale), [locale])
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
@@ -196,7 +196,7 @@ const AppAccessConfigContent = ({ appId, maintainerId }: AppAccessConfigContentP
|
||||
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-background-default-subtle">
|
||||
<header className="flex min-h-15.5 shrink-0 flex-col justify-center px-6 py-3">
|
||||
<h1 className="system-xl-semibold text-text-primary">
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'navigation' })}
|
||||
</h1>
|
||||
<p className="mt-0.5 system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['accessRule.appDescription'], { ns: 'permission' })}
|
||||
|
||||
@@ -143,7 +143,7 @@ export function TriggerAccessPointCard({
|
||||
canManageAccessPoint,
|
||||
highlighted,
|
||||
}: TriggerAccessPointCardProps) {
|
||||
const { t } = useTranslation(['appOverview', 'common', 'deployments'])
|
||||
const { t } = useTranslation(['appOverview', 'deployments', 'navigation'])
|
||||
const docLink = useDocLink()
|
||||
const { data: response, isLoading } = useAppTriggers(appInfo.id)
|
||||
const { data: triggerPlugins = [] } = useAllTriggerPlugins()
|
||||
@@ -171,7 +171,7 @@ export function TriggerAccessPointCard({
|
||||
|
||||
return (
|
||||
<AccessPointCard
|
||||
title={t(($) => $['settings.trigger'], { ns: 'common' })}
|
||||
title={t(($) => $['settings.trigger'], { ns: 'navigation' })}
|
||||
description={t(($) => $['studio.accessPoint.triggerDescription'], {
|
||||
ns: 'deployments',
|
||||
})}
|
||||
|
||||
@@ -79,7 +79,14 @@ export function WebAppAccessPointCard({
|
||||
showAccessControl,
|
||||
workflow,
|
||||
}: WebAppAccessPointCardProps) {
|
||||
const { t } = useTranslation(['agentV2', 'app', 'appOverview', 'common', 'deployments'])
|
||||
const { t } = useTranslation([
|
||||
'agentV2',
|
||||
'app',
|
||||
'appOverview',
|
||||
'common',
|
||||
'deployments',
|
||||
'navigation',
|
||||
])
|
||||
const setAppDetail = useAppStore((state) => state.setAppDetail)
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showEmbedded, setShowEmbedded] = useState(false)
|
||||
@@ -237,7 +244,7 @@ export function WebAppAccessPointCard({
|
||||
onClick={() => setShowSettings(true)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-equalizer-2-line size-4" />
|
||||
{t(($) => $['settings.settings'], { ns: 'common' })}
|
||||
{t(($) => $['settings.settings'], { ns: 'navigation' })}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
+9
-2
@@ -53,7 +53,14 @@ export function EnvironmentWebAppCard({
|
||||
canManageAccessPoint,
|
||||
highlighted,
|
||||
}: EnvironmentWebAppCardProps) {
|
||||
const { t } = useTranslation(['agentV2', 'app', 'appOverview', 'common', 'deployments'])
|
||||
const { t } = useTranslation([
|
||||
'agentV2',
|
||||
'app',
|
||||
'appOverview',
|
||||
'common',
|
||||
'deployments',
|
||||
'navigation',
|
||||
])
|
||||
const queryClient = useQueryClient()
|
||||
const appInfo = useAppStore((state) => state.appDetail) as AccessPointAppInfo | null
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
@@ -201,7 +208,7 @@ export function EnvironmentWebAppCard({
|
||||
onClick={() => setShowSettings(true)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-equalizer-2-line size-4" />
|
||||
{t(($) => $['settings.settings'], { ns: 'common' })}
|
||||
{t(($) => $['settings.settings'], { ns: 'navigation' })}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -42,13 +42,13 @@ export function DeployedEnvironmentAccessPoints({
|
||||
canManageAccessPoint,
|
||||
highlightedAccessPoint,
|
||||
}: DeployedEnvironmentAccessPointsProps) {
|
||||
const { t } = useTranslation(['common', 'deployments', 'tools'])
|
||||
const { t } = useTranslation(['deployments', 'tools', 'navigation'])
|
||||
const unsupportedStatusLabel = useAccessPointStatusLabel('unsupported')
|
||||
|
||||
const title = (accessPoint: (typeof UNSUPPORTED_ACCESS_POINTS)[number]) => {
|
||||
const key = ACCESS_POINT_CONFIG[accessPoint].title
|
||||
if (key === 'mcp') return t(($) => $['mcp.server.title'], { ns: 'tools' })
|
||||
return t(($) => $['settings.trigger'], { ns: 'common' })
|
||||
return t(($) => $['settings.trigger'], { ns: 'navigation' })
|
||||
}
|
||||
|
||||
const description = (accessPoint: (typeof UNSUPPORTED_ACCESS_POINTS)[number]) => {
|
||||
|
||||
@@ -26,9 +26,9 @@ const WorkflowToolAction = ({
|
||||
published,
|
||||
onConfigure,
|
||||
}: WorkflowToolActionProps) => {
|
||||
const { t } = useTranslation(['appApi', 'workflow'])
|
||||
const { t } = useTranslation(['appApi', 'workflow', 'navigation'])
|
||||
const disabledReason = disabled ? message : undefined
|
||||
const workflowToolLabel = t(($) => $['common.workflowAsTool'], { ns: 'workflow' })
|
||||
const workflowToolLabel = t(($) => $['common.workflowAsTool'], { ns: 'navigation' })
|
||||
|
||||
if (!published || isLoading)
|
||||
return (
|
||||
|
||||
+2
-2
@@ -247,7 +247,7 @@ describe('ConfigContent', () => {
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('dataset.weightedScore.title'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('common.modelProvider.rerankModel.key'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.rerankModel.key'))!.toBeInTheDocument()
|
||||
expect(screen.getByTitle('dataset.weightedScore.semantic'))!.toBeVisible()
|
||||
expect(screen.getByTitle('dataset.weightedScore.keyword'))!.toBeVisible()
|
||||
})
|
||||
@@ -337,7 +337,7 @@ describe('ConfigContent', () => {
|
||||
selectedDatasets={selectedDatasets}
|
||||
/>,
|
||||
)
|
||||
await user.click(screen.getByText('common.modelProvider.rerankModel.key'))
|
||||
await user.click(screen.getByText('modelProvider.modelProvider.rerankModel.key'))
|
||||
|
||||
// Assert
|
||||
expect(toastErrorSpy).toHaveBeenCalledWith('workflow.errorMsg.rerankModelRequired')
|
||||
|
||||
+7
-7
@@ -52,7 +52,7 @@ const ConfigContent: FC<Props> = ({
|
||||
const rerankLabelId = useId()
|
||||
const rerankOptionLabelId = useId()
|
||||
|
||||
const { t } = useTranslation(['common', 'dataset', 'workflow'])
|
||||
const { t } = useTranslation(['dataset', 'workflow', 'modelProvider'])
|
||||
const selectedDatasetsMode = useSelectedDatasetsMode(selectedDatasets)
|
||||
const type = datasetConfigs.retrieval_model
|
||||
|
||||
@@ -156,8 +156,8 @@ const ConfigContent: FC<Props> = ({
|
||||
},
|
||||
{
|
||||
value: RerankingModeEnum.RerankingModel,
|
||||
label: t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' }),
|
||||
tips: t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' }),
|
||||
label: t(($) => $['modelProvider.rerankModel.key'], { ns: 'modelProvider' }),
|
||||
tips: t(($) => $['modelProvider.rerankModel.tip'], { ns: 'modelProvider' }),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -280,12 +280,12 @@ const ConfigContent: FC<Props> = ({
|
||||
id={rerankLabelId}
|
||||
className="ml-1 system-sm-semibold leading-8 text-text-secondary"
|
||||
>
|
||||
{t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.rerankModel.key'], { ns: 'modelProvider' })}
|
||||
</div>
|
||||
<Infotip>
|
||||
<InfotipTrigger aria-labelledby={rerankLabelId} className="ml-1" />
|
||||
<InfotipContent aria-labelledby={rerankLabelId} className="w-50">
|
||||
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'modelProvider' })}
|
||||
</InfotipContent>
|
||||
</Infotip>
|
||||
</div>
|
||||
@@ -358,12 +358,12 @@ const ConfigContent: FC<Props> = ({
|
||||
id={reasoningLabelId}
|
||||
className="text-[13px] leading-8 font-medium text-text-primary"
|
||||
>
|
||||
{t(($) => $['modelProvider.systemReasoningModel.key'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.systemReasoningModel.key'], { ns: 'modelProvider' })}
|
||||
</div>
|
||||
<Infotip>
|
||||
<InfotipTrigger aria-labelledby={reasoningLabelId} />
|
||||
<InfotipContent aria-labelledby={reasoningLabelId}>
|
||||
{t(($) => $['modelProvider.systemReasoningModel.tip'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.systemReasoningModel.tip'], { ns: 'modelProvider' })}
|
||||
</InfotipContent>
|
||||
</Infotip>
|
||||
</div>
|
||||
|
||||
@@ -983,7 +983,9 @@ describe('Debug', () => {
|
||||
},
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.modelProvider.addModel(1/4)' }))
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: 'modelProvider.modelProvider.addModel(1/4)' }),
|
||||
)
|
||||
expect(onMultipleModelConfigsChange).toHaveBeenCalledWith(true, [
|
||||
{ id: 'model-1', model: 'vision-model', provider: 'openai', parameters: {} },
|
||||
expect.objectContaining({ model: '', provider: '', parameters: {} }),
|
||||
@@ -1004,7 +1006,7 @@ describe('Debug', () => {
|
||||
})
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.modelProvider.addModel(4/4)' }),
|
||||
screen.getByRole('button', { name: 'modelProvider.modelProvider.addModel(4/4)' }),
|
||||
)!.toBeDisabled()
|
||||
})
|
||||
|
||||
@@ -1025,7 +1027,7 @@ describe('Debug', () => {
|
||||
})
|
||||
|
||||
const addModelButton = screen.getByRole('button', {
|
||||
name: 'common.modelProvider.addModel(1/4)',
|
||||
name: 'modelProvider.modelProvider.addModel(1/4)',
|
||||
})
|
||||
expect(addModelButton).toBeDisabled()
|
||||
fireEvent.click(addModelButton)
|
||||
|
||||
+9
-7
@@ -393,7 +393,7 @@ describe('ModelParameterTrigger', () => {
|
||||
})
|
||||
|
||||
// When currentProvider and currentModel are null, shows "Select Model"
|
||||
expect(screen.getByText('common.modelProvider.selectModel')).toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.selectModel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render configured model id and incompatible tooltip when model is missing from the provider list', async () => {
|
||||
@@ -402,13 +402,13 @@ describe('ModelParameterTrigger', () => {
|
||||
|
||||
expect(screen.getByText('gpt-3.5-turbo')).toBeInTheDocument()
|
||||
const trigger = screen.getByRole('button', {
|
||||
name: /common.modelProvider.selector.incompatibleTip/,
|
||||
name: /modelProvider.modelProvider.selector.incompatibleTip/,
|
||||
})
|
||||
await user.hover(trigger)
|
||||
expect(
|
||||
await screen.findByText(
|
||||
(content, element) =>
|
||||
content === 'common.modelProvider.selector.incompatibleTip' &&
|
||||
content === 'modelProvider.modelProvider.selector.incompatibleTip' &&
|
||||
!!element &&
|
||||
!trigger.contains(element),
|
||||
),
|
||||
@@ -424,13 +424,13 @@ describe('ModelParameterTrigger', () => {
|
||||
renderComponent()
|
||||
|
||||
const trigger = screen.getByRole('button', {
|
||||
name: /common.modelProvider.selector.configureRequired/,
|
||||
name: /modelProvider.modelProvider.selector.configureRequired/,
|
||||
})
|
||||
await user.hover(trigger)
|
||||
expect(
|
||||
await screen.findByText(
|
||||
(content, element) =>
|
||||
content === 'common.modelProvider.selector.configureRequired' &&
|
||||
content === 'modelProvider.modelProvider.selector.configureRequired' &&
|
||||
!!element &&
|
||||
!trigger.contains(element),
|
||||
),
|
||||
@@ -445,12 +445,14 @@ describe('ModelParameterTrigger', () => {
|
||||
})
|
||||
renderComponent()
|
||||
|
||||
const trigger = screen.getByRole('button', { name: /common.modelProvider.selector.disabled/ })
|
||||
const trigger = screen.getByRole('button', {
|
||||
name: /modelProvider.modelProvider.selector.disabled/,
|
||||
})
|
||||
await user.hover(trigger)
|
||||
expect(
|
||||
await screen.findByText(
|
||||
(content, element) =>
|
||||
content === 'common.modelProvider.selector.disabled' &&
|
||||
content === 'modelProvider.modelProvider.selector.disabled' &&
|
||||
!!element &&
|
||||
!trigger.contains(element),
|
||||
),
|
||||
|
||||
+3
-3
@@ -31,7 +31,7 @@ const DebugModelParameterTrigger: FC<DebugModelParameterTriggerProps> = ({
|
||||
className,
|
||||
...triggerProps
|
||||
}) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['modelProvider'])
|
||||
const { currentProvider, currentModel } = useTextGenerationCurrentProviderAndModelAndModelList({
|
||||
provider: modelAndParameter.provider,
|
||||
model: modelAndParameter.model,
|
||||
@@ -58,7 +58,7 @@ const DebugModelParameterTrigger: FC<DebugModelParameterTriggerProps> = ({
|
||||
const isActive = status === 'active'
|
||||
const statusTooltipLabel =
|
||||
!isEmpty && !isActive && statusLabelKey
|
||||
? t(($) => $[statusTooltipKey || statusLabelKey], { ns: 'common' })
|
||||
? t(($) => $[statusTooltipKey || statusLabelKey], { ns: 'modelProvider' })
|
||||
: undefined
|
||||
|
||||
return (
|
||||
@@ -100,7 +100,7 @@ const DebugModelParameterTrigger: FC<DebugModelParameterTriggerProps> = ({
|
||||
)}
|
||||
{isEmpty && (
|
||||
<span className="mr-0.5 truncate text-[13px] font-medium text-text-accent">
|
||||
{t(($) => $['modelProvider.selectModel'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.selectModel'], { ns: 'modelProvider' })}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
|
||||
@@ -67,7 +67,7 @@ const Debug: FC<IDebug> = ({
|
||||
multipleModelConfigs,
|
||||
onMultipleModelConfigsChange,
|
||||
}) => {
|
||||
const { t } = useTranslation(['appDebug', 'common', 'workflow'])
|
||||
const { t } = useTranslation(['appDebug', 'common', 'workflow', 'modelProvider'])
|
||||
const {
|
||||
canTestAndRun = false,
|
||||
appId,
|
||||
@@ -439,7 +439,7 @@ const Debug: FC<IDebug> = ({
|
||||
disabled={multipleModelConfigs.length >= 4 || !canTestAndRun}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-add-line size-3.5" />
|
||||
{t(($) => $['modelProvider.addModel'], { ns: 'common' })}(
|
||||
{t(($) => $['modelProvider.addModel'], { ns: 'modelProvider' })}(
|
||||
{multipleModelConfigs.length}
|
||||
/4)
|
||||
</Button>
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ const mockGetSelectedDatasetsMode = vi.fn()
|
||||
const mockToastError = vi.fn()
|
||||
const mockToastSuccess = vi.fn()
|
||||
const mockToastWarning = vi.fn()
|
||||
const t = withSelectorKey((key: string) => key) as TFunction<['appDebug', 'common']>
|
||||
const t = withSelectorKey((key: string) => key) as TFunction<
|
||||
['appDebug', 'common', 'modelProvider']
|
||||
>
|
||||
|
||||
const baseVisionConfig: VisionSettings = {
|
||||
enabled: false,
|
||||
|
||||
@@ -28,7 +28,7 @@ type ModelChangeHandlerOptions = {
|
||||
resolvedModelModeType: ModelModeType
|
||||
setCompletionParams: (value: FormValue) => void
|
||||
setModelConfig: (config: ModelConfig) => void
|
||||
t: TFunction<['appDebug', 'common']>
|
||||
t: TFunction<['appDebug', 'common', 'modelProvider']>
|
||||
visionConfig: VisionSettings
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ export const createModelChangeHandler =
|
||||
|
||||
if (Object.keys(removedDetails).length)
|
||||
toast.warning(
|
||||
`${t(($) => $['modelProvider.parametersInvalidRemoved'], { ns: 'common' })}: ${Object.entries(
|
||||
`${t(($) => $['modelProvider.parametersInvalidRemoved'], { ns: 'modelProvider' })}: ${Object.entries(
|
||||
removedDetails,
|
||||
)
|
||||
.map(([key, reason]) => `${key} (${reason})`)
|
||||
|
||||
@@ -41,7 +41,7 @@ import { useMultipleModelDebug } from './configuration-lifecycle/use-multiple-mo
|
||||
import { usePublishedConfigSync } from './configuration-lifecycle/use-published-config-sync'
|
||||
|
||||
export const useConfiguration = (): ConfigurationViewModel => {
|
||||
const { t } = useTranslation(['appDebug', 'common'])
|
||||
const { t } = useTranslation(['appDebug', 'common', 'modelProvider'])
|
||||
const [_settingsDestination, setSettingsDestination] = useQueryState(
|
||||
settingsQueryParamName,
|
||||
settingsQueryParser,
|
||||
|
||||
@@ -785,7 +785,7 @@ vi.mock('react-i18next', async () => {
|
||||
'deployments.studio.precheck.from': 'From',
|
||||
'deployments.studio.precheck.nodeCount': '{{count}} nodes',
|
||||
'deployments.studio.updatedAtBy': 'Updated at {{time}} by {{name}}',
|
||||
'workflow.common.workflowAsTool': 'Workflow as Tool',
|
||||
'navigation.common.workflowAsTool': 'Workflow as Tool',
|
||||
'workflow.common.publishedBy': 'Published {{time}} by {{author}}',
|
||||
})
|
||||
})
|
||||
@@ -1038,7 +1038,7 @@ describe('AppDeploy', () => {
|
||||
).toHaveAttribute('href', '/app/app-1/access-point?environment=built-in&accessPoint=mcp')
|
||||
expect(
|
||||
builtInEnvironment.getByRole('button', {
|
||||
name: 'common.settings.trigger · agentV2.agentDetail.access.status.outOfService',
|
||||
name: 'navigation.settings.trigger · agentV2.agentDetail.access.status.outOfService',
|
||||
}),
|
||||
).toBeDisabled()
|
||||
expect(builtInEnvironment.getByText('Updated at 03-09 16:03 by Bob')).toBeInTheDocument()
|
||||
@@ -1091,7 +1091,7 @@ describe('AppDeploy', () => {
|
||||
).toBeDisabled()
|
||||
expect(
|
||||
builtInEnvironment.getByRole('link', {
|
||||
name: 'common.settings.trigger · agentV2.agentDetail.access.status.inService',
|
||||
name: 'navigation.settings.trigger · agentV2.agentDetail.access.status.inService',
|
||||
}),
|
||||
).toHaveAttribute('href', '/app/app-1/access-point?environment=built-in&accessPoint=trigger')
|
||||
})
|
||||
|
||||
@@ -15,12 +15,12 @@ const ACCESS_POINT_ICON_CLASS_NAMES: Record<AccessPoint, string> = {
|
||||
|
||||
function useAccessPointLabels() {
|
||||
const { t: tAgent } = useTranslation(['agentV2'])
|
||||
const { t: tCommon } = useTranslation(['common'])
|
||||
const { t: tCommon } = useTranslation(['navigation'])
|
||||
|
||||
return {
|
||||
mcp: 'MCP',
|
||||
serviceApi: tAgent(($) => $['agentDetail.access.serviceApi.title']),
|
||||
trigger: tCommon(($) => $['settings.trigger']),
|
||||
trigger: tCommon(($) => $['settings.trigger'], { ns: 'navigation' }),
|
||||
webApp: tAgent(($) => $['agentDetail.access.webApp.title']),
|
||||
} satisfies Record<AccessPoint, string>
|
||||
}
|
||||
|
||||
+3
-2
@@ -46,7 +46,7 @@ export const CredentialsSection = memo(
|
||||
onChange: DeploymentConfigurationValuesController['setCredential']
|
||||
}) => {
|
||||
const { t } = useTranslation(['deployments'])
|
||||
const { t: tWorkflow } = useTranslation(['workflow'])
|
||||
const { t: tWorkflow } = useTranslation(['navigation'])
|
||||
const appCredentialSlots = credentialSlots.filter((slot) => !slot.workflow_as_tool_dependency)
|
||||
const subworkflowCredentialSlots = credentialSlots.filter(
|
||||
(slot) => slot.workflow_as_tool_dependency,
|
||||
@@ -77,7 +77,8 @@ export const CredentialsSection = memo(
|
||||
<>
|
||||
<div className="flex min-w-0 items-center gap-2 pt-2">
|
||||
<span className="shrink-0 system-xs-medium-uppercase text-text-tertiary">
|
||||
{t(($) => $['studio.precheck.from'])} {tWorkflow(($) => $['common.workflowAsTool'])}
|
||||
{t(($) => $['studio.precheck.from'])}{' '}
|
||||
{tWorkflow(($) => $['common.workflowAsTool'], { ns: 'navigation' })}
|
||||
</span>
|
||||
<span
|
||||
aria-hidden
|
||||
|
||||
@@ -1652,7 +1652,7 @@ describe('AppCard', () => {
|
||||
expect(screen.getByText('common.operation.delete')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.queryByText('app.accessControl')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.resourceAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.resourceAccess')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide resource access option when RBAC is disabled', async () => {
|
||||
@@ -1669,7 +1669,7 @@ describe('AppCard', () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('common.operation.delete')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.queryByText('common.settings.resourceAccess')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.resourceAccess')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should navigate to app access config when resource access is clicked', async () => {
|
||||
@@ -1682,7 +1682,7 @@ describe('AppCard', () => {
|
||||
|
||||
fireEvent.click(getOperationsTrigger())
|
||||
await waitFor(() => {
|
||||
fireEvent.click(screen.getByText('common.settings.resourceAccess'))
|
||||
fireEvent.click(screen.getByText('navigation.settings.resourceAccess'))
|
||||
})
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith('/app/test-app-id/access-config')
|
||||
|
||||
@@ -1090,7 +1090,7 @@ describe('List', () => {
|
||||
const searchBox = screen.getByRole('searchbox', {
|
||||
name: 'app.gotoAnything.actions.searchApplications',
|
||||
})
|
||||
const scrollContainer = screen.getByRole('region', { name: 'common.menus.apps' })
|
||||
const scrollContainer = screen.getByRole('region', { name: 'navigation.menus.apps' })
|
||||
expect(scrollContainer).not.toContainElement(searchBox)
|
||||
const scrollTo = vi.fn()
|
||||
scrollContainer.scrollTo = scrollTo
|
||||
|
||||
@@ -121,7 +121,7 @@ function AppCardOperationsMenuItems({
|
||||
onDelete,
|
||||
onAccessConfig,
|
||||
}: AppCardOperationsMenuItemsProps) {
|
||||
const { t } = useTranslation(['app', 'common'])
|
||||
const { t } = useTranslation(['app', 'common', 'navigation'])
|
||||
const openAsyncWindow = useAsyncWindowOpen()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { data: userCanAccessApp, isLoading: isGettingUserCanAccessApp } = useGetUserCanAccessApp({
|
||||
@@ -231,7 +231,7 @@ function AppCardOperationsMenuItems({
|
||||
onClick={(event) => handleMenuAction(event, onAccessConfig)}
|
||||
>
|
||||
<span className="text-sm/5 text-text-secondary">
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'navigation' })}
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
@@ -63,7 +63,7 @@ export function AppListHeader({
|
||||
onOpenTagManagement,
|
||||
showCreateButton,
|
||||
}: AppListHeaderProps) {
|
||||
const { t } = useTranslation(['app', 'common'])
|
||||
const { t } = useTranslation(['app', 'navigation'])
|
||||
const activeStepByStepTourTaskId = useAtomValue(activeStepByStepTourTaskIdAtom)
|
||||
const activeStepByStepTourGuideIndex = useAtomValue(activeStepByStepTourGuideIndexAtom)
|
||||
const activeStepByStepTourGuideGroup = useAtomValue(activeStepByStepTourGuideGroupAtom)
|
||||
@@ -80,7 +80,7 @@ export function AppListHeader({
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<h1 id={titleId} className="text-[18px]/[21.6px] font-semibold text-text-primary">
|
||||
{t(($) => $['menus.apps'], { ns: 'common' })}
|
||||
{t(($) => $['menus.apps'], { ns: 'navigation' })}
|
||||
</h1>
|
||||
</div>
|
||||
}
|
||||
|
||||
+3
-1
@@ -252,7 +252,9 @@ describe('ConfigParamModal', () => {
|
||||
const saveBtn = buttons.find((b) => b.textContent?.includes('initSetup'))
|
||||
fireEvent.click(saveBtn!)
|
||||
|
||||
expect(toastErrorSpy).toHaveBeenCalledWith('common.modelProvider.embeddingModel.required')
|
||||
expect(toastErrorSpy).toHaveBeenCalledWith(
|
||||
'modelProvider.modelProvider.embeddingModel.required',
|
||||
)
|
||||
})
|
||||
|
||||
it('should call onHide when cancel is clicked and not loading', () => {
|
||||
|
||||
+3
-3
@@ -35,7 +35,7 @@ const ConfigParamModal: FC<Props> = ({
|
||||
isInit,
|
||||
annotationConfig: oldAnnotationConfig,
|
||||
}) => {
|
||||
const { t } = useTranslation(['appAnnotation', 'appDebug', 'common'])
|
||||
const { t } = useTranslation(['appAnnotation', 'appDebug', 'common', 'modelProvider'])
|
||||
const {
|
||||
modelList: embeddingsModelList,
|
||||
defaultModel: embeddingsDefaultModel,
|
||||
@@ -65,7 +65,7 @@ const ConfigParamModal: FC<Props> = ({
|
||||
!embeddingModel.modelName ||
|
||||
(embeddingModel.modelName === embeddingsDefaultModel?.model && !isEmbeddingsDefaultModelValid)
|
||||
) {
|
||||
toast.error(t(($) => $['modelProvider.embeddingModel.required'], { ns: 'common' }))
|
||||
toast.error(t(($) => $['modelProvider.embeddingModel.required'], { ns: 'modelProvider' }))
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
@@ -110,7 +110,7 @@ const ConfigParamModal: FC<Props> = ({
|
||||
</Item>
|
||||
|
||||
<Item
|
||||
title={t(($) => $['modelProvider.embeddingModel.key'], { ns: 'common' })}
|
||||
title={t(($) => $['modelProvider.embeddingModel.key'], { ns: 'modelProvider' })}
|
||||
tooltip={t(($) => $.embeddingModelSwitchTip, { ns: 'appAnnotation' })}
|
||||
>
|
||||
<div className="pt-1">
|
||||
|
||||
@@ -22,7 +22,7 @@ type Props = Readonly<{
|
||||
}>
|
||||
|
||||
const AnnotationReply = ({ disabled, onChange }: Props) => {
|
||||
const { t } = useTranslation(['appDebug', 'common'])
|
||||
const { t } = useTranslation(['appDebug', 'common', 'modelProvider'])
|
||||
const pathname = usePathname()
|
||||
const matched = /\/app\/([^/]+)/.exec(pathname)
|
||||
const appId = matched?.length && matched[1] ? matched[1] : ''
|
||||
@@ -112,7 +112,7 @@ const AnnotationReply = ({ disabled, onChange }: Props) => {
|
||||
<div className="h-6.75 w-px rotate-12 bg-divider-subtle"></div>
|
||||
<div className="">
|
||||
<div className="mb-0.5 system-2xs-medium-uppercase text-text-tertiary">
|
||||
{t(($) => $['modelProvider.embeddingModel.key'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.embeddingModel.key'], { ns: 'modelProvider' })}
|
||||
</div>
|
||||
<div className="system-xs-regular text-text-secondary">
|
||||
{annotationReply.embedding_model?.embedding_model_name}
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ type ModerationSettingModalProps = {
|
||||
}
|
||||
|
||||
const ModerationSettingModal: FC<ModerationSettingModalProps> = ({ data, onCancel, onSave }) => {
|
||||
const { t } = useTranslation(['appDebug', 'common'])
|
||||
const { t } = useTranslation(['appDebug', 'common', 'navigation'])
|
||||
const docLink = useDocLink()
|
||||
const locale = useLocale()
|
||||
const { data: modelProviders, isPending: isLoading } = useModelProviderDetails()
|
||||
@@ -361,7 +361,7 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({ data, onCance
|
||||
onClick={handleOpenSettingsModal}
|
||||
>
|
||||
|
||||
{t(($) => $['settings.provider'], { ns: 'common' })}
|
||||
{t(($) => $['settings.provider'], { ns: 'navigation' })}
|
||||
|
||||
</button>
|
||||
{t(($) => $['feature.moderation.modal.openaiNotConfig.after'], {
|
||||
|
||||
@@ -184,7 +184,7 @@ describe('DatasetAccessConfigPage', () => {
|
||||
render(<DatasetAccessConfigPage datasetId="dataset-1" />)
|
||||
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'common.settings.resourceAccess' }),
|
||||
screen.getByRole('heading', { name: 'navigation.settings.resourceAccess' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByTestId('access-rules-editor')).toBeInTheDocument()
|
||||
expect(useDatasetUserAccessSettings).toHaveBeenCalledWith(
|
||||
|
||||
@@ -30,7 +30,7 @@ type DatasetAccessConfigPageProps = {
|
||||
}
|
||||
|
||||
const DatasetAccessConfigPage = ({ datasetId }: DatasetAccessConfigPageProps) => {
|
||||
const { t } = useTranslation(['common', 'permission'])
|
||||
const { t } = useTranslation(['permission', 'navigation'])
|
||||
const locale = useLocale()
|
||||
const language = useMemo(() => getAccessControlTemplateLanguage(locale), [locale])
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
@@ -225,7 +225,7 @@ const DatasetAccessConfigPage = ({ datasetId }: DatasetAccessConfigPageProps) =>
|
||||
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-background-default-subtle">
|
||||
<header className="flex min-h-15.5 shrink-0 flex-col justify-center px-6 py-3">
|
||||
<h1 className="system-xl-semibold text-text-primary">
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'navigation' })}
|
||||
</h1>
|
||||
<p className="mt-0.5 system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['accessRule.datasetDescription'], { ns: 'permission' })}
|
||||
|
||||
@@ -146,7 +146,7 @@ describe('RetrievalParamConfig', () => {
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' }),
|
||||
screen.getByRole('switch', { name: 'modelProvider.modelProvider.rerankModel.key' }),
|
||||
).toBeChecked()
|
||||
})
|
||||
|
||||
@@ -213,7 +213,9 @@ describe('RetrievalParamConfig', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' }))
|
||||
fireEvent.click(
|
||||
screen.getByRole('switch', { name: 'modelProvider.modelProvider.rerankModel.key' }),
|
||||
)
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({
|
||||
...config,
|
||||
@@ -232,7 +234,9 @@ describe('RetrievalParamConfig', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' }))
|
||||
fireEvent.click(
|
||||
screen.getByRole('switch', { name: 'modelProvider.modelProvider.rerankModel.key' }),
|
||||
)
|
||||
|
||||
expect(mockNotify).toHaveBeenCalledWith('workflow.errorMsg.rerankModelRequired')
|
||||
})
|
||||
@@ -357,7 +361,7 @@ describe('RetrievalParamConfig', () => {
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' }),
|
||||
screen.getByRole('switch', { name: 'modelProvider.modelProvider.rerankModel.key' }),
|
||||
).toBeChecked()
|
||||
})
|
||||
|
||||
@@ -487,7 +491,7 @@ describe('RetrievalParamConfig', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.modelProvider.rerankModel.key'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.rerankModel.key'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show model selector when RerankingModel mode is selected', () => {
|
||||
@@ -557,7 +561,7 @@ describe('RetrievalParamConfig', () => {
|
||||
)
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('radio', { name: /common\.modelProvider\.rerankModel\.key/ }),
|
||||
screen.getByRole('radio', { name: /modelProvider\.modelProvider\.rerankModel\.key/ }),
|
||||
)
|
||||
|
||||
expect(mockOnChange).not.toHaveBeenCalled()
|
||||
@@ -589,7 +593,7 @@ describe('RetrievalParamConfig', () => {
|
||||
)
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('radio', { name: /common\.modelProvider\.rerankModel\.key/ }),
|
||||
screen.getByRole('radio', { name: /modelProvider\.modelProvider\.rerankModel\.key/ }),
|
||||
)
|
||||
|
||||
expect(mockNotify).toHaveBeenCalledWith('workflow.errorMsg.rerankModelRequired')
|
||||
@@ -781,7 +785,7 @@ describe('RetrievalParamConfig', () => {
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.modelProvider.rerankModel.key' }),
|
||||
screen.getByRole('button', { name: 'modelProvider.modelProvider.rerankModel.key' }),
|
||||
)!.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -797,7 +801,7 @@ describe('RetrievalParamConfig', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.modelProvider.rerankModel.key'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.rerankModel.key'))!.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -39,7 +39,13 @@ const RetrievalParamConfig: FC<Props> = ({
|
||||
disabled = false,
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation(['common', 'dataset', 'datasetSettings', 'workflow'])
|
||||
const { t } = useTranslation([
|
||||
'common',
|
||||
'dataset',
|
||||
'datasetSettings',
|
||||
'workflow',
|
||||
'modelProvider',
|
||||
])
|
||||
const rerankLabelId = useId()
|
||||
const canToggleRerankModalEnable = type !== RETRIEVE_METHOD.hybrid
|
||||
const isEconomical = type === RETRIEVE_METHOD.keywordSearch
|
||||
@@ -106,8 +112,8 @@ const RetrievalParamConfig: FC<Props> = ({
|
||||
},
|
||||
{
|
||||
value: RerankingModeEnum.RerankingModel,
|
||||
label: t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' }),
|
||||
tips: t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' }),
|
||||
label: t(($) => $['modelProvider.rerankModel.key'], { ns: 'modelProvider' }),
|
||||
tips: t(($) => $['modelProvider.rerankModel.tip'], { ns: 'modelProvider' }),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -127,12 +133,12 @@ const RetrievalParamConfig: FC<Props> = ({
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<span id={rerankLabelId} className="mr-0.5 system-sm-semibold text-text-secondary">
|
||||
{t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.rerankModel.key'], { ns: 'modelProvider' })}
|
||||
</span>
|
||||
<Infotip>
|
||||
<InfotipTrigger aria-labelledby={rerankLabelId} />
|
||||
<InfotipContent aria-labelledby={rerankLabelId} className="w-50">
|
||||
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'modelProvider' })}
|
||||
</InfotipContent>
|
||||
</Infotip>
|
||||
</div>
|
||||
@@ -221,7 +227,7 @@ const RetrievalParamConfig: FC<Props> = ({
|
||||
{isHybridSearch && (
|
||||
<>
|
||||
<RadioGroup<RerankingModeEnum>
|
||||
aria-label={t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' })}
|
||||
aria-label={t(($) => $['modelProvider.rerankModel.key'], { ns: 'modelProvider' })}
|
||||
value={value.reranking_mode}
|
||||
onValueChange={handleChangeRerankMode}
|
||||
className="mb-4 flex gap-2 @max-[28rem]/retrieval:flex-col"
|
||||
|
||||
@@ -7,9 +7,9 @@ import Header from './header'
|
||||
import List from './list'
|
||||
|
||||
const CreateFromPipeline = () => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['onboarding'])
|
||||
useDocumentTitle(
|
||||
t(($) => $['stepByStepTour.guides.knowledge.empty.pipeline.title'], { ns: 'common' }),
|
||||
t(($) => $['stepByStepTour.guides.knowledge.empty.pipeline.title'], { ns: 'onboarding' }),
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -57,7 +57,7 @@ const NON_TERMINAL_DISPLAY_STATUSES = new Set<(typeof DisplayStatusList)[number]
|
||||
const DocumentDetail: FC<DocumentDetailProps> = ({ datasetId, documentId }) => {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const { t } = useTranslation(['common', 'datasetDocuments'])
|
||||
const { t } = useTranslation(['common', 'datasetDocuments', 'navigation'])
|
||||
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
@@ -151,7 +151,7 @@ const DocumentDetail: FC<DocumentDetailProps> = ({ datasetId, documentId }) => {
|
||||
})
|
||||
const documentTitle =
|
||||
documentDetail?.name || t(($) => $['datasetMenus.documents'], { ns: 'common' })
|
||||
const datasetTitle = dataset?.name || t(($) => $['menus.datasets'], { ns: 'common' })
|
||||
const datasetTitle = dataset?.name || t(($) => $['menus.datasets'], { ns: 'navigation' })
|
||||
useDocumentTitle(`${documentTitle} · ${datasetTitle}`)
|
||||
|
||||
const { data: documentMetadata } = useDocumentMetadata({
|
||||
|
||||
@@ -38,7 +38,7 @@ type DocumentSettingsProps = {
|
||||
}
|
||||
|
||||
const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
|
||||
const { t } = useTranslation(['common', 'datasetCreation', 'datasetPipeline'])
|
||||
const { t } = useTranslation(['common', 'datasetCreation', 'datasetPipeline', 'navigation'])
|
||||
const router = useRouter()
|
||||
const [, setSettingsDestination] = useQueryState(settingsQueryParamName, settingsQueryParser)
|
||||
const { indexingTechnique, dataset } = useContext(DatasetDetailContext)
|
||||
@@ -65,7 +65,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
|
||||
const settingsTitle = t(($) => $['documentSettings.title'], { ns: 'datasetPipeline' })
|
||||
const documentTitle =
|
||||
documentDetail?.name || t(($) => $['datasetMenus.documents'], { ns: 'common' })
|
||||
const datasetTitle = dataset?.name || t(($) => $['menus.datasets'], { ns: 'common' })
|
||||
const datasetTitle = dataset?.name || t(($) => $['menus.datasets'], { ns: 'navigation' })
|
||||
useDocumentTitle(`${settingsTitle} · ${documentTitle} · ${datasetTitle}`)
|
||||
|
||||
const dataSourceInfo = documentDetail?.data_source_info
|
||||
|
||||
@@ -14,9 +14,9 @@ import { createExternalKnowledgeBase } from '@/service/datasets'
|
||||
const ExternalKnowledgeBaseConnector = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation(['common', 'dataset'])
|
||||
const { t } = useTranslation(['dataset', 'onboarding'])
|
||||
useDocumentTitle(
|
||||
t(($) => $['stepByStepTour.guides.knowledge.empty.connect.title'], { ns: 'common' }),
|
||||
t(($) => $['stepByStepTour.guides.knowledge.empty.connect.title'], { ns: 'onboarding' }),
|
||||
)
|
||||
|
||||
const handleConnect = async (formValue: CreateKnowledgeBaseReq) => {
|
||||
|
||||
+3
-3
@@ -133,7 +133,7 @@ describe('OperationsDropdown', () => {
|
||||
|
||||
fireEvent.click(screen.getByLabelText('Dataset operations'))
|
||||
|
||||
expect(screen.getByText('common.settings.resourceAccess')).toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.resourceAccess')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide resource access option when RBAC is disabled', () => {
|
||||
@@ -146,7 +146,7 @@ describe('OperationsDropdown', () => {
|
||||
fireEvent.click(screen.getByLabelText('Dataset operations'))
|
||||
|
||||
expect(screen.getByText('common.operation.delete')).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.resourceAccess')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.resourceAccess')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -291,7 +291,7 @@ describe('OperationsDropdown', () => {
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByLabelText('Dataset operations'))
|
||||
fireEvent.click(screen.getByText('common.settings.resourceAccess'))
|
||||
fireEvent.click(screen.getByText('navigation.settings.resourceAccess'))
|
||||
|
||||
expect(openAccessConfig).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ const Operations = ({
|
||||
openAccessConfig,
|
||||
onClose,
|
||||
}: OperationsProps) => {
|
||||
const { t } = useTranslation(['common', 'datasetPipeline'])
|
||||
const { t } = useTranslation(['common', 'datasetPipeline', 'navigation'])
|
||||
|
||||
const handleRename = () => {
|
||||
onClose?.()
|
||||
@@ -64,7 +64,7 @@ const Operations = ({
|
||||
{showAccessConfig && (
|
||||
<DropdownMenuItem onClick={handleAccessConfig}>
|
||||
<span aria-hidden className="mr-1 i-ri-lock-line size-4 text-text-tertiary" />
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'navigation' })}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{showDelete && (
|
||||
|
||||
@@ -577,7 +577,9 @@ describe('GotoAnything', () => {
|
||||
|
||||
await user.type(input, '/')
|
||||
|
||||
expect(screen.getByText('common.modelProvider.systemModelSettingsDesc')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('modelProvider.modelProvider.systemModelSettingsDesc'),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.queryByText('Fallback description')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export const accountCommand: SlashCommandHandler<AccountDeps> = {
|
||||
return [
|
||||
{
|
||||
id: 'account',
|
||||
title: i18n.t(($) => $['account.account'], { ns: 'common', lng: locale }),
|
||||
title: i18n.t(($) => $['account.account'], { ns: 'accountSettings', lng: locale }),
|
||||
description: i18n.t(($) => $['gotoAnything.actions.accountDesc'], {
|
||||
ns: 'app',
|
||||
lng: locale,
|
||||
|
||||
@@ -23,11 +23,11 @@ export const modelsCommand: SlashCommandHandler<ModelsDeps> = {
|
||||
{
|
||||
id: 'models',
|
||||
title: i18n.t(($) => $['modelProvider.systemModelSettings'], {
|
||||
ns: 'common',
|
||||
ns: 'modelProvider',
|
||||
lng: locale,
|
||||
}),
|
||||
description: i18n.t(($) => $['modelProvider.systemModelSettingsDesc'], {
|
||||
ns: 'common',
|
||||
ns: 'modelProvider',
|
||||
lng: locale,
|
||||
}),
|
||||
type: 'command' as const,
|
||||
|
||||
@@ -226,7 +226,7 @@ function chunkArray<T>(items: readonly T[], size: number): T[][] {
|
||||
}
|
||||
|
||||
export function GotoAnything() {
|
||||
const { t } = useTranslation(['agentV2', 'app', 'common', 'skill'])
|
||||
const { t } = useTranslation(['agentV2', 'app', 'common', 'skill', 'modelProvider'])
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const defaultLocale = useGetLanguage()
|
||||
@@ -446,7 +446,7 @@ export function GotoAnything() {
|
||||
|
||||
function getCommandOptionDescription(option: CommandOption) {
|
||||
if (option.shortcut === '/models')
|
||||
return t(($) => $['modelProvider.systemModelSettingsDesc'], { ns: 'common' })
|
||||
return t(($) => $['modelProvider.systemModelSettingsDesc'], { ns: 'modelProvider' })
|
||||
|
||||
const descriptionKey = isSlashMode
|
||||
? slashCommandDescriptionKeys[option.shortcut as keyof typeof slashCommandDescriptionKeys]
|
||||
|
||||
@@ -50,7 +50,7 @@ const userProfile = {
|
||||
email: 'current@example.com',
|
||||
avatar_url: 'current-avatar.png',
|
||||
}
|
||||
const accountMenuAccessibleName = `${userProfile.name} common.account.account`
|
||||
const accountMenuAccessibleName = `${userProfile.name} accountSettings.account.account`
|
||||
|
||||
const renderAccountDropdown = () => {
|
||||
const queryClient = createAccountProfileQueryClient(userProfile)
|
||||
@@ -130,30 +130,32 @@ describe('AccountDropdown', () => {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = html
|
||||
|
||||
expect(container.querySelector('button[aria-label="common.account.account"]')).toBeDisabled()
|
||||
expect(
|
||||
container.querySelector('button[aria-label="accountSettings.account.account"]'),
|
||||
).toBeDisabled()
|
||||
})
|
||||
|
||||
it('opens the main navigation account menu through the composed trigger', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAccountDropdown()
|
||||
|
||||
const trigger = screen.getByRole('button', { name: 'common.account.account' })
|
||||
const trigger = screen.getByRole('button', { name: 'accountSettings.account.account' })
|
||||
expect(trigger).not.toHaveAttribute('data-popup-open')
|
||||
|
||||
await user.click(trigger)
|
||||
|
||||
expect(await screen.findByText('current@example.com')).toBeInTheDocument()
|
||||
expect(trigger).toHaveAttribute('data-popup-open', '')
|
||||
expect(screen.getByText('common.settings.preferences')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.account.appearanceLabel')).toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.preferences')).toBeInTheDocument()
|
||||
expect(screen.getByText('accountSettings.account.appearanceLabel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('opens preferences from the account menu', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAccountDropdown()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'common.account.account' }))
|
||||
await user.click(await screen.findByText('common.settings.preferences'))
|
||||
await user.click(screen.getByRole('button', { name: 'accountSettings.account.account' }))
|
||||
await user.click(await screen.findByText('navigation.settings.preferences'))
|
||||
|
||||
expect(mockSetSettingsDestination).toHaveBeenCalledWith('preferences')
|
||||
})
|
||||
@@ -162,7 +164,7 @@ describe('AccountDropdown', () => {
|
||||
mockLogout.mockResolvedValue({})
|
||||
renderAccountDropdown()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.account.account' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'accountSettings.account.account' }))
|
||||
fireEvent.click(await screen.findByText('common.userProfile.logout'))
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function AccountDropdown({ trigger }: AccountDropdownProps) {
|
||||
getHydrationSnapshot,
|
||||
getServerHydrationSnapshot,
|
||||
)
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['accountSettings'])
|
||||
|
||||
const { mutateAsync: logout } = useLogout()
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function AccountDropdown({ trigger }: AccountDropdownProps) {
|
||||
<DropdownMenuTrigger
|
||||
disabled={isHydrating}
|
||||
render={trigger({
|
||||
ariaLabel: t(($) => $['account.account'], { ns: 'common' }),
|
||||
ariaLabel: t(($) => $['account.account'], { ns: 'accountSettings' }),
|
||||
})}
|
||||
/>
|
||||
<DropdownMenuContent
|
||||
|
||||
@@ -55,7 +55,7 @@ function MainNavRadioItemContent({ iconClassName, label }: MainNavRadioItemConte
|
||||
}
|
||||
|
||||
function AppearanceSubmenu() {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['accountSettings'])
|
||||
const { theme, setTheme } = useTheme()
|
||||
const currentTheme: Theme =
|
||||
theme === 'light' || theme === 'dark' || theme === 'system' ? theme : 'system'
|
||||
@@ -65,7 +65,7 @@ function AppearanceSubmenu() {
|
||||
<DropdownMenuSubTrigger className="mx-0 h-8 gap-1 px-3 py-1">
|
||||
<MenuItemContent
|
||||
iconClassName="i-ri-sun-line"
|
||||
label={t(($) => $['account.appearanceLabel'], { ns: 'common' })}
|
||||
label={t(($) => $['account.appearanceLabel'], { ns: 'accountSettings' })}
|
||||
/>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent
|
||||
@@ -84,7 +84,7 @@ function AppearanceSubmenu() {
|
||||
>
|
||||
<MainNavRadioItemContent
|
||||
iconClassName="i-ri-sun-line"
|
||||
label={t(($) => $['account.appearanceLight'], { ns: 'common' })}
|
||||
label={t(($) => $['account.appearanceLight'], { ns: 'accountSettings' })}
|
||||
/>
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem<Theme>
|
||||
@@ -94,7 +94,7 @@ function AppearanceSubmenu() {
|
||||
>
|
||||
<MainNavRadioItemContent
|
||||
iconClassName="i-ri-moon-line"
|
||||
label={t(($) => $['account.appearanceDark'], { ns: 'common' })}
|
||||
label={t(($) => $['account.appearanceDark'], { ns: 'accountSettings' })}
|
||||
/>
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem<Theme>
|
||||
@@ -104,7 +104,7 @@ function AppearanceSubmenu() {
|
||||
>
|
||||
<MainNavRadioItemContent
|
||||
iconClassName="i-ri-computer-line"
|
||||
label={t(($) => $['account.appearanceSystem'], { ns: 'common' })}
|
||||
label={t(($) => $['account.appearanceSystem'], { ns: 'accountSettings' })}
|
||||
/>
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
@@ -118,7 +118,7 @@ type MainNavMenuContentProps = {
|
||||
}
|
||||
|
||||
export function MainNavMenuContent({ onLogout }: MainNavMenuContentProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'accountSettings', 'navigation'])
|
||||
const { data: userProfile } = useSuspenseQuery({
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.profile,
|
||||
@@ -177,7 +177,7 @@ export function MainNavMenuContent({ onLogout }: MainNavMenuContentProps) {
|
||||
>
|
||||
<MenuItemContent
|
||||
iconClassName="i-ri-account-circle-line"
|
||||
label={t(($) => $['account.account'], { ns: 'common' })}
|
||||
label={t(($) => $['account.account'], { ns: 'accountSettings' })}
|
||||
trailing={<ExternalLinkIndicator />}
|
||||
/>
|
||||
</DropdownMenuLinkItem>
|
||||
@@ -187,7 +187,7 @@ export function MainNavMenuContent({ onLogout }: MainNavMenuContentProps) {
|
||||
>
|
||||
<MenuItemContent
|
||||
iconClassName="i-ri-equalizer-2-line"
|
||||
label={t(($) => $['settings.preferences'], { ns: 'common' })}
|
||||
label={t(($) => $['settings.preferences'], { ns: 'navigation' })}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
<AppearanceSubmenu />
|
||||
|
||||
@@ -224,27 +224,29 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('dialog', { name: 'common.settings.settings' })).toBeInTheDocument()
|
||||
expect(screen.getAllByText('common.settings.workspace').length).toBeGreaterThan(0)
|
||||
expect(screen.queryByText('common.settings.provider'))!.not.toBeInTheDocument()
|
||||
expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0)
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' }),
|
||||
screen.getByRole('dialog', { name: 'navigation.settings.settings' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getAllByText('navigation.settings.workspace').length).toBeGreaterThan(0)
|
||||
expect(screen.queryByText('navigation.settings.provider'))!.not.toBeInTheDocument()
|
||||
expect(screen.getAllByText('navigation.settings.members').length).toBeGreaterThan(0)
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'navigation.settings.rolesAndPermissions' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.settings.permissionSet' }),
|
||||
screen.getByRole('button', { name: 'navigation.settings.permissionSet' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.billing'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.billing'))!.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'appLog.archives.title' })).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.dataSource'))!.not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.customEndpoint'))!.not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.dataSource'))!.not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.customEndpoint'))!.not.toBeInTheDocument()
|
||||
expect(screen.getByText('custom.custom'))!.toBeInTheDocument()
|
||||
expect(
|
||||
screen
|
||||
.getByRole('button', { name: 'custom.custom' })
|
||||
.compareDocumentPosition(screen.getByRole('button', { name: 'appLog.archives.title' })),
|
||||
).toBe(Node.DOCUMENT_POSITION_FOLLOWING)
|
||||
expect(screen.getByText('common.settings.preferences'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.preferences'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide sidebar labels on mobile', () => {
|
||||
@@ -318,7 +320,7 @@ describe('AccountSetting', () => {
|
||||
// On mobile, the labels should not be rendered as per the implementation
|
||||
// Assert
|
||||
// On mobile, the labels should not be rendered as per the implementation
|
||||
expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.provider')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide billing from dataset operators', () => {
|
||||
@@ -333,20 +335,22 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' }),
|
||||
screen.getByRole('button', { name: 'navigation.settings.members' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.settings.permissionSet' }),
|
||||
screen.getByRole('button', { name: 'navigation.settings.rolesAndPermissions' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'common.settings.billing' }),
|
||||
screen.getByRole('button', { name: 'navigation.settings.permissionSet' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'navigation.settings.billing' }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'appLog.archives.title' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'custom.custom' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.settings.preferences' }),
|
||||
screen.getByRole('button', { name: 'navigation.settings.preferences' }),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -364,9 +368,9 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.dataSource')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.settings.customEndpoint')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.provider')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.dataSource')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.customEndpoint')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show custom tab when customization permission is missing', () => {
|
||||
@@ -383,11 +387,13 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByText('common.settings.provider')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.provider')).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'navigation.settings.members' }),
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'appLog.archives.title' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'custom.custom' })).toBeInTheDocument()
|
||||
expect(screen.getByText('common.settings.preferences'))!.toBeInTheDocument()
|
||||
expect(screen.getByText('navigation.settings.preferences'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide role and permission set entries when role management permission is missing', () => {
|
||||
@@ -404,12 +410,14 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'common.settings.rolesAndPermissions' }),
|
||||
screen.getByRole('button', { name: 'navigation.settings.members' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'navigation.settings.rolesAndPermissions' }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'common.settings.permissionSet' }),
|
||||
screen.queryByRole('button', { name: 'navigation.settings.permissionSet' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -418,12 +426,14 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting({ rbacEnabled: false })
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button', { name: 'common.settings.members' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'common.settings.rolesAndPermissions' }),
|
||||
screen.getByRole('button', { name: 'navigation.settings.members' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'navigation.settings.rolesAndPermissions' }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'common.settings.permissionSet' }),
|
||||
screen.queryByRole('button', { name: 'navigation.settings.permissionSet' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -434,7 +444,7 @@ describe('AccountSetting', () => {
|
||||
// Assert
|
||||
expect(screen.queryByTestId('access-rules-page')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('permissions-page')).not.toBeInTheDocument()
|
||||
expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('navigation.settings.members').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should hide billing and custom tabs when disabled', () => {
|
||||
@@ -446,7 +456,7 @@ describe('AccountSetting', () => {
|
||||
|
||||
// Assert
|
||||
// Assert
|
||||
expect(screen.queryByText('common.settings.billing')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('navigation.settings.billing')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('custom.custom')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -469,7 +479,9 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button', { name: 'common.settings.billing' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'navigation.settings.billing' }),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide workflow log archives outside cloud edition', () => {
|
||||
@@ -519,7 +531,7 @@ describe('AccountSetting', () => {
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByText('appLog.archives.upgradeTip.title')).not.toBeInTheDocument()
|
||||
expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('navigation.settings.members').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should render a direct billing entry for regular members without billing permission keys', () => {
|
||||
@@ -562,11 +574,11 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting({ onTabChange: mockOnTabChange })
|
||||
|
||||
// Act
|
||||
fireEvent.click(screen.getByText('common.settings.billing'))
|
||||
fireEvent.click(screen.getByText('navigation.settings.billing'))
|
||||
|
||||
// Assert
|
||||
expect(mockOnTabChange).toHaveBeenCalledWith(ACCOUNT_SETTING_TAB.BILLING)
|
||||
expect(screen.getAllByText('common.settings.billing').length).toBeGreaterThan(1)
|
||||
expect(screen.getAllByText('navigation.settings.billing').length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('should navigate through various tabs and show correct details', () => {
|
||||
@@ -574,10 +586,10 @@ describe('AccountSetting', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
// Billing
|
||||
fireEvent.click(screen.getByText('common.settings.billing'))
|
||||
fireEvent.click(screen.getByText('navigation.settings.billing'))
|
||||
// Billing Page renders plansCommon.plan if data is loaded, or generic text.
|
||||
// Checking for title in header which is always there
|
||||
expect(screen.getAllByText('common.settings.billing').length).toBeGreaterThan(1)
|
||||
expect(screen.getAllByText('navigation.settings.billing').length).toBeGreaterThan(1)
|
||||
|
||||
// Custom
|
||||
fireEvent.click(screen.getByText('custom.custom'))
|
||||
@@ -589,28 +601,32 @@ describe('AccountSetting', () => {
|
||||
expect(screen.getByText('appLog.archives.upgradeTip.title')).toBeInTheDocument()
|
||||
|
||||
// Members
|
||||
fireEvent.click(screen.getAllByText('common.settings.members')[0]!)
|
||||
expect(screen.getAllByText('common.settings.members').length).toBeGreaterThan(1)
|
||||
fireEvent.click(screen.getAllByText('navigation.settings.members')[0]!)
|
||||
expect(screen.getAllByText('navigation.settings.members').length).toBeGreaterThan(1)
|
||||
|
||||
// Roles & Permissions
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.settings.rolesAndPermissions' }))
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: 'navigation.settings.rolesAndPermissions' }),
|
||||
)
|
||||
expect(screen.getByTestId('permissions-page')).toBeInTheDocument()
|
||||
|
||||
// Permission Set
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.settings.permissionSet' }))
|
||||
expect(screen.getByText('common.settings.permissionSetDescription')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'navigation.settings.permissionSet' }))
|
||||
expect(screen.getByText('navigation.settings.permissionSetDescription')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('access-rules-page')).toBeInTheDocument()
|
||||
|
||||
// Language
|
||||
fireEvent.click(screen.getByText('common.settings.preferences'))
|
||||
expect(screen.getByText('common.account.general')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.account.appearanceLabel')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByText('navigation.settings.preferences'))
|
||||
expect(screen.getByText('accountSettings.account.general')).toBeInTheDocument()
|
||||
expect(screen.getByText('accountSettings.account.appearanceLabel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should switch the preferences icon when the tab is active', () => {
|
||||
renderAccountSetting()
|
||||
|
||||
const preferencesButton = screen.getByRole('button', { name: 'common.settings.preferences' })
|
||||
const preferencesButton = screen.getByRole('button', {
|
||||
name: 'navigation.settings.preferences',
|
||||
})
|
||||
expect(preferencesButton.querySelector('.i-ri-equalizer-2-line')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(preferencesButton)
|
||||
@@ -623,7 +639,7 @@ describe('AccountSetting', () => {
|
||||
it('should call onCancel when clicking close button', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAccountSetting()
|
||||
const dialog = screen.getByRole('dialog', { name: 'common.settings.settings' })
|
||||
const dialog = screen.getByRole('dialog', { name: 'navigation.settings.settings' })
|
||||
|
||||
await user.click(within(dialog).getByRole('button', { name: 'common.operation.close' }))
|
||||
|
||||
|
||||
+4
-4
@@ -192,10 +192,10 @@ describe('WorkspaceRoleCheckboxList', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.members.adminTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.members.editorTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.members.normalTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.members.datasetOperatorTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('workspaceMembers.members.adminTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('workspaceMembers.members.editorTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('workspaceMembers.members.normalTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('workspaceMembers.members.datasetOperatorTip')).toBeInTheDocument()
|
||||
expect(screen.queryByText('permission.role.noDescription')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
+13
-7
@@ -242,7 +242,9 @@ describe('DataSourcePage Component', () => {
|
||||
expect(screen.queryByText('Dify Source')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('common.dataSourcePage.notSetUpTitle')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.dataSourcePage.installFirst')).toBeInTheDocument()
|
||||
expect(screen.queryByText('common.modelProvider.installDataSource')).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('modelProvider.modelProvider.installDataSource'),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show data source placeholders while the list is loading', () => {
|
||||
@@ -260,7 +262,9 @@ describe('DataSourcePage Component', () => {
|
||||
// Assert
|
||||
expect(screen.getByRole('status', { name: 'common.loading' })).toBeInTheDocument()
|
||||
expect(screen.queryByText('dataSourcePage.notSetUpTitle')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('common.modelProvider.installDataSource')).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('modelProvider.modelProvider.installDataSource'),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -342,8 +346,8 @@ describe('DataSourcePage Component', () => {
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('common.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.modelProvider.discoverMore')).toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.discoverMore')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass an empty array to InstallFromMarketplace if data result is missing but marketplace is enabled', () => {
|
||||
@@ -358,7 +362,7 @@ describe('DataSourcePage Component', () => {
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('common.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle the case where data exists but result is an empty array', () => {
|
||||
@@ -374,7 +378,7 @@ describe('DataSourcePage Component', () => {
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByText('Dify Source')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('common.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle the case where enable_marketplace is false (edge case for coverage)', () => {
|
||||
@@ -389,7 +393,9 @@ describe('DataSourcePage Component', () => {
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByText('common.modelProvider.installDataSource')).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('modelProvider.modelProvider.installDataSource'),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
+4
-4
@@ -127,8 +127,8 @@ describe('InstallFromMarketplace Component', () => {
|
||||
render(<InstallFromMarketplace providers={mockProviders} searchText="" />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('common.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.modelProvider.discoverMore')).toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.installDataSource')).toBeInTheDocument()
|
||||
expect(screen.getByText('modelProvider.modelProvider.discoverMore')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('mock-link')).toHaveAttribute(
|
||||
'href',
|
||||
'https://marketplace.url/plugins/datasource?theme=light',
|
||||
@@ -164,7 +164,7 @@ describe('InstallFromMarketplace Component', () => {
|
||||
isLoading: false,
|
||||
})
|
||||
render(<InstallFromMarketplace providers={mockProviders} searchText="" />)
|
||||
const toggleHeader = screen.getByText('common.modelProvider.installDataSource')
|
||||
const toggleHeader = screen.getByText('modelProvider.modelProvider.installDataSource')
|
||||
|
||||
// Act (Collapse)
|
||||
fireEvent.click(toggleHeader)
|
||||
@@ -184,7 +184,7 @@ describe('InstallFromMarketplace Component', () => {
|
||||
isLoading: true,
|
||||
})
|
||||
render(<InstallFromMarketplace providers={mockProviders} searchText="" />)
|
||||
const toggleHeader = screen.getByText('common.modelProvider.installDataSource')
|
||||
const toggleHeader = screen.getByText('modelProvider.modelProvider.installDataSource')
|
||||
|
||||
// Act (Collapse)
|
||||
fireEvent.click(toggleHeader)
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ const InstallFromMarketplace = ({
|
||||
providers,
|
||||
searchText,
|
||||
}: InstallFromMarketplaceProps) => {
|
||||
const { t } = useTranslation(['common', 'plugin'])
|
||||
const { t } = useTranslation(['plugin', 'modelProvider'])
|
||||
const { theme } = useTheme()
|
||||
const { canInstallPlugin } = usePluginSettingsAccess()
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
@@ -53,11 +53,11 @@ const InstallFromMarketplace = ({
|
||||
className={cn('i-ri-arrow-down-s-line size-4', collapse && '-rotate-90')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{t(($) => $['modelProvider.installDataSource'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.installDataSource'], { ns: 'modelProvider' })}
|
||||
</button>
|
||||
<div className="mb-2 flex items-center pt-2">
|
||||
<span className="pr-1 system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['modelProvider.discoverMore'], { ns: 'common' })}
|
||||
{t(($) => $['modelProvider.discoverMore'], { ns: 'modelProvider' })}
|
||||
</span>
|
||||
{onOpenMarketplace ? (
|
||||
<button
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function AccountSetting({
|
||||
activeTab,
|
||||
onTabChangeAction,
|
||||
}: IAccountSettingProps) {
|
||||
const { t } = useTranslation(['appLog', 'billing', 'common', 'custom'])
|
||||
const { t } = useTranslation(['appLog', 'billing', 'custom', 'navigation', 'accountSettings'])
|
||||
const { data: enableReplaceWebAppLogo } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.can_replace_logo,
|
||||
@@ -91,26 +91,26 @@ export default function AccountSetting({
|
||||
const settingItems: GroupItem[] = [
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.MEMBERS,
|
||||
name: t(($) => $['settings.members'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.members'], { ns: 'navigation' }),
|
||||
icon: <span className={cn('i-ri-group-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-group-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS,
|
||||
name: t(($) => $['settings.rolesAndPermissions'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.rolesAndPermissions'], { ns: 'navigation' }),
|
||||
icon: <span className={cn('i-ri-shield-user-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-shield-user-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.PERMISSION_SET,
|
||||
name: t(($) => $['settings.permissionSet'], { ns: 'common' }),
|
||||
description: t(($) => $['settings.permissionSetDescription'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.permissionSet'], { ns: 'navigation' }),
|
||||
description: t(($) => $['settings.permissionSetDescription'], { ns: 'navigation' }),
|
||||
icon: <span className={cn('i-ri-lock-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-lock-2-fill', iconClassName)} />,
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.BILLING,
|
||||
name: t(($) => $['settings.billing'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.billing'], { ns: 'navigation' }),
|
||||
description: t(($) => $['plansCommon.receiptInfo'], { ns: 'billing' }),
|
||||
icon: <span className={cn('i-ri-money-dollar-circle-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-money-dollar-circle-fill', iconClassName)} />,
|
||||
@@ -130,8 +130,8 @@ export default function AccountSetting({
|
||||
},
|
||||
{
|
||||
key: ACCOUNT_SETTING_TAB.PREFERENCES,
|
||||
name: t(($) => $['settings.preferences'], { ns: 'common' }),
|
||||
title: t(($) => $['account.general'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.preferences'], { ns: 'navigation' }),
|
||||
title: t(($) => $['account.general'], { ns: 'accountSettings' }),
|
||||
icon: <span className={cn('i-ri-equalizer-2-line', iconClassName)} />,
|
||||
activeIcon: <span className={cn('i-ri-equalizer-2-fill', iconClassName)} />,
|
||||
},
|
||||
@@ -167,7 +167,7 @@ export default function AccountSetting({
|
||||
const menuItems = [
|
||||
{
|
||||
key: 'workspace-group',
|
||||
name: t(($) => $['settings.workspace'], { ns: 'common' }),
|
||||
name: t(($) => $['settings.workspace'], { ns: 'navigation' }),
|
||||
items: visibleSettingItems,
|
||||
},
|
||||
{
|
||||
@@ -177,11 +177,14 @@ export default function AccountSetting({
|
||||
]
|
||||
|
||||
return (
|
||||
<MenuDialog title={t(($) => $['settings.settings'], { ns: 'common' })} onClose={onCancelAction}>
|
||||
<MenuDialog
|
||||
title={t(($) => $['settings.settings'], { ns: 'navigation' })}
|
||||
onClose={onCancelAction}
|
||||
>
|
||||
<div className="mx-auto flex h-screen w-full max-w-270 px-4">
|
||||
<div className="flex w-11 shrink-0 flex-col pr-6 pl-4 sm:w-56">
|
||||
<div className="mt-6 mb-8 flex h-9.5 items-center px-3 title-2xl-semi-bold whitespace-nowrap text-text-primary">
|
||||
{t(($) => $['settings.settings'], { ns: 'common' })}
|
||||
{t(($) => $['settings.settings'], { ns: 'navigation' })}
|
||||
</div>
|
||||
<div className="w-full">
|
||||
{menuItems.map((menuItem) => (
|
||||
|
||||
@@ -264,13 +264,13 @@ describe('MembersPage', () => {
|
||||
|
||||
const table = screen.getByRole('table')
|
||||
expect(
|
||||
within(table).getByRole('columnheader', { name: 'common.members.name' }),
|
||||
within(table).getByRole('columnheader', { name: 'workspaceMembers.members.name' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
within(table).getByRole('columnheader', { name: 'common.members.lastActive' }),
|
||||
within(table).getByRole('columnheader', { name: 'workspaceMembers.members.lastActive' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
within(table).getByRole('columnheader', { name: 'common.members.role' }),
|
||||
within(table).getByRole('columnheader', { name: 'workspaceMembers.members.role' }),
|
||||
).toBeInTheDocument()
|
||||
const row = within(table).getByRole('row', { name: /owner@example.com/ })
|
||||
expect(within(row).getByRole('cell', { name: 'just now' })).toBeInTheDocument()
|
||||
@@ -290,9 +290,13 @@ describe('MembersPage', () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(screen.getByRole('columnheader', { name: 'common.members.roles' })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('common.members.role', { selector: '.system-xs-medium-uppercase' }),
|
||||
screen.getByRole('columnheader', { name: 'workspaceMembers.members.roles' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('workspaceMembers.members.role', {
|
||||
selector: '.system-xs-medium-uppercase',
|
||||
}),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
+10
-6
@@ -114,14 +114,18 @@ describe('MemberMenu', () => {
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /members\.memberActions/i }))
|
||||
|
||||
expect(screen.getByRole('menuitem', { name: /common\.members\.editRole/i })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: /common\.members\.assignRoles/i }),
|
||||
screen.getByRole('menuitem', { name: /workspaceMembers\.members\.editRole/i }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: /workspaceMembers\.members\.assignRoles/i }),
|
||||
).not.toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('menuitem', { name: /common\.members\.editRole/i }))
|
||||
await user.click(screen.getByRole('menuitem', { name: /workspaceMembers\.members\.editRole/i }))
|
||||
|
||||
expect(screen.getByRole('dialog', { name: /common\.members\.editRole/i })).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('dialog', { name: /workspaceMembers\.members\.editRole/i }),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should submit only one selected role from the assign modal when RBAC is disabled', async () => {
|
||||
@@ -164,9 +168,9 @@ describe('MemberMenu', () => {
|
||||
await user.click(screen.getByRole('menuitem', { name: /members\.removeFromTeam/i }))
|
||||
|
||||
const dialog = screen.getByRole('alertdialog', {
|
||||
name: /common\.members\.removeFromTeamConfirmTitle:\{"memberName":"Member User"\}/i,
|
||||
name: /workspaceMembers\.members\.removeFromTeamConfirmTitle:\{"memberName":"Member User"\}/i,
|
||||
})
|
||||
expect(dialog).toHaveTextContent('common.members.removeFromTeamConfirmDescription')
|
||||
expect(dialog).toHaveTextContent('workspaceMembers.members.removeFromTeamConfirmDescription')
|
||||
expect(deleteMemberOrCancelInvitation).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(within(dialog).getByRole('button', { name: /common\.operation\.confirm/i }))
|
||||
|
||||
+7
-3
@@ -90,16 +90,20 @@ describe('MemberRow details entry points', () => {
|
||||
const user = userEvent.setup()
|
||||
const onOpenDetails = renderMemberRow()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /common\.members\.memberActions/ }))
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /workspaceMembers\.members\.memberActions/ }),
|
||||
)
|
||||
expect(screen.getByRole('menu')).toBeInTheDocument()
|
||||
expect(onOpenDetails).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(screen.getByRole('menuitem', { name: 'common.members.removeFromTeam' }))
|
||||
await user.click(
|
||||
screen.getByRole('menuitem', { name: 'workspaceMembers.members.removeFromTeam' }),
|
||||
)
|
||||
const confirmation = screen.getByRole('alertdialog')
|
||||
expect(onOpenDetails).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(
|
||||
within(confirmation).getByText('common.members.removeFromTeamConfirmDescription'),
|
||||
within(confirmation).getByText('workspaceMembers.members.removeFromTeamConfirmDescription'),
|
||||
)
|
||||
await user.click(within(confirmation).getByRole('button', { name: 'common.operation.cancel' }))
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
|
||||
+3
-3
@@ -62,7 +62,7 @@ describe('AssignRolesModal', () => {
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.queryByText(/common\.members\.assignRolesModal\.selectedCount/i),
|
||||
screen.queryByText(/workspaceMembers\.members\.assignRolesModal\.selectedCount/i),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -77,10 +77,10 @@ describe('AssignRolesModal', () => {
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.getByText(/common\.members\.assignRolesModal\.singleDescription/i),
|
||||
screen.getByText(/workspaceMembers\.members\.assignRolesModal\.singleDescription/i),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText(/common\.members\.assignRolesModal\.description/i),
|
||||
screen.queryByText(/workspaceMembers\.members\.assignRolesModal\.description/i),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@@ -28,21 +28,24 @@ const AssignRolesModalBody = ({
|
||||
onClose,
|
||||
onSubmit,
|
||||
}: AssignRolesModalBodyProps) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'workspaceMembers'])
|
||||
const [selected, setSelected] = useState(selectedRoles)
|
||||
const selectedRoleIds = selected.map((role) => role.id)
|
||||
const isConfirmDisabled = selected.length === 0
|
||||
const title = allowMultipleRoles
|
||||
? t(($) => $['members.assignRolesModal.title'], { ns: 'common', defaultValue: 'Assign Roles' })
|
||||
: t(($) => $['members.editRole'], { ns: 'common', defaultValue: 'Edit Role' })
|
||||
? t(($) => $['members.assignRolesModal.title'], {
|
||||
ns: 'workspaceMembers',
|
||||
defaultValue: 'Assign Roles',
|
||||
})
|
||||
: t(($) => $['members.editRole'], { ns: 'workspaceMembers', defaultValue: 'Edit Role' })
|
||||
const description = allowMultipleRoles
|
||||
? t(($) => $['members.assignRolesModal.description'], {
|
||||
ns: 'common',
|
||||
ns: 'workspaceMembers',
|
||||
defaultValue:
|
||||
'Select roles to assign to this member. All permissions from selected roles will be combined.',
|
||||
})
|
||||
: t(($) => $['members.assignRolesModal.singleDescription'], {
|
||||
ns: 'common',
|
||||
ns: 'workspaceMembers',
|
||||
defaultValue: 'Select one role to assign to this member.',
|
||||
})
|
||||
|
||||
@@ -89,7 +92,7 @@ const AssignRolesModalBody = ({
|
||||
{allowMultipleRoles && (
|
||||
<div className="system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['members.assignRolesModal.selectedCount'], {
|
||||
ns: 'common',
|
||||
ns: 'workspaceMembers',
|
||||
count: selected.length,
|
||||
})}
|
||||
</div>
|
||||
|
||||
+7
-5
@@ -15,7 +15,7 @@ type IEditWorkspaceModalProps = {
|
||||
onCancel: () => void
|
||||
}
|
||||
const EditWorkspaceModal = ({ onCancel }: IEditWorkspaceModalProps) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'accountSettings'])
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||
const [name, setName] = useState<string>(currentWorkspace.name)
|
||||
@@ -31,7 +31,7 @@ const EditWorkspaceModal = ({ onCancel }: IEditWorkspaceModalProps) => {
|
||||
if (!hasError) return ''
|
||||
return t(($) => $['errorMsg.fieldRequired'], {
|
||||
ns: 'common',
|
||||
field: t(($) => $['account.workspaceName'], { ns: 'common' }),
|
||||
field: t(($) => $['account.workspaceName'], { ns: 'accountSettings' }),
|
||||
})
|
||||
}, [hasError, t])
|
||||
const changeWorkspaceInfo = async () => {
|
||||
@@ -81,18 +81,20 @@ const EditWorkspaceModal = ({ onCancel }: IEditWorkspaceModalProps) => {
|
||||
>
|
||||
<div className="mb-4 pr-8">
|
||||
<DialogTitle className="text-xl font-semibold text-text-primary">
|
||||
{t(($) => $['account.editWorkspaceInfo'], { ns: 'common' })}
|
||||
{t(($) => $['account.editWorkspaceInfo'], { ns: 'accountSettings' })}
|
||||
</DialogTitle>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label htmlFor={inputId} className="block text-sm font-medium text-text-primary">
|
||||
{t(($) => $['account.workspaceName'], { ns: 'common' })}
|
||||
{t(($) => $['account.workspaceName'], { ns: 'accountSettings' })}
|
||||
</label>
|
||||
<Input
|
||||
id={inputId}
|
||||
value={name}
|
||||
placeholder={t(($) => $['account.workspaceNamePlaceholder'], { ns: 'common' })}
|
||||
placeholder={t(($) => $['account.workspaceNamePlaceholder'], {
|
||||
ns: 'accountSettings',
|
||||
})}
|
||||
onChange={(e) => {
|
||||
setName(e.target.value)
|
||||
}}
|
||||
|
||||
@@ -29,7 +29,7 @@ import MemberRow from './member-row'
|
||||
import TransferOwnershipModal from './transfer-ownership-modal'
|
||||
|
||||
const MembersPage = () => {
|
||||
const { t } = useTranslation(['billing', 'common'])
|
||||
const { t } = useTranslation(['billing', 'common', 'workspaceMembers', 'accountSettings'])
|
||||
const locale = useLocale()
|
||||
const language = getAccessControlTemplateLanguage(locale)
|
||||
|
||||
@@ -71,8 +71,8 @@ const MembersPage = () => {
|
||||
|
||||
const canManageMembers = hasPermission(workspacePermissionKeys, 'workspace.member.manage')
|
||||
const roleColumnLabel = systemFeatures.rbac_enabled
|
||||
? t(($) => $['members.roles'], { ns: 'common' })
|
||||
: t(($) => $['members.role'], { ns: 'common' })
|
||||
? t(($) => $['members.roles'], { ns: 'workspaceMembers' })
|
||||
: t(($) => $['members.role'], { ns: 'workspaceMembers' })
|
||||
|
||||
const handleOpenDetails = useCallback((member: Member) => {
|
||||
setDetailsMember(member)
|
||||
@@ -122,7 +122,9 @@ const MembersPage = () => {
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t(($) => $['account.editWorkspaceInfo'], { ns: 'common' })}
|
||||
aria-label={t(($) => $['account.editWorkspaceInfo'], {
|
||||
ns: 'accountSettings',
|
||||
})}
|
||||
className="cursor-pointer rounded-md border-none bg-transparent p-1 hover:bg-black/5"
|
||||
onClick={() => {
|
||||
setEditWorkspaceModalVisible(true)
|
||||
@@ -136,7 +138,7 @@ const MembersPage = () => {
|
||||
}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t(($) => $['account.editWorkspaceInfo'], { ns: 'common' })}
|
||||
{t(($) => $['account.editWorkspaceInfo'], { ns: 'accountSettings' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</span>
|
||||
@@ -192,10 +194,10 @@ const MembersPage = () => {
|
||||
<thead>
|
||||
<tr className="border-b border-divider-regular">
|
||||
<th className="px-3 py-1.75 text-left system-xs-medium-uppercase text-text-tertiary">
|
||||
{t(($) => $['members.name'], { ns: 'common' })}
|
||||
{t(($) => $['members.name'], { ns: 'workspaceMembers' })}
|
||||
</th>
|
||||
<th className="py-1.75 text-left system-xs-medium-uppercase text-text-tertiary">
|
||||
{t(($) => $['members.lastActive'], { ns: 'common' })}
|
||||
{t(($) => $['members.lastActive'], { ns: 'workspaceMembers' })}
|
||||
</th>
|
||||
<th className="px-3 py-1.75 text-left system-xs-medium-uppercase text-text-tertiary">
|
||||
{roleColumnLabel}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useWorkspacePermissions } from '@/service/use-workspace'
|
||||
type InviteButtonProps = Omit<ButtonProps, 'children' | 'variant'>
|
||||
|
||||
const InviteButton = (props: InviteButtonProps) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['workspaceMembers'])
|
||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const { data: workspacePermissions, isFetching: isFetchingWorkspacePermissions } =
|
||||
@@ -27,7 +27,7 @@ const InviteButton = (props: InviteButtonProps) => {
|
||||
return (
|
||||
<Button {...props} variant="primary">
|
||||
<span aria-hidden="true" className="i-ri-user-add-line size-4" />
|
||||
{t(($) => $['members.invite'], { ns: 'common' })}
|
||||
{t(($) => $['members.invite'], { ns: 'workspaceMembers' })}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
+4
-2
@@ -109,9 +109,11 @@ describe('RoleSelector', () => {
|
||||
|
||||
await user.click(getTrigger())
|
||||
|
||||
expect(within(getListbox()).getByText(/common\.members\.adminTip/i)).toBeInTheDocument()
|
||||
expect(
|
||||
within(getListbox()).getByText(/common\.members\.datasetOperatorTip/i),
|
||||
within(getListbox()).getByText(/workspaceMembers\.members\.adminTip/i),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
within(getListbox()).getByText(/workspaceMembers\.members\.datasetOperatorTip/i),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
+13
-10
@@ -30,7 +30,7 @@ export function EmailRecipientsField({
|
||||
onChange,
|
||||
disabled = false,
|
||||
}: EmailRecipientsFieldProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'workspaceMembers'])
|
||||
const internalInputRef = useRef<HTMLInputElement>(null)
|
||||
const chipButtonRef = useRef<Array<HTMLButtonElement | null>>([])
|
||||
const selectDraftOnRenderRef = useRef(false)
|
||||
@@ -42,17 +42,18 @@ export function EmailRecipientsField({
|
||||
)
|
||||
const fieldError =
|
||||
hasInvalidRecipient || hasInvalidDraft
|
||||
? t(($) => $['members.emailInvalid'], { ns: 'common' })
|
||||
? t(($) => $['members.emailInvalid'], { ns: 'workspaceMembers' })
|
||||
: null
|
||||
|
||||
const validateRecipients = (value: unknown) => {
|
||||
const nextDraft = typeof value === 'string' ? value : draft
|
||||
const nextRecipients = mergeEmailRecipients(recipients, nextDraft)
|
||||
|
||||
if (nextRecipients.length === 0) return t(($) => $['members.emailRequired'], { ns: 'common' })
|
||||
if (nextRecipients.length === 0)
|
||||
return t(($) => $['members.emailRequired'], { ns: 'workspaceMembers' })
|
||||
|
||||
if (nextRecipients.some(({ isValid }) => !isValid))
|
||||
return t(($) => $['members.emailInvalid'], { ns: 'common' })
|
||||
return t(($) => $['members.emailInvalid'], { ns: 'workspaceMembers' })
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -158,11 +159,13 @@ export function EmailRecipientsField({
|
||||
return (
|
||||
<Field name="emails" invalid={Boolean(fieldError)} validate={validateRecipients}>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>{t(($) => $['members.emailRecipients'], { ns: 'common' })}</FieldLabel>
|
||||
<FieldLabel>
|
||||
{t(($) => $['members.emailRecipients'], { ns: 'workspaceMembers' })}
|
||||
</FieldLabel>
|
||||
{recipients.length > 0 && (
|
||||
<span aria-live="polite" className="py-1 body-xs-regular text-text-tertiary tabular-nums">
|
||||
{t(($) => $['members.recipientCount'], {
|
||||
ns: 'common',
|
||||
ns: 'workspaceMembers',
|
||||
count: recipients.length,
|
||||
})}
|
||||
</span>
|
||||
@@ -180,7 +183,7 @@ export function EmailRecipientsField({
|
||||
>
|
||||
{recipients.length > 0 && (
|
||||
<ul
|
||||
aria-label={t(($) => $['members.emailRecipients'], { ns: 'common' })}
|
||||
aria-label={t(($) => $['members.emailRecipients'], { ns: 'workspaceMembers' })}
|
||||
className="contents"
|
||||
>
|
||||
{recipients.map(({ value, isValid }, index) => {
|
||||
@@ -236,7 +239,7 @@ export function EmailRecipientsField({
|
||||
</button>
|
||||
{!isValid && (
|
||||
<span id={errorId} className="sr-only">
|
||||
{t(($) => $['members.emailInvalid'], { ns: 'common' })}
|
||||
{t(($) => $['members.emailInvalid'], { ns: 'workspaceMembers' })}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
@@ -253,7 +256,7 @@ export function EmailRecipientsField({
|
||||
inputMode="email"
|
||||
placeholder={
|
||||
recipients.length === 0
|
||||
? t(($) => $['members.emailPlaceholder'], { ns: 'common' }) || ''
|
||||
? t(($) => $['members.emailPlaceholder'], { ns: 'workspaceMembers' }) || ''
|
||||
: ''
|
||||
}
|
||||
value={draft}
|
||||
@@ -312,7 +315,7 @@ export function EmailRecipientsField({
|
||||
<>
|
||||
<FieldError />
|
||||
<FieldDescription className="group-data-invalid/field:hidden">
|
||||
{t(($) => $['members.emailRecipientsTip'], { ns: 'common' })}
|
||||
{t(($) => $['members.emailRecipientsTip'], { ns: 'workspaceMembers' })}
|
||||
</FieldDescription>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -47,7 +47,7 @@ type SubmissionError =
|
||||
type InviteFormProps = Omit<InviteModalProps, 'open' | 'trigger'>
|
||||
|
||||
function InviteForm({ isEmailSetup, onOpenChange, onSend }: InviteFormProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['workspaceMembers'])
|
||||
const locale = useLocale()
|
||||
const queryClient = useQueryClient()
|
||||
const deploymentEdition = useAtomValue(deploymentEditionAtom)
|
||||
@@ -105,20 +105,20 @@ function InviteForm({ isEmailSetup, onOpenChange, onSend }: InviteFormProps) {
|
||||
setSubmissionError({
|
||||
kind: 'fields',
|
||||
errors: {
|
||||
emails: t(($) => $['members.inviteLimitExceeded'], { ns: 'common' }),
|
||||
emails: t(($) => $['members.inviteLimitExceeded'], { ns: 'workspaceMembers' }),
|
||||
},
|
||||
})
|
||||
break
|
||||
case 'invalid_role':
|
||||
setSubmissionError({
|
||||
kind: 'fields',
|
||||
errors: { role: t(($) => $['members.invalidRole'], { ns: 'common' }) },
|
||||
errors: { role: t(($) => $['members.invalidRole'], { ns: 'workspaceMembers' }) },
|
||||
})
|
||||
break
|
||||
default:
|
||||
setSubmissionError({
|
||||
kind: 'form',
|
||||
message: t(($) => $['members.inviteFailed'], { ns: 'common' }),
|
||||
message: t(($) => $['members.inviteFailed'], { ns: 'workspaceMembers' }),
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -128,7 +128,7 @@ function InviteForm({ isEmailSetup, onOpenChange, onSend }: InviteFormProps) {
|
||||
|
||||
return (
|
||||
<Form<InviteFormValues>
|
||||
aria-label={t(($) => $['members.inviteTeamMember'], { ns: 'common' })}
|
||||
aria-label={t(($) => $['members.inviteTeamMember'], { ns: 'workspaceMembers' })}
|
||||
errors={fieldErrors}
|
||||
className="grid gap-5 pt-5"
|
||||
onFormSubmit={handleSubmit}
|
||||
@@ -137,7 +137,7 @@ function InviteForm({ isEmailSetup, onOpenChange, onSend }: InviteFormProps) {
|
||||
<div className="flex items-start gap-1.5 rounded-lg bg-state-warning-hover p-2 text-text-warning">
|
||||
<span aria-hidden="true" className="i-ri-error-warning-fill size-4 shrink-0" />
|
||||
<span className="system-xs-medium text-text-primary">
|
||||
{t(($) => $['members.emailNotSetup'], { ns: 'common' })}
|
||||
{t(($) => $['members.emailNotSetup'], { ns: 'workspaceMembers' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -158,11 +158,11 @@ function InviteForm({ isEmailSetup, onOpenChange, onSend }: InviteFormProps) {
|
||||
<span aria-hidden="true" className="i-ri-error-warning-line size-4 shrink-0" />
|
||||
<span>
|
||||
{t(($) => $['members.seatsRemaining'], {
|
||||
ns: 'common',
|
||||
ns: 'workspaceMembers',
|
||||
count: remainingSeats,
|
||||
})}
|
||||
<span aria-hidden="true"> · </span>
|
||||
{t(($) => $['members.recipientCountExceedsSeats'], { ns: 'common' })}
|
||||
{t(($) => $['members.recipientCountExceedsSeats'], { ns: 'workspaceMembers' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -174,10 +174,10 @@ function InviteForm({ isEmailSetup, onOpenChange, onSend }: InviteFormProps) {
|
||||
<Button type="submit" variant="primary" className="w-full" loading={isPending}>
|
||||
{validRecipientCount > 0
|
||||
? t(($) => $['members.sendInviteCount'], {
|
||||
ns: 'common',
|
||||
ns: 'workspaceMembers',
|
||||
count: validRecipientCount,
|
||||
})
|
||||
: t(($) => $['members.sendInvite'], { ns: 'common' })}
|
||||
: t(($) => $['members.sendInvite'], { ns: 'workspaceMembers' })}
|
||||
</Button>
|
||||
</Form>
|
||||
)
|
||||
@@ -190,7 +190,7 @@ export function InviteModal({
|
||||
onOpenChange,
|
||||
onSend,
|
||||
}: InviteModalProps) {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'workspaceMembers'])
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(nextOpen) => onOpenChange(nextOpen)}>
|
||||
@@ -198,10 +198,10 @@ export function InviteModal({
|
||||
<DialogContent backdropProps={{ forceRender: true }}>
|
||||
<div className="grid gap-1 pr-8">
|
||||
<DialogTitle className="text-xl font-semibold text-text-primary">
|
||||
{t(($) => $['members.inviteTeamMember'], { ns: 'common' })}
|
||||
{t(($) => $['members.inviteTeamMember'], { ns: 'workspaceMembers' })}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm text-text-tertiary">
|
||||
{t(($) => $['members.inviteTeamMemberTip'], { ns: 'common' })}
|
||||
{t(($) => $['members.inviteTeamMemberTip'], { ns: 'workspaceMembers' })}
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<InviteForm isEmailSetup={isEmailSetup} onOpenChange={onOpenChange} onSend={onSend} />
|
||||
|
||||
+10
-8
@@ -50,7 +50,7 @@ function getLegacyRoleDescriptionKey(role: Role) {
|
||||
}
|
||||
|
||||
export function RoleSelector({ hasServerError = false, disabled = false }: RoleSelectorProps) {
|
||||
const { t } = useTranslation(['common', 'permission'])
|
||||
const { t } = useTranslation(['common', 'permission', 'workspaceMembers'])
|
||||
const locale = useLocale()
|
||||
const [open, setOpen] = useState(false)
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
@@ -128,13 +128,13 @@ export function RoleSelector({ hasServerError = false, disabled = false }: RoleS
|
||||
|
||||
switch (getLegacyRoleDescriptionKey(role)) {
|
||||
case 'admin':
|
||||
return t(($) => $['members.adminTip'], { ns: 'common' })
|
||||
return t(($) => $['members.adminTip'], { ns: 'workspaceMembers' })
|
||||
case 'editor':
|
||||
return t(($) => $['members.editorTip'], { ns: 'common' })
|
||||
return t(($) => $['members.editorTip'], { ns: 'workspaceMembers' })
|
||||
case 'normal':
|
||||
return t(($) => $['members.normalTip'], { ns: 'common' })
|
||||
return t(($) => $['members.normalTip'], { ns: 'workspaceMembers' })
|
||||
case 'dataset_operator':
|
||||
return t(($) => $['members.datasetOperatorTip'], { ns: 'common' })
|
||||
return t(($) => $['members.datasetOperatorTip'], { ns: 'workspaceMembers' })
|
||||
}
|
||||
|
||||
return t(($) => $['role.noDescription'], { ns: 'permission' })
|
||||
@@ -152,9 +152,11 @@ export function RoleSelector({ hasServerError = false, disabled = false }: RoleS
|
||||
itemToStringValue={(role) => role.id}
|
||||
isItemEqualToValue={(role, selectedRole) => role.id === selectedRole.id}
|
||||
>
|
||||
<SelectLabel>{t(($) => $['members.role'], { ns: 'common' })}</SelectLabel>
|
||||
<SelectLabel>{t(($) => $['members.role'], { ns: 'workspaceMembers' })}</SelectLabel>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t(($) => $['members.selectRole'], { ns: 'common' })} />
|
||||
<SelectValue
|
||||
placeholder={t(($) => $['members.selectRole'], { ns: 'workspaceMembers' })}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectPortal>
|
||||
<SelectPositioner>
|
||||
@@ -199,7 +201,7 @@ export function RoleSelector({ hasServerError = false, disabled = false }: RoleS
|
||||
<FieldError />
|
||||
) : (
|
||||
<FieldError match="valueMissing">
|
||||
{t(($) => $['members.selectRole'], { ns: 'common' })}
|
||||
{t(($) => $['members.selectRole'], { ns: 'workspaceMembers' })}
|
||||
</FieldError>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -18,7 +18,7 @@ type IInvitedModalProps = {
|
||||
onCancel: () => void
|
||||
}
|
||||
const InvitedModal = ({ invitationResults, onCancel }: IInvitedModalProps) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const { t } = useTranslation(['common', 'workspaceMembers'])
|
||||
const { data: deploymentEdition } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: ({ deployment_edition }) => deployment_edition,
|
||||
@@ -41,7 +41,7 @@ const InvitedModal = ({ invitationResults, onCancel }: IInvitedModalProps) => {
|
||||
failedInvitationResults.length === 0
|
||||
const description = t(
|
||||
($) => $[onlyAlreadyMembers ? 'members.alreadyInTeamTip' : 'members.invitationSentTip'],
|
||||
{ ns: 'common' },
|
||||
{ ns: 'workspaceMembers' },
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -72,7 +72,7 @@ const InvitedModal = ({ invitationResults, onCancel }: IInvitedModalProps) => {
|
||||
{t(
|
||||
($) =>
|
||||
$[onlyAlreadyMembers ? 'members.noNewInvitationsSent' : 'members.invitationSent'],
|
||||
{ ns: 'common' },
|
||||
{ ns: 'workspaceMembers' },
|
||||
)}
|
||||
</DialogTitle>
|
||||
{isCloudEdition && <div className="mb-5 text-sm text-text-tertiary">{description}</div>}
|
||||
@@ -85,7 +85,7 @@ const InvitedModal = ({ invitationResults, onCancel }: IInvitedModalProps) => {
|
||||
{isNonCloudEdition && !!successInvitationResults.length && (
|
||||
<>
|
||||
<div className="py-2 text-sm font-medium text-text-primary">
|
||||
{t(($) => $['members.invitationLink'], { ns: 'common' })}
|
||||
{t(($) => $['members.invitationLink'], { ns: 'workspaceMembers' })}
|
||||
</div>
|
||||
{successInvitationResults.map((item) => (
|
||||
<InvitationLink key={item.email} value={item} />
|
||||
@@ -95,11 +95,11 @@ const InvitedModal = ({ invitationResults, onCancel }: IInvitedModalProps) => {
|
||||
{!!alreadyMemberInvitationResults.length && (
|
||||
<>
|
||||
<div className="py-2 text-sm font-medium text-text-primary">
|
||||
{t(($) => $['members.alreadyInTeam'], { ns: 'common' })}
|
||||
{t(($) => $['members.alreadyInTeam'], { ns: 'workspaceMembers' })}
|
||||
</div>
|
||||
{!onlyAlreadyMembers && (
|
||||
<div className="text-sm text-text-tertiary">
|
||||
{t(($) => $['members.alreadyInTeamTip'], { ns: 'common' })}
|
||||
{t(($) => $['members.alreadyInTeamTip'], { ns: 'workspaceMembers' })}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap justify-between gap-y-1">
|
||||
@@ -117,7 +117,7 @@ const InvitedModal = ({ invitationResults, onCancel }: IInvitedModalProps) => {
|
||||
{isNonCloudEdition && !!failedInvitationResults.length && (
|
||||
<>
|
||||
<div className="py-2 text-sm font-medium text-text-primary">
|
||||
{t(($) => $['members.failedInvitationEmails'], { ns: 'common' })}
|
||||
{t(($) => $['members.failedInvitationEmails'], { ns: 'workspaceMembers' })}
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-between gap-y-1">
|
||||
{failedInvitationResults.map((item) => (
|
||||
@@ -146,7 +146,7 @@ const InvitedModal = ({ invitationResults, onCancel }: IInvitedModalProps) => {
|
||||
)}
|
||||
<div className="flex justify-end">
|
||||
<Button className="w-24" onClick={onCancel} variant="primary">
|
||||
{t(($) => $['members.ok'], { ns: 'common' })}
|
||||
{t(($) => $['members.ok'], { ns: 'workspaceMembers' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
Loaded 100 of 446 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user