Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🌐 Translate workspace #528

Merged
merged 2 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/builder/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const nextConfig = {
],
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr', 'pt'],
locales: ['en', 'fr', 'pt', 'de'],
},
experimental: {
outputFileTracingRoot: path.join(__dirname, '../../'),
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"@trpc/server": "10.27.3",
"@typebot.io/emails": "workspace:*",
"@typebot.io/js": "workspace:*",
"@typebot.io/next-international": "0.3.8",
"@typebot.io/react": "workspace:*",
"@udecode/plate-basic-marks": "21.1.5",
"@udecode/plate-common": "^21.1.5",
Expand Down Expand Up @@ -71,6 +70,7 @@
"minio": "7.1.1",
"next": "13.4.3",
"next-auth": "4.22.1",
"next-international": "^0.4.1",
"nextjs-cors": "^2.1.2",
"nodemailer": "6.9.2",
"nprogress": "0.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -52,7 +54,7 @@ export const AddMemberForm = ({
return (
<HStack as="form" onSubmit={handleInvitationSubmit}>
<Input
placeholder="[email protected]"
placeholder={scopedT('inviteInput.placeholder')}
name="inviteEmail"
value={invitationEmail}
onChange={(e) => setInvitationEmail(e.target.value)}
Expand All @@ -73,7 +75,7 @@ export const AddMemberForm = ({
type="submit"
isDisabled={isLoading || isLocked || invitationEmail === ''}
>
Invite
{scopedT('inviteButton.label')}
</Button>
</HStack>
)
Expand Down
60 changes: 33 additions & 27 deletions apps/builder/src/features/workspace/components/MemberItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,6 +38,7 @@ export const MemberItem = ({
onDeleteClick,
onSelectNewRole,
}: Props) => {
const t = useI18n()
const handleAdminClick = () => onSelectNewRole(WorkspaceRole.ADMIN)
const handleMemberClick = () => onSelectNewRole(WorkspaceRole.MEMBER)

Expand Down Expand Up @@ -65,7 +67,7 @@ export const MemberItem = ({
{convertWorkspaceRoleToReadable(WorkspaceRole.MEMBER)}
</MenuItem>
<MenuItem color="red.500" onClick={onDeleteClick}>
Remove
{t('remove')}
</MenuItem>
</MenuList>
)}
Expand All @@ -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>
</HStack>
<HStack flexShrink={0}>
{isGuest && (
<Tag color="gray.400" data-testid="tag">
Pending
</Tag>
)}
<Tag data-testid="tag">{tag}</Tag>
<Tag data-testid="tag">{tag}</Tag>
</HStack>
</HStack>
</HStack>
)
)
}
11 changes: 4 additions & 7 deletions apps/builder/src/features/workspace/components/MembersList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 ?? []
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
Expand All @@ -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}
Expand All @@ -71,20 +73,22 @@ 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}
onConfirm={onConfirm}
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"
Expand Down
Loading