Compare commits

..

3 Commits

Author SHA1 Message Date
huang47
ede5556644 fix(ci): strip public assets from e2e coverage 2026-06-24 18:01:45 -07:00
Benjamin Lu
da55529d23 GTM-93 point Windows download at comfy.org proxy (#12974)
## Summary

- Point the website Windows desktop download URL at
`https://comfy.org/download/windows/nsis/x64`.
- Keep macOS on the existing ToDesktop URL.
- Update the download page smoke test to expect the new Windows href.

## Context

This is the frontend leg of the GTM-93 Windows MVP. ToDesktop still
controls `download.comfy.org`; instead of changing DNS, the website
sends Windows users to a controlled `comfy.org` proxy path that the
router PR handles. The proxy forwards to ToDesktop and adds a tokenized
`Content-Disposition` filename for Desktop to consume on Windows.

Linear:
https://linear.app/comfyorg/issue/GTM-93/fix-posthog-identify-call-unblock-funnel-attribution-desktop-funnel
Router PR: https://github.com/Comfy-Org/comfy-router/pull/33
Desktop PR: https://github.com/Comfy-Org/Comfy-Desktop/pull/1149

## Validation

- `pnpm --filter @comfyorg/website run typecheck`
- `pnpm --filter @comfyorg/website run build`
- `pnpm --filter @comfyorg/website exec playwright test
e2e/download.spec.ts`
- pre-commit: `pnpm typecheck`, `pnpm typecheck:website`
2026-06-24 17:29:53 -07:00
Dante
52d430d1b6 fix(billing): repoint direct-bypass billing consumers to the facade (B3) (FE-933) (#12643)
## What
**B3 — Repoint direct-bypass billing consumers to the facade.** Billing
data was read from the legacy `useSubscription` store / `authStore`
directly (empty or personal-only for team workspaces) instead of the
workspace-aware `useBillingContext` facade.

FE-933 (parent FE-903).

> **Stacked on #12622 (B2 / FE-904)** — depends on the facade `tier` /
`renewalDate` fields added there. Base is the B2 branch; retarget to
`main` once B2 merges.

## Repointed consumers
- **T3 — `SubscribeButton.vue`**: `subscribe_clicked` telemetry
`current_tier` ← facade `tier` (was wrong/empty for team users)
- **T4 — `PostHogTelemetryProvider.ts`**: PostHog `subscription_tier`
person property ← facade `tier` watch (tier-segmented analytics was
polluted for team users)
- **T5 — `FreeTierDialogContent.vue`**: next-refresh date ← facade raw
ISO `renewalDate`, formatted at the display site (the line silently
disappeared for team users)
- **`useSubscriptionActions.handleRefresh` + `SettingDialog`
credits-nav**: balance refresh ← facade `fetchBalance()` (was legacy
`/customers`-only `authActions.fetchBalance`)
- **`CurrentUserPopoverLegacy.vue`**: tier badge / balance / skeleton /
refreshes ← facade (`tier`, `balance`, `isLoading`, `fetchStatus`,
`fetchBalance`); tier name via shared `useWorkspaceTierLabel` instead of
a duplicated mapping
- **`PricingTable.vue`**: `isActiveSubscription` / `isFreeTier` / `tier`
/ yearly-vs-monthly ← facade; the billing-portal flow
(`accessBillingPortal` deep-links + proration) is intentionally
unchanged — facade `manageSubscription` is not behavior-identical

## Out of scope (triaged)
- `TopUpCreditsDialogContentLegacy` / `SubscriptionPanelContentLegacy` /
`useSubscriptionDialog` / cancellation watcher — legacy-mode-only
surfaces decommissioned by B1 (FE-966); repointing is churn, and
`useSubscriptionDialog` would create a legacy↔facade cycle
- `LegacyCreditsPanel` / `UserCredit` — deleted/orphaned by FE-964
(#12734); its successor `CreditsPanel.vue` keeps an
`authStore.lastBalanceUpdateTime` watch (no facade equivalent yet) —
follow-up after FE-964 lands

## Known semantic deltas (intentional, match shipped facade consumers)
- Balance-refresh failures no longer toast: legacy
`authActions.fetchBalance` wrapped errors with a toast; facade
`fetchBalance` rejections are void-ed, same as
`CurrentUserPopoverWorkspace` / `SubscriptionPanelContentWorkspace`.
Facade-level error surfacing is a follow-up.
- Popover skeleton keys on facade `isLoading` (init-time) rather than
per-fetch `isFetchingBalance`, matching the workspace popover.

## Tests
- New behavioral coverage: FreeTier renewal-date render/disappear,
popover tier badge + balance from facade, current-plan highlight from
facade tier+duration, facade-vs-legacy fetchBalance tripwire, PostHog
`subscription_tier` from facade tier.
- Local gates clean (typecheck / lint / format / dead-code); touched
unit files 71/71 pass.

## E2E coverage
Browser regression tests live in the stacked #12760
(`billingFacadeConsumers.spec.ts`, `@cloud`): avatar popover tier badge
+ balance, and the free-tier dialog renewal-date line (T5) rendered from
the facade. The team-user telemetry fixes (PostHog person property,
telemetry payload) are non-UI observables covered by unit tests that
mock only the facade and fail on revert.

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2026-06-25 00:09:14 +00:00
31 changed files with 578 additions and 1598 deletions

View File

@@ -88,9 +88,9 @@ jobs:
- name: Strip non-source entries from coverage
if: steps.coverage-shards.outputs.has-coverage == 'true'
run: |
# Drop served bundle scripts (localhost-8188/assets/*.js) that V8 records but have no source file on disk, which would abort genhtml.
lcov --remove coverage/playwright/coverage.lcov \
'*localhost-8188*' \
'assets/images/*' \
-o coverage/playwright/coverage.lcov \
--ignore-errors unused
wc -l coverage/playwright/coverage.lcov
@@ -121,7 +121,8 @@ jobs:
--title "ComfyUI E2E Coverage" \
--no-function-coverage \
--precision 1 \
--ignore-errors source,unmapped
--ignore-errors source,unmapped,range \
--synthesize-missing
- name: Upload HTML report artifact
if: steps.coverage-shards.outputs.has-coverage == 'true'

View File

@@ -47,6 +47,11 @@ test.describe('Download page @smoke', () => {
const downloadBtn = hero.getByRole('link', { name: /DOWNLOAD DESKTOP/i })
await expect(downloadBtn).toBeVisible()
await expect(downloadBtn).toHaveAttribute('target', '_blank')
await expect(downloadBtn).toHaveAttribute(
'href',
'https://comfy.org/download/windows/nsis/x64'
)
await expect(downloadBtn).toHaveAttribute('data-astro-prefetch', 'false')
const githubBtn = hero.getByRole('link', { name: /INSTALL FROM GITHUB/i })
await expect(githubBtn).toBeVisible()
@@ -73,7 +78,7 @@ test.describe('Download page @smoke', () => {
})
const windowsBtn = hero.locator(
'a[href="https://download.comfy.org/windows/nsis/x64"]'
'a[href="https://comfy.org/download/windows/nsis/x64"]'
)
await expect(windowsBtn).toBeVisible()
await expect(windowsBtn).toHaveText(/DOWNLOAD DESKTOP/i)

View File

@@ -72,6 +72,7 @@ const buttons = computed<ButtonSpec[]>(() => {
size="lg"
:class="customClass"
:aria-label="btn.ariaLabel"
:data-astro-prefetch="btn.key === 'windows' ? 'false' : undefined"
@click="captureDownloadClick(btn.key)"
>
<span class="inline-flex items-center gap-2">

View File

@@ -3,7 +3,7 @@ import { computed, onMounted, ref } from 'vue'
import { externalLinks } from '@/config/routes'
export const downloadUrls = {
windows: 'https://download.comfy.org/windows/nsis/x64',
windows: 'https://comfy.org/download/windows/nsis/x64',
macArm: 'https://download.comfy.org/mac/dmg/arm64'
} as const

View File

@@ -0,0 +1,165 @@
import { expect } from '@playwright/test'
import type { Page } from '@playwright/test'
import type { CloudSubscriptionStatusResponse } from '@/platform/cloud/subscription/composables/useSubscription'
import type { RemoteConfig } from '@/platform/remoteConfig/types'
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import { mockSystemStats } from '@e2e/fixtures/data/systemStats'
import { CloudAuthHelper } from '@e2e/fixtures/helpers/CloudAuthHelper'
/**
* Billing facade consumers — FE-933 (B3) regression.
*
* The repointed surfaces (avatar popover tier badge / balance, free-tier
* dialog renewal date) must keep rendering from `useBillingContext`, which in
* a personal workspace routes through the legacy `/customers/*` endpoints
* (mocked here). Drives a raw `page` (not the `comfyPage` fixture) so the
* cloud app boots against fully mocked endpoints — same pattern as
* creditsTile.spec.ts. `team_workspaces_enabled: false` keeps the topbar on
* the legacy popover variant that FE-933 repointed.
*/
const APP_URL = process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188'
const jsonRoute = (body: unknown) => ({
status: 200,
contentType: 'application/json',
body: JSON.stringify(body)
})
async function mockCloudBoot(
page: Page,
subscriptionStatus: CloudSubscriptionStatusResponse,
remoteConfig: RemoteConfig = { team_workspaces_enabled: false }
) {
await page.route('**/api/features', (r) => r.fulfill(jsonRoute(remoteConfig)))
await page.route('**/api/system_stats', (r) =>
r.fulfill(jsonRoute(mockSystemStats))
)
await page.route('**/api/users', (r) =>
r.fulfill(
jsonRoute({
storage: 'server',
migrated: true,
users: { 'test-user-e2e': 'E2E Test User' }
})
)
)
// TutorialCompleted suppresses the new-user template browser, whose modal
// overlay would otherwise intercept clicks on the topbar.
await page.route('**/api/settings', (r) =>
r.fulfill(jsonRoute({ 'Comfy.TutorialCompleted': true }))
)
await page.route('**/api/userdata**', (r) => r.fulfill(jsonRoute([])))
await page.route('**/api/extensions', (r) => r.fulfill(jsonRoute([])))
await page.route('**/api/object_info', (r) => r.fulfill(jsonRoute({})))
await page.route('**/api/global_subgraphs', (r) => r.fulfill(jsonRoute({})))
await page.route('**/api/i18n', (r) => r.fulfill(jsonRoute({})))
await page.route('**/api/auth/session', (r) =>
r.fulfill(jsonRoute({ token: 'mock-workspace-token' }))
)
await page.route('**/releases**', (r) => r.fulfill(jsonRoute([])))
// Single personal workspace: keeps the billing facade on the legacy
// `/customers/*` path when team workspaces are enabled.
await page.route('**/api/workspaces', (r) =>
r.fulfill(
jsonRoute({
workspaces: [
{
id: 'ws-personal',
name: 'Personal Workspace',
type: 'personal',
role: 'owner'
}
]
})
)
)
await page.route('**/customers/cloud-subscription-status', (r) =>
r.fulfill(jsonRoute(subscriptionStatus))
)
await page.route('**/customers/balance', (r) =>
r.fulfill(
jsonRoute({
amount_micros: 6000, // -> 12,660 credits
currency: 'usd',
effective_balance_micros: 6000
})
)
)
}
async function bootApp(page: Page) {
const auth = new CloudAuthHelper(page)
await auth.mockAuth()
await page.addInitScript(() => {
localStorage.setItem('Comfy.userId', 'test-user-e2e')
})
await page.goto(APP_URL)
await page.waitForFunction(() => !!window.app?.extensionManager, null, {
timeout: 45_000
})
}
test.describe('Billing facade consumers (FE-933)', { tag: '@cloud' }, () => {
test('avatar popover renders tier badge and balance from the facade', async ({
page
}) => {
test.setTimeout(60_000)
await mockCloudBoot(page, {
is_active: true,
subscription_tier: 'PRO',
subscription_duration: 'MONTHLY',
renewal_date: '2099-02-20T10:00:00Z',
end_date: null
})
await bootApp(page)
await page.getByRole('button', { name: 'Current user' }).click()
const popover = page.locator('.current-user-popover')
await expect(popover).toBeVisible()
await expect(popover.getByText('Pro', { exact: true })).toBeVisible()
await expect(popover.getByText('12,660')).toBeVisible()
await expect(popover.getByTestId('add-credits-button')).toBeVisible()
})
test('free-tier dialog shows the renewal date from the facade', async ({
page
}) => {
test.setTimeout(60_000)
// Boots with team workspaces enabled (production shape); the facade still
// routes a personal workspace through `/customers/*`. With subscription
// gating on, an inactive FREE user gets the "Subscribe to run" button,
// which opens the free-tier dialog on click. (refreshRemoteConfig
// overwrites window.__CONFIG__ from /api/features, so the flags must come
// from the features mock, not an init script.)
await mockCloudBoot(
page,
{
is_active: false,
subscription_tier: 'FREE',
subscription_duration: 'MONTHLY',
// 10:00Z keeps the en-US calendar date stable across CI timezones.
renewal_date: '2099-02-20T10:00:00Z',
end_date: null
},
{ team_workspaces_enabled: true, subscription_required: true }
)
await bootApp(page)
await page.getByTestId('subscribe-to-run-button').click()
// T5: the dialog must source the date from facade renewalDate — when this
// line read the legacy store it silently vanished for team users.
await expect(
page.getByText('Your credits refresh on Feb 20, 2099.')
).toBeVisible()
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.47.4",
"version": "1.47.3",
"private": true,
"description": "Official front-end implementation of ComfyUI",
"homepage": "https://comfy.org",

View File

@@ -1,37 +1,18 @@
import { render, screen } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { h, ref } from 'vue'
import { defineComponent, h, ref } from 'vue'
import { createI18n } from 'vue-i18n'
import { formatCreditsFromCents } from '@/base/credits/comfyCredits'
import type { BalanceInfo, SubscriptionInfo } from '@/composables/billing/types'
import enMessages from '@/locales/en/main.json' with { type: 'json' }
import CurrentUserPopoverLegacy from './CurrentUserPopoverLegacy.vue'
// Mock all firebase modules
vi.mock('firebase/app', () => ({
initializeApp: vi.fn(),
getApp: vi.fn()
}))
vi.mock('firebase/auth', () => ({
getAuth: vi.fn(),
setPersistence: vi.fn(),
browserLocalPersistence: {},
onAuthStateChanged: vi.fn(),
signInWithEmailAndPassword: vi.fn(),
signOut: vi.fn()
}))
// Mock pinia
vi.mock('pinia')
// Mock showSettingsDialog and showTopUpCreditsDialog
const mockShowSettingsDialog = vi.fn()
const mockShowTopUpCreditsDialog = vi.fn()
// Mock the settings dialog composable
vi.mock('@/platform/settings/composables/useSettingsDialog', () => ({
useSettingsDialog: vi.fn(() => ({
show: mockShowSettingsDialog,
@@ -40,7 +21,6 @@ vi.mock('@/platform/settings/composables/useSettingsDialog', () => ({
}))
}))
// Mock window.open
const originalWindowOpen = window.open
beforeEach(() => {
window.open = vi.fn()
@@ -50,7 +30,6 @@ afterAll(() => {
window.open = originalWindowOpen
})
// Mock the useCurrentUser composable
const mockHandleSignOut = vi.fn()
vi.mock('@/composables/auth/useCurrentUser', () => ({
useCurrentUser: vi.fn(() => ({
@@ -61,60 +40,50 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({
}))
}))
// Mock the useAuthActions composable
const mockLogout = vi.fn()
vi.mock('@/composables/auth/useAuthActions', () => ({
useAuthActions: vi.fn(() => ({
fetchBalance: vi.fn().mockResolvedValue(undefined),
logout: mockLogout
}))
}))
// Mock the dialog service
vi.mock('@/services/dialogService', () => ({
useDialogService: vi.fn(() => ({
showTopUpCreditsDialog: mockShowTopUpCreditsDialog
}))
}))
// Mock the authStore with hoisted state for per-test manipulation
const mockAuthStoreState = vi.hoisted(() => ({
balance: {
amount_micros: 100_000,
effective_balance_micros: 100_000,
currency: 'usd'
} as {
amount_micros?: number
effective_balance_micros?: number
currency: string
},
isFetchingBalance: false
}))
function makeSubscription(
overrides: Partial<SubscriptionInfo> = {}
): SubscriptionInfo {
return {
isActive: true,
tier: 'CREATOR',
duration: 'MONTHLY',
planSlug: null,
renewalDate: null,
endDate: null,
isCancelled: false,
hasFunds: true,
...overrides
}
}
vi.mock('@/stores/authStore', () => ({
useAuthStore: vi.fn(() => ({
getAuthHeader: vi
.fn()
.mockResolvedValue({ Authorization: 'Bearer mock-token' }),
balance: mockAuthStoreState.balance,
isFetchingBalance: mockAuthStoreState.isFetchingBalance
}))
}))
// Mock the useSubscription composable
const mockFetchStatus = vi.fn().mockResolvedValue(undefined)
const mockFetchBalance = vi.fn().mockResolvedValue(undefined)
const mockIsActiveSubscription = ref(true)
const mockIsFreeTier = ref(false)
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: vi.fn(() => ({
isActiveSubscription: ref(true),
const mockTier = ref<SubscriptionInfo['tier']>('CREATOR')
const mockSubscription = ref<SubscriptionInfo | null>(makeSubscription())
const mockBalance = ref<BalanceInfo | null>(null)
const mockIsLoading = ref(false)
vi.mock('@/composables/billing/useBillingContext', () => ({
useBillingContext: vi.fn(() => ({
isActiveSubscription: mockIsActiveSubscription,
isFreeTier: mockIsFreeTier,
subscriptionTierName: ref('Creator'),
subscriptionTier: ref('CREATOR'),
fetchStatus: mockFetchStatus
tier: mockTier,
subscription: mockSubscription,
balance: mockBalance,
isLoading: mockIsLoading,
fetchStatus: mockFetchStatus,
fetchBalance: mockFetchBalance
}))
}))
// Mock the useSubscriptionDialog composable
const mockShowPricingTable = vi.fn()
vi.mock(
'@/platform/cloud/subscription/composables/useSubscriptionDialog',
@@ -127,7 +96,6 @@ vi.mock(
})
)
// Mock UserAvatar component
vi.mock('@/components/common/UserAvatar.vue', () => ({
default: {
name: 'UserAvatarMock',
@@ -137,22 +105,10 @@ vi.mock('@/components/common/UserAvatar.vue', () => ({
}
}))
// Mock UserCredit component
vi.mock('@/components/common/UserCredit.vue', () => ({
default: {
name: 'UserCreditMock',
render() {
return h('div', 'Credit: 100')
}
}
}))
// Mock formatCreditsFromCents
vi.mock('@/base/credits/comfyCredits', () => ({
formatCreditsFromCents: vi.fn(({ cents }) => (cents / 100).toString())
}))
// Mock useExternalLink
vi.mock('@/composables/useExternalLink', () => ({
useExternalLink: vi.fn(() => ({
buildDocsUrl: vi.fn((path) => `https://docs.comfy.org${path}`),
@@ -162,14 +118,12 @@ vi.mock('@/composables/useExternalLink', () => ({
}))
}))
// Mock useTelemetry
vi.mock('@/platform/telemetry', () => ({
useTelemetry: vi.fn(() => ({
trackAddApiCreditButtonClicked: vi.fn()
}))
}))
// Mock isCloud with hoisted state for per-test toggling
const mockIsCloud = vi.hoisted(() => ({ value: true }))
vi.mock('@/platform/distribution/types', () => ({
get isCloud() {
@@ -178,25 +132,37 @@ vi.mock('@/platform/distribution/types', () => ({
}))
vi.mock('@/platform/cloud/subscription/components/SubscribeButton.vue', () => ({
default: {
default: defineComponent({
name: 'SubscribeButtonMock',
render() {
return h('div', 'Subscribe Button')
emits: ['subscribed'],
setup(_, { emit }) {
return () =>
h(
'button',
{
'data-testid': 'subscribe-button-mock',
onClick: () => emit('subscribed')
},
'Subscribe Button'
)
}
}
})
}))
describe('CurrentUserPopoverLegacy', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsCloud.value = true
mockIsActiveSubscription.value = true
mockIsFreeTier.value = false
mockAuthStoreState.balance = {
amount_micros: 100_000,
effective_balance_micros: 100_000,
mockTier.value = 'CREATOR'
mockSubscription.value = makeSubscription()
mockBalance.value = {
amountMicros: 100_000,
effectiveBalanceMicros: 100_000,
currency: 'usd'
}
mockAuthStoreState.isFetchingBalance = false
mockIsLoading.value = false
})
function renderComponent() {
@@ -230,7 +196,47 @@ describe('CurrentUserPopoverLegacy', () => {
expect(screen.getByText('test@example.com')).toBeInTheDocument()
})
it('calls formatCreditsFromCents with correct parameters and displays formatted credits', () => {
it('fetches the balance through the billing facade on mount', () => {
renderComponent()
expect(mockFetchBalance).toHaveBeenCalled()
})
it('refreshes subscription status through the billing facade after subscribing', async () => {
mockIsActiveSubscription.value = false
const { user } = renderComponent()
await user.click(screen.getByTestId('subscribe-button-mock'))
expect(mockFetchStatus).toHaveBeenCalled()
})
describe('subscription tier badge', () => {
it('renders the tier name derived from the facade tier', () => {
renderComponent()
expect(screen.getByText('Creator')).toBeInTheDocument()
})
it('renders the yearly tier name when the facade subscription is annual', () => {
mockSubscription.value = makeSubscription({ duration: 'ANNUAL' })
renderComponent()
expect(screen.getByText('Creator Yearly')).toBeInTheDocument()
})
it('hides the badge when the facade reports no tier', () => {
mockTier.value = null
mockSubscription.value = null
renderComponent()
expect(screen.queryByText('Creator')).not.toBeInTheDocument()
})
})
it('formats and displays the facade balance', () => {
renderComponent()
expect(formatCreditsFromCents).toHaveBeenCalledWith({
@@ -245,6 +251,14 @@ describe('CurrentUserPopoverLegacy', () => {
expect(screen.getByText('1000')).toBeInTheDocument()
})
it('shows a skeleton instead of the balance while billing is loading', () => {
mockIsLoading.value = true
renderComponent()
expect(screen.queryByText('1000')).not.toBeInTheDocument()
})
it('renders logout menu item with correct text', () => {
renderComponent()
@@ -324,11 +338,11 @@ describe('CurrentUserPopoverLegacy', () => {
expect(onClose).toHaveBeenCalledTimes(1)
})
describe('effective_balance_micros handling', () => {
it('uses effective_balance_micros when present (positive balance)', () => {
mockAuthStoreState.balance = {
amount_micros: 200_000,
effective_balance_micros: 150_000,
describe('facade balance handling', () => {
it('uses effectiveBalanceMicros when present (positive balance)', () => {
mockBalance.value = {
amountMicros: 200_000,
effectiveBalanceMicros: 150_000,
currency: 'usd'
}
@@ -345,10 +359,10 @@ describe('CurrentUserPopoverLegacy', () => {
expect(screen.getByText('1500')).toBeInTheDocument()
})
it('uses effective_balance_micros when zero', () => {
mockAuthStoreState.balance = {
amount_micros: 100_000,
effective_balance_micros: 0,
it('uses effectiveBalanceMicros when zero', () => {
mockBalance.value = {
amountMicros: 100_000,
effectiveBalanceMicros: 0,
currency: 'usd'
}
@@ -365,10 +379,10 @@ describe('CurrentUserPopoverLegacy', () => {
expect(screen.getByText('0')).toBeInTheDocument()
})
it('uses effective_balance_micros when negative', () => {
mockAuthStoreState.balance = {
amount_micros: 0,
effective_balance_micros: -50_000,
it('uses effectiveBalanceMicros when negative', () => {
mockBalance.value = {
amountMicros: 0,
effectiveBalanceMicros: -50_000,
currency: 'usd'
}
@@ -385,9 +399,9 @@ describe('CurrentUserPopoverLegacy', () => {
expect(screen.getByText('-500')).toBeInTheDocument()
})
it('falls back to amount_micros when effective_balance_micros is missing', () => {
mockAuthStoreState.balance = {
amount_micros: 100_000,
it('falls back to amountMicros when effectiveBalanceMicros is missing', () => {
mockBalance.value = {
amountMicros: 100_000,
currency: 'usd'
}
@@ -404,10 +418,8 @@ describe('CurrentUserPopoverLegacy', () => {
expect(screen.getByText('1000')).toBeInTheDocument()
})
it('falls back to 0 when both effective_balance_micros and amount_micros are missing', () => {
mockAuthStoreState.balance = {
currency: 'usd'
}
it('falls back to 0 when the facade reports no balance', () => {
mockBalance.value = null
renderComponent()
@@ -466,8 +478,11 @@ describe('CurrentUserPopoverLegacy', () => {
})
it('hides subscribe button', () => {
mockIsActiveSubscription.value = false
renderComponent()
expect(screen.queryByText('Subscribe Button')).not.toBeInTheDocument()
expect(
screen.queryByTestId('subscribe-button-mock')
).not.toBeInTheDocument()
})
it('still shows partner nodes menu item', () => {

View File

@@ -32,12 +32,7 @@
<!-- Credits Section -->
<div v-if="isActiveSubscription" class="flex items-center gap-2 px-4 py-2">
<i class="icon-[lucide--component] text-sm text-amber-400" />
<Skeleton
v-if="authStore.isFetchingBalance"
width="4rem"
height="1.25rem"
class="w-full"
/>
<Skeleton v-if="isLoading" width="4rem" height="1.25rem" class="w-full" />
<span v-else class="text-base font-semibold text-base-foreground">{{
formattedBalance
}}</span>
@@ -162,16 +157,15 @@ import { formatCreditsFromCents } from '@/base/credits/comfyCredits'
import UserAvatar from '@/components/common/UserAvatar.vue'
import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useAuthActions } from '@/composables/auth/useAuthActions'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useExternalLink } from '@/composables/useExternalLink'
import SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useSettingsDialog } from '@/platform/settings/composables/useSettingsDialog'
import { useWorkspaceTierLabel } from '@/platform/workspace/composables/useWorkspaceTierLabel'
import { useDialogService } from '@/services/dialogService'
import { useAuthStore } from '@/stores/authStore'
const emit = defineEmits<{
close: []
@@ -181,25 +175,29 @@ const { buildDocsUrl, docsPaths } = useExternalLink()
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
useCurrentUser()
const authActions = useAuthActions()
const authStore = useAuthStore()
const settingsDialog = useSettingsDialog()
const dialogService = useDialogService()
const {
isActiveSubscription,
isFreeTier,
subscriptionTierName,
subscriptionTier,
fetchStatus
} = useSubscription()
tier,
subscription,
balance,
isLoading,
fetchStatus,
fetchBalance
} = useBillingContext()
const { formatTierName } = useWorkspaceTierLabel()
const subscriptionDialog = useSubscriptionDialog()
const { locale } = useI18n()
const subscriptionTierName = computed(() =>
formatTierName(tier.value, subscription.value?.duration === 'ANNUAL')
)
const formattedBalance = computed(() => {
const cents =
authStore.balance?.effective_balance_micros ??
authStore.balance?.amount_micros ??
0
balance.value?.effectiveBalanceMicros ?? balance.value?.amountMicros ?? 0
return formatCreditsFromCents({
cents,
locale: locale.value,
@@ -211,12 +209,12 @@ const formattedBalance = computed(() => {
})
const canUpgrade = computed(() => {
const tier = subscriptionTier.value
const currentTier = tier.value
return (
tier === 'FREE' ||
tier === 'FOUNDERS_EDITION' ||
tier === 'STANDARD' ||
tier === 'CREATOR'
currentTier === 'FREE' ||
currentTier === 'FOUNDERS_EDITION' ||
currentTier === 'STANDARD' ||
currentTier === 'CREATOR'
)
})
@@ -270,6 +268,6 @@ const handleSubscribed = async () => {
}
onMounted(() => {
void authActions.fetchBalance()
void fetchBalance()
})
</script>

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "رمز API الخاص بك لا يملك صلاحية الوصول إلى هذا المورد. يرجى التحقق من أذونات الرمز.",
"errorUserTokenInvalid": "رمز API المخزن غير صالح أو منتهي الصلاحية. يرجى تحديث الرمز في الإعدادات.",
"failedToCreateNode": "فشل إنشاء العقدة. يرجى المحاولة مرة أخرى أو التحقق من وحدة التحكم للحصول على التفاصيل.",
"failedToSetModelValue": "تمت إضافة العقدة، لكن لم يتم تعيين النموذج تلقائيًا. تحقق من وحدة التحكم لمزيد من التفاصيل.",
"fileFormats": "تنسيقات الملفات",
"fileName": "اسم الملف",
"fileSize": "حجم الملف",
@@ -242,8 +241,7 @@
"auth/user-not-found": "لم يتم العثور على حساب بهذا البريد الإلكتروني. هل ترغب في إنشاء حساب جديد؟",
"auth/weak-password": "كلمة المرور ضعيفة جداً. يرجى استخدام كلمة مرور أقوى تحتوي على 6 أحرف على الأقل.",
"auth/wrong-password": "كلمة المرور التي أدخلتها غير صحيحة. يرجى المحاولة مرة أخرى.",
"generic": "حدث خطأ أثناء تسجيل الدخول. يرجى المحاولة مرة أخرى.",
"signupBlocked": "تعذر إنشاء حسابك الآن. يرجى المحاولة لاحقًا. إذا استمرت المشكلة، راسل support@comfy.org."
"generic": "حدث خطأ أثناء تسجيل الدخول. يرجى المحاولة مرة أخرى."
},
"login": {
"andText": "و",
@@ -348,17 +346,6 @@
"x": "س",
"y": "ص"
},
"boundingBoxes": {
"clearAll": "مسح الكل",
"clickRegionToEdit": "انقر على منطقة لتعديلها.",
"colors": "لوحة الألوان",
"descLabel": "الوصف",
"descPlaceholder": "وصف هذه المنطقة",
"textLabel": "نص",
"textPlaceholder": "النص المراد عرضه (كما هو)",
"typeObj": "كائن",
"typeText": "نص"
},
"breadcrumbsMenu": {
"app": "التطبيق",
"blueprint": "المخطط",
@@ -768,13 +755,6 @@
"creditsAvailable": "الرصيد المتاح",
"details": "التفاصيل",
"eventType": "نوع الحدث",
"eventTypes": {
"accountCreated": "تم إنشاء الحساب",
"apiNodeUsage": "استخدام عقدة الشريك",
"apiUsage": "استخدام API",
"creditAdded": "تمت إضافة أرصدة",
"gpuUsage": "استخدام GPU"
},
"faqs": "الأسئلة المتكررة",
"invoiceHistory": "تاريخ الفواتير",
"lastUpdated": "آخر تحديث",
@@ -1850,35 +1830,6 @@
"zoomOptions": "خيارات التكبير",
"zoomOut": "تصغير"
},
"hdrViewer": {
"channel": "القناة",
"channels": {
"a": "ألفا",
"b": "B",
"g": "G",
"luminance": "السطوع",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "تحذيرات القص",
"dither": "تنعيم",
"exposure": "التعريض",
"failedToLoad": "فشل في تحميل صورة HDR",
"fitView": "ملاءمة",
"hdrImage": "صورة HDR",
"histogram": "مخطط بياني",
"inf": "لانهاية",
"max": "الحد الأقصى",
"mean": "المتوسط",
"min": "الحد الأدنى",
"nan": "غير رقم",
"normalizeExposure": "التعريض التلقائي",
"openInHdrViewer": "افتح في عارض HDR",
"resolution": "الدقة",
"sourceGamut": "مجال الألوان الأصلي",
"stdDev": "الانحراف المعياري",
"title": "عارض HDR"
},
"help": {
"helpCenterMenu": "قائمة مركز المساعدة",
"recentReleases": "الإصدارات الأخيرة"
@@ -3006,10 +2957,6 @@
"uploadError": "فشل في رفع صورة الرسام: {status} - {statusText}",
"width": "العرض"
},
"palette": {
"addColor": "إضافة لون",
"swatchTitle": "انقر للتعديل · اسحب لإعادة الترتيب · انقر بزر الفأرة الأيمن للإزالة"
},
"progressToast": {
"allDownloadsCompleted": "اكتملت جميع التنزيلات",
"downloadingModel": "جاري تنزيل النموذج...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "استيراد LoRAs الخاصة بك",
"description": "اختر الخطة الأنسب لك",
"descriptionWorkspace": "اختر أفضل خطة لمساحة العمل الخاصة بك",
"downgrade": {
"body": "سيتم إزالة جميع الأعضاء الآخرين من مساحة العمل هذه فورًا.",
"confirm": "تغيير الخطة",
"confirmationPhrase": "أنا أفهم",
"confirmationPrompt": "اكتب \"{phrase}\" للتأكيد.",
"failed": "فشل في تغيير الخطة",
"failedAfterMemberRemoval": "تمت إزالة أعضاء الفريق، لكن لم يكتمل تغيير الخطة — يرجى المحاولة مرة أخرى أو التواصل مع الدعم",
"memberRemovalFailed": "تعذر إزالة {email} من الفريق — قد يكون بعض الأعضاء قد أُزيلوا بالفعل ولم يتم تغيير خطتك",
"notAllowed": "تغيير الخطة غير متاح",
"paymentMethodRequired": "مطلوب وسيلة دفع لتغيير الخطط",
"paymentPageBlocked": "تعذر فتح صفحة الدفع — يرجى المحاولة مرة أخرى",
"title": "تغيير إلى خطة {plan}؟"
},
"enterprise": {
"cta": "اعرف المزيد",
"flexibility": "تبحث عن مزيد من المرونة أو ميزات مخصصة؟",
"name": "المؤسسات",
"needMoreMembers": "تحتاج إلى المزيد من الأعضاء؟",
"reachOut": "تواصل معنا ولنحدد موعدًا للحديث."
},
"everythingInPlus": "كل ما في {plan}، بالإضافة إلى:",
"expiresDate": "ينتهي في {date}",
"freeTier": {
"description": "تشمل خطتك المجانية {credits} رصيد شهري لتجربة Comfy Cloud.",
@@ -3822,7 +3748,6 @@
"messageSupport": "مراسلة الدعم",
"monthly": "شهري",
"monthlyBonusDescription": "مكافأة الرصيد الشهرية",
"monthlyCredits": "رصيد شهري",
"monthlyCreditsInfo": "يتم تحديث هذا الرصيد شهريًا ولا ينتقل للشهر التالي",
"monthlyCreditsLabel": "الرصيد الشهري",
"monthlyCreditsPerMemberLabel": "الرصيد الشهري / عضو",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "رصيد العقد الشريكة",
"partnerNodesDescription": "لتشغيل النماذج التجارية/المملوكة",
"perMonth": "دولار أمريكي / شهر",
"personalHeader": "الخطط الشخصية للاستخدام الفردي فقط. {action}",
"personalHeaderAction": "لإضافة زملاء، اشترك في خطة الفريق.",
"personalWorkspace": "مساحة العمل الشخصية",
"planScope": {
"personal": "للاستخدام الشخصي",
"team": "للفرق"
},
"plansAndPricing": "الخطط والأسعار",
"plansForWorkspace": "الخطط لمساحة العمل {workspace}",
"prepaidCreditsInfo": "رصيد تم شراؤه بشكل منفصل ولا ينتهي صلاحيته",
@@ -3852,10 +3771,8 @@
"confirm": "تأكيد",
"confirmPayment": "تأكيد الدفع",
"confirmPlanChange": "تأكيد تغيير الخطة",
"creditsRefillTo": "سيتم إعادة تعبئة الرصيد إلى",
"eachMonthCreditsRefill": "يتم إعادة تعبئة الرصيد كل شهر إلى",
"ends": "ينتهي في {date}",
"everyMonthStarting": "كل شهر ابتداءً من {date}",
"hideFeatures": "إخفاء الميزات",
"nextPaymentDue": "الدفع القادم مستحق في {date}. يمكنك الإلغاء في أي وقت.",
"perMember": "/ عضو",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "عرض المزيد من الميزات",
"starting": "يبدأ في {date}",
"startingToday": "يبدأ اليوم",
"subscribeToPlan": "اشترك في {plan}",
"switchToPlan": "انتقل إلى {plan}",
"terms": "الشروط",
"termsAgreement": "بالمتابعة، أنت توافق على {terms} و{privacy} الخاصة بـ Comfy Org.",
"totalDueToday": "الإجمالي المستحق اليوم",
"youllBeCharged": "سيتم خصم"
"totalDueToday": "الإجمالي المستحق اليوم"
},
"pricingBlurb": "*استنادًا إلى هذا القالب، {seeDetails}. تواصل معنا لـ {questions} أو {enterpriseDiscussions}. لمزيد من تفاصيل الأسعار، {clickHere}.",
"pricingBlurbClickHere": "اضغط هنا",
"pricingBlurbEnterprise": "مناقشات المؤسسات",
"pricingBlurbQuestions": "الاستفسارات",
"pricingBlurbSeeDetails": "اعرض التفاصيل",
"refreshCredits": "تحديث الرصيد",
"renewsDate": "تجديد في {date}",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "إعادة الاشتراك",
"resubscribeSuccess": "تمت إعادة تفعيل الاشتراك بنجاح",
"resubscribeTo": "إعادة الاشتراك في {plan}",
"saveYearly": "وفر ٢٠٪",
"saveYearlyUpTo": "وفر حتى ٢٠٪",
"soloUseOnly": "للاستخدام الفردي فقط",
"subscribeForMore": "ترقية",
"subscribeNow": "اشترك الآن",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "اشتراك",
"subscribeToRunFull": "الاشتراك للتشغيل",
"subscriptionRequiredMessage": "الاشتراك مطلوب للأعضاء لتشغيل سير العمل على السحابة",
"success": {
"allSet": "تم كل شيء بنجاح",
"planUpdated": "تم تحديث خطتك بنجاح.",
"receiptEmailed": "تم إرسال إيصال إلى بريدك الإلكتروني."
},
"teamHeader": "للفرق التي ترغب في التعاون. تحتاج إلى المزيد من الأعضاء؟ {learnMore} حول المؤسسات.",
"teamHeaderLearnMore": "اعرف المزيد",
"teamPlan": {
"changePlan": "تغيير الخطة",
"checkoutComingSoon": "الدفع لخطة الفريق قادم قريبًا.",
"comingSoonLabel": "قريبًا:",
"cta": "اشترك في خطة الفريق السنوية",
"ctaMonthly": "اشترك في خطة الفريق الشهرية",
"currentPlan": "الخطة الحالية",
"detailsTitle": "التفاصيل",
"name": "خطة الفريق",
"perkConcurrentRuns": "يمكن للأعضاء تشغيل سير العمل في نفس الوقت",
"perkInviteMembers": "دعوة أعضاء الفريق",
"perkProjectAssets": "إدارة المشاريع والأصول",
"perkRolePermissions": "أذونات حسب الدور",
"perkSharedPool": "رصيد مشترك لجميع الأعضاء",
"tagline": "اختر اشتراك الرصيد الشهري الخاص بك. احصل على خصم أكبر مع اشتراك رصيد أكبر."
},
"teamWorkspace": "مساحة عمل الفريق",
"tierNameYearly": "{name} سنوي",
"tiers": {
"creator": {
"feature1": "استيراد نماذجك الخاصة",
"name": "المُبدع"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "مجاني"
},
"pro": {
"feature1": "مدة تشغيل سير عمل أطول (حتى ساعة واحدة)",
"name": "احترافي"
},
"standard": {
"feature1": "حد أقصى لمدة تشغيل سير العمل ٣٠ دقيقة",
"feature2": "إضافة المزيد من الرصيد في أي وقت",
"name": "قياسي"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "قم بالترقية لإضافة أرصدة",
"usdPerMonth": "دولار أمريكي / شهريًا",
"usdPerMonthPerMember": "دولار أمريكي / شهر / عضو",
"videoEstimate": "ينتج تقريبًا ~{count} فيديوهات ٥ ثوانٍ*",
"videoEstimateExplanation": "هذه التقديرات مبنية على قالب Wan 2.2 لتحويل الصورة إلى فيديو باستخدام الإعدادات الافتراضية (5 ثوانٍ، 640x640، 16 إطار/ثانية، 4 خطوات أخذ عينات).",
"videoEstimateHelp": "مزيد من التفاصيل حول هذا القالب",
"videoEstimateLabel": "العدد التقريبي لمقاطع الفيديو 5 ثوانٍ التي يتم إنشاؤها باستخدام قالب Wan 2.2 لتحويل الصورة إلى فيديو",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "عرض المزيد من التفاصيل",
"viewMoreDetailsPlans": "عرض المزيد من التفاصيل حول الخطط والأسعار",
"viewUsageHistory": "عرض سجل الاستخدام",
"whatsIncluded": "ما يتضمنه:",
"workspaceNotSubscribed": "هذه مساحة العمل ليست مشتركة",
"yearly": "سنوي",
"yearlyCreditsLabel": "إجمالي الرصيد السنوي",
"yearlyDiscount": "خصم 20%",
"saveYearly": "وفّر 20%",
"yourPlanIncludes": "خطتك تشمل:"
},
"tabMenu": {

View File

@@ -895,8 +895,8 @@
"nodes": "Nodes",
"models": "Models",
"assets": "Assets",
"workflows": "Work­flows",
"templates": "Tem­plates",
"workflows": "Work\u00adflows",
"templates": "Tem\u00adplates",
"console": "Console",
"menu": "Menu",
"imported": "Imported",
@@ -2563,6 +2563,7 @@
"billedYearly": "{total} Billed yearly",
"monthly": "Monthly",
"yearly": "Yearly",
"saveYearly": "Save 20%",
"tierNameYearly": "{name} Yearly",
"messageSupport": "Message support",
"invoiceHistory": "Invoice history",
@@ -2573,7 +2574,6 @@
"benefit2": "Up to 1 hour runtime per job on Pro",
"benefit3": "Bring your own models (Creator & Pro)"
},
"yearlyDiscount": "20% DISCOUNT",
"tiers": {
"free": {
"name": "Free"
@@ -2636,6 +2636,7 @@
"everythingInPlus": "Everything in {plan}, plus:",
"monthlyCredits": "monthly credits",
"videoEstimate": "Generates ~{count} 5s videos*",
"saveYearly": "Save 20%",
"saveYearlyUpTo": "Save up to 20%",
"teamPlan": {
"name": "Team Plan",
@@ -2989,7 +2990,7 @@
"share": "Share"
},
"shortcuts": {
"shortcuts": "Short­cuts",
"shortcuts": "Short\u00adcuts",
"essentials": "Essential",
"viewControls": "View Controls",
"manageShortcuts": "Manage Shortcuts",

View File

@@ -5419,7 +5419,7 @@
},
"resolution": {
"name": "resolution",
"tooltip": "The resolution of the output video. 1080p is only available for grok-imagine-video-1.5."
"tooltip": "The resolution of the output video."
},
"aspect_ratio": {
"name": "aspect_ratio",

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "Tu token de API no tiene acceso a este recurso. Por favor, revisa los permisos de tu token.",
"errorUserTokenInvalid": "Tu token de API almacenado no es válido o ha expirado. Por favor, actualiza tu token en la configuración.",
"failedToCreateNode": "No se pudo crear el nodo. Inténtalo de nuevo o revisa la consola para más detalles.",
"failedToSetModelValue": "Nodo añadido, pero su modelo no pudo establecerse automáticamente. Consulta la consola para más detalles.",
"fileFormats": "Formatos de archivo",
"fileName": "Nombre del archivo",
"fileSize": "Tamaño del archivo",
@@ -242,8 +241,7 @@
"auth/user-not-found": "No se encontró ninguna cuenta con este correo electrónico. ¿Te gustaría crear una nueva cuenta?",
"auth/weak-password": "La contraseña es demasiado débil. Por favor, usa una contraseña más segura con al menos 6 caracteres.",
"auth/wrong-password": "La contraseña que ingresaste es incorrecta. Por favor, inténtalo de nuevo.",
"generic": "Ocurrió un error al iniciar sesión. Por favor, inténtalo de nuevo.",
"signupBlocked": "No pudimos crear tu cuenta en este momento. Por favor, inténtalo de nuevo más tarde. Si el problema persiste, escribe a support@comfy.org."
"generic": "Ocurrió un error al iniciar sesión. Por favor, inténtalo de nuevo."
},
"login": {
"andText": "y",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "Borrar todo",
"clickRegionToEdit": "Haz clic en una región para editarla.",
"colors": "paleta_de_colores",
"descLabel": "descripción",
"descPlaceholder": "descripción de esta región",
"textLabel": "Texto",
"textPlaceholder": "texto a renderizar (literal)",
"typeObj": "obj",
"typeText": "texto"
},
"breadcrumbsMenu": {
"app": "Aplicación",
"blueprint": "Plano",
@@ -768,13 +755,6 @@
"creditsAvailable": "Créditos disponibles",
"details": "Detalles",
"eventType": "Tipo de evento",
"eventTypes": {
"accountCreated": "Cuenta creada",
"apiNodeUsage": "Uso de nodo asociado",
"apiUsage": "Uso de API",
"creditAdded": "Créditos añadidos",
"gpuUsage": "Uso de GPU"
},
"faqs": "Preguntas frecuentes",
"invoiceHistory": "Historial de facturas",
"lastUpdated": "Última actualización",
@@ -1850,35 +1830,6 @@
"zoomOptions": "Opciones de Zoom",
"zoomOut": "Alejar"
},
"hdrViewer": {
"channel": "Canal",
"channels": {
"a": "Alfa",
"b": "B",
"g": "G",
"luminance": "Luminancia",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "Avisos de recorte",
"dither": "Difuminado",
"exposure": "Exposición",
"failedToLoad": "No se pudo cargar la imagen HDR",
"fitView": "Ajustar",
"hdrImage": "Imagen HDR",
"histogram": "Histograma",
"inf": "Inf",
"max": "Máx",
"mean": "Media",
"min": "Mín",
"nan": "NaN",
"normalizeExposure": "Exposición automática",
"openInHdrViewer": "Abrir en el visor HDR",
"resolution": "Resolución",
"sourceGamut": "Gama de origen",
"stdDev": "Desv. estándar",
"title": "Visor HDR"
},
"help": {
"helpCenterMenu": "Menú del Centro de Ayuda",
"recentReleases": "Lanzamientos recientes"
@@ -3006,10 +2957,6 @@
"uploadError": "Error al cargar la imagen del pintor: {status} - {statusText}",
"width": "Ancho"
},
"palette": {
"addColor": "Agregar un color",
"swatchTitle": "Haz clic para editar · arrastra para reordenar · clic derecho para eliminar"
},
"progressToast": {
"allDownloadsCompleted": "Todas las descargas completadas",
"downloadingModel": "Descargando modelo...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "Importa tus propios LoRAs",
"description": "Elige el mejor plan para ti",
"descriptionWorkspace": "Elige el mejor plan para tu espacio de trabajo",
"downgrade": {
"body": "Todos los demás miembros de este espacio de trabajo serán eliminados inmediatamente.",
"confirm": "Cambiar plan",
"confirmationPhrase": "Entiendo",
"confirmationPrompt": "Escribe \"{phrase}\" para confirmar.",
"failed": "No se pudo cambiar el plan",
"failedAfterMemberRemoval": "Los miembros del equipo fueron eliminados, pero el cambio de plan no se completó — por favor, inténtalo de nuevo o contacta con soporte",
"memberRemovalFailed": "No se pudo eliminar a {email} del equipo — algunos miembros pueden haber sido eliminados y tu plan no fue cambiado",
"notAllowed": "Este cambio de plan no está disponible",
"paymentMethodRequired": "Se requiere un método de pago para cambiar de plan",
"paymentPageBlocked": "No se pudo abrir la página de pago — por favor, inténtalo de nuevo",
"title": "¿Cambiar al plan {plan}?"
},
"enterprise": {
"cta": "Saber más",
"flexibility": "¿Buscas más flexibilidad o funciones personalizadas?",
"name": "Enterprise",
"needMoreMembers": "¿Necesitas más miembros?",
"reachOut": "Contáctanos y agendemos una charla."
},
"everythingInPlus": "Todo en {plan}, además:",
"expiresDate": "Caduca el {date}",
"freeTier": {
"description": "Tu plan gratuito incluye {credits} créditos cada mes para probar Comfy Cloud.",
@@ -3822,7 +3748,6 @@
"messageSupport": "Contactar con soporte",
"monthly": "Mensual",
"monthlyBonusDescription": "Bono de créditos mensual",
"monthlyCredits": "créditos mensuales",
"monthlyCreditsInfo": "Estos créditos se renuevan mensualmente y no se acumulan",
"monthlyCreditsLabel": "Créditos mensuales",
"monthlyCreditsPerMemberLabel": "Créditos mensuales / miembro",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "Créditos de Nodos de Socio",
"partnerNodesDescription": "Para ejecutar modelos comerciales/propietarios",
"perMonth": "USD / mes",
"personalHeader": "Los planes personales son solo para uso individual. {action}",
"personalHeaderAction": "Para agregar compañeros, suscríbete al plan de equipo.",
"personalWorkspace": "Espacio de trabajo personal",
"planScope": {
"personal": "Para uso personal",
"team": "Para equipos"
},
"plansAndPricing": "Planes y precios",
"plansForWorkspace": "Planes para {workspace}",
"prepaidCreditsInfo": "Créditos comprados por separado que no expiran",
@@ -3852,10 +3771,8 @@
"confirm": "Confirmar",
"confirmPayment": "Confirma tu pago",
"confirmPlanChange": "Confirma el cambio de plan",
"creditsRefillTo": "Los créditos se recargan hasta",
"eachMonthCreditsRefill": "Cada mes los créditos se recargan a",
"ends": "Finaliza el {date}",
"everyMonthStarting": "Cada mes a partir del {date}",
"hideFeatures": "Ocultar funciones",
"nextPaymentDue": "Próximo pago el {date}. Cancela en cualquier momento.",
"perMember": "/ miembro",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "Mostrar más funciones",
"starting": "Comienza el {date}",
"startingToday": "Comienza hoy",
"subscribeToPlan": "Suscribirse al plan {plan}",
"switchToPlan": "Cambiar al plan {plan}",
"terms": "Términos",
"termsAgreement": "Al continuar, aceptas los {terms} y la {privacy} de Comfy Org.",
"totalDueToday": "Total a pagar hoy",
"youllBeCharged": "Se te cobrará"
"totalDueToday": "Total a pagar hoy"
},
"pricingBlurb": "*Basado en esta plantilla, {seeDetails}. Contáctanos para {questions} o {enterpriseDiscussions}. Para más detalles sobre precios, {clickHere}.",
"pricingBlurbClickHere": "haz clic aquí",
"pricingBlurbEnterprise": "discusiones enterprise",
"pricingBlurbQuestions": "preguntas",
"pricingBlurbSeeDetails": "ver detalles",
"refreshCredits": "Actualizar créditos",
"renewsDate": "Se renueva el {date}",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "Volver a suscribirse",
"resubscribeSuccess": "¡Suscripción reactivada correctamente!",
"resubscribeTo": "Volver a suscribirse a {plan}",
"saveYearly": "Ahorra 20%",
"saveYearlyUpTo": "Ahorra hasta un 20%",
"soloUseOnly": "Solo para uso individual",
"subscribeForMore": "Mejorar",
"subscribeNow": "Suscribirse Ahora",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "Suscribirse",
"subscribeToRunFull": "Suscribirse a Ejecutar",
"subscriptionRequiredMessage": "Se requiere una suscripción para que los miembros ejecuten flujos de trabajo en la nube",
"success": {
"allSet": "Todo listo",
"planUpdated": "Tu plan se ha actualizado correctamente.",
"receiptEmailed": "Se ha enviado un recibo a tu correo electrónico."
},
"teamHeader": "Para equipos que desean colaborar. ¿Necesitas más miembros? {learnMore} sobre enterprise.",
"teamHeaderLearnMore": "Saber más",
"teamPlan": {
"changePlan": "Cambiar plan",
"checkoutComingSoon": "El pago del plan de equipo estará disponible próximamente.",
"comingSoonLabel": "Próximamente:",
"cta": "Suscribirse al plan anual de equipo",
"ctaMonthly": "Suscribirse al plan mensual de equipo",
"currentPlan": "Plan actual",
"detailsTitle": "Detalles",
"name": "Plan de equipo",
"perkConcurrentRuns": "Los miembros pueden ejecutar flujos de trabajo simultáneamente",
"perkInviteMembers": "Invita a miembros del equipo",
"perkProjectAssets": "Gestión de proyectos y recursos",
"perkRolePermissions": "Permisos basados en roles",
"perkSharedPool": "Bolsa de créditos compartida para todos los miembros",
"tagline": "Elige tu propia suscripción mensual de créditos. Obtén un mayor descuento con una suscripción de más créditos."
},
"teamWorkspace": "Espacio de trabajo en equipo",
"tierNameYearly": "{name} Anual",
"tiers": {
"creator": {
"feature1": "Importa tus propios modelos",
"name": "Creador"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "Gratis"
},
"pro": {
"feature1": "Mayor tiempo de ejecución del flujo de trabajo (hasta 1 hora)",
"name": "Pro"
},
"standard": {
"feature1": "Tiempo máximo de ejecución del flujo de trabajo de 30 minutos",
"feature2": "Agrega más créditos en cualquier momento",
"name": "Estándar"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "Mejorar para añadir créditos",
"usdPerMonth": "USD / mes",
"usdPerMonthPerMember": "USD / mes / miembro",
"videoEstimate": "Genera ~{count} videos de 5s*",
"videoEstimateExplanation": "Estas estimaciones se basan en la plantilla Wan 2.2 Imagen a Video usando la configuración predeterminada (5 segundos, 640x640, 16fps, muestreo de 4 pasos).",
"videoEstimateHelp": "Más detalles sobre esta plantilla",
"videoEstimateLabel": "Cantidad aprox. de videos de 5s generados con la plantilla Wan 2.2 Imagen a Video",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "Ver más detalles",
"viewMoreDetailsPlans": "Ver más detalles sobre planes y precios",
"viewUsageHistory": "Ver historial de uso",
"whatsIncluded": "Qué está incluido:",
"workspaceNotSubscribed": "Este espacio de trabajo no tiene una suscripción",
"yearly": "Anual",
"yearlyCreditsLabel": "Total de créditos anuales",
"yearlyDiscount": "20% DESCUENTO",
"saveYearly": "Ahorra 20%",
"yourPlanIncludes": "Tu plan incluye:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "توکن API شما به این منبع دسترسی ندارد. لطفاً مجوزهای توکن خود را بررسی کنید.",
"errorUserTokenInvalid": "توکن API ذخیره‌شده شما نامعتبر یا منقضی شده است. لطفاً توکن خود را در تنظیمات به‌روزرسانی کنید.",
"failedToCreateNode": "ایجاد node ناموفق بود. لطفاً دوباره تلاش کنید یا کنسول را بررسی کنید.",
"failedToSetModelValue": "Node اضافه شد، اما مدل آن به‌صورت خودکار تنظیم نشد. برای جزئیات بیشتر کنسول را بررسی کنید.",
"fileFormats": "فرمت‌های فایل",
"fileName": "نام فایل",
"fileSize": "اندازه فایل",
@@ -242,8 +241,7 @@
"auth/user-not-found": "حسابی با این ایمیل یافت نشد. مایل به ایجاد حساب جدید هستید؟",
"auth/weak-password": "رمز عبور خیلی ضعیف است. لطفاً از رمز عبور قوی‌تر با حداقل ۶ کاراکتر استفاده کنید.",
"auth/wrong-password": "رمز عبور وارد شده نادرست است. لطفاً دوباره تلاش کنید.",
"generic": "در ورود شما مشکلی پیش آمد. لطفاً دوباره تلاش کنید.",
"signupBlocked": "در حال حاضر امکان ایجاد حساب کاربری وجود ندارد. لطفاً بعداً دوباره تلاش کنید. اگر این مشکل ادامه داشت، با support@comfy.org تماس بگیرید."
"generic": "در ورود شما مشکلی پیش آمد. لطفاً دوباره تلاش کنید."
},
"login": {
"andText": "و",
@@ -348,17 +346,6 @@
"x": "ایکس",
"y": "وای"
},
"boundingBoxes": {
"clearAll": "پاک‌سازی همه",
"clickRegionToEdit": "برای ویرایش، روی یک ناحیه کلیک کنید.",
"colors": "پالت رنگ",
"descLabel": "توضیحات",
"descPlaceholder": "توضیحات این ناحیه",
"textLabel": "متن",
"textPlaceholder": "متن برای نمایش (عیناً)",
"typeObj": "obj",
"typeText": "متن"
},
"breadcrumbsMenu": {
"app": "برنامه",
"blueprint": "نقشه راه",
@@ -768,13 +755,6 @@
"creditsAvailable": "اعتبار موجود",
"details": "جزئیات",
"eventType": "نوع رویداد",
"eventTypes": {
"accountCreated": "حساب کاربری ایجاد شد",
"apiNodeUsage": "استفاده از Node شریک",
"apiUsage": "استفاده از API",
"creditAdded": "اعتبار افزوده شد",
"gpuUsage": "استفاده از GPU"
},
"faqs": "سؤالات متداول",
"invoiceHistory": "تاریخچه فاکتورها",
"lastUpdated": "آخرین به‌روزرسانی",
@@ -1850,35 +1830,6 @@
"zoomOptions": "گزینه‌های بزرگ‌نمایی",
"zoomOut": "کوچک‌نمایی"
},
"hdrViewer": {
"channel": "کانال",
"channels": {
"a": "آلفا",
"b": "B",
"g": "G",
"luminance": "درخشندگی",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "هشدار برش",
"dither": "دیتر",
"exposure": "نوردهی",
"failedToLoad": "بارگذاری تصویر HDR ناموفق بود",
"fitView": "متناسب‌سازی",
"hdrImage": "تصویر HDR",
"histogram": "هیستوگرام",
"inf": "بی‌نهایت",
"max": "بیشینه",
"mean": "میانگین",
"min": "کمینه",
"nan": "NaN",
"normalizeExposure": "نوردهی خودکار",
"openInHdrViewer": "باز کردن در نمایشگر HDR",
"resolution": "وضوح",
"sourceGamut": "گاموت منبع",
"stdDev": "انحراف معیار",
"title": "نمایشگر HDR"
},
"help": {
"helpCenterMenu": "منوی مرکز راهنما",
"recentReleases": "انتشارهای اخیر"
@@ -3006,10 +2957,6 @@
"uploadError": "بارگذاری تصویر painter ناموفق بود: {status} - {statusText}",
"width": "عرض"
},
"palette": {
"addColor": "افزودن رنگ",
"swatchTitle": "برای ویرایش کلیک کنید · برای جابجایی بکشید · برای حذف راست‌کلیک کنید"
},
"progressToast": {
"allDownloadsCompleted": "همه دانلودها تکمیل شدند",
"downloadingModel": "در حال دانلود مدل...",
@@ -3767,27 +3714,6 @@
"customLoRAsLabel": "LoRAهای خود را وارد کنید",
"description": "بهترین طرح را برای خود انتخاب کنید",
"descriptionWorkspace": "بهترین طرح را برای فضای کاری خود انتخاب کنید",
"downgrade": {
"body": "تمام اعضای دیگر این فضای کاری بلافاصله حذف خواهند شد.",
"confirm": "تغییر پلن",
"confirmationPhrase": "متوجه شدم",
"confirmationPrompt": "برای تأیید، عبارت «{phrase}» را وارد کنید.",
"failed": "تغییر پلن انجام نشد",
"failedAfterMemberRemoval": "اعضای تیم حذف شدند، اما تغییر پلن کامل نشد — لطفاً دوباره تلاش کنید یا با پشتیبانی تماس بگیرید",
"memberRemovalFailed": "امکان حذف {email} از تیم وجود ندارد — برخی اعضا ممکن است قبلاً حذف شده باشند و پلن شما تغییر نکرده است",
"notAllowed": "این تغییر پلن در دسترس نیست",
"paymentMethodRequired": "برای تغییر پلن، نیاز به روش پرداخت است",
"paymentPageBlocked": "امکان باز کردن صفحه پرداخت وجود ندارد — لطفاً دوباره تلاش کنید",
"title": "تغییر به پلن {plan}؟"
},
"enterprise": {
"cta": "بیشتر بدانید",
"flexibility": "به دنبال امکانات یا انعطاف‌پذیری بیشتر هستید؟",
"name": "سازمانی",
"needMoreMembers": "به اعضای بیشتری نیاز دارید؟",
"reachOut": "با ما تماس بگیرید تا زمانی برای گفتگو هماهنگ کنیم."
},
"everythingInPlus": "همه امکانات {plan}، به‌علاوه:",
"expiresDate": "انقضا در {date}",
"freeTier": {
"description": "طرح رایگان شما شامل {credits} اعتبار در هر ماه برای استفاده از Comfy Cloud است.",
@@ -3834,7 +3760,6 @@
"messageSupport": "پیام به پشتیبانی",
"monthly": "ماهانه",
"monthlyBonusDescription": "پاداش ماهانه اعتبار",
"monthlyCredits": "اعتبار ماهانه",
"monthlyCreditsInfo": "این اعتبارها هر ماه شارژ می‌شوند و منتقل نمی‌شوند",
"monthlyCreditsLabel": "اعتبار ماهانه",
"monthlyCreditsPerMemberLabel": "اعتبار ماهانه / هر عضو",
@@ -3847,13 +3772,7 @@
"partnerNodesCredits": "قیمت‌گذاری Partner Nodes",
"partnerNodesDescription": "برای اجرای مدل‌های تجاری/اختصاصی",
"perMonth": "/ ماه",
"personalHeader": "پلن‌های شخصی فقط برای استفاده فردی هستند. {action}",
"personalHeaderAction": "برای افزودن هم‌تیمی، به پلن تیمی ارتقا دهید.",
"personalWorkspace": "فضای کاری شخصی",
"planScope": {
"personal": "برای استفاده شخصی",
"team": "برای تیم‌ها"
},
"plansAndPricing": "طرح‌ها و قیمت‌ها",
"plansForWorkspace": "طرح‌ها برای {workspace}",
"prepaidCreditsInfo": "اعتبارهای پیش‌پرداخت تا یک سال پس از تاریخ خرید منقضی می‌شوند.",
@@ -3864,10 +3783,8 @@
"confirm": "تأیید",
"confirmPayment": "تأیید پرداخت",
"confirmPlanChange": "تأیید تغییر طرح",
"creditsRefillTo": "اعتبارها به این مقدار شارژ می‌شوند",
"eachMonthCreditsRefill": "هر ماه اعتبار به این مقدار بازنشانی می‌شود",
"ends": "پایان در {date}",
"everyMonthStarting": "هر ماه از {date} شروع می‌شود",
"hideFeatures": "مخفی کردن امکانات",
"nextPaymentDue": "پرداخت بعدی در {date}. هر زمان می‌توانید لغو کنید.",
"perMember": "/ هر عضو",
@@ -3877,18 +3794,10 @@
"showMoreFeatures": "نمایش امکانات بیشتر",
"starting": "شروع از {date}",
"startingToday": "شروع از امروز",
"subscribeToPlan": "اشتراک پلن {plan}",
"switchToPlan": "تغییر به پلن {plan}",
"terms": "شرایط",
"termsAgreement": "با ادامه، شما با {terms} و {privacy} Comfy Org موافقت می‌کنید.",
"totalDueToday": "مبلغ قابل پرداخت امروز",
"youllBeCharged": "شما مبلغ زیر را پرداخت خواهید کرد"
"totalDueToday": "مبلغ قابل پرداخت امروز"
},
"pricingBlurb": "*بر اساس این قالب، {seeDetails}. برای {questions} یا {enterpriseDiscussions} با ما تماس بگیرید. برای جزئیات بیشتر قیمت‌گذاری، {clickHere}.",
"pricingBlurbClickHere": "اینجا کلیک کنید",
"pricingBlurbEnterprise": "بحث‌های سازمانی",
"pricingBlurbQuestions": "سؤالات",
"pricingBlurbSeeDetails": "جزئیات را ببینید",
"refreshCredits": "به‌روزرسانی اعتبارها",
"renewsDate": "تمدید در {date}",
"required": {
@@ -3902,8 +3811,6 @@
"resubscribe": "تمدید اشتراک",
"resubscribeSuccess": "اشتراک با موفقیت فعال شد",
"resubscribeTo": "تمدید اشتراک {plan}",
"saveYearly": "صرفه‌جویی ۲۰٪",
"saveYearlyUpTo": "تا ۲۰٪ صرفه‌جویی کنید",
"soloUseOnly": "فقط برای استفاده فردی",
"subscribeForMore": "ارتقاء",
"subscribeNow": "هم‌اکنون اشتراک بگیرید",
@@ -3912,34 +3819,10 @@
"subscribeToRun": "اشتراک",
"subscribeToRunFull": "اشتراک برای اجرا",
"subscriptionRequiredMessage": "برای اجرای workflowها در Cloud، اشتراک لازم است.",
"success": {
"allSet": "همه چیز آماده است",
"planUpdated": "پلن شما با موفقیت به‌روزرسانی شد.",
"receiptEmailed": "رسید به ایمیل شما ارسال شد."
},
"teamHeader": "برای تیم‌هایی که قصد همکاری دارند. به اعضای بیشتری نیاز دارید؟ {learnMore} درباره پلن سازمانی.",
"teamHeaderLearnMore": "بیشتر بدانید",
"teamPlan": {
"changePlan": "تغییر پلن",
"checkoutComingSoon": "پرداخت پلن تیمی به‌زودی فعال می‌شود.",
"comingSoonLabel": "به‌زودی:",
"cta": "اشتراک سالانه تیمی",
"ctaMonthly": "اشتراک ماهانه تیمی",
"currentPlan": "پلن فعلی",
"detailsTitle": "جزئیات",
"name": "پلن تیمی",
"perkConcurrentRuns": "اعضا می‌توانند workflowها را به‌صورت همزمان اجرا کنند",
"perkInviteMembers": "دعوت اعضای تیم",
"perkProjectAssets": "مدیریت پروژه و دارایی‌ها",
"perkRolePermissions": "دسترسی مبتنی بر نقش",
"perkSharedPool": "استخر اعتبار مشترک برای همه اعضا",
"tagline": "اشتراک اعتباری ماهانه دلخواه خود را انتخاب کنید. با اعتبار بیشتر، تخفیف بیشتری دریافت کنید."
},
"teamWorkspace": "فضای کاری تیمی",
"tierNameYearly": "{name} سالانه",
"tiers": {
"creator": {
"feature1": "امکان وارد کردن مدل‌های شخصی",
"name": "خالق"
},
"founder": {
@@ -3949,12 +3832,9 @@
"name": "رایگان"
},
"pro": {
"feature1": "زمان اجرای workflow طولانی‌تر (تا ۱ ساعت)",
"name": "حرفه‌ای"
},
"standard": {
"feature1": "حداکثر زمان اجرای workflow: ۳۰ دقیقه",
"feature2": "امکان افزودن اعتبار بیشتر در هر زمان",
"name": "استاندارد"
}
},
@@ -3967,7 +3847,6 @@
"upgradeToAddCredits": "برای افزودن اعتبار ارتقاء دهید",
"usdPerMonth": "دلار آمریکا / ماه",
"usdPerMonthPerMember": "دلار آمریکا / ماه / هر عضو",
"videoEstimate": "تولید حدود ~{count} ویدیو ۵ ثانیه‌ای*",
"videoEstimateExplanation": "این تخمین‌ها بر اساس قالب Wan 2.2 Image-to-Video با تنظیمات پیش‌فرض (۵ ثانیه، ۶۴۰×۶۴۰، ۱۶ فریم بر ثانیه، ۴ مرحله نمونه‌گیری) است.",
"videoEstimateHelp": "جزئیات بیشتر درباره این قالب",
"videoEstimateLabel": "تخمین تعداد ویدیوهای ۵ ثانیه‌ای تولید شده با قالب Wan 2.2 Image-to-Video",
@@ -3977,11 +3856,10 @@
"viewMoreDetails": "مشاهده جزئیات بیشتر",
"viewMoreDetailsPlans": "مشاهده جزئیات بیشتر درباره طرح‌ها و قیمت‌ها",
"viewUsageHistory": "مشاهده تاریخچه استفاده",
"whatsIncluded": "شامل موارد زیر:",
"workspaceNotSubscribed": "این محیط کاری اشتراک فعال ندارد",
"yearly": "سالانه",
"yearlyCreditsLabel": "کل اعتبار سالانه",
"yearlyDiscount": "٪۲۰ تخفیف",
"saveYearly": "٪۲۰ صرفه‌جویی",
"yourPlanIncludes": "طرح شما شامل:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "Votre jeton API na pas accès à cette ressource. Veuillez vérifier les autorisations de votre jeton.",
"errorUserTokenInvalid": "Votre jeton API enregistré est invalide ou expiré. Veuillez le mettre à jour dans les paramètres.",
"failedToCreateNode": "Échec de la création du nœud. Veuillez réessayer ou consulter la console pour plus de détails.",
"failedToSetModelValue": "Nœud ajouté, mais son modèle na pas pu être défini automatiquement. Consultez la console pour plus de détails.",
"fileFormats": "Formats de fichier",
"fileName": "Nom du fichier",
"fileSize": "Taille du fichier",
@@ -242,8 +241,7 @@
"auth/user-not-found": "Aucun compte trouvé avec cette adresse e-mail. Souhaitez-vous créer un nouveau compte ?",
"auth/weak-password": "Le mot de passe est trop faible. Veuillez utiliser un mot de passe plus fort avec au moins 6 caractères.",
"auth/wrong-password": "Le mot de passe que vous avez saisi est incorrect. Veuillez réessayer.",
"generic": "Une erreur s'est produite lors de votre connexion. Veuillez réessayer.",
"signupBlocked": "Nous ne pouvons pas créer votre compte pour le moment. Veuillez réessayer plus tard. Si le problème persiste, contactez support@comfy.org."
"generic": "Une erreur s'est produite lors de votre connexion. Veuillez réessayer."
},
"login": {
"andText": "et",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "Tout effacer",
"clickRegionToEdit": "Cliquez sur une région pour la modifier.",
"colors": "palette_de_couleurs",
"descLabel": "description",
"descPlaceholder": "description de cette région",
"textLabel": "Texte",
"textPlaceholder": "texte à afficher (tel quel)",
"typeObj": "objet",
"typeText": "texte"
},
"breadcrumbsMenu": {
"app": "Application",
"blueprint": "Plan",
@@ -768,13 +755,6 @@
"creditsAvailable": "Crédits disponibles",
"details": "Détails",
"eventType": "Type d'événement",
"eventTypes": {
"accountCreated": "Compte créé",
"apiNodeUsage": "Utilisation du nœud partenaire",
"apiUsage": "Utilisation de l'API",
"creditAdded": "Crédits ajoutés",
"gpuUsage": "Utilisation du GPU"
},
"faqs": "FAQ",
"invoiceHistory": "Historique des factures",
"lastUpdated": "Dernière mise à jour",
@@ -1850,35 +1830,6 @@
"zoomOptions": "Options de zoom",
"zoomOut": "Zoom arrière"
},
"hdrViewer": {
"channel": "Canal",
"channels": {
"a": "Alpha",
"b": "B",
"g": "V",
"luminance": "Luminance",
"r": "R",
"rgb": "RVB"
},
"clipWarnings": "Avertissements de clipping",
"dither": "Tramage",
"exposure": "Exposition",
"failedToLoad": "Échec du chargement de l'image HDR",
"fitView": "Ajuster",
"hdrImage": "Image HDR",
"histogram": "Histogramme",
"inf": "Inf",
"max": "Max",
"mean": "Moyenne",
"min": "Min",
"nan": "NaN",
"normalizeExposure": "Exposition auto",
"openInHdrViewer": "Ouvrir dans la visionneuse HDR",
"resolution": "Résolution",
"sourceGamut": "Gamme source",
"stdDev": "Écart-type",
"title": "Visionneuse HDR"
},
"help": {
"helpCenterMenu": "Menu du centre daide",
"recentReleases": "Dernières versions"
@@ -3006,10 +2957,6 @@
"uploadError": "Échec du téléversement de l'image du peintre : {status} - {statusText}",
"width": "Largeur"
},
"palette": {
"addColor": "Ajouter une couleur",
"swatchTitle": "Cliquez pour éditer · glissez pour réordonner · clic droit pour supprimer"
},
"progressToast": {
"allDownloadsCompleted": "Tous les téléchargements sont terminés",
"downloadingModel": "Téléchargement du modèle...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "Importer vos propres LoRAs",
"description": "Choisissez le forfait qui vous convient",
"descriptionWorkspace": "Choisissez la meilleure offre pour votre espace de travail",
"downgrade": {
"body": "Tous les autres membres de cet espace de travail seront immédiatement supprimés.",
"confirm": "Changer de plan",
"confirmationPhrase": "Je comprends",
"confirmationPrompt": "Tapez « {phrase} » pour confirmer.",
"failed": "Échec du changement de plan",
"failedAfterMemberRemoval": "Les membres de léquipe ont été retirés, mais le changement de plan na pas abouti — veuillez réessayer ou contacter le support",
"memberRemovalFailed": "Impossible de retirer {email} de léquipe — certains membres ont peut-être déjà été retirés et votre plan na pas été modifié",
"notAllowed": "Ce changement de plan n'est pas disponible",
"paymentMethodRequired": "Un moyen de paiement est requis pour changer de plan",
"paymentPageBlocked": "Impossible douvrir la page de paiement — veuillez réessayer",
"title": "Passer au plan {plan} ?"
},
"enterprise": {
"cta": "En savoir plus",
"flexibility": "Vous cherchez plus de flexibilité ou des fonctionnalités personnalisées ?",
"name": "Entreprise",
"needMoreMembers": "Besoin de plus de membres ?",
"reachOut": "Contactez-nous pour planifier un échange."
},
"everythingInPlus": "Tout ce qui est dans {plan}, plus :",
"expiresDate": "Expire le {date}",
"freeTier": {
"description": "Votre plan gratuit inclut {credits} crédits chaque mois pour essayer Comfy Cloud.",
@@ -3822,7 +3748,6 @@
"messageSupport": "Contacter le support",
"monthly": "Mensuel",
"monthlyBonusDescription": "Bonus de crédits mensuel",
"monthlyCredits": "crédits mensuels",
"monthlyCreditsInfo": "Ces crédits se renouvellent chaque mois et ne sont pas reportés",
"monthlyCreditsLabel": "Crédits mensuels",
"monthlyCreditsPerMemberLabel": "Crédits mensuels / membre",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "Crédits Nœuds Partenaires",
"partnerNodesDescription": "Pour exécuter des modèles commerciaux/propriétaires",
"perMonth": "USD / mois",
"personalHeader": "Les plans personnels sont réservés à un usage individuel. {action}",
"personalHeaderAction": "Pour ajouter des coéquipiers, abonnez-vous au plan équipe.",
"personalWorkspace": "Espace de travail personnel",
"planScope": {
"personal": "Pour usage personnel",
"team": "Pour les équipes"
},
"plansAndPricing": "Forfaits & tarifs",
"plansForWorkspace": "Formules pour {workspace}",
"prepaidCreditsInfo": "Crédits achetés séparément qui n'expirent pas",
@@ -3852,10 +3771,8 @@
"confirm": "Confirmer",
"confirmPayment": "Confirmer votre paiement",
"confirmPlanChange": "Confirmer le changement d'offre",
"creditsRefillTo": "Les crédits sont réinitialisés à",
"eachMonthCreditsRefill": "Chaque mois, les crédits sont réinitialisés à",
"ends": "Se termine le {date}",
"everyMonthStarting": "Chaque mois à partir du {date}",
"hideFeatures": "Masquer les fonctionnalités",
"nextPaymentDue": "Prochain paiement dû le {date}. Annulez à tout moment.",
"perMember": "/ membre",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "Afficher plus de fonctionnalités",
"starting": "À partir du {date}",
"startingToday": "À partir d'aujourd'hui",
"subscribeToPlan": "Sabonner au plan {plan}",
"switchToPlan": "Passer au plan {plan}",
"terms": "Conditions",
"termsAgreement": "En continuant, vous acceptez les {terms} et la {privacy} de Comfy Org.",
"totalDueToday": "Total dû aujourd'hui",
"youllBeCharged": "Vous serez facturé"
"totalDueToday": "Total dû aujourd'hui"
},
"pricingBlurb": "*Basé sur ce modèle, {seeDetails}. Contactez-nous pour {questions} ou {enterpriseDiscussions}. Pour plus de détails sur les tarifs, {clickHere}.",
"pricingBlurbClickHere": "cliquez ici",
"pricingBlurbEnterprise": "discussions entreprise",
"pricingBlurbQuestions": "questions",
"pricingBlurbSeeDetails": "voir les détails",
"refreshCredits": "Actualiser les crédits",
"renewsDate": "Renouvellement le {date}",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "Se réabonner",
"resubscribeSuccess": "Abonnement réactivé avec succès",
"resubscribeTo": "Se réabonner à {plan}",
"saveYearly": "Économisez 20 %",
"saveYearlyUpTo": "Économisez jusquà 20 %",
"soloUseOnly": "Usage solo uniquement",
"subscribeForMore": "Mettre à niveau",
"subscribeNow": "S'abonner maintenant",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "S'abonner",
"subscribeToRunFull": "S'abonner pour exécuter",
"subscriptionRequiredMessage": "Un abonnement est requis pour que les membres puissent exécuter des workflows sur le Cloud",
"success": {
"allSet": "Tout est prêt",
"planUpdated": "Votre plan a été mis à jour avec succès.",
"receiptEmailed": "Un reçu vous a été envoyé par e-mail."
},
"teamHeader": "Pour les équipes souhaitant collaborer. Besoin de plus de membres ? {learnMore} sur loffre entreprise.",
"teamHeaderLearnMore": "En savoir plus",
"teamPlan": {
"changePlan": "Changer de plan",
"checkoutComingSoon": "Le paiement du plan équipe arrive bientôt.",
"comingSoonLabel": "Bientôt disponible :",
"cta": "Sabonner à loffre Équipe annuelle",
"ctaMonthly": "Sabonner à loffre Équipe mensuelle",
"currentPlan": "Plan actuel",
"detailsTitle": "Détails",
"name": "Plan Équipe",
"perkConcurrentRuns": "Les membres peuvent exécuter des workflows simultanément",
"perkInviteMembers": "Invitez des membres dans léquipe",
"perkProjectAssets": "Gestion des projets et des ressources",
"perkRolePermissions": "Permissions basées sur les rôles",
"perkSharedPool": "Crédits partagés pour tous les membres",
"tagline": "Choisissez votre propre abonnement mensuel de crédits. Bénéficiez dune plus grande remise avec un abonnement de crédits plus important."
},
"teamWorkspace": "Espace de travail déquipe",
"tierNameYearly": "{name} Annuel",
"tiers": {
"creator": {
"feature1": "Importez vos propres modèles",
"name": "Créateur"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "Gratuit"
},
"pro": {
"feature1": "Durée dexécution du workflow prolongée (jusquà 1 h)",
"name": "Pro"
},
"standard": {
"feature1": "Durée maximale dexécution du workflow : 30 minutes",
"feature2": "Ajoutez des crédits à tout moment",
"name": "Standard"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "Mettre à niveau pour ajouter des crédits",
"usdPerMonth": "USD / mois",
"usdPerMonthPerMember": "USD / mois / membre",
"videoEstimate": "Génère environ {count} vidéos de 5 s*",
"videoEstimateExplanation": "Ces estimations sont basées sur le modèle Wan 2.2 Image-to-Video avec les paramètres par défaut (5 secondes, 640x640, 16fps, échantillonnage en 4 étapes).",
"videoEstimateHelp": "Plus de détails sur ce modèle",
"videoEstimateLabel": "Nombre approx. de vidéos de 5s générées avec le modèle Wan 2.2 Image-to-Video",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "Voir plus de détails",
"viewMoreDetailsPlans": "Voir plus de détails sur les forfaits et tarifs",
"viewUsageHistory": "Voir l'historique d'utilisation",
"whatsIncluded": "Inclus :",
"workspaceNotSubscribed": "Cet espace de travail na pas dabonnement",
"yearly": "Annuel",
"yearlyCreditsLabel": "Crédits annuels totaux",
"yearlyDiscount": "20% DE RÉDUCTION",
"saveYearly": "Économisez 20 %",
"yourPlanIncludes": "Votre forfait comprend :"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "お使いのAPIトークンにはこのリソースへのアクセス権がありません。トークンの権限を確認してください。",
"errorUserTokenInvalid": "保存されているAPIトークンが無効または期限切れです。設定でトークンを更新してください。",
"failedToCreateNode": "ノードの作成に失敗しました。再試行するか、詳細はコンソールをご確認ください。",
"failedToSetModelValue": "ノードは追加されましたが、モデルを自動設定できませんでした。詳細はコンソールをご確認ください。",
"fileFormats": "ファイル形式",
"fileName": "ファイル名",
"fileSize": "ファイルサイズ",
@@ -242,8 +241,7 @@
"auth/user-not-found": "このメールアドレスに紐づくアカウントが見つかりません。新しいアカウントを作成しますか?",
"auth/weak-password": "パスワードが弱すぎます。6文字以上のより強力なパスワードを使用してください。",
"auth/wrong-password": "入力されたパスワードが正しくありません。もう一度お試しください。",
"generic": "サインイン中に問題が発生しました。もう一度お試しください。",
"signupBlocked": "現在アカウントを作成できません。しばらくしてから再度お試しください。繰り返し発生する場合は support@comfy.org までご連絡ください。"
"generic": "サインイン中に問題が発生しました。もう一度お試しください。"
},
"login": {
"andText": "および",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "すべてクリア",
"clickRegionToEdit": "編集する領域をクリックしてください。",
"colors": "カラーパレット",
"descLabel": "説明",
"descPlaceholder": "この領域の説明",
"textLabel": "テキスト",
"textPlaceholder": "表示するテキスト(そのまま)",
"typeObj": "obj",
"typeText": "テキスト"
},
"breadcrumbsMenu": {
"app": "アプリ",
"blueprint": "ブループリント",
@@ -768,13 +755,6 @@
"creditsAvailable": "利用可能なクレジット",
"details": "詳細",
"eventType": "イベントタイプ",
"eventTypes": {
"accountCreated": "アカウント作成",
"apiNodeUsage": "パートナーノード利用",
"apiUsage": "API利用",
"creditAdded": "クレジット追加",
"gpuUsage": "GPU利用"
},
"faqs": "よくある質問",
"invoiceHistory": "請求履歴",
"lastUpdated": "最終更新",
@@ -1850,35 +1830,6 @@
"zoomOptions": "ズームオプション",
"zoomOut": "縮小"
},
"hdrViewer": {
"channel": "チャンネル",
"channels": {
"a": "アルファ",
"b": "B",
"g": "G",
"luminance": "輝度",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "クリップ警告",
"dither": "ディザ",
"exposure": "露出",
"failedToLoad": "HDR画像の読み込みに失敗しました",
"fitView": "フィット",
"hdrImage": "HDR画像",
"histogram": "ヒストグラム",
"inf": "Inf",
"max": "最大",
"mean": "平均",
"min": "最小",
"nan": "NaN",
"normalizeExposure": "自動露出",
"openInHdrViewer": "HDRビューアで開く",
"resolution": "解像度",
"sourceGamut": "ソースガマット",
"stdDev": "標準偏差",
"title": "HDRビューア"
},
"help": {
"helpCenterMenu": "ヘルプセンターメニュー",
"recentReleases": "最近のリリース"
@@ -3006,10 +2957,6 @@
"uploadError": "ペインター画像のアップロードに失敗しました: {status} - {statusText}",
"width": "幅"
},
"palette": {
"addColor": "色を追加",
"swatchTitle": "クリックで編集 · ドラッグで並べ替え · 右クリックで削除"
},
"progressToast": {
"allDownloadsCompleted": "すべてのダウンロードが完了しました",
"downloadingModel": "モデルをダウンロード中...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "独自のLoRAをインポート",
"description": "あなたに最適なプランを選択してください",
"descriptionWorkspace": "ワークスペースに最適なプランを選択してください",
"downgrade": {
"body": "このワークスペースの他のメンバーはすぐに削除されます。",
"confirm": "プランを変更",
"confirmationPhrase": "理解しました",
"confirmationPrompt": "確認のため「{phrase}」と入力してください。",
"failed": "プランの変更に失敗しました",
"failedAfterMemberRemoval": "チームメンバーは削除されましたが、プランの変更が完了しませんでした。再度お試しいただくか、サポートまでご連絡ください。",
"memberRemovalFailed": "{email} をチームから削除できませんでした。一部のメンバーはすでに削除されている可能性があり、プランは変更されませんでした。",
"notAllowed": "このプラン変更は利用できません",
"paymentMethodRequired": "プランを変更するにはお支払い方法が必要です",
"paymentPageBlocked": "お支払いページを開けませんでした。再度お試しください。",
"title": "{plan}プランに変更しますか?"
},
"enterprise": {
"cta": "詳細を見る",
"flexibility": "より柔軟な対応やカスタム機能をご希望ですか?",
"name": "エンタープライズ",
"needMoreMembers": "さらに多くのメンバーが必要ですか?",
"reachOut": "ぜひご連絡ください。お打ち合わせを調整いたします。"
},
"everythingInPlus": "{plan}のすべて、さらに:",
"expiresDate": "{date} に期限切れ",
"freeTier": {
"description": "無料プランには、Comfy Cloudをお試しいただける毎月{credits}クレジットが含まれています。",
@@ -3822,7 +3748,6 @@
"messageSupport": "サポートに連絡",
"monthly": "月額",
"monthlyBonusDescription": "月間クレジットボーナス",
"monthlyCredits": "月間クレジット",
"monthlyCreditsInfo": "これらのクレジットは毎月リフレッシュされ、繰り越しはできません",
"monthlyCreditsLabel": "月間クレジット",
"monthlyCreditsPerMemberLabel": "月間クレジット / メンバー",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "パートナーノードクレジット",
"partnerNodesDescription": "商用/独自モデルの実行用",
"perMonth": "USD / 月",
"personalHeader": "個人プランは個人利用専用です。{action}",
"personalHeaderAction": "チームメンバーを追加するには、チームプランにご加入ください。",
"personalWorkspace": "個人ワークスペース",
"planScope": {
"personal": "個人向け",
"team": "チーム向け"
},
"plansAndPricing": "プランと価格",
"plansForWorkspace": "{workspace} のプラン",
"prepaidCreditsInfo": "別途購入した有効期限のないクレジット",
@@ -3852,10 +3771,8 @@
"confirm": "確認",
"confirmPayment": "お支払いを確認",
"confirmPlanChange": "プラン変更を確認",
"creditsRefillTo": "クレジットが補充されます:",
"eachMonthCreditsRefill": "毎月クレジットが補充されます",
"ends": "{date}に終了",
"everyMonthStarting": "毎月{date}から開始",
"hideFeatures": "機能を隠す",
"nextPaymentDue": "次回のお支払いは{date}です。いつでもキャンセルできます。",
"perMember": "/ メンバー",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "さらに機能を表示",
"starting": "{date}から開始",
"startingToday": "本日から開始",
"subscribeToPlan": "{plan}に加入",
"switchToPlan": "{plan}に切り替え",
"terms": "利用規約",
"termsAgreement": "続行することで、Comfy Orgの{terms}および{privacy}に同意したものとみなされます。",
"totalDueToday": "本日のお支払い合計",
"youllBeCharged": "ご請求額:"
"totalDueToday": "本日のお支払い合計"
},
"pricingBlurb": "*このテンプレートに基づきます。{seeDetails}。{questions}や{enterpriseDiscussions}についてはお問い合わせください。詳細な料金については{clickHere}。",
"pricingBlurbClickHere": "こちらをクリック",
"pricingBlurbEnterprise": "エンタープライズのご相談",
"pricingBlurbQuestions": "ご質問",
"pricingBlurbSeeDetails": "詳細を見る",
"refreshCredits": "クレジットを更新",
"renewsDate": "{date} に更新",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "再購読する",
"resubscribeSuccess": "サブスクリプションが再開されました",
"resubscribeTo": "{plan}を再購読する",
"saveYearly": "年間契約で20%お得",
"saveYearlyUpTo": "最大20%お得",
"soloUseOnly": "個人利用のみ",
"subscribeForMore": "アップグレード",
"subscribeNow": "今すぐ購読",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "購読する",
"subscribeToRunFull": "実行を購読",
"subscriptionRequiredMessage": "クラウドでワークフローを実行するにはメンバーにサブスクリプションが必要です",
"success": {
"allSet": "準備が整いました",
"planUpdated": "プランが正常に更新されました。",
"receiptEmailed": "領収書をメールで送信しました。"
},
"teamHeader": "コラボレーションを希望するチーム向け。さらに多くのメンバーが必要ですか?{learnMore}(エンタープライズについて)。",
"teamHeaderLearnMore": "詳細はこちら",
"teamPlan": {
"changePlan": "プランを変更",
"checkoutComingSoon": "チームプランのチェックアウトは近日公開予定です。",
"comingSoonLabel": "近日公開:",
"cta": "チーム年間プランに加入",
"ctaMonthly": "チーム月間プランに加入",
"currentPlan": "現在のプラン",
"detailsTitle": "詳細",
"name": "チームプラン",
"perkConcurrentRuns": "メンバーはワークフローを同時に実行可能",
"perkInviteMembers": "チームメンバーを招待",
"perkProjectAssets": "プロジェクト・アセット管理",
"perkRolePermissions": "ロールベースの権限管理",
"perkSharedPool": "全メンバーで共有するクレジットプール",
"tagline": "月間クレジット数を自由に選択。多くのクレジットでさらに割引。"
},
"teamWorkspace": "チームワークスペース",
"tierNameYearly": "{name} 年間",
"tiers": {
"creator": {
"feature1": "独自モデルのインポート",
"name": "クリエイター"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "無料"
},
"pro": {
"feature1": "より長いワークフロー実行時間最大1時間",
"name": "プロ"
},
"standard": {
"feature1": "最大30分のワークフロー実行時間",
"feature2": "いつでもクレジットを追加可能",
"name": "スタンダード"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "クレジット追加のためアップグレード",
"usdPerMonth": "USD / 月",
"usdPerMonthPerMember": "USD / 月 / メンバー",
"videoEstimate": "約{count}本の5秒動画を生成*",
"videoEstimateExplanation": "これらの見積もりは、Wan 2.2 画像から動画テンプレートのデフォルト設定5秒、640x640、16fps、4ステップサンプリングに基づいています。",
"videoEstimateHelp": "このテンプレートの詳細",
"videoEstimateLabel": "Wan 2.2 画像から動画テンプレートで生成される約5秒動画数",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "詳細を表示",
"viewMoreDetailsPlans": "プランと価格の詳細を見る",
"viewUsageHistory": "利用履歴を表示",
"whatsIncluded": "含まれる内容:",
"workspaceNotSubscribed": "このワークスペースはサブスクリプションに加入していません",
"yearly": "年額",
"yearlyCreditsLabel": "年間合計クレジット",
"yearlyDiscount": "20%割引",
"saveYearly": "20%お得",
"yourPlanIncludes": "ご利用プランに含まれるもの:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "API 토큰에 이 리소스에 대한 접근 권한이 없습니다. 토큰 권한을 확인해 주세요.",
"errorUserTokenInvalid": "저장된 API 토큰이 유효하지 않거나 만료되었습니다. 설정에서 토큰을 업데이트해 주세요.",
"failedToCreateNode": "노드 생성에 실패했습니다. 다시 시도하거나 콘솔에서 세부 정보를 확인하세요.",
"failedToSetModelValue": "노드는 추가되었으나 모델이 자동으로 설정되지 않았습니다. 자세한 내용은 콘솔을 확인하세요.",
"fileFormats": "파일 형식",
"fileName": "파일 이름",
"fileSize": "파일 크기",
@@ -242,8 +241,7 @@
"auth/user-not-found": "이 이메일로 등록된 계정이 없습니다. 새 계정을 생성하시겠습니까?",
"auth/weak-password": "비밀번호가 너무 약합니다. 최소 6자 이상의 강력한 비밀번호를 사용해 주세요.",
"auth/wrong-password": "입력하신 비밀번호가 올바르지 않습니다. 다시 시도해 주세요.",
"generic": "로그인 중 문제가 발생했습니다. 다시 시도해 주세요.",
"signupBlocked": "현재 계정을 생성할 수 없습니다. 잠시 후 다시 시도해 주세요. 계속 문제가 발생하면 support@comfy.org로 문의해 주세요."
"generic": "로그인 중 문제가 발생했습니다. 다시 시도해 주세요."
},
"login": {
"andText": "및",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "모두 지우기",
"clickRegionToEdit": "편집할 영역을 클릭하세요.",
"colors": "색상 팔레트",
"descLabel": "설명",
"descPlaceholder": "이 영역에 대한 설명",
"textLabel": "텍스트",
"textPlaceholder": "표시할 텍스트 (원문 그대로)",
"typeObj": "obj",
"typeText": "텍스트"
},
"breadcrumbsMenu": {
"app": "앱",
"blueprint": "블루프린트",
@@ -768,13 +755,6 @@
"creditsAvailable": "사용 가능한 크레딧",
"details": "세부 정보",
"eventType": "이벤트 유형",
"eventTypes": {
"accountCreated": "계정 생성됨",
"apiNodeUsage": "파트너 노드 사용",
"apiUsage": "API 사용",
"creditAdded": "크레딧 추가됨",
"gpuUsage": "GPU 사용"
},
"faqs": "자주 묻는 질문",
"invoiceHistory": "청구서 내역",
"lastUpdated": "마지막 업데이트",
@@ -1850,35 +1830,6 @@
"zoomOptions": "줌 옵션",
"zoomOut": "축소"
},
"hdrViewer": {
"channel": "채널",
"channels": {
"a": "알파",
"b": "B",
"g": "G",
"luminance": "휘도",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "클리핑 경고",
"dither": "디더",
"exposure": "노출",
"failedToLoad": "HDR 이미지를 불러오지 못했습니다",
"fitView": "맞춤",
"hdrImage": "HDR 이미지",
"histogram": "히스토그램",
"inf": "Inf",
"max": "최대",
"mean": "평균",
"min": "최소",
"nan": "NaN",
"normalizeExposure": "자동 노출",
"openInHdrViewer": "HDR 뷰어에서 열기",
"resolution": "해상도",
"sourceGamut": "소스 감마",
"stdDev": "표준편차",
"title": "HDR 뷰어"
},
"help": {
"helpCenterMenu": "도움말 센터 메뉴",
"recentReleases": "최근 릴리스"
@@ -3006,10 +2957,6 @@
"uploadError": "페인터 이미지를 업로드하지 못했습니다: {status} - {statusText}",
"width": "너비"
},
"palette": {
"addColor": "색상 추가",
"swatchTitle": "클릭: 편집 · 드래그: 순서 변경 · 우클릭: 삭제"
},
"progressToast": {
"allDownloadsCompleted": "모든 다운로드가 완료되었습니다",
"downloadingModel": "모델 다운로드 중...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "나만의 LoRA 가져오기",
"description": "가장 적합한 플랜을 선택하세요",
"descriptionWorkspace": "워크스페이스에 가장 적합한 플랜을 선택하세요",
"downgrade": {
"body": "이 워크스페이스의 다른 모든 멤버가 즉시 제거됩니다.",
"confirm": "플랜 변경",
"confirmationPhrase": "이해했습니다",
"confirmationPrompt": "확인을 위해 \"{phrase}\"를 입력하세요.",
"failed": "플랜 변경에 실패했습니다",
"failedAfterMemberRemoval": "팀 멤버는 제거되었지만 플랜 변경이 완료되지 않았습니다 — 다시 시도하거나 지원팀에 문의해 주세요",
"memberRemovalFailed": "{email}을(를) 팀에서 제거할 수 없습니다 — 일부 멤버가 이미 제거되었을 수 있으며 플랜이 변경되지 않았습니다",
"notAllowed": "이 플랜 변경은 사용할 수 없습니다",
"paymentMethodRequired": "플랜을 변경하려면 결제 수단이 필요합니다",
"paymentPageBlocked": "결제 페이지를 열 수 없습니다 — 다시 시도해 주세요",
"title": "{plan} 플랜으로 변경하시겠습니까?"
},
"enterprise": {
"cta": "자세히 알아보기",
"flexibility": "더 많은 유연성이나 맞춤 기능이 필요하신가요?",
"name": "엔터프라이즈",
"needMoreMembers": "더 많은 멤버가 필요하신가요?",
"reachOut": "문의해 주시면 상담 일정을 잡아드리겠습니다."
},
"everythingInPlus": "{plan}의 모든 기능, 그리고 추가로:",
"expiresDate": "만료일 {date}",
"freeTier": {
"description": "무료 플랜에는 Comfy Cloud를 체험할 수 있도록 매월 {credits} 크레딧이 포함되어 있습니다.",
@@ -3822,7 +3748,6 @@
"messageSupport": "고객 지원 문의",
"monthly": "월간",
"monthlyBonusDescription": "월간 크레딧 보너스",
"monthlyCredits": "월간 크레딧",
"monthlyCreditsInfo": "이 크레딧은 매월 갱신되며 이월되지 않습니다",
"monthlyCreditsLabel": "월간 크레딧",
"monthlyCreditsPerMemberLabel": "월별 크레딧 / 멤버",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "파트너 노드 크레딧",
"partnerNodesDescription": "상용/독점 모델 실행용",
"perMonth": "USD / 월",
"personalHeader": "개인 플랜은 개인 사용만을 위한 것입니다. {action}",
"personalHeaderAction": "팀원을 추가하려면 팀 플랜을 구독하세요.",
"personalWorkspace": "개인 워크스페이스",
"planScope": {
"personal": "개인용",
"team": "팀용"
},
"plansAndPricing": "플랜 및 가격",
"plansForWorkspace": "{workspace}의 플랜",
"prepaidCreditsInfo": "별도 구매하여 만료되지 않는 크레딧",
@@ -3852,10 +3771,8 @@
"confirm": "확인",
"confirmPayment": "결제 확인",
"confirmPlanChange": "플랜 변경 확인",
"creditsRefillTo": "크레딧이 다음으로 충전됨",
"eachMonthCreditsRefill": "매월 크레딧이 다시 충전됩니다",
"ends": "{date}에 종료",
"everyMonthStarting": "매월 {date}부터",
"hideFeatures": "기능 숨기기",
"nextPaymentDue": "다음 결제일: {date}. 언제든지 취소할 수 있습니다.",
"perMember": "/ 멤버",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "더 많은 기능 보기",
"starting": "{date}부터 시작",
"startingToday": "오늘부터 시작",
"subscribeToPlan": "{plan} 구독하기",
"switchToPlan": "{plan}으로 전환",
"terms": "이용약관",
"termsAgreement": "계속 진행하면 Comfy Org의 {terms} 및 {privacy}에 동의하게 됩니다.",
"totalDueToday": "오늘 결제 금액 합계",
"youllBeCharged": "결제가 진행됩니다"
"totalDueToday": "오늘 결제 금액 합계"
},
"pricingBlurb": "*이 템플릿 기준, {seeDetails}. {questions} 또는 {enterpriseDiscussions}에 대해 문의해 주세요. 더 자세한 가격 정보는 {clickHere}.",
"pricingBlurbClickHere": "여기를 클릭하세요",
"pricingBlurbEnterprise": "엔터프라이즈 상담",
"pricingBlurbQuestions": "문의",
"pricingBlurbSeeDetails": "자세히 보기",
"refreshCredits": "크레딧 새로고침",
"renewsDate": "{date}에 갱신됨",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "다시 구독하기",
"resubscribeSuccess": "구독이 성공적으로 재활성화되었습니다",
"resubscribeTo": "{plan} 다시 구독하기",
"saveYearly": "연간 결제 시 20% 절약",
"saveYearlyUpTo": "최대 20% 절약",
"soloUseOnly": "개인용 전용",
"subscribeForMore": "업그레이드",
"subscribeNow": "지금 구독하기",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "구독",
"subscribeToRunFull": "실행 구독",
"subscriptionRequiredMessage": "클라우드에서 워크플로우를 실행하려면 멤버가 구독해야 합니다",
"success": {
"allSet": "모든 준비가 완료되었습니다",
"planUpdated": "플랜이 성공적으로 업데이트되었습니다.",
"receiptEmailed": "영수증이 이메일로 발송되었습니다."
},
"teamHeader": "협업을 원하는 팀을 위한 플랜입니다. 더 많은 멤버가 필요하신가요? {learnMore} 엔터프라이즈에 대해 알아보세요.",
"teamHeaderLearnMore": "자세히 알아보기",
"teamPlan": {
"changePlan": "플랜 변경",
"checkoutComingSoon": "팀 플랜 결제는 곧 제공될 예정입니다.",
"comingSoonLabel": "곧 제공:",
"cta": "팀 연간 구독하기",
"ctaMonthly": "팀 월간 구독하기",
"currentPlan": "현재 플랜",
"detailsTitle": "상세 정보",
"name": "팀 플랜",
"perkConcurrentRuns": "멤버별 동시 워크플로우 실행 가능",
"perkInviteMembers": "팀 멤버 초대",
"perkProjectAssets": "프로젝트 및 에셋 관리",
"perkRolePermissions": "역할 기반 권한 관리",
"perkSharedPool": "모든 멤버를 위한 공유 크레딧 풀",
"tagline": "원하는 월간 크레딧 구독을 선택하세요. 더 많은 크레딧 구독 시 더 큰 할인 혜택을 받으세요."
},
"teamWorkspace": "팀 워크스페이스",
"tierNameYearly": "{name} 연간",
"tiers": {
"creator": {
"feature1": "나만의 모델 가져오기",
"name": "Creator"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "무료"
},
"pro": {
"feature1": "더 긴 워크플로우 실행 시간(최대 1시간)",
"name": "Pro"
},
"standard": {
"feature1": "최대 30분 워크플로우 실행 시간",
"feature2": "언제든지 크레딧 추가 가능",
"name": "Standard"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "크레딧 추가를 위해 업그레이드하세요",
"usdPerMonth": "USD / 월",
"usdPerMonthPerMember": "USD / 월 / 멤버",
"videoEstimate": "약 {count}개의 5초 영상 생성*",
"videoEstimateExplanation": "이 추정치는 기본 설정(5초, 640x640, 16fps, 4단계 샘플링)을 사용한 Wan 2.2 이미지-투-비디오 템플릿을 기준으로 합니다.",
"videoEstimateHelp": "이 템플릿에 대한 자세한 정보",
"videoEstimateLabel": "Wan 2.2 이미지-투-비디오 템플릿으로 생성 가능한 5초 비디오 수",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "자세히 보기",
"viewMoreDetailsPlans": "플랜 및 가격에 대한 자세한 정보 보기",
"viewUsageHistory": "사용 기록 보기",
"whatsIncluded": "포함 내용:",
"workspaceNotSubscribed": "이 워크스페이스는 구독 중이 아닙니다",
"yearly": "연간",
"yearlyCreditsLabel": "연간 총 크레딧",
"yearlyDiscount": "20% 할인",
"saveYearly": "20% 절감",
"yourPlanIncludes": "귀하의 플랜 포함 사항:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "Seu token de API não tem acesso a este recurso. Verifique as permissões do seu token.",
"errorUserTokenInvalid": "Seu token de API armazenado é inválido ou expirou. Atualize seu token nas configurações.",
"failedToCreateNode": "Falha ao criar o nó. Por favor, tente novamente ou verifique o console para mais detalhes.",
"failedToSetModelValue": "Nó adicionado, mas seu modelo não pôde ser definido automaticamente. Verifique o console para mais detalhes.",
"fileFormats": "Formatos de arquivo",
"fileName": "Nome do arquivo",
"fileSize": "Tamanho do arquivo",
@@ -242,8 +241,7 @@
"auth/user-not-found": "Nenhuma conta encontrada com este e-mail. Gostaria de criar uma nova conta?",
"auth/weak-password": "A senha é muito fraca. Use uma senha mais forte com pelo menos 6 caracteres.",
"auth/wrong-password": "A senha que você digitou está incorreta. Por favor, tente novamente.",
"generic": "Algo deu errado ao fazer login. Por favor, tente novamente.",
"signupBlocked": "Não foi possível criar sua conta agora. Por favor, tente novamente mais tarde. Se o problema persistir, envie um e-mail para support@comfy.org."
"generic": "Algo deu errado ao fazer login. Por favor, tente novamente."
},
"login": {
"andText": "e",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "Limpar tudo",
"clickRegionToEdit": "Clique em uma região para editar.",
"colors": "paleta_de_cores",
"descLabel": "descrição",
"descPlaceholder": "descrição desta região",
"textLabel": "Texto",
"textPlaceholder": "texto a ser exibido (literal)",
"typeObj": "obj",
"typeText": "texto"
},
"breadcrumbsMenu": {
"app": "App",
"blueprint": "Blueprint",
@@ -768,13 +755,6 @@
"creditsAvailable": "Créditos disponíveis",
"details": "Detalhes",
"eventType": "Tipo de evento",
"eventTypes": {
"accountCreated": "Conta criada",
"apiNodeUsage": "Uso de Node Parceiro",
"apiUsage": "Uso da API",
"creditAdded": "Créditos adicionados",
"gpuUsage": "Uso de GPU"
},
"faqs": "Perguntas frequentes",
"invoiceHistory": "Histórico de faturas",
"lastUpdated": "Última atualização",
@@ -1850,35 +1830,6 @@
"zoomOptions": "Opções de Zoom",
"zoomOut": "Afastar"
},
"hdrViewer": {
"channel": "Canal",
"channels": {
"a": "Alfa",
"b": "B",
"g": "G",
"luminance": "Luminância",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "Avisos de recorte",
"dither": "Dithering",
"exposure": "Exposição",
"failedToLoad": "Falha ao carregar a imagem HDR",
"fitView": "Ajustar",
"hdrImage": "Imagem HDR",
"histogram": "Histograma",
"inf": "Inf",
"max": "Máx",
"mean": "Média",
"min": "Mín",
"nan": "NaN",
"normalizeExposure": "Exposição automática",
"openInHdrViewer": "Abrir no Visualizador HDR",
"resolution": "Resolução",
"sourceGamut": "Gama de origem",
"stdDev": "Desvio padrão",
"title": "Visualizador HDR"
},
"help": {
"helpCenterMenu": "Menu do Centro de Ajuda",
"recentReleases": "Lançamentos recentes"
@@ -3006,10 +2957,6 @@
"uploadError": "Falha ao enviar imagem do painter: {status} - {statusText}",
"width": "Largura"
},
"palette": {
"addColor": "Adicionar cor",
"swatchTitle": "Clique para editar · arraste para reordenar · clique direito para remover"
},
"progressToast": {
"allDownloadsCompleted": "Todos os downloads concluídos",
"downloadingModel": "Baixando modelo...",
@@ -3767,27 +3714,6 @@
"customLoRAsLabel": "Importe seus próprios LoRAs",
"description": "Escolha o melhor plano para você",
"descriptionWorkspace": "Escolha o melhor plano para seu workspace",
"downgrade": {
"body": "Todos os outros membros deste workspace serão removidos imediatamente.",
"confirm": "Mudar plano",
"confirmationPhrase": "Eu entendo",
"confirmationPrompt": "Digite \"{phrase}\" para confirmar.",
"failed": "Falha ao mudar o plano",
"failedAfterMemberRemoval": "Os membros da equipe foram removidos, mas a alteração do plano não foi concluída — tente novamente ou entre em contato com o suporte",
"memberRemovalFailed": "Não foi possível remover {email} da equipe — alguns membros podem já ter sido removidos e seu plano não foi alterado",
"notAllowed": "Esta alteração de plano não está disponível",
"paymentMethodRequired": "É necessário um método de pagamento para mudar de plano",
"paymentPageBlocked": "Não foi possível abrir a página de pagamento — tente novamente",
"title": "Mudar para o plano {plan}?"
},
"enterprise": {
"cta": "Saiba mais",
"flexibility": "Procurando mais flexibilidade ou recursos personalizados?",
"name": "Enterprise",
"needMoreMembers": "Precisa de mais membros?",
"reachOut": "Entre em contato conosco e vamos agendar uma conversa."
},
"everythingInPlus": "Tudo do {plan}, além de:",
"expiresDate": "Expira em {date}",
"freeTier": {
"description": "Seu plano gratuito inclui {credits} créditos por mês para testar o Comfy Cloud.",
@@ -3834,7 +3760,6 @@
"messageSupport": "Falar com o suporte",
"monthly": "Mensal",
"monthlyBonusDescription": "Bônus mensal de créditos",
"monthlyCredits": "créditos mensais",
"monthlyCreditsInfo": "Esses créditos são renovados mensalmente e não acumulam",
"monthlyCreditsLabel": "Créditos mensais",
"monthlyCreditsPerMemberLabel": "Créditos mensais / membro",
@@ -3847,13 +3772,7 @@
"partnerNodesCredits": "Preços dos Partner Nodes",
"partnerNodesDescription": "Para executar modelos comerciais/proprietários",
"perMonth": "/ mês",
"personalHeader": "Planos pessoais são apenas para uso individual. {action}",
"personalHeaderAction": "Para adicionar colegas, assine o plano de equipe.",
"personalWorkspace": "Espaço de Trabalho Pessoal",
"planScope": {
"personal": "Para uso pessoal",
"team": "Para equipes"
},
"plansAndPricing": "Planos e preços",
"plansForWorkspace": "Planos para {workspace}",
"prepaidCreditsInfo": "Créditos pré-pagos expiram após 1 ano da data de compra.",
@@ -3864,10 +3783,8 @@
"confirm": "Confirmar",
"confirmPayment": "Confirme seu pagamento",
"confirmPlanChange": "Confirme a alteração do plano",
"creditsRefillTo": "Créditos recarregados para",
"eachMonthCreditsRefill": "A cada mês os créditos são recarregados para",
"ends": "Termina em {date}",
"everyMonthStarting": "Todo mês a partir de {date}",
"hideFeatures": "Ocultar recursos",
"nextPaymentDue": "Próximo pagamento em {date}. Cancele a qualquer momento.",
"perMember": "/ membro",
@@ -3877,18 +3794,10 @@
"showMoreFeatures": "Mostrar mais recursos",
"starting": "Começando em {date}",
"startingToday": "Começando hoje",
"subscribeToPlan": "Assinar {plan}",
"switchToPlan": "Mudar para {plan}",
"terms": "Termos",
"termsAgreement": "Ao continuar, você concorda com os {terms} e {privacy} da Comfy Org.",
"totalDueToday": "Total devido hoje",
"youllBeCharged": "Você será cobrado"
"totalDueToday": "Total devido hoje"
},
"pricingBlurb": "*Baseado neste template, {seeDetails}. Entre em contato para {questions} ou {enterpriseDiscussions}. Para mais detalhes sobre preços, {clickHere}.",
"pricingBlurbClickHere": "clique aqui",
"pricingBlurbEnterprise": "discussões enterprise",
"pricingBlurbQuestions": "dúvidas",
"pricingBlurbSeeDetails": "ver detalhes",
"refreshCredits": "Atualizar créditos",
"renewsDate": "Renova em {date}",
"required": {
@@ -3902,8 +3811,6 @@
"resubscribe": "Reassinar",
"resubscribeSuccess": "Assinatura reativada com sucesso",
"resubscribeTo": "Reassinar {plan}",
"saveYearly": "Economize 20%",
"saveYearlyUpTo": "Economize até 20%",
"soloUseOnly": "Apenas para uso individual",
"subscribeForMore": "Fazer upgrade",
"subscribeNow": "Assine Agora",
@@ -3912,34 +3819,10 @@
"subscribeToRun": "Assinar",
"subscribeToRunFull": "Assine para Executar",
"subscriptionRequiredMessage": "Uma assinatura é necessária para que os membros executem fluxos de trabalho na Nuvem",
"success": {
"allSet": "Tudo pronto",
"planUpdated": "Seu plano foi atualizado com sucesso.",
"receiptEmailed": "Um recibo foi enviado para seu e-mail."
},
"teamHeader": "Para equipes que desejam colaborar. Precisa de mais membros? {learnMore} sobre enterprise.",
"teamHeaderLearnMore": "Saiba mais",
"teamPlan": {
"changePlan": "Mudar plano",
"checkoutComingSoon": "Checkout do plano de equipe em breve.",
"comingSoonLabel": "Em breve:",
"cta": "Assinar Equipe Anual",
"ctaMonthly": "Assinar Equipe Mensal",
"currentPlan": "Plano atual",
"detailsTitle": "Detalhes",
"name": "Plano Equipe",
"perkConcurrentRuns": "Membros podem executar workflows simultaneamente",
"perkInviteMembers": "Convide membros da equipe",
"perkProjectAssets": "Gestão de projetos e ativos",
"perkRolePermissions": "Permissões baseadas em função",
"perkSharedPool": "Pool de créditos compartilhado para todos os membros",
"tagline": "Escolha sua própria assinatura mensal de créditos. Obtenha um desconto maior com uma assinatura de mais créditos."
},
"teamWorkspace": "Espaço de Trabalho em Equipe",
"tierNameYearly": "{name} Anual",
"tiers": {
"creator": {
"feature1": "Importe seus próprios modelos",
"name": "Criador"
},
"founder": {
@@ -3949,12 +3832,9 @@
"name": "Gratuito"
},
"pro": {
"feature1": "Tempo de execução do workflow mais longo (até 1 hora)",
"name": "Pro"
},
"standard": {
"feature1": "Tempo máximo de execução do workflow: 30 minutos",
"feature2": "Adicione mais créditos a qualquer momento",
"name": "Padrão"
}
},
@@ -3967,7 +3847,6 @@
"upgradeToAddCredits": "Faça upgrade para adicionar créditos",
"usdPerMonth": "USD / mês",
"usdPerMonthPerMember": "USD / mês / membro",
"videoEstimate": "Gera aproximadamente {count} vídeos de 5s*",
"videoEstimateExplanation": "Essas estimativas são baseadas no template Wan 2.2 Image-to-Video usando as configurações padrão (5 segundos, 640x640, 16fps, amostragem de 4 etapas).",
"videoEstimateHelp": "Mais detalhes sobre este template",
"videoEstimateLabel": "Quantidade aprox. de vídeos de 5s gerados com o template Wan 2.2 Image-to-Video",
@@ -3977,11 +3856,10 @@
"viewMoreDetails": "Ver mais detalhes",
"viewMoreDetailsPlans": "Veja mais detalhes sobre planos e preços",
"viewUsageHistory": "Ver histórico de uso",
"whatsIncluded": "O que está incluído:",
"workspaceNotSubscribed": "Este espaço de trabalho não possui uma assinatura",
"yearly": "Anual",
"yearlyCreditsLabel": "Total de créditos anuais",
"yearlyDiscount": "20% DE DESCONTO",
"saveYearly": "Economize 20%",
"yourPlanIncludes": "Seu plano inclui:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "Ваш API-токен не имеет доступа к этому ресурсу. Проверьте права доступа токена.",
"errorUserTokenInvalid": "Ваш сохранённый API-токен недействителен или истёк. Обновите токен в настройках.",
"failedToCreateNode": "Не удалось создать узел. Попробуйте ещё раз или проверьте консоль для подробностей.",
"failedToSetModelValue": "Узел добавлен, но его модель не удалось установить автоматически. Проверьте консоль для подробностей.",
"fileFormats": "Форматы файлов",
"fileName": "Имя файла",
"fileSize": "Размер файла",
@@ -242,8 +241,7 @@
"auth/user-not-found": "Учетная запись с этим email не найдена. Хотите создать новую учетную запись?",
"auth/weak-password": "Пароль слишком слабый. Пожалуйста, используйте более надежный пароль длиной не менее 6 символов.",
"auth/wrong-password": "Введенный пароль неверен. Пожалуйста, попробуйте еще раз.",
"generic": "Произошла ошибка при входе в систему. Пожалуйста, попробуйте еще раз.",
"signupBlocked": "Сейчас не удалось создать ваш аккаунт. Пожалуйста, попробуйте позже. Если проблема повторяется, напишите на support@comfy.org."
"generic": "Произошла ошибка при входе в систему. Пожалуйста, попробуйте еще раз."
},
"login": {
"andText": "и",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "Очистить всё",
"clickRegionToEdit": "Кликните по области для редактирования.",
"colors": "палитра_цветов",
"descLabel": "описание",
"descPlaceholder": "описание этой области",
"textLabel": "Текст",
"textPlaceholder": "текст для отображения (дословно)",
"typeObj": "объект",
"typeText": "текст"
},
"breadcrumbsMenu": {
"app": "Приложение",
"blueprint": "Чертёж",
@@ -768,13 +755,6 @@
"creditsAvailable": "Доступно кредитов",
"details": "Детали",
"eventType": "Тип события",
"eventTypes": {
"accountCreated": "Аккаунт создан",
"apiNodeUsage": "Использование партнерского узла",
"apiUsage": "Использование API",
"creditAdded": "Кредиты добавлены",
"gpuUsage": "Использование GPU"
},
"faqs": "Часто задаваемые вопросы",
"invoiceHistory": "История счетов",
"lastUpdated": "Последнее обновление",
@@ -1850,35 +1830,6 @@
"zoomOptions": "Параметры масштабирования",
"zoomOut": "Уменьшить"
},
"hdrViewer": {
"channel": "Канал",
"channels": {
"a": "Альфа",
"b": "B",
"g": "G",
"luminance": "Яркость",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "Предупреждения о клиппинге",
"dither": "Дизеринг",
"exposure": "Экспозиция",
"failedToLoad": "Не удалось загрузить HDR изображение",
"fitView": "Вписать",
"hdrImage": "HDR изображение",
"histogram": "Гистограмма",
"inf": "Беск.",
"max": "Макс.",
"mean": "Среднее",
"min": "Мин.",
"nan": "NaN",
"normalizeExposure": "Автоэкспозиция",
"openInHdrViewer": "Открыть в HDR просмотрщике",
"resolution": "Разрешение",
"sourceGamut": "Исходный цветовой охват",
"stdDev": "Ст. отклонение",
"title": "HDR Просмотрщик"
},
"help": {
"helpCenterMenu": "Меню справочного центра",
"recentReleases": "Недавние релизы"
@@ -3006,10 +2957,6 @@
"uploadError": "Не удалось загрузить изображение: {status} - {statusText}",
"width": "Ширина"
},
"palette": {
"addColor": "Добавить цвет",
"swatchTitle": "Клик — редактировать · перетащить — изменить порядок · ПКМ — удалить"
},
"progressToast": {
"allDownloadsCompleted": "Все загрузки завершены",
"downloadingModel": "Загрузка модели...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "Импортируйте свои LoRA",
"description": "Выберите лучший план для себя",
"descriptionWorkspace": "Выберите лучший тариф для вашего рабочего пространства",
"downgrade": {
"body": "Все остальные участники этой рабочей области будут немедленно удалены.",
"confirm": "Сменить тариф",
"confirmationPhrase": "Я понимаю",
"confirmationPrompt": "Введите «{phrase}» для подтверждения.",
"failed": "Не удалось сменить тариф",
"failedAfterMemberRemoval": "Участники команды были удалены, но смена тарифа не завершена — попробуйте еще раз или обратитесь в поддержку",
"memberRemovalFailed": "Не удалось удалить {email} из команды — некоторые участники могли быть уже удалены, и ваш тариф не был изменён",
"notAllowed": "Смена этого тарифа недоступна",
"paymentMethodRequired": "Для смены тарифа требуется способ оплаты",
"paymentPageBlocked": "Не удалось открыть страницу оплаты — попробуйте еще раз",
"title": "Перейти на тариф {plan}?"
},
"enterprise": {
"cta": "Узнать больше",
"flexibility": "Нужна большая гибкость или индивидуальные функции?",
"name": "Корпоративный тариф",
"needMoreMembers": "Нужно больше участников?",
"reachOut": "Свяжитесь с нами, чтобы назначить встречу."
},
"everythingInPlus": "Всё в {plan}, плюс:",
"expiresDate": "Истекает {date}",
"freeTier": {
"description": "Ваш бесплатный тариф включает {credits} кредитов каждый месяц для использования Comfy Cloud.",
@@ -3822,7 +3748,6 @@
"messageSupport": "Написать в поддержку",
"monthly": "Ежемесячно",
"monthlyBonusDescription": "Ежемесячный бонус кредитов",
"monthlyCredits": "кредитов в месяц",
"monthlyCreditsInfo": "Эти кредиты обновляются ежемесячно и не переносятся",
"monthlyCreditsLabel": "Ежемесячные кредиты",
"monthlyCreditsPerMemberLabel": "Ежемесячные кредиты / участник",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "Кредиты партнёрских узлов",
"partnerNodesDescription": "Для запуска коммерческих/проприетарных моделей",
"perMonth": "USD / месяц",
"personalHeader": "Личные тарифы предназначены только для индивидуального использования. {action}",
"personalHeaderAction": "Чтобы добавить участников, оформите командный тариф.",
"personalWorkspace": "Личное рабочее пространство",
"planScope": {
"personal": "Для личного использования",
"team": "Для команд"
},
"plansAndPricing": "Планы и цены",
"plansForWorkspace": "Тарифы для {workspace}",
"prepaidCreditsInfo": "Кредиты, приобретённые отдельно и не имеющие срока действия",
@@ -3852,10 +3771,8 @@
"confirm": "Подтвердить",
"confirmPayment": "Подтвердите оплату",
"confirmPlanChange": "Подтвердите смену тарифа",
"creditsRefillTo": "Кредиты пополняются до",
"eachMonthCreditsRefill": "Ежемесячное пополнение кредитов до",
"ends": "Заканчивается {date}",
"everyMonthStarting": "Каждый месяц начиная с {date}",
"hideFeatures": "Скрыть функции",
"nextPaymentDue": "Следующий платеж {date}. Отменить можно в любое время.",
"perMember": "/ участник",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "Показать больше функций",
"starting": "Начинается {date}",
"startingToday": "Начинается сегодня",
"subscribeToPlan": "Подписаться на {plan}",
"switchToPlan": "Перейти на {plan}",
"terms": "Условия",
"termsAgreement": "Продолжая, вы соглашаетесь с {terms} и {privacy} Comfy Org.",
"totalDueToday": "Итого к оплате сегодня",
"youllBeCharged": "С вас будет списано"
"totalDueToday": "Итого к оплате сегодня"
},
"pricingBlurb": "*Основано на этом шаблоне, {seeDetails}. Свяжитесь с нами по {questions} или для {enterpriseDiscussions}. Для подробной информации о ценах {clickHere}.",
"pricingBlurbClickHere": "нажмите здесь",
"pricingBlurbEnterprise": "корпоративным обсуждениям",
"pricingBlurbQuestions": "вопросам",
"pricingBlurbSeeDetails": "подробнее",
"refreshCredits": "Обновить кредиты",
"renewsDate": "Обновляется {date}",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "Возобновить подписку",
"resubscribeSuccess": "Подписка успешно возобновлена",
"resubscribeTo": "Возобновить подписку на {plan}",
"saveYearly": "Экономьте 20%",
"saveYearlyUpTo": "Экономьте до 20%",
"soloUseOnly": "Только для индивидуального использования",
"subscribeForMore": "Обновить",
"subscribeNow": "Подписаться сейчас",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "Подписаться",
"subscribeToRunFull": "Подписаться для запуска",
"subscriptionRequiredMessage": "Для запуска рабочих процессов в облаке участникам требуется подписка",
"success": {
"allSet": "Всё готово",
"planUpdated": "Ваш тариф успешно обновлён.",
"receiptEmailed": "Квитанция отправлена на вашу электронную почту."
},
"teamHeader": "Для команд, желающих сотрудничать. Нужно больше участников? {learnMore} о корпоративных возможностях.",
"teamHeaderLearnMore": "Узнать больше",
"teamPlan": {
"changePlan": "Сменить тариф",
"checkoutComingSoon": "Оформление командного тарифа скоро появится.",
"comingSoonLabel": "Скоро появится:",
"cta": "Оформить командную годовую подписку",
"ctaMonthly": "Оформить командную месячную подписку",
"currentPlan": "Текущий тариф",
"detailsTitle": "Детали",
"name": "Командный тариф",
"perkConcurrentRuns": "Участники могут запускать рабочие процессы одновременно",
"perkInviteMembers": "Приглашайте участников команды",
"perkProjectAssets": "Управление проектами и ресурсами",
"perkRolePermissions": "Ролевые разрешения",
"perkSharedPool": "Общий пул кредитов для всех участников",
"tagline": "Выберите собственную ежемесячную подписку на кредиты. Чем больше кредитов — тем больше скидка."
},
"teamWorkspace": "Командное рабочее пространство",
"tierNameYearly": "{name} Ежегодно",
"tiers": {
"creator": {
"feature1": "Импортируйте собственные модели",
"name": "Creator"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "Бесплатно"
},
"pro": {
"feature1": "Более длительное выполнение рабочего процесса (до 1 часа)",
"name": "Pro"
},
"standard": {
"feature1": "Максимальное время выполнения рабочего процесса — 30 минут",
"feature2": "Добавляйте кредиты в любое время",
"name": "Standard"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "Обновите, чтобы добавить кредиты",
"usdPerMonth": "USD / мес",
"usdPerMonthPerMember": "USD / мес / участник",
"videoEstimate": "Генерирует ~{count} 5-секундных видео*",
"videoEstimateExplanation": "Эти оценки основаны на шаблоне Wan 2.2 Image-to-Video с настройками по умолчанию (5 секунд, 640x640, 16 кадров/с, 4 шага семплирования).",
"videoEstimateHelp": "Подробнее об этом шаблоне",
"videoEstimateLabel": "Примерное количество 5-секундных видео, созданных с помощью шаблона Wan 2.2 Image-to-Video",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "Подробнее",
"viewMoreDetailsPlans": "Подробнее о планах и ценах",
"viewUsageHistory": "История использования",
"whatsIncluded": "Что входит:",
"workspaceNotSubscribed": "Это рабочее пространство не имеет подписки",
"yearly": "Ежегодно",
"yearlyCreditsLabel": "Годовые кредиты",
"yearlyDiscount": "СКИДКА 20%",
"saveYearly": "Экономия 20%",
"yourPlanIncludes": "Ваш план включает:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "API anahtarınızın bu kaynağa erişimi yok. Lütfen anahtar izinlerinizi kontrol edin.",
"errorUserTokenInvalid": "Kayıtlı API anahtarınız geçersiz veya süresi dolmuş. Lütfen ayarlardan anahtarınızı güncelleyin.",
"failedToCreateNode": "Düğüm oluşturulamadı. Lütfen tekrar deneyin veya ayrıntılar için konsolu kontrol edin.",
"failedToSetModelValue": "Düğüm eklendi, ancak modeli otomatik olarak ayarlanamadı. Detaylar için konsolu kontrol edin.",
"fileFormats": "Dosya formatları",
"fileName": "Dosya Adı",
"fileSize": "Dosya Boyutu",
@@ -242,8 +241,7 @@
"auth/user-not-found": "Bu e-posta ile ilişkili bir hesap bulunamadı. Yeni bir hesap oluşturmak ister misiniz?",
"auth/weak-password": "Parola çok zayıf. Lütfen en az 6 karakterden oluşan daha güçlü bir parola kullanın.",
"auth/wrong-password": "Girdiğiniz parola yanlış. Lütfen tekrar deneyin.",
"generic": "Oturum açarken bir hata oluştu. Lütfen tekrar deneyin.",
"signupBlocked": "Şu anda hesabınızı oluşturamıyoruz. Lütfen daha sonra tekrar deneyin. Sorun devam ederse support@comfy.org adresine e-posta gönderin."
"generic": "Oturum açarken bir hata oluştu. Lütfen tekrar deneyin."
},
"login": {
"andText": "ve",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "Tümünü temizle",
"clickRegionToEdit": "Düzenlemek için bir bölgeye tıklayın.",
"colors": "renk_paleti",
"descLabel": "açıklama",
"descPlaceholder": "bu bölgenin açıklaması",
"textLabel": "Metin",
"textPlaceholder": "görüntülenecek metin (aynen)",
"typeObj": "nesne",
"typeText": "metin"
},
"breadcrumbsMenu": {
"app": "Uygulama",
"blueprint": "Plan",
@@ -768,13 +755,6 @@
"creditsAvailable": "Mevcut kredi",
"details": "Detaylar",
"eventType": "Etkinlik Türü",
"eventTypes": {
"accountCreated": "Hesap Oluşturuldu",
"apiNodeUsage": "Partner Node Kullanımı",
"apiUsage": "API Kullanımı",
"creditAdded": "Kredi Eklendi",
"gpuUsage": "GPU Kullanımı"
},
"faqs": "SSS",
"invoiceHistory": "Fatura Geçmişi",
"lastUpdated": "Son güncellenme",
@@ -1850,35 +1830,6 @@
"zoomOptions": "Yakınlaştırma Seçenekleri",
"zoomOut": "Uzaklaştır"
},
"hdrViewer": {
"channel": "Kanal",
"channels": {
"a": "Alfa",
"b": "B",
"g": "G",
"luminance": "Parlaklık",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "Kırpma uyarıları",
"dither": "Dithering",
"exposure": "Pozlama",
"failedToLoad": "HDR görüntü yüklenemedi",
"fitView": "Sığdır",
"hdrImage": "HDR görüntü",
"histogram": "Histogram",
"inf": "Sonsuz",
"max": "Maks",
"mean": "Ortalama",
"min": "Min",
"nan": "NaN",
"normalizeExposure": "Otomatik pozlama",
"openInHdrViewer": "HDR Görüntüleyicide Aç",
"resolution": "Çözünürlük",
"sourceGamut": "Kaynak renk aralığı",
"stdDev": "Std sapma",
"title": "HDR Görüntüleyici"
},
"help": {
"helpCenterMenu": "Yardım Merkezi Menüsü",
"recentReleases": "Son sürümler"
@@ -3006,10 +2957,6 @@
"uploadError": "Ressam görseli yüklenemedi: {status} - {statusText}",
"width": "Genişlik"
},
"palette": {
"addColor": "Renk ekle",
"swatchTitle": "Düzenlemek için tıkla · Sürükleyerek sırala · Kaldırmak için sağ tıkla"
},
"progressToast": {
"allDownloadsCompleted": "Tüm indirmeler tamamlandı",
"downloadingModel": "Model indiriliyor...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "Kendi LoRA'larınızı içe aktarın",
"description": "Sizin için en iyi planı seçin",
"descriptionWorkspace": "Çalışma alanınız için en iyi planı seçin",
"downgrade": {
"body": "Bu çalışma alanındaki diğer tüm üyeler hemen kaldırılacaktır.",
"confirm": "Planı değiştir",
"confirmationPhrase": "Anladım",
"confirmationPrompt": "Onaylamak için \"{phrase}\" yazın.",
"failed": "Plan değiştirilemedi",
"failedAfterMemberRemoval": "Ekip üyeleri kaldırıldı, ancak plan değişikliği tamamlanamadı — lütfen tekrar deneyin veya destek ile iletişime geçin",
"memberRemovalFailed": "{email} ekipten kaldırılamadı — bazı üyeler zaten kaldırılmış olabilir ve planınız değiştirilmedi",
"notAllowed": "Bu plan değişikliği mevcut değil",
"paymentMethodRequired": "Plan değiştirmek için bir ödeme yöntemi gereklidir",
"paymentPageBlocked": "Ödeme sayfasıılamadı — lütfen tekrar deneyin",
"title": "{plan} planına geçilsin mi?"
},
"enterprise": {
"cta": "Daha fazla bilgi",
"flexibility": "Daha fazla esneklik veya özel özellikler mi arıyorsunuz?",
"name": "Kurumsal",
"needMoreMembers": "Daha fazla üyeye mi ihtiyacınız var?",
"reachOut": "Bizimle iletişime geçin ve bir görüşme planlayalım."
},
"everythingInPlus": "{plan} planındaki her şey ve ayrıca:",
"expiresDate": "{date} tarihinde sona erer",
"freeTier": {
"description": "Ücretsiz planınız, Comfy Cloud'u denemek için her ay {credits} kredi içerir.",
@@ -3822,7 +3748,6 @@
"messageSupport": "Destek ekibine mesaj gönder",
"monthly": "Aylık",
"monthlyBonusDescription": "Aylık kredi bonusu",
"monthlyCredits": "aylık kredi",
"monthlyCreditsInfo": "Bu krediler her ay yenilenir ve devretmez",
"monthlyCreditsLabel": "Aylık krediler",
"monthlyCreditsPerMemberLabel": "Aylık kredi / üye",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "Partner Düğümleri kredileri",
"partnerNodesDescription": "Ticari/özel modelleri çalıştırmak için",
"perMonth": "USD / ay",
"personalHeader": "Bireysel planlar yalnızca kişisel kullanım içindir. {action}",
"personalHeaderAction": "Ekip arkadaşları eklemek için ekip planına abone olun.",
"personalWorkspace": "Kişisel Çalışma Alanı",
"planScope": {
"personal": "Bireysel Kullanım İçin",
"team": "Ekipler İçin"
},
"plansAndPricing": "Planlar ve fiyatlandırma",
"plansForWorkspace": "{workspace} için planlar",
"prepaidCreditsInfo": "Ayrıca satın alınan ve son kullanma tarihi olmayan krediler",
@@ -3852,10 +3771,8 @@
"confirm": "Onayla",
"confirmPayment": "Ödemenizi onaylayın",
"confirmPlanChange": "Plan değişikliğini onaylayın",
"creditsRefillTo": "Krediler yenilenir",
"eachMonthCreditsRefill": "Her ay krediler yenilenir",
"ends": "{date} tarihinde bitiyor",
"everyMonthStarting": "Her ay {date} tarihinde başlar",
"hideFeatures": "Özellikleri gizle",
"nextPaymentDue": "Sonraki ödeme {date} tarihinde. İstediğiniz zaman iptal edebilirsiniz.",
"perMember": "/ üye",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "Daha fazla özelliği göster",
"starting": "{date} tarihinde başlıyor",
"startingToday": "Bugünden itibaren başlıyor",
"subscribeToPlan": "{plan} planına abone ol",
"switchToPlan": "{plan} planına geç",
"terms": "Şartlar",
"termsAgreement": "Devam ederek Comfy Org'un {terms} ve {privacy} politikasını kabul etmiş olursunuz.",
"totalDueToday": "Bugün ödenecek toplam tutar",
"youllBeCharged": "Şu kadar ücretlendirileceksiniz"
"totalDueToday": "Bugün ödenecek toplam tutar"
},
"pricingBlurb": "*Bu şablona göre, {seeDetails}. {questions} veya {enterpriseDiscussions} için bizimle iletişime geçin. Daha fazla fiyatlandırma detayı için {clickHere}.",
"pricingBlurbClickHere": "buraya tıklayın",
"pricingBlurbEnterprise": "kurumsal görüşmeler",
"pricingBlurbQuestions": "sorular",
"pricingBlurbSeeDetails": "detayları gör",
"refreshCredits": "Kredileri yenile",
"renewsDate": "{date} tarihinde yenilenir",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "Yeniden abone ol",
"resubscribeSuccess": "Abonelik başarıyla yeniden etkinleştirildi",
"resubscribeTo": "{plan} planına yeniden abone ol",
"saveYearly": "%20 tasarruf edin",
"saveYearlyUpTo": "%20'ye kadar tasarruf edin",
"soloUseOnly": "Sadece bireysel kullanım",
"subscribeForMore": "Yükselt",
"subscribeNow": "Hemen Abone Ol",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "Abone Ol",
"subscribeToRunFull": "Çalıştırmaya Abone Ol",
"subscriptionRequiredMessage": "Üyelerin Bulut'ta iş akışlarını çalıştırabilmesi için abonelik gereklidir",
"success": {
"allSet": "Her şey hazır",
"planUpdated": "Planınız başarıyla güncellendi.",
"receiptEmailed": "Makbuz e-posta ile gönderildi."
},
"teamHeader": "Birlikte çalışmak isteyen ekipler için. Daha fazla üyeye mi ihtiyacınız var? {learnMore} kurumsal hakkında.",
"teamHeaderLearnMore": "Daha fazla bilgi",
"teamPlan": {
"changePlan": "Planı değiştir",
"checkoutComingSoon": "Ekip planı ödeme sayfası yakında geliyor.",
"comingSoonLabel": "Yakında:",
"cta": "Ekip Yıllık Aboneliğe Geç",
"ctaMonthly": "Ekip Aylık Aboneliğe Geç",
"currentPlan": "Mevcut plan",
"detailsTitle": "Detaylar",
"name": "Ekip Planı",
"perkConcurrentRuns": "Üyeler iş akışlarını aynı anda çalıştırabilir",
"perkInviteMembers": "Ekip üyelerini davet edin",
"perkProjectAssets": "Proje ve varlık yönetimi",
"perkRolePermissions": "Rol tabanlı izinler",
"perkSharedPool": "Tüm üyeler için ortak kredi havuzu",
"tagline": "Kendi aylık kredi aboneliğinizi seçin. Daha fazla krediyle daha büyük indirim elde edin."
},
"teamWorkspace": "Takım Çalışma Alanı",
"tierNameYearly": "{name} Yıllık",
"tiers": {
"creator": {
"feature1": "Kendi modellerinizi içe aktarın",
"name": "Yaratıcı"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "Ücretsiz"
},
"pro": {
"feature1": "Daha uzun iş akışı çalışma süresi (1 saate kadar)",
"name": "Pro"
},
"standard": {
"feature1": "30 dakika maksimum iş akışı çalışma süresi",
"feature2": "İstediğiniz zaman daha fazla kredi ekleyin",
"name": "Standart"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "Kredi eklemek için yükselt",
"usdPerMonth": "USD / ay",
"usdPerMonthPerMember": "USD / ay / üye",
"videoEstimate": "Yaklaşık {count} adet 5 sn'lik video üretir*",
"videoEstimateExplanation": "Bu tahminler, varsayılan ayarlarla (5 saniye, 640x640, 16fps, 4 adım örnekleme) Wan 2.2 Görselden Videoya şablonuna dayanmaktadır.",
"videoEstimateHelp": "Bu şablon hakkında daha fazla bilgi",
"videoEstimateLabel": "Wan 2.2 Görselden Videoya şablonu ile yaklaşık 5 sn'lik video sayısı",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "Daha fazla detay görüntüle",
"viewMoreDetailsPlans": "Planlar ve fiyatlandırma hakkında daha fazla detay",
"viewUsageHistory": "Kullanım geçmişini görüntüle",
"whatsIncluded": "Dahil olanlar:",
"workspaceNotSubscribed": "Bu çalışma alanı bir aboneliğe sahip değil",
"yearly": "Yıllık",
"yearlyCreditsLabel": "Toplam yıllık krediler",
"yearlyDiscount": "%20 İNDİRİM",
"saveYearly": "%20 tasarruf",
"yourPlanIncludes": "Planınız şunları içerir:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "您的 API 金鑰無法存取此資源,請檢查權限。",
"errorUserTokenInvalid": "儲存的 API 金鑰無效或已過期,請在設定中更新。",
"failedToCreateNode": "無法建立節點。請重試或查看主控台以取得詳細資訊。",
"failedToSetModelValue": "已新增節點,但無法自動設定其模型。請查看主控台以獲取詳細資訊。",
"fileFormats": "檔案格式",
"fileName": "檔案名稱",
"fileSize": "檔案大小",
@@ -242,8 +241,7 @@
"auth/user-not-found": "找不到使用此電子郵件的帳戶。您要建立新帳戶嗎?",
"auth/weak-password": "密碼強度不足。請使用至少 6 個字元的更強密碼。",
"auth/wrong-password": "您輸入的密碼不正確。請再試一次。",
"generic": "登入時發生錯誤,請再試一次。",
"signupBlocked": "目前無法建立您的帳號。請稍後再試。如果問題持續發生,請聯絡 support@comfy.org。"
"generic": "登入時發生錯誤,請再試一次。"
},
"login": {
"andText": "以及",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "全部清除",
"clickRegionToEdit": "點擊區域以編輯。",
"colors": "色彩調色盤",
"descLabel": "描述",
"descPlaceholder": "此區域的描述",
"textLabel": "文字",
"textPlaceholder": "要呈現的文字(原文)",
"typeObj": "物件",
"typeText": "文字"
},
"breadcrumbsMenu": {
"app": "應用程式",
"blueprint": "藍圖",
@@ -768,13 +755,6 @@
"creditsAvailable": "可用點數",
"details": "詳細資料",
"eventType": "事件類型",
"eventTypes": {
"accountCreated": "帳號已建立",
"apiNodeUsage": "合作夥伴節點使用量",
"apiUsage": "API 使用量",
"creditAdded": "已新增點數",
"gpuUsage": "GPU 使用量"
},
"faqs": "常見問題",
"invoiceHistory": "發票紀錄",
"lastUpdated": "最後更新",
@@ -1850,35 +1830,6 @@
"zoomOptions": "縮放選項",
"zoomOut": "縮小"
},
"hdrViewer": {
"channel": "通道",
"channels": {
"a": "Alpha",
"b": "B",
"g": "G",
"luminance": "亮度",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "裁切警告",
"dither": "抖動",
"exposure": "曝光",
"failedToLoad": "無法載入 HDR 圖像",
"fitView": "適合畫面",
"hdrImage": "HDR 圖像",
"histogram": "直方圖",
"inf": "無限大",
"max": "最大值",
"mean": "平均值",
"min": "最小值",
"nan": "NaN",
"normalizeExposure": "自動曝光",
"openInHdrViewer": "在 HDR 檢視器中開啟",
"resolution": "解析度",
"sourceGamut": "來源色域",
"stdDev": "標準差",
"title": "HDR 檢視器"
},
"help": {
"helpCenterMenu": "說明中心選單",
"recentReleases": "近期版本"
@@ -3006,10 +2957,6 @@
"uploadError": "上傳繪圖圖像失敗:{status} - {statusText}",
"width": "寬度"
},
"palette": {
"addColor": "新增顏色",
"swatchTitle": "點擊編輯 · 拖曳排序 · 右鍵移除"
},
"progressToast": {
"allDownloadsCompleted": "所有下載已完成",
"downloadingModel": "正在下載模型...",
@@ -3755,27 +3702,6 @@
"customLoRAsLabel": "匯入您自己的 LoRAs",
"description": "選擇最適合您的方案",
"descriptionWorkspace": "為您的工作區選擇最佳方案",
"downgrade": {
"body": "此工作區的所有其他成員將會立即被移除。",
"confirm": "更改方案",
"confirmationPhrase": "我已了解",
"confirmationPrompt": "請輸入「{phrase}」以確認。",
"failed": "更改方案失敗",
"failedAfterMemberRemoval": "團隊成員已移除,但方案更改未完成 — 請再試一次或聯絡客服",
"memberRemovalFailed": "無法將 {email} 從團隊移除 — 有些成員可能已被移除,您的方案未被更改",
"notAllowed": "無法進行此方案更改",
"paymentMethodRequired": "更改方案需要新增付款方式",
"paymentPageBlocked": "無法開啟付款頁面 — 請再試一次",
"title": "更改為 {plan} 方案?"
},
"enterprise": {
"cta": "了解更多",
"flexibility": "需要更多彈性或自訂功能?",
"name": "企業方案",
"needMoreMembers": "需要更多成員嗎?",
"reachOut": "歡迎聯絡我們,安排諮詢。"
},
"everythingInPlus": "包含 {plan} 的所有內容,並加上:",
"expiresDate": "將於 {date} 到期",
"freeTier": {
"description": "您的免費方案每月包含 {credits} 點數,可體驗 Comfy Cloud。",
@@ -3822,7 +3748,6 @@
"messageSupport": "聯繫客服",
"monthly": "每月",
"monthlyBonusDescription": "每月點數獎勵",
"monthlyCredits": "每月額度",
"monthlyCreditsInfo": "這些點數每月重置,不可累積",
"monthlyCreditsLabel": "每月點數",
"monthlyCreditsPerMemberLabel": "每月點數/成員",
@@ -3835,13 +3760,7 @@
"partnerNodesCredits": "合作節點點數",
"partnerNodesDescription": "用於執行商業/專有模型",
"perMonth": "美元 / 月",
"personalHeader": "個人方案僅供個人使用。{action}",
"personalHeaderAction": "如需新增團隊成員,請訂閱團隊方案。",
"personalWorkspace": "個人工作區",
"planScope": {
"personal": "個人使用",
"team": "團隊使用"
},
"plansAndPricing": "方案與價格",
"plansForWorkspace": "{workspace} 的方案",
"prepaidCreditsInfo": "單獨購買且不會過期的點數",
@@ -3852,10 +3771,8 @@
"confirm": "確認",
"confirmPayment": "確認您的付款",
"confirmPlanChange": "確認您的方案變更",
"creditsRefillTo": "額度補充至",
"eachMonthCreditsRefill": "每月點數補充至",
"ends": "結束於 {date}",
"everyMonthStarting": "每月自 {date} 起",
"hideFeatures": "隱藏功能",
"nextPaymentDue": "下次付款日為 {date}。可隨時取消。",
"perMember": "/成員",
@@ -3865,18 +3782,10 @@
"showMoreFeatures": "顯示更多功能",
"starting": "自 {date} 開始",
"startingToday": "今日開始",
"subscribeToPlan": "訂閱 {plan}",
"switchToPlan": "切換至 {plan}",
"terms": "服務條款",
"termsAgreement": "繼續即表示您同意 Comfy Org 的{terms}與{privacy}。",
"totalDueToday": "今日應付總額",
"youllBeCharged": "您將被收取"
"totalDueToday": "今日應付總額"
},
"pricingBlurb": "*根據此範本,{seeDetails}。如有{questions}或{enterpriseDiscussions},歡迎聯絡我們。更多價格資訊,請{clickHere}。",
"pricingBlurbClickHere": "點此查看",
"pricingBlurbEnterprise": "企業洽談",
"pricingBlurbQuestions": "問題諮詢",
"pricingBlurbSeeDetails": "查看詳情",
"refreshCredits": "刷新點數",
"renewsDate": "將於 {date} 續訂",
"required": {
@@ -3890,8 +3799,6 @@
"resubscribe": "重新訂閱",
"resubscribeSuccess": "訂閱已重新啟用",
"resubscribeTo": "重新訂閱 {plan}",
"saveYearly": "年繳省 20%",
"saveYearlyUpTo": "最高可省 20%",
"soloUseOnly": "僅限個人使用",
"subscribeForMore": "升級",
"subscribeNow": "立即訂閱",
@@ -3900,34 +3807,10 @@
"subscribeToRun": "訂閱",
"subscribeToRunFull": "訂閱運行方案",
"subscriptionRequiredMessage": "會員需訂閱才能在雲端執行工作流程",
"success": {
"allSet": "設定完成",
"planUpdated": "您的方案已成功更新。",
"receiptEmailed": "收據已寄送至您的電子郵件。"
},
"teamHeader": "適合需要協作的團隊。需要更多成員嗎?{learnMore} 企業方案。",
"teamHeaderLearnMore": "了解更多",
"teamPlan": {
"changePlan": "更改方案",
"checkoutComingSoon": "團隊方案結帳功能即將推出。",
"comingSoonLabel": "即將推出:",
"cta": "訂閱團隊年繳方案",
"ctaMonthly": "訂閱團隊月繳方案",
"currentPlan": "目前方案",
"detailsTitle": "詳細資訊",
"name": "團隊方案",
"perkConcurrentRuns": "成員可同時運行多個工作流程",
"perkInviteMembers": "邀請團隊成員",
"perkProjectAssets": "專案與資產管理",
"perkRolePermissions": "依角色分配權限",
"perkSharedPool": "全體成員共享額度池",
"tagline": "自選每月額度訂閱。額度越高,折扣越多。"
},
"teamWorkspace": "團隊工作區",
"tierNameYearly": "{name} 年度方案",
"tiers": {
"creator": {
"feature1": "可匯入自有模型",
"name": "創作者版"
},
"founder": {
@@ -3937,12 +3820,9 @@
"name": "免費"
},
"pro": {
"feature1": "更長的工作流程運行時間(最長 1 小時)",
"name": "專業版"
},
"standard": {
"feature1": "工作流程最長 30 分鐘",
"feature2": "隨時可加購額度",
"name": "標準版"
}
},
@@ -3955,7 +3835,6 @@
"upgradeToAddCredits": "升級以增加點數",
"usdPerMonth": "美元/月",
"usdPerMonthPerMember": "美元/月/成員",
"videoEstimate": "可產生約 {count} 個 5 秒影片*",
"videoEstimateExplanation": "此估算以 Wan 2.2 圖轉影範本的預設設定5 秒、640x640、16fps、4 步採樣)為基礎。",
"videoEstimateHelp": "查看更多此範本細節",
"videoEstimateLabel": "以 Wan 2.2 圖轉影範本約可產生的 5 秒影片數量",
@@ -3965,11 +3844,10 @@
"viewMoreDetails": "查看更多詳情",
"viewMoreDetailsPlans": "查看更多方案與價格細節",
"viewUsageHistory": "檢視使用記錄",
"whatsIncluded": "包含內容:",
"workspaceNotSubscribed": "此工作區尚未訂閱",
"yearly": "每年",
"yearlyCreditsLabel": "年度總點數",
"yearlyDiscount": "八折優惠",
"saveYearly": "節省 20%",
"yourPlanIncludes": "您的方案包含:"
},
"tabMenu": {

View File

@@ -92,7 +92,6 @@
"errorUserTokenAccessDenied": "您的 API 密钥无权访问此资源。请检查密钥权限。",
"errorUserTokenInvalid": "您保存的 API 密钥无效或已过期。请在设置中更新密钥。",
"failedToCreateNode": "创建节点失败。请重试或查看控制台获取详细信息。",
"failedToSetModelValue": "节点已添加,但其模型未能自动设置。详情请查看控制台。",
"fileFormats": "文件格式",
"fileName": "文件名",
"fileSize": "文件大小",
@@ -242,8 +241,7 @@
"auth/user-not-found": "未找到使用此电子邮件的账户。您想要创建一个新账户吗?",
"auth/weak-password": "密码强度太弱。请使用至少6个字符的更强密码。",
"auth/wrong-password": "您输入的密码不正确,请重试。",
"generic": "登录时出现问题,请重试。",
"signupBlocked": "我们目前无法创建您的账户。请稍后再试。如果问题持续,请发送邮件至 support@comfy.org。"
"generic": "登录时出现问题,请重试。"
},
"login": {
"andText": "和",
@@ -348,17 +346,6 @@
"x": "X",
"y": "Y"
},
"boundingBoxes": {
"clearAll": "全部清除",
"clickRegionToEdit": "点击区域进行编辑。",
"colors": "调色板",
"descLabel": "描述",
"descPlaceholder": "该区域的描述",
"textLabel": "文本",
"textPlaceholder": "要渲染的文本(原文)",
"typeObj": "obj",
"typeText": "text"
},
"breadcrumbsMenu": {
"app": "应用",
"blueprint": "蓝图",
@@ -768,13 +755,6 @@
"creditsAvailable": "积分可用",
"details": "详情",
"eventType": "事件类型",
"eventTypes": {
"accountCreated": "账户已创建",
"apiNodeUsage": "合作节点使用",
"apiUsage": "API 使用",
"creditAdded": "已添加积分",
"gpuUsage": "GPU 使用"
},
"faqs": "常见问题",
"invoiceHistory": "发票历史",
"lastUpdated": "最近更新",
@@ -1850,35 +1830,6 @@
"zoomOptions": "缩放选项",
"zoomOut": "缩小"
},
"hdrViewer": {
"channel": "通道",
"channels": {
"a": "Alpha",
"b": "B",
"g": "G",
"luminance": "亮度",
"r": "R",
"rgb": "RGB"
},
"clipWarnings": "裁剪警告",
"dither": "抖动",
"exposure": "曝光",
"failedToLoad": "HDR 图像加载失败",
"fitView": "适应",
"hdrImage": "HDR 图像",
"histogram": "直方图",
"inf": "无穷大",
"max": "最大值",
"mean": "均值",
"min": "最小值",
"nan": "NaN",
"normalizeExposure": "自动曝光",
"openInHdrViewer": "在 HDR 查看器中打开",
"resolution": "分辨率",
"sourceGamut": "源色域",
"stdDev": "标准差",
"title": "HDR 查看器"
},
"help": {
"helpCenterMenu": "帮助中心菜单",
"recentReleases": "最近发布"
@@ -3006,10 +2957,6 @@
"uploadError": "上传绘图图像失败:{status} - {statusText}",
"width": "宽度"
},
"palette": {
"addColor": "添加颜色",
"swatchTitle": "点击编辑 · 拖动排序 · 右键移除"
},
"progressToast": {
"allDownloadsCompleted": "所有下载已完成",
"downloadingModel": "正在下载模型...",
@@ -3767,27 +3714,6 @@
"customLoRAsLabel": "导入您的 Lora",
"description": "选择最适合您的订阅计划",
"descriptionWorkspace": "为您的工作区选择最佳方案",
"downgrade": {
"body": "该工作区的所有其他成员将被立即移除。",
"confirm": "更改方案",
"confirmationPhrase": "我已了解",
"confirmationPrompt": "请输入“{phrase}”以确认。",
"failed": "更改方案失败",
"failedAfterMemberRemoval": "团队成员已被移除,但方案更改未完成 — 请重试或联系客服",
"memberRemovalFailed": "无法将 {email} 从团队移除 — 部分成员可能已被移除,您的方案未更改",
"notAllowed": "无法进行此方案更改",
"paymentMethodRequired": "更改方案需要添加支付方式",
"paymentPageBlocked": "无法打开支付页面 — 请重试",
"title": "切换到 {plan} 方案?"
},
"enterprise": {
"cta": "了解更多",
"flexibility": "需要更多灵活性或定制功能?",
"name": "企业版",
"needMoreMembers": "需要更多成员?",
"reachOut": "请联系我们,安排沟通。"
},
"everythingInPlus": "包含 {plan} 的所有内容,并额外提供:",
"expiresDate": "于 {date} 过期",
"freeTier": {
"description": "您的免费套餐每月包含 {credits} 积分,可体验 Comfy Cloud。",
@@ -3834,7 +3760,6 @@
"messageSupport": "消息支持",
"monthly": "月度",
"monthlyBonusDescription": "每月积分奖励",
"monthlyCredits": "每月积分",
"monthlyCreditsInfo": "积分每月刷新,不会保留",
"monthlyCreditsLabel": "每月积分",
"monthlyCreditsPerMemberLabel": "每成员每月积分",
@@ -3847,13 +3772,7 @@
"partnerNodesCredits": "合作伙伴节点积分",
"partnerNodesDescription": "用于运行商业/专有模型",
"perMonth": "美元 / 月",
"personalHeader": "个人方案仅限个人使用。{action}",
"personalHeaderAction": "如需添加团队成员,请订阅团队方案。",
"personalWorkspace": "个人工作区",
"planScope": {
"personal": "个人使用",
"team": "团队使用"
},
"plansAndPricing": "订阅和定价",
"plansForWorkspace": "{workspace} 的套餐",
"prepaidCreditsInfo": "单独购买且不会过期的积分",
@@ -3864,10 +3783,8 @@
"confirm": "确认",
"confirmPayment": "确认付款",
"confirmPlanChange": "确认更改方案",
"creditsRefillTo": "积分补充至",
"eachMonthCreditsRefill": "每月积分补充至",
"ends": "{date} 结束",
"everyMonthStarting": "每月从 {date} 开始",
"hideFeatures": "隐藏功能",
"nextPaymentDue": "下次付款截止日期 {date}。可随时取消。",
"perMember": "/ 每成员",
@@ -3877,18 +3794,10 @@
"showMoreFeatures": "显示更多功能",
"starting": "{date} 开始",
"startingToday": "今日开始",
"subscribeToPlan": "订阅 {plan}",
"switchToPlan": "切换到 {plan}",
"terms": "条款",
"termsAgreement": "继续操作即表示您同意 Comfy Org 的{terms}和{privacy}。",
"totalDueToday": "今日应付总额",
"youllBeCharged": "您将被收取"
"totalDueToday": "今日应付总额"
},
"pricingBlurb": "*基于此模板,{seeDetails}。如有{questions}或{enterpriseDiscussions},请联系我们。更多价格详情,{clickHere}。",
"pricingBlurbClickHere": "点击这里",
"pricingBlurbEnterprise": "企业合作",
"pricingBlurbQuestions": "问题咨询",
"pricingBlurbSeeDetails": "查看详情",
"refreshCredits": "刷新额度",
"renewsDate": "将于 {date} 续订",
"required": {
@@ -3902,8 +3811,6 @@
"resubscribe": "重新订阅",
"resubscribeSuccess": "订阅已成功重新激活",
"resubscribeTo": "重新订阅 {plan}",
"saveYearly": "年付节省 20%",
"saveYearlyUpTo": "最高可节省 20%",
"soloUseOnly": "仅限个人使用",
"subscribeForMore": "升级",
"subscribeNow": "立即订阅",
@@ -3912,34 +3819,10 @@
"subscribeToRun": "订阅",
"subscribeToRunFull": "订阅 Run",
"subscriptionRequiredMessage": "成员在云端运行工作流需要订阅",
"success": {
"allSet": "设置完成",
"planUpdated": "您的方案已成功更新。",
"receiptEmailed": "收据已发送至您的邮箱。"
},
"teamHeader": "适合希望协作的团队。需要更多成员?{learnMore} 了解企业版。",
"teamHeaderLearnMore": "了解更多",
"teamPlan": {
"changePlan": "更改方案",
"checkoutComingSoon": "团队方案结算即将上线。",
"comingSoonLabel": "即将上线:",
"cta": "订阅团队年付方案",
"ctaMonthly": "订阅团队月付方案",
"currentPlan": "当前方案",
"detailsTitle": "详情",
"name": "团队方案",
"perkConcurrentRuns": "成员可同时运行多个工作流",
"perkInviteMembers": "邀请团队成员",
"perkProjectAssets": "项目与资产管理",
"perkRolePermissions": "基于角色的权限管理",
"perkSharedPool": "所有成员共享积分池",
"tagline": "自定义每月积分订阅。订阅更多积分可享更大折扣。"
},
"teamWorkspace": "团队工作区",
"tierNameYearly": "{name} 年度",
"tiers": {
"creator": {
"feature1": "导入您自己的模型",
"name": "Creator"
},
"founder": {
@@ -3949,12 +3832,9 @@
"name": "免费"
},
"pro": {
"feature1": "更长的工作流运行时间(最长 1 小时)",
"name": "Pro"
},
"standard": {
"feature1": "最长 30 分钟工作流运行时间",
"feature2": "可随时增加积分",
"name": "Standard"
}
},
@@ -3967,7 +3847,6 @@
"upgradeToAddCredits": "升级以添加积分",
"usdPerMonth": "USD / mo",
"usdPerMonthPerMember": "美元 / 月 / 每成员",
"videoEstimate": "可生成约 {count} 个 5 秒视频*",
"videoEstimateExplanation": "这些预估基于 Wan Fun Control 生成 5 秒视频。",
"videoEstimateHelp": "这是什么?",
"videoEstimateLabel": "可使用 Wan Fun Control 模板生成 5 秒视频的数量",
@@ -3977,11 +3856,10 @@
"viewMoreDetails": "查看更多详情",
"viewMoreDetailsPlans": "查看有关订阅和定价的更多信息",
"viewUsageHistory": "查看使用历史",
"whatsIncluded": "包含内容:",
"workspaceNotSubscribed": "此工作区未订阅",
"yearly": "年度",
"yearlyCreditsLabel": "总共年度积分",
"yearlyDiscount": "20% 减免",
"saveYearly": "立省 20%",
"yourPlanIncludes": "您的计划包括:"
},
"tabMenu": {

View File

@@ -0,0 +1,46 @@
import { describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import { render, screen } from '@testing-library/vue'
import enMessages from '@/locales/en/main.json' with { type: 'json' }
import FreeTierDialogContent from './FreeTierDialogContent.vue'
const mockRenewalDate = vi.hoisted(() => ({ value: null as string | null }))
vi.mock('@/composables/billing/useBillingContext', () => ({
useBillingContext: vi.fn(() => ({
renewalDate: mockRenewalDate
}))
}))
function renderComponent() {
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: { en: enMessages }
})
return render(FreeTierDialogContent, {
global: {
plugins: [i18n]
}
})
}
describe('FreeTierDialogContent', () => {
it('renders the next refresh line formatted from the facade renewalDate', () => {
mockRenewalDate.value = '2026-07-15T10:00:00Z'
renderComponent()
expect(
screen.getByText('Your credits refresh on Jul 15, 2026.')
).toBeInTheDocument()
})
it('hides the next refresh line when renewalDate is null', () => {
mockRenewalDate.value = null
renderComponent()
expect(screen.queryByText(/credits refresh on/)).not.toBeInTheDocument()
})
})

View File

@@ -102,9 +102,9 @@
import { computed } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import type { SubscriptionDialogReason } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
import SubscriptionBenefits from '@/platform/cloud/subscription/components/SubscriptionBenefits.vue'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { getTierCredits } from '@/platform/cloud/subscription/constants/tierPricing'
defineProps<{
@@ -116,7 +116,17 @@ defineEmits<{
upgrade: []
}>()
const { formattedRenewalDate } = useSubscription()
const { renewalDate } = useBillingContext()
const formattedRenewalDate = computed(() => {
if (!renewalDate.value) return ''
return new Date(renewalDate.value).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
})
const freeTierCredits = computed(() => getTierCredits('free'))
</script>

View File

@@ -7,6 +7,7 @@ import { createI18n } from 'vue-i18n'
import PricingTable from '@/platform/cloud/subscription/components/PricingTable.vue'
import Button from '@/components/ui/button/Button.vue'
import type { SubscriptionTier } from '@/platform/cloud/subscription/constants/tierPricing'
import { PENDING_SUBSCRIPTION_CHECKOUT_STORAGE_KEY } from '@/platform/cloud/subscription/utils/subscriptionCheckoutTracker'
async function flushPromises() {
@@ -23,10 +24,8 @@ function createDeferredPromise<T>() {
}
const mockIsActiveSubscription = ref(false)
const mockSubscriptionTier = ref<
'STANDARD' | 'CREATOR' | 'PRO' | 'FOUNDERS_EDITION' | null
>(null)
const mockIsYearlySubscription = ref(false)
const mockSubscriptionTier = ref<SubscriptionTier | null>(null)
const mockSubscriptionDuration = ref<'MONTHLY' | 'ANNUAL'>('MONTHLY')
const mockAccessBillingPortal = vi.fn()
const mockReportError = vi.fn()
const mockTrackBeginCheckout = vi.fn()
@@ -65,13 +64,25 @@ Object.defineProperty(globalThis, 'localStorage', {
writable: true
})
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: () => ({
vi.mock('@/composables/billing/useBillingContext', () => ({
useBillingContext: () => ({
isActiveSubscription: computed(() => mockIsActiveSubscription.value),
isFreeTier: computed(() => false),
subscriptionTier: computed(() => mockSubscriptionTier.value),
isYearlySubscription: computed(() => mockIsYearlySubscription.value),
subscriptionStatus: ref(null)
isFreeTier: computed(() => mockSubscriptionTier.value === 'FREE'),
tier: computed(() => mockSubscriptionTier.value),
subscription: computed(() =>
mockSubscriptionTier.value
? {
isActive: mockIsActiveSubscription.value,
tier: mockSubscriptionTier.value,
duration: mockSubscriptionDuration.value,
planSlug: null,
renewalDate: null,
endDate: null,
isCancelled: false,
hasFunds: true
}
: null
)
})
}))
@@ -217,7 +228,7 @@ describe('PricingTable', () => {
vi.clearAllMocks()
mockIsActiveSubscription.value = false
mockSubscriptionTier.value = null
mockIsYearlySubscription.value = false
mockSubscriptionDuration.value = 'MONTHLY'
mockUserId.value = 'user-123'
mockAccessBillingPortal.mockReset()
mockAccessBillingPortal.mockResolvedValue(true)
@@ -362,6 +373,7 @@ describe('PricingTable', () => {
it('should not call accessBillingPortal when clicking current plan', async () => {
mockIsActiveSubscription.value = true
mockSubscriptionTier.value = 'CREATOR'
mockSubscriptionDuration.value = 'ANNUAL'
renderComponent()
await flushPromises()
@@ -370,12 +382,29 @@ describe('PricingTable', () => {
.getAllByRole('button')
.find((b) => b.textContent?.includes('Current Plan'))
expect(currentPlanButton).toBeDefined()
expect(currentPlanButton).toBeDisabled()
await userEvent.click(currentPlanButton!)
await flushPromises()
expect(mockAccessBillingPortal).not.toHaveBeenCalled()
})
it('does not highlight a current plan when the facade duration differs from the selected cycle', async () => {
mockIsActiveSubscription.value = true
mockSubscriptionTier.value = 'CREATOR'
mockSubscriptionDuration.value = 'MONTHLY'
renderComponent()
await flushPromises()
const currentPlanButton = screen
.getAllByRole('button')
.find((b) => b.textContent?.includes('Current Plan'))
expect(currentPlanButton).toBeUndefined()
})
it('should initiate checkout instead of billing portal for new subscribers', async () => {
mockIsActiveSubscription.value = false

View File

@@ -30,9 +30,9 @@
<span>{{ option.label }}</span>
<div
v-if="option.value === 'yearly'"
class="flex items-center rounded-full bg-primary-background px-1 py-0.5 text-2xs font-bold text-white"
class="flex items-center rounded-full bg-primary-background px-2 py-0.5 text-2xs font-bold whitespace-nowrap text-white"
>
-20%
{{ t('subscription.saveYearly') }}
</div>
</div>
</template>
@@ -67,15 +67,15 @@
<div class="flex flex-col gap-2">
<div class="flex flex-row items-baseline gap-2">
<span
class="font-inter text-[28px] leading-normal font-semibold text-base-foreground"
class="font-inter text-[28px] leading-normal font-semibold text-base-foreground tabular-nums"
>
${{ getPrice(tier) }}
<span
v-show="currentBillingCycle === 'yearly'"
class="text-2xl text-muted-foreground line-through"
>
${{ tier.pricing.monthly }}
</span>
${{ getPrice(tier) }}
</span>
<span class="font-inter text-xl/normal text-base-foreground">
{{ t('subscription.usdPerMonth') }}
@@ -122,9 +122,12 @@
}}
</span>
<div class="flex flex-row items-center gap-1">
<i class="icon-[lucide--component] text-sm text-amber-400" />
<i
class="icon-[comfy--credits] size-4 shrink-0 bg-amber-400"
aria-hidden="true"
/>
<span
class="font-inter text-sm/normal font-bold text-base-foreground"
class="font-inter text-sm/normal font-bold text-base-foreground tabular-nums"
>
{{ n(getCreditsDisplay(tier)) }}
</span>
@@ -136,7 +139,7 @@
{{ t('subscription.maxDurationLabel') }}
</span>
<span
class="font-inter text-sm/normal font-bold text-base-foreground"
class="font-inter text-sm/normal font-bold text-base-foreground tabular-nums"
>
{{ tier.maxDuration }}
</span>
@@ -186,7 +189,7 @@
</div>
</div>
<span
class="font-inter text-sm/normal font-bold text-base-foreground"
class="font-inter text-sm/normal font-bold text-base-foreground tabular-nums"
>
~{{ n(tier.pricing.videoEstimate) }}
</span>
@@ -263,8 +266,8 @@ import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useAuthActions } from '@/composables/auth/useAuthActions'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import {
TIER_PRICING,
TIER_TO_KEY
@@ -361,9 +364,13 @@ const tiers: PricingTierConfig[] = [
const {
isActiveSubscription,
isFreeTier,
subscriptionTier,
isYearlySubscription
} = useSubscription()
tier: subscriptionTier,
subscription
} = useBillingContext()
const isYearlySubscription = computed(
() => subscription.value?.duration === 'ANNUAL'
)
const telemetry = useTelemetry()
const { userId } = storeToRefs(useAuthStore())
const { accessBillingPortal, reportError } = useAuthActions()

View File

@@ -16,7 +16,6 @@ import { onBeforeUnmount, ref, watch } from 'vue'
import Button from '@/components/ui/button/Button.vue'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { isCloud } from '@/platform/distribution/types'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useTelemetry } from '@/platform/telemetry'
import { cn } from '@comfyorg/tailwind-utils'
@@ -38,8 +37,8 @@ const emit = defineEmits<{
subscribed: []
}>()
const { isActiveSubscription, showSubscriptionDialog } = useBillingContext()
const { subscriptionTier } = useSubscription()
const { isActiveSubscription, showSubscriptionDialog, tier } =
useBillingContext()
const isAwaitingStripeSubscription = ref(false)
watch(
@@ -54,7 +53,7 @@ watch(
const handleSubscribe = () => {
useTelemetry()?.trackSubscription('subscribe_clicked', {
current_tier: subscriptionTier.value?.toLowerCase()
current_tier: tier.value?.toLowerCase()
})
isAwaitingStripeSubscription.value = true
showSubscriptionDialog()

View File

@@ -2,26 +2,26 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useSubscriptionActions } from '@/platform/cloud/subscription/composables/useSubscriptionActions'
// Mock dependencies
const mockFetchBalance = vi.fn()
const mockBillingFetchBalance = vi.fn()
const mockAuthFetchBalance = vi.fn()
const mockFetchStatus = vi.fn()
const mockShowTopUpCreditsDialog = vi.fn()
const mockExecute = vi.fn()
const mockToastAdd = vi.fn()
vi.mock('@/platform/updates/common/toastStore', () => ({
useToastStore: () => ({ add: mockToastAdd })
}))
vi.mock('@/composables/auth/useAuthActions', () => ({
useAuthActions: () => ({
fetchBalance: mockFetchBalance
})
}))
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: () => ({
fetchStatus: mockFetchStatus
fetchBalance: mockAuthFetchBalance
})
}))
vi.mock('@/composables/billing/useBillingContext', () => ({
useBillingContext: () => ({
fetchBalance: mockBillingFetchBalance,
fetchStatus: mockFetchStatus
})
}))
@@ -119,20 +119,21 @@ describe('useSubscriptionActions', () => {
})
describe('handleRefresh', () => {
it('should call both fetchBalance and fetchStatus', async () => {
it('should refresh balance and status through the billing facade', async () => {
const { handleRefresh } = useSubscriptionActions()
await handleRefresh()
expect(mockFetchBalance).toHaveBeenCalledOnce()
expect(mockBillingFetchBalance).toHaveBeenCalledOnce()
expect(mockFetchStatus).toHaveBeenCalledOnce()
expect(mockAuthFetchBalance).not.toHaveBeenCalled()
})
it('should handle errors gracefully', async () => {
mockFetchBalance.mockRejectedValueOnce(new Error('Fetch failed'))
it('swallows refresh failures without surfacing a toast', async () => {
mockBillingFetchBalance.mockRejectedValueOnce(new Error('Fetch failed'))
const { handleRefresh } = useSubscriptionActions()
// Should not throw
await expect(handleRefresh()).resolves.toBeUndefined()
expect(mockToastAdd).not.toHaveBeenCalled()
})
})

View File

@@ -1,7 +1,6 @@
import { onMounted, ref } from 'vue'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useAuthActions } from '@/composables/auth/useAuthActions'
import { useTelemetry } from '@/platform/telemetry'
import { useDialogService } from '@/services/dialogService'
import { useCommandStore } from '@/stores/commandStore'
@@ -11,10 +10,9 @@ import { useCommandStore } from '@/stores/commandStore'
*/
export function useSubscriptionActions() {
const dialogService = useDialogService()
const authActions = useAuthActions()
const commandStore = useCommandStore()
const telemetry = useTelemetry()
const { fetchStatus } = useBillingContext()
const { fetchBalance, fetchStatus } = useBillingContext()
const isLoadingSupport = ref(false)
@@ -44,7 +42,7 @@ export function useSubscriptionActions() {
const handleRefresh = async () => {
try {
await Promise.all([authActions.fetchBalance(), fetchStatus()])
await Promise.all([fetchBalance(), fetchStatus()])
} catch (error) {
console.error('[useSubscriptionActions] Error refreshing data:', error)
}

View File

@@ -90,7 +90,7 @@ import CurrentUserMessage from '@/components/dialog/content/setting/CurrentUserM
import BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'
import NavItem from '@/components/widget/nav/NavItem.vue'
import NavTitle from '@/components/widget/nav/NavTitle.vue'
import { useAuthActions } from '@/composables/auth/useAuthActions'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import ColorPaletteMessage from '@/platform/settings/components/ColorPaletteMessage.vue'
import SettingsPanel from '@/platform/settings/components/SettingsPanel.vue'
import { useSettingSearch } from '@/platform/settings/composables/useSettingSearch'
@@ -130,7 +130,7 @@ const {
getSearchResults
} = useSettingSearch()
const authActions = useAuthActions()
const { fetchBalance } = useBillingContext()
const navRef = ref<HTMLElement | null>(null)
const activeCategoryKey = ref<string | null>(defaultCategory.value?.key ?? null)
@@ -238,7 +238,7 @@ watch(activeCategoryKey, (newKey, oldKey) => {
activeCategoryKey.value = oldKey
}
if (newKey === 'credits') {
void authActions.fetchBalance()
void fetchBalance()
}
if (newKey) {
void nextTick(() => {

View File

@@ -1,4 +1,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as VueModule from 'vue'
import type { Ref } from 'vue'
import { nextTick, ref } from 'vue'
import { TelemetryEvents } from '../../types'
@@ -12,6 +15,10 @@ const hoisted = vi.hoisted(() => {
const mockReset = vi.fn()
const mockOnUserResolved = vi.fn()
const mockOnUserLogout = vi.fn()
const refs = {
tier: null as unknown as Ref<string | null>,
remoteConfig: null as unknown as Ref<Record<string, unknown> | null>
}
return {
mockCapture,
@@ -23,6 +30,7 @@ const hoisted = vi.hoisted(() => {
mockReset,
mockOnUserResolved,
mockOnUserLogout,
refs,
mockPosthog: {
default: {
init: mockInit,
@@ -36,14 +44,6 @@ const hoisted = vi.hoisted(() => {
}
})
vi.mock('vue', async () => {
const actual = await vi.importActual('vue')
return {
...actual,
watch: vi.fn()
}
})
vi.mock('@/composables/auth/useCurrentUser', () => ({
useCurrentUser: () => ({
onUserResolved: hoisted.mockOnUserResolved,
@@ -51,21 +51,19 @@ vi.mock('@/composables/auth/useCurrentUser', () => ({
})
}))
const mockRemoteConfig = vi.hoisted(
() => ({ value: null }) as { value: Record<string, unknown> | null }
)
vi.mock('@/platform/remoteConfig/remoteConfig', () => ({
remoteConfig: mockRemoteConfig
}))
vi.mock('@/platform/remoteConfig/remoteConfig', async () => {
const { ref } = await vi.importActual<typeof VueModule>('vue')
hoisted.refs.remoteConfig = ref<Record<string, unknown> | null>(null)
return { remoteConfig: hoisted.refs.remoteConfig }
})
vi.mock('posthog-js', () => hoisted.mockPosthog)
vi.mock('@/platform/cloud/subscription/composables/useSubscription', () => ({
useSubscription: () => ({
subscriptionTier: { value: null }
})
}))
vi.mock('@/composables/billing/useBillingContext', async () => {
const { ref } = await vi.importActual<typeof VueModule>('vue')
hoisted.refs.tier = ref<string | null>(null)
return { useBillingContext: () => ({ tier: hoisted.refs.tier }) }
})
import { PostHogTelemetryProvider } from './PostHogTelemetryProvider'
@@ -82,7 +80,10 @@ function createProvider(
describe('PostHogTelemetryProvider', () => {
beforeEach(() => {
vi.clearAllMocks()
mockRemoteConfig.value = null
hoisted.refs.remoteConfig.value = null
// Fresh tier ref per test: each provider registers an undisposed tier
// watch, so a shared ref would leak watchers across tests.
hoisted.refs.tier = ref<string | null>(null)
window.__CONFIG__ = {
posthog_project_token: 'phc_test_token'
} as typeof window.__CONFIG__
@@ -116,7 +117,7 @@ describe('PostHogTelemetryProvider', () => {
})
it('applies posthog_config overrides from remote config', async () => {
mockRemoteConfig.value = {
hoisted.refs.remoteConfig.value = {
posthog_config: {
debug: true,
api_host: 'https://custom.host.com'
@@ -150,6 +151,48 @@ describe('PostHogTelemetryProvider', () => {
expect(hoisted.mockIdentify).toHaveBeenCalledWith('user-123')
})
function tierPropertySets(): unknown[] {
return hoisted.mockPeopleSet.mock.calls
.map(([props]) => props)
.filter((props) => props && 'subscription_tier' in props)
}
it('sets subscription_tier reactively when the facade tier resolves', async () => {
createProvider()
await vi.dynamicImportSettled()
const onResolved = hoisted.mockOnUserResolved.mock.calls[0][0]
onResolved({ id: 'user-123' })
// Unresolved tier (null) does not set the property
expect(tierPropertySets()).toHaveLength(0)
hoisted.refs.tier.value = 'PRO'
await nextTick()
expect(hoisted.mockPeopleSet).toHaveBeenCalledWith({
subscription_tier: 'PRO'
})
hoisted.refs.tier.value = null
await nextTick()
expect(tierPropertySets()).toHaveLength(1)
})
it('keeps a single tier watcher across repeated user resolutions', async () => {
createProvider()
await vi.dynamicImportSettled()
const onResolved = hoisted.mockOnUserResolved.mock.calls[0][0]
onResolved({ id: 'user-1' })
onResolved({ id: 'user-1' })
onResolved({ id: 'user-2' })
hoisted.refs.tier.value = 'PRO'
await nextTick()
expect(tierPropertySets()).toHaveLength(1)
})
})
describe('desktop entry capture', () => {
@@ -670,7 +713,7 @@ describe('PostHogTelemetryProvider', () => {
it('remoteConfig.posthog_config cannot override before_send or person_profiles', async () => {
const remoteBefore_send = vi.fn()
mockRemoteConfig.value = {
hoisted.refs.remoteConfig.value = {
posthog_config: {
before_send: remoteBefore_send,
person_profiles: 'always'

View File

@@ -1,10 +1,11 @@
import type { PostHog } from 'posthog-js'
import { watch } from 'vue'
import type { WatchStopHandle } from 'vue'
import { createPostHogBeforeSend } from '@comfyorg/shared-frontend-utils/piiUtil'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
import type { RemoteConfig } from '@/platform/remoteConfig/types'
@@ -98,6 +99,7 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
private isInitialized = false
private disabledEvents = new Set<TelemetryEventName>(DEFAULT_DISABLED_EVENTS)
private desktopEntryProps: DesktopEntryProps | null = null
private stopSubscriptionTierWatch: WatchStopHandle | null = null
constructor() {
this.configureDisabledEvents(
@@ -307,12 +309,13 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
}
private setSubscriptionProperties(): void {
const { subscriptionTier } = useSubscription()
watch(
subscriptionTier,
(tier) => {
if (tier && this.posthog) {
this.posthog.people.set({ subscription_tier: tier })
if (this.stopSubscriptionTierWatch) return
const { tier } = useBillingContext()
this.stopSubscriptionTierWatch = watch(
tier,
(value) => {
if (value && this.posthog) {
this.posthog.people.set({ subscription_tier: value })
}
},
{ immediate: true }