chore(web): improve web accessibility semantics (#39505)

This commit is contained in:
林玮 (Jade Lin) 2026-07-24 16:16:04 +08:00 committed by GitHub
parent e1495c0bae
commit 7bb09ce039
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 206 additions and 76 deletions

View File

@ -1,7 +1,7 @@
import type { RenderOptions } from '@testing-library/react'
import type { ReactElement } from 'react'
import type { UsagePlanInfo, UsageResetInfo } from '@/app/components/billing/type'
import { screen } from '@testing-library/react'
import { screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import AnnotationFull from '@/app/components/billing/annotation-full'
@ -200,20 +200,22 @@ describe('Billing Page + Plan Integration', () => {
expect(screen.getByText(/plansCommon\.apiRateLimit/i)).toBeInTheDocument()
})
it('should display usage values as "usage / total" format', () => {
it('should expose each quota card and its value through stable semantics', () => {
setupProviderContext({
type: Plan.sandbox,
usage: { buildApps: 3, teamMembers: 1 },
total: { buildApps: 5, teamMembers: 1 },
usage: { teamMembers: 3 },
total: { teamMembers: 5 },
})
render(<PlanComp loc="test" />)
// Check that the buildApps usage fraction "3 / 5" is rendered
const usageContainers = screen.getAllByText('3')
expect(usageContainers.length).toBeGreaterThan(0)
const totalContainers = screen.getAllByText('5')
expect(totalContainers.length).toBeGreaterThan(0)
const quotaCard = screen.getByRole('group', { name: /usagePage\.teamMembers/i })
const quotaLabel = within(quotaCard).getByText(/usagePage\.teamMembers/i)
const quotaValue = within(quotaCard).getByTestId('billing-quota-value')
expect(quotaLabel.tagName).toBe('DT')
expect(quotaValue.tagName).toBe('DD')
expect(quotaValue).toHaveTextContent(/3\s*\/\s*5/)
})
it('should show "unlimited" for infinite quotas (professional API rate limit)', () => {

View File

@ -169,6 +169,9 @@ describe('Pricing Modal Flow', () => {
it('should show plan range switcher (annual billing toggle) by default for cloud', () => {
render(<Pricing onCancel={onCancel} />)
expect(
screen.getByRole('switch', { name: 'billing.plansCommon.yearlyBilling' }),
).toBeInTheDocument()
expect(screen.getByText(/plansCommon\.annualBilling/i)).toBeInTheDocument()
})
@ -284,7 +287,9 @@ describe('Pricing Modal Flow', () => {
const user = userEvent.setup()
render(<Pricing onCancel={onCancel} />)
expect(screen.getByRole('switch')).toBeChecked()
expect(
screen.getByRole('switch', { name: 'billing.plansCommon.yearlyBilling' }),
).toBeChecked()
await user.click(screen.getByRole('button', { name: 'education.useEducationDiscount' }))

View File

@ -1,5 +1,4 @@
'use client'
import type { FC } from 'react'
import { Switch } from '@langgenius/dify-ui/switch'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
@ -14,12 +13,13 @@ type PlanRangeSwitcherProps = {
onChange: (value: PlanRange) => void
}
const PlanRangeSwitcher: FC<PlanRangeSwitcherProps> = ({ value, onChange }) => {
function PlanRangeSwitcher({ value, onChange }: PlanRangeSwitcherProps) {
const { t } = useTranslation()
return (
<div className="flex items-center justify-end gap-x-3 pr-5">
<Switch
aria-label={t(($) => $['plansCommon.yearlyBilling'], { ns: 'billing' })}
size="lg"
checked={value === PlanRange.yearly}
onCheckedChange={(v) => {

View File

@ -154,20 +154,29 @@ const UsageInfo: FC<Props> = ({
}
return (
<div className={cn('flex flex-col gap-2 rounded-xl bg-components-panel-bg p-4', className)}>
<div
role="group"
aria-label={name}
className={cn('flex flex-col gap-2 rounded-xl bg-components-panel-bg p-4', className)}
>
{!hideIcon && Icon && <Icon className="size-4 text-text-tertiary" />}
<div className="flex items-center gap-1">
<div className="system-xs-medium text-text-tertiary">{name}</div>
{tooltip && (
<Infotip aria-label={tooltip} popupClassName="w-[180px] max-w-[180px]">
{tooltip}
</Infotip>
)}
</div>
<div className="flex items-center gap-1 system-md-semibold text-text-primary">
{wrapWithStorageTooltip(usageDisplay)}
{rightInfo}
</div>
<dl className="flex flex-col gap-2">
<dt className="flex items-center gap-1 system-xs-medium text-text-tertiary">
{name}
{tooltip && (
<Infotip aria-label={tooltip} popupClassName="w-[180px] max-w-[180px]">
{tooltip}
</Infotip>
)}
</dt>
<dd
data-testid="billing-quota-value"
className="flex items-center gap-1 system-md-semibold text-text-primary"
>
{wrapWithStorageTooltip(usageDisplay)}
{rightInfo}
</dd>
</dl>
{wrapWithStorageTooltip(bar)}
</div>
)

View File

@ -27,7 +27,8 @@ const createMockBanner = (overrides: Partial<Banner> = {}): Banner =>
const renderBannerItem = (
banner: Banner = createMockBanner(),
props: Partial<ComponentProps<typeof BannerItem>> = {},
) => render(<BannerItem banner={banner} sort={1} language="en-US" {...props} />)
) =>
render(<BannerItem banner={banner} sort={1} language="en-US" titleId="banner-title" {...props} />)
describe('BannerItem', () => {
afterEach(() => {

View File

@ -130,11 +130,13 @@ vi.mock('../banner-item', () => ({
sort,
language,
accountId,
titleId,
}: {
banner: BannerType
sort: number
language: string
accountId?: string
titleId?: string
}) => (
<article
data-testid="banner-item"
@ -143,7 +145,7 @@ vi.mock('../banner-item', () => ({
data-language={language}
data-account-id={accountId}
>
{banner.content.title}
<p id={titleId}>{banner.content.title}</p>
</article>
),
}))
@ -220,6 +222,17 @@ describe('Banner', () => {
expect(secondSlide).toHaveAttribute('inert')
})
it('names each slide with its visible title', () => {
render(<Banner banners={[createMockBanner('1', 'enabled', 'First banner')]} />)
const slide = screen.getByRole('group', { name: 'First banner' })
const title = document.getElementById(slide.getAttribute('aria-labelledby')!)
expect(slide).not.toHaveAttribute('aria-label')
expect(title).toHaveTextContent('First banner')
expect(slide).toContainElement(title)
})
it('keeps one shared control set mounted while selecting a banner', () => {
render(
<Banner

View File

@ -1,5 +1,4 @@
import type { Banner } from '@/models/app'
import { useId } from 'react'
import { trackEvent } from '@/app/components/base/amplitude'
type BannerItemProps = {
@ -7,10 +6,10 @@ type BannerItemProps = {
sort: number
language: string
accountId?: string
titleId: string
}
export function BannerItem({ banner, sort, language, accountId }: BannerItemProps) {
const titleId = useId()
export function BannerItem({ banner, sort, language, accountId, titleId }: BannerItemProps) {
const { category, title, description, 'img-src': imgSrc } = banner.content
const handleBannerClick = () => {

View File

@ -1,7 +1,7 @@
import type { ComponentProps, FocusEvent } from 'react'
import type { Banner as BannerType } from '@/models/app'
import { useAtomValue } from 'jotai'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useId, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { trackEvent } from '@/app/components/base/amplitude'
import { Carousel, useCarousel } from '@/app/components/base/carousel'
@ -23,6 +23,35 @@ type BannerCarouselContentProps = {
language: string
}
type BannerSlideProps = {
banner: BannerType
index: number
isActive: boolean
accountId?: string
language: string
}
function BannerSlide({ banner, index, isActive, accountId, language }: BannerSlideProps) {
const titleId = useId()
return (
<Carousel.Item
data-banner-id={banner.id}
aria-labelledby={titleId}
aria-hidden={!isActive}
inert={!isActive}
>
<BannerItem
banner={banner}
sort={index + 1}
language={language}
accountId={accountId}
titleId={titleId}
/>
</Carousel.Item>
)
}
function BannerCarouselContent({ banners, accountId, language }: BannerCarouselContentProps) {
const { t } = useTranslation()
const { api, selectedIndex } = useCarousel()
@ -120,26 +149,16 @@ function BannerCarouselContent({ banners, accountId, language }: BannerCarouselC
return (
<>
<Carousel.Content aria-live={isPlaying ? 'off' : 'polite'}>
{banners.map((banner, index) => {
const isActive = index === selectedIndex
return (
<Carousel.Item
key={banner.id}
data-banner-id={banner.id}
aria-label={banner.content.title}
aria-hidden={!isActive}
inert={!isActive}
>
<BannerItem
banner={banner}
sort={index + 1}
language={language}
accountId={accountId}
/>
</Carousel.Item>
)
})}
{banners.map((banner, index) => (
<BannerSlide
key={banner.id}
banner={banner}
index={index}
isActive={index === selectedIndex}
accountId={accountId}
language={language}
/>
))}
</Carousel.Content>
{hasFooter ? (

View File

@ -1,3 +1,4 @@
import { screen, within } from '@testing-library/react'
import { renderWorkflowComponent } from '../../__tests__/workflow-test-env'
import EditingTitle from '../editing-title'
@ -24,7 +25,7 @@ describe('EditingTitle', () => {
})
it('should render autosave, published time, and syncing status when the draft has metadata', () => {
const { container } = renderWorkflowComponent(<EditingTitle />, {
renderWorkflowComponent(<EditingTitle />, {
initialStoreState: {
draftUpdatedAt: 1_710_000_000_000,
publishedAt: 1_710_003_600_000,
@ -34,26 +35,44 @@ describe('EditingTitle', () => {
expect(mockFormatTime).toHaveBeenCalledWith(1_710_000_000, 'HH:mm:ss')
expect(mockFormatTimeFromNow).toHaveBeenCalledWith(1_710_003_600_000)
expect(container).toHaveTextContent('workflow.common.autoSaved')
expect(container).toHaveTextContent('08:00:00')
expect(container).toHaveTextContent('workflow.common.published')
expect(container).toHaveTextContent('2 hours ago')
expect(container).toHaveTextContent('workflow.common.syncingData')
const saveStatus = screen.getByRole('status', {
name: 'workflow.common.workflowSaveStatus',
})
const statusContent = within(saveStatus)
expect(statusContent.getByText('workflow.common.autoSaved')).toBeInTheDocument()
expect(statusContent.getByText('workflow.common.published')).toBeInTheDocument()
expect(statusContent.getByText('workflow.common.syncingData')).toBeInTheDocument()
const draftUpdatedTime = statusContent.getByText('08:00:00')
const publishedTime = statusContent.getByText('2 hours ago')
expect(draftUpdatedTime.tagName).toBe('TIME')
expect(draftUpdatedTime).toHaveTextContent('08:00:00')
expect(draftUpdatedTime).toHaveAttribute('datetime', '2024-03-09T16:00:00.000Z')
expect(publishedTime.tagName).toBe('TIME')
expect(publishedTime).toHaveTextContent('2 hours ago')
expect(publishedTime).toHaveAttribute('datetime', '2024-03-09T17:00:00.000Z')
})
it('should render unpublished status without autosave metadata when the workflow has not been published', () => {
const { container } = renderWorkflowComponent(<EditingTitle />, {
it('should expose autosave time separately from the unpublished status', () => {
renderWorkflowComponent(<EditingTitle />, {
initialStoreState: {
draftUpdatedAt: 0,
draftUpdatedAt: 1_710_000_000_000,
publishedAt: 0,
isSyncingWorkflowDraft: false,
},
})
expect(mockFormatTime).not.toHaveBeenCalled()
expect(mockFormatTime).toHaveBeenCalledWith(1_710_000_000, 'HH:mm:ss')
expect(mockFormatTimeFromNow).not.toHaveBeenCalled()
expect(container).toHaveTextContent('workflow.common.unpublished')
expect(container).not.toHaveTextContent('workflow.common.autoSaved')
expect(container).not.toHaveTextContent('workflow.common.syncingData')
const saveStatus = screen.getByRole('status', {
name: 'workflow.common.workflowSaveStatus',
})
const statusContent = within(saveStatus)
expect(statusContent.getByText('workflow.common.autoSaved')).toBeInTheDocument()
expect(statusContent.getByText('workflow.common.unpublished')).toBeInTheDocument()
expect(statusContent.queryByText('workflow.common.syncingData')).not.toBeInTheDocument()
expect(statusContent.getByText('08:00:00').tagName).toBe('TIME')
})
})

View File

@ -4,7 +4,7 @@ import { useStore } from '@/app/components/workflow/store'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import useTimestamp from '@/hooks/use-timestamp'
const EditingTitle = () => {
function EditingTitle() {
const { t } = useTranslation()
const { formatTime } = useTimestamp()
const { formatTimeFromNow } = useFormatTimeFromNow()
@ -13,21 +13,38 @@ const EditingTitle = () => {
const isSyncingWorkflowDraft = useStore((s) => s.isSyncingWorkflowDraft)
return (
<div className="flex h-[18px] min-w-[300px] items-center system-xs-regular whitespace-nowrap text-text-tertiary">
<div
role="status"
aria-label={t(($) => $['common.workflowSaveStatus'], { ns: 'workflow' })}
className="flex h-[18px] min-w-[300px] items-center system-xs-regular whitespace-nowrap text-text-tertiary"
>
{!!draftUpdatedAt && (
<>
{t(($) => $['common.autoSaved'], { ns: 'workflow' })}{' '}
{formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')}
</>
<span className="flex items-center gap-1">
<span>{t(($) => $['common.autoSaved'], { ns: 'workflow' })}</span>
<time dateTime={new Date(draftUpdatedAt).toISOString()}>
{formatTime(draftUpdatedAt / 1000, 'HH:mm:ss')}
</time>
</span>
)}
<span aria-hidden="true" className="mx-1 flex items-center">
·
</span>
{publishedAt ? (
<span className="flex items-center gap-1">
<span>{t(($) => $['common.published'], { ns: 'workflow' })}</span>
<time dateTime={new Date(publishedAt).toISOString()}>
{formatTimeFromNow(publishedAt)}
</time>
</span>
) : (
<span>{t(($) => $['common.unpublished'], { ns: 'workflow' })}</span>
)}
<span className="mx-1 flex items-center">·</span>
{publishedAt
? `${t(($) => $['common.published'], { ns: 'workflow' })} ${formatTimeFromNow(publishedAt)}`
: t(($) => $['common.unpublished'], { ns: 'workflow' })}
{isSyncingWorkflowDraft && (
<>
<span className="mx-1 flex items-center">·</span>
{t(($) => $['common.syncingData'], { ns: 'workflow' })}
<span aria-hidden="true" className="mx-1 flex items-center">
·
</span>
<span>{t(($) => $['common.syncingData'], { ns: 'workflow' })}</span>
</>
)}
</div>

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "تنفيذ سير عمل قياسي",
"plansCommon.workflowExecution.tooltip": "أولوية وسرعة قائمة انتظار تنفيذ سير العمل.",
"plansCommon.year": "سنة",
"plansCommon.yearlyBilling": "الفوترة السنوية",
"plansCommon.yearlyTip": "ادفع لمدة 10 أشهر، واستمتع بسنة كاملة!",
"teamMembers": "أعضاء الفريق",
"triggerLimitModal.description": "لقد وصلت إلى الحد الأقصى لمشغلات أحداث سير العمل لهذه الخطة.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "تم إيقاف عملية سير العمل مؤقتًا",
"common.workflowProcessRunning": "عملية سير العمل قيد التشغيل",
"common.workflowProcessSucceeded": "نجحت عملية سير العمل",
"common.workflowSaveStatus": "حالة حفظ سير العمل",
"customWebhook": "ويب هوك مخصص",
"debug.copyLastRun": "نسخ آخر تشغيل",
"debug.copyLastRunError": "فشل نسخ مدخلات آخر تشغيل",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Standard-Workflow-Ausführung",
"plansCommon.workflowExecution.tooltip": "Priorität und Geschwindigkeit der Arbeitsablauf-Ausführungswarteschlange.",
"plansCommon.year": "Jahr",
"plansCommon.yearlyBilling": "Jährliche Abrechnung",
"plansCommon.yearlyTip": "Erhalten Sie 2 Monate kostenlos durch jährliches Abonnieren!",
"teamMembers": "Teammitglieder",
"triggerLimitModal.description": "Sie haben das Limit der Workflow-Ereignisauslöser für diesen Plan erreicht.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Arbeitsablauf pausiert",
"common.workflowProcessRunning": "Arbeitsablauf wird ausgeführt",
"common.workflowProcessSucceeded": "Arbeitsablauf erfolgreich",
"common.workflowSaveStatus": "Speicherstatus des Workflows",
"customWebhook": "Benutzerdefinierter Webhook",
"debug.copyLastRun": "Letzte Ausführung kopieren",
"debug.copyLastRunError": "Fehler beim Kopieren der letzten Lauf-Eingaben",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Standard Workflow Execution",
"plansCommon.workflowExecution.tooltip": "Workflow execution queue priority and speed.",
"plansCommon.year": "year",
"plansCommon.yearlyBilling": "Yearly billing",
"plansCommon.yearlyTip": "Pay for 10 months, enjoy 1 Year!",
"teamMembers": "Team Members",
"triggerLimitModal.description": "You've reached the limit of workflow event triggers for this plan.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Workflow Process paused",
"common.workflowProcessRunning": "Workflow Process running",
"common.workflowProcessSucceeded": "Workflow Process succeeded",
"common.workflowSaveStatus": "Workflow save status",
"customWebhook": "Custom Webhook",
"debug.copyLastRun": "Copy Last Run",
"debug.copyLastRunError": "Failed to copy last run inputs",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Ejecución estándar del flujo de trabajo",
"plansCommon.workflowExecution.tooltip": "Prioridad y velocidad de la cola de ejecución de flujos de trabajo.",
"plansCommon.year": "año",
"plansCommon.yearlyBilling": "Facturación anual",
"plansCommon.yearlyTip": "¡Obtén 2 meses gratis al suscribirte anualmente!",
"teamMembers": "Miembros del equipo",
"triggerLimitModal.description": "Has alcanzado el límite de activadores de eventos de flujo de trabajo para este plan.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Proceso de flujo de trabajo pausado",
"common.workflowProcessRunning": "Proceso de flujo de trabajo en ejecución",
"common.workflowProcessSucceeded": "Proceso de flujo de trabajo completado correctamente",
"common.workflowSaveStatus": "Estado de guardado del flujo de trabajo",
"customWebhook": "Webhook personalizado",
"debug.copyLastRun": "Copiar última ejecución",
"debug.copyLastRunError": "No se pudo copiar las entradas de la última ejecución",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "اجرای جریان کاری استاندارد",
"plansCommon.workflowExecution.tooltip": "اولویت و سرعت صف اجرای گردش کار.",
"plansCommon.year": "سال",
"plansCommon.yearlyBilling": "صورتحساب سالانه",
"plansCommon.yearlyTip": "با اشتراک سالانه 2 ماه رایگان دریافت کنید!",
"teamMembers": "اعضای تیم",
"triggerLimitModal.description": "شما به حد مجاز تریگرهای رویداد گردش کار برای این طرح رسیده‌اید.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "فرآیند گردش کار متوقف شده است",
"common.workflowProcessRunning": "فرآیند گردش کار در حال اجراست",
"common.workflowProcessSucceeded": "فرآیند گردش کار با موفقیت انجام شد",
"common.workflowSaveStatus": "وضعیت ذخیره‌سازی گردش کار",
"customWebhook": "وب‌هوک سفارشی",
"debug.copyLastRun": "کپی آخرین اجرا",
"debug.copyLastRunError": "کپی ورودی‌های آخرین اجرا ناموفق بود",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Exécution du flux de travail standard",
"plansCommon.workflowExecution.tooltip": "Priorité et vitesse de la file d'exécution des flux de travail.",
"plansCommon.year": "année",
"plansCommon.yearlyBilling": "Facturation annuelle",
"plansCommon.yearlyTip": "Obtenez 2 mois gratuitement en vous abonnant annuellement !",
"teamMembers": "Membres de l'équipe",
"triggerLimitModal.description": "Vous avez atteint la limite des déclencheurs d'événements de flux de travail pour ce plan.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Processus de flux de travail en pause",
"common.workflowProcessRunning": "Processus de flux de travail en cours d'exécution",
"common.workflowProcessSucceeded": "Processus de flux de travail réussi",
"common.workflowSaveStatus": "État denregistrement du workflow",
"customWebhook": "Webhook personnalisé",
"debug.copyLastRun": "Copier la dernière exécution",
"debug.copyLastRunError": "Échec de la copie des entrées de la dernière exécution",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "मानक कार्यप्रवाह निष्पादन",
"plansCommon.workflowExecution.tooltip": "वर्कफ़्लो निष्पादन कतार की प्राथमिकता और गति।",
"plansCommon.year": "साल",
"plansCommon.yearlyBilling": "वार्षिक बिलिंग",
"plansCommon.yearlyTip": "वार्षिक सब्सक्राइब करने पर 2 महीने मुफ्त पाएं!",
"teamMembers": "टीम के सदस्य",
"triggerLimitModal.description": "आप इस योजना के लिए वर्कफ़्लो इवेंट ट्रिगर्स की सीमा तक पहुँच चुके हैं।",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "कार्यप्रवाह प्रक्रिया रुकी हुई है",
"common.workflowProcessRunning": "कार्यप्रवाह प्रक्रिया चल रही है",
"common.workflowProcessSucceeded": "कार्यप्रवाह प्रक्रिया सफल रही",
"common.workflowSaveStatus": "वर्कफ़्लो सहेजने की स्थिति",
"customWebhook": "कस्टम वेबहुक",
"debug.copyLastRun": "अंतिम रन कॉपी करें",
"debug.copyLastRunError": "अंतिम रन इनपुट को कॉपी करने में विफल",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Eksekusi Alur Kerja Standar",
"plansCommon.workflowExecution.tooltip": "Prioritas dan kecepatan antrian eksekusi alur kerja.",
"plansCommon.year": "tahun",
"plansCommon.yearlyBilling": "Penagihan tahunan",
"plansCommon.yearlyTip": "Bayar selama 10 bulan, nikmati 1 tahun!",
"teamMembers": "Anggota Tim",
"triggerLimitModal.description": "Anda telah mencapai batas pemicu acara alur kerja untuk paket ini.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Proses Alur Kerja dijeda",
"common.workflowProcessRunning": "Proses Alur Kerja sedang berjalan",
"common.workflowProcessSucceeded": "Proses Alur Kerja berhasil",
"common.workflowSaveStatus": "Status penyimpanan alur kerja",
"customWebhook": "Webhook Kustom",
"debug.copyLastRun": "Salin Eksekusi Terakhir",
"debug.copyLastRunError": "Gagal menyalin input eksekusi terakhir",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Esecuzione del flusso di lavoro standard",
"plansCommon.workflowExecution.tooltip": "Priorità e velocità della coda di esecuzione del flusso di lavoro.",
"plansCommon.year": "anno",
"plansCommon.yearlyBilling": "Fatturazione annuale",
"plansCommon.yearlyTip": "Ottieni 2 mesi gratis abbonandoti annualmente!",
"teamMembers": "Membri del team",
"triggerLimitModal.description": "Hai raggiunto il limite degli eventi di attivazione del flusso di lavoro per questo piano.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Processo di flusso di lavoro in pausa",
"common.workflowProcessRunning": "Processo di flusso di lavoro in esecuzione",
"common.workflowProcessSucceeded": "Processo di flusso di lavoro riuscito",
"common.workflowSaveStatus": "Stato di salvataggio del flusso di lavoro",
"customWebhook": "Webhook personalizzato",
"debug.copyLastRun": "Copia ultimo eseguito",
"debug.copyLastRunError": "Impossibile copiare gli input dell'ultima esecuzione",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "標準ワークフロー実行キュー",
"plansCommon.workflowExecution.tooltip": "ワークフローの実行キューの優先度と実行速度。",
"plansCommon.year": "年",
"plansCommon.yearlyBilling": "年間請求",
"plansCommon.yearlyTip": "10 ヶ月分支払って、1 年間楽しもう!",
"teamMembers": "チームメンバー",
"triggerLimitModal.description": "このプランでは、ワークフローのトリガーイベント数の上限に達しています。",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "ワークフロー処理は一時停止中です",
"common.workflowProcessRunning": "ワークフロー処理を実行中です",
"common.workflowProcessSucceeded": "ワークフロー処理が成功しました",
"common.workflowSaveStatus": "ワークフローの保存状態",
"customWebhook": "カスタムWebhook",
"debug.copyLastRun": "最後の実行をコピー",
"debug.copyLastRunError": "最後の実行の入力をコピーできませんでした",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "표준 워크플로 실행",
"plansCommon.workflowExecution.tooltip": "워크플로 실행 대기열 우선순위 및 속도.",
"plansCommon.year": "년",
"plansCommon.yearlyBilling": "연간 결제",
"plansCommon.yearlyTip": "연간 구독 시 2개월 무료!",
"teamMembers": "팀원들",
"triggerLimitModal.description": "이 요금제의 워크플로 이벤트 트리거 한도에 도달했습니다.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "워크플로우 과정 일시 중지됨",
"common.workflowProcessRunning": "워크플로우 과정 실행 중",
"common.workflowProcessSucceeded": "워크플로우 과정 성공",
"common.workflowSaveStatus": "워크플로우 저장 상태",
"customWebhook": "맞춤 웹훅",
"debug.copyLastRun": "마지막 실행 복사",
"debug.copyLastRunError": "마지막 실행 입력을 복사하는 데 실패했습니다.",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Standard Workflow Execution",
"plansCommon.workflowExecution.tooltip": "Workflow execution queue priority and speed.",
"plansCommon.year": "year",
"plansCommon.yearlyBilling": "Jaarlijkse facturering",
"plansCommon.yearlyTip": "Pay for 10 months, enjoy 1 Year!",
"teamMembers": "Team Members",
"triggerLimitModal.description": "You've reached the limit of workflow event triggers for this plan.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Workflowproces gepauzeerd",
"common.workflowProcessRunning": "Workflowproces wordt uitgevoerd",
"common.workflowProcessSucceeded": "Workflowproces geslaagd",
"common.workflowSaveStatus": "Opslagstatus van workflow",
"customWebhook": "Custom Webhook",
"debug.copyLastRun": "Copy Last Run",
"debug.copyLastRunError": "Failed to copy last run inputs",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Standardowe wykonywanie przepływu pracy",
"plansCommon.workflowExecution.tooltip": "Priorytet i szybkość wykonywania kolejki przepływu pracy.",
"plansCommon.year": "rok",
"plansCommon.yearlyBilling": "Rozliczenie roczne",
"plansCommon.yearlyTip": "Otrzymaj 2 miesiące za darmo, subskrybując rocznie!",
"teamMembers": "Członkowie zespołu",
"triggerLimitModal.description": "Osiągnąłeś limit wyzwalaczy zdarzeń przepływu pracy dla tego planu.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Proces przepływu pracy wstrzymany",
"common.workflowProcessRunning": "Proces przepływu pracy w toku",
"common.workflowProcessSucceeded": "Proces przepływu pracy zakończony powodzeniem",
"common.workflowSaveStatus": "Stan zapisu przepływu pracy",
"customWebhook": "Niestandardowy webhook",
"debug.copyLastRun": "Kopiuj ostatnie uruchomienie",
"debug.copyLastRunError": "Nie udało się skopiować danych wejściowych z ostatniego uruchomienia",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Execução Padrão de Fluxo de Trabalho",
"plansCommon.workflowExecution.tooltip": "Prioridade e velocidade da fila de execução de fluxo de trabalho.",
"plansCommon.year": "ano",
"plansCommon.yearlyBilling": "Cobrança anual",
"plansCommon.yearlyTip": "Receba 2 meses grátis assinando anualmente!",
"teamMembers": "Membros da equipe",
"triggerLimitModal.description": "Você atingiu o limite de eventos de gatilho de fluxo de trabalho para este plano.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Processo de fluxo de trabalho pausado",
"common.workflowProcessRunning": "Processo de fluxo de trabalho em execução",
"common.workflowProcessSucceeded": "Processo de fluxo de trabalho bem-sucedido",
"common.workflowSaveStatus": "Status de salvamento do fluxo de trabalho",
"customWebhook": "Webhook Personalizado",
"debug.copyLastRun": "Copiar Última Execução",
"debug.copyLastRunError": "Falha ao copiar as entradas da última execução",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Executarea fluxului de lucru standard",
"plansCommon.workflowExecution.tooltip": "Prioritatea și viteza cozii de execuție a fluxului de lucru.",
"plansCommon.year": "an",
"plansCommon.yearlyBilling": "Facturare anuală",
"plansCommon.yearlyTip": "Obțineți 2 luni gratuite prin abonarea anuală!",
"teamMembers": "Membrii echipei",
"triggerLimitModal.description": "Ai atins limita de evenimente declanșatoare de flux de lucru pentru acest plan.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Procesul de flux de lucru este întrerupt",
"common.workflowProcessRunning": "Procesul de flux de lucru rulează",
"common.workflowProcessSucceeded": "Procesul de flux de lucru a reușit",
"common.workflowSaveStatus": "Starea salvării fluxului de lucru",
"customWebhook": "Webhook personalizat",
"debug.copyLastRun": "Copiază ultima execuție",
"debug.copyLastRunError": "Nu s-au putut copia ultimele intrări de rulare",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Стандартное выполнение рабочего процесса",
"plansCommon.workflowExecution.tooltip": "Приоритет и скорость выполнения очереди рабочих процессов.",
"plansCommon.year": "год",
"plansCommon.yearlyBilling": "Ежегодная оплата",
"plansCommon.yearlyTip": "Получите 2 месяца бесплатно, подписавшись на год!",
"teamMembers": "Члены команды",
"triggerLimitModal.description": "Вы достигли предела триггеров событий рабочего процесса для этого плана.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Процесс рабочего процесса приостановлен",
"common.workflowProcessRunning": "Процесс рабочего процесса выполняется",
"common.workflowProcessSucceeded": "Процесс рабочего процесса выполнен успешно",
"common.workflowSaveStatus": "Статус сохранения рабочего процесса",
"customWebhook": "Пользовательский вебхук",
"debug.copyLastRun": "Копировать последний запуск",
"debug.copyLastRunError": "Не удалось скопировать последние входные данные выполнения",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Izvajanje standardnega delovnega procesa",
"plansCommon.workflowExecution.tooltip": "Prednostna vrstni red in hitrost izvajanja delovnega toka.",
"plansCommon.year": "leto",
"plansCommon.yearlyBilling": "Letno obračunavanje",
"plansCommon.yearlyTip": "Z letno naročnino pridobite 2 meseca brezplačno!",
"teamMembers": "Člani ekipe",
"triggerLimitModal.description": "Dosegli ste omejitev sprožilcev dogodkov delovnega toka za ta načrt.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Delovni postopek je začasno ustavljen",
"common.workflowProcessRunning": "Delovni postopek se izvaja",
"common.workflowProcessSucceeded": "Delovni postopek je uspel",
"common.workflowSaveStatus": "Stanje shranjevanja delovnega toka",
"customWebhook": "Prilagojeni spletni kavelj",
"debug.copyLastRun": "Kopiraj zadnji zagon",
"debug.copyLastRunError": "Kopiranje vhodov zadnjega zagona ni uspelo",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "การดำเนินงานเวิร์กโฟลว์มาตรฐาน",
"plansCommon.workflowExecution.tooltip": "ลำดับความสำคัญและความเร็วของคิวการดำเนินงานของเวิร์กโฟลว์",
"plansCommon.year": "ปี",
"plansCommon.yearlyBilling": "การเรียกเก็บเงินรายปี",
"plansCommon.yearlyTip": "รับฟรี 2 เดือนโดยสมัครสมาชิกรายปี!",
"teamMembers": "สมาชิกในทีม",
"triggerLimitModal.description": "คุณได้ถึงขีดจำกัดของทริกเกอร์เหตุการณ์เวิร์กโฟลว์สำหรับแผนนี้แล้ว",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "กระบวนการเวิร์กโฟลว์หยุดชั่วคราว",
"common.workflowProcessRunning": "กระบวนการเวิร์กโฟลว์กำลังทำงาน",
"common.workflowProcessSucceeded": "กระบวนการเวิร์กโฟลว์สำเร็จ",
"common.workflowSaveStatus": "สถานะการบันทึกเวิร์กโฟลว์",
"customWebhook": "เว็บฮุกที่กำหนดเอง",
"debug.copyLastRun": "คัดลอกการทำงานล่าสุด",
"debug.copyLastRunError": "ไม่สามารถคัดลอกข้อมูลการทำงานครั้งสุดท้ายได้",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Standart İş Akışı Yürütme",
"plansCommon.workflowExecution.tooltip": "İş akışı yürütme kuyruğu önceliği ve hızı.",
"plansCommon.year": "yıl",
"plansCommon.yearlyBilling": "Yıllık faturalandırma",
"plansCommon.yearlyTip": "Yıllık abonelikle 2 ay ücretsiz!",
"teamMembers": "Ekip Üyeleri",
"triggerLimitModal.description": "Bu plan için iş akışı etkinliği tetikleyici sınırına ulaştınız.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "İş Akışı Süreci duraklatıldı",
"common.workflowProcessRunning": "İş Akışı Süreci çalışıyor",
"common.workflowProcessSucceeded": "İş Akışı Süreci başarılı oldu",
"common.workflowSaveStatus": "İş akışı kaydetme durumu",
"customWebhook": "Özel Webhook",
"debug.copyLastRun": "Son Çalışmayı Kopyala",
"debug.copyLastRunError": "Son çalışma girdilerini kopyalamak başarısız oldu.",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Виконання стандартного робочого процесу",
"plansCommon.workflowExecution.tooltip": "Пріоритет і швидкість виконання черги робочого процесу.",
"plansCommon.year": "рік",
"plansCommon.yearlyBilling": "Щорічна оплата",
"plansCommon.yearlyTip": "Отримайте 2 місяці безкоштовно, оформивши річну підписку!",
"teamMembers": "Члени команди",
"triggerLimitModal.description": "Ви досягли ліміту тригерів подій робочого процесу для цього плану.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Процес робочого потоку призупинено",
"common.workflowProcessRunning": "Процес робочого потоку виконується",
"common.workflowProcessSucceeded": "Процес робочого потоку успішно завершено",
"common.workflowSaveStatus": "Стан збереження робочого процесу",
"customWebhook": "Користувацький вебхук",
"debug.copyLastRun": "Копіювати останній запуск",
"debug.copyLastRunError": "Не вдалося скопіювати вхідні дані останнього виконання",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "Thực thi Quy trình Làm việc Chuẩn",
"plansCommon.workflowExecution.tooltip": "Ưu tiên và tốc độ hàng đợi thực thi quy trình làm việc.",
"plansCommon.year": "năm",
"plansCommon.yearlyBilling": "Thanh toán hằng năm",
"plansCommon.yearlyTip": "Nhận 2 tháng miễn phí khi đăng ký hàng năm!",
"teamMembers": "Các thành viên trong nhóm",
"triggerLimitModal.description": "Bạn đã đạt đến giới hạn kích hoạt sự kiện quy trình cho gói này.",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "Quy trình làm việc đã tạm dừng",
"common.workflowProcessRunning": "Quy trình làm việc đang chạy",
"common.workflowProcessSucceeded": "Quy trình làm việc thành công",
"common.workflowSaveStatus": "Trạng thái lưu quy trình làm việc",
"customWebhook": "Webhook Tùy Chỉnh",
"debug.copyLastRun": "Sao chép lần chạy cuối",
"debug.copyLastRunError": "Không thể sao chép đầu vào của lần chạy trước",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "标准工作流执行队列",
"plansCommon.workflowExecution.tooltip": "工作流的执行队列优先级与运行速度。",
"plansCommon.year": "年",
"plansCommon.yearlyBilling": "按年计费",
"plansCommon.yearlyTip": "支付 10 个月,享受 1 年!",
"teamMembers": "团队成员",
"triggerLimitModal.description": "您已达到此计划上工作流的触发器事件数限制。",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "工作流运行已暂停",
"common.workflowProcessRunning": "工作流运行中",
"common.workflowProcessSucceeded": "工作流运行成功",
"common.workflowSaveStatus": "工作流保存状态",
"customWebhook": "自定义 Webhook",
"debug.copyLastRun": "复制上次运行值",
"debug.copyLastRunError": "复制上次运行输入失败",

View File

@ -151,6 +151,7 @@
"plansCommon.workflowExecution.standard": "標準工作流程執行",
"plansCommon.workflowExecution.tooltip": "工作流程執行隊列的優先順序與速度。",
"plansCommon.year": "年",
"plansCommon.yearlyBilling": "按年計費",
"plansCommon.yearlyTip": "訂閱年度計劃可免費獲得 2 個月!",
"teamMembers": "團隊成員",
"triggerLimitModal.description": "您已達到此方案的工作流程事件觸發上限。",

View File

@ -280,6 +280,7 @@
"common.workflowProcessPaused": "工作流運行已暫停",
"common.workflowProcessRunning": "工作流運行中",
"common.workflowProcessSucceeded": "工作流運行成功",
"common.workflowSaveStatus": "工作流程儲存狀態",
"customWebhook": "自訂 Webhook",
"debug.copyLastRun": "複製上一次運行",
"debug.copyLastRunError": "未能複製上一次運行的輸入",