Support I18n (#243)

* feat: support i18n

* feat: wip support i18n

* feat: complete space translation

* feat: complete page translation

* feat: update space translation

* feat: update workspace translation

* feat: update group translation

* feat: update workspace translation

* feat: update page translation

* feat: update user translation

* chore: update pnpm-lock

* feat: add query translation

* refactor: merge to single file

* chore: remove necessary code

* feat: save language to BE

* fix: only load current language

* feat: save language to locale column

* fix: cleanups

* add language menu to preferences page

* new translations

* translate editor

* Translate editor placeholders

* translate space selection component

---------

Co-authored-by: Philip Okugbe <phil@docmost.com>
Co-authored-by: Philip Okugbe <16838612+Philipinho@users.noreply.github.com>
This commit is contained in:
lleohao
2025-01-04 21:17:17 +08:00
committed by GitHub
parent 290b7d9d94
commit 670ee64179
119 changed files with 1672 additions and 649 deletions

View File

@ -5,6 +5,7 @@ import { useAddSpaceMemberMutation } from "@/features/space/queries/space-query.
import { MultiMemberSelect } from "@/features/space/components/multi-member-select.tsx";
import { SpaceMemberRole } from "@/features/space/components/space-member-role.tsx";
import { SpaceRole } from "@/lib/types.ts";
import { useTranslation } from "react-i18next";
interface AddSpaceMemberModalProps {
spaceId: string;
@ -12,6 +13,7 @@ interface AddSpaceMemberModalProps {
export default function AddSpaceMembersModal({
spaceId,
}: AddSpaceMemberModalProps) {
const { t } = useTranslation();
const [opened, { open, close }] = useDisclosure(false);
const [memberIds, setMemberIds] = useState<string[]>([]);
const [role, setRole] = useState<string>(SpaceRole.WRITER);
@ -48,8 +50,8 @@ export default function AddSpaceMembersModal({
return (
<>
<Button onClick={open}>Add space members</Button>
<Modal opened={opened} onClose={close} title="Add space members">
<Button onClick={open}>{t("Add space members")}</Button>
<Modal opened={opened} onClose={close} title={t("Add space members")}>
<Divider size="xs" mb="xs" />
<Stack>
@ -57,13 +59,13 @@ export default function AddSpaceMembersModal({
<SpaceMemberRole
onSelect={handleRoleSelection}
defaultRole={role}
label="Select role"
label={t("Select role")}
/>
</Stack>
<Group justify="flex-end" mt="md">
<Button onClick={handleSubmit} type="submit">
Add
{t("Add")}
</Button>
</Group>
</Modal>

View File

@ -6,6 +6,7 @@ import { useNavigate } from "react-router-dom";
import { useCreateSpaceMutation } from "@/features/space/queries/space-query.ts";
import { computeSpaceSlug } from "@/lib";
import { getSpaceUrl } from "@/lib/config.ts";
import { useTranslation } from "react-i18next";
const formSchema = z.object({
name: z.string().trim().min(2).max(50),
@ -23,6 +24,7 @@ const formSchema = z.object({
type FormValues = z.infer<typeof formSchema>;
export function CreateSpaceForm() {
const { t } = useTranslation();
const createSpaceMutation = useCreateSpaceMutation();
const navigate = useNavigate();
@ -74,8 +76,8 @@ export function CreateSpaceForm() {
<TextInput
withAsterisk
id="name"
label="Space name"
placeholder="e.g Product Team"
label={t("Space name")}
placeholder={t("e.g Product Team")}
variant="filled"
{...form.getInputProps("name")}
/>
@ -83,16 +85,16 @@ export function CreateSpaceForm() {
<TextInput
withAsterisk
id="slug"
label="Space slug"
placeholder="e.g product"
label={t("Space slug")}
placeholder={t("e.g product")}
variant="filled"
{...form.getInputProps("slug")}
/>
<Textarea
id="description"
label="Space description"
placeholder="e.g Space for product team"
label={t("Space description")}
placeholder={t("e.g Space for product team")}
variant="filled"
autosize
minRows={2}
@ -102,7 +104,7 @@ export function CreateSpaceForm() {
</Stack>
<Group justify="flex-end" mt="md">
<Button type="submit">Create</Button>
<Button type="submit">{t("Create")}</Button>
</Group>
</form>
</Box>

View File

@ -1,15 +1,17 @@
import { Button, Divider, Modal } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { CreateSpaceForm } from "@/features/space/components/create-space-form.tsx";
import { useTranslation } from "react-i18next";
export default function CreateSpaceModal() {
const { t } = useTranslation();
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Button onClick={open}>Create space</Button>
<Button onClick={open}>{t("Create space")}</Button>
<Modal opened={opened} onClose={close} title="Create space">
<Modal opened={opened} onClose={close} title={t("Create space")}>
<Divider size="xs" mb="xs" />
<CreateSpaceForm />
</Modal>

View File

@ -4,6 +4,7 @@ import { useForm, zodResolver } from "@mantine/form";
import * as z from "zod";
import { useUpdateSpaceMutation } from "@/features/space/queries/space-query.ts";
import { ISpace } from "@/features/space/types/space.types.ts";
import { useTranslation } from "react-i18next";
const formSchema = z.object({
name: z.string().min(2).max(50),
@ -24,6 +25,7 @@ interface EditSpaceFormProps {
readOnly?: boolean;
}
export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
const { t } = useTranslation();
const updateSpaceMutation = useUpdateSpaceMutation();
const form = useForm<FormValues>({
@ -65,8 +67,8 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
<Stack>
<TextInput
id="name"
label="Name"
placeholder="e.g Sales"
label={t("Name")}
placeholder={t("e.g Sales")}
variant="filled"
readOnly={readOnly}
{...form.getInputProps("name")}
@ -74,7 +76,7 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
<TextInput
id="slug"
label="Slug"
label={t("Slug")}
variant="filled"
readOnly={readOnly}
{...form.getInputProps("slug")}
@ -82,8 +84,8 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
<Textarea
id="description"
label="Description"
placeholder="e.g Space for sales team to collaborate"
label={t("Description")}
placeholder={t("e.g Space for sales team to collaborate")}
variant="filled"
readOnly={readOnly}
autosize
@ -96,7 +98,7 @@ export function EditSpaceForm({ space, readOnly }: EditSpaceFormProps) {
{!readOnly && (
<Group justify="flex-end" mt="md">
<Button type="submit" disabled={!form.isDirty()}>
Save
{t("Save")}
</Button>
</Group>
)}

View File

@ -6,6 +6,7 @@ import { useSearchSuggestionsQuery } from "@/features/search/queries/search-quer
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
import { IUser } from "@/features/user/types/user.types.ts";
import { IconGroupCircle } from "@/components/icons/icon-people-circle.tsx";
import { useTranslation } from "react-i18next";
interface MultiMemberSelectProps {
onChange: (value: string[]) => void;
@ -30,6 +31,7 @@ const renderMultiSelectOption: MultiSelectProps["renderOption"] = ({
);
export function MultiMemberSelect({ onChange }: MultiMemberSelectProps) {
const { t } = useTranslation();
const [searchValue, setSearchValue] = useState("");
const [debouncedQuery] = useDebouncedValue(searchValue, 500);
const { data: suggestion, isLoading } = useSearchSuggestionsQuery({
@ -83,14 +85,14 @@ export function MultiMemberSelect({ onChange }: MultiMemberSelectProps) {
const updatedUserGroups = mergeItemsIntoGroups(
data,
userItems,
"Select a user",
t("Select a user"),
);
// Merge group items into groups
const finalData = mergeItemsIntoGroups(
updatedUserGroups,
groupItems,
"Select a group",
t("Select a group"),
);
setData(finalData);
@ -103,8 +105,8 @@ export function MultiMemberSelect({ onChange }: MultiMemberSelectProps) {
renderOption={renderMultiSelectOption}
hidePickedOptions
maxDropdownHeight={300}
label="Add members"
placeholder="Search for users and groups"
label={t("Add members")}
placeholder={t("Search for users and groups")}
searchable
searchValue={searchValue}
onSearchChange={setSearchValue}

View File

@ -9,6 +9,7 @@ import {
SpaceCaslAction,
SpaceCaslSubject,
} from "@/features/space/permissions/permissions.type.ts";
import { useTranslation } from "react-i18next";
interface SpaceSettingsModalProps {
spaceId: string;
@ -17,11 +18,12 @@ interface SpaceSettingsModalProps {
}
export default function SpaceSettingsModal({
spaceId,
opened,
onClose,
}: SpaceSettingsModalProps) {
const {data: space, isLoading} = useSpaceQuery(spaceId);
spaceId,
opened,
onClose,
}: SpaceSettingsModalProps) {
const { t } = useTranslation();
const { data: space, isLoading } = useSpaceQuery(spaceId);
const spaceRules = space?.membership?.permissions;
const spaceAbility = useSpaceAbility(spaceRules);
@ -50,10 +52,10 @@ export default function SpaceSettingsModal({
<Tabs defaultValue="members">
<Tabs.List>
<Tabs.Tab fw={500} value="general">
Settings
{t("Settings")}
</Tabs.Tab>
<Tabs.Tab fw={500} value="members">
Members
{t("Members")}
</Tabs.Tab>
</Tabs.List>

View File

@ -1,8 +1,9 @@
import { useEffect, useState } from 'react';
import { useDebouncedValue } from '@mantine/hooks';
import { Avatar, Group, Select, SelectProps, Text } from '@mantine/core';
import { useGetSpacesQuery } from '@/features/space/queries/space-query.ts';
import { ISpace } from '../../types/space.types';
import { useEffect, useState } from "react";
import { useDebouncedValue } from "@mantine/hooks";
import { Avatar, Group, Select, SelectProps, Text } from "@mantine/core";
import { useGetSpacesQuery } from "@/features/space/queries/space-query.ts";
import { ISpace } from "../../types/space.types";
import { useTranslation } from "react-i18next";
interface SpaceSelectProps {
onChange: (value: string) => void;
@ -10,7 +11,7 @@ interface SpaceSelectProps {
label?: string;
}
const renderSelectOption: SelectProps['renderOption'] = ({ option }) => (
const renderSelectOption: SelectProps["renderOption"] = ({ option }) => (
<Group gap="sm">
<Avatar color="initials" variant="filled" name={option.label} size={20} />
<div>
@ -20,7 +21,8 @@ const renderSelectOption: SelectProps['renderOption'] = ({ option }) => (
);
export function SpaceSelect({ onChange, label, value }: SpaceSelectProps) {
const [searchValue, setSearchValue] = useState('');
const { t } = useTranslation();
const [searchValue, setSearchValue] = useState("");
const [debouncedQuery] = useDebouncedValue(searchValue, 500);
const { data: spaces, isLoading } = useGetSpacesQuery({
query: debouncedQuery,
@ -41,7 +43,7 @@ export function SpaceSelect({ onChange, label, value }: SpaceSelectProps) {
const filteredSpaceData = spaceData.filter(
(user) =>
!data.find((existingUser) => existingUser.value === user.value)
!data.find((existingUser) => existingUser.value === user.value),
);
setData((prevData) => [...prevData, ...filteredSpaceData]);
}
@ -53,14 +55,14 @@ export function SpaceSelect({ onChange, label, value }: SpaceSelectProps) {
renderOption={renderSelectOption}
maxDropdownHeight={300}
//label={label || 'Select space'}
placeholder="Search for spaces"
placeholder={t("Search for spaces")}
searchable
searchValue={searchValue}
onSearchChange={setSearchValue}
clearable
variant="filled"
onChange={onChange}
nothingFoundMessage="No space found"
nothingFoundMessage={t("No space found")}
limit={50}
checkIconPosition="right"
comboboxProps={{ width: 300, withinPortal: false }}

View File

@ -35,10 +35,12 @@ import {
SpaceCaslSubject,
} from "@/features/space/permissions/permissions.type.ts";
import PageImportModal from "@/features/page/components/page-import-modal.tsx";
import { useTranslation } from "react-i18next";
import { SwitchSpace } from "./switch-space";
import ExportModal from "@/components/common/export-modal";
export function SpaceSidebar() {
const { t } = useTranslation();
const [tree] = useAtom(treeApiAtom);
const location = useLocation();
const [opened, { open: openSettings, close: closeSettings }] =
@ -89,7 +91,7 @@ export function SpaceSidebar() {
className={classes.menuItemIcon}
stroke={2}
/>
<span>Overview</span>
<span>{t("Overview")}</span>
</div>
</UnstyledButton>
@ -100,7 +102,7 @@ export function SpaceSidebar() {
className={classes.menuItemIcon}
stroke={2}
/>
<span>Search</span>
<span>{t("Search")}</span>
</div>
</UnstyledButton>
@ -111,7 +113,7 @@ export function SpaceSidebar() {
className={classes.menuItemIcon}
stroke={2}
/>
<span>Space settings</span>
<span>{t("Space settings")}</span>
</div>
</UnstyledButton>
@ -129,7 +131,7 @@ export function SpaceSidebar() {
className={classes.menuItemIcon}
stroke={2}
/>
<span>New page</span>
<span>{t("New page")}</span>
</div>
</UnstyledButton>
)}
@ -139,7 +141,7 @@ export function SpaceSidebar() {
<div className={clsx(classes.section, classes.sectionPages)}>
<Group className={classes.pagesHeader} justify="space-between">
<Text size="xs" fw={500} c="dimmed">
Pages
{t("Pages")}
</Text>
{spaceAbility.can(
@ -149,12 +151,12 @@ export function SpaceSidebar() {
<Group gap="xs">
<SpaceMenu spaceId={space.id} onSpaceSettings={openSettings} />
<Tooltip label="Create page" withArrow position="right">
<Tooltip label={t("Create page")} withArrow position="right">
<ActionIcon
variant="default"
size={18}
onClick={handleCreatePage}
aria-label="Create page"
aria-label={t("Create page")}
>
<IconPlus />
</ActionIcon>
@ -191,6 +193,7 @@ interface SpaceMenuProps {
onSpaceSettings: () => void;
}
function SpaceMenu({ spaceId, onSpaceSettings }: SpaceMenuProps) {
const { t } = useTranslation();
const [importOpened, { open: openImportModal, close: closeImportModal }] =
useDisclosure(false);
const [exportOpened, { open: openExportModal, close: closeExportModal }] =
@ -201,11 +204,15 @@ function SpaceMenu({ spaceId, onSpaceSettings }: SpaceMenuProps) {
<Menu width={200} shadow="md" withArrow>
<Menu.Target>
<Tooltip
label="Import pages & space settings"
label={t("Import pages & space settings")}
withArrow
position="top"
>
<ActionIcon variant="default" size={18} aria-label="Space menu">
<ActionIcon
variant="default"
size={18}
aria-label={t("Space menu")}
>
<IconDots />
</ActionIcon>
</Tooltip>
@ -216,7 +223,7 @@ function SpaceMenu({ spaceId, onSpaceSettings }: SpaceMenuProps) {
onClick={openImportModal}
leftSection={<IconArrowDown size={16} />}
>
Import pages
{t("Import pages")}
</Menu.Item>
<Menu.Item
@ -232,7 +239,7 @@ function SpaceMenu({ spaceId, onSpaceSettings }: SpaceMenuProps) {
onClick={onSpaceSettings}
leftSection={<IconSettings size={16} />}
>
Space settings
{t("Space settings")}
</Menu.Item>
</Menu.Dropdown>
</Menu>

View File

@ -5,12 +5,14 @@ import { Button, Divider, Group, Text } from '@mantine/core';
import DeleteSpaceModal from './delete-space-modal';
import { useDisclosure } from "@mantine/hooks";
import ExportModal from "@/components/common/export-modal.tsx";
import { useTranslation } from "react-i18next";
interface SpaceDetailsProps {
spaceId: string;
readOnly?: boolean;
}
export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
const { t } = useTranslation();
const { data: space, isLoading } = useSpaceQuery(spaceId);
const [exportOpened, { open: openExportModal, close: closeExportModal }] =
useDisclosure(false);
@ -20,7 +22,7 @@ export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
{space && (
<div>
<Text my="md" fw={600}>
Details
{t("Details")}
</Text>
<EditSpaceForm space={space} readOnly={readOnly} />
@ -33,12 +35,12 @@ export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
<div>
<Text size="md">Export space</Text>
<Text size="sm" c="dimmed">
Export all pages and attachments in this space
{t("Export all pages and attachments in this space.")}
</Text>
</div>
<Button onClick={openExportModal}>
Export
{t("Export")}
</Button>
</Group>
@ -46,9 +48,9 @@ export default function SpaceDetails({ spaceId, readOnly }: SpaceDetailsProps) {
<Group justify="space-between" wrap="nowrap" gap="xl">
<div>
<Text size="md">Delete space</Text>
<Text size="md">{t("Delete space")}</Text>
<Text size="sm" c="dimmed">
Delete this space with all its pages and data.
{t("Delete this space with all its pages and data.")}
</Text>
</div>

View File

@ -5,8 +5,10 @@ import { getSpaceUrl } from "@/lib/config.ts";
import { Link } from "react-router-dom";
import classes from "./space-grid.module.css";
import { formatMemberCount } from "@/lib";
import { useTranslation } from "react-i18next";
export default function SpaceGrid() {
const { t } = useTranslation();
const { data, isLoading } = useGetSpacesQuery();
const cards = data?.items.map((space, index) => (
@ -33,7 +35,7 @@ export default function SpaceGrid() {
</Text>
<Text c="dimmed" size="xs" fw={700} mt="md">
{formatMemberCount(space.memberCount)}
{formatMemberCount(space.memberCount, t)}
</Text>
</Card>
));
@ -41,7 +43,7 @@ export default function SpaceGrid() {
return (
<>
<Text fz="sm" fw={500} mb={"md"}>
Spaces you belong to
{t("Spaces you belong to")}
</Text>
<SimpleGrid cols={{ base: 1, xs: 2, sm: 3 }}>{cards}</SimpleGrid>

View File

@ -3,8 +3,10 @@ import { IconClockHour3 } from "@tabler/icons-react";
import RecentChanges from "@/components/common/recent-changes.tsx";
import { useParams } from "react-router-dom";
import { useGetSpaceBySlugQuery } from "@/features/space/queries/space-query.ts";
import { useTranslation } from "react-i18next";
export default function SpaceHomeTabs() {
const { t } = useTranslation();
const { spaceSlug } = useParams();
const { data: space } = useGetSpaceBySlugQuery(spaceSlug);
@ -13,7 +15,7 @@ export default function SpaceHomeTabs() {
<Tabs.List>
<Tabs.Tab value="recent" leftSection={<IconClockHour3 size={18} />}>
<Text size="sm" fw={500}>
Recently updated
{t("Recently updated")}
</Text>
</Tabs.Tab>
</Tabs.List>

View File

@ -1,13 +1,15 @@
import {Table, Group, Text, Avatar} from "@mantine/core";
import React, {useState} from "react";
import {useGetSpacesQuery} from "@/features/space/queries/space-query.ts";
import { Table, Group, Text, Avatar } from "@mantine/core";
import React, { useState } from "react";
import { useGetSpacesQuery } from "@/features/space/queries/space-query.ts";
import SpaceSettingsModal from "@/features/space/components/settings-modal.tsx";
import {useDisclosure} from "@mantine/hooks";
import {formatMemberCount} from "@/lib";
import { useDisclosure } from "@mantine/hooks";
import { formatMemberCount } from "@/lib";
import { useTranslation } from "react-i18next";
export default function SpaceList() {
const {data, isLoading} = useGetSpacesQuery();
const [opened, {open, close}] = useDisclosure(false);
const { t } = useTranslation();
const { data, isLoading } = useGetSpacesQuery();
const [opened, { open, close }] = useDisclosure(false);
const [selectedSpaceId, setSelectedSpaceId] = useState<string>(null);
const handleClick = (spaceId: string) => {
@ -22,8 +24,8 @@ export default function SpaceList() {
<Table highlightOnHover verticalSpacing="sm">
<Table.Thead>
<Table.Tr>
<Table.Th>Space</Table.Th>
<Table.Th>Members</Table.Th>
<Table.Th>{t("Space")}</Table.Th>
<Table.Th>{t("Members")}</Table.Th>
</Table.Tr>
</Table.Thead>
@ -31,7 +33,7 @@ export default function SpaceList() {
{data?.items.map((space, index) => (
<Table.Tr
key={index}
style={{cursor: "pointer"}}
style={{ cursor: "pointer" }}
onClick={() => handleClick(space.id)}
>
<Table.Td>
@ -51,9 +53,10 @@ export default function SpaceList() {
</div>
</Group>
</Table.Td>
<Table.Td>
<Text size="sm" style={{whiteSpace: 'nowrap'}}>{formatMemberCount(space.memberCount)}</Text>
<Text size="sm" style={{ whiteSpace: "nowrap" }}>
{formatMemberCount(space.memberCount, t)}
</Text>
</Table.Td>
</Table.Tr>
))}

View File

@ -1,21 +1,22 @@
import {Group, Table, Text, Menu, ActionIcon} from "@mantine/core";
import { Group, Table, Text, Menu, ActionIcon } from "@mantine/core";
import React from "react";
import {IconDots} from "@tabler/icons-react";
import {modals} from "@mantine/modals";
import {CustomAvatar} from "@/components/ui/custom-avatar.tsx";
import { IconDots } from "@tabler/icons-react";
import { modals } from "@mantine/modals";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
import {
useChangeSpaceMemberRoleMutation,
useRemoveSpaceMemberMutation,
useSpaceMembersQuery,
} from "@/features/space/queries/space-query.ts";
import {IconGroupCircle} from "@/components/icons/icon-people-circle.tsx";
import {IRemoveSpaceMember} from "@/features/space/types/space.types.ts";
import { IconGroupCircle } from "@/components/icons/icon-people-circle.tsx";
import { IRemoveSpaceMember } from "@/features/space/types/space.types.ts";
import RoleSelectMenu from "@/components/ui/role-select-menu.tsx";
import {
getSpaceRoleLabel,
spaceRoleData,
} from "@/features/space/types/space-role-data.ts";
import {formatMemberCount} from "@/lib";
import { formatMemberCount } from "@/lib";
import { useTranslation } from "react-i18next";
type MemberType = "user" | "group";
@ -25,10 +26,12 @@ interface SpaceMembersProps {
}
export default function SpaceMembersList({
spaceId,
readOnly,
}: SpaceMembersProps) {
const {data, isLoading} = useSpaceMembersQuery(spaceId);
spaceId,
readOnly,
}: SpaceMembersProps) {
const { t } = useTranslation();
const { data, isLoading } = useSpaceMembersQuery(spaceId);
const removeSpaceMember = useRemoveSpaceMemberMutation();
const changeSpaceMemberRoleMutation = useChangeSpaceMemberRoleMutation();
@ -79,16 +82,17 @@ export default function SpaceMembersList({
const openRemoveModal = (memberId: string, type: MemberType) =>
modals.openConfirmModal({
title: "Remove space member",
title: t("Remove space member"),
children: (
<Text size="sm">
Are you sure you want to remove this user from the space? The user
will lose all access to this space.
{t(
"Are you sure you want to remove this user from the space? The user will lose all access to this space.",
)}
</Text>
),
centered: true,
labels: {confirm: "Remove", cancel: "Cancel"},
confirmProps: {color: "red"},
labels: { confirm: t("Remove"), cancel: t("Cancel") },
confirmProps: { color: "red" },
onConfirm: () => onRemove(memberId, type),
});
@ -99,8 +103,8 @@ export default function SpaceMembersList({
<Table verticalSpacing={8}>
<Table.Thead>
<Table.Tr>
<Table.Th>Member</Table.Th>
<Table.Th>Role</Table.Th>
<Table.Th>{t("Member")}</Table.Th>
<Table.Th>{t("Role")}</Table.Th>
<Table.Th></Table.Th>
</Table.Tr>
</Table.Thead>
@ -117,7 +121,7 @@ export default function SpaceMembersList({
/>
)}
{member.type === "group" && <IconGroupCircle/>}
{member.type === "group" && <IconGroupCircle />}
<div>
<Text fz="sm" fw={500}>
@ -127,7 +131,7 @@ export default function SpaceMembersList({
{member.type == "user" && member?.email}
{member.type == "group" &&
`Group - ${formatMemberCount(member?.memberCount)}`}
`${t("Group")} - ${formatMemberCount(member?.memberCount, t)}`}
</Text>
</div>
</Group>
@ -161,7 +165,7 @@ export default function SpaceMembersList({
>
<Menu.Target>
<ActionIcon variant="subtle" c="gray">
<IconDots size={20} stroke={2}/>
<IconDots size={20} stroke={2} />
</ActionIcon>
</Menu.Target>
@ -171,7 +175,7 @@ export default function SpaceMembersList({
openRemoveModal(member.id, member.type)
}
>
Remove space member
{t("Remove space member")}
</Menu.Item>
</Menu.Dropdown>
</Menu>