perf(web): split shared translation namespaces by feature (#42874)

This commit is contained in:
Stephen Zhou authored and GitHub committed 2026-09-24 04:24:20 +00:00
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
@@ -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>
+1 -1
View File
@@ -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() {
@@ -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
}
+1 -1
View File
@@ -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
@@ -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}')
@@ -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' }))
@@ -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} />
+3 -3
View File
@@ -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" />
+2 -2
View File
@@ -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>
</>
}
@@ -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 (
@@ -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')
@@ -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)
@@ -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),
),
@@ -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>
@@ -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>
}
@@ -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>
)}
+2 -2
View File
@@ -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>
}
@@ -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', () => {
@@ -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}
@@ -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}
>
&nbsp;
{t(($) => $['settings.provider'], { ns: 'common' })}
{t(($) => $['settings.provider'], { ns: 'navigation' })}
&nbsp;
</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) => {
@@ -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,
+2 -2
View File
@@ -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' }))
@@ -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()
})
})
@@ -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()
})
})
})
@@ -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)
@@ -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()
})
@@ -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 }))
@@ -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()
@@ -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>
@@ -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>
)
}
@@ -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()
})
@@ -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} />
@@ -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