🌐 Translate workspace (#528)
This commit is contained in:
@@ -1,15 +1,36 @@
|
||||
import { Stack, Heading, useColorMode } from '@chakra-ui/react'
|
||||
import {
|
||||
Stack,
|
||||
Heading,
|
||||
useColorMode,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Button,
|
||||
HStack,
|
||||
} from '@chakra-ui/react'
|
||||
import { GraphNavigation } from '@typebot.io/prisma'
|
||||
import React, { useEffect } from 'react'
|
||||
import { GraphNavigationRadioGroup } from './GraphNavigationRadioGroup'
|
||||
import { AppearanceRadioGroup } from './AppearanceRadioGroup'
|
||||
import { useUser } from '../hooks/useUser'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import { useChangeLocale, useCurrentLocale, useScopedI18n } from '@/locales'
|
||||
import { ChevronDownIcon } from '@/components/icons'
|
||||
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
|
||||
|
||||
const localeHumanReadable = {
|
||||
en: 'English',
|
||||
fr: 'Français',
|
||||
de: 'Deutsch',
|
||||
pt: 'Português',
|
||||
} as const
|
||||
|
||||
export const UserPreferencesForm = () => {
|
||||
const scopedT = useScopedI18n('account.preferences')
|
||||
const { colorMode, setColorMode } = useColorMode()
|
||||
const { user, updateUser } = useUser()
|
||||
const changeLocale = useChangeLocale()
|
||||
const currentLocale = useCurrentLocale()
|
||||
|
||||
useEffect(() => {
|
||||
if (!user?.graphNavigation)
|
||||
@@ -25,8 +46,40 @@ export const UserPreferencesForm = () => {
|
||||
updateUser({ preferredAppAppearance: value })
|
||||
}
|
||||
|
||||
const updateLocale = (locale: keyof typeof localeHumanReadable) => () => {
|
||||
changeLocale(locale)
|
||||
document.cookie = `NEXT_LOCALE=${locale}; path=/; max-age=31536000`
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack spacing={12}>
|
||||
<HStack spacing={4}>
|
||||
<Heading size="md">{scopedT('language.heading')}</Heading>
|
||||
<Menu>
|
||||
<MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
|
||||
{localeHumanReadable[currentLocale]}
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
{Object.keys(localeHumanReadable).map((locale) => (
|
||||
<MenuItem
|
||||
key={locale}
|
||||
onClick={updateLocale(
|
||||
locale as keyof typeof localeHumanReadable
|
||||
)}
|
||||
>
|
||||
{
|
||||
localeHumanReadable[
|
||||
locale as keyof typeof localeHumanReadable
|
||||
]
|
||||
}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
{currentLocale !== 'en' && (
|
||||
<MoreInfoTooltip>{scopedT('language.tooltip')}</MoreInfoTooltip>
|
||||
)}
|
||||
</HStack>
|
||||
<Stack spacing={6}>
|
||||
<Heading size="md">{scopedT('graphNavigation.heading')}</Heading>
|
||||
<GraphNavigationRadioGroup
|
||||
|
||||
@@ -13,6 +13,7 @@ import { WorkspaceInvitation, WorkspaceRole } from '@typebot.io/prisma'
|
||||
import { FormEvent, useState } from 'react'
|
||||
import { Member } from '../types'
|
||||
import { sendInvitationQuery } from '../queries/sendInvitationQuery'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
workspaceId: string
|
||||
@@ -28,6 +29,7 @@ export const AddMemberForm = ({
|
||||
isLoading,
|
||||
isLocked,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('workspace.membersList')
|
||||
const [invitationEmail, setInvitationEmail] = useState('')
|
||||
const [invitationRole, setInvitationRole] = useState<WorkspaceRole>(
|
||||
WorkspaceRole.MEMBER
|
||||
@@ -52,7 +54,7 @@ export const AddMemberForm = ({
|
||||
return (
|
||||
<HStack as="form" onSubmit={handleInvitationSubmit}>
|
||||
<Input
|
||||
placeholder="colleague@company.com"
|
||||
placeholder={scopedT('inviteInput.placeholder')}
|
||||
name="inviteEmail"
|
||||
value={invitationEmail}
|
||||
onChange={(e) => setInvitationEmail(e.target.value)}
|
||||
@@ -73,7 +75,7 @@ export const AddMemberForm = ({
|
||||
type="submit"
|
||||
isDisabled={isLoading || isLocked || invitationEmail === ''}
|
||||
>
|
||||
Invite
|
||||
{scopedT('inviteButton.label')}
|
||||
</Button>
|
||||
</HStack>
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { WorkspaceRole } from '@typebot.io/prisma'
|
||||
import React from 'react'
|
||||
import { convertWorkspaceRoleToReadable } from './AddMemberForm'
|
||||
import { useI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
image?: string
|
||||
@@ -37,6 +38,7 @@ export const MemberItem = ({
|
||||
onDeleteClick,
|
||||
onSelectNewRole,
|
||||
}: Props) => {
|
||||
const t = useI18n()
|
||||
const handleAdminClick = () => onSelectNewRole(WorkspaceRole.ADMIN)
|
||||
const handleMemberClick = () => onSelectNewRole(WorkspaceRole.MEMBER)
|
||||
|
||||
@@ -65,7 +67,7 @@ export const MemberItem = ({
|
||||
{convertWorkspaceRoleToReadable(WorkspaceRole.MEMBER)}
|
||||
</MenuItem>
|
||||
<MenuItem color="red.500" onClick={onDeleteClick}>
|
||||
Remove
|
||||
{t('remove')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
)}
|
||||
@@ -85,32 +87,36 @@ export const MemberIdentityContent = ({
|
||||
image?: string
|
||||
isGuest?: boolean
|
||||
email: string
|
||||
}) => (
|
||||
<HStack justifyContent="space-between" maxW="full" p="2">
|
||||
<HStack minW={0} spacing="4">
|
||||
<Avatar name={name} src={image} size="sm" />
|
||||
<Stack spacing={0} minW="0">
|
||||
{name && (
|
||||
<Text textAlign="left" fontSize="15px">
|
||||
{name}
|
||||
}) => {
|
||||
const t = useI18n()
|
||||
|
||||
return (
|
||||
<HStack justifyContent="space-between" maxW="full" p="2">
|
||||
<HStack minW={0} spacing="4">
|
||||
<Avatar name={name} src={image} size="sm" />
|
||||
<Stack spacing={0} minW="0">
|
||||
{name && (
|
||||
<Text textAlign="left" fontSize="15px">
|
||||
{name}
|
||||
</Text>
|
||||
)}
|
||||
<Text
|
||||
color="gray.500"
|
||||
fontSize={name ? '14px' : 'inherit'}
|
||||
noOfLines={1}
|
||||
>
|
||||
{email}
|
||||
</Text>
|
||||
</Stack>
|
||||
</HStack>
|
||||
<HStack flexShrink={0}>
|
||||
{isGuest && (
|
||||
<Tag color="gray.400" data-testid="tag">
|
||||
{t('pending')}
|
||||
</Tag>
|
||||
)}
|
||||
<Text
|
||||
color="gray.500"
|
||||
fontSize={name ? '14px' : 'inherit'}
|
||||
noOfLines={1}
|
||||
>
|
||||
{email}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Tag data-testid="tag">{tag}</Tag>
|
||||
</HStack>
|
||||
</HStack>
|
||||
<HStack flexShrink={0}>
|
||||
{isGuest && (
|
||||
<Tag color="gray.400" data-testid="tag">
|
||||
Pending
|
||||
</Tag>
|
||||
)}
|
||||
<Tag data-testid="tag">{tag}</Tag>
|
||||
</HStack>
|
||||
</HStack>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@ import { updateInvitationQuery } from '../queries/updateInvitationQuery'
|
||||
import { updateMemberQuery } from '../queries/updateMemberQuery'
|
||||
import { Member } from '../types'
|
||||
import { useWorkspace } from '../WorkspaceProvider'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
export const MembersList = () => {
|
||||
const scopedT = useScopedI18n('workspace.membersList')
|
||||
const { user } = useUser()
|
||||
const { workspace, currentRole } = useWorkspace()
|
||||
const { members, invitations, isLoading, mutate } = useMembers({
|
||||
@@ -102,16 +104,11 @@ export const MembersList = () => {
|
||||
return (
|
||||
<Stack w="full" spacing={3}>
|
||||
{!canInviteNewMember && (
|
||||
<UnlockPlanAlertInfo
|
||||
contentLabel={`
|
||||
Upgrade your plan to work with more team members, and unlock awesome
|
||||
power features 🚀
|
||||
`}
|
||||
/>
|
||||
<UnlockPlanAlertInfo contentLabel={scopedT('unlockBanner.label')} />
|
||||
)}
|
||||
{isDefined(seatsLimit) && (
|
||||
<Heading fontSize="2xl">
|
||||
Members{' '}
|
||||
{scopedT('title')}{' '}
|
||||
{seatsLimit === -1 ? '' : `(${currentMembersCount}/${seatsLimit})`}
|
||||
</Heading>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@/components/icons'
|
||||
import { PlanTag } from '@/features/billing/components/PlanTag'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
@@ -31,6 +32,7 @@ export const WorkspaceDropdown = ({
|
||||
onLogoutClick,
|
||||
onCreateNewWorkspaceClick,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('workspace.dropdown')
|
||||
const { data } = trpc.workspace.listWorkspaces.useQuery()
|
||||
|
||||
const workspaces = data?.workspaces ?? []
|
||||
@@ -70,14 +72,14 @@ export const WorkspaceDropdown = ({
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem onClick={onCreateNewWorkspaceClick} icon={<PlusIcon />}>
|
||||
New workspace
|
||||
{scopedT('newButton.label')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={onLogoutClick}
|
||||
icon={<LogOutIcon />}
|
||||
color="orange.500"
|
||||
>
|
||||
Log out
|
||||
{scopedT('logoutButton.label')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
@@ -12,8 +12,10 @@ import React from 'react'
|
||||
import { EditableEmojiOrImageIcon } from '@/components/EditableEmojiOrImageIcon'
|
||||
import { useWorkspace } from '../WorkspaceProvider'
|
||||
import { TextInput } from '@/components/inputs'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||
const scopedT = useScopedI18n('workspace.settings')
|
||||
const { workspace, workspaces, updateWorkspace, deleteCurrentWorkspace } =
|
||||
useWorkspace()
|
||||
|
||||
@@ -34,7 +36,7 @@ export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||
return (
|
||||
<Stack spacing="6" w="full">
|
||||
<FormControl>
|
||||
<FormLabel>Icon</FormLabel>
|
||||
<FormLabel>{scopedT('icon.title')}</FormLabel>
|
||||
<Flex>
|
||||
{workspace && (
|
||||
<EditableEmojiOrImageIcon
|
||||
@@ -48,7 +50,7 @@ export const WorkspaceSettingsForm = ({ onClose }: { onClose: () => void }) => {
|
||||
</FormControl>
|
||||
{workspace && (
|
||||
<TextInput
|
||||
label="Name:"
|
||||
label={scopedT('name.label')}
|
||||
withVariableButton={false}
|
||||
defaultValue={workspace?.name}
|
||||
onChange={handleNameChange}
|
||||
@@ -71,11 +73,12 @@ const DeleteWorkspaceButton = ({
|
||||
workspaceName: string
|
||||
onConfirm: () => Promise<void>
|
||||
}) => {
|
||||
const scopedT = useScopedI18n('workspace.settings')
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
return (
|
||||
<>
|
||||
<Button colorScheme="red" variant="outline" onClick={onOpen}>
|
||||
Delete workspace
|
||||
{scopedT('deleteButton.label')}
|
||||
</Button>
|
||||
<ConfirmModal
|
||||
isOpen={isOpen}
|
||||
@@ -83,8 +86,9 @@ const DeleteWorkspaceButton = ({
|
||||
onClose={onClose}
|
||||
message={
|
||||
<Text>
|
||||
Are you sure you want to delete {workspaceName} workspace? All its
|
||||
folders, typebots and results will be deleted forever.
|
||||
{scopedT('deleteButton.confirmMessage', {
|
||||
workspaceName,
|
||||
})}
|
||||
</Text>
|
||||
}
|
||||
confirmButtonLabel="Delete"
|
||||
|
||||
@@ -24,6 +24,7 @@ import packageJson from '../../../../../../package.json'
|
||||
import { UserPreferencesForm } from '@/features/account/components/UserPreferencesForm'
|
||||
import { MyAccountForm } from '@/features/account/components/MyAccountForm'
|
||||
import { BillingSettingsLayout } from '@/features/billing/components/BillingSettingsLayout'
|
||||
import { useScopedI18n } from '@/locales'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
@@ -45,6 +46,7 @@ export const WorkspaceSettingsModal = ({
|
||||
workspace,
|
||||
onClose,
|
||||
}: Props) => {
|
||||
const scopedT = useScopedI18n('workspace.settings.modal')
|
||||
const { currentRole } = useWorkspace()
|
||||
const [selectedTab, setSelectedTab] = useState<SettingsTab>('my-account')
|
||||
|
||||
@@ -56,7 +58,7 @@ export const WorkspaceSettingsModal = ({
|
||||
<ModalContent minH="600px" flexDir="row">
|
||||
<Stack
|
||||
spacing={8}
|
||||
w="200px"
|
||||
w="180px"
|
||||
py="6"
|
||||
borderRightWidth={1}
|
||||
justifyContent="space-between"
|
||||
@@ -80,7 +82,7 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
My account
|
||||
{scopedT('menu.myAccount.label')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={selectedTab === 'user-settings' ? 'solid' : 'ghost'}
|
||||
@@ -90,12 +92,12 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
Preferences
|
||||
{scopedT('menu.preferences.label')}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text pl="4" color="gray.500">
|
||||
Workspace
|
||||
{scopedT('menu.workspace.label')}
|
||||
</Text>
|
||||
{canEditWorkspace && (
|
||||
<Button
|
||||
@@ -114,7 +116,7 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
Settings
|
||||
{scopedT('menu.settings.label')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -125,7 +127,7 @@ export const WorkspaceSettingsModal = ({
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
>
|
||||
Members
|
||||
{scopedT('menu.members.label')}
|
||||
</Button>
|
||||
{canEditWorkspace && (
|
||||
<Button
|
||||
@@ -135,8 +137,10 @@ export const WorkspaceSettingsModal = ({
|
||||
size="sm"
|
||||
justifyContent="flex-start"
|
||||
pl="4"
|
||||
overflow="scroll"
|
||||
className="hide-scrollbar"
|
||||
>
|
||||
Billing & Usage
|
||||
{scopedT('menu.billingAndUsage.label')}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -144,7 +148,7 @@ export const WorkspaceSettingsModal = ({
|
||||
|
||||
<Flex justify="center" pt="10">
|
||||
<Text color="gray.500" fontSize="xs">
|
||||
Version: {packageJson.version}
|
||||
{scopedT('menu.version.label', { version: packageJson.version })}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user