diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx index 81675ce604..74288e1106 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/cancel.tsx @@ -2,6 +2,7 @@ import { Button, type ButtonProps, useConfirmModal } from '@affine/component'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useMutation } from '@affine/core/hooks/use-mutation'; import { cancelSubscriptionMutation, SubscriptionPlan } from '@affine/graphql'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { nanoid } from 'nanoid'; import { useState } from 'react'; @@ -12,6 +13,7 @@ export const AICancel = ({ onSubscriptionUpdate, ...btnProps }: AICancelProps) => { + const t = useAFFiNEI18N(); const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); const { trigger, isMutating } = useMutation({ mutation: cancelSubscriptionMutation, @@ -20,15 +22,17 @@ export const AICancel = ({ const cancel = useAsyncCallback(async () => { openConfirmModal({ - title: 'Cancel Subscription', + title: t['com.affine.payment.ai.action.cancel.confirm.title'](), description: - 'If you end your subscription now, you can still use AFFiNE AI until the end of this billing period.', + t['com.affine.payment.ai.action.cancel.confirm.description'](), reverseFooter: true, confirmButtonOptions: { - children: 'Cancel Subscription', + children: + t['com.affine.payment.ai.action.cancel.confirm.confirm-text'](), type: 'default', }, - cancelText: 'Keep AFFiNE AI', + cancelText: + t['com.affine.payment.ai.action.cancel.confirm.cancel-text'](), cancelButtonOptions: { type: 'primary', }, @@ -45,11 +49,11 @@ export const AICancel = ({ ); }, }); - }, [openConfirmModal, trigger, idempotencyKey, onSubscriptionUpdate]); + }, [openConfirmModal, t, trigger, idempotencyKey, onSubscriptionUpdate]); return ( ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/login.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/login.tsx index 8ed8c64841..e4635227f2 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/login.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/login.tsx @@ -1,11 +1,13 @@ import { Button, type ButtonProps } from '@affine/component'; import { authAtom } from '@affine/core/atoms'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useSetAtom } from 'jotai'; import { useCallback } from 'react'; import type { BaseActionProps } from '../types'; export const AILogin = (btnProps: BaseActionProps & ButtonProps) => { + const t = useAFFiNEI18N(); const setOpen = useSetAtom(authAtom); const onClickSignIn = useCallback(() => { @@ -17,7 +19,7 @@ export const AILogin = (btnProps: BaseActionProps & ButtonProps) => { return ( ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx index 1259160c20..fb2ca500fe 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/actions/resume.tsx @@ -7,6 +7,7 @@ import { import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useMutation } from '@affine/core/hooks/use-mutation'; import { resumeSubscriptionMutation, SubscriptionPlan } from '@affine/graphql'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { SingleSelectSelectSolidIcon } from '@blocksuite/icons'; import { cssVar } from '@toeverything/theme'; import { nanoid } from 'nanoid'; @@ -20,6 +21,7 @@ export const AIResume = ({ onSubscriptionUpdate, ...btnProps }: AIResumeProps) => { + const t = useAFFiNEI18N(); const [idempotencyKey, setIdempotencyKey] = useState(nanoid()); const { isMutating, trigger } = useMutation({ @@ -29,13 +31,16 @@ export const AIResume = ({ const resume = useAsyncCallback(async () => { openConfirmModal({ - title: 'Resume Auto-Renewal?', + title: t['com.affine.payment.ai.action.resume.confirm.title'](), description: - 'Are you sure you want to resume the subscription for AFFiNE AI? This means your payment method will be charged automatically at the end of each billing cycle, starting from the next billing cycle.', + t['com.affine.payment.ai.action.resume.confirm.description'](), confirmButtonOptions: { - children: 'Confirm', + children: + t['com.affine.payment.ai.action.resume.confirm.confirm-text'](), type: 'primary', }, + cancelText: + t['com.affine.payment.ai.action.resume.confirm.cancel-text'](), onConfirm: async () => { await trigger( { idempotencyKey, plan: SubscriptionPlan.AI }, @@ -50,19 +55,23 @@ export const AIResume = ({ color={cssVar('processingColor')} /> ), - title: 'Subscription Updated', - message: 'You will be charged in the next billing cycle.', + title: + t[ + 'com.affine.payment.ai.action.resume.confirm.notify.title' + ](), + message: + t['com.affine.payment.ai.action.resume.confirm.notify.msg'](), }); }, } ); }, }); - }, [openConfirmModal, trigger, idempotencyKey, onSubscriptionUpdate]); + }, [openConfirmModal, t, trigger, idempotencyKey, onSubscriptionUpdate]); return ( ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.css.ts index b6113186c5..335b0014cf 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.css.ts +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.css.ts @@ -41,6 +41,10 @@ export const actionBlock = style({ alignItems: 'start', marginBottom: 24, }); +export const actionButtons = style({ + display: 'flex', + gap: 12, +}); export const purchaseButton = style({ minWidth: 160, height: 37, @@ -50,6 +54,14 @@ export const purchaseButton = style({ lineHeight: '14px', letterSpacing: '-1%', }); +export const learnAIButton = style([ + purchaseButton, + { + color: cssVar('textEmphasisColor'), + paddingLeft: 16, + paddingRight: 16, + }, +]); export const agreement = style({ fontSize: cssVar('fontXs'), fontWeight: 400, diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.tsx index 5d4f7fa92e..2f75452e1a 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/ai-plan.tsx @@ -1,3 +1,4 @@ +import { Button } from '@affine/component'; import { type SubscriptionMutator, useUserSubscription, @@ -7,6 +8,7 @@ import { SubscriptionPlan, SubscriptionRecurring, } from '@affine/graphql'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { AIPlanLayout } from '../layout'; import * as styles from './ai-plan.css'; @@ -19,6 +21,7 @@ interface AIPlanProps { onSubscriptionUpdate: SubscriptionMutator; } export const AIPlan = ({ price, onSubscriptionUpdate }: AIPlanProps) => { + const t = useAFFiNEI18N(); const recurring = SubscriptionRecurring.Yearly; const { Action, billingTip } = useAffineAISubscription(); @@ -35,25 +38,37 @@ export const AIPlan = ({ price, onSubscriptionUpdate }: AIPlanProps) => { return (
- Turn all your ideas into reality + {t['com.affine.payment.ai.pricing-plan.title-caption-1']()} +
+
+ {t['com.affine.payment.ai.pricing-plan.title']()}
-
AFFiNE AI
- A true multimodal AI copilot. + {t['com.affine.payment.ai.pricing-plan.title-caption-2']()}
- +
+ + {subscription ? null : ( + + + + )} +
{billingTip ? (
{billingTip}
) : null} diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/benefits.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/benefits.tsx index 0fb0337187..620c15709b 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/benefits.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/benefits.tsx @@ -1,38 +1,42 @@ -import { ShapeIcon } from '@blocksuite/icons'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { CheckBoxCheckLinearIcon, PenIcon, TextIcon } from '@blocksuite/icons'; +import { useMemo } from 'react'; import * as styles from './ai-plan.css'; -const benefits = [ +const benefitsGetter = (t: ReturnType) => [ { - name: 'Write with you', - icon: , + name: t['com.affine.payment.ai.benefit.g1'](), + icon: , items: [ - 'Create quality content from sentences to articles on topics you need', - 'Rewrite like the professionals', - 'Change the tones / fix spelling & grammar', + t['com.affine.payment.ai.benefit.g1-1'](), + t['com.affine.payment.ai.benefit.g1-2'](), + t['com.affine.payment.ai.benefit.g1-3'](), ], }, { - name: 'Draw with you', - icon: , + name: t['com.affine.payment.ai.benefit.g2'](), + icon: , items: [ - 'Visualize your mind, magically', - 'Turn your outline into beautiful, engaging presentations', - 'Summarize your content into structured mind-map', + t['com.affine.payment.ai.benefit.g2-1'](), + t['com.affine.payment.ai.benefit.g2-2'](), + t['com.affine.payment.ai.benefit.g2-3'](), ], }, { - name: 'Plan with you', - icon: , + name: t['com.affine.payment.ai.benefit.g3'](), + icon: , items: [ - 'Memorize and tidy up your knowledge', - 'Auto-sorting and auto-tagging', - 'Open source & Privacy ensured', + t['com.affine.payment.ai.benefit.g3-1'](), + t['com.affine.payment.ai.benefit.g3-2'](), + t['com.affine.payment.ai.benefit.g3-3'](), ], }, ]; export const AIBenefits = () => { + const t = useAFFiNEI18N(); + const benefits = useMemo(() => benefitsGetter(t), [t]); // TODO: responsive return (
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/use-affine-ai-subscription.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/use-affine-ai-subscription.ts index c1b975e589..6895db8437 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/use-affine-ai-subscription.ts +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/ai/use-affine-ai-subscription.ts @@ -2,6 +2,7 @@ import { useCurrentLoginStatus } from '@affine/core/hooks/affine/use-current-log import { useUserSubscription } from '@affine/core/hooks/use-subscription'; import { timestampToLocalDate } from '@affine/core/utils'; import { SubscriptionPlan } from '@affine/graphql'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { AICancel, AILogin, AIResume, AISubscribe } from './actions'; @@ -10,6 +11,7 @@ const plan = SubscriptionPlan.AI; export type ActionType = 'login' | 'subscribe' | 'resume' | 'cancel'; export const useAffineAISubscription = () => { + const t = useAFFiNEI18N(); const loggedIn = useCurrentLoginStatus() === 'authenticated'; const [subscription] = useUserSubscription(plan); @@ -31,9 +33,13 @@ export const useAffineAISubscription = () => { }[actionType]; const billingTip = subscription?.nextBillAt - ? `You have purchased AFFiNE AI. The next payment date is ${timestampToLocalDate(subscription.nextBillAt)}.` + ? t['com.affine.payment.ai.billing-tip.next-bill-at']({ + due: timestampToLocalDate(subscription.nextBillAt), + }) : subscription?.canceledAt && subscription.end - ? `You have purchased AFFiNE AI. The expiration date is ${timestampToLocalDate(subscription.end)}.` + ? t['com.affine.payment.ai.billing-tip.end-at']({ + end: timestampToLocalDate(subscription.end), + }) : null; return { actionType, Action, billingTip }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/cloud-plans.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/cloud-plans.tsx index 6359a7adb6..cf101db7d1 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/cloud-plans.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/cloud-plans.tsx @@ -1,9 +1,14 @@ // TODO: we don't handle i18n for now // it's better to manage all equity at server side import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql'; +import type { useAFFiNEI18N } from '@affine/i18n/hooks'; import { AfFiNeIcon } from '@blocksuite/icons'; import type { ReactNode } from 'react'; +import { planTitleTitleCaption } from './style.css'; + +type T = ReturnType; + export type Benefits = Record< string, Array<{ @@ -11,6 +16,7 @@ export type Benefits = Record< title: ReactNode; }> >; +type BenefitsGetter = (t: T) => Benefits; interface BasePrice { plan: SubscriptionPlan; name: string; @@ -37,49 +43,49 @@ export interface DynamicPrice extends BasePrice { ) => ReactNode; } -const freeBenefits: Benefits = { - 'Include in FOSS': [ - { title: 'Unlimited Local Workspaces.' }, - { title: 'Unlimited use and Customization.' }, - { title: 'Unlimited Doc and Edgeless editing.' }, - ], - 'Include in Basic': [ - { title: '10 GB of Cloud Storage.' }, - { title: '10 MB of Maximum file size.' }, - { title: 'Up to 3 members per Workspace.' }, - { title: '7-days Cloud Time Machine file version history.' }, - { title: 'Up to 3 login devices.' }, - ], -}; +const freeBenefits: BenefitsGetter = t => ({ + [t['com.affine.payment.cloud.free.benefit.g1']()]: ([1, 2, 3] as const).map( + i => ({ + title: t[`com.affine.payment.cloud.free.benefit.g1-${i}`](), + }) + ), + [t['com.affine.payment.cloud.free.benefit.g2']()]: ( + [1, 2, 3, 4, 5] as const + ).map(i => ({ + title: t[`com.affine.payment.cloud.free.benefit.g2-${i}`](), + })), +}); -const proBenefits: Benefits = { - 'Include in Pro': [ - { title: 'Everything in AFFiNE FOSS & Basic.', icon: }, - { title: '100 GB of Cloud Storage.' }, - { title: '100 MB of Maximum file size.' }, - { title: 'Up to 10 members per Workspace.' }, - { title: '30-days Cloud Time Machine file version history.' }, - { title: 'Add comments on Doc and Edgeless.' }, - { title: 'Community Support.' }, - { title: 'Real-time Syncing & Collaboration for more people.' }, +const proBenefits: BenefitsGetter = t => ({ + [t['com.affine.payment.cloud.pro.benefit.g1']()]: [ + { + title: t['com.affine.payment.cloud.pro.benefit.g1-1'](), + icon: , + }, + ...([2, 3, 4, 5, 6, 7, 8] as const).map(i => ({ + title: t[`com.affine.payment.cloud.pro.benefit.g1-${i}`](), + })), ], -}; +}); -const teamBenefits: Benefits = { - 'Both in Team & Enterprise': [ - { title: 'Everything in AFFiNE Pro.', icon: }, - { title: 'Advanced Permission control, Page history and Review mode.' }, - { title: 'Pay for seats, fits all team size.' }, - { title: 'Email & Slack Support.' }, +const teamBenefits: BenefitsGetter = t => ({ + [t['com.affine.payment.cloud.team.benefit.g1']()]: [ + { + title: t['com.affine.payment.cloud.team.benefit.g1-1'](), + icon: , + }, + ...([2, 3, 4] as const).map(i => ({ + title: t[`com.affine.payment.cloud.team.benefit.g1-${i}`](), + })), ], - 'Enterprise only': [ - { title: 'SSO Authorization.' }, - { title: 'Solutions & Best Practices for Dedicated needs.' }, - { title: 'Embed-able & Integrations with IT support.' }, + [t['com.affine.payment.cloud.team.benefit.g2']()]: [ + { title: t['com.affine.payment.cloud.team.benefit.g2-1']() }, + { title: t['com.affine.payment.cloud.team.benefit.g2-2']() }, + { title: t['com.affine.payment.cloud.team.benefit.g2-3']() }, ], -}; +}); -export function getPlanDetail() { +export function getPlanDetail(t: T) { return new Map([ [ SubscriptionPlan.Free, @@ -88,10 +94,10 @@ export function getPlanDetail() { plan: SubscriptionPlan.Free, price: '0', yearlyPrice: '0', - name: 'FOSS + Basic', - description: 'Open-Source under MIT license.', - titleRenderer: () => 'Free forever', - benefits: freeBenefits, + name: t['com.affine.payment.cloud.free.name'](), + description: t['com.affine.payment.cloud.free.description'](), + titleRenderer: () => t['com.affine.payment.cloud.free.title'](), + benefits: freeBenefits(t), }, ], [ @@ -101,16 +107,25 @@ export function getPlanDetail() { plan: SubscriptionPlan.Pro, price: '1', yearlyPrice: '1', - name: 'Pro', - description: 'For family and small teams.', + name: t['com.affine.payment.cloud.pro.name'](), + description: t['com.affine.payment.cloud.pro.description'](), titleRenderer: (recurring, detail) => { const price = recurring === SubscriptionRecurring.Yearly ? detail.yearlyPrice : detail.price; - return `$${price} per month`; + return ( + <> + {t['com.affine.payment.cloud.pro.title.price-monthly']({ price })} + {recurring === SubscriptionRecurring.Yearly ? ( + + {t['com.affine.payment.cloud.pro.title.billed-yearly']()} + + ) : null} + + ); }, - benefits: proBenefits, + benefits: proBenefits(t), }, ], [ @@ -119,10 +134,10 @@ export function getPlanDetail() { type: 'dynamic', plan: SubscriptionPlan.Team, contact: true, - name: 'Team / Enterprise', - description: 'Best for scalable teams.', - titleRenderer: () => 'Contact Sales', - benefits: teamBenefits, + name: t['com.affine.payment.cloud.team.name'](), + description: t['com.affine.payment.cloud.team.description'](), + titleRenderer: () => t['com.affine.payment.cloud.team.title'](), + benefits: teamBenefits(t), }, ], ]); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx index e576e9c0c9..39a9a80b80 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx @@ -8,7 +8,7 @@ import { Trans } from '@affine/i18n'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { SingleSelectSelectSolidIcon } from '@blocksuite/icons'; import { cssVar } from '@toeverything/theme'; -import { Suspense, useEffect, useRef, useState } from 'react'; +import { Suspense, useEffect, useMemo, useRef, useState } from 'react'; import type { FallbackProps } from 'react-error-boundary'; import { SWRErrorBoundary } from '../../../../../components/pure/swr-error-bundary'; @@ -39,7 +39,7 @@ const Settings = () => { const [subscription, mutateSubscription] = useUserSubscription(); const loggedIn = useCurrentLoginStatus() === 'authenticated'; - const planDetail = getPlanDetail(); + const planDetail = useMemo(() => getPlanDetail(t), [t]); const scrollWrapper = useRef(null); const { @@ -138,15 +138,23 @@ const Settings = () => {
{recurring === SubscriptionRecurring.Yearly ? ( -
Yearly
+
+ {t['com.affine.payment.cloud.pricing-plan.toggle-yearly']()} +
) : ( <>
- Billed Yearly + + {t[ + 'com.affine.payment.cloud.pricing-plan.toggle-billed-yearly' + ]()} +
{yearlyDiscount ? (
- Saving {yearlyDiscount}% + {t['com.affine.payment.cloud.pricing-plan.toggle-discount']({ + discount: yearlyDiscount, + })}
) : null} @@ -201,8 +209,8 @@ const Settings = () => { const cloudSelect = (
- Hosted by AFFiNE.Pro - We host, no technical setup required. + {t['com.affine.payment.cloud.pricing-plan.select.title']()} + {t['com.affine.payment.cloud.pricing-plan.select.caption']()}
); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts index f14fe13b00..bf0b03daca 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts @@ -153,6 +153,13 @@ export const planTitleTitle = style({ fontSize: cssVar('fontBase'), lineHeight: '20px', }); +export const planTitleTitleCaption = style({ + fontWeight: 500, + fontSize: cssVar('fontXs'), + lineHeight: '20px', + color: cssVar('textSecondaryColor'), + marginLeft: 4, +}); export const planPriceWrapper = style({ minHeight: '28px', lineHeight: 1, diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index fc01dd81fc..c8fb983623 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -82,6 +82,7 @@ export enum ServerDeploymentType { } export enum ServerFeature { + Copilot = 'Copilot', OAuth = 'OAuth', Payment = 'Payment', } diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index e506a808d0..3baccc8c67 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -834,6 +834,82 @@ "com.affine.pageMode.all": "all", "com.affine.pageMode.edgeless": "Edgeless", "com.affine.pageMode.page": "Page", + "com.affine.payment.cloud.pricing-plan.select.title": "Hosted by AFFiNE.Pro", + "com.affine.payment.cloud.pricing-plan.select.caption": "We host, no technical setup required.", + "com.affine.payment.cloud.pricing-plan.toggle-yearly": "Yearly", + "com.affine.payment.cloud.pricing-plan.toggle-billed-yearly": "Billed Yearly", + "com.affine.payment.cloud.pricing-plan.toggle-discount": "Saving {{discount}}%", + "com.affine.payment.cloud.free.name": "FOSS + Basic", + "com.affine.payment.cloud.free.description": "Open-Source under MIT license.", + "com.affine.payment.cloud.free.title": "Free forever", + "com.affine.payment.cloud.free.benefit.g1": "Include in FOSS", + "com.affine.payment.cloud.free.benefit.g1-1": "Unlimited Local Workspaces", + "com.affine.payment.cloud.free.benefit.g1-2": "Unlimited use and Customization", + "com.affine.payment.cloud.free.benefit.g1-3": "Unlimited Doc and Edgeless editing", + "com.affine.payment.cloud.free.benefit.g2": "Include in Basic", + "com.affine.payment.cloud.free.benefit.g2-1": "10 GB of Cloud Storage.", + "com.affine.payment.cloud.free.benefit.g2-2": "10 MB of Maximum file size.", + "com.affine.payment.cloud.free.benefit.g2-3": "Up to 3 members per Workspace.", + "com.affine.payment.cloud.free.benefit.g2-4": "7-days Cloud Time Machine file version history.", + "com.affine.payment.cloud.free.benefit.g2-5": "Up to 3 login devices.", + "com.affine.payment.cloud.pro.name": "Pro", + "com.affine.payment.cloud.pro.description": "For family and small teams.", + "com.affine.payment.cloud.pro.title.price-monthly": "{{price}} per month", + "com.affine.payment.cloud.pro.title.billed-yearly": "billed yearly", + "com.affine.payment.cloud.pro.benefit.g1": "Include in Pro", + "com.affine.payment.cloud.pro.benefit.g1-1": "Everything in AFFiNE FOSS & Basic.", + "com.affine.payment.cloud.pro.benefit.g1-2": "100 GB of Cloud Storage.", + "com.affine.payment.cloud.pro.benefit.g1-3": "100 MB of Maximum file size.", + "com.affine.payment.cloud.pro.benefit.g1-4": "Up to 10 members per Workspace.", + "com.affine.payment.cloud.pro.benefit.g1-5": "30-days Cloud Time Machine file version history.", + "com.affine.payment.cloud.pro.benefit.g1-6": "Add comments on Doc and Edgeless.", + "com.affine.payment.cloud.pro.benefit.g1-7": "Community Support.", + "com.affine.payment.cloud.pro.benefit.g1-8": "Real-time Syncing & Collaboration for more people.", + "com.affine.payment.cloud.team.name": "Team / Enterprise", + "com.affine.payment.cloud.team.description": "Best for scalable teams.", + "com.affine.payment.cloud.team.title": "Contact Sales", + "com.affine.payment.cloud.team.benefit.g1": "Both in Team & Enterprise", + "com.affine.payment.cloud.team.benefit.g1-1": "Everything in AFFiNE Pro.", + "com.affine.payment.cloud.team.benefit.g1-2": "Advanced Permission control, Page history and Review mode.", + "com.affine.payment.cloud.team.benefit.g1-3": "Pay for seats, fits all team size.", + "com.affine.payment.cloud.team.benefit.g1-4": "Email & Slack Support.", + "com.affine.payment.cloud.team.benefit.g2": "Enterprise only", + "com.affine.payment.cloud.team.benefit.g2-1": "SSO Authorization.", + "com.affine.payment.cloud.team.benefit.g2-2": "Solutions & Best Practices for Dedicated needs.", + "com.affine.payment.cloud.team.benefit.g2-3": "Embed-able & Integrations with IT support.", + "com.affine.payment.ai.pricing-plan.title-caption-1": "Turn all your ideas into reality", + "com.affine.payment.ai.pricing-plan.title-caption-2": "A true multimodal AI copilot.", + "com.affine.payment.ai.pricing-plan.title": "AFFiNE AI", + "com.affine.payment.ai.pricing-plan.caption-purchased": "You have purchased AFFiNE AI", + "com.affine.payment.ai.pricing-plan.caption-free": "You are current on the Basic plan.", + "com.affine.payment.ai.pricing-plan.learn": "Learn About AFFiNE AI", + "com.affine.payment.ai.billing-tip.next-bill-at": "You have purchased AFFiNE AI. The next payment date is {{due}}.", + "com.affine.payment.ai.billing-tip.end-at": "You have purchased AFFiNE AI. The expiration date is {{end}}.", + "com.affine.payment.ai.action.cancel.confirm.title": "Cancel Subscription", + "com.affine.payment.ai.action.cancel.confirm.description": "If you end your subscription now, you can still use AFFiNE AI until the end of this billing period.", + "com.affine.payment.ai.action.cancel.confirm.confirm-text": "Cancel Subscription", + "com.affine.payment.ai.action.cancel.confirm.cancel-text": "Keep AFFiNE AI", + "com.affine.payment.ai.action.cancel.button-label": "Cancel Subscription", + "com.affine.payment.ai.action.login.button-label": "Login", + "com.affine.payment.ai.action.resume.confirm.title": "Resume Auto-Renewal?", + "com.affine.payment.ai.action.resume.confirm.description": "Are you sure you want to resume the subscription for AFFiNE AI? This means your payment method will be charged automatically at the end of each billing cycle, starting from the next billing cycle.", + "com.affine.payment.ai.action.resume.confirm.confirm-text": "Confirm", + "com.affine.payment.ai.action.resume.confirm.cancel-text": "Cancel", + "com.affine.payment.ai.action.resume.confirm.notify.title": "Subscription Updated", + "com.affine.payment.ai.action.resume.confirm.notify.msg": "You will be charged in the next billing cycle.", + "com.affine.payment.ai.action.resume.button-label": "Resume", + "com.affine.payment.ai.benefit.g1": "Write with you", + "com.affine.payment.ai.benefit.g1-1": "Create quality content from sentences to articles on topics you need", + "com.affine.payment.ai.benefit.g1-2": "Rewrite like the professionals", + "com.affine.payment.ai.benefit.g1-3": "Change the tones / fix spelling & grammar", + "com.affine.payment.ai.benefit.g2": "Draw with you", + "com.affine.payment.ai.benefit.g2-1": "Visualize your mind, magically", + "com.affine.payment.ai.benefit.g2-2": "Turn your outline into beautiful, engaging presentations", + "com.affine.payment.ai.benefit.g2-3": "Summarize your content into structured mind-map", + "com.affine.payment.ai.benefit.g3": "Plan with you", + "com.affine.payment.ai.benefit.g3-1": "Memorize and tidy up your knowledge", + "com.affine.payment.ai.benefit.g3-2": "Auto-sorting and auto-tagging", + "com.affine.payment.ai.benefit.g3-3": "Open source & Privacy ensured", "com.affine.payment.benefit-1": "Unlimited local workspaces", "com.affine.payment.benefit-2": "Unlimited login devices", "com.affine.payment.benefit-3": "Unlimited blocks",