🌐 Add pt_BR and more translations (#767)

Original PR: https://github.com/baptisteArno/typebot.io/pull/694

---------

Co-authored-by: Daniel Oliveira <daniel.oliveira@kununu.com>
Co-authored-by: Daniel Oliveira <daniel@headdev.com.br>
This commit is contained in:
Baptiste Arnaud
2023-09-05 18:15:59 +02:00
committed by GitHub
parent e4ece315ed
commit aaa208cef4
34 changed files with 1153 additions and 189 deletions

View File

@@ -14,6 +14,7 @@ import { Plan } from '@typebot.io/prisma'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { BlockLabel } from './BlockLabel'
import { LockTag } from '@/features/billing/components/LockTag'
import { useScopedI18n } from '@/locales'
type Props = {
type: DraggableBlockType
@@ -26,6 +27,7 @@ type Props = {
export const BlockCard = (
props: Pick<Props, 'type' | 'onMouseDown'>
): JSX.Element => {
const scopedT = useScopedI18n('editor.blockCard')
const { workspace } = useWorkspace()
switch (props.type) {
@@ -33,7 +35,7 @@ export const BlockCard = (
return (
<BlockCardLayout
{...props}
tooltip="Embed a pdf, an iframe, a website..."
tooltip={scopedT('bubbleBlock.tooltip.label')}
>
<BlockIcon type={props.type} />
<BlockLabel type={props.type} />
@@ -41,7 +43,10 @@ export const BlockCard = (
)
case InputBlockType.FILE:
return (
<BlockCardLayout {...props} tooltip="Upload Files">
<BlockCardLayout
{...props}
tooltip={scopedT('inputBlock.tooltip.files.label')}
>
<BlockIcon type={props.type} />
<HStack>
<BlockLabel type={props.type} />
@@ -51,14 +56,20 @@ export const BlockCard = (
)
case LogicBlockType.SCRIPT:
return (
<BlockCardLayout {...props} tooltip="Execute Javascript code">
<BlockCardLayout
{...props}
tooltip={scopedT('logicBlock.tooltip.code.label')}
>
<BlockIcon type={props.type} />
<BlockLabel type={props.type} />
</BlockCardLayout>
)
case LogicBlockType.TYPEBOT_LINK:
return (
<BlockCardLayout {...props} tooltip="Link and jump to another typebot">
<BlockCardLayout
{...props}
tooltip={scopedT('logicBlock.tooltip.typebotLink.label')}
>
<BlockIcon type={props.type} />
<BlockLabel type={props.type} />
</BlockCardLayout>
@@ -67,7 +78,7 @@ export const BlockCard = (
return (
<BlockCardLayout
{...props}
tooltip="Fast forward the flow to another group"
tooltip={scopedT('logicBlock.tooltip.jump.label')}
>
<BlockIcon type={props.type} />
<BlockLabel type={props.type} />
@@ -75,14 +86,20 @@ export const BlockCard = (
)
case IntegrationBlockType.GOOGLE_SHEETS:
return (
<BlockCardLayout {...props} tooltip="Google Sheets">
<BlockCardLayout
{...props}
tooltip={scopedT('integrationBlock.tooltip.googleSheets.label')}
>
<BlockIcon type={props.type} />
<BlockLabel type={props.type} />
</BlockCardLayout>
)
case IntegrationBlockType.GOOGLE_ANALYTICS:
return (
<BlockCardLayout {...props} tooltip="Google Analytics">
<BlockCardLayout
{...props}
tooltip={scopedT('integrationBlock.tooltip.googleAnalytics.label')}
>
<BlockIcon type={props.type} />
<BlockLabel type={props.type} />
</BlockCardLayout>

View File

@@ -7,79 +7,82 @@ import {
BlockType,
} from '@typebot.io/schemas'
import React from 'react'
import { useScopedI18n } from '@/locales'
type Props = { type: BlockType }
export const BlockLabel = ({ type }: Props): JSX.Element => {
const scopedT = useScopedI18n('editor.sidebarBlock')
switch (type) {
case 'start':
return <Text fontSize="sm">Start</Text>
return <Text fontSize="sm">{scopedT('start.label')}</Text>
case BubbleBlockType.TEXT:
case InputBlockType.TEXT:
return <Text fontSize="sm">Text</Text>
return <Text fontSize="sm">{scopedT('text.label')}</Text>
case BubbleBlockType.IMAGE:
return <Text fontSize="sm">Image</Text>
return <Text fontSize="sm">{scopedT('image.label')}</Text>
case BubbleBlockType.VIDEO:
return <Text fontSize="sm">Video</Text>
return <Text fontSize="sm">{scopedT('video.label')}</Text>
case BubbleBlockType.EMBED:
return <Text fontSize="sm">Embed</Text>
return <Text fontSize="sm">{scopedT('embed.label')}</Text>
case BubbleBlockType.AUDIO:
return <Text fontSize="sm">Audio</Text>
return <Text fontSize="sm">{scopedT('audio.label')}</Text>
case InputBlockType.NUMBER:
return <Text fontSize="sm">Number</Text>
return <Text fontSize="sm">{scopedT('number.label')}</Text>
case InputBlockType.EMAIL:
return <Text fontSize="sm">Email</Text>
return <Text fontSize="sm">{scopedT('email.label')}</Text>
case InputBlockType.URL:
return <Text fontSize="sm">Website</Text>
return <Text fontSize="sm">{scopedT('website.label')}</Text>
case InputBlockType.DATE:
return <Text fontSize="sm">Date</Text>
return <Text fontSize="sm">{scopedT('date.label')}</Text>
case InputBlockType.PHONE:
return <Text fontSize="sm">Phone</Text>
return <Text fontSize="sm">{scopedT('phone.label')}</Text>
case InputBlockType.CHOICE:
return <Text fontSize="sm">Button</Text>
return <Text fontSize="sm">{scopedT('button.label')}</Text>
case InputBlockType.PICTURE_CHOICE:
return <Text fontSize="sm">Pic choice</Text>
return <Text fontSize="sm">{scopedT('picChoice.label')}</Text>
case InputBlockType.PAYMENT:
return <Text fontSize="sm">Payment</Text>
return <Text fontSize="sm">{scopedT('payment.label')}</Text>
case InputBlockType.RATING:
return <Text fontSize="sm">Rating</Text>
return <Text fontSize="sm">{scopedT('rating.label')}</Text>
case InputBlockType.FILE:
return <Text fontSize="sm">File</Text>
return <Text fontSize="sm">{scopedT('file.label')}</Text>
case LogicBlockType.SET_VARIABLE:
return <Text fontSize="sm">Set variable</Text>
return <Text fontSize="sm">{scopedT('setVariable.label')}</Text>
case LogicBlockType.CONDITION:
return <Text fontSize="sm">Condition</Text>
return <Text fontSize="sm">{scopedT('condition.label')}</Text>
case LogicBlockType.REDIRECT:
return <Text fontSize="sm">Redirect</Text>
return <Text fontSize="sm">{scopedT('redirect.label')}</Text>
case LogicBlockType.SCRIPT:
return <Text fontSize="sm">Script</Text>
return <Text fontSize="sm">{scopedT('script.label')}</Text>
case LogicBlockType.TYPEBOT_LINK:
return <Text fontSize="sm">Typebot</Text>
return <Text fontSize="sm">{scopedT('typebot.label')}</Text>
case LogicBlockType.WAIT:
return <Text fontSize="sm">Wait</Text>
return <Text fontSize="sm">{scopedT('wait.label')}</Text>
case LogicBlockType.JUMP:
return <Text fontSize="sm">Jump</Text>
return <Text fontSize="sm">{scopedT('jump.label')}</Text>
case LogicBlockType.AB_TEST:
return <Text fontSize="sm">AB Test</Text>
return <Text fontSize="sm">{scopedT('abTest.label')}</Text>
case IntegrationBlockType.GOOGLE_SHEETS:
return <Text fontSize="sm">Sheets</Text>
return <Text fontSize="sm">{scopedT('sheets.label')}</Text>
case IntegrationBlockType.GOOGLE_ANALYTICS:
return <Text fontSize="sm">Analytics</Text>
return <Text fontSize="sm">{scopedT('analytics.label')}</Text>
case IntegrationBlockType.WEBHOOK:
return <Text fontSize="sm">Webhook</Text>
return <Text fontSize="sm">{scopedT('webhook.label')}</Text>
case IntegrationBlockType.ZAPIER:
return <Text fontSize="sm">Zapier</Text>
return <Text fontSize="sm">{scopedT('zapier.label')}</Text>
case IntegrationBlockType.MAKE_COM:
return <Text fontSize="sm">Make.com</Text>
return <Text fontSize="sm">{scopedT('makecom.label')}</Text>
case IntegrationBlockType.PABBLY_CONNECT:
return <Text fontSize="sm">Pabbly</Text>
return <Text fontSize="sm">{scopedT('pabbly.label')}</Text>
case IntegrationBlockType.EMAIL:
return <Text fontSize="sm">Email</Text>
return <Text fontSize="sm">{scopedT('email.label')}</Text>
case IntegrationBlockType.CHATWOOT:
return <Text fontSize="sm">Chatwoot</Text>
return <Text fontSize="sm">{scopedT('chatwoot.label')}</Text>
case IntegrationBlockType.OPEN_AI:
return <Text fontSize="sm">OpenAI</Text>
return <Text fontSize="sm">{scopedT('openai.label')}</Text>
case IntegrationBlockType.PIXEL:
return <Text fontSize="sm">Pixel</Text>
return <Text fontSize="sm">{scopedT('pixel.label')}</Text>
}
}

View File

@@ -23,8 +23,10 @@ import { BlockCard } from './BlockCard'
import { LockedIcon, UnlockedIcon } from '@/components/icons'
import { BlockCardOverlay } from './BlockCardOverlay'
import { headerHeight } from '../constants'
import { useScopedI18n } from '@/locales'
export const BlocksSideBar = () => {
const scopedT = useScopedI18n('editor.sidebarBlocks')
const { setDraggedBlockType, draggedBlockType } = useBlockDnd()
const [position, setPosition] = useState({
x: 0,
@@ -102,10 +104,20 @@ export const BlocksSideBar = () => {
className="hide-scrollbar"
>
<Flex justifyContent="flex-end">
<Tooltip label={isLocked ? 'Unlock sidebar' : 'Lock sidebar'}>
<Tooltip
label={
isLocked
? scopedT('sidebar.unlock.label')
: scopedT('sidebar.lock.label')
}
>
<IconButton
icon={isLocked ? <LockedIcon /> : <UnlockedIcon />}
aria-label={isLocked ? 'Unlock' : 'Lock'}
aria-label={
isLocked
? scopedT('sidebar.icon.unlock.label')
: scopedT('sidebar.icon.lock.label')
}
size="sm"
onClick={handleLockClick}
/>
@@ -114,7 +126,7 @@ export const BlocksSideBar = () => {
<Stack>
<Text fontSize="sm" fontWeight="semibold">
Bubbles
{scopedT('blockType.bubbles.heading')}
</Text>
<SimpleGrid columns={2} spacing="3">
{Object.values(BubbleBlockType).map((type) => (
@@ -125,7 +137,7 @@ export const BlocksSideBar = () => {
<Stack>
<Text fontSize="sm" fontWeight="semibold">
Inputs
{scopedT('blockType.inputs.heading')}
</Text>
<SimpleGrid columns={2} spacing="3">
{Object.values(InputBlockType).map((type) => (
@@ -136,7 +148,7 @@ export const BlocksSideBar = () => {
<Stack>
<Text fontSize="sm" fontWeight="semibold">
Logic
{scopedT('blockType.logic.heading')}
</Text>
<SimpleGrid columns={2} spacing="3">
{Object.values(LogicBlockType).map((type) => (
@@ -147,7 +159,7 @@ export const BlocksSideBar = () => {
<Stack>
<Text fontSize="sm" fontWeight="semibold">
Integrations
{scopedT('blockType.integrations.heading')}
</Text>
<SimpleGrid columns={2} spacing="3">
{Object.values(IntegrationBlockType).map((type) => (

View File

@@ -6,6 +6,7 @@ import {
useColorModeValue,
} from '@chakra-ui/react'
import React, { useState } from 'react'
import { useScopedI18n } from '@/locales'
type EditableProps = {
defaultName: string
@@ -15,6 +16,7 @@ export const EditableTypebotName = ({
defaultName,
onNewName,
}: EditableProps) => {
const scopedT = useScopedI18n('editor.editableTypebotName')
const emptyNameBg = useColorModeValue('gray.100', 'gray.700')
const [currentName, setCurrentName] = useState(defaultName)
@@ -25,7 +27,7 @@ export const EditableTypebotName = ({
}
return (
<Tooltip label="Rename">
<Tooltip label={scopedT('tooltip.rename.label')}>
<Editable
value={currentName}
onChange={setCurrentName}

View File

@@ -21,8 +21,10 @@ import {
} from '@chakra-ui/react'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useScopedI18n } from '@/locales'
export const GettingStartedModal = () => {
const scopedT = useScopedI18n('editor.gettingStartedModal')
const { query } = useRouter()
const { isOpen, onOpen, onClose } = useDisclosure()
@@ -38,7 +40,7 @@ export const GettingStartedModal = () => {
<ModalCloseButton />
<ModalBody as={Stack} spacing="8" py="10">
<Stack spacing={4}>
<Heading fontSize="xl">Editor basics</Heading>
<Heading fontSize="xl">{scopedT('editorBasics.heading')}</Heading>
<List spacing={4}>
<HStack as={ListItem}>
<Flex
@@ -54,10 +56,7 @@ export const GettingStartedModal = () => {
>
1
</Flex>
<Text>
The left side bar contains blocks that you can drag and drop
to the board.
</Text>
<Text>{scopedT('editorBasics.list.one.label')}</Text>
</HStack>
<HStack as={ListItem}>
<Flex
@@ -73,10 +72,7 @@ export const GettingStartedModal = () => {
>
2
</Flex>
<Text>
You can group blocks together by dropping them below or above
each other
</Text>
<Text>{scopedT('editorBasics.list.two.label')}</Text>
</HStack>
<HStack as={ListItem}>
<Flex
@@ -92,7 +88,7 @@ export const GettingStartedModal = () => {
>
3
</Flex>
<Text>Connect the groups together</Text>
<Text>{scopedT('editorBasics.list.three.label')}</Text>
</HStack>
<HStack as={ListItem}>
<Flex
@@ -108,20 +104,16 @@ export const GettingStartedModal = () => {
>
4
</Flex>
<Text>
Preview your bot by clicking the preview button on the top
right
</Text>
<Text>{scopedT('editorBasics.list.four.label')}</Text>
</HStack>
</List>
</Stack>
<Text>
Feel free to use the bottom-right bubble to reach out if you have
any question. I usually answer within the next 24 hours. 😃
</Text>
<Text>{scopedT('editorBasics.list.label')}</Text>
<Stack spacing={4}>
<Heading fontSize="xl">See it in action ({`<`} 5 minutes)</Heading>
<Heading fontSize="xl">
{scopedT('seeAction.label')} ({`<`} {scopedT('seeAction.time')})
</Heading>
<iframe
width="100%"
height="315"
@@ -135,7 +127,7 @@ export const GettingStartedModal = () => {
<AccordionItem>
<AccordionButton>
<Box flex="1" textAlign="left">
Other videos
{scopedT('seeAction.item.label')}
</Box>
<AccordionIcon />
</AccordionButton>

View File

@@ -30,8 +30,10 @@ import { RightPanel, useEditor } from '../providers/EditorProvider'
import { useTypebot } from '../providers/TypebotProvider'
import { SupportBubble } from '@/components/SupportBubble'
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
import { useScopedI18n } from '@/locales'
export const TypebotHeader = () => {
const scopedT = useScopedI18n('editor.headers')
const router = useRouter()
const {
typebot,
@@ -103,7 +105,7 @@ export const TypebotHeader = () => {
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
size="sm"
>
Flow
{scopedT('flowButton.label')}
</Button>
<Button
as={Link}
@@ -112,7 +114,7 @@ export const TypebotHeader = () => {
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
size="sm"
>
Theme
{scopedT('themeButton.label')}
</Button>
<Button
as={Link}
@@ -121,7 +123,7 @@ export const TypebotHeader = () => {
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
size="sm"
>
Settings
{scopedT('settingsButton.label')}
</Button>
<Button
as={Link}
@@ -130,7 +132,7 @@ export const TypebotHeader = () => {
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
size="sm"
>
Share
{scopedT('shareButton.label')}
</Button>
{isDefined(publishedTypebot) && (
<Button
@@ -140,7 +142,7 @@ export const TypebotHeader = () => {
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
size="sm"
>
Results
{scopedT('resultsButton.label')}
</Button>
)}
</HStack>
@@ -219,14 +221,14 @@ export const TypebotHeader = () => {
</Tooltip>
</HStack>
<Button leftIcon={<BuoyIcon />} onClick={handleHelpClick} size="sm">
Help
{scopedT('helpButton.label')}
</Button>
</HStack>
{isSavingLoading && (
<HStack>
<Spinner speed="0.7s" size="sm" color="gray.400" />
<Text fontSize="sm" color="gray.400">
Saving...
{scopedT('savingSpinner.label')}
</Text>
</HStack>
)}
@@ -241,7 +243,7 @@ export const TypebotHeader = () => {
isLoading={isNotDefined(typebot)}
size="sm"
>
Preview
{scopedT('previewButton.label')}
</Button>
)}
<PublishButton size="sm" />

View File

@@ -23,6 +23,7 @@ import { areTypebotsEqual } from '@/features/publish/helpers/areTypebotsEqual'
import { isPublished as isPublishedHelper } from '@/features/publish/helpers/isPublished'
import { convertPublicTypebotToTypebot } from '@/features/publish/helpers/convertPublicTypebotToTypebot'
import { trpc } from '@/lib/trpc'
import { useScopedI18n } from '@/locales'
const autoSaveTimeout = 10000
@@ -79,6 +80,7 @@ export const TypebotProvider = ({
children: ReactNode
typebotId?: string
}) => {
const scopedT = useScopedI18n('editor.provider')
const { push } = useRouter()
const { showToast } = useToast()
@@ -92,12 +94,15 @@ export const TypebotProvider = ({
enabled: isDefined(typebotId),
onError: (error) => {
if (error.data?.httpStatus === 404) {
showToast({ status: 'info', description: "Couldn't find typebot" })
showToast({
status: 'info',
description: scopedT('messages.getTypebotError.description'),
})
push('/typebots')
return
}
showToast({
title: 'Error while fetching typebot. Refresh the page.',
title: scopedT('messages.getTypebotError.title'),
description: error.message,
})
},
@@ -112,7 +117,7 @@ export const TypebotProvider = ({
onError: (error) => {
if (error.data?.httpStatus === 404) return
showToast({
title: 'Error while fetching published typebot',
title: scopedT('messages.publishedTypebotError.title'),
description: error.message,
})
},
@@ -123,7 +128,7 @@ export const TypebotProvider = ({
trpc.typebot.updateTypebot.useMutation({
onError: (error) =>
showToast({
title: 'Error while updating typebot',
title: scopedT('messages.updateTypebotError.title'),
description: error.message,
}),
onSuccess: () => {
@@ -253,7 +258,10 @@ export const TypebotProvider = ({
isPublished,
updateTypebot: updateLocalTypebot,
restorePublishedTypebot,
...groupsActions(setLocalTypebot as SetTypebot),
...groupsActions(
setLocalTypebot as SetTypebot,
scopedT('groups.copy.title')
),
...blocksAction(setLocalTypebot as SetTypebot),
...variablesAction(setLocalTypebot as SetTypebot),
...edgesAction(setLocalTypebot as SetTypebot),

View File

@@ -28,7 +28,10 @@ export type GroupsActions = {
deleteGroup: (groupIndex: number) => void
}
const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
const groupsActions = (
setTypebot: SetTypebot,
groupCopyLabel: string
): GroupsActions => ({
createGroup: ({
id,
block,
@@ -63,11 +66,12 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
produce(typebot, (typebot) => {
const group = typebot.groups[groupIndex]
const id = createId()
const newGroup: Group = {
...group,
title: isEmpty(group.title)
? ''
: `${parseGroupTitle(group.title)} copy`,
: `${parseGroupTitle(group.title)} ${groupCopyLabel}`,
id,
blocks: group.blocks.map((block) => duplicateBlockDraft(id)(block)),
graphCoordinates: {