refactor(web): align main nav review feedback

- move main nav active edge styling into Tailwind classes

- split account dropdown menu content into focused components

- align frontend review skill rules with i18n and styling guidance

- add missing common i18n keys across supported locales
This commit is contained in:
Jingyi-Dify 2026-05-06 21:43:48 -07:00
parent 646fd90716
commit cacfb7e544
32 changed files with 1031 additions and 515 deletions

View File

@ -19,7 +19,7 @@ See [references/code-quality.md](references/code-quality.md), [references/perfor
Flag each rule violation with urgency metadata so future reviewers can prioritize fixes.
## Review Process
1. Open the relevant component/module. Gather lines that relate to class names, React Flow hooks, prop memoization, and styling.
1. Open the relevant component/module. Gather lines that relate to class names, styling/CSS imports, file size and component boundaries, i18n keys, behavior-sensitive UI interactions, React Flow hooks, and prop memoization.
2. For each rule in the review point, note where the code deviates and capture a representative snippet.
3. Compose the review section per the template below. Group violations first by **Urgent** flag, then by category order (Code Quality, Performance, Business Logic).
@ -70,4 +70,3 @@ If you use Template A (i.e., there are issues to fix) and at least one issue req
## Code review
No issues found.
```

View File

@ -13,3 +13,29 @@ Node components are also used when creating a RAG Pipe from a template, but in t
### Suggested Fix
Use `import { useNodes } from 'reactflow'` instead of `import useNodes from '@/app/components/workflow/store/workflow/use-nodes'`.
## Locale keys must be complete
IsUrgent: True
Category: Business Logic
### Description
When adding or changing user-facing i18n keys, ensure every supported locale file has the same key set as `web/i18n/en-US/`. Do not add only English keys or only a partial subset of locales; `pnpm i18n:check --file <name>` should pass for the touched translation file.
### Suggested Fix
Add matching keys to every existing supported locale file for the touched translation namespace, keeping key paths aligned with the English entry.
## Preserve behavior-sensitive interactions
IsUrgent: True
Category: Business Logic
### Description
When changing existing navigation, sidebar, dropdown, webapp list, or app-switching UI, compare behavior against the existing implementation before approving the change. Watch for regressions in expand/collapse arrows, hover persistence, pin/delete controls, routing, keyboard/focus handling, and open-state ownership.
### Suggested Fix
Reuse or extend the existing component when it already owns the interaction logic. If a refactor is needed, preserve the old interaction contract and add or update focused tests for the changed behavior.

View File

@ -7,12 +7,12 @@ Category: Code Quality
### Description
Ensure conditional CSS is handled via the shared `classNames` instead of custom ternaries, string concatenation, or template strings. Centralizing class logic keeps components consistent and easier to maintain.
Ensure conditional CSS and multi-line class composition are handled via the shared `cn` helper instead of custom ternaries, string concatenation, array `.join(' ')`, or template strings. Centralizing class logic keeps components consistent and easier to maintain.
### Suggested Fix
```ts
import { cn } from '@/utils/classnames'
import { cn } from '@langgenius/dify-ui/cn'
const classNames = cn(isActive ? 'text-primary-600' : 'text-gray-500')
```
@ -25,6 +25,23 @@ Category: Code Quality
Favor Tailwind CSS utility classes instead of adding new `.module.css` files unless a Tailwind combination cannot achieve the required styling. Keeping styles in Tailwind improves consistency and reduces maintenance overhead.
## CSS files must be scoped
IsUrgent: True
Category: Code Quality
### Description
When CSS is truly necessary, use component-scoped `*.module.css`. Do not add component-level CSS through plain `.css` files, and do not import component CSS from `globals.css`; both patterns risk style leakage across the app.
## Split oversized components cautiously
Category: Code Quality
### Description
When a frontend file grows large or mixes multiple responsibilities, suggest splitting it into focused components, hooks, or utilities. Prefer shallow local structure that matches existing repo patterns, such as a sibling `components/` folder, and avoid deep folder hierarchies unless the surrounding code already uses them.
Update this file when adding, editing, or removing Code Quality rules so the catalog remains accurate.
## Classname ordering for easy overrides
@ -36,7 +53,7 @@ When writing components, always place the incoming `className` prop after the co
Example:
```tsx
import { cn } from '@/utils/classnames'
import { cn } from '@langgenius/dify-ui/cn'
const Button = ({ className }) => {
return <div className={cn('bg-primary-600', className)}></div>

View File

@ -0,0 +1,233 @@
'use client'
import type { MouseEventHandler, ReactNode } from 'react'
import { Avatar } from '@langgenius/dify-ui/avatar'
import {
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLinkItem,
DropdownMenuSeparator,
} from '@langgenius/dify-ui/dropdown-menu'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import PremiumBadge from '@/app/components/base/premium-badge'
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { IS_CLOUD_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useDocLink } from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { env } from '@/env'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import GithubStar from '../github-star'
import Indicator from '../indicator'
import Compliance from './compliance'
import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content'
import Support from './support'
type AccountMenuRouteItemProps = {
href: string
iconClassName: string
label: ReactNode
trailing?: ReactNode
}
function AccountMenuRouteItem({
href,
iconClassName,
label,
trailing,
}: AccountMenuRouteItemProps) {
return (
<DropdownMenuLinkItem
className="justify-between"
render={<Link href={href} />}
>
<MenuItemContent iconClassName={iconClassName} label={label} trailing={trailing} />
</DropdownMenuLinkItem>
)
}
type AccountMenuExternalItemProps = {
href: string
iconClassName: string
label: ReactNode
trailing?: ReactNode
}
function AccountMenuExternalItem({
href,
iconClassName,
label,
trailing,
}: AccountMenuExternalItemProps) {
return (
<DropdownMenuLinkItem
className="justify-between"
href={href}
rel="noopener noreferrer"
target="_blank"
>
<MenuItemContent iconClassName={iconClassName} label={label} trailing={trailing} />
</DropdownMenuLinkItem>
)
}
type AccountMenuActionItemProps = {
iconClassName: string
label: ReactNode
onClick?: MouseEventHandler<HTMLElement>
trailing?: ReactNode
}
function AccountMenuActionItem({
iconClassName,
label,
onClick,
trailing,
}: AccountMenuActionItemProps) {
return (
<DropdownMenuItem
className="justify-between"
onClick={onClick}
>
<MenuItemContent iconClassName={iconClassName} label={label} trailing={trailing} />
</DropdownMenuItem>
)
}
type AccountMenuSectionProps = {
children: ReactNode
}
function AccountMenuSection({ children }: AccountMenuSectionProps) {
return <DropdownMenuGroup className="py-1">{children}</DropdownMenuGroup>
}
type DefaultMenuContentProps = {
closeAccountDropdown: () => void
onShowAbout: () => void
onLogout: () => Promise<void>
}
export function DefaultMenuContent({
closeAccountDropdown,
onShowAbout,
onLogout,
}: DefaultMenuContentProps) {
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
const { t } = useTranslation()
const docLink = useDocLink()
const { userProfile, langGeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
const { isEducationAccount } = useProviderContext()
const { setShowAccountSettingModal } = useModalContext()
return (
<>
<DropdownMenuGroup className="py-1">
<div className="mx-1 flex flex-nowrap items-center py-2 pr-2 pl-3">
<div className="grow">
<div className="system-md-medium break-all text-text-primary">
{userProfile.name}
{isEducationAccount && (
<PremiumBadge size="s" color="blue" className="ml-1 px-2!">
<span aria-hidden className="mr-1 i-ri-graduation-cap-fill h-3 w-3" />
<span className="system-2xs-medium">EDU</span>
</PremiumBadge>
)}
</div>
<div className="system-xs-regular break-all text-text-tertiary">{userProfile.email}</div>
</div>
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size="lg" />
</div>
<AccountMenuRouteItem
href="/account"
iconClassName="i-ri-account-circle-line"
label={t('account.account', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
<AccountMenuActionItem
iconClassName="i-ri-settings-3-line"
label={t('userProfile.settings', { ns: 'common' })}
onClick={() => setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.MEMBERS })}
/>
</DropdownMenuGroup>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
{!systemFeatures.branding.enabled && (
<>
<AccountMenuSection>
<AccountMenuExternalItem
href={docLink('/use-dify/getting-started/introduction')}
iconClassName="i-ri-book-open-line"
label={t('userProfile.helpCenter', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
<Support closeAccountDropdown={closeAccountDropdown} />
{IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />}
</AccountMenuSection>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
<AccountMenuSection>
<AccountMenuExternalItem
href="https://roadmap.dify.ai"
iconClassName="i-ri-map-2-line"
label={t('userProfile.roadmap', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
<AccountMenuExternalItem
href="https://github.com/langgenius/dify"
iconClassName="i-ri-github-line"
label={t('userProfile.github', { ns: 'common' })}
trailing={(
<div className="flex items-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px]">
<span aria-hidden className="i-ri-star-line size-3 shrink-0 text-text-tertiary" />
<GithubStar className="system-2xs-medium-uppercase text-text-tertiary" />
</div>
)}
/>
{env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && (
<AccountMenuActionItem
iconClassName="i-ri-information-2-line"
label={t('userProfile.about', { ns: 'common' })}
onClick={() => {
onShowAbout()
closeAccountDropdown()
}}
trailing={(
<div className="flex shrink-0 items-center">
<div className="mr-2 system-xs-regular text-text-tertiary">{langGeniusVersionInfo.current_version}</div>
<Indicator color={langGeniusVersionInfo.current_version === langGeniusVersionInfo.latest_version ? 'green' : 'orange'} />
</div>
)}
/>
)}
</AccountMenuSection>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
</>
)}
<AccountMenuSection>
<DropdownMenuItem
closeOnClick={false}
className="cursor-default data-highlighted:bg-transparent"
>
<MenuItemContent
iconClassName="i-ri-t-shirt-2-line"
label={t('theme.theme', { ns: 'common' })}
trailing={<ThemeSwitcher />}
/>
</DropdownMenuItem>
</AccountMenuSection>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
<AccountMenuSection>
<AccountMenuActionItem
iconClassName="i-ri-logout-box-r-line"
label={t('userProfile.logout', { ns: 'common' })}
onClick={() => {
void onLogout()
}}
/>
</AccountMenuSection>
</>
)
}

View File

@ -1,133 +1,22 @@
'use client'
import type { MouseEventHandler, ReactElement, ReactNode } from 'react'
import type { Theme } from '@/app/components/base/theme-selector'
import type { Locale } from '@/i18n-config'
import type { ReactElement, ReactNode } from 'react'
import { Avatar } from '@langgenius/dify-ui/avatar'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLinkItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuRadioItemIndicator,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { toast } from '@langgenius/dify-ui/toast'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useTheme } from 'next-themes'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { resetUser } from '@/app/components/base/amplitude/utils'
import PremiumBadge from '@/app/components/base/premium-badge'
import ThemeSwitcher from '@/app/components/base/theme-switcher'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { IS_CLOUD_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useDocLink, useLocale } from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { env } from '@/env'
import { setLocaleOnClient } from '@/i18n-config'
import { languages } from '@/i18n-config/language'
import Link from '@/next/link'
import { useRouter } from '@/next/navigation'
import { updateUserProfile } from '@/service/common'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useLogout } from '@/service/use-common'
import { timezones } from '@/utils/timezone'
import AccountAbout from '../account-about'
import GithubStar from '../github-star'
import Indicator from '../indicator'
import Compliance from './compliance'
import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content'
import Support from './support'
type AccountMenuRouteItemProps = {
href: string
iconClassName: string
label: ReactNode
trailing?: ReactNode
}
function AccountMenuRouteItem({
href,
iconClassName,
label,
trailing,
}: AccountMenuRouteItemProps) {
return (
<DropdownMenuLinkItem
className="justify-between"
render={<Link href={href} />}
>
<MenuItemContent iconClassName={iconClassName} label={label} trailing={trailing} />
</DropdownMenuLinkItem>
)
}
type AccountMenuExternalItemProps = {
href: string
iconClassName: string
label: ReactNode
trailing?: ReactNode
}
function AccountMenuExternalItem({
href,
iconClassName,
label,
trailing,
}: AccountMenuExternalItemProps) {
return (
<DropdownMenuLinkItem
className="justify-between"
href={href}
rel="noopener noreferrer"
target="_blank"
>
<MenuItemContent iconClassName={iconClassName} label={label} trailing={trailing} />
</DropdownMenuLinkItem>
)
}
type AccountMenuActionItemProps = {
iconClassName: string
label: ReactNode
onClick?: MouseEventHandler<HTMLElement>
trailing?: ReactNode
}
function AccountMenuActionItem({
iconClassName,
label,
onClick,
trailing,
}: AccountMenuActionItemProps) {
return (
<DropdownMenuItem
className="justify-between"
onClick={onClick}
>
<MenuItemContent iconClassName={iconClassName} label={label} trailing={trailing} />
</DropdownMenuItem>
)
}
type AccountMenuSectionProps = {
children: ReactNode
}
function AccountMenuSection({ children }: AccountMenuSectionProps) {
return <DropdownMenuGroup className="py-1">{children}</DropdownMenuGroup>
}
import { DefaultMenuContent } from './default-menu-content'
import { MainNavMenuContent } from './main-nav-menu-content'
type AccountDropdownProps = {
trigger?: (props: {
@ -139,159 +28,6 @@ type AccountDropdownProps = {
}
const mainNavMenuPopupClassName = 'w-60 max-w-80 overflow-hidden bg-components-panel-bg-blur! p-0! backdrop-blur-[5px]'
const mainNavMenuGroupClassName = 'p-1'
const mainNavMenuItemClassName = 'mx-0 h-8 gap-1 px-3 py-1'
const mainNavMenuSubPopupClassName = 'w-60 max-h-[360px] bg-components-panel-bg-blur! p-1! backdrop-blur-[5px]'
function MainNavRadioItemContent({
iconClassName,
label,
}: {
iconClassName?: string
label: ReactNode
}) {
return (
<>
{iconClassName && <span aria-hidden className={cn('size-4 shrink-0 text-text-tertiary', iconClassName)} />}
<span className="min-w-0 grow truncate px-1 system-md-regular text-text-secondary">{label}</span>
<DropdownMenuRadioItemIndicator />
</>
)
}
function AppearanceSubmenu() {
const { t } = useTranslation()
const { theme, setTheme } = useTheme()
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger className={mainNavMenuItemClassName}>
<MenuItemContent
iconClassName="i-ri-sun-line"
label={t('account.appearanceLabel', { ns: 'common' })}
/>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
placement="right-start"
sideOffset={6}
popupClassName={mainNavMenuSubPopupClassName}
>
<DropdownMenuRadioGroup value={theme || 'system'} onValueChange={value => setTheme(value as Theme)}>
<DropdownMenuRadioItem value="light" closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent iconClassName="i-ri-sun-line" label={t('account.appearanceLight', { ns: 'common' })} />
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark" closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent iconClassName="i-ri-moon-line" label={t('account.appearanceDark', { ns: 'common' })} />
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system" closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent iconClassName="i-ri-computer-line" label={t('account.appearanceSystem', { ns: 'common' })} />
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
)
}
function LanguageSubmenu() {
const locale = useLocale()
const router = useRouter()
const { t } = useTranslation()
const { userProfile, mutateUserProfile } = useAppContext()
const [editing, setEditing] = useState(false)
const languageOptions = languages.filter(item => item.supported)
const selectedLanguage = locale || userProfile.interface_language
const selectedTimezone = userProfile.timezone
const handleSelectLanguage = async (value: string) => {
setEditing(true)
try {
await updateUserProfile({ url: '/account/interface-language', body: { interface_language: value } })
toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' }))
await setLocaleOnClient(value as Locale, false)
router.refresh()
}
catch (error) {
toast.error((error as Error).message)
}
finally {
setEditing(false)
}
}
const handleSelectTimezone = async (value: string) => {
setEditing(true)
try {
await updateUserProfile({ url: '/account/timezone', body: { timezone: value } })
toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' }))
mutateUserProfile()
}
catch (error) {
toast.error((error as Error).message)
}
finally {
setEditing(false)
}
}
return (
<>
<DropdownMenuSub>
<DropdownMenuSubTrigger className={mainNavMenuItemClassName}>
<MenuItemContent
iconClassName="i-ri-translate-2"
label={t('language.language', { ns: 'common' })}
/>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
placement="right-start"
sideOffset={6}
popupClassName={mainNavMenuSubPopupClassName}
>
<DropdownMenuRadioGroup
value={selectedLanguage}
onValueChange={(value) => {
if (!editing)
void handleSelectLanguage(value)
}}
>
{languageOptions.map(item => (
<DropdownMenuRadioItem key={item.value} value={item.value} closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent label={item.name} />
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger className={mainNavMenuItemClassName}>
<MenuItemContent
iconClassName="i-ri-global-line"
label={t('language.timezone', { ns: 'common' })}
/>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
placement="right-start"
sideOffset={6}
popupClassName={mainNavMenuSubPopupClassName}
>
<DropdownMenuRadioGroup
value={selectedTimezone}
onValueChange={(value) => {
if (!editing)
void handleSelectTimezone(value)
}}
>
{timezones.map(item => (
<DropdownMenuRadioItem key={item.value} value={String(item.value)} closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent label={item.name} />
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</>
)
}
export default function AppSelector({
mainNavBadge,
@ -301,15 +37,10 @@ export default function AppSelector({
const router = useRouter()
const [aboutVisible, setAboutVisible] = useState(false)
const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false)
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
const { t } = useTranslation()
const docLink = useDocLink()
const { userProfile, langGeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
const { isEducationAccount } = useProviderContext()
const { setShowAccountSettingModal } = useModalContext()
const { userProfile, langGeniusVersionInfo } = useAppContext()
const { mutateAsync: logout } = useLogout()
const handleLogout = async () => {
await logout()
resetUser()
@ -351,163 +82,17 @@ export default function AppSelector({
popupClassName={variant === 'mainNav' ? mainNavMenuPopupClassName : 'w-60 max-w-80 bg-components-panel-bg-blur! py-0! backdrop-blur-xs'}
>
{variant === 'mainNav'
? (
<>
<DropdownMenuGroup className={mainNavMenuGroupClassName}>
<div className="flex items-center gap-1 rounded-xl bg-gradient-to-b from-background-section-burn to-background-section p-3">
<div className="flex min-w-0 grow flex-col gap-1">
<div className="flex min-w-0 items-center gap-1">
<div className="truncate body-md-medium text-text-primary">{userProfile.name}</div>
{mainNavBadge}
</div>
<div className="truncate system-xs-regular text-text-tertiary">{userProfile.email}</div>
</div>
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size="lg" />
</div>
</DropdownMenuGroup>
<DropdownMenuGroup className={mainNavMenuGroupClassName}>
<DropdownMenuLinkItem
className={cn('justify-between', mainNavMenuItemClassName)}
render={<Link href="/account" />}
>
<MenuItemContent
iconClassName="i-ri-account-circle-line"
label={t('account.account', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
</DropdownMenuLinkItem>
<AppearanceSubmenu />
<LanguageSubmenu />
</DropdownMenuGroup>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
<DropdownMenuGroup className={mainNavMenuGroupClassName}>
<DropdownMenuItem
className={mainNavMenuItemClassName}
onClick={() => {
void handleLogout()
}}
>
<MenuItemContent
iconClassName="i-ri-logout-box-r-line"
label={t('userProfile.logout', { ns: 'common' })}
/>
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)
? <MainNavMenuContent mainNavBadge={mainNavBadge} onLogout={handleLogout} />
: (
<>
<DropdownMenuGroup className="py-1">
<div className="mx-1 flex flex-nowrap items-center py-2 pr-2 pl-3">
<div className="grow">
<div className="system-md-medium break-all text-text-primary">
{userProfile.name}
{isEducationAccount && (
<PremiumBadge size="s" color="blue" className="ml-1 px-2!">
<span aria-hidden className="mr-1 i-ri-graduation-cap-fill h-3 w-3" />
<span className="system-2xs-medium">EDU</span>
</PremiumBadge>
)}
</div>
<div className="system-xs-regular break-all text-text-tertiary">{userProfile.email}</div>
</div>
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size="lg" />
</div>
<AccountMenuRouteItem
href="/account"
iconClassName="i-ri-account-circle-line"
label={t('account.account', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
<AccountMenuActionItem
iconClassName="i-ri-settings-3-line"
label={t('userProfile.settings', { ns: 'common' })}
onClick={() => setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.MEMBERS })}
/>
</DropdownMenuGroup>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
{!systemFeatures.branding.enabled && (
<>
<AccountMenuSection>
<AccountMenuExternalItem
href={docLink('/use-dify/getting-started/introduction')}
iconClassName="i-ri-book-open-line"
label={t('userProfile.helpCenter', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
<Support closeAccountDropdown={() => setIsAccountMenuOpen(false)} />
{IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />}
</AccountMenuSection>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
<AccountMenuSection>
<AccountMenuExternalItem
href="https://roadmap.dify.ai"
iconClassName="i-ri-map-2-line"
label={t('userProfile.roadmap', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
<AccountMenuExternalItem
href="https://github.com/langgenius/dify"
iconClassName="i-ri-github-line"
label={t('userProfile.github', { ns: 'common' })}
trailing={(
<div className="flex items-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px]">
<span aria-hidden className="i-ri-star-line size-3 shrink-0 text-text-tertiary" />
<GithubStar className="system-2xs-medium-uppercase text-text-tertiary" />
</div>
)}
/>
{
env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && (
<AccountMenuActionItem
iconClassName="i-ri-information-2-line"
label={t('userProfile.about', { ns: 'common' })}
onClick={() => {
setAboutVisible(true)
setIsAccountMenuOpen(false)
}}
trailing={(
<div className="flex shrink-0 items-center">
<div className="mr-2 system-xs-regular text-text-tertiary">{langGeniusVersionInfo.current_version}</div>
<Indicator color={langGeniusVersionInfo.current_version === langGeniusVersionInfo.latest_version ? 'green' : 'orange'} />
</div>
)}
/>
)
}
</AccountMenuSection>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
</>
)}
<AccountMenuSection>
<DropdownMenuItem
closeOnClick={false}
className="cursor-default data-highlighted:bg-transparent"
>
<MenuItemContent
iconClassName="i-ri-t-shirt-2-line"
label={t('theme.theme', { ns: 'common' })}
trailing={<ThemeSwitcher />}
/>
</DropdownMenuItem>
</AccountMenuSection>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
<AccountMenuSection>
<AccountMenuActionItem
iconClassName="i-ri-logout-box-r-line"
label={t('userProfile.logout', { ns: 'common' })}
onClick={() => {
void handleLogout()
}}
/>
</AccountMenuSection>
</>
<DefaultMenuContent
closeAccountDropdown={() => setIsAccountMenuOpen(false)}
onShowAbout={() => setAboutVisible(true)}
onLogout={handleLogout}
/>
)}
</DropdownMenuContent>
</DropdownMenu>
{
aboutVisible && <AccountAbout onCancel={() => setAboutVisible(false)} langGeniusVersionInfo={langGeniusVersionInfo} />
}
{aboutVisible && <AccountAbout onCancel={() => setAboutVisible(false)} langGeniusVersionInfo={langGeniusVersionInfo} />}
</div>
)
}

View File

@ -0,0 +1,246 @@
'use client'
import type { ReactNode } from 'react'
import type { Theme } from '@/app/components/base/theme-selector'
import type { Locale } from '@/i18n-config'
import { Avatar } from '@langgenius/dify-ui/avatar'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLinkItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuRadioItemIndicator,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { toast } from '@langgenius/dify-ui/toast'
import { useTheme } from 'next-themes'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useAppContext } from '@/context/app-context'
import { useLocale } from '@/context/i18n'
import { setLocaleOnClient } from '@/i18n-config'
import { languages } from '@/i18n-config/language'
import Link from '@/next/link'
import { useRouter } from '@/next/navigation'
import { updateUserProfile } from '@/service/common'
import { timezones } from '@/utils/timezone'
import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content'
const mainNavMenuGroupClassName = 'p-1'
const mainNavMenuItemClassName = 'mx-0 h-8 gap-1 px-3 py-1'
const mainNavMenuSubPopupClassName = 'w-60 max-h-[360px] bg-components-panel-bg-blur! p-1! backdrop-blur-[5px]'
type MainNavRadioItemContentProps = {
iconClassName?: string
label: ReactNode
}
function MainNavRadioItemContent({
iconClassName,
label,
}: MainNavRadioItemContentProps) {
return (
<>
{iconClassName && <span aria-hidden className={cn('size-4 shrink-0 text-text-tertiary', iconClassName)} />}
<span className="min-w-0 grow truncate px-1 system-md-regular text-text-secondary">{label}</span>
<DropdownMenuRadioItemIndicator />
</>
)
}
function AppearanceSubmenu() {
const { t } = useTranslation()
const { theme, setTheme } = useTheme()
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger className={mainNavMenuItemClassName}>
<MenuItemContent
iconClassName="i-ri-sun-line"
label={t('account.appearanceLabel', { ns: 'common' })}
/>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
placement="right-start"
sideOffset={6}
popupClassName={mainNavMenuSubPopupClassName}
>
<DropdownMenuRadioGroup value={theme || 'system'} onValueChange={value => setTheme(value as Theme)}>
<DropdownMenuRadioItem value="light" closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent iconClassName="i-ri-sun-line" label={t('account.appearanceLight', { ns: 'common' })} />
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="dark" closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent iconClassName="i-ri-moon-line" label={t('account.appearanceDark', { ns: 'common' })} />
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="system" closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent iconClassName="i-ri-computer-line" label={t('account.appearanceSystem', { ns: 'common' })} />
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
)
}
function LanguageSubmenu() {
const locale = useLocale()
const router = useRouter()
const { t } = useTranslation()
const { userProfile, mutateUserProfile } = useAppContext()
const [editing, setEditing] = useState(false)
const languageOptions = languages.filter(item => item.supported)
const selectedLanguage = locale || userProfile.interface_language
const selectedTimezone = userProfile.timezone
const handleSelectLanguage = async (value: string) => {
setEditing(true)
try {
await updateUserProfile({ url: '/account/interface-language', body: { interface_language: value } })
toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' }))
await setLocaleOnClient(value as Locale, false)
router.refresh()
}
catch (error) {
toast.error((error as Error).message)
}
finally {
setEditing(false)
}
}
const handleSelectTimezone = async (value: string) => {
setEditing(true)
try {
await updateUserProfile({ url: '/account/timezone', body: { timezone: value } })
toast.success(t('actionMsg.modifiedSuccessfully', { ns: 'common' }))
mutateUserProfile()
}
catch (error) {
toast.error((error as Error).message)
}
finally {
setEditing(false)
}
}
return (
<>
<DropdownMenuSub>
<DropdownMenuSubTrigger className={mainNavMenuItemClassName}>
<MenuItemContent
iconClassName="i-ri-translate-2"
label={t('language.language', { ns: 'common' })}
/>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
placement="right-start"
sideOffset={6}
popupClassName={mainNavMenuSubPopupClassName}
>
<DropdownMenuRadioGroup
value={selectedLanguage}
onValueChange={(value) => {
if (!editing)
void handleSelectLanguage(value)
}}
>
{languageOptions.map(item => (
<DropdownMenuRadioItem key={item.value} value={item.value} closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent label={item.name} />
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger className={mainNavMenuItemClassName}>
<MenuItemContent
iconClassName="i-ri-global-line"
label={t('language.timezone', { ns: 'common' })}
/>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent
placement="right-start"
sideOffset={6}
popupClassName={mainNavMenuSubPopupClassName}
>
<DropdownMenuRadioGroup
value={selectedTimezone}
onValueChange={(value) => {
if (!editing)
void handleSelectTimezone(value)
}}
>
{timezones.map(item => (
<DropdownMenuRadioItem key={item.value} value={String(item.value)} closeOnClick className={mainNavMenuItemClassName}>
<MainNavRadioItemContent label={item.name} />
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</>
)
}
type MainNavMenuContentProps = {
mainNavBadge?: ReactNode
onLogout: () => Promise<void>
}
export function MainNavMenuContent({
mainNavBadge,
onLogout,
}: MainNavMenuContentProps) {
const { t } = useTranslation()
const { userProfile } = useAppContext()
return (
<>
<DropdownMenuGroup className={mainNavMenuGroupClassName}>
<div className="flex items-center gap-1 rounded-xl bg-gradient-to-b from-background-section-burn to-background-section p-3">
<div className="flex min-w-0 grow flex-col gap-1">
<div className="flex min-w-0 items-center gap-1">
<div className="truncate body-md-medium text-text-primary">{userProfile.name}</div>
{mainNavBadge}
</div>
<div className="truncate system-xs-regular text-text-tertiary">{userProfile.email}</div>
</div>
<Avatar avatar={userProfile.avatar_url} name={userProfile.name} size="lg" />
</div>
</DropdownMenuGroup>
<DropdownMenuGroup className={mainNavMenuGroupClassName}>
<DropdownMenuLinkItem
className={cn('justify-between', mainNavMenuItemClassName)}
render={<Link href="/account" />}
>
<MenuItemContent
iconClassName="i-ri-account-circle-line"
label={t('account.account', { ns: 'common' })}
trailing={<ExternalLinkIndicator />}
/>
</DropdownMenuLinkItem>
<AppearanceSubmenu />
<LanguageSubmenu />
</DropdownMenuGroup>
<DropdownMenuSeparator className="my-0! bg-divider-subtle" />
<DropdownMenuGroup className={mainNavMenuGroupClassName}>
<DropdownMenuItem
className={mainNavMenuItemClassName}
onClick={() => {
void onLogout()
}}
>
<MenuItemContent
iconClassName="i-ri-logout-box-r-line"
label={t('userProfile.logout', { ns: 'common' })}
/>
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)
}

View File

@ -18,6 +18,8 @@ import { useGetInstalledApps, useUninstallApp, useUpdateAppPinStatus } from '@/s
import { AppModeEnum } from '@/types/app'
import MainNav from '../index'
const activeEdgeClassName = 'before:pointer-events-none'
const { mockToastSuccess } = vi.hoisted(() => ({
mockToastSuccess: vi.fn(),
}))
@ -207,7 +209,7 @@ describe('MainNav', () => {
const datasetsLink = screen.getByRole('link', { name: /common.menus.datasets/ })
expect(datasetsLink.className).toContain('bg-[linear-gradient(98.077deg')
expect(datasetsLink).toHaveClass('main-nav-active-edge')
expect(datasetsLink).toHaveClass(activeEdgeClassName)
})
it('applies the Figma glass active state to the Home route', () => {
@ -220,7 +222,8 @@ describe('MainNav', () => {
expect(homeLink).toHaveClass(
'border-transparent',
'backdrop-blur-[5px]',
'main-nav-active-edge',
activeEdgeClassName,
'after:border-components-main-nav-glass-edge-highlight-first',
)
expect(homeLink.className).toContain('bg-[linear-gradient(98.077deg')
})
@ -278,6 +281,26 @@ describe('MainNav', () => {
expect(mockPush).toHaveBeenCalledWith('/explore/installed/installed-2')
})
it('collapses and expands installed web apps from the section arrow', () => {
mockInstalledApps = [createInstalledApp()]
renderMainNav()
const webAppsButton = screen.getByRole('button', { name: 'explore.sidebar.webApps' })
expect(webAppsButton).toHaveAttribute('aria-expanded', 'true')
expect(screen.getByText('Alpha App')).toBeInTheDocument()
fireEvent.click(webAppsButton)
expect(webAppsButton).toHaveAttribute('aria-expanded', 'false')
expect(screen.queryByText('Alpha App')).not.toBeInTheDocument()
fireEvent.click(webAppsButton)
expect(webAppsButton).toHaveAttribute('aria-expanded', 'true')
expect(screen.getByText('Alpha App')).toBeInTheDocument()
})
it('updates pin status and reuses the existing delete confirmation for installed web apps', async () => {
mockInstalledApps = [createInstalledApp()]
mockUninstall.mockResolvedValue(undefined)

View File

@ -6,13 +6,16 @@ import Link from '@/next/link'
const navItemClassName = 'group relative flex h-9 items-center gap-2 rounded-xl p-2 transition-colors'
const activeNavItemClassName = [
const activeNavItemClassName = cn(
'overflow-hidden border border-transparent',
'bg-[linear-gradient(98.077deg,var(--color-components-main-nav-glass-surface-first)_0%,var(--color-components-main-nav-glass-surface-middle-1)_17.98%,var(--color-components-main-nav-glass-surface-middle-2)_58.75%,var(--color-components-main-nav-glass-surface-end)_101.09%)]',
'system-md-semibold text-components-main-nav-text-active backdrop-blur-[5px]',
'shadow-[0px_4px_8px_0px_var(--color-components-main-nav-glass-shadow-reflection-glow),0px_12px_16px_-4px_var(--color-shadow-shadow-5),0px_4px_6px_-2px_var(--color-shadow-shadow-1),0px_10px_16px_-4px_var(--color-components-main-nav-glass-shadow-reflection)]',
'main-nav-active-edge',
].join(' ')
'before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit] before:p-px',
'before:bg-[linear-gradient(var(--color-components-main-nav-glass-edge-highlight-first),var(--color-components-main-nav-glass-edge-highlight-first))_top/100%_1px_no-repeat,linear-gradient(var(--color-components-main-nav-glass-edge-highlight-end),var(--color-components-main-nav-glass-edge-highlight-end))_bottom/100%_1px_no-repeat,linear-gradient(180deg,var(--color-components-main-nav-glass-edge-reflection-first)_0%,var(--color-components-main-nav-glass-edge-reflection-middle)_50%,var(--color-components-main-nav-glass-edge-reflection-end)_100%)_left/1px_100%_no-repeat,linear-gradient(180deg,var(--color-components-main-nav-glass-edge-reflection-first)_0%,var(--color-components-main-nav-glass-edge-reflection-middle)_50%,var(--color-components-main-nav-glass-edge-reflection-end)_100%)_right/1px_100%_no-repeat]',
'before:[mask-composite:exclude] before:[content:""] before:[-webkit-mask-composite:xor] before:[-webkit-mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)] before:[mask:linear-gradient(#000_0_0)_content-box,linear-gradient(#000_0_0)]',
'after:pointer-events-none after:absolute after:inset-[-1px] after:rounded-[inherit] after:border after:border-components-main-nav-glass-edge-highlight-first after:shadow-[inset_0_0_8px_0_var(--color-components-main-nav-glass-inner-glow)] after:[content:""]',
)
const inactiveNavItemClassName = 'system-md-medium bg-components-main-nav-nav-button-bg text-components-main-nav-text hover:bg-state-base-hover hover:text-components-main-nav-text'

View File

@ -24,6 +24,7 @@ const WebAppsSection = () => {
const installedApps = useMemo(() => data?.installed_apps ?? [], [data?.installed_apps])
const { mutateAsync: uninstallApp, isPending: isUninstalling } = useUninstallApp()
const { mutateAsync: updatePinStatus } = useUpdateAppPinStatus()
const [appsExpanded, setAppsExpanded] = useState(true)
const [searchVisible, setSearchVisible] = useState(false)
const [searchText, setSearchText] = useState('')
const [showConfirm, setShowConfirm] = useState(false)
@ -53,18 +54,22 @@ const WebAppsSection = () => {
<div className="flex items-center justify-between py-1 pr-2.5 pl-2">
<button
type="button"
aria-expanded={appsExpanded}
className="flex min-w-0 items-center rounded-md px-2 py-1 text-left system-xs-medium-uppercase text-text-tertiary hover:text-text-secondary"
onClick={() => setSearchVisible(value => !value)}
onClick={() => setAppsExpanded(value => !value)}
>
<span>{t('sidebar.webApps', { ns: 'explore' })}</span>
<span aria-hidden className="i-ri-arrow-down-s-fill h-4 w-4 shrink-0" />
<span aria-hidden className={cn('i-ri-arrow-down-s-fill h-4 w-4 shrink-0 transition-transform', !appsExpanded && '-rotate-90')} />
</button>
<div className="flex items-center gap-0.5">
<button
type="button"
aria-label={t('operation.search', { ns: 'common' })}
className={cn('flex h-6 w-6 items-center justify-center rounded-md p-0.5 text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', searchVisible && 'bg-state-base-hover text-text-secondary')}
onClick={() => setSearchVisible(value => !value)}
onClick={() => {
setAppsExpanded(true)
setSearchVisible(value => !value)
}}
>
<span className="flex size-5 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-search-line size-3.5" />
@ -72,7 +77,7 @@ const WebAppsSection = () => {
</button>
</div>
</div>
{searchVisible && (
{appsExpanded && searchVisible && (
<div className="px-2 pb-2">
<input
value={searchText}
@ -82,39 +87,41 @@ const WebAppsSection = () => {
/>
</div>
)}
<div className="min-h-0 flex-1 space-y-0.5 overflow-x-hidden overflow-y-auto px-2 pb-2">
{isPending && (
<div className="px-2 py-1 system-xs-regular text-components-main-nav-text">{t('loading', { ns: 'common' })}</div>
)}
{!isPending && filteredApps.length === 0 && (
<div className="px-2 py-1 system-xs-regular text-components-main-nav-text">
{searchText ? t('mainNav.webApps.noResults', { ns: 'common' }) : t('sidebar.noApps.title', { ns: 'explore' })}
</div>
)}
{filteredApps.map(({ id, is_pinned, uninstallable, app }) => (
<AppNavItem
key={id}
variant="mainNav"
isMobile={false}
name={app.name}
icon_type={app.icon_type}
icon={app.icon}
icon_background={app.icon_background}
icon_url={app.icon_url}
id={id}
isSelected={pathname.endsWith(`/installed/${id}`)}
isPinned={is_pinned}
togglePin={() => {
void handleUpdatePinStatus(id, !is_pinned)
}}
uninstallable={uninstallable}
onDelete={(id) => {
setCurrentId(id)
setShowConfirm(true)
}}
/>
))}
</div>
{appsExpanded && (
<div className="min-h-0 flex-1 space-y-0.5 overflow-x-hidden overflow-y-auto px-2 pb-2">
{isPending && (
<div className="px-2 py-1 system-xs-regular text-components-main-nav-text">{t('loading', { ns: 'common' })}</div>
)}
{!isPending && filteredApps.length === 0 && (
<div className="px-2 py-1 system-xs-regular text-components-main-nav-text">
{searchText ? t('mainNav.webApps.noResults', { ns: 'common' }) : t('sidebar.noApps.title', { ns: 'explore' })}
</div>
)}
{filteredApps.map(({ id, is_pinned, uninstallable, app }) => (
<AppNavItem
key={id}
variant="mainNav"
isMobile={false}
name={app.name}
icon_type={app.icon_type}
icon={app.icon}
icon_background={app.icon_background}
icon_url={app.icon_url}
id={id}
isSelected={pathname.endsWith(`/installed/${id}`)}
isPinned={is_pinned}
togglePin={() => {
void handleUpdatePinStatus(id, !is_pinned)
}}
uninstallable={uninstallable}
onDelete={(id) => {
setCurrentId(id)
setShowConfirm(true)
}}
/>
))}
</div>
)}
<AlertDialog open={showConfirm} onOpenChange={setShowConfirm}>
<AlertDialogContent>
<div className="flex flex-col items-start gap-2 self-stretch pt-6 pr-6 pb-4 pl-6">

View File

@ -1,41 +0,0 @@
.main-nav-active-edge::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
pointer-events: none;
background:
linear-gradient(var(--color-components-main-nav-glass-edge-highlight-first), var(--color-components-main-nav-glass-edge-highlight-first)) top / 100% 1px no-repeat,
linear-gradient(var(--color-components-main-nav-glass-edge-highlight-end), var(--color-components-main-nav-glass-edge-highlight-end)) bottom / 100% 1px no-repeat,
linear-gradient(
180deg,
var(--color-components-main-nav-glass-edge-reflection-first) 0%,
var(--color-components-main-nav-glass-edge-reflection-middle) 50%,
var(--color-components-main-nav-glass-edge-reflection-end) 100%
) left / 1px 100% no-repeat,
linear-gradient(
180deg,
var(--color-components-main-nav-glass-edge-reflection-first) 0%,
var(--color-components-main-nav-glass-edge-reflection-middle) 50%,
var(--color-components-main-nav-glass-edge-reflection-end) 100%
) right / 1px 100% no-repeat;
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
.main-nav-active-edge::after {
content: "";
position: absolute;
inset: -1px;
border: 1px solid var(--color-components-main-nav-glass-edge-highlight-first);
border-radius: inherit;
pointer-events: none;
box-shadow: inset 0 0 8px 0 var(--color-components-main-nav-glass-inner-glow);
}

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} هو أحدث إصدار متاح.",
"about.nowAvailable": "Dify {{version}} متاح الآن.",
"about.updateNow": "تحديث الآن",
"about.version": "الإصدار {{version}}",
"account.account": "الحساب",
"account.appearanceDark": "داكن",
"account.appearanceLabel": "المظهر",
"account.appearanceLight": "فاتح",
"account.appearanceSystem": "النظام",
"account.avatar": "الصورة الرمزية",
"account.changeEmail.authTip": "بمجرد تغيير بريدك الإلكتروني، لن تتمكن حسابات Google أو GitHub المرتبطة ببريدك الإلكتروني القديم من تسجيل الدخول إلى هذا الحساب.",
"account.changeEmail.changeTo": "تغيير إلى {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "تسجيل الدخول بحساب Google",
"label.optional": "(اختياري)",
"language.displayLanguage": "لغة العرض",
"language.language": "اللغة",
"language.timezone": "المنطقة الزمنية",
"license.expiring": "تنتهي في يوم واحد",
"license.expiring_plural": "تنتهي في {{count}} أيام",
"license.unlimited": "غير محدود",
"loading": "جارٍ التحميل",
"mainNav.help.docs": "الوثائق",
"mainNav.help.openMenu": "فتح قائمة المساعدة",
"mainNav.home": "الرئيسية",
"mainNav.integrations": "التكاملات",
"mainNav.marketplace": "سوق الإضافات",
"mainNav.webApps.noResults": "لم يتم العثور على تطبيقات ويب",
"mainNav.webApps.searchPlaceholder": "البحث في تطبيقات الويب",
"mainNav.workspace.credits": "{{count}} رصيد",
"mainNav.workspace.creditsUnit": "أرصدة",
"mainNav.workspace.inviteMembers": "دعوة الأعضاء وإدارتهم",
"mainNav.workspace.openMenu": "فتح قائمة مساحة العمل",
"mainNav.workspace.settings": "الإعدادات",
"mainNav.workspace.switchWorkspace": "تبديل مساحة العمل",
"members.admin": "المسؤول",
"members.adminTip": "يمكنه بناء التطبيقات وإدارة إعدادات الفريق",
"members.builder": "باني",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} ist die neueste verfügbare Version.",
"about.nowAvailable": "Dify {{version}} ist jetzt verfügbar.",
"about.updateNow": "Jetzt aktualisieren",
"about.version": "Version {{version}}",
"account.account": "Konto",
"account.appearanceDark": "Dunkel",
"account.appearanceLabel": "Darstellung",
"account.appearanceLight": "Hell",
"account.appearanceSystem": "System",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Sobald Ihre E-Mail geändert wurde, können Google- oder GitHub-Konten, die mit Ihrer alten E-Mail verknüpft sind, nicht mehr auf dieses Konto zugreifen.",
"account.changeEmail.changeTo": "Ändern zu {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Mit Google-Konto anmelden",
"label.optional": "(fakultativ)",
"language.displayLanguage": "Anzeigesprache",
"language.language": "Sprache",
"language.timezone": "Zeitzone",
"license.expiring": "Läuft an einem Tag ab",
"license.expiring_plural": "Läuft in {{count}} Tagen ab",
"license.unlimited": "Unbegrenzt",
"loading": "Wird geladen",
"mainNav.help.docs": "Dokumentation",
"mainNav.help.openMenu": "Hilfemenü öffnen",
"mainNav.home": "Startseite",
"mainNav.integrations": "Integrationen",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Keine Web-Apps gefunden",
"mainNav.webApps.searchPlaceholder": "Web-Apps suchen",
"mainNav.workspace.credits": "{{count}} Credits",
"mainNav.workspace.creditsUnit": "Credits",
"mainNav.workspace.inviteMembers": "Mitglieder einladen und verwalten",
"mainNav.workspace.openMenu": "Workspace-Menü öffnen",
"mainNav.workspace.settings": "Einstellungen",
"mainNav.workspace.switchWorkspace": "Workspace wechseln",
"members.admin": "Admin",
"members.adminTip": "Kann Apps erstellen & Team-Einstellungen verwalten",
"members.builder": "Bauherr",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} es la última versión disponible.",
"about.nowAvailable": "Dify {{version}} ya está disponible.",
"about.updateNow": "Actualizar ahora",
"about.version": "versión {{version}}",
"account.account": "Cuenta",
"account.appearanceDark": "Oscuro",
"account.appearanceLabel": "Apariencia",
"account.appearanceLight": "Claro",
"account.appearanceSystem": "Sistema",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Una vez que tu correo electrónico sea cambiado, las cuentas de Google o GitHub vinculadas a tu antiguo correo electrónico ya no podrán iniciar sesión en esta cuenta.",
"account.changeEmail.changeTo": "Cambia a {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Iniciar sesión con cuenta de Google",
"label.optional": "(opcional)",
"language.displayLanguage": "Idioma de visualización",
"language.language": "Idioma",
"language.timezone": "Zona horaria",
"license.expiring": "Caduca en un día",
"license.expiring_plural": "Caducando en {{count}} días",
"license.unlimited": "Ilimitado",
"loading": "Cargando",
"mainNav.help.docs": "Documentación",
"mainNav.help.openMenu": "Abrir menú de ayuda",
"mainNav.home": "Inicio",
"mainNav.integrations": "Integraciones",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "No se encontraron aplicaciones web",
"mainNav.webApps.searchPlaceholder": "Buscar aplicaciones web",
"mainNav.workspace.credits": "{{count}} créditos",
"mainNav.workspace.creditsUnit": "créditos",
"mainNav.workspace.inviteMembers": "Invitar y gestionar miembros",
"mainNav.workspace.openMenu": "Abrir menú del espacio de trabajo",
"mainNav.workspace.settings": "Configuración",
"mainNav.workspace.switchWorkspace": "Cambiar espacio de trabajo",
"members.admin": "Administrador",
"members.adminTip": "Puede crear aplicaciones y administrar configuraciones del equipo",
"members.builder": "Constructor",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} آخرین نسخه در دسترس است.",
"about.nowAvailable": "Dify {{version}} اکنون در دسترس است.",
"about.updateNow": "به‌روزرسانی اکنون",
"about.version": "نسخه {{version}}",
"account.account": "حساب",
"account.appearanceDark": "تیره",
"account.appearanceLabel": "ظاهر",
"account.appearanceLight": "روشن",
"account.appearanceSystem": "سیستم",
"account.avatar": "آواتار",
"account.changeEmail.authTip": "زمانی که ایمیل شما تغییر کند، حساب‌های گوگل یا گیت‌هاب مرتبط با ایمیل قدیمی شما دیگر قادر به ورود به این حساب نخواهند بود.",
"account.changeEmail.changeTo": "تغییر به {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "ورود با حساب گوگل",
"label.optional": "(اختیاری)",
"language.displayLanguage": "زبان نمایش",
"language.language": "زبان",
"language.timezone": "منطقه زمانی",
"license.expiring": "انقضا در یک روز",
"license.expiring_plural": "انقضا در {{count}} روز",
"license.unlimited": "نامحدود",
"loading": "در حال بارگذاری",
"mainNav.help.docs": "مستندات",
"mainNav.help.openMenu": "باز کردن منوی راهنما",
"mainNav.home": "خانه",
"mainNav.integrations": "یکپارچه‌سازی‌ها",
"mainNav.marketplace": "بازارچه",
"mainNav.webApps.noResults": "هیچ برنامه وبی یافت نشد",
"mainNav.webApps.searchPlaceholder": "جستجوی برنامه‌های وب",
"mainNav.workspace.credits": "{{count}} اعتبار",
"mainNav.workspace.creditsUnit": "اعتبار",
"mainNav.workspace.inviteMembers": "دعوت و مدیریت اعضا",
"mainNav.workspace.openMenu": "باز کردن منوی فضای کاری",
"mainNav.workspace.settings": "تنظیمات",
"mainNav.workspace.switchWorkspace": "تغییر فضای کاری",
"members.admin": "مدیر",
"members.adminTip": "می‌تواند برنامه‌ها را بسازد و تنظیمات تیم را مدیریت کند",
"members.builder": "سازنده",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} est la dernière version disponible.",
"about.nowAvailable": "Dify {{version}} est maintenant disponible.",
"about.updateNow": "Mettre à jour maintenant",
"about.version": "version {{version}}",
"account.account": "Compte",
"account.appearanceDark": "Sombre",
"account.appearanceLabel": "Apparence",
"account.appearanceLight": "Clair",
"account.appearanceSystem": "Système",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Une fois que votre email est changé, les comptes Google ou GitHub liés à votre ancien email ne pourront plus se connecter à ce compte.",
"account.changeEmail.changeTo": "Changer pour {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Connectez-vous avec un compte Google",
"label.optional": "(facultatif)",
"language.displayLanguage": "Langue d'affichage",
"language.language": "Langue",
"language.timezone": "Fuseau horaire",
"license.expiring": "Expirant dans un jour",
"license.expiring_plural": "Expirant dans {{count}} jours",
"license.unlimited": "Illimité",
"loading": "Chargement",
"mainNav.help.docs": "Documentation",
"mainNav.help.openMenu": "Ouvrir le menu daide",
"mainNav.home": "Accueil",
"mainNav.integrations": "Intégrations",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Aucune application web trouvée",
"mainNav.webApps.searchPlaceholder": "Rechercher des applications web",
"mainNav.workspace.credits": "{{count}} crédits",
"mainNav.workspace.creditsUnit": "crédits",
"mainNav.workspace.inviteMembers": "Inviter et gérer les membres",
"mainNav.workspace.openMenu": "Ouvrir le menu de lespace de travail",
"mainNav.workspace.settings": "Paramètres",
"mainNav.workspace.switchWorkspace": "Changer despace de travail",
"members.admin": "Administrateur",
"members.adminTip": "Peut construire des applications & gérer les paramètres de l'équipe",
"members.builder": "Constructeur",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} नवीनतम उपलब्ध संस्करण है।",
"about.nowAvailable": "Dify {{version}} अब उपलब्ध है।",
"about.updateNow": "अभी अपडेट करें",
"about.version": "संस्करण {{version}}",
"account.account": "खाता",
"account.appearanceDark": "डार्क",
"account.appearanceLabel": "दिखावट",
"account.appearanceLight": "लाइट",
"account.appearanceSystem": "सिस्टम",
"account.avatar": "अवतार",
"account.changeEmail.authTip": "एक बार जब आपका ईमेल बदल दिया जाता है, तो आपके पुराने ईमेल से जुड़े Google या GitHub खाते इस खाते में लॉग इन नहीं कर सकेंगे।",
"account.changeEmail.changeTo": "{{email}} में परिवर्तन करें",
@ -209,11 +214,25 @@
"integrations.googleAccount": "गूगल खाते के साथ लॉगिन करें",
"label.optional": "(अनिवार्य नहीं)",
"language.displayLanguage": "प्रदर्शन भाषा",
"language.language": "भाषा",
"language.timezone": "समय क्षेत्र",
"license.expiring": "एक दिन में समाप्त हो रहा है",
"license.expiring_plural": "{{count}} दिनों में समाप्त हो रहा है",
"license.unlimited": "असीमित",
"loading": "लोड हो रहा है",
"mainNav.help.docs": "दस्तावेज़",
"mainNav.help.openMenu": "सहायता मेनू खोलें",
"mainNav.home": "होम",
"mainNav.integrations": "इंटीग्रेशन",
"mainNav.marketplace": "मार्केटप्लेस",
"mainNav.webApps.noResults": "कोई वेब ऐप नहीं मिला",
"mainNav.webApps.searchPlaceholder": "वेब ऐप खोजें",
"mainNav.workspace.credits": "{{count}} क्रेडिट",
"mainNav.workspace.creditsUnit": "क्रेडिट",
"mainNav.workspace.inviteMembers": "सदस्यों को आमंत्रित और प्रबंधित करें",
"mainNav.workspace.openMenu": "वर्कस्पेस मेनू खोलें",
"mainNav.workspace.settings": "सेटिंग्स",
"mainNav.workspace.switchWorkspace": "वर्कस्पेस बदलें",
"members.admin": "प्रशासक",
"members.adminTip": "ऐप्स बना सकते हैं और टीम सेटिंग्स का प्रबंधन कर सकते हैं",
"members.builder": "निर्माता",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} adalah versi terbaru yang tersedia.",
"about.nowAvailable": "Dify {{version}} kini tersedia.",
"about.updateNow": "Perbarui sekarang",
"about.version": "versi {{version}}",
"account.account": "Rekening",
"account.appearanceDark": "Gelap",
"account.appearanceLabel": "Tampilan",
"account.appearanceLight": "Terang",
"account.appearanceSystem": "Sistem",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Setelah email Anda diubah, akun Google atau GitHub yang ditautkan ke email lama Anda tidak akan dapat lagi masuk ke akun ini.",
"account.changeEmail.changeTo": "Ubah ke {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Masuk dengan akun Google",
"label.optional": "(opsional)",
"language.displayLanguage": "Bahasa Tampilan",
"language.language": "Bahasa",
"language.timezone": "Zona Waktu",
"license.expiring": "Kedaluwarsa dalam satu hari",
"license.expiring_plural": "Kedaluwarsa dalam {{count}} hari",
"license.unlimited": "Unlimited",
"loading": "Memuat",
"mainNav.help.docs": "Dokumentasi",
"mainNav.help.openMenu": "Buka menu bantuan",
"mainNav.home": "Beranda",
"mainNav.integrations": "Integrasi",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Tidak ada aplikasi web ditemukan",
"mainNav.webApps.searchPlaceholder": "Cari aplikasi web",
"mainNav.workspace.credits": "{{count}} kredit",
"mainNav.workspace.creditsUnit": "kredit",
"mainNav.workspace.inviteMembers": "Undang dan kelola anggota",
"mainNav.workspace.openMenu": "Buka menu workspace",
"mainNav.workspace.settings": "Pengaturan",
"mainNav.workspace.switchWorkspace": "Ganti workspace",
"members.admin": "Admin",
"members.adminTip": "Dapat membangun aplikasi & mengelola pengaturan tim",
"members.builder": "Pembangun",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} è l'ultima versione disponibile.",
"about.nowAvailable": "Dify {{version}} è ora disponibile.",
"about.updateNow": "Aggiorna ora",
"about.version": "versione {{version}}",
"account.account": "Conto",
"account.appearanceDark": "Scuro",
"account.appearanceLabel": "Aspetto",
"account.appearanceLight": "Chiaro",
"account.appearanceSystem": "Sistema",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Una volta che la tua email è cambiata, gli account Google o GitHub collegati alla tua vecchia email non potranno più accedere a questo account.",
"account.changeEmail.changeTo": "Cambia in {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Accedi con l'account Google",
"label.optional": "(Facoltativo)",
"language.displayLanguage": "Lingua di visualizzazione",
"language.language": "Lingua",
"language.timezone": "Fuso orario",
"license.expiring": "Scadenza in un giorno",
"license.expiring_plural": "Scadenza tra {{count}} giorni",
"license.unlimited": "Illimitato",
"loading": "Caricamento",
"mainNav.help.docs": "Documentazione",
"mainNav.help.openMenu": "Apri menu di aiuto",
"mainNav.home": "Home",
"mainNav.integrations": "Integrazioni",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Nessuna app web trovata",
"mainNav.webApps.searchPlaceholder": "Cerca app web",
"mainNav.workspace.credits": "{{count}} crediti",
"mainNav.workspace.creditsUnit": "crediti",
"mainNav.workspace.inviteMembers": "Invita e gestisci membri",
"mainNav.workspace.openMenu": "Apri menu workspace",
"mainNav.workspace.settings": "Impostazioni",
"mainNav.workspace.switchWorkspace": "Cambia workspace",
"members.admin": "Admin",
"members.adminTip": "Può creare app e gestire le impostazioni del team",
"members.builder": "Builder",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} が最新バージョンです。",
"about.nowAvailable": "Dify {{version}} が利用可能です。",
"about.updateNow": "今すぐ更新",
"about.version": "バージョン {{version}}",
"account.account": "アカウント",
"account.appearanceDark": "ダーク",
"account.appearanceLabel": "外観",
"account.appearanceLight": "ライト",
"account.appearanceSystem": "システム",
"account.avatar": "アバター",
"account.changeEmail.authTip": "メールアドレスが変更されると、旧メールアドレスにリンクされている Google または GitHub アカウントは、このアカウントにログインできなくなります。",
"account.changeEmail.changeTo": "{{email}} に変更",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Google アカウントでログイン",
"label.optional": "(オプション)",
"language.displayLanguage": "表示言語",
"language.language": "言語",
"language.timezone": "タイムゾーン",
"license.expiring": "1 日で有効期限が切れます",
"license.expiring_plural": "有効期限 {{count}} 日",
"license.unlimited": "無制限",
"loading": "読み込み中",
"mainNav.help.docs": "ドキュメント",
"mainNav.help.openMenu": "ヘルプメニューを開く",
"mainNav.home": "ホーム",
"mainNav.integrations": "連携",
"mainNav.marketplace": "マーケットプレイス",
"mainNav.webApps.noResults": "Webアプリが見つかりません",
"mainNav.webApps.searchPlaceholder": "Webアプリを検索",
"mainNav.workspace.credits": "{{count}} クレジット",
"mainNav.workspace.creditsUnit": "クレジット",
"mainNav.workspace.inviteMembers": "メンバーを招待・管理",
"mainNav.workspace.openMenu": "ワークスペースメニューを開く",
"mainNav.workspace.settings": "設定",
"mainNav.workspace.switchWorkspace": "ワークスペースを切り替え",
"members.admin": "管理者",
"members.adminTip": "アプリの構築およびチーム設定の管理ができます",
"members.builder": "ビルダー",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} 최신 버전입니다.",
"about.nowAvailable": "Dify {{version}} 사용 가능합니다.",
"about.updateNow": "지금 업데이트",
"about.version": "버전 {{version}}",
"account.account": "계정",
"account.appearanceDark": "어둡게",
"account.appearanceLabel": "모양",
"account.appearanceLight": "밝게",
"account.appearanceSystem": "시스템",
"account.avatar": "아바타",
"account.changeEmail.authTip": "이메일이 변경되면, 이전 이메일에 연결된 Google 또는 GitHub 계정은 더 이상 이 계정에 로그인할 수 없습니다.",
"account.changeEmail.changeTo": "{{email}}로 변경",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Google 계정으로 로그인",
"label.optional": "(선택 사항)",
"language.displayLanguage": "표시 언어",
"language.language": "언어",
"language.timezone": "시간대",
"license.expiring": "하루 후에 만료",
"license.expiring_plural": "{{count}}일 후에 만료",
"license.unlimited": "무제한",
"loading": "로딩 중",
"mainNav.help.docs": "문서",
"mainNav.help.openMenu": "도움말 메뉴 열기",
"mainNav.home": "홈",
"mainNav.integrations": "연동",
"mainNav.marketplace": "마켓플레이스",
"mainNav.webApps.noResults": "웹 앱을 찾을 수 없습니다",
"mainNav.webApps.searchPlaceholder": "웹 앱 검색",
"mainNav.workspace.credits": "{{count}} 크레딧",
"mainNav.workspace.creditsUnit": "크레딧",
"mainNav.workspace.inviteMembers": "멤버 초대 및 관리",
"mainNav.workspace.openMenu": "워크스페이스 메뉴 열기",
"mainNav.workspace.settings": "설정",
"mainNav.workspace.switchWorkspace": "워크스페이스 전환",
"members.admin": "관리자",
"members.adminTip": "앱 빌드 및 팀 설정 관리 가능",
"members.builder": "빌더",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} is the latest version available.",
"about.nowAvailable": "Dify {{version}} is now available.",
"about.updateNow": "Update now",
"about.version": "versie {{version}}",
"account.account": "Account",
"account.appearanceDark": "Donker",
"account.appearanceLabel": "Weergave",
"account.appearanceLight": "Licht",
"account.appearanceSystem": "Systeem",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Once your email is changed, Google or GitHub accounts linked to your old email will no longer be able to log in to this account.",
"account.changeEmail.changeTo": "Change to {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Login with Google account",
"label.optional": "(optional)",
"language.displayLanguage": "Display Language",
"language.language": "Taal",
"language.timezone": "Time Zone",
"license.expiring": "Expiring in one day",
"license.expiring_plural": "Expiring in {{count}} days",
"license.unlimited": "Unlimited",
"loading": "Loading",
"mainNav.help.docs": "Documentatie",
"mainNav.help.openMenu": "Helpmenu openen",
"mainNav.home": "Start",
"mainNav.integrations": "Integraties",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Geen webapps gevonden",
"mainNav.webApps.searchPlaceholder": "Webapps zoeken",
"mainNav.workspace.credits": "{{count}} credits",
"mainNav.workspace.creditsUnit": "credits",
"mainNav.workspace.inviteMembers": "Leden uitnodigen en beheren",
"mainNav.workspace.openMenu": "Werkruimtemenu openen",
"mainNav.workspace.settings": "Instellingen",
"mainNav.workspace.switchWorkspace": "Werkruimte wisselen",
"members.admin": "Admin",
"members.adminTip": "Can build apps & manage team settings",
"members.builder": "Builder",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} jest najnowszą dostępną wersją.",
"about.nowAvailable": "Dify {{version}} jest teraz dostępny.",
"about.updateNow": "Aktualizuj teraz",
"about.version": "wersja {{version}}",
"account.account": "Rachunek",
"account.appearanceDark": "Ciemny",
"account.appearanceLabel": "Wygląd",
"account.appearanceLight": "Jasny",
"account.appearanceSystem": "Systemowy",
"account.avatar": "Awatar",
"account.changeEmail.authTip": "Gdy twoje e-mail zostanie zmienione, konta Google lub GitHub powiązane z twoim starym e-mailem nie będą mogły już logować się do tego konta.",
"account.changeEmail.changeTo": "Zmień na {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Zaloguj się przy użyciu konta Google",
"label.optional": "(Opcjonalnie)",
"language.displayLanguage": "Język interfejsu",
"language.language": "Język",
"language.timezone": "Strefa czasowa",
"license.expiring": "Wygasa w ciągu jednego dnia",
"license.expiring_plural": "Wygasa za {{count}} dni",
"license.unlimited": "Nieograniczony",
"loading": "Ładowanie",
"mainNav.help.docs": "Dokumentacja",
"mainNav.help.openMenu": "Otwórz menu pomocy",
"mainNav.home": "Strona główna",
"mainNav.integrations": "Integracje",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Nie znaleziono aplikacji webowych",
"mainNav.webApps.searchPlaceholder": "Szukaj aplikacji webowych",
"mainNav.workspace.credits": "{{count}} kredytów",
"mainNav.workspace.creditsUnit": "kredyty",
"mainNav.workspace.inviteMembers": "Zaproś członków i zarządzaj nimi",
"mainNav.workspace.openMenu": "Otwórz menu obszaru roboczego",
"mainNav.workspace.settings": "Ustawienia",
"mainNav.workspace.switchWorkspace": "Przełącz obszar roboczy",
"members.admin": "Admin",
"members.adminTip": "Może tworzyć aplikacje i zarządzać ustawieniami zespołu",
"members.builder": "Budowniczy",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} é a última versão disponível.",
"about.nowAvailable": "Dify {{version}} já está disponível.",
"about.updateNow": "Atualizar agora",
"about.version": "versão {{version}}",
"account.account": "Conta",
"account.appearanceDark": "Escuro",
"account.appearanceLabel": "Aparência",
"account.appearanceLight": "Claro",
"account.appearanceSystem": "Sistema",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Uma vez que seu e-mail seja alterado, as contas do Google ou GitHub vinculadas ao seu e-mail antigo não poderão mais fazer login nesta conta.",
"account.changeEmail.changeTo": "Mudar para {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Faça login com a conta do Google",
"label.optional": "(opcional)",
"language.displayLanguage": "Idioma de exibição",
"language.language": "Idioma",
"language.timezone": "Fuso horário",
"license.expiring": "Expirando em um dia",
"license.expiring_plural": "Expirando em {{count}} dias",
"license.unlimited": "Ilimitado",
"loading": "Carregando",
"mainNav.help.docs": "Documentação",
"mainNav.help.openMenu": "Abrir menu de ajuda",
"mainNav.home": "Início",
"mainNav.integrations": "Integrações",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Nenhum app web encontrado",
"mainNav.webApps.searchPlaceholder": "Pesquisar apps web",
"mainNav.workspace.credits": "{{count}} créditos",
"mainNav.workspace.creditsUnit": "créditos",
"mainNav.workspace.inviteMembers": "Convidar e gerenciar membros",
"mainNav.workspace.openMenu": "Abrir menu do workspace",
"mainNav.workspace.settings": "Configurações",
"mainNav.workspace.switchWorkspace": "Trocar workspace",
"members.admin": "Admin",
"members.adminTip": "Pode criar aplicativos e gerenciar configurações da equipe",
"members.builder": "Construtor",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} este ultima versiune disponibilă.",
"about.nowAvailable": "Dify {{version}} este acum disponibil.",
"about.updateNow": "Actualizați acum",
"about.version": "versiunea {{version}}",
"account.account": "Cont",
"account.appearanceDark": "Întunecat",
"account.appearanceLabel": "Aspect",
"account.appearanceLight": "Luminos",
"account.appearanceSystem": "Sistem",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Odată ce adresa ta de email este schimbată, conturile Google sau GitHub legate de vechea ta adresă de email nu vor mai putea să se conecteze la acest cont.",
"account.changeEmail.changeTo": "Schimbă la {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Autentificare cu cont Google",
"label.optional": "(opțional)",
"language.displayLanguage": "Limbă de afișare",
"language.language": "Limbă",
"language.timezone": "Fus orar",
"license.expiring": "Expiră într-o zi",
"license.expiring_plural": "Expiră în {{count}} zile",
"license.unlimited": "Nelimitat",
"loading": "Se încarcă",
"mainNav.help.docs": "Documentație",
"mainNav.help.openMenu": "Deschide meniul de ajutor",
"mainNav.home": "Acasă",
"mainNav.integrations": "Integrări",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Nu s-au găsit aplicații web",
"mainNav.webApps.searchPlaceholder": "Caută aplicații web",
"mainNav.workspace.credits": "{{count}} credite",
"mainNav.workspace.creditsUnit": "credite",
"mainNav.workspace.inviteMembers": "Invită și gestionează membri",
"mainNav.workspace.openMenu": "Deschide meniul spațiului de lucru",
"mainNav.workspace.settings": "Setări",
"mainNav.workspace.switchWorkspace": "Schimbă spațiul de lucru",
"members.admin": "Administrator",
"members.adminTip": "Poate construi aplicații și gestiona setările echipei",
"members.builder": "Constructor",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} - последняя доступная версия.",
"about.nowAvailable": "Dify {{version}} теперь доступен.",
"about.updateNow": "Обновить сейчас",
"about.version": "версия {{version}}",
"account.account": "Счет",
"account.appearanceDark": "Темная",
"account.appearanceLabel": "Внешний вид",
"account.appearanceLight": "Светлая",
"account.appearanceSystem": "Системная",
"account.avatar": "Аватар",
"account.changeEmail.authTip": "После изменения вашего адреса электронной почты учетные записи Google или GitHub, связанные с вашим старым адресом, больше не смогут войти в эту учетную запись.",
"account.changeEmail.changeTo": "Изменить на {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Войти с помощью учетной записи Google",
"label.optional": "(необязательно)",
"language.displayLanguage": "Язык отображения",
"language.language": "Язык",
"language.timezone": "Часовой пояс",
"license.expiring": "Срок действия истекает за один день",
"license.expiring_plural": "Срок действия истекает через {{count}} дней",
"license.unlimited": "Неограниченный",
"loading": "Загрузка",
"mainNav.help.docs": "Документация",
"mainNav.help.openMenu": "Открыть меню помощи",
"mainNav.home": "Главная",
"mainNav.integrations": "Интеграции",
"mainNav.marketplace": "Маркетплейс",
"mainNav.webApps.noResults": "Веб-приложения не найдены",
"mainNav.webApps.searchPlaceholder": "Поиск веб-приложений",
"mainNav.workspace.credits": "{{count}} кредитов",
"mainNav.workspace.creditsUnit": "кредиты",
"mainNav.workspace.inviteMembers": "Пригласить участников и управлять ими",
"mainNav.workspace.openMenu": "Открыть меню рабочей области",
"mainNav.workspace.settings": "Настройки",
"mainNav.workspace.switchWorkspace": "Переключить рабочую область",
"members.admin": "Администратор",
"members.adminTip": "Может создавать приложения и управлять настройками команды",
"members.builder": "Разработчик",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} je najnovejša različica, ki je na voljo.",
"about.nowAvailable": "Dify {{version}} je zdaj na voljo.",
"about.updateNow": "Posodobi zdaj",
"about.version": "različica {{version}}",
"account.account": "Račun",
"account.appearanceDark": "Temno",
"account.appearanceLabel": "Videz",
"account.appearanceLight": "Svetlo",
"account.appearanceSystem": "Sistem",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "Ko bo vaš e-poštni naslov spremenjen, se računi Google ali GitHub, povezani z vašim starim e-poštnim naslovom, ne bodo mogli več prijaviti v ta račun.",
"account.changeEmail.changeTo": "Spremeni v {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Prijavite se z Google računom",
"label.optional": "(neobvezno)",
"language.displayLanguage": "Jezik prikaza",
"language.language": "Jezik",
"language.timezone": "Časovni pas",
"license.expiring": "Poteče v enem dnevu",
"license.expiring_plural": "Poteče v {{count}} dneh",
"license.unlimited": "Brez omejitev",
"loading": "Nalaganje",
"mainNav.help.docs": "Dokumentacija",
"mainNav.help.openMenu": "Odpri meni pomoči",
"mainNav.home": "Domov",
"mainNav.integrations": "Integracije",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Ni najdenih spletnih aplikacij",
"mainNav.webApps.searchPlaceholder": "Išči spletne aplikacije",
"mainNav.workspace.credits": "{{count}} kreditov",
"mainNav.workspace.creditsUnit": "krediti",
"mainNav.workspace.inviteMembers": "Povabi in upravljaj člane",
"mainNav.workspace.openMenu": "Odpri meni delovnega prostora",
"mainNav.workspace.settings": "Nastavitve",
"mainNav.workspace.switchWorkspace": "Preklopi delovni prostor",
"members.admin": "Administrator",
"members.adminTip": "Lahko ustvarja aplikacije in upravlja nastavitve ekipe",
"members.builder": "Graditelj",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} เป็นเวอร์ชันล่าสุดที่มี",
"about.nowAvailable": "Dify {{version}} พร้อมใช้งานแล้ว",
"about.updateNow": "อัพเดตเดี๋ยวนี้",
"about.version": "เวอร์ชัน {{version}}",
"account.account": "บัญชี",
"account.appearanceDark": "มืด",
"account.appearanceLabel": "รูปลักษณ์",
"account.appearanceLight": "สว่าง",
"account.appearanceSystem": "ระบบ",
"account.avatar": "อวตาร",
"account.changeEmail.authTip": "เมื่ออีเมลของคุณถูกเปลี่ยนแปลง บัญชี Google หรือบัญชี GitHub ที่เชื่อมโยงกับอีเมลเก่าของคุณจะไม่สามารถเข้าสู่ระบบบัญชีนี้ได้อีกต่อไป.",
"account.changeEmail.changeTo": "เปลี่ยนเป็น {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "เข้าสู่ระบบด้วยบัญชี Google",
"label.optional": "(ไม่บังคับ)",
"language.displayLanguage": "ภาษาที่แสดง",
"language.language": "ภาษา",
"language.timezone": "เขตเวลา",
"license.expiring": "หมดอายุในหนึ่งวัน",
"license.expiring_plural": "หมดอายุใน {{count}} วัน",
"license.unlimited": "ไม่มีขีดจำกัด",
"loading": "กำลังโหลด",
"mainNav.help.docs": "เอกสาร",
"mainNav.help.openMenu": "เปิดเมนูช่วยเหลือ",
"mainNav.home": "หน้าแรก",
"mainNav.integrations": "การผสานรวม",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "ไม่พบเว็บแอป",
"mainNav.webApps.searchPlaceholder": "ค้นหาเว็บแอป",
"mainNav.workspace.credits": "{{count}} เครดิต",
"mainNav.workspace.creditsUnit": "เครดิต",
"mainNav.workspace.inviteMembers": "เชิญและจัดการสมาชิก",
"mainNav.workspace.openMenu": "เปิดเมนูพื้นที่ทำงาน",
"mainNav.workspace.settings": "การตั้งค่า",
"mainNav.workspace.switchWorkspace": "สลับพื้นที่ทำงาน",
"members.admin": "ผู้ดูแลระบบ",
"members.adminTip": "สามารถสร้างแอพและจัดการการตั้งค่าทีมได้",
"members.builder": "ผู้สร้าง",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} en son mevcut sürüm.",
"about.nowAvailable": "Dify {{version}} şimdi mevcut.",
"about.updateNow": "Şimdi güncelle",
"about.version": "sürüm {{version}}",
"account.account": "Hesap",
"account.appearanceDark": "Koyu",
"account.appearanceLabel": "Görünüm",
"account.appearanceLight": "Açık",
"account.appearanceSystem": "Sistem",
"account.avatar": "Avatar",
"account.changeEmail.authTip": "E-posta adresiniz değiştiğinde, eski e-posta adresinize bağlı Google veya GitHub hesapları bu hesaba giriş yapamayacak.",
"account.changeEmail.changeTo": "{{email}}'yi değiştir",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Google hesabıyla giriş yap",
"label.optional": "(isteğe bağlı)",
"language.displayLanguage": "Görüntüleme Dili",
"language.language": "Dil",
"language.timezone": "Zaman Dilimi",
"license.expiring": "Bir günde sona eriyor",
"license.expiring_plural": "{{count}} gün içinde sona eriyor",
"license.unlimited": "Sınırsız",
"loading": "Yükleniyor",
"mainNav.help.docs": "Belgeler",
"mainNav.help.openMenu": "Yardım menüsünü aç",
"mainNav.home": "Ana sayfa",
"mainNav.integrations": "Entegrasyonlar",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Web uygulaması bulunamadı",
"mainNav.webApps.searchPlaceholder": "Web uygulamalarında ara",
"mainNav.workspace.credits": "{{count}} kredi",
"mainNav.workspace.creditsUnit": "kredi",
"mainNav.workspace.inviteMembers": "Üyeleri davet et ve yönet",
"mainNav.workspace.openMenu": "Çalışma alanı menüsünü aç",
"mainNav.workspace.settings": "Ayarlar",
"mainNav.workspace.switchWorkspace": "Çalışma alanını değiştir",
"members.admin": "Yönetici",
"members.adminTip": "Uygulama oluşturabilir ve takım ayarlarını yönetebilir",
"members.builder": "Oluşturucu",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} це найновіша доступна версія.",
"about.nowAvailable": "Dify {{version}} тепер доступна.",
"about.updateNow": "Оновити зараз",
"about.version": "версія {{version}}",
"account.account": "Рахунок",
"account.appearanceDark": "Темна",
"account.appearanceLabel": "Вигляд",
"account.appearanceLight": "Світла",
"account.appearanceSystem": "Системна",
"account.avatar": "Аватар",
"account.changeEmail.authTip": "Коли ви зміните свою електронну адресу, облікові записи Google або GitHub, пов'язані з вашою старою електронною адресою, більше не зможуть увійти в цей обліковий запис.",
"account.changeEmail.changeTo": "Змінити на {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Увійти за допомогою облікового запису Google",
"label.optional": "(необов'язково)",
"language.displayLanguage": "Мова інтерфейсу",
"language.language": "Мова",
"language.timezone": "Часовий пояс",
"license.expiring": "Термін дії закінчується за один день",
"license.expiring_plural": "Термін дії закінчується за {{count}} днів",
"license.unlimited": "Безмежний",
"loading": "Завантаження",
"mainNav.help.docs": "Документація",
"mainNav.help.openMenu": "Відкрити меню довідки",
"mainNav.home": "Головна",
"mainNav.integrations": "Інтеграції",
"mainNav.marketplace": "Маркетплейс",
"mainNav.webApps.noResults": "Вебзастосунки не знайдено",
"mainNav.webApps.searchPlaceholder": "Пошук вебзастосунків",
"mainNav.workspace.credits": "{{count}} кредитів",
"mainNav.workspace.creditsUnit": "кредити",
"mainNav.workspace.inviteMembers": "Запросити учасників і керувати ними",
"mainNav.workspace.openMenu": "Відкрити меню робочого простору",
"mainNav.workspace.settings": "Налаштування",
"mainNav.workspace.switchWorkspace": "Перемкнути робочий простір",
"members.admin": "Адміністратор",
"members.adminTip": "Може створювати програми та керувати налаштуваннями команди",
"members.builder": "Будівник",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} là phiên bản mới nhất hiện có.",
"about.nowAvailable": "Dify {{version}} hiện đã có sẵn.",
"about.updateNow": "Cập nhật ngay",
"about.version": "phiên bản {{version}}",
"account.account": "Tài khoản",
"account.appearanceDark": "Tối",
"account.appearanceLabel": "Giao diện",
"account.appearanceLight": "Sáng",
"account.appearanceSystem": "Hệ thống",
"account.avatar": "Ảnh đại diện",
"account.changeEmail.authTip": "Khi email của bạn được thay đổi, các tài khoản Google hoặc GitHub liên kết với email cũ của bạn sẽ không còn có thể đăng nhập vào tài khoản này.",
"account.changeEmail.changeTo": "Thay đổi thành {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Đăng nhập bằng tài khoản Google",
"label.optional": "(tùy chọn)",
"language.displayLanguage": "Ngôn ngữ hiển thị",
"language.language": "Ngôn ngữ",
"language.timezone": "Múi giờ",
"license.expiring": "Hết hạn trong một ngày",
"license.expiring_plural": "Hết hạn sau {{count}} ngày",
"license.unlimited": "Vô hạn",
"loading": "Đang tải",
"mainNav.help.docs": "Tài liệu",
"mainNav.help.openMenu": "Mở menu trợ giúp",
"mainNav.home": "Trang chủ",
"mainNav.integrations": "Tích hợp",
"mainNav.marketplace": "Marketplace",
"mainNav.webApps.noResults": "Không tìm thấy ứng dụng web",
"mainNav.webApps.searchPlaceholder": "Tìm kiếm ứng dụng web",
"mainNav.workspace.credits": "{{count}} tín dụng",
"mainNav.workspace.creditsUnit": "tín dụng",
"mainNav.workspace.inviteMembers": "Mời và quản lý thành viên",
"mainNav.workspace.openMenu": "Mở menu workspace",
"mainNav.workspace.settings": "Cài đặt",
"mainNav.workspace.switchWorkspace": "Chuyển workspace",
"members.admin": "Quản trị viên",
"members.adminTip": "Có thể xây dựng ứng dụng và quản lý cài đặt nhóm",
"members.builder": "Chủ thầu",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} 已是最新版本。",
"about.nowAvailable": "Dify {{version}} 现已可用。",
"about.updateNow": "现在更新",
"about.version": "版本 {{version}}",
"account.account": "账户",
"account.appearanceDark": "深色",
"account.appearanceLabel": "外观",
"account.appearanceLight": "浅色",
"account.appearanceSystem": "跟随系统",
"account.avatar": "头像",
"account.changeEmail.authTip": "一旦您的电子邮件地址更改,链接到您旧电子邮件地址的 Google 或 GitHub 帐户将无法再登录该帐户。",
"account.changeEmail.changeTo": "更改为 {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Google 账号登录",
"label.optional": "(可选)",
"language.displayLanguage": "界面语言",
"language.language": "语言",
"language.timezone": "时区",
"license.expiring": "许可证还有 1 天到期",
"license.expiring_plural": "许可证还有 {{count}} 天到期",
"license.unlimited": "无限制",
"loading": "加载中",
"mainNav.help.docs": "文档",
"mainNav.help.openMenu": "打开帮助菜单",
"mainNav.home": "首页",
"mainNav.integrations": "集成",
"mainNav.marketplace": "插件市场",
"mainNav.webApps.noResults": "未找到 Web 应用",
"mainNav.webApps.searchPlaceholder": "搜索 Web 应用",
"mainNav.workspace.credits": "{{count}} 点额度",
"mainNav.workspace.creditsUnit": "点额度",
"mainNav.workspace.inviteMembers": "邀请并管理成员",
"mainNav.workspace.openMenu": "打开工作空间菜单",
"mainNav.workspace.settings": "设置",
"mainNav.workspace.switchWorkspace": "切换工作空间",
"members.admin": "管理员",
"members.adminTip": "能够建立应用程序和管理团队设置",
"members.builder": "构建器",

View File

@ -3,7 +3,12 @@
"about.latestAvailable": "Dify {{version}} 已是最新版本。",
"about.nowAvailable": "Dify {{version}} 現已可用。",
"about.updateNow": "現在更新",
"about.version": "版本 {{version}}",
"account.account": "帳戶",
"account.appearanceDark": "深色",
"account.appearanceLabel": "外觀",
"account.appearanceLight": "淺色",
"account.appearanceSystem": "跟隨系統",
"account.avatar": "頭像",
"account.changeEmail.authTip": "一旦您的電子郵件更改,與您的舊電子郵件相關聯的 Google 或 GitHub 帳戶將無法再登錄此帳戶。",
"account.changeEmail.changeTo": "更改為 {{email}}",
@ -209,11 +214,25 @@
"integrations.googleAccount": "Google 賬號登入",
"label.optional": "(選用)",
"language.displayLanguage": "介面語言",
"language.language": "語言",
"language.timezone": "時區",
"license.expiring": "將在 1 天內過期",
"license.expiring_plural": "將在 {{count}} 天后過期",
"license.unlimited": "無限制",
"loading": "載入中",
"mainNav.help.docs": "文件",
"mainNav.help.openMenu": "開啟幫助選單",
"mainNav.home": "首頁",
"mainNav.integrations": "整合",
"mainNav.marketplace": "外掛市場",
"mainNav.webApps.noResults": "未找到 Web 應用",
"mainNav.webApps.searchPlaceholder": "搜尋 Web 應用",
"mainNav.workspace.credits": "{{count}} 點額度",
"mainNav.workspace.creditsUnit": "點額度",
"mainNav.workspace.inviteMembers": "邀請並管理成員",
"mainNav.workspace.openMenu": "開啟工作區選單",
"mainNav.workspace.settings": "設定",
"mainNav.workspace.switchWorkspace": "切換工作區",
"members.admin": "管理員",
"members.adminTip": "能夠建立應用程式和管理團隊設定",
"members.builder": "建築工人",