Add editor header translation keys (#1110)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Implemented internationalization support across various components
using the `useTranslate` function for dynamic content translation.

- **Enhancements**
- Updated UI elements such as buttons, tooltips, placeholders, and
labels to display translated text, improving accessibility and user
experience for non-English speakers.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Baptiste Arnaud <contact@baptiste-arnaud.fr>
Co-authored-by: Baptiste Arnaud <baptiste.arnaud95@gmail.com>
This commit is contained in:
Gabriel Pavão
2023-12-28 06:50:33 -03:00
committed by GitHub
parent 74f5a17de0
commit d42e4a9ce1
38 changed files with 520 additions and 199 deletions

View File

@@ -28,9 +28,11 @@ import { deleteInvitationQuery } from '../queries/deleteInvitationQuery'
import { updateCollaboratorQuery } from '../queries/updateCollaboratorQuery'
import { deleteCollaboratorQuery } from '../queries/deleteCollaboratorQuery'
import { sendInvitationQuery } from '../queries/sendInvitationQuery'
import { TFnType, useTranslate } from '@tolgee/react'
export const CollaborationList = () => {
const { currentRole, workspace } = useWorkspace()
const { t } = useTranslate()
const { typebot } = useTypebot()
const [invitationType, setInvitationType] = useState<CollaborationType>(
CollaborationType.READ
@@ -50,7 +52,7 @@ export const CollaborationList = () => {
typebotId: typebot?.id,
onError: (e) =>
showToast({
title: "Couldn't fetch collaborators",
title: t('share.button.popover.collaboratorsFetch.error.label'),
description: e.message,
}),
})
@@ -62,7 +64,7 @@ export const CollaborationList = () => {
typebotId: typebot?.id,
onError: (e) =>
showToast({
title: "Couldn't fetch invitations",
title: t('share.button.popover.invitationsFetch.error.label'),
description: e.message,
}),
})
@@ -132,7 +134,10 @@ export const CollaborationList = () => {
mutateCollaborators({ collaborators: collaborators ?? [] })
if (error)
return showToast({ title: error.name, description: error.message })
showToast({ status: 'success', title: 'Invitation sent! 📧' })
showToast({
status: 'success',
title: t('share.button.popover.invitationSent.successToast.label'),
})
setInvitationEmail('')
}
@@ -141,7 +146,7 @@ export const CollaborationList = () => {
<HStack as="form" onSubmit={handleInvitationSubmit} px="4" pb="2">
<Input
size="sm"
placeholder="colleague@company.com"
placeholder={t('share.button.popover.inviteInput.placeholder')}
name="inviteEmail"
value={invitationEmail}
onChange={(e) => setInvitationEmail(e.target.value)}
@@ -163,7 +168,7 @@ export const CollaborationList = () => {
type="submit"
isDisabled={!hasFullAccess}
>
Invite
{t('share.button.popover.inviteButton.label')}
</Button>
</HStack>
{workspace && (
@@ -176,6 +181,7 @@ export const CollaborationList = () => {
</HStack>
<Tag flexShrink={0}>
{convertCollaborationTypeEnumToReadable(
t,
CollaborationType.FULL_ACCESS
)}
</Tag>
@@ -227,6 +233,8 @@ const CollaborationTypeMenuButton = ({
type: CollaborationType
onChange: (type: CollaborationType) => void
}) => {
const { t } = useTranslate()
return (
<Menu placement="bottom-end">
<MenuButton
@@ -235,15 +243,15 @@ const CollaborationTypeMenuButton = ({
as={Button}
rightIcon={<ChevronLeftIcon transform={'rotate(-90deg)'} />}
>
{convertCollaborationTypeEnumToReadable(type)}
{convertCollaborationTypeEnumToReadable(t, type)}
</MenuButton>
<MenuList minW={0}>
<Stack maxH={'35vh'} overflowY="scroll" spacing="0">
<MenuItem onClick={() => onChange(CollaborationType.READ)}>
{convertCollaborationTypeEnumToReadable(CollaborationType.READ)}
{convertCollaborationTypeEnumToReadable(t, CollaborationType.READ)}
</MenuItem>
<MenuItem onClick={() => onChange(CollaborationType.WRITE)}>
{convertCollaborationTypeEnumToReadable(CollaborationType.WRITE)}
{convertCollaborationTypeEnumToReadable(t, CollaborationType.WRITE)}
</MenuItem>
</Stack>
</MenuList>
@@ -252,14 +260,15 @@ const CollaborationTypeMenuButton = ({
}
export const convertCollaborationTypeEnumToReadable = (
t: TFnType,
type: CollaborationType
) => {
switch (type) {
case CollaborationType.READ:
return 'Can view'
return t('collaboration.roles.view.label')
case CollaborationType.WRITE:
return 'Can edit'
return t('collaboration.roles.edit.label')
case CollaborationType.FULL_ACCESS:
return 'Full access'
return t('collaboration.roles.full.label')
}
}

View File

@@ -13,6 +13,7 @@ import {
import { CollaborationType } from '@typebot.io/prisma'
import React from 'react'
import { convertCollaborationTypeEnumToReadable } from './CollaborationList'
import { useTranslate } from '@tolgee/react'
type Props = {
image?: string
@@ -35,6 +36,7 @@ export const CollaboratorItem = ({
onDeleteClick,
onChangeCollaborationType,
}: Props) => {
const { t } = useTranslate()
const hoverBgColor = useColorModeValue('gray.100', 'gray.700')
const handleEditClick = () =>
onChangeCollaborationType(CollaborationType.WRITE)
@@ -48,19 +50,19 @@ export const CollaboratorItem = ({
name={name}
image={image}
isGuest={isGuest}
tag={convertCollaborationTypeEnumToReadable(type)}
tag={convertCollaborationTypeEnumToReadable(t, type)}
/>
</MenuButton>
{isOwner && (
<MenuList shadow="lg">
<MenuItem onClick={handleEditClick}>
{convertCollaborationTypeEnumToReadable(CollaborationType.WRITE)}
{convertCollaborationTypeEnumToReadable(t, CollaborationType.WRITE)}
</MenuItem>
<MenuItem onClick={handleViewClick}>
{convertCollaborationTypeEnumToReadable(CollaborationType.READ)}
{convertCollaborationTypeEnumToReadable(t, CollaborationType.READ)}
</MenuItem>
<MenuItem color="red.500" onClick={onDeleteClick}>
Remove
{t('remove')}
</MenuItem>
</MenuList>
)}
@@ -80,28 +82,32 @@ export const CollaboratorIdentityContent = ({
image?: string
isGuest?: boolean
email: string
}) => (
<HStack justifyContent="space-between" maxW="full" py="2" px="4">
<HStack minW={0} spacing={3}>
<Avatar name={name} src={image} size="sm" />
<Stack spacing={0} minW="0">
{name && (
<Text textAlign="left" fontSize="15px">
{name}
}) => {
const { t } = useTranslate()
return (
<HStack justifyContent="space-between" maxW="full" py="2" px="4">
<HStack minW={0} spacing={3}>
<Avatar name={name} src={image} size="sm" />
<Stack spacing={0} minW="0">
{name && (
<Text textAlign="left" fontSize="15px">
{name}
</Text>
)}
<Text
color="gray.500"
fontSize={name ? '14px' : 'inherit'}
noOfLines={1}
>
{email}
</Text>
)}
<Text
color="gray.500"
fontSize={name ? '14px' : 'inherit'}
noOfLines={1}
>
{email}
</Text>
</Stack>
</Stack>
</HStack>
<HStack flexShrink={0}>
{isGuest && <Tag color="gray.400">{t('pending')}</Tag>}
<Tag>{tag}</Tag>
</HStack>
</HStack>
<HStack flexShrink={0}>
{isGuest && <Tag color="gray.400">Pending</Tag>}
<Tag>{tag}</Tag>
</HStack>
</HStack>
)
)
}

View File

@@ -22,6 +22,7 @@ import { useRouter } from 'next/router'
import React, { useEffect, useState } from 'react'
import { EditorSettingsModal } from './EditorSettingsModal'
import { parseDefaultPublicId } from '@/features/publish/helpers/parseDefaultPublicId'
import { useTranslate } from '@tolgee/react'
export const BoardMenuButton = (props: FlexProps) => {
const { query } = useRouter()
@@ -29,6 +30,7 @@ export const BoardMenuButton = (props: FlexProps) => {
const { user } = useUser()
const [isDownloading, setIsDownloading] = useState(false)
const { isOpen, onOpen, onClose } = useDisclosure()
const { t } = useTranslate()
useEffect(() => {
if (user && !user.graphNavigation && !query.isFirstBot) onOpen()
@@ -71,13 +73,13 @@ export const BoardMenuButton = (props: FlexProps) => {
/>
<MenuList>
<MenuItem icon={<BookIcon />} onClick={redirectToDocumentation}>
Documentation
{t('editor.graph.menu.documentationItem.label')}
</MenuItem>
<MenuItem icon={<SettingsIcon />} onClick={onOpen}>
Editor settings
{t('editor.graph.menu.editorSettingsItem.label')}
</MenuItem>
<MenuItem icon={<DownloadIcon />} onClick={downloadFlow}>
Export flow
{t('editor.graph.menu.exportFlowItem.label')}
</MenuItem>
</MenuList>
<EditorSettingsModal isOpen={isOpen} onClose={onClose} />

View File

@@ -116,7 +116,7 @@ export const TypebotHeader = () => {
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.flowButton.label')}
{t('editor.header.flowButton.label')}
</Button>
<Button
as={Link}
@@ -125,7 +125,7 @@ export const TypebotHeader = () => {
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.themeButton.label')}
{t('editor.header.themeButton.label')}
</Button>
<Button
as={Link}
@@ -134,7 +134,7 @@ export const TypebotHeader = () => {
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.settingsButton.label')}
{t('editor.header.settingsButton.label')}
</Button>
<Button
as={Link}
@@ -143,7 +143,7 @@ export const TypebotHeader = () => {
variant={router.pathname.endsWith('share') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.shareButton.label')}
{t('share.button.label')}
</Button>
{isDefined(publishedTypebot) && (
<Button
@@ -153,7 +153,7 @@ export const TypebotHeader = () => {
variant={router.pathname.includes('results') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.resultsButton.label')}
{t('editor.header.resultsButton.label')}
</Button>
)}
</HStack>
@@ -211,7 +211,11 @@ export const TypebotHeader = () => {
{currentUserMode === 'write' && (
<HStack>
<Tooltip
label={isUndoShortcutTooltipOpen ? 'Changes reverted!' : 'Undo'}
label={
isUndoShortcutTooltipOpen
? t('editor.header.undo.tooltip.label')
: t('editor.header.undoButton.label')
}
isOpen={isUndoShortcutTooltipOpen ? true : undefined}
hasArrow={isUndoShortcutTooltipOpen}
>
@@ -219,18 +223,18 @@ export const TypebotHeader = () => {
display={['none', 'flex']}
icon={<UndoIcon />}
size="sm"
aria-label="Undo"
aria-label={t('editor.header.undoButton.label')}
onClick={undo}
isDisabled={!canUndo}
/>
</Tooltip>
<Tooltip label="Redo">
<Tooltip label={t('editor.header.redoButton.label')}>
<IconButton
display={['none', 'flex']}
icon={<RedoIcon />}
size="sm"
aria-label="Redo"
aria-label={t('editor.header.redoButton.label')}
onClick={redo}
isDisabled={!canRedo}
/>
@@ -238,14 +242,14 @@ export const TypebotHeader = () => {
</HStack>
)}
<Button leftIcon={<BuoyIcon />} onClick={handleHelpClick} size="sm">
{t('editor.headers.helpButton.label')}
{t('editor.header.helpButton.label')}
</Button>
</HStack>
{isSavingLoading && (
<HStack>
<Spinner speed="0.7s" size="sm" color="gray.400" />
<Text fontSize="sm" color="gray.400">
{t('editor.headers.savingSpinner.label')}
{t('editor.header.savingSpinner.label')}
</Text>
</HStack>
)}
@@ -263,7 +267,7 @@ export const TypebotHeader = () => {
leftIcon={<PlayIcon />}
size="sm"
>
{t('editor.headers.previewButton.label')}
{t('editor.header.previewButton.label')}
</Button>
)}
{currentUserMode === 'write' && <PublishButton size="sm" />}

View File

@@ -62,7 +62,7 @@ export const GuestTypebotHeader = () => {
variant={router.pathname.includes('/edit') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.flowButton.label')}
{t('editor.header.flowButton.label')}
</Button>
<Button
as={Link}
@@ -71,7 +71,7 @@ export const GuestTypebotHeader = () => {
variant={router.pathname.endsWith('theme') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.themeButton.label')}
{t('editor.header.themeButton.label')}
</Button>
<Button
as={Link}
@@ -80,7 +80,7 @@ export const GuestTypebotHeader = () => {
variant={router.pathname.endsWith('settings') ? 'outline' : 'ghost'}
size="sm"
>
{t('editor.headers.settingsButton.label')}
{t('editor.header.settingsButton.label')}
</Button>
</HStack>
<HStack

View File

@@ -1,11 +1,14 @@
import { FlagIcon } from '@/components/icons'
import { HStack, Text } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
export const StartEventNode = () => {
const { t } = useTranslate()
return (
<HStack spacing={3}>
<FlagIcon />
<Text>Start</Text>
<Text>{t('editor.blocks.start.text')}</Text>
</HStack>
)
}

View File

@@ -17,6 +17,7 @@ import { runtimes } from '../data'
import { PreviewDrawerBody } from './PreviewDrawerBody'
import { useDrag } from '@use-gesture/react'
import { ResizeHandle } from './ResizeHandle'
import { useTranslate } from '@tolgee/react'
const preferredRuntimeKey = 'preferredRuntime'
@@ -30,6 +31,7 @@ const getDefaultRuntime = (typebotId?: string) => {
export const PreviewDrawer = () => {
const { typebot, save, isSavingLoading } = useTypebot()
const { t } = useTranslate()
const { setRightPanel } = useEditor()
const { setPreviewingBlock } = useGraph()
const [width, setWidth] = useState(500)
@@ -103,7 +105,7 @@ export const PreviewDrawer = () => {
isLoading={isSavingLoading}
variant="ghost"
>
Restart
{t('preview.restartButton.label')}
</Button>
) : null}
</HStack>

View File

@@ -73,7 +73,7 @@ export const PublishButton = ({
trpc.typebot.publishTypebot.useMutation({
onError: (error) => {
showToast({
title: 'Error while publishing typebot',
title: t('publish.error.label'),
description: error.message,
})
if (error.data?.httpStatus === 403) logOut()
@@ -91,7 +91,7 @@ export const PublishButton = ({
trpc.typebot.unpublishTypebot.useMutation({
onError: (error) =>
showToast({
title: 'Error while unpublishing typebot',
title: t('editor.header.unpublishTypebot.error.label'),
description: error.message,
}),
onSuccess: () => {
@@ -149,41 +149,40 @@ export const PublishButton = ({
onConfirm={handlePublishClick}
onClose={onNewEngineWarningClose}
confirmButtonColor="blue"
title="⚠️ New engine version"
title={t('publish.versionWarning.title.label')}
message={
<Stack spacing="3">
<Text>
You are about to a deploy a version of your bot with an updated
engine (Typebot V6).
{t('publish.versionWarning.message.aboutToDeploy.label')}
</Text>
<Text fontWeight="bold">
Make sure to check out all the{' '}
{t('publish.versionWarning.message.check.label')}{' '}
<TextLink
href="https://docs.typebot.io/breaking-changes#typebot-v6"
isExternal
>
associated breaking changes
{t('publish.versionWarning.message.breakingChanges.label')}
</TextLink>
</Text>
<Text>
{' '}
Then test, the bot thoroughly in preview mode before publishing.
{t('publish.versionWarning.message.testInPreviewMode.label')}
</Text>
</Stack>
}
confirmButtonLabel={'Publish'}
confirmButtonLabel={t('publishButton.label')}
/>
)}
<Tooltip
placement="bottom-end"
label={
<Stack>
<Text>There are non published changes.</Text>
<Text>{t('publishButton.tooltip.nonPublishedChanges.label')}</Text>
<Text fontStyle="italic">
Published version from{' '}
{t('publishButton.tooltip.publishedVersion.from.label')}{' '}
{publishedTypebot &&
parseTimeSince(publishedTypebot.updatedAt.toString())}{' '}
ago
{t('publishButton.tooltip.publishedVersion.ago.label')}
</Text>
</Stack>
}
@@ -205,9 +204,9 @@ export const PublishButton = ({
>
{isPublished
? typebot?.isClosed
? 'Closed'
: 'Published'
: 'Publish'}
? t('publishButton.closed.label')
: t('publishButton.published.label')
: t('publishButton.label')}
</Button>
</Tooltip>
@@ -218,27 +217,27 @@ export const PublishButton = ({
colorScheme={'blue'}
borderLeftRadius={0}
icon={<ChevronLeftIcon transform="rotate(-90deg)" />}
aria-label="Show published typebot menu"
aria-label={t('publishButton.dropdown.showMenu.label')}
size="sm"
isDisabled={isPublishing || isSavingLoading}
/>
<MenuList>
{!isPublished && (
<MenuItem onClick={restorePublishedTypebot}>
Restore published version
{t('publishButton.dropdown.restoreVersion.label')}
</MenuItem>
)}
{!typebot?.isClosed ? (
<MenuItem onClick={closeTypebot} icon={<LockedIcon />}>
Close typebot to new responses
{t('publishButton.dropdown.close.label')}
</MenuItem>
) : (
<MenuItem onClick={openTypebot} icon={<UnlockedIcon />}>
Reopen typebot to new responses
{t('publishButton.dropdown.reopen.label')}
</MenuItem>
)}
<MenuItem onClick={unpublishTypebot} icon={<CloudOffIcon />}>
Unpublish typebot
{t('publishButton.dropdown.unpublish.label')}
</MenuItem>
</MenuList>
</Menu>

View File

@@ -6,10 +6,7 @@ import { parseReactBotProps } from '../../snippetParsers'
type Props = { widthLabel?: string; heightLabel: string }
export const NextjsStandardSnippet = ({
widthLabel,
heightLabel,
}: Props) => {
export const NextjsStandardSnippet = ({ widthLabel, heightLabel }: Props) => {
const { typebot } = useTypebot()
const snippet = prettier.format(
`import { Standard } from "@typebot.io/nextjs";

View File

@@ -4,8 +4,10 @@ import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSetting
import { CopyButton } from '@/components/CopyButton'
import { CollaborationList } from '@/features/collaboration/components/CollaborationList'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { useTranslate } from '@tolgee/react'
export const SharePopoverContent = () => {
const { t } = useTranslate()
const { typebot, updateTypebot } = useTypebot()
const currentUrl = `${window.location.origin}/typebots/${typebot?.id}/edit`
@@ -30,7 +32,7 @@ export const SharePopoverContent = () => {
<CollaborationList />
<Stack p="4" borderTopWidth={1}>
<SwitchWithRelatedSettings
label={'Make the flow publicly available'}
label={t('share.button.popover.publicFlow.label')}
initialValue={typebot?.settings.publicShare?.isEnabled ?? false}
onCheckChange={updateIsPublicShareEnabled}
>

View File

@@ -7,18 +7,21 @@ import {
import { UsersIcon } from '@/components/icons'
import React from 'react'
import { SharePopoverContent } from './SharePopoverContent'
import { useTranslate } from '@tolgee/react'
export const ShareTypebotButton = ({ isLoading }: { isLoading: boolean }) => {
const { t } = useTranslate()
return (
<Popover isLazy placement="bottom-end">
<PopoverTrigger>
<Button
isLoading={isLoading}
leftIcon={<UsersIcon />}
aria-label="Open share popover"
aria-label={t('share.button.popover.ariaLabel')}
size="sm"
>
Share
{t('share.button.label')}
</Button>
</PopoverTrigger>
<PopoverContent

View File

@@ -15,12 +15,14 @@ import React, { useRef } from 'react'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { useOutsideClick } from '@/hooks/useOutsideClick'
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
import { useTranslate } from '@tolgee/react'
type Props = {
onSelectVariable: (variable: Pick<Variable, 'name' | 'id'>) => void
} & Omit<IconButtonProps, 'aria-label'>
export const VariablesButton = ({ onSelectVariable, ...props }: Props) => {
const { t } = useTranslate()
const { isOpen, onOpen, onClose } = useDisclosure()
const popoverRef = useRef<HTMLDivElement>(null)
const { ref: parentModalRef } = useParentModal()
@@ -35,9 +37,9 @@ export const VariablesButton = ({ onSelectVariable, ...props }: Props) => {
<Popover isLazy isOpen={isOpen}>
<PopoverAnchor>
<Flex>
<Tooltip label="Insert a variable">
<Tooltip label={t('variables.button.tooltip')}>
<IconButton
aria-label={'Insert a variable'}
aria-label={t('variables.button.tooltip')}
icon={<UserIcon />}
pos="relative"
onClick={onOpen}
@@ -54,7 +56,7 @@ export const VariablesButton = ({ onSelectVariable, ...props }: Props) => {
onClose()
if (variable) onSelectVariable(variable)
}}
placeholder="Search for a variable"
placeholder={t('variables.button.searchInput.placeholder')}
shadow="lg"
autoFocus
/>