More translation in FR & PT (#436)

Related to #210
This commit is contained in:
Baptiste Arnaud
2023-04-06 17:31:23 +02:00
committed by GitHub
parent 44975f9742
commit 75d2a95d08
37 changed files with 751 additions and 286 deletions

View File

@@ -193,7 +193,7 @@ test('plan changes should work', async ({ page }) => {
// Go to customer portal
await Promise.all([
page.waitForNavigation(),
page.click('text="Billing Portal"'),
page.click('text="Billing portal"'),
])
await expect(page.locator('text="Add payment method"')).toBeVisible()

View File

@@ -1,5 +1,6 @@
import { useToast } from '@/hooks/useToast'
import { trpc } from '@/lib/trpc'
import { useScopedI18n } from '@/locales'
import { Button, Link } from '@chakra-ui/react'
type Props = {
@@ -7,6 +8,7 @@ type Props = {
}
export const BillingPortalButton = ({ workspaceId }: Props) => {
const scopedT = useScopedI18n('billing')
const { showToast } = useToast()
const { data } = trpc.billing.getBillingPortalUrl.useQuery(
{
@@ -22,7 +24,7 @@ export const BillingPortalButton = ({ workspaceId }: Props) => {
)
return (
<Button as={Link} href={data?.billingPortalUrl} isLoading={!data}>
Billing Portal
{scopedT('billingPortalButton.label')}
</Button>
)
}

View File

@@ -8,8 +8,10 @@ import { TextLink } from '@/components/TextLink'
import { ChangePlanForm } from './ChangePlanForm'
import { UsageProgressBars } from './UsageProgressBars'
import { CurrentSubscriptionSummary } from './CurrentSubscriptionSummary'
import { useScopedI18n } from '@/locales'
export const BillingSettingsLayout = () => {
const scopedT = useScopedI18n('billing')
const { workspace, refreshWorkspace } = useWorkspace()
if (!workspace) return null
@@ -24,10 +26,9 @@ export const BillingSettingsLayout = () => {
<HStack maxW="500px">
<StripeClimateLogo />
<Text fontSize="xs" color="gray.500">
Typebot is contributing 1% of your subscription to remove CO from
the atmosphere.{' '}
{scopedT('contribution.preLink')}{' '}
<TextLink href="https://climate.stripe.com/5VCRAq" isExternal>
More info.
{scopedT('contribution.link')}
</TextLink>
</Text>
</HStack>

View File

@@ -11,6 +11,7 @@ import { ParentModalProvider } from '@/features/graph/providers/ParentModalProvi
import { useUser } from '@/features/account/hooks/useUser'
import { StarterPlanPricingCard } from './StarterPlanPricingCard'
import { ProPlanPricingCard } from './ProPlanPricingCard'
import { useScopedI18n } from '@/locales'
type Props = {
workspace: Pick<Workspace, 'id' | 'stripeId' | 'plan'>
@@ -18,6 +19,7 @@ type Props = {
}
export const ChangePlanForm = ({ workspace, onUpgradeSuccess }: Props) => {
const scopedT = useScopedI18n('billing')
const { user } = useUser()
const { showToast } = useToast()
const [preCheckoutPlan, setPreCheckoutPlan] =
@@ -38,7 +40,7 @@ export const ChangePlanForm = ({ workspace, onUpgradeSuccess }: Props) => {
onUpgradeSuccess()
showToast({
status: 'success',
description: `Workspace ${plan} plan successfully updated 🎉`,
description: scopedT('updateSuccessToast.description', { plan }),
})
},
})
@@ -123,9 +125,9 @@ export const ChangePlanForm = ({ workspace, onUpgradeSuccess }: Props) => {
/>
</HStack>
<Text color="gray.500">
Need custom limits? Specific features?{' '}
{scopedT('customLimit.preLink')}{' '}
<TextLink href={'https://typebot.io/enterprise-lead-form'} isExternal>
Let&apos;s chat!
{scopedT('customLimit.link')}
</TextLink>
</Text>
</Stack>

View File

@@ -1,5 +1,6 @@
import { AlertInfo } from '@/components/AlertInfo'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { useI18n, useScopedI18n } from '@/locales'
import {
Modal,
ModalBody,
@@ -10,11 +11,10 @@ import {
Button,
HStack,
} from '@chakra-ui/react'
import { LimitReached } from '../types'
import { ChangePlanForm } from './ChangePlanForm'
type ChangePlanModalProps = {
type?: LimitReached
type?: string
isOpen: boolean
onClose: () => void
}
@@ -24,6 +24,7 @@ export const ChangePlanModal = ({
isOpen,
type,
}: ChangePlanModalProps) => {
const t = useI18n()
const { workspace, refreshWorkspace } = useWorkspace()
return (
<Modal isOpen={isOpen} onClose={onClose} size="2xl">
@@ -32,7 +33,7 @@ export const ChangePlanModal = ({
<ModalBody as={Stack} spacing="6" pt="10">
{type && (
<AlertInfo>
You need to upgrade your plan in order to {type}
{t('billing.upgradeLimitLabel', { type: type })}
</AlertInfo>
)}
{workspace && (
@@ -46,7 +47,7 @@ export const ChangePlanModal = ({
<ModalFooter>
<HStack>
<Button colorScheme="gray" onClick={onClose}>
Cancel
{t('cancel')}
</Button>
</HStack>
</ModalFooter>

View File

@@ -6,6 +6,7 @@ import { PlanTag } from './PlanTag'
import { BillingPortalButton } from './BillingPortalButton'
import { trpc } from '@/lib/trpc'
import { Workspace } from '@typebot.io/schemas'
import { useScopedI18n } from '@/locales'
type Props = {
workspace: Pick<Workspace, 'id' | 'plan' | 'stripeId'>
@@ -16,6 +17,7 @@ export const CurrentSubscriptionSummary = ({
workspace,
onCancelSuccess,
}: Props) => {
const scopedT = useScopedI18n('billing.currentSubscription')
const { showToast } = useToast()
const { mutate: cancelSubscription, isLoading: isCancelling } =
@@ -34,9 +36,9 @@ export const CurrentSubscriptionSummary = ({
return (
<Stack spacing="4">
<Heading fontSize="3xl">Subscription</Heading>
<Heading fontSize="3xl">{scopedT('heading')}</Heading>
<HStack data-testid="current-subscription">
<Text>Current workspace subscription: </Text>
<Text>{scopedT('subheading')} </Text>
{isCancelling ? (
<Spinner color="gray.500" size="xs" />
) : (
@@ -52,7 +54,7 @@ export const CurrentSubscriptionSummary = ({
cancelSubscription({ workspaceId: workspace.id })
}
>
Cancel my subscription
{scopedT('cancelLink')}
</Link>
)}
</>
@@ -62,10 +64,7 @@ export const CurrentSubscriptionSummary = ({
{isSubscribed && !isCancelling && (
<>
<Stack spacing="4">
<Text fontSize="sm">
Need to change payment method or billing information? Head over to
your billing portal:
</Text>
<Text fontSize="sm">{scopedT('billingPortalDescription')}</Text>
<BillingPortalButton workspaceId={workspace.id} />
</Stack>
</>

View File

@@ -18,12 +18,14 @@ import Link from 'next/link'
import React from 'react'
import { trpc } from '@/lib/trpc'
import { useToast } from '@/hooks/useToast'
import { useScopedI18n } from '@/locales'
type Props = {
workspaceId: string
}
export const InvoicesList = ({ workspaceId }: Props) => {
const scopedT = useScopedI18n('billing.invoices')
const { showToast } = useToast()
const { data, status } = trpc.billing.listInvoices.useQuery(
{
@@ -38,9 +40,9 @@ export const InvoicesList = ({ workspaceId }: Props) => {
return (
<Stack spacing={6}>
<Heading fontSize="3xl">Invoices</Heading>
<Heading fontSize="3xl">{scopedT('heading')}</Heading>
{data?.invoices.length === 0 && status !== 'loading' ? (
<Text>No invoices found for this workspace.</Text>
<Text>{scopedT('empty')}</Text>
) : (
<TableContainer>
<Table>
@@ -48,8 +50,8 @@ export const InvoicesList = ({ workspaceId }: Props) => {
<Tr>
<Th w="0" />
<Th>#</Th>
<Th>Paid at</Th>
<Th>Subtotal</Th>
<Th>{scopedT('paidAt')}</Th>
<Th>{scopedT('subtotal')}</Th>
<Th w="0" />
</Tr>
</Thead>

View File

@@ -1,3 +1,4 @@
import { useI18n, useScopedI18n } from '@/locales'
import { Tag, TagProps, ThemeTypings } from '@chakra-ui/react'
import { Plan } from '@typebot.io/prisma'

View File

@@ -18,6 +18,7 @@ import { useRouter } from 'next/router'
import React, { FormEvent, useState } from 'react'
import { isDefined } from '@typebot.io/lib'
import { taxIdTypes } from '../taxIdTypes'
import { useScopedI18n } from '@/locales'
export type PreCheckoutModalProps = {
selectedSubscription:
@@ -48,6 +49,7 @@ export const PreCheckoutModal = ({
existingEmail,
onClose,
}: PreCheckoutModalProps) => {
const scopedT = useScopedI18n('billing.preCheckoutModal')
const { ref } = useParentModal()
const vatValueInputRef = React.useRef<HTMLInputElement>(null)
const router = useRouter()
@@ -131,7 +133,7 @@ export const PreCheckoutModal = ({
<Stack as="form" spacing="4" onSubmit={goToCheckout}>
<TextInput
isRequired
label="Company name"
label={scopedT('companyInput.label')}
defaultValue={customer.company}
onChange={updateCustomerCompany}
withVariableButton={false}
@@ -140,17 +142,17 @@ export const PreCheckoutModal = ({
<TextInput
isRequired
type="email"
label="Email"
label={scopedT('emailInput.label')}
defaultValue={customer.email}
onChange={updateCustomerEmail}
withVariableButton={false}
debounceTimeout={0}
/>
<FormControl>
<FormLabel>Tax ID</FormLabel>
<FormLabel>{scopedT('taxId.label')}</FormLabel>
<HStack>
<Select
placeholder="ID type"
placeholder={scopedT('taxId.placeholder')}
items={vatCodeLabels}
isPopoverMatchingInputWidth={false}
onSelect={updateVatType}
@@ -171,7 +173,7 @@ export const PreCheckoutModal = ({
colorScheme="blue"
isDisabled={customer.company === '' || customer.email === ''}
>
Go to checkout
{scopedT('submitButton.label')}
</Button>
</Stack>
</ModalBody>

View File

@@ -29,6 +29,7 @@ import {
} from '@typebot.io/lib/pricing'
import { FeaturesList } from './FeaturesList'
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
import { useI18n, useScopedI18n } from '@/locales'
type Props = {
initialChatsLimitIndex?: number
@@ -48,6 +49,8 @@ export const ProPlanPricingCard = ({
isLoading,
onPayClick,
}: Props) => {
const t = useI18n()
const scopedT = useScopedI18n('billing.pricingCard')
const { workspace } = useWorkspace()
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>()
@@ -94,15 +97,15 @@ export const ProPlanPricingCard = ({
)
return ''
if (workspace?.plan === Plan.PRO) {
if (isCurrentPlan) return 'Your current plan'
if (isCurrentPlan) return scopedT('upgradeButton.current')
if (
selectedChatsLimitIndex !== initialChatsLimitIndex ||
selectedStorageLimitIndex !== initialStorageLimitIndex
)
return 'Update'
return t('update')
}
return 'Upgrade'
return t('upgrade')
}
const handlePayClick = async () => {
@@ -139,15 +142,17 @@ export const ProPlanPricingCard = ({
fontWeight="semibold"
style={{ marginTop: 0 }}
>
Most popular
{scopedT('pro.mostPopularLabel')}
</Tag>
</Flex>
<Stack justifyContent="space-between" h="full">
<Stack spacing="4" mt={2}>
<Heading fontSize="2xl">
Upgrade to <chakra.span color="blue.400">Pro</chakra.span>
{scopedT('heading', {
plan: <chakra.span color="blue.400">Pro</chakra.span>,
})}
</Heading>
<Text>For agencies & growing startups.</Text>
<Text>{scopedT('pro.description')}</Text>
</Stack>
<Stack spacing="4">
<Heading>
@@ -159,16 +164,16 @@ export const ProPlanPricingCard = ({
) ?? NaN,
currency
)}
<chakra.span fontSize="md">/ month</chakra.span>
<chakra.span fontSize="md">{scopedT('perMonth')}</chakra.span>
</Heading>
<Text fontWeight="bold">
<Tooltip
label={
<FeaturesList
features={[
'Branding removed',
'File upload input block',
'Create folders',
scopedT('starter.brandingRemoved'),
scopedT('starter.fileUploadBlock'),
scopedT('starter.createFolders'),
]}
spacing="0"
/>
@@ -177,14 +182,14 @@ export const ProPlanPricingCard = ({
placement="top"
>
<chakra.span textDecoration="underline" cursor="pointer">
Everything in Starter
{scopedT('pro.everythingFromStarter')}
</chakra.span>
</Tooltip>
, plus:
{scopedT('plus')}
</Text>
<FeaturesList
features={[
'5 seats included',
scopedT('pro.includedSeats'),
<HStack key="test">
<Text>
<Menu>
@@ -242,12 +247,9 @@ export const ProPlanPricingCard = ({
)}
</MenuList>
</Menu>{' '}
chats/mo
{scopedT('chatsPerMonth')}
</Text>
<MoreInfoTooltip>
A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives.
</MoreInfoTooltip>
<MoreInfoTooltip>{scopedT('chatsTooltip')}</MoreInfoTooltip>
</HStack>,
<HStack key="test">
<Text>
@@ -318,16 +320,14 @@ export const ProPlanPricingCard = ({
)}
</MenuList>
</Menu>{' '}
GB of storage
{scopedT('storageLimit')}
</Text>
<MoreInfoTooltip>
You accumulate storage for every file that your user upload
into your bot. If you delete the result, it will free up the
space.
{scopedT('storageLimitTooltip')}
</MoreInfoTooltip>
</HStack>,
'Custom domains',
'In-depth analytics',
scopedT('pro.customDomains'),
scopedT('pro.analytics'),
]}
/>
<Button

View File

@@ -25,6 +25,7 @@ import {
} from '@typebot.io/lib/pricing'
import { FeaturesList } from './FeaturesList'
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
import { useI18n, useScopedI18n } from '@/locales'
type Props = {
initialChatsLimitIndex?: number
@@ -44,6 +45,8 @@ export const StarterPlanPricingCard = ({
currency,
onPayClick,
}: Props) => {
const t = useI18n()
const scopedT = useScopedI18n('billing.pricingCard')
const { workspace } = useWorkspace()
const [selectedChatsLimitIndex, setSelectedChatsLimitIndex] =
useState<number>()
@@ -89,17 +92,17 @@ export const StarterPlanPricingCard = ({
selectedStorageLimitIndex === undefined
)
return ''
if (workspace?.plan === Plan.PRO) return 'Downgrade'
if (workspace?.plan === Plan.PRO) return t('downgrade')
if (workspace?.plan === Plan.STARTER) {
if (isCurrentPlan) return 'Your current plan'
if (isCurrentPlan) return scopedT('upgradeButton.current')
if (
selectedChatsLimitIndex !== initialChatsLimitIndex ||
selectedStorageLimitIndex !== initialStorageLimitIndex
)
return 'Update'
return t('update')
}
return 'Upgrade'
return t('upgrade')
}
const handlePayClick = async () => {
@@ -118,9 +121,11 @@ export const StarterPlanPricingCard = ({
<Stack spacing={6} p="6" rounded="lg" borderWidth="1px" flex="1" h="full">
<Stack spacing="4">
<Heading fontSize="2xl">
Upgrade to <chakra.span color="orange.400">Starter</chakra.span>
{scopedT('heading', {
plan: <chakra.span color="orange.400">Starter</chakra.span>,
})}
</Heading>
<Text>For individuals & small businesses.</Text>
<Text>{scopedT('starter.description')}</Text>
<Heading>
{formatPrice(
computePrice(
@@ -130,11 +135,11 @@ export const StarterPlanPricingCard = ({
) ?? NaN,
currency
)}
<chakra.span fontSize="md">/ month</chakra.span>
<chakra.span fontSize="md">{scopedT('perMonth')}</chakra.span>
</Heading>
<FeaturesList
features={[
'2 seats included',
scopedT('starter.includedSeats'),
<HStack key="test">
<Text>
<Menu>
@@ -194,12 +199,9 @@ export const StarterPlanPricingCard = ({
)}
</MenuList>
</Menu>{' '}
chats/mo
{scopedT('chatsPerMonth')}
</Text>
<MoreInfoTooltip>
A chat is counted whenever a user starts a discussion. It is
independant of the number of messages he sends and receives.
</MoreInfoTooltip>
<MoreInfoTooltip>{scopedT('chatsTooltip')}</MoreInfoTooltip>
</HStack>,
<HStack key="test">
<Text>
@@ -260,16 +262,15 @@ export const StarterPlanPricingCard = ({
)}
</MenuList>
</Menu>{' '}
GB of storage
{scopedT('storageLimit')}
</Text>
<MoreInfoTooltip>
You accumulate storage for every file that your user upload into
your bot. If you delete the result, it will free up the space.
{scopedT('storageLimitTooltip')}
</MoreInfoTooltip>
</HStack>,
'Branding removed',
'File upload input block',
'Create folders',
scopedT('starter.brandingRemoved'),
scopedT('starter.fileUploadBlock'),
scopedT('starter.createFolders'),
]}
/>
</Stack>

View File

@@ -3,11 +3,12 @@ import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import React from 'react'
import { isNotDefined } from '@typebot.io/lib'
import { ChangePlanModal } from './ChangePlanModal'
import { LimitReached } from '../types'
import { useI18n } from '@/locales'
type Props = { limitReachedType?: LimitReached } & ButtonProps
type Props = { limitReachedType?: string } & ButtonProps
export const UpgradeButton = ({ limitReachedType, ...props }: Props) => {
const t = useI18n()
const { isOpen, onOpen, onClose } = useDisclosure()
const { workspace } = useWorkspace()
return (
@@ -17,7 +18,7 @@ export const UpgradeButton = ({ limitReachedType, ...props }: Props) => {
isLoading={isNotDefined(workspace)}
onClick={onOpen}
>
{props.children ?? 'Upgrade'}
{props.children ?? t('upgrade')}
<ChangePlanModal
isOpen={isOpen}
onClose={onClose}

View File

@@ -15,12 +15,14 @@ import { parseNumberWithCommas } from '@typebot.io/lib'
import { getChatsLimit, getStorageLimit } from '@typebot.io/lib/pricing'
import { defaultQueryOptions, trpc } from '@/lib/trpc'
import { storageToReadable } from '../helpers/storageToReadable'
import { useScopedI18n } from '@/locales'
type Props = {
workspace: Workspace
}
export const UsageProgressBars = ({ workspace }: Props) => {
const scopedT = useScopedI18n('billing.usage')
const { data, isLoading } = trpc.billing.getUsage.useQuery(
{
workspaceId: workspace.id,
@@ -44,12 +46,12 @@ export const UsageProgressBars = ({ workspace }: Props) => {
return (
<Stack spacing={6}>
<Heading fontSize="3xl">Usage</Heading>
<Heading fontSize="3xl">{scopedT('heading')}</Heading>
<Stack spacing={3}>
<Flex justifyContent="space-between">
<HStack>
<Heading fontSize="xl" as="h3">
Chats
{scopedT('chats.heading')}
</Heading>
{chatsPercentage >= 80 && (
<Tooltip
@@ -58,12 +60,10 @@ export const UsageProgressBars = ({ workspace }: Props) => {
p="3"
label={
<Text>
Your typebots are popular! You will soon reach your
plan&apos;s chats limit. 🚀
{scopedT('chats.alert.soonReach')}
<br />
<br />
Make sure to <strong>update your plan</strong> to increase
this limit and continue chatting with your users.
{scopedT('chats.alert.updatePlan')}
</Text>
}
>
@@ -73,7 +73,7 @@ export const UsageProgressBars = ({ workspace }: Props) => {
</Tooltip>
)}
<Text fontSize="sm" fontStyle="italic" color="gray.500">
(resets on 1st of every month)
{scopedT('chats.resetInfo')}
</Text>
</HStack>
@@ -108,7 +108,7 @@ export const UsageProgressBars = ({ workspace }: Props) => {
<Flex justifyContent="space-between">
<HStack>
<Heading fontSize="xl" as="h3">
Storage
{scopedT('storage.heading')}
</Heading>
{storagePercentage >= 80 && (
<Tooltip
@@ -117,13 +117,10 @@ export const UsageProgressBars = ({ workspace }: Props) => {
p="3"
label={
<Text>
Your typebots are popular! You will soon reach your
plan&apos;s storage limit. 🚀
{scopedT('storage.alert.soonReach')}
<br />
<br />
Make sure to <strong>update your plan</strong> in order to
continue collecting uploaded files. You can also{' '}
<strong>delete files</strong> to free up space.
{scopedT('storage.alert.updatePlan')}
</Text>
}
>

View File

@@ -1,7 +0,0 @@
export enum LimitReached {
BRAND = 'remove branding',
CUSTOM_DOMAIN = 'add custom domains',
FOLDER = 'create folders',
FILE_INPUT = 'use file input blocks',
ANALYTICS = 'unlock in-depth analytics',
}