2
0

🌐 Add translation keys for input blocks (#1114)

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

- **New Features**
- Integrated localization support across various components using the
`useTranslate` hook for dynamic translations.

- **Enhancements**
- Replaced hardcoded text with localized strings to support multiple
languages in the user interface.

- **User Interface**
- Updated labels, placeholders, tooltips, and button texts to utilize
translated content for a multilingual experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Baptiste Arnaud <baptiste.arnaud95@gmail.com>
This commit is contained in:
Gabriel Pavão
2023-12-29 08:42:50 -03:00
committed by GitHub
parent 5fbbe9d86e
commit 53b702e8b1
37 changed files with 550 additions and 152 deletions

View File

@@ -1,4 +1,5 @@
import { useColorModeValue, HStack, Tag, Text } from '@chakra-ui/react' import { useColorModeValue, HStack, Tag, Text } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { Variable } from '@typebot.io/schemas' import { Variable } from '@typebot.io/schemas'
export const SetVariableLabel = ({ export const SetVariableLabel = ({
@@ -8,6 +9,7 @@ export const SetVariableLabel = ({
variableId: string variableId: string
variables?: Variable[] variables?: Variable[]
}) => { }) => {
const { t } = useTranslate()
const textColor = useColorModeValue('gray.600', 'gray.400') const textColor = useColorModeValue('gray.600', 'gray.400')
const variableName = variables?.find( const variableName = variables?.find(
(variable) => variable.id === variableId (variable) => variable.id === variableId
@@ -17,7 +19,7 @@ export const SetVariableLabel = ({
return ( return (
<HStack fontStyle="italic" spacing={1}> <HStack fontStyle="italic" spacing={1}>
<Text fontSize="sm" color={textColor}> <Text fontSize="sm" color={textColor}>
Set {t('variables.set')}
</Text> </Text>
<Tag bg="orange.400" color="white" size="sm"> <Tag bg="orange.400" color="white" size="sm">
{variableName} {variableName}

View File

@@ -33,6 +33,7 @@ import { byId, isDefined, isNotDefined } from '@typebot.io/lib'
import { useOutsideClick } from '@/hooks/useOutsideClick' import { useOutsideClick } from '@/hooks/useOutsideClick'
import { useParentModal } from '@/features/graph/providers/ParentModalProvider' import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
import { MoreInfoTooltip } from '../MoreInfoTooltip' import { MoreInfoTooltip } from '../MoreInfoTooltip'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
initialVariableId: string | undefined initialVariableId: string | undefined
@@ -78,6 +79,7 @@ export const VariableSearchInput = ({
const createVariableItemRef = useRef<HTMLButtonElement | null>(null) const createVariableItemRef = useRef<HTMLButtonElement | null>(null)
const itemsRef = useRef<(HTMLButtonElement | null)[]>([]) const itemsRef = useRef<(HTMLButtonElement | null)[]>([])
const { ref: parentModalRef } = useParentModal() const { ref: parentModalRef } = useParentModal()
const { t } = useTranslate()
useOutsideClick({ useOutsideClick({
ref: dropdownRef, ref: dropdownRef,
@@ -137,7 +139,7 @@ export const VariableSearchInput = ({
const handleRenameVariableClick = const handleRenameVariableClick =
(variable: Variable) => (e: React.MouseEvent) => { (variable: Variable) => (e: React.MouseEvent) => {
e.stopPropagation() e.stopPropagation()
const name = prompt('Rename variable', variable.name) const name = prompt(t('variables.rename'), variable.name)
if (!name) return if (!name) return
updateVariable(variable.id, { name }) updateVariable(variable.id, { name })
setFilteredItems( setFilteredItems(
@@ -221,7 +223,7 @@ export const VariableSearchInput = ({
onChange={onInputChange} onChange={onInputChange}
onFocus={openDropdown} onFocus={openDropdown}
onKeyDown={handleKeyUp} onKeyDown={handleKeyUp}
placeholder={placeholder ?? 'Select a variable'} placeholder={placeholder ?? t('variables.select')}
autoComplete="off" autoComplete="off"
{...inputProps} {...inputProps}
/> />
@@ -255,7 +257,7 @@ export const VariableSearchInput = ({
: 'transparent' : 'transparent'
} }
> >
Create {t('create')}
<Tag colorScheme="orange" ml="1"> <Tag colorScheme="orange" ml="1">
<Text noOfLines={0} display="block"> <Text noOfLines={0} display="block">
{inputValue} {inputValue}
@@ -296,13 +298,13 @@ export const VariableSearchInput = ({
<HStack> <HStack>
<IconButton <IconButton
icon={<EditIcon />} icon={<EditIcon />}
aria-label="Rename variable" aria-label={t('variables.rename')}
size="xs" size="xs"
onClick={handleRenameVariableClick(item)} onClick={handleRenameVariableClick(item)}
/> />
<IconButton <IconButton
icon={<TrashIcon />} icon={<TrashIcon />}
aria-label="Remove variable" aria-label={t('variables.remove')}
size="xs" size="xs"
onClick={handleDeleteVariableClick(item)} onClick={handleDeleteVariableClick(item)}
/> />

View File

@@ -4,6 +4,7 @@ import { Stack, Tag, Text, Wrap } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider' import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { SetVariableLabel } from '@/components/SetVariableLabel' import { SetVariableLabel } from '@/components/SetVariableLabel'
import { ItemNodesList } from '@/features/graph/components/nodes/item/ItemNodesList' import { ItemNodesList } from '@/features/graph/components/nodes/item/ItemNodesList'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
block: ChoiceInputBlock block: ChoiceInputBlock
@@ -12,6 +13,7 @@ type Props = {
export const ButtonsBlockNode = ({ block, indices }: Props) => { export const ButtonsBlockNode = ({ block, indices }: Props) => {
const { typebot } = useTypebot() const { typebot } = useTypebot()
const { t } = useTranslate()
const dynamicVariableName = typebot?.variables.find( const dynamicVariableName = typebot?.variables.find(
(variable) => variable.id === block.options?.dynamicVariableId (variable) => variable.id === block.options?.dynamicVariableId
)?.name )?.name
@@ -20,11 +22,11 @@ export const ButtonsBlockNode = ({ block, indices }: Props) => {
<Stack w="full"> <Stack w="full">
{block.options?.dynamicVariableId ? ( {block.options?.dynamicVariableId ? (
<Wrap spacing={1}> <Wrap spacing={1}>
<Text>Display</Text> <Text>{t('blocks.inputs.button.variables.display.label')}</Text>
<Tag bg="orange.400" color="white"> <Tag bg="orange.400" color="white">
{dynamicVariableName} {dynamicVariableName}
</Tag> </Tag>
<Text>buttons</Text> <Text>{t('blocks.inputs.button.variables.buttons.label')}</Text>
</Wrap> </Wrap>
) : ( ) : (
<ItemNodesList block={block} indices={indices} /> <ItemNodesList block={block} indices={indices} />

View File

@@ -6,6 +6,7 @@ import { ChoiceInputBlock, Variable } from '@typebot.io/schemas'
import React from 'react' import React from 'react'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings' import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice/constants' import { defaultChoiceInputOptions } from '@typebot.io/schemas/features/blocks/inputs/choice/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
options?: ChoiceInputBlock['options'] options?: ChoiceInputBlock['options']
@@ -13,6 +14,7 @@ type Props = {
} }
export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => { export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const updateIsMultiple = (isMultipleChoice: boolean) => const updateIsMultiple = (isMultipleChoice: boolean) =>
onOptionsChange({ ...options, isMultipleChoice }) onOptionsChange({ ...options, isMultipleChoice })
const updateIsSearchable = (isSearchable: boolean) => const updateIsSearchable = (isSearchable: boolean) =>
@@ -29,7 +31,7 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Multiple choice?" label={t('blocks.inputs.settings.multipleChoice.label')}
initialValue={ initialValue={
options?.isMultipleChoice ?? options?.isMultipleChoice ??
defaultChoiceInputOptions.isMultipleChoice defaultChoiceInputOptions.isMultipleChoice
@@ -37,35 +39,34 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
onCheckChange={updateIsMultiple} onCheckChange={updateIsMultiple}
> >
<TextInput <TextInput
label="Submit button label:" label={t('blocks.inputs.settings.submitButton.label')}
defaultValue={ defaultValue={
options?.buttonLabel ?? defaultChoiceInputOptions.buttonLabel options?.buttonLabel ?? t('blocks.inputs.settings.buttonText.label')
} }
onChange={updateButtonLabel} onChange={updateButtonLabel}
/> />
</SwitchWithRelatedSettings> </SwitchWithRelatedSettings>
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Is searchable?" label={t('blocks.inputs.settings.isSearchable.label')}
initialValue={ initialValue={
options?.isSearchable ?? defaultChoiceInputOptions.isSearchable options?.isSearchable ?? defaultChoiceInputOptions.isSearchable
} }
onCheckChange={updateIsSearchable} onCheckChange={updateIsSearchable}
> >
<TextInput <TextInput
label="Input placeholder:" label={t('blocks.inputs.settings.input.placeholder.label')}
defaultValue={ defaultValue={
options?.searchInputPlaceholder ?? options?.searchInputPlaceholder ??
defaultChoiceInputOptions.searchInputPlaceholder t('blocks.inputs.settings.input.filterOptions.label')
} }
onChange={updateSearchInputPlaceholder} onChange={updateSearchInputPlaceholder}
/> />
</SwitchWithRelatedSettings> </SwitchWithRelatedSettings>
<FormControl> <FormControl>
<FormLabel> <FormLabel>
Dynamic data:{' '} {t('blocks.inputs.button.settings.dynamicData.label')}{' '}
<MoreInfoTooltip> <MoreInfoTooltip>
If defined, buttons will be dynamically displayed based on what the {t('blocks.inputs.button.settings.dynamicData.infoText.label')}
variable contains.
</MoreInfoTooltip> </MoreInfoTooltip>
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
@@ -75,7 +76,7 @@ export const ButtonsBlockSettings = ({ options, onOptionsChange }: Props) => {
</FormControl> </FormControl>
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -21,6 +21,7 @@ import React, { useRef, useState } from 'react'
import { isNotDefined } from '@typebot.io/lib' import { isNotDefined } from '@typebot.io/lib'
import { useGraph } from '@/features/graph/providers/GraphProvider' import { useGraph } from '@/features/graph/providers/GraphProvider'
import { ButtonsItemSettings } from './ButtonsItemSettings' import { ButtonsItemSettings } from './ButtonsItemSettings'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
item: ButtonItem item: ButtonItem
@@ -29,9 +30,12 @@ type Props = {
} }
export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => { export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
const { t } = useTranslate()
const { deleteItem, updateItem, createItem } = useTypebot() const { deleteItem, updateItem, createItem } = useTypebot()
const { openedItemId, setOpenedItemId } = useGraph() const { openedItemId, setOpenedItemId } = useGraph()
const [itemValue, setItemValue] = useState(item.content ?? 'Click to edit') const [itemValue, setItemValue] = useState(
item.content ?? t('blocks.inputs.button.clickToEdit.label')
)
const editableRef = useRef<HTMLDivElement | null>(null) const editableRef = useRef<HTMLDivElement | null>(null)
const ref = useRef<HTMLDivElement | null>(null) const ref = useRef<HTMLDivElement | null>(null)
const arrowColor = useColorModeValue('white', 'gray.800') const arrowColor = useColorModeValue('white', 'gray.800')
@@ -47,8 +51,16 @@ export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
} }
const handleKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => { const handleKeyPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === 'Escape' && itemValue === 'Click to edit') deleteItem(indices) if (
if (e.key === 'Enter' && itemValue !== '' && itemValue !== 'Click to edit') e.key === 'Escape' &&
itemValue === t('blocks.inputs.button.clickToEdit.label')
)
deleteItem(indices)
if (
e.key === 'Enter' &&
itemValue !== '' &&
itemValue !== t('blocks.inputs.button.clickToEdit.label')
)
handlePlusClick() handlePlusClick()
} }
@@ -82,7 +94,11 @@ export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
> >
<EditablePreview <EditablePreview
w="full" w="full"
color={item.content !== 'Click to edit' ? 'inherit' : 'gray.500'} color={
item.content !== t('blocks.inputs.button.clickToEdit.label')
? 'inherit'
: 'gray.500'
}
cursor="pointer" cursor="pointer"
/> />
<EditableInput onMouseDownCapture={(e) => e.stopPropagation()} /> <EditableInput onMouseDownCapture={(e) => e.stopPropagation()} />
@@ -101,7 +117,7 @@ export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
> >
<Flex bgColor={useColorModeValue('white', 'gray.800')} rounded="md"> <Flex bgColor={useColorModeValue('white', 'gray.800')} rounded="md">
<IconButton <IconButton
aria-label="Open settings" aria-label={t('blocks.inputs.button.openSettings.ariaLabel')}
icon={<SettingsIcon />} icon={<SettingsIcon />}
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -121,7 +137,7 @@ export const ButtonsItemNode = ({ item, indices, isMouseOver }: Props) => {
unmountOnExit unmountOnExit
> >
<IconButton <IconButton
aria-label="Add item" aria-label={t('blocks.inputs.button.addItem.ariaLabel')}
icon={<PlusIcon />} icon={<PlusIcon />}
size="xs" size="xs"
shadow="md" shadow="md"

View File

@@ -4,6 +4,7 @@ import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSetting
import { ConditionForm } from '@/features/blocks/logic/condition/components/ConditionForm' import { ConditionForm } from '@/features/blocks/logic/condition/components/ConditionForm'
import { ButtonItem, Condition } from '@typebot.io/schemas' import { ButtonItem, Condition } from '@typebot.io/schemas'
import { LogicalOperator } from '@typebot.io/schemas/features/blocks/logic/condition/constants' import { LogicalOperator } from '@typebot.io/schemas/features/blocks/logic/condition/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
item: ButtonItem item: ButtonItem
@@ -11,6 +12,8 @@ type Props = {
} }
export const ButtonsItemSettings = ({ item, onSettingsChange }: Props) => { export const ButtonsItemSettings = ({ item, onSettingsChange }: Props) => {
const { t } = useTranslate()
const updateIsDisplayConditionEnabled = (isEnabled: boolean) => const updateIsDisplayConditionEnabled = (isEnabled: boolean) =>
onSettingsChange({ onSettingsChange({
...item, ...item,
@@ -32,8 +35,10 @@ export const ButtonsItemSettings = ({ item, onSettingsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Display condition" label={t('blocks.inputs.settings.displayCondition.label')}
moreInfoContent="Only display this item if a condition is met." moreInfoContent={t(
'blocks.inputs.button.buttonSettings.displayCondition.infoText.label'
)}
initialValue={item.displayCondition?.isEnabled ?? false} initialValue={item.displayCondition?.isEnabled ?? false}
onCheckChange={updateIsDisplayConditionEnabled} onCheckChange={updateIsDisplayConditionEnabled}
> >

View File

@@ -6,6 +6,7 @@ import { FormLabel, Stack } from '@chakra-ui/react'
import { DateInputBlock, Variable } from '@typebot.io/schemas' import { DateInputBlock, Variable } from '@typebot.io/schemas'
import React from 'react' import React from 'react'
import { defaultDateInputOptions } from '@typebot.io/schemas/features/blocks/inputs/date/constants' import { defaultDateInputOptions } from '@typebot.io/schemas/features/blocks/inputs/date/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
options: DateInputBlock['options'] options: DateInputBlock['options']
@@ -13,6 +14,7 @@ type Props = {
} }
export const DateInputSettings = ({ options, onOptionsChange }: Props) => { export const DateInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const updateFromLabel = (from: string) => const updateFromLabel = (from: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, from } }) onOptionsChange({ ...options, labels: { ...options?.labels, from } })
const updateToLabel = (to: string) => const updateToLabel = (to: string) =>
@@ -41,64 +43,69 @@ export const DateInputSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Is range?" label={t('blocks.inputs.date.settings.isRange.label')}
initialValue={options?.isRange ?? defaultDateInputOptions.isRange} initialValue={options?.isRange ?? defaultDateInputOptions.isRange}
onCheckChange={updateIsRange} onCheckChange={updateIsRange}
> >
<TextInput <TextInput
label="From label:" label={t('blocks.inputs.date.settings.from.label')}
defaultValue={ defaultValue={
options?.labels?.from ?? defaultDateInputOptions.labels.from options?.labels?.from ?? defaultDateInputOptions.labels.from
} }
onChange={updateFromLabel} onChange={updateFromLabel}
/> />
<TextInput <TextInput
label="To label:" label={t('blocks.inputs.date.settings.to.label')}
defaultValue={ defaultValue={
options?.labels?.to ?? defaultDateInputOptions.labels.to options?.labels?.to ??
t('blocks.inputs.date.settings.toInputValue.label')
} }
onChange={updateToLabel} onChange={updateToLabel}
/> />
</SwitchWithRelatedSettings> </SwitchWithRelatedSettings>
<SwitchWithLabel <SwitchWithLabel
label="With time?" label={t('blocks.inputs.date.settings.withTime.label')}
initialValue={options?.hasTime ?? defaultDateInputOptions.hasTime} initialValue={options?.hasTime ?? defaultDateInputOptions.hasTime}
onCheckChange={updateHasTime} onCheckChange={updateHasTime}
/> />
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultDateInputOptions.labels.button options?.labels?.button ?? defaultDateInputOptions.labels.button
} }
onChange={updateButtonLabel} onChange={updateButtonLabel}
/> />
<TextInput <TextInput
label="Min:" label={t('blocks.inputs.settings.min.label')}
defaultValue={options?.min} defaultValue={options?.min}
placeholder={options?.hasTime ? 'YYYY-MM-DDTHH:mm' : 'YYYY-MM-DD'} placeholder={options?.hasTime ? 'YYYY-MM-DDTHH:mm' : 'YYYY-MM-DD'}
onChange={updateMin} onChange={updateMin}
/> />
<TextInput <TextInput
label="Max:" label={t('blocks.inputs.settings.max.label')}
defaultValue={options?.max} defaultValue={options?.max}
placeholder={options?.hasTime ? 'YYYY-MM-DDTHH:mm' : 'YYYY-MM-DD'} placeholder={options?.hasTime ? 'YYYY-MM-DDTHH:mm' : 'YYYY-MM-DD'}
onChange={updateMax} onChange={updateMax}
/> />
<TextInput <TextInput
label="Format:" label={t('blocks.inputs.date.settings.format.label')}
defaultValue={ defaultValue={
options?.format ?? options?.format ??
(options?.hasTime (options?.hasTime
? defaultDateInputOptions.formatWithTime ? defaultDateInputOptions.formatWithTime
: defaultDateInputOptions.format) : defaultDateInputOptions.format)
} }
moreInfoTooltip="i.e dd/MM/yyyy, MM/dd/yy, yyyy-MM-dd" moreInfoTooltip={`
${t(
'blocks.inputs.date.settings.format.example.label'
)} dd/MM/yyyy, MM/dd/yy, yyyy-MM-dd
`}
placeholder={options?.hasTime ? 'dd/MM/yyyy HH:mm' : 'dd/MM/yyyy'} placeholder={options?.hasTime ? 'dd/MM/yyyy HH:mm' : 'dd/MM/yyyy'}
onChange={updateFormat} onChange={updateFormat}
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -1,13 +1,17 @@
import React from 'react' import React from 'react'
import { Text } from '@chakra-ui/react' import { Text } from '@chakra-ui/react'
import { WithVariableContent } from '@/features/graph/components/nodes/block/WithVariableContent' import { WithVariableContent } from '@/features/graph/components/nodes/block/WithVariableContent'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
variableId?: string variableId?: string
} }
export const DateNodeContent = ({ variableId }: Props) => export const DateNodeContent = ({ variableId }: Props) => {
variableId ? ( const { t } = useTranslate()
return variableId ? (
<WithVariableContent variableId={variableId} /> <WithVariableContent variableId={variableId} />
) : ( ) : (
<Text color={'gray.500'}>Pick a date...</Text> <Text color={'gray.500'}>{t('blocks.inputs.date.placeholder.label')}</Text>
) )
}

View File

@@ -1,6 +1,7 @@
import { TextInput } from '@/components/inputs' import { TextInput } from '@/components/inputs'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { FormLabel, Stack } from '@chakra-ui/react' import { FormLabel, Stack } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { EmailInputBlock, Variable } from '@typebot.io/schemas' import { EmailInputBlock, Variable } from '@typebot.io/schemas'
import { defaultEmailInputOptions } from '@typebot.io/schemas/features/blocks/inputs/email/constants' import { defaultEmailInputOptions } from '@typebot.io/schemas/features/blocks/inputs/email/constants'
import React from 'react' import React from 'react'
@@ -11,6 +12,7 @@ type Props = {
} }
export const EmailInputSettings = ({ options, onOptionsChange }: Props) => { export const EmailInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handlePlaceholderChange = (placeholder: string) => const handlePlaceholderChange = (placeholder: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } }) onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) => const handleButtonLabelChange = (button: string) =>
@@ -23,7 +25,7 @@ export const EmailInputSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<TextInput <TextInput
label="Placeholder:" label={t('blocks.inputs.settings.placeholder.label')}
defaultValue={ defaultValue={
options?.labels?.placeholder ?? options?.labels?.placeholder ??
defaultEmailInputOptions.labels.placeholder defaultEmailInputOptions.labels.placeholder
@@ -31,14 +33,14 @@ export const EmailInputSettings = ({ options, onOptionsChange }: Props) => {
onChange={handlePlaceholderChange} onChange={handlePlaceholderChange}
/> />
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultEmailInputOptions.labels.button options?.labels?.button ?? defaultEmailInputOptions.labels.button
} }
onChange={handleButtonLabelChange} onChange={handleButtonLabelChange}
/> />
<TextInput <TextInput
label="Retry message:" label={t('blocks.inputs.settings.retryMessage.label')}
defaultValue={ defaultValue={
options?.retryMessageContent ?? options?.retryMessageContent ??
defaultEmailInputOptions.retryMessageContent defaultEmailInputOptions.retryMessageContent
@@ -47,7 +49,7 @@ export const EmailInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -1,16 +1,22 @@
import { WithVariableContent } from '@/features/graph/components/nodes/block/WithVariableContent' import { WithVariableContent } from '@/features/graph/components/nodes/block/WithVariableContent'
import { Text } from '@chakra-ui/react' import { Text } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { FileInputBlock } from '@typebot.io/schemas' import { FileInputBlock } from '@typebot.io/schemas'
type Props = { type Props = {
options: FileInputBlock['options'] options: FileInputBlock['options']
} }
export const FileInputContent = ({ options }: Props) => export const FileInputContent = ({ options }: Props) => {
options?.variableId ? ( const { t } = useTranslate()
return options?.variableId ? (
<WithVariableContent variableId={options.variableId} /> <WithVariableContent variableId={options.variableId} />
) : ( ) : (
<Text noOfLines={1} pr="6"> <Text noOfLines={1} pr="6">
Collect {options?.isMultipleAllowed ? 'files' : 'file'} {options?.isMultipleAllowed
? t('blocks.inputs.file.collectMultiple.label')
: t('blocks.inputs.file.collectSingle.label')}
</Text> </Text>
) )
}

View File

@@ -6,6 +6,7 @@ import { TextInput } from '@/components/inputs'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel' import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { defaultFileInputOptions } from '@typebot.io/schemas/features/blocks/inputs/file/constants' import { defaultFileInputOptions } from '@typebot.io/schemas/features/blocks/inputs/file/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
options: FileInputBlock['options'] options: FileInputBlock['options']
@@ -13,6 +14,8 @@ type Props = {
} }
export const FileInputSettings = ({ options, onOptionsChange }: Props) => { export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handleButtonLabelChange = (button: string) => const handleButtonLabelChange = (button: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, button } }) onOptionsChange({ ...options, labels: { ...options?.labels, button } })
@@ -37,12 +40,12 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<SwitchWithLabel <SwitchWithLabel
label="Required?" label={t('blocks.inputs.file.settings.required.label')}
initialValue={options?.isRequired ?? defaultFileInputOptions.isRequired} initialValue={options?.isRequired ?? defaultFileInputOptions.isRequired}
onCheckChange={handleRequiredChange} onCheckChange={handleRequiredChange}
/> />
<SwitchWithLabel <SwitchWithLabel
label="Allow multiple files?" label={t('blocks.inputs.file.settings.allowMultiple.label')}
initialValue={ initialValue={
options?.isMultipleAllowed ?? options?.isMultipleAllowed ??
defaultFileInputOptions.isMultipleAllowed defaultFileInputOptions.isMultipleAllowed
@@ -50,7 +53,9 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
onCheckChange={handleMultipleFilesChange} onCheckChange={handleMultipleFilesChange}
/> />
<Stack> <Stack>
<FormLabel mb="0">Placeholder:</FormLabel> <FormLabel mb="0">
{t('blocks.inputs.settings.placeholder.label')}
</FormLabel>
<CodeEditor <CodeEditor
lang="html" lang="html"
onChange={handlePlaceholderLabelChange} onChange={handlePlaceholderLabelChange}
@@ -63,7 +68,7 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
</Stack> </Stack>
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultFileInputOptions.labels.button options?.labels?.button ?? defaultFileInputOptions.labels.button
} }
@@ -71,7 +76,7 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
withVariableButton={false} withVariableButton={false}
/> />
<TextInput <TextInput
label="Clear button label:" label={t('blocks.inputs.file.settings.clear.label')}
defaultValue={ defaultValue={
options?.labels?.clear ?? defaultFileInputOptions.labels.clear options?.labels?.clear ?? defaultFileInputOptions.labels.clear
} }
@@ -79,7 +84,7 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
withVariableButton={false} withVariableButton={false}
/> />
<TextInput <TextInput
label="Skip button label:" label={t('blocks.inputs.file.settings.skip.label')}
defaultValue={ defaultValue={
options?.labels?.skip ?? defaultFileInputOptions.labels.skip options?.labels?.skip ?? defaultFileInputOptions.labels.skip
} }
@@ -88,7 +93,9 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save upload URL{options?.isMultipleAllowed ? 's' : ''} in a variable: {options?.isMultipleAllowed
? t('blocks.inputs.file.settings.saveMultipleUpload.label')
: t('blocks.inputs.file.settings.saveSingleUpload.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -1,6 +1,7 @@
import { TextInput, NumberInput } from '@/components/inputs' import { TextInput, NumberInput } from '@/components/inputs'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { FormLabel, Stack } from '@chakra-ui/react' import { FormLabel, Stack } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { NumberInputBlock, Variable } from '@typebot.io/schemas' import { NumberInputBlock, Variable } from '@typebot.io/schemas'
import { defaultNumberInputOptions } from '@typebot.io/schemas/features/blocks/inputs/number/constants' import { defaultNumberInputOptions } from '@typebot.io/schemas/features/blocks/inputs/number/constants'
import React from 'react' import React from 'react'
@@ -11,6 +12,7 @@ type Props = {
} }
export const NumberInputSettings = ({ options, onOptionsChange }: Props) => { export const NumberInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handlePlaceholderChange = (placeholder: string) => const handlePlaceholderChange = (placeholder: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } }) onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) => const handleButtonLabelChange = (button: string) =>
@@ -31,7 +33,7 @@ export const NumberInputSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<TextInput <TextInput
label="Placeholder:" label={t('blocks.inputs.settings.placeholder.label')}
defaultValue={ defaultValue={
options?.labels?.placeholder ?? options?.labels?.placeholder ??
defaultNumberInputOptions.labels.placeholder defaultNumberInputOptions.labels.placeholder
@@ -39,30 +41,30 @@ export const NumberInputSettings = ({ options, onOptionsChange }: Props) => {
onChange={handlePlaceholderChange} onChange={handlePlaceholderChange}
/> />
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultNumberInputOptions.labels.button options?.labels?.button ?? defaultNumberInputOptions.labels.button
} }
onChange={handleButtonLabelChange} onChange={handleButtonLabelChange}
/> />
<NumberInput <NumberInput
label="Min:" label={t('blocks.inputs.settings.min.label')}
defaultValue={options?.min} defaultValue={options?.min}
onValueChange={handleMinChange} onValueChange={handleMinChange}
/> />
<NumberInput <NumberInput
label="Max:" label={t('blocks.inputs.settings.max.label')}
defaultValue={options?.max} defaultValue={options?.max}
onValueChange={handleMaxChange} onValueChange={handleMaxChange}
/> />
<NumberInput <NumberInput
label="Step:" label={t('blocks.inputs.number.settings.step.label')}
defaultValue={options?.step} defaultValue={options?.step}
onValueChange={handleStepChange} onValueChange={handleStepChange}
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -9,6 +9,7 @@ import {
import React from 'react' import React from 'react'
import { TextInput } from '@/components/inputs' import { TextInput } from '@/components/inputs'
import { PaymentAddress } from '@typebot.io/schemas' import { PaymentAddress } from '@typebot.io/schemas'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
address: PaymentAddress address: PaymentAddress
@@ -16,6 +17,8 @@ type Props = {
} }
export const PaymentAddressSettings = ({ address, onAddressChange }: Props) => { export const PaymentAddressSettings = ({ address, onAddressChange }: Props) => {
const { t } = useTranslate()
const updateCountry = (country: string) => const updateCountry = (country: string) =>
onAddressChange({ onAddressChange({
...address, ...address,
@@ -56,37 +59,41 @@ export const PaymentAddressSettings = ({ address, onAddressChange }: Props) => {
<Accordion allowToggle> <Accordion allowToggle>
<AccordionItem> <AccordionItem>
<AccordionButton justifyContent="space-between"> <AccordionButton justifyContent="space-between">
Address {t('blocks.inputs.payment.settings.address.label')}
<AccordionIcon /> <AccordionIcon />
</AccordionButton> </AccordionButton>
<AccordionPanel py={4} as={Stack} spacing="4"> <AccordionPanel py={4} as={Stack} spacing="4">
<TextInput <TextInput
label="Country:" label={t('blocks.inputs.payment.settings.address.country.label')}
defaultValue={address?.country ?? ''} defaultValue={address?.country ?? ''}
onChange={updateCountry} onChange={updateCountry}
/> />
<TextInput <TextInput
label="Line 1:" label={t('blocks.inputs.payment.settings.address.line.label', {
line: '1',
})}
defaultValue={address?.line1 ?? ''} defaultValue={address?.line1 ?? ''}
onChange={updateLine1} onChange={updateLine1}
/> />
<TextInput <TextInput
label="Line 2:" label={t('blocks.inputs.payment.settings.address.line.label', {
line: '2',
})}
defaultValue={address?.line2 ?? ''} defaultValue={address?.line2 ?? ''}
onChange={updateLine2} onChange={updateLine2}
/> />
<TextInput <TextInput
label="City:" label={t('blocks.inputs.payment.settings.address.city.label')}
defaultValue={address?.city ?? ''} defaultValue={address?.city ?? ''}
onChange={updateCity} onChange={updateCity}
/> />
<TextInput <TextInput
label="State:" label={t('blocks.inputs.payment.settings.address.state.label')}
defaultValue={address?.state ?? ''} defaultValue={address?.state ?? ''}
onChange={updateState} onChange={updateState}
/> />
<TextInput <TextInput
label="Postal code:" label={t('blocks.inputs.payment.settings.address.postalCode.label')}
defaultValue={address?.postalCode ?? ''} defaultValue={address?.postalCode ?? ''}
onChange={updatePostalCode} onChange={updatePostalCode}
/> />

View File

@@ -1,4 +1,5 @@
import { Text } from '@chakra-ui/react' import { Text } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { PaymentInputBlock } from '@typebot.io/schemas' import { PaymentInputBlock } from '@typebot.io/schemas'
type Props = { type Props = {
@@ -6,15 +7,22 @@ type Props = {
} }
export const PaymentInputContent = ({ block }: Props) => { export const PaymentInputContent = ({ block }: Props) => {
const { t } = useTranslate()
if ( if (
!block.options?.amount || !block.options?.amount ||
!block.options.credentialsId || !block.options.credentialsId ||
!block.options.currency !block.options.currency
) )
return <Text color="gray.500">Configure...</Text> return (
<Text color="gray.500">
{t('blocks.inputs.payment.placeholder.label')}
</Text>
)
return ( return (
<Text noOfLines={1} pr="6"> <Text noOfLines={1} pr="6">
Collect {block.options.amount} {block.options.currency} {t('blocks.inputs.payment.collect.label')} {block.options.amount}{' '}
{block.options.currency}
</Text> </Text>
) )
} }

View File

@@ -23,6 +23,7 @@ import {
PaymentProvider, PaymentProvider,
defaultPaymentInputOptions, defaultPaymentInputOptions,
} from '@typebot.io/schemas/features/blocks/inputs/payment/constants' } from '@typebot.io/schemas/features/blocks/inputs/payment/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
options: PaymentInputBlock['options'] options: PaymentInputBlock['options']
@@ -32,6 +33,7 @@ type Props = {
export const PaymentSettings = ({ options, onOptionsChange }: Props) => { export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
const { workspace } = useWorkspace() const { workspace } = useWorkspace()
const { isOpen, onOpen, onClose } = useDisclosure() const { isOpen, onOpen, onClose } = useDisclosure()
const { t } = useTranslate()
const updateProvider = (provider: PaymentProvider) => { const updateProvider = (provider: PaymentProvider) => {
onOptionsChange({ onOptionsChange({
@@ -104,7 +106,7 @@ export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<Stack> <Stack>
<Text>Provider:</Text> <Text>{t('blocks.inputs.payment.settings.provider.label')}</Text>
<DropdownList <DropdownList
onItemSelect={updateProvider} onItemSelect={updateProvider}
items={Object.values(PaymentProvider)} items={Object.values(PaymentProvider)}
@@ -112,7 +114,7 @@ export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
/> />
</Stack> </Stack>
<Stack> <Stack>
<Text>Account:</Text> <Text>{t('blocks.inputs.payment.settings.account.label')}</Text>
{workspace && ( {workspace && (
<CredentialsDropdown <CredentialsDropdown
type="stripe" type="stripe"
@@ -120,19 +122,24 @@ export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
currentCredentialsId={options?.credentialsId} currentCredentialsId={options?.credentialsId}
onCredentialsSelect={updateCredentials} onCredentialsSelect={updateCredentials}
onCreateNewClick={onOpen} onCreateNewClick={onOpen}
credentialsName="Stripe account" credentialsName={t(
'blocks.inputs.payment.settings.accountText.label',
{
provider: 'Stripe',
}
)}
/> />
)} )}
</Stack> </Stack>
<HStack> <HStack>
<TextInput <TextInput
label="Price amount:" label={t('blocks.inputs.payment.settings.priceAmount.label')}
onChange={updateAmount} onChange={updateAmount}
defaultValue={options?.amount} defaultValue={options?.amount}
placeholder="30.00" placeholder="30.00"
/> />
<Stack> <Stack>
<Text>Currency:</Text> <Text>{t('blocks.inputs.payment.settings.currency.label')}</Text>
<Select <Select
placeholder="Select option" placeholder="Select option"
value={options?.currency ?? defaultPaymentInputOptions.currency} value={options?.currency ?? defaultPaymentInputOptions.currency}
@@ -147,14 +154,14 @@ export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
</Stack> </Stack>
</HStack> </HStack>
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
onChange={updateButtonLabel} onChange={updateButtonLabel}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultPaymentInputOptions.labels.button options?.labels?.button ?? defaultPaymentInputOptions.labels.button
} }
/> />
<TextInput <TextInput
label="Success message:" label={t('blocks.inputs.payment.settings.successMessage.label')}
onChange={updateSuccessLabel} onChange={updateSuccessLabel}
defaultValue={ defaultValue={
options?.labels?.success ?? defaultPaymentInputOptions.labels.success options?.labels?.success ?? defaultPaymentInputOptions.labels.success
@@ -163,30 +170,38 @@ export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
<Accordion allowToggle> <Accordion allowToggle>
<AccordionItem> <AccordionItem>
<AccordionButton justifyContent="space-between"> <AccordionButton justifyContent="space-between">
Additional information {t('blocks.inputs.payment.settings.additionalInformation.label')}
<AccordionIcon /> <AccordionIcon />
</AccordionButton> </AccordionButton>
<AccordionPanel py={4} as={Stack} spacing="6"> <AccordionPanel py={4} as={Stack} spacing="6">
<TextInput <TextInput
label="Description:" label={t('blocks.inputs.settings.description.label')}
defaultValue={options?.additionalInformation?.description} defaultValue={options?.additionalInformation?.description}
onChange={updateDescription} onChange={updateDescription}
placeholder="A digital product" placeholder={t(
'blocks.inputs.payment.settings.additionalInformation.description.placeholder.label'
)}
/> />
<TextInput <TextInput
label="Name:" label={t(
'blocks.inputs.payment.settings.additionalInformation.name.label'
)}
defaultValue={options?.additionalInformation?.name} defaultValue={options?.additionalInformation?.name}
onChange={updateName} onChange={updateName}
placeholder="John Smith" placeholder="John Smith"
/> />
<TextInput <TextInput
label="Email:" label={t(
'blocks.inputs.payment.settings.additionalInformation.email.label'
)}
defaultValue={options?.additionalInformation?.email} defaultValue={options?.additionalInformation?.email}
onChange={updateEmail} onChange={updateEmail}
placeholder="john@gmail.com" placeholder="john@gmail.com"
/> />
<TextInput <TextInput
label="Phone number:" label={t(
'blocks.inputs.payment.settings.additionalInformation.phone.label'
)}
defaultValue={options?.additionalInformation?.phoneNumber} defaultValue={options?.additionalInformation?.phoneNumber}
onChange={updatePhoneNumber} onChange={updatePhoneNumber}
placeholder="+33XXXXXXXXX" placeholder="+33XXXXXXXXX"

View File

@@ -23,6 +23,7 @@ import { StripeCredentials } from '@typebot.io/schemas'
import { trpc } from '@/lib/trpc' import { trpc } from '@/lib/trpc'
import { isNotEmpty } from '@typebot.io/lib' import { isNotEmpty } from '@typebot.io/lib'
import { useUser } from '@/features/account/hooks/useUser' import { useUser } from '@/features/account/hooks/useUser'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
isOpen: boolean isOpen: boolean
@@ -35,6 +36,7 @@ export const StripeConfigModal = ({
onNewCredentials, onNewCredentials,
onClose, onClose,
}: Props) => { }: Props) => {
const { t } = useTranslate()
const { user } = useUser() const { user } = useUser()
const { workspace } = useWorkspace() const { workspace } = useWorkspace()
const [isCreating, setIsCreating] = useState(false) const [isCreating, setIsCreating] = useState(false)
@@ -122,13 +124,17 @@ export const StripeConfigModal = ({
<Modal isOpen={isOpen} onClose={onClose}> <Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay /> <ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader>Connect Stripe account</ModalHeader> <ModalHeader>
{t('blocks.inputs.payment.settings.stripeConfig.title.label')}
</ModalHeader>
<ModalCloseButton /> <ModalCloseButton />
<ModalBody> <ModalBody>
<Stack as="form" spacing={4}> <Stack as="form" spacing={4}>
<TextInput <TextInput
isRequired isRequired
label="Account name:" label={t(
'blocks.inputs.payment.settings.stripeConfig.accountName.label'
)}
onChange={handleNameChange} onChange={handleNameChange}
placeholder="Typebot" placeholder="Typebot"
withVariableButton={false} withVariableButton={false}
@@ -136,9 +142,13 @@ export const StripeConfigModal = ({
/> />
<Stack> <Stack>
<FormLabel> <FormLabel>
Test keys:{' '} {t(
'blocks.inputs.payment.settings.stripeConfig.testKeys.label'
)}{' '}
<MoreInfoTooltip> <MoreInfoTooltip>
Will be used when previewing the bot. {t(
'blocks.inputs.payment.settings.stripeConfig.testKeys.infoText.label'
)}
</MoreInfoTooltip> </MoreInfoTooltip>
</FormLabel> </FormLabel>
<HStack> <HStack>
@@ -157,7 +167,11 @@ export const StripeConfigModal = ({
</HStack> </HStack>
</Stack> </Stack>
<Stack> <Stack>
<FormLabel>Live keys:</FormLabel> <FormLabel>
{t(
'blocks.inputs.payment.settings.stripeConfig.liveKeys.label'
)}
</FormLabel>
<HStack> <HStack>
<FormControl> <FormControl>
<TextInput <TextInput
@@ -179,9 +193,11 @@ export const StripeConfigModal = ({
</Stack> </Stack>
<Text> <Text>
(You can find your keys{' '} ({t('blocks.inputs.payment.settings.stripeConfig.findKeys.label')}{' '}
<TextLink href="https://dashboard.stripe.com/apikeys" isExternal> <TextLink href="https://dashboard.stripe.com/apikeys" isExternal>
here {t(
'blocks.inputs.payment.settings.stripeConfig.findKeys.here.label'
)}
</TextLink> </TextLink>
) )
</Text> </Text>
@@ -199,7 +215,7 @@ export const StripeConfigModal = ({
} }
isLoading={isCreating} isLoading={isCreating}
> >
Connect {t('connect')}
</Button> </Button>
</ModalFooter> </ModalFooter>
</ModalContent> </ModalContent>

View File

@@ -1,4 +1,5 @@
import { Select } from '@chakra-ui/react' import { Select } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import React, { ChangeEvent } from 'react' import React, { ChangeEvent } from 'react'
type Props = { type Props = {
@@ -7,12 +8,15 @@ type Props = {
} }
export const CountryCodeSelect = ({ countryCode, onSelect }: Props) => { export const CountryCodeSelect = ({ countryCode, onSelect }: Props) => {
const { t } = useTranslate()
const handleOnChange = (e: ChangeEvent<HTMLSelectElement>) => { const handleOnChange = (e: ChangeEvent<HTMLSelectElement>) => {
onSelect(e.target.value) onSelect(e.target.value)
} }
return ( return (
<Select <Select
placeholder="International" placeholder={t(
'blocks.inputs.phone.settings.international.placeholder.label'
)}
value={countryCode} value={countryCode}
onChange={handleOnChange} onChange={handleOnChange}
> >

View File

@@ -4,6 +4,7 @@ import { FormLabel, Stack } from '@chakra-ui/react'
import { PhoneNumberInputBlock, Variable } from '@typebot.io/schemas' import { PhoneNumberInputBlock, Variable } from '@typebot.io/schemas'
import React from 'react' import React from 'react'
import { CountryCodeSelect } from './CountryCodeSelect' import { CountryCodeSelect } from './CountryCodeSelect'
import { useTranslate } from '@tolgee/react'
import { defaultPhoneInputOptions } from '@typebot.io/schemas/features/blocks/inputs/phone/constants' import { defaultPhoneInputOptions } from '@typebot.io/schemas/features/blocks/inputs/phone/constants'
type Props = { type Props = {
@@ -12,6 +13,7 @@ type Props = {
} }
export const PhoneInputSettings = ({ options, onOptionsChange }: Props) => { export const PhoneInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handlePlaceholderChange = (placeholder: string) => const handlePlaceholderChange = (placeholder: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } }) onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) => const handleButtonLabelChange = (button: string) =>
@@ -26,7 +28,7 @@ export const PhoneInputSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<TextInput <TextInput
label="Placeholder:" label={t('blocks.inputs.settings.placeholder.label')}
defaultValue={ defaultValue={
options?.labels?.placeholder ?? options?.labels?.placeholder ??
defaultPhoneInputOptions.labels.placeholder defaultPhoneInputOptions.labels.placeholder
@@ -34,7 +36,7 @@ export const PhoneInputSettings = ({ options, onOptionsChange }: Props) => {
onChange={handlePlaceholderChange} onChange={handlePlaceholderChange}
/> />
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultPhoneInputOptions.labels.button options?.labels?.button ?? defaultPhoneInputOptions.labels.button
} }
@@ -42,7 +44,7 @@ export const PhoneInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="button"> <FormLabel mb="0" htmlFor="button">
Default country: {t('blocks.inputs.phone.settings.defaultCountry.label')}
</FormLabel> </FormLabel>
<CountryCodeSelect <CountryCodeSelect
onSelect={handleDefaultCountryChange} onSelect={handleDefaultCountryChange}
@@ -50,7 +52,7 @@ export const PhoneInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
</Stack> </Stack>
<TextInput <TextInput
label="Retry message:" label={t('blocks.inputs.settings.retryMessage.label')}
defaultValue={ defaultValue={
options?.retryMessageContent ?? options?.retryMessageContent ??
defaultPhoneInputOptions.retryMessageContent defaultPhoneInputOptions.retryMessageContent
@@ -59,7 +61,7 @@ export const PhoneInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -15,6 +15,7 @@ import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSetting
import { ConditionForm } from '@/features/blocks/logic/condition/components/ConditionForm' import { ConditionForm } from '@/features/blocks/logic/condition/components/ConditionForm'
import { Condition } from '@typebot.io/schemas' import { Condition } from '@typebot.io/schemas'
import { LogicalOperator } from '@typebot.io/schemas/features/blocks/logic/condition/constants' import { LogicalOperator } from '@typebot.io/schemas/features/blocks/logic/condition/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
workspaceId: string workspaceId: string
@@ -31,6 +32,8 @@ export const PictureChoiceItemSettings = ({
item, item,
onItemChange, onItemChange,
}: Props) => { }: Props) => {
const { t } = useTranslate()
const updateTitle = (title: string) => onItemChange({ ...item, title }) const updateTitle = (title: string) => onItemChange({ ...item, title })
const updateImage = (pictureSrc: string) => { const updateImage = (pictureSrc: string) => {
@@ -61,13 +64,17 @@ export const PictureChoiceItemSettings = ({
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<HStack> <HStack>
<Text fontWeight="medium">Image:</Text> <Text fontWeight="medium">
{t('blocks.inputs.picture.itemSettings.image.label')}
</Text>
<Popover isLazy> <Popover isLazy>
{({ onClose }) => ( {({ onClose }) => (
<> <>
<PopoverTrigger> <PopoverTrigger>
<Button size="sm"> <Button size="sm">
{item.pictureSrc ? 'Change image' : 'Pick an image'} {item.pictureSrc
? t('blocks.inputs.picture.itemSettings.image.change.label')
: t('blocks.inputs.picture.itemSettings.image.pick.label')}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent p="4" w="500px"> <PopoverContent p="4" w="500px">
@@ -91,17 +98,17 @@ export const PictureChoiceItemSettings = ({
</Popover> </Popover>
</HStack> </HStack>
<TextInput <TextInput
label="Title:" label={t('blocks.inputs.picture.itemSettings.title.label')}
defaultValue={item.title} defaultValue={item.title}
onChange={updateTitle} onChange={updateTitle}
/> />
<Textarea <Textarea
label="Description:" label={t('blocks.inputs.settings.description.label')}
defaultValue={item.description} defaultValue={item.description}
onChange={updateDescription} onChange={updateDescription}
/> />
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Display condition" label={t('blocks.inputs.settings.displayCondition.label')}
initialValue={item.displayCondition?.isEnabled ?? false} initialValue={item.displayCondition?.isEnabled ?? false}
onCheckChange={updateIsDisplayConditionEnabled} onCheckChange={updateIsDisplayConditionEnabled}
> >

View File

@@ -5,6 +5,7 @@ import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { SetVariableLabel } from '@/components/SetVariableLabel' import { SetVariableLabel } from '@/components/SetVariableLabel'
import { ItemNodesList } from '@/features/graph/components/nodes/item/ItemNodesList' import { ItemNodesList } from '@/features/graph/components/nodes/item/ItemNodesList'
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice' import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
block: PictureChoiceBlock block: PictureChoiceBlock
@@ -12,6 +13,7 @@ type Props = {
} }
export const PictureChoiceNode = ({ block, indices }: Props) => { export const PictureChoiceNode = ({ block, indices }: Props) => {
const { t } = useTranslate()
const { typebot } = useTypebot() const { typebot } = useTypebot()
const dynamicVariableName = typebot?.variables.find( const dynamicVariableName = typebot?.variables.find(
(variable) => (variable) =>
@@ -22,11 +24,17 @@ export const PictureChoiceNode = ({ block, indices }: Props) => {
<Stack w="full"> <Stack w="full">
{block.options?.dynamicItems?.isEnabled && dynamicVariableName ? ( {block.options?.dynamicItems?.isEnabled && dynamicVariableName ? (
<Wrap spacing={1}> <Wrap spacing={1}>
<Text>Display</Text> <Text>
{t('blocks.inputs.picture.settings.dynamicVariables.display.label')}
</Text>
<Tag bg="orange.400" color="white"> <Tag bg="orange.400" color="white">
{dynamicVariableName} {dynamicVariableName}
</Tag> </Tag>
<Text>pictures</Text> <Text>
{t(
'blocks.inputs.picture.settings.dynamicVariables.pictures.label'
)}
</Text>
</Wrap> </Wrap>
) : ( ) : (
<ItemNodesList block={block} indices={indices} /> <ItemNodesList block={block} indices={indices} />

View File

@@ -6,6 +6,7 @@ import React from 'react'
import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice' import { PictureChoiceBlock } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings' import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
import { defaultPictureChoiceOptions } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice/constants' import { defaultPictureChoiceOptions } from '@typebot.io/schemas/features/blocks/inputs/pictureChoice/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
options?: PictureChoiceBlock['options'] options?: PictureChoiceBlock['options']
@@ -13,6 +14,8 @@ type Props = {
} }
export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => { export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const updateIsMultiple = (isMultipleChoice: boolean) => const updateIsMultiple = (isMultipleChoice: boolean) =>
onOptionsChange({ ...options, isMultipleChoice }) onOptionsChange({ ...options, isMultipleChoice })
const updateButtonLabel = (buttonLabel: string) => const updateButtonLabel = (buttonLabel: string) =>
@@ -63,14 +66,14 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Is searchable?" label={t('blocks.inputs.settings.isSearchable.label')}
initialValue={ initialValue={
options?.isSearchable ?? defaultPictureChoiceOptions.isSearchable options?.isSearchable ?? defaultPictureChoiceOptions.isSearchable
} }
onCheckChange={updateIsSearchable} onCheckChange={updateIsSearchable}
> >
<TextInput <TextInput
label="Input placeholder:" label={t('blocks.inputs.settings.input.placeholder.label')}
defaultValue={ defaultValue={
options?.searchInputPlaceholder ?? options?.searchInputPlaceholder ??
defaultPictureChoiceOptions.searchInputPlaceholder defaultPictureChoiceOptions.searchInputPlaceholder
@@ -79,7 +82,7 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
/> />
</SwitchWithRelatedSettings> </SwitchWithRelatedSettings>
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Multiple choice?" label={t('blocks.inputs.settings.multipleChoice.label')}
initialValue={ initialValue={
options?.isMultipleChoice ?? options?.isMultipleChoice ??
defaultPictureChoiceOptions.isMultipleChoice defaultPictureChoiceOptions.isMultipleChoice
@@ -87,7 +90,7 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
onCheckChange={updateIsMultiple} onCheckChange={updateIsMultiple}
> >
<TextInput <TextInput
label="Submit button label:" label={t('blocks.inputs.settings.submitButton.label')}
defaultValue={ defaultValue={
options?.buttonLabel ?? defaultPictureChoiceOptions.buttonLabel options?.buttonLabel ?? defaultPictureChoiceOptions.buttonLabel
} }
@@ -96,7 +99,7 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
</SwitchWithRelatedSettings> </SwitchWithRelatedSettings>
<SwitchWithRelatedSettings <SwitchWithRelatedSettings
label="Dynamic items?" label={t('blocks.inputs.picture.settings.dynamicItems.label')}
initialValue={ initialValue={
options?.dynamicItems?.isEnabled ?? options?.dynamicItems?.isEnabled ??
defaultPictureChoiceOptions.dynamicItems.isEnabled defaultPictureChoiceOptions.dynamicItems.isEnabled
@@ -105,7 +108,7 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
> >
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Images: {t('blocks.inputs.picture.settings.dynamicItems.images.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.dynamicItems?.pictureSrcsVariableId} initialVariableId={options?.dynamicItems?.pictureSrcsVariableId}
@@ -114,7 +117,7 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
</Stack> </Stack>
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Titles: {t('blocks.inputs.picture.settings.dynamicItems.titles.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.dynamicItems?.titlesVariableId} initialVariableId={options?.dynamicItems?.titlesVariableId}
@@ -123,7 +126,9 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
</Stack> </Stack>
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Descriptions: {t(
'blocks.inputs.picture.settings.dynamicItems.descriptions.label'
)}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.dynamicItems?.descriptionsVariableId} initialVariableId={options?.dynamicItems?.descriptionsVariableId}
@@ -134,7 +139,7 @@ export const PictureChoiceSettings = ({ options, onOptionsChange }: Props) => {
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -1,5 +1,6 @@
import { WithVariableContent } from '@/features/graph/components/nodes/block/WithVariableContent' import { WithVariableContent } from '@/features/graph/components/nodes/block/WithVariableContent'
import { Text } from '@chakra-ui/react' import { Text } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { RatingInputBlock } from '@typebot.io/schemas' import { RatingInputBlock } from '@typebot.io/schemas'
import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants' import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants'
@@ -8,12 +9,17 @@ type Props = {
block: RatingInputBlock block: RatingInputBlock
} }
export const RatingInputContent = ({ variableId, block }: Props) => export const RatingInputContent = ({ variableId, block }: Props) => {
variableId ? ( const { t } = useTranslate()
return variableId ? (
<WithVariableContent variableId={variableId} /> <WithVariableContent variableId={variableId} />
) : ( ) : (
<Text noOfLines={1} pr="6"> <Text noOfLines={1} pr="6">
Rate from {block.options?.buttonType === 'Icons' ? 1 : 0} to{' '} {t('blocks.inputs.rating.from.label')}{' '}
{block.options?.buttonType === 'Icons' ? 1 : 0}{' '}
{t('blocks.inputs.rating.to.label')}{' '}
{block.options?.length ?? defaultRatingInputOptions.length} {block.options?.length ?? defaultRatingInputOptions.length}
</Text> </Text>
) )
}

View File

@@ -6,6 +6,7 @@ import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { TextInput } from '@/components/inputs' import { TextInput } from '@/components/inputs'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants' import { defaultRatingInputOptions } from '@typebot.io/schemas/features/blocks/inputs/rating/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
options: RatingInputBlock['options'] options: RatingInputBlock['options']
@@ -13,6 +14,8 @@ type Props = {
} }
export const RatingInputSettings = ({ options, onOptionsChange }: Props) => { export const RatingInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handleLengthChange = (length: number) => const handleLengthChange = (length: number) =>
onOptionsChange({ ...options, length }) onOptionsChange({ ...options, length })
@@ -52,7 +55,7 @@ export const RatingInputSettings = ({ options, onOptionsChange }: Props) => {
<Stack spacing={4}> <Stack spacing={4}>
<Stack> <Stack>
<FormLabel mb="0" htmlFor="button"> <FormLabel mb="0" htmlFor="button">
Maximum: {t('blocks.inputs.rating.settings.maximum.label')}
</FormLabel> </FormLabel>
<DropdownList <DropdownList
onItemSelect={handleLengthChange} onItemSelect={handleLengthChange}
@@ -63,7 +66,7 @@ export const RatingInputSettings = ({ options, onOptionsChange }: Props) => {
<Stack> <Stack>
<FormLabel mb="0" htmlFor="button"> <FormLabel mb="0" htmlFor="button">
Type: {t('blocks.inputs.rating.settings.type.label')}
</FormLabel> </FormLabel>
<DropdownList <DropdownList
onItemSelect={handleTypeChange} onItemSelect={handleTypeChange}
@@ -76,7 +79,7 @@ export const RatingInputSettings = ({ options, onOptionsChange }: Props) => {
{options?.buttonType === 'Icons' && ( {options?.buttonType === 'Icons' && (
<SwitchWithLabel <SwitchWithLabel
label="Custom icon?" label={t('blocks.inputs.rating.settings.customIcon.label')}
initialValue={ initialValue={
options?.customIcon?.isEnabled ?? options?.customIcon?.isEnabled ??
defaultRatingInputOptions.customIcon.isEnabled defaultRatingInputOptions.customIcon.isEnabled
@@ -86,33 +89,43 @@ export const RatingInputSettings = ({ options, onOptionsChange }: Props) => {
)} )}
{options?.buttonType === 'Icons' && options.customIcon?.isEnabled && ( {options?.buttonType === 'Icons' && options.customIcon?.isEnabled && (
<TextInput <TextInput
label="Icon SVG:" label={t('blocks.inputs.rating.settings.iconSVG.label')}
defaultValue={options.customIcon.svg} defaultValue={options.customIcon.svg}
onChange={handleIconSvgChange} onChange={handleIconSvgChange}
placeholder="<svg>...</svg>" placeholder="<svg>...</svg>"
/> />
)} )}
<TextInput <TextInput
label={`${options?.buttonType === 'Icons' ? '1' : '0'} label:`} label={t('blocks.inputs.rating.settings.rateLabel.label', {
rate: options?.buttonType === 'Icons' ? '1' : '0',
})}
defaultValue={options?.labels?.left} defaultValue={options?.labels?.left}
onChange={handleLeftLabelChange} onChange={handleLeftLabelChange}
placeholder="Not likely at all" placeholder={t(
'blocks.inputs.rating.settings.notLikely.placeholder.label'
)}
/> />
<TextInput <TextInput
label={`${length} label:`} label={t('blocks.inputs.rating.settings.rateLabel.label', {
rate: length,
})}
defaultValue={options?.labels?.right} defaultValue={options?.labels?.right}
onChange={handleRightLabelChange} onChange={handleRightLabelChange}
placeholder="Extremely likely" placeholder={t(
'blocks.inputs.rating.settings.extremelyLikely.placeholder.label'
)}
/> />
<SwitchWithLabel <SwitchWithLabel
label="One click submit" label={t('blocks.inputs.rating.settings.oneClickSubmit.label')}
moreInfoContent='If enabled, the answer will be submitted as soon as the user clicks on a rating instead of showing the "Send" button.' moreInfoContent={t(
'blocks.inputs.rating.settings.oneClickSubmit.infoText.label'
)}
initialValue={isOneClickSubmitEnabled} initialValue={isOneClickSubmitEnabled}
onCheckChange={handleOneClickSubmitChange} onCheckChange={handleOneClickSubmitChange}
/> />
{!isOneClickSubmitEnabled && ( {!isOneClickSubmitEnabled && (
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultRatingInputOptions.labels.button options?.labels?.button ?? defaultRatingInputOptions.labels.button
} }
@@ -121,7 +134,7 @@ export const RatingInputSettings = ({ options, onOptionsChange }: Props) => {
)} )}
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -2,6 +2,7 @@ import { TextInput } from '@/components/inputs'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel' import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { FormLabel, Stack } from '@chakra-ui/react' import { FormLabel, Stack } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { TextInputBlock, Variable } from '@typebot.io/schemas' import { TextInputBlock, Variable } from '@typebot.io/schemas'
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants' import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
import React from 'react' import React from 'react'
@@ -12,6 +13,7 @@ type Props = {
} }
export const TextInputSettings = ({ options, onOptionsChange }: Props) => { export const TextInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handlePlaceholderChange = (placeholder: string) => const handlePlaceholderChange = (placeholder: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } }) onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) => const handleButtonLabelChange = (button: string) =>
@@ -24,12 +26,12 @@ export const TextInputSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<SwitchWithLabel <SwitchWithLabel
label="Long text?" label={t('blocks.inputs.text.settings.longText.label')}
initialValue={options?.isLong ?? defaultTextInputOptions.isLong} initialValue={options?.isLong ?? defaultTextInputOptions.isLong}
onCheckChange={handleLongChange} onCheckChange={handleLongChange}
/> />
<TextInput <TextInput
label="Placeholder:" label={t('blocks.inputs.settings.placeholder.label')}
defaultValue={ defaultValue={
options?.labels?.placeholder ?? options?.labels?.placeholder ??
defaultTextInputOptions.labels.placeholder defaultTextInputOptions.labels.placeholder
@@ -37,7 +39,7 @@ export const TextInputSettings = ({ options, onOptionsChange }: Props) => {
onChange={handlePlaceholderChange} onChange={handlePlaceholderChange}
/> />
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultTextInputOptions.labels.button options?.labels?.button ?? defaultTextInputOptions.labels.button
} }
@@ -45,7 +47,7 @@ export const TextInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -1,6 +1,7 @@
import { TextInput } from '@/components/inputs' import { TextInput } from '@/components/inputs'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { FormLabel, Stack } from '@chakra-ui/react' import { FormLabel, Stack } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { UrlInputBlock, Variable } from '@typebot.io/schemas' import { UrlInputBlock, Variable } from '@typebot.io/schemas'
import { defaultUrlInputOptions } from '@typebot.io/schemas/features/blocks/inputs/url/constants' import { defaultUrlInputOptions } from '@typebot.io/schemas/features/blocks/inputs/url/constants'
import React from 'react' import React from 'react'
@@ -11,6 +12,7 @@ type Props = {
} }
export const UrlInputSettings = ({ options, onOptionsChange }: Props) => { export const UrlInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handlePlaceholderChange = (placeholder: string) => const handlePlaceholderChange = (placeholder: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } }) onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) => const handleButtonLabelChange = (button: string) =>
@@ -23,7 +25,7 @@ export const UrlInputSettings = ({ options, onOptionsChange }: Props) => {
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
<TextInput <TextInput
label="Placeholder:" label={t('blocks.inputs.settings.placeholder.label')}
defaultValue={ defaultValue={
options?.labels?.placeholder ?? options?.labels?.placeholder ??
defaultUrlInputOptions.labels.placeholder defaultUrlInputOptions.labels.placeholder
@@ -31,14 +33,14 @@ export const UrlInputSettings = ({ options, onOptionsChange }: Props) => {
onChange={handlePlaceholderChange} onChange={handlePlaceholderChange}
/> />
<TextInput <TextInput
label="Button label:" label={t('blocks.inputs.settings.button.label')}
defaultValue={ defaultValue={
options?.labels?.button ?? defaultUrlInputOptions.labels.button options?.labels?.button ?? defaultUrlInputOptions.labels.button
} }
onChange={handleButtonLabelChange} onChange={handleButtonLabelChange}
/> />
<TextInput <TextInput
label="Retry message:" label={t('blocks.inputs.settings.retryMessage.label')}
defaultValue={ defaultValue={
options?.retryMessageContent ?? options?.retryMessageContent ??
defaultUrlInputOptions.retryMessageContent defaultUrlInputOptions.retryMessageContent
@@ -47,7 +49,7 @@ export const UrlInputSettings = ({ options, onOptionsChange }: Props) => {
/> />
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: {t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel> </FormLabel>
<VariableSearchInput <VariableSearchInput
initialVariableId={options?.variableId} initialVariableId={options?.variableId}

View File

@@ -8,8 +8,8 @@ type Props = {
options: UrlInputBlock['options'] options: UrlInputBlock['options']
} }
export const UrlNodeContent = ({ options }: Props) => export const UrlNodeContent = ({ options }: Props) => {
options?.variableId ? ( return options?.variableId ? (
<WithVariableContent variableId={options.variableId} /> <WithVariableContent variableId={options.variableId} />
) : ( ) : (
<Text color={'gray.500'} w="90%"> <Text color={'gray.500'} w="90%">
@@ -17,3 +17,4 @@ export const UrlNodeContent = ({ options }: Props) =>
defaultUrlInputOptions.labels.placeholder} defaultUrlInputOptions.labels.placeholder}
</Text> </Text>
) )
}

View File

@@ -5,11 +5,14 @@ import { TableListItemProps } from '@/components/TableList'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { TextInput } from '@/components/inputs' import { TextInput } from '@/components/inputs'
import { ComparisonOperators } from '@typebot.io/schemas/features/blocks/logic/condition/constants' import { ComparisonOperators } from '@typebot.io/schemas/features/blocks/logic/condition/constants'
import { useTranslate } from '@tolgee/react'
export const ComparisonItem = ({ export const ComparisonItem = ({
item, item,
onItemChange, onItemChange,
}: TableListItemProps<Comparison>) => { }: TableListItemProps<Comparison>) => {
const { t } = useTranslate()
const handleSelectVariable = (variable?: Variable) => { const handleSelectVariable = (variable?: Variable) => {
if (variable?.id === item.variableId) return if (variable?.id === item.variableId) return
onItemChange({ ...item, variableId: variable?.id }) onItemChange({ ...item, variableId: variable?.id })
@@ -31,13 +34,15 @@ export const ComparisonItem = ({
<VariableSearchInput <VariableSearchInput
initialVariableId={item.variableId} initialVariableId={item.variableId}
onSelectVariable={handleSelectVariable} onSelectVariable={handleSelectVariable}
placeholder="Search for a variable" placeholder={t('variables.search')}
/> />
<DropdownList <DropdownList
currentItem={item.comparisonOperator} currentItem={item.comparisonOperator}
onItemSelect={handleSelectComparisonOperator} onItemSelect={handleSelectComparisonOperator}
items={Object.values(ComparisonOperators)} items={Object.values(ComparisonOperators)}
placeholder="Select an operator" placeholder={t(
'blocks.inputs.button.buttonSettings.displayCondition.selectOperator.label'
)}
/> />
{item.comparisonOperator !== ComparisonOperators.IS_SET && {item.comparisonOperator !== ComparisonOperators.IS_SET &&
item.comparisonOperator !== ComparisonOperators.IS_EMPTY && ( item.comparisonOperator !== ComparisonOperators.IS_EMPTY && (

View File

@@ -1,4 +1,5 @@
import { Stack, Wrap, Tag, Text, useColorModeValue } from '@chakra-ui/react' import { Stack, Wrap, Tag, Text, useColorModeValue } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { byId } from '@typebot.io/lib' import { byId } from '@typebot.io/lib'
import { Condition, Variable } from '@typebot.io/schemas' import { Condition, Variable } from '@typebot.io/schemas'
import { ComparisonOperators } from '@typebot.io/schemas/features/blocks/logic/condition/constants' import { ComparisonOperators } from '@typebot.io/schemas/features/blocks/logic/condition/constants'
@@ -15,6 +16,7 @@ export const ConditionContent = ({
size = 'sm', size = 'sm',
displaySemicolon, displaySemicolon,
}: Props) => { }: Props) => {
const { t } = useTranslate()
const comparisonValueBg = useColorModeValue('gray.200', 'gray.700') const comparisonValueBg = useColorModeValue('gray.200', 'gray.700')
return ( return (
<Stack> <Stack>
@@ -22,7 +24,11 @@ export const ConditionContent = ({
const variable = variables.find(byId(comparison.variableId)) const variable = variables.find(byId(comparison.variableId))
return ( return (
<Wrap key={comparison.id} spacing={1} noOfLines={1}> <Wrap key={comparison.id} spacing={1} noOfLines={1}>
{idx === 0 && <Text fontSize={size}>IF</Text>} {idx === 0 && (
<Text fontSize={size}>
{t('blocks.inputs.button.conditionContent.if.label')}
</Text>
)}
{idx > 0 && ( {idx > 0 && (
<Text fontSize={size}>{condition.logicalOperator ?? ''}</Text> <Text fontSize={size}>{condition.logicalOperator ?? ''}</Text>
)} )}

View File

@@ -8,6 +8,7 @@ import {
LogicalOperator, LogicalOperator,
defaultConditionItemContent, defaultConditionItemContent,
} from '@typebot.io/schemas/features/blocks/logic/condition/constants' } from '@typebot.io/schemas/features/blocks/logic/condition/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
condition: Condition | undefined condition: Condition | undefined
@@ -15,6 +16,7 @@ type Props = {
} }
export const ConditionForm = ({ condition, onConditionChange }: Props) => { export const ConditionForm = ({ condition, onConditionChange }: Props) => {
const { t } = useTranslate()
const handleComparisonsChange = (comparisons: Comparison[]) => const handleComparisonsChange = (comparisons: Comparison[]) =>
onConditionChange({ ...condition, comparisons }) onConditionChange({ ...condition, comparisons })
const handleLogicalOperatorChange = (logicalOperator: LogicalOperator) => const handleLogicalOperatorChange = (logicalOperator: LogicalOperator) =>
@@ -36,7 +38,9 @@ export const ConditionForm = ({ condition, onConditionChange }: Props) => {
/> />
</Flex> </Flex>
)} )}
addLabel="Add a comparison" addLabel={t(
'blocks.inputs.button.buttonSettings.addComparisonButton.label'
)}
> >
{(props) => <ComparisonItem {...props} />} {(props) => <ComparisonItem {...props} />}
</TableList> </TableList>

View File

@@ -15,6 +15,7 @@ import { useToast } from '../../../hooks/useToast'
import { Credentials } from '@typebot.io/schemas' import { Credentials } from '@typebot.io/schemas'
import { trpc } from '@/lib/trpc' import { trpc } from '@/lib/trpc'
import { useWorkspace } from '@/features/workspace/WorkspaceProvider' import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
import { useTranslate } from '@tolgee/react'
type Props = Omit<ButtonProps, 'type'> & { type Props = Omit<ButtonProps, 'type'> & {
type: Credentials['type'] type: Credentials['type']
@@ -36,6 +37,7 @@ export const CredentialsDropdown = ({
credentialsName, credentialsName,
...props ...props
}: Props) => { }: Props) => {
const { t } = useTranslate()
const { showToast } = useToast() const { showToast } = useToast()
const { currentRole } = useWorkspace() const { currentRole } = useWorkspace()
const { data, refetch } = trpc.credentials.listCredentials.useQuery({ const { data, refetch } = trpc.credentials.listCredentials.useQuery({
@@ -62,7 +64,7 @@ export const CredentialsDropdown = ({
}) })
const defaultCredentialsLabel = const defaultCredentialsLabel =
defaultCredentialLabel ?? `Select ${credentialsName}` defaultCredentialLabel ?? `${t('select')} ${credentialsName}`
const currentCredential = data?.credentials.find( const currentCredential = data?.credentials.find(
(c) => c.id === currentCredentialsId (c) => c.id === currentCredentialsId
@@ -91,7 +93,7 @@ export const CredentialsDropdown = ({
isDisabled={currentRole === 'GUEST'} isDisabled={currentRole === 'GUEST'}
{...props} {...props}
> >
Add {credentialsName} {t('add')} {credentialsName}
</Button> </Button>
) )
} }
@@ -140,7 +142,9 @@ export const CredentialsDropdown = ({
{credentials.name} {credentials.name}
<IconButton <IconButton
icon={<TrashIcon />} icon={<TrashIcon />}
aria-label="Remove credentials" aria-label={t(
'blocks.inputs.payment.settings.credentials.removeCredentials.label'
)}
size="xs" size="xs"
onClick={deleteCredentials(credentials.id)} onClick={deleteCredentials(credentials.id)}
isLoading={isDeleting === credentials.id} isLoading={isDeleting === credentials.id}
@@ -156,7 +160,7 @@ export const CredentialsDropdown = ({
icon={<PlusIcon />} icon={<PlusIcon />}
onClick={onCreateNewClick} onClick={onCreateNewClick}
> >
Connect new {t('blocks.inputs.payment.settings.credentials.connectNew.label')}
</MenuItem> </MenuItem>
)} )}
</Stack> </Stack>

View File

@@ -31,9 +31,11 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
id, id,
block, block,
indices, indices,
groupLabel,
...graphCoordinates ...graphCoordinates
}: Coordinates & { }: Coordinates & {
id: string id: string
groupLabel?: string
block: BlockV6 | BlockV6['type'] block: BlockV6 | BlockV6['type']
indices: BlockIndices indices: BlockIndices
}) => }) =>
@@ -42,7 +44,7 @@ const groupsActions = (setTypebot: SetTypebot): GroupsActions => ({
const newGroup: GroupV6 = { const newGroup: GroupV6 = {
id, id,
graphCoordinates, graphCoordinates,
title: `Group #${typebot.groups.length + 1}`, title: `${groupLabel ?? 'Group'} #${typebot.groups.length + 1}`,
blocks: [], blocks: [],
} }
typebot.groups.push(newGroup) typebot.groups.push(newGroup)

View File

@@ -2,9 +2,11 @@ import { MenuList, MenuItem } from '@chakra-ui/react'
import { CopyIcon, TrashIcon } from '@/components/icons' import { CopyIcon, TrashIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor/providers/TypebotProvider' import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { BlockIndices } from '@typebot.io/schemas' import { BlockIndices } from '@typebot.io/schemas'
import { useTranslate } from '@tolgee/react'
type Props = { indices: BlockIndices } type Props = { indices: BlockIndices }
export const BlockNodeContextMenu = ({ indices }: Props) => { export const BlockNodeContextMenu = ({ indices }: Props) => {
const { t } = useTranslate()
const { deleteBlock, duplicateBlock } = useTypebot() const { deleteBlock, duplicateBlock } = useTypebot()
const handleDuplicateClick = () => duplicateBlock(indices) const handleDuplicateClick = () => duplicateBlock(indices)
@@ -14,10 +16,10 @@ export const BlockNodeContextMenu = ({ indices }: Props) => {
return ( return (
<MenuList> <MenuList>
<MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}> <MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}>
Duplicate {t('duplicate')}
</MenuItem> </MenuItem>
<MenuItem icon={<TrashIcon />} onClick={handleDeleteClick}> <MenuItem icon={<TrashIcon />} onClick={handleDeleteClick}>
Delete {t('delete')}
</MenuItem> </MenuItem>
</MenuList> </MenuList>
) )

View File

@@ -9,6 +9,7 @@ import {
import { BlockWithOptions } from '@typebot.io/schemas' import { BlockWithOptions } from '@typebot.io/schemas'
import { getHelpDocUrl } from '@/features/graph/helpers/getHelpDocUrl' import { getHelpDocUrl } from '@/features/graph/helpers/getHelpDocUrl'
import { useForgedBlock } from '@/features/forge/hooks/useForgedBlock' import { useForgedBlock } from '@/features/forge/hooks/useForgedBlock'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
blockType: BlockWithOptions['type'] blockType: BlockWithOptions['type']
@@ -16,6 +17,7 @@ type Props = {
} }
export const SettingsHoverBar = ({ blockType, onExpandClick }: Props) => { export const SettingsHoverBar = ({ blockType, onExpandClick }: Props) => {
const { t } = useTranslate()
const { blockDef } = useForgedBlock(blockType) const { blockDef } = useForgedBlock(blockType)
const helpDocUrl = getHelpDocUrl(blockType, blockDef) const helpDocUrl = getHelpDocUrl(blockType, blockDef)
return ( return (
@@ -46,7 +48,7 @@ export const SettingsHoverBar = ({ blockType, onExpandClick }: Props) => {
href={helpDocUrl} href={helpDocUrl}
isExternal isExternal
> >
Help {t('help')}
</Button> </Button>
)} )}
</HStack> </HStack>

View File

@@ -1,12 +1,14 @@
import { MenuList, MenuItem } from '@chakra-ui/react' import { MenuList, MenuItem } from '@chakra-ui/react'
import { CopyIcon, TrashIcon } from '@/components/icons' import { CopyIcon, TrashIcon } from '@/components/icons'
import { useTypebot } from '@/features/editor/providers/TypebotProvider' import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { useTranslate } from '@tolgee/react'
export const GroupNodeContextMenu = ({ export const GroupNodeContextMenu = ({
groupIndex, groupIndex,
}: { }: {
groupIndex: number groupIndex: number
}) => { }) => {
const { t } = useTranslate()
const { deleteGroup, duplicateGroup } = useTypebot() const { deleteGroup, duplicateGroup } = useTypebot()
const handleDeleteClick = () => deleteGroup(groupIndex) const handleDeleteClick = () => deleteGroup(groupIndex)
@@ -16,10 +18,10 @@ export const GroupNodeContextMenu = ({
return ( return (
<MenuList> <MenuList>
<MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}> <MenuItem icon={<CopyIcon />} onClick={handleDuplicateClick}>
Duplicate {t('duplicate')}
</MenuItem> </MenuItem>
<MenuItem icon={<TrashIcon />} onClick={handleDeleteClick}> <MenuItem icon={<TrashIcon />} onClick={handleDeleteClick}>
Delete {t('delete')}
</MenuItem> </MenuItem>
</MenuList> </MenuList>
) )

View File

@@ -22,6 +22,7 @@ import { Coordinates } from '@dnd-kit/utilities'
import { BlockSourceEndpoint } from '../../endpoints/BlockSourceEndpoint' import { BlockSourceEndpoint } from '../../endpoints/BlockSourceEndpoint'
import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants' import { LogicBlockType } from '@typebot.io/schemas/features/blocks/logic/constants'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants' import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { useTranslate } from '@tolgee/react'
type Props = { type Props = {
block: BlockWithItems block: BlockWithItems
@@ -192,6 +193,8 @@ const DefaultItemNode = ({
block: BlockWithItems block: BlockWithItems
groupId: string groupId: string
}) => { }) => {
const { t } = useTranslate()
return ( return (
<Flex <Flex
px="4" px="4"
@@ -205,7 +208,9 @@ const DefaultItemNode = ({
cursor="not-allowed" cursor="not-allowed"
> >
<Text color="gray.500"> <Text color="gray.500">
{block.type === LogicBlockType.CONDITION ? 'Else' : 'Default'} {block.type === LogicBlockType.CONDITION
? t('blocks.inputs.button.else.label')
: t('blocks.inputs.button.default.label')}
</Text> </Text>
<BlockSourceEndpoint <BlockSourceEndpoint
source={{ source={{

View File

@@ -30,6 +30,7 @@
"account.preferences.graphNavigation.trackpad.label": "Trackpad", "account.preferences.graphNavigation.trackpad.label": "Trackpad",
"account.preferences.language.heading": "Language", "account.preferences.language.heading": "Language",
"account.preferences.language.tooltip": "The translations are not complete yet. It is a work in progress. \uD83E\uDD13", "account.preferences.language.tooltip": "The translations are not complete yet. It is a work in progress. \uD83E\uDD13",
"add": "Add",
"analytics.completionRateLabel": "Completion rate", "analytics.completionRateLabel": "Completion rate",
"analytics.notAvailableLabel": "Not available", "analytics.notAvailableLabel": "Not available",
"analytics.startsLabel": "Starts", "analytics.startsLabel": "Starts",
@@ -112,7 +113,103 @@
"billing.usage.heading": "Usage", "billing.usage.heading": "Usage",
"billing.usage.unlimited": "Unlimited", "billing.usage.unlimited": "Unlimited",
"blocks.bubbles.embed.blockCard.tooltip": "Embed a pdf, an iframe, a website...", "blocks.bubbles.embed.blockCard.tooltip": "Embed a pdf, an iframe, a website...",
"blocks.inputs.button.addItem.ariaLabel": "Add item",
"blocks.inputs.button.buttonSettings.addComparisonButton.label": "Add comparison",
"blocks.inputs.button.buttonSettings.displayCondition.infoText.label": "Only display this item if a condition is met.",
"blocks.inputs.button.buttonSettings.displayCondition.selectOperator.label": "Select an operator",
"blocks.inputs.button.clickToEdit.label": "Click to edit",
"blocks.inputs.button.conditionContent.if.label": "IF",
"blocks.inputs.button.default.label": "Default",
"blocks.inputs.button.else.label": "Else",
"blocks.inputs.button.openSettings.ariaLabel": "Open settings",
"blocks.inputs.button.settings.dynamicData.infoText.label": "If defined, buttons will be dynamically displayed based on what the variable contains.",
"blocks.inputs.button.settings.dynamicData.label": "Dynamic data:",
"blocks.inputs.button.variables.buttons.label": "buttons",
"blocks.inputs.button.variables.display.label": "Display",
"blocks.inputs.date.placeholder.label": "Pick a date...",
"blocks.inputs.date.settings.format.example.label": "i.e",
"blocks.inputs.date.settings.format.label": "Format:",
"blocks.inputs.date.settings.from.label": "From label:",
"blocks.inputs.date.settings.isRange.label": "Is range?",
"blocks.inputs.date.settings.to.label": "To label:",
"blocks.inputs.date.settings.toInputValue.label": "To:",
"blocks.inputs.date.settings.withTime.label": "With time?",
"blocks.inputs.file.collectMultiple.label": "Collect files",
"blocks.inputs.file.collectSingle.label": "Collect file",
"blocks.inputs.file.settings.allowMultiple.label": "Allow multiple files?",
"blocks.inputs.file.settings.clear.label": "Clear button label:",
"blocks.inputs.file.settings.required.label": "Required?",
"blocks.inputs.file.settings.saveMultipleUpload.label": "Save upload URLs in a variable:",
"blocks.inputs.file.settings.saveSingleUpload.label": "Save upload URL in a variable:",
"blocks.inputs.file.settings.skip.label": "Skip button label:",
"blocks.inputs.fileUpload.blockCard.tooltip": "Upload Files", "blocks.inputs.fileUpload.blockCard.tooltip": "Upload Files",
"blocks.inputs.number.settings.step.label": "Step:",
"blocks.inputs.payment.collect.label": "Coletar",
"blocks.inputs.payment.placeholder.label": "Configure...",
"blocks.inputs.payment.settings.account.label": "Account:",
"blocks.inputs.payment.settings.accountText.label": "{provider} account",
"blocks.inputs.payment.settings.additionalInformation.description.placeholder.label": "A digital product",
"blocks.inputs.payment.settings.additionalInformation.email.label": "Email:",
"blocks.inputs.payment.settings.additionalInformation.label": "Additional information",
"blocks.inputs.payment.settings.additionalInformation.name.label": "Name:",
"blocks.inputs.payment.settings.additionalInformation.phone.label": "Phone number:",
"blocks.inputs.payment.settings.address.city.label": "City:",
"blocks.inputs.payment.settings.address.country.label": "Country:",
"blocks.inputs.payment.settings.address.label": "Address",
"blocks.inputs.payment.settings.address.line.label": "Line {line}:",
"blocks.inputs.payment.settings.address.postalCode.label": "Postal code:",
"blocks.inputs.payment.settings.address.state.label": "State:",
"blocks.inputs.payment.settings.credentials.connectNew.label": "Connect new",
"blocks.inputs.payment.settings.credentials.removeCredentials.label": "Remove credentials",
"blocks.inputs.payment.settings.currency.label": "Currency:",
"blocks.inputs.payment.settings.priceAmount.label": "Price amount:",
"blocks.inputs.payment.settings.provider.label": "Provider:",
"blocks.inputs.payment.settings.stripeConfig.accountName.label": "Account name:",
"blocks.inputs.payment.settings.stripeConfig.findKeys.here.label": "here",
"blocks.inputs.payment.settings.stripeConfig.findKeys.label": "You can find your keys",
"blocks.inputs.payment.settings.stripeConfig.liveKeys.label": "Live keys:",
"blocks.inputs.payment.settings.stripeConfig.testKeys.infoText.label": "Will be used when previewing the bot.",
"blocks.inputs.payment.settings.stripeConfig.testKeys.label": "Test keys:",
"blocks.inputs.payment.settings.stripeConfig.title.label": "Connect Stripe account",
"blocks.inputs.payment.settings.successMessage.label": "Success message:",
"blocks.inputs.phone.settings.defaultCountry.label": "Default country:",
"blocks.inputs.phone.settings.international.placeholder.label": "International",
"blocks.inputs.picture.itemSettings.image.change.label": "Change image",
"blocks.inputs.picture.itemSettings.image.label": "Image:",
"blocks.inputs.picture.itemSettings.image.pick.label": "Pick an image",
"blocks.inputs.picture.itemSettings.title.label": "Title:",
"blocks.inputs.picture.settings.dynamicItems.descriptions.label": "Descriptions:",
"blocks.inputs.picture.settings.dynamicItems.images.label": "Images:",
"blocks.inputs.picture.settings.dynamicItems.label": "Dynamic items?",
"blocks.inputs.picture.settings.dynamicItems.titles.label": "Titles:",
"blocks.inputs.picture.settings.dynamicVariables.display.label": "Display",
"blocks.inputs.picture.settings.dynamicVariables.pictures.label": "pictures",
"blocks.inputs.rating.from.label": "Rate from",
"blocks.inputs.rating.settings.customIcon.label": "Custom icon?",
"blocks.inputs.rating.settings.extremelyLikely.placeholder.label": "Extremely likely",
"blocks.inputs.rating.settings.iconSVG.label": "Icon SVG:",
"blocks.inputs.rating.settings.maximum.label": "Maximum:",
"blocks.inputs.rating.settings.notLikely.placeholder.label": "Not likely at all",
"blocks.inputs.rating.settings.oneClickSubmit.infoText.label": "If enabled, the answer will be submitted as soon as the user clicks on a rating instead of showing the \"Send\" button.",
"blocks.inputs.rating.settings.oneClickSubmit.label": "One click submit",
"blocks.inputs.rating.settings.rateLabel.label": "{rate} label",
"blocks.inputs.rating.settings.type.label": "Type:",
"blocks.inputs.rating.to.label": "to",
"blocks.inputs.settings.button.label": "Button label:",
"blocks.inputs.settings.buttonText.label": "Send",
"blocks.inputs.settings.description.label": "Description:",
"blocks.inputs.settings.displayCondition.label": "Display condition",
"blocks.inputs.settings.input.filterOptions.label": "Filter the options...",
"blocks.inputs.settings.input.placeholder.label": "Input placeholder:",
"blocks.inputs.settings.isSearchable.label": "Is searchable?",
"blocks.inputs.settings.max.label": "Max:",
"blocks.inputs.settings.min.label": "Min:",
"blocks.inputs.settings.multipleChoice.label": "Multiple choice?",
"blocks.inputs.settings.placeholder.label": "Placeholder:",
"blocks.inputs.settings.retryMessage.label": "Retry message:",
"blocks.inputs.settings.saveAnswer.label": "Save the answer in a variable:",
"blocks.inputs.settings.submitButton.label": "Submit button label:",
"blocks.inputs.text.settings.longText.label": "Long text?",
"blocks.integrations.googleAnalytics.blockCard.tooltip": "Google Analytics", "blocks.integrations.googleAnalytics.blockCard.tooltip": "Google Analytics",
"blocks.integrations.googleSheets.blockCard.tooltip": "Google Sheets", "blocks.integrations.googleSheets.blockCard.tooltip": "Google Sheets",
"cancel": "Cancel", "cancel": "Cancel",
@@ -124,13 +221,16 @@
"colorPicker.colorValue.ariaLabel": "Color value", "colorPicker.colorValue.ariaLabel": "Color value",
"colorPicker.pickColor.ariaLabel": "Pick a color", "colorPicker.pickColor.ariaLabel": "Pick a color",
"confirmModal.defaultTitle": "Are you sure?", "confirmModal.defaultTitle": "Are you sure?",
"connect": "Connect",
"copied": "Copied", "copied": "Copied",
"copy": "Copy", "copy": "Copy",
"create": "Create",
"dashboard.header.settingsButton.label": "Settings & Members", "dashboard.header.settingsButton.label": "Settings & Members",
"dashboard.redirectionMessage": "You are being redirected...", "dashboard.redirectionMessage": "You are being redirected...",
"dashboard.title": "My typebots", "dashboard.title": "My typebots",
"delete": "Delete", "delete": "Delete",
"downgrade": "Downgrade", "downgrade": "Downgrade",
"duplicate": "Duplicate",
"editor.blockCard.logicBlock.tooltip.code.label": "Execute Javascript code", "editor.blockCard.logicBlock.tooltip.code.label": "Execute Javascript code",
"editor.blockCard.logicBlock.tooltip.jump.label": "Fast forward the flow to another group", "editor.blockCard.logicBlock.tooltip.jump.label": "Fast forward the flow to another group",
"editor.blockCard.logicBlock.tooltip.typebotLink.label": "Link and jump to another typebot", "editor.blockCard.logicBlock.tooltip.typebotLink.label": "Link and jump to another typebot",
@@ -238,6 +338,7 @@
"folders.typebotButton.live": "Live", "folders.typebotButton.live": "Live",
"folders.typebotButton.showMoreOptions": "Show more options", "folders.typebotButton.showMoreOptions": "Show more options",
"folders.typebotButton.unpublish": "Unpublish", "folders.typebotButton.unpublish": "Unpublish",
"help": "Help",
"pending": "Pending", "pending": "Pending",
"preview.restartButton.label": "Restart", "preview.restartButton.label": "Restart",
"publish.error.label": "Error while publishing typebot", "publish.error.label": "Error while publishing typebot",
@@ -258,6 +359,7 @@
"remove": "Remove", "remove": "Remove",
"rename": "Rename", "rename": "Rename",
"save": "Save", "save": "Save",
"select": "Select",
"share.button.label": "Share", "share.button.label": "Share",
"share.button.popover.ariaLabel": "Open share popover", "share.button.popover.ariaLabel": "Open share popover",
"share.button.popover.collaboratorsFetch.error.label": "Couldn't fetch collaborators", "share.button.popover.collaboratorsFetch.error.label": "Couldn't fetch collaborators",
@@ -355,6 +457,11 @@
"upgrade": "Upgrade", "upgrade": "Upgrade",
"variables.button.searchInput.placeholder": "Search for a variable", "variables.button.searchInput.placeholder": "Search for a variable",
"variables.button.tooltip": "Insert a variable", "variables.button.tooltip": "Insert a variable",
"variables.remove": "Remove variable",
"variables.rename": "Rename variable",
"variables.search": "Search for a variable",
"variables.select": "Select a variable",
"variables.set": "Set",
"video.aspectRatioInput.label": "Aspect ratio", "video.aspectRatioInput.label": "Aspect ratio",
"video.aspectRatioInput.moreInfoTooltip": "Example: \"16/9\" or \"9/16\"", "video.aspectRatioInput.moreInfoTooltip": "Example: \"16/9\" or \"9/16\"",
"video.maxWidthInput.label": "Max width", "video.maxWidthInput.label": "Max width",

View File

@@ -30,6 +30,7 @@
"account.preferences.graphNavigation.trackpad.label": "Trackpad", "account.preferences.graphNavigation.trackpad.label": "Trackpad",
"account.preferences.language.heading": "Idioma", "account.preferences.language.heading": "Idioma",
"account.preferences.language.tooltip": "As traduções ainda não estão completas. É um trabalho em andamento. \uD83E\uDD13", "account.preferences.language.tooltip": "As traduções ainda não estão completas. É um trabalho em andamento. \uD83E\uDD13",
"add": "Adicionar",
"analytics.completionRateLabel": "Taxa de conclusão", "analytics.completionRateLabel": "Taxa de conclusão",
"analytics.notAvailableLabel": "Não disponível", "analytics.notAvailableLabel": "Não disponível",
"analytics.startsLabel": "Inícios", "analytics.startsLabel": "Inícios",
@@ -112,7 +113,103 @@
"billing.usage.heading": "Uso", "billing.usage.heading": "Uso",
"billing.usage.unlimited": "Ilimitado", "billing.usage.unlimited": "Ilimitado",
"blocks.bubbles.embed.blockCard.tooltip": "Incorporar pdf, iframe, website...", "blocks.bubbles.embed.blockCard.tooltip": "Incorporar pdf, iframe, website...",
"blocks.inputs.button.addItem.ariaLabel": "Adicionar item",
"blocks.inputs.button.buttonSettings.addComparisonButton.label": "Adicionar comparação",
"blocks.inputs.button.buttonSettings.displayCondition.infoText.label": "Apenas exiba esse item se a condição for cumprida.",
"blocks.inputs.button.buttonSettings.displayCondition.selectOperator.label": "Selecione um operador",
"blocks.inputs.button.clickToEdit.label": "Clique para editar",
"blocks.inputs.button.conditionContent.if.label": "SE",
"blocks.inputs.button.default.label": "Padrão",
"blocks.inputs.button.else.label": "Senão",
"blocks.inputs.button.openSettings.ariaLabel": "Abrir configurações",
"blocks.inputs.button.settings.dynamicData.infoText.label": "Se definido, os botões serão exibidos dinamicamente com base no que a variável contém.",
"blocks.inputs.button.settings.dynamicData.label": "Dados dinâmicos:",
"blocks.inputs.button.variables.buttons.label": "botões",
"blocks.inputs.button.variables.display.label": "Exibir",
"blocks.inputs.date.placeholder.label": "Escolha uma data...",
"blocks.inputs.date.settings.format.example.label": "Ex:",
"blocks.inputs.date.settings.format.label": "Formatar:",
"blocks.inputs.date.settings.from.label": "Legenda de:",
"blocks.inputs.date.settings.isRange.label": "Intervalo?",
"blocks.inputs.date.settings.to.label": "Legenda até:",
"blocks.inputs.date.settings.toInputValue.label": "Até:",
"blocks.inputs.date.settings.withTime.label": "Com tempo?",
"blocks.inputs.file.collectMultiple.label": "Coletar arquivos",
"blocks.inputs.file.collectSingle.label": "Coletar arquivo",
"blocks.inputs.file.settings.allowMultiple.label": "Permitir múltiplos arquivos?",
"blocks.inputs.file.settings.clear.label": "Legenda do botão de limpar:",
"blocks.inputs.file.settings.required.label": "Obrigatório?",
"blocks.inputs.file.settings.saveMultipleUpload.label": "Salve as URLs carregadas numa variável:",
"blocks.inputs.file.settings.saveSingleUpload.label": "Salve a URL carregada numa variável:",
"blocks.inputs.file.settings.skip.label": "Legenda do botão de pular:",
"blocks.inputs.fileUpload.blockCard.tooltip": "Anexar arquivos", "blocks.inputs.fileUpload.blockCard.tooltip": "Anexar arquivos",
"blocks.inputs.number.settings.step.label": "Intervalo",
"blocks.inputs.payment.collect.label": "Coletar",
"blocks.inputs.payment.placeholder.label": "Configure...",
"blocks.inputs.payment.settings.account.label": "Conta:",
"blocks.inputs.payment.settings.accountText.label": "conta {provider}",
"blocks.inputs.payment.settings.additionalInformation.description.placeholder.label": "Um produto digital",
"blocks.inputs.payment.settings.additionalInformation.email.label": "Email:",
"blocks.inputs.payment.settings.additionalInformation.label": "Informação adicional",
"blocks.inputs.payment.settings.additionalInformation.name.label": "Nome:",
"blocks.inputs.payment.settings.additionalInformation.phone.label": "Número de telefone:",
"blocks.inputs.payment.settings.address.city.label": "Cidade:",
"blocks.inputs.payment.settings.address.country.label": "País:",
"blocks.inputs.payment.settings.address.label": "Endereço",
"blocks.inputs.payment.settings.address.line.label": "Linha {line}:",
"blocks.inputs.payment.settings.address.postalCode.label": "Código postal:",
"blocks.inputs.payment.settings.address.state.label": "Estado:",
"blocks.inputs.payment.settings.credentials.connectNew.label": "Nova conexão",
"blocks.inputs.payment.settings.credentials.removeCredentials.label": "Remover credenciais",
"blocks.inputs.payment.settings.currency.label": "Moeda:",
"blocks.inputs.payment.settings.priceAmount.label": "Valor do preço:",
"blocks.inputs.payment.settings.provider.label": "Provedor:",
"blocks.inputs.payment.settings.stripeConfig.accountName.label": "Nome da conta:",
"blocks.inputs.payment.settings.stripeConfig.findKeys.here.label": "aqui",
"blocks.inputs.payment.settings.stripeConfig.findKeys.label": "Você pode encontrar suas chaves",
"blocks.inputs.payment.settings.stripeConfig.liveKeys.label": "Chaves de produção:",
"blocks.inputs.payment.settings.stripeConfig.testKeys.infoText.label": "Será usada durante a visualização do bot.",
"blocks.inputs.payment.settings.stripeConfig.testKeys.label": "Chaves teste:",
"blocks.inputs.payment.settings.stripeConfig.title.label": "Conectar conta Stripe",
"blocks.inputs.payment.settings.successMessage.label": "Mensagem de sucesso:",
"blocks.inputs.phone.settings.defaultCountry.label": "País padrão:",
"blocks.inputs.phone.settings.international.placeholder.label": "Internacional",
"blocks.inputs.picture.itemSettings.image.change.label": "Alterar imagem",
"blocks.inputs.picture.itemSettings.image.label": "Imagem",
"blocks.inputs.picture.itemSettings.image.pick.label": "Escolha uma imagem",
"blocks.inputs.picture.itemSettings.title.label": "Título:",
"blocks.inputs.picture.settings.dynamicItems.descriptions.label": "Descrições:",
"blocks.inputs.picture.settings.dynamicItems.images.label": "Imagens:",
"blocks.inputs.picture.settings.dynamicItems.label": "Itens dinâmicos?",
"blocks.inputs.picture.settings.dynamicItems.titles.label": "Títulos:",
"blocks.inputs.picture.settings.dynamicVariables.display.label": "Exibir",
"blocks.inputs.picture.settings.dynamicVariables.pictures.label": "imagens",
"blocks.inputs.rating.from.label": "Avalie de",
"blocks.inputs.rating.settings.customIcon.label": "Customizar ícone?",
"blocks.inputs.rating.settings.extremelyLikely.placeholder.label": "Extremamente provável",
"blocks.inputs.rating.settings.iconSVG.label": "Ícone SVG:",
"blocks.inputs.rating.settings.maximum.label": "Máximo:",
"blocks.inputs.rating.settings.notLikely.placeholder.label": "Nada provável",
"blocks.inputs.rating.settings.oneClickSubmit.infoText.label": "Se habilitado, a resposta será enviada no momento em que o usuário clicar em uma avaliação ao invés de mostrar o botão de envio",
"blocks.inputs.rating.settings.oneClickSubmit.label": "Enviar em um clique",
"blocks.inputs.rating.settings.rateLabel.label": "Legenda {rate}",
"blocks.inputs.rating.settings.type.label": "Tipo:",
"blocks.inputs.rating.to.label": "a",
"blocks.inputs.settings.button.label": "Legenda do botão:",
"blocks.inputs.settings.buttonText.label": "Enviar",
"blocks.inputs.settings.description.label": "Descrição:",
"blocks.inputs.settings.displayCondition.label": "Condição de exibição",
"blocks.inputs.settings.input.filterOptions.label": "Filtre as opções...",
"blocks.inputs.settings.input.placeholder.label": "Placeholder do input:",
"blocks.inputs.settings.isSearchable.label": "É pesquisável?",
"blocks.inputs.settings.max.label": "Máx:",
"blocks.inputs.settings.min.label": "Mín:",
"blocks.inputs.settings.multipleChoice.label": "Múltipla escolha?",
"blocks.inputs.settings.placeholder.label": "Placeholder:",
"blocks.inputs.settings.retryMessage.label": "Mensagem de nova tentativa:",
"blocks.inputs.settings.saveAnswer.label": "Salve a resposta em uma variável:",
"blocks.inputs.settings.submitButton.label": "Legenda do botão de envio:",
"blocks.inputs.text.settings.longText.label": "Texto longo?",
"blocks.integrations.googleAnalytics.blockCard.tooltip": "Google Analytics", "blocks.integrations.googleAnalytics.blockCard.tooltip": "Google Analytics",
"blocks.integrations.googleSheets.blockCard.tooltip": "Google Sheets", "blocks.integrations.googleSheets.blockCard.tooltip": "Google Sheets",
"cancel": "Cancelar", "cancel": "Cancelar",
@@ -124,13 +221,16 @@
"colorPicker.colorValue.ariaLabel": "Valor da cor", "colorPicker.colorValue.ariaLabel": "Valor da cor",
"colorPicker.pickColor.ariaLabel": "Escolha uma cor", "colorPicker.pickColor.ariaLabel": "Escolha uma cor",
"confirmModal.defaultTitle": "Tem certeza?", "confirmModal.defaultTitle": "Tem certeza?",
"connect": "Conectar",
"copied": "Copiado", "copied": "Copiado",
"copy": "Copiar", "copy": "Copiar",
"create": "Criar",
"dashboard.header.settingsButton.label": "Configurações & Membros", "dashboard.header.settingsButton.label": "Configurações & Membros",
"dashboard.redirectionMessage": "Você está sendo redirecionado...", "dashboard.redirectionMessage": "Você está sendo redirecionado...",
"dashboard.title": "Meus typebots", "dashboard.title": "Meus typebots",
"delete": "Apagar", "delete": "Apagar",
"downgrade": "Downgrade", "downgrade": "Downgrade",
"duplicate": "Duplicar",
"editor.blockCard.logicBlock.tooltip.code.label": "Executar código Javascript", "editor.blockCard.logicBlock.tooltip.code.label": "Executar código Javascript",
"editor.blockCard.logicBlock.tooltip.jump.label": "Encaminhar fluxo para outro grupo", "editor.blockCard.logicBlock.tooltip.jump.label": "Encaminhar fluxo para outro grupo",
"editor.blockCard.logicBlock.tooltip.typebotLink.label": "Link e salte para outro typebot", "editor.blockCard.logicBlock.tooltip.typebotLink.label": "Link e salte para outro typebot",
@@ -238,6 +338,7 @@
"folders.typebotButton.live": "Live", "folders.typebotButton.live": "Live",
"folders.typebotButton.showMoreOptions": "Mostrar mais opções", "folders.typebotButton.showMoreOptions": "Mostrar mais opções",
"folders.typebotButton.unpublish": "Despublicar", "folders.typebotButton.unpublish": "Despublicar",
"help": "Ajuda",
"pending": "Pendente", "pending": "Pendente",
"preview.restartButton.label": "Reiniciar", "preview.restartButton.label": "Reiniciar",
"publish.error.label": "Erro durante a publicação do typebot", "publish.error.label": "Erro durante a publicação do typebot",
@@ -257,6 +358,7 @@
"remove": "Remover", "remove": "Remover",
"rename": "Renomear", "rename": "Renomear",
"save": "Salvar", "save": "Salvar",
"select": "Selecionar",
"share.button.label": "Compartilhar", "share.button.label": "Compartilhar",
"share.button.popover.ariaLabel": "Abrir popover de compartilhamento", "share.button.popover.ariaLabel": "Abrir popover de compartilhamento",
"share.button.popover.collaboratorsFetch.error.label": "Falha ao buscar os colaboradores", "share.button.popover.collaboratorsFetch.error.label": "Falha ao buscar os colaboradores",
@@ -348,6 +450,11 @@
"upgrade": "Upgrade", "upgrade": "Upgrade",
"variables.button.searchInput.placeholder": "Procure por uma variável", "variables.button.searchInput.placeholder": "Procure por uma variável",
"variables.button.tooltip": "Insira uma variável", "variables.button.tooltip": "Insira uma variável",
"variables.remove": "Remova a variável",
"variables.rename": "Renomeie a variável",
"variables.search": "Pesquise uma variável",
"variables.select": "Selecione uma variável",
"variables.set": "Definir",
"video.aspectRatioInput.label": "Proporção", "video.aspectRatioInput.label": "Proporção",
"video.aspectRatioInput.moreInfoTooltip": "Exemplo: \"16/9\" ou \"9/16\"", "video.aspectRatioInput.moreInfoTooltip": "Exemplo: \"16/9\" ou \"9/16\"",
"video.maxWidthInput.label": "Largura máxima", "video.maxWidthInput.label": "Largura máxima",