localize room settings

This commit is contained in:
heaven 2026-04-14 21:27:03 +03:00
parent d120a9a933
commit becdbc3d3a
33 changed files with 836 additions and 291 deletions

View file

@ -607,5 +607,228 @@
"existing_space": "Existing Space", "existing_space": "Existing Space",
"add_room": "Add Room", "add_room": "Add Room",
"existing_room": "Existing Room" "existing_room": "Existing Room"
},
"RoomSettings": {
"general": "General",
"members": "Members",
"permissions": "Permissions",
"emojis_stickers": "Emojis & Stickers",
"developer_tools": "Developer Tools",
"profile": "Profile",
"edit": "Edit",
"unknown": "Unknown",
"avatar": "Avatar",
"upload": "Upload",
"reset": "Reset",
"remove": "Remove",
"name": "Name",
"topic": "Topic",
"save": "Save",
"cancel": "Cancel",
"options": "Options",
"addresses": "Addresses",
"advanced_options": "Advanced Options",
"space_access": "Space Access",
"room_access": "Room Access",
"space_access_desc": "Change how people can join the space.",
"room_access_desc": "Change how people can join the room.",
"join_invite_only": "Invite Only",
"join_knock_invite": "Knock & Invite",
"join_space_members_or_knock": "Space Members or Knock",
"join_space_members": "Space Members",
"join_public": "Public",
"join_unsupported": "Unsupported",
"history_visibility": "Message History Visibility",
"history_visibility_desc": "Changes to history visibility will only apply to future messages and will not affect existing history.",
"visibility_after_invite": "After Invite",
"visibility_after_join": "After Join",
"visibility_all_messages": "All Messages",
"visibility_all_messages_guests": "All Messages (Guests)",
"room_encryption": "Room Encryption",
"encryption_enabled_desc": "Messages in this room are protected by end-to-end encryption.",
"encryption_disabled_desc": "Once enabled, encryption cannot be disabled!",
"enabled": "Enabled",
"enable": "Enable",
"enable_encryption": "Enable Encryption",
"enable_encryption_confirm": "Are you sure? Once enabled, encryption cannot be disabled!",
"enable_e2e_encryption": "Enable E2E Encryption",
"publish_to_directory": "Publish to Directory",
"publish_space_desc": "List the space in the public directory to make it discoverable by others.",
"publish_room_desc": "List the room in the public directory to make it discoverable by others.",
"published_addresses": "Published Addresses",
"published_addresses_desc": "If access is <b>Public</b>, Published addresses will be used to join by anyone.",
"no_addresses": "No Addresses",
"no_addresses_hint": "To publish an address, it needs to be set as a local address first",
"main": "Main",
"unset_main": "Unset Main",
"set_main": "Set Main",
"address_in_use": "Address is already in use!",
"published": "Published",
"unpublish": "Unpublish",
"publish": "Publish",
"delete": "Delete",
"selected_count": "{{count}} Selected",
"local_addresses": "Local Addresses",
"local_addresses_desc": "Set local address so users can join through your homeserver.",
"collapse": "Collapse",
"expand": "Expand",
"loading": "Loading...",
"space_upgrade": "Space Upgrade",
"room_upgrade": "Room Upgrade",
"upgrade": "Upgrade",
"action_irreversible": "This action is irreversible!",
"upgrade_space": "Upgrade Space",
"upgrade_room": "Upgrade Room",
"space_replaced": "This space has been replaced!",
"room_replaced": "This room has been replaced!",
"current_version": "Current version: {{version}}.",
"old_space": "Old Space",
"old_room": "Old Room",
"open_new_space": "Open New Space",
"open_new_room": "Open New Room",
"members_count": "{{count}} Members",
"search": "Search",
"no_results": "No Results",
"results_count": "{{count}} Results",
"scroll_to_top": "Scroll to Top",
"no_membership_members": "No \"{{filter}}\" Members",
"filter_joined": "Joined",
"filter_invited": "Invited",
"filter_left": "Left",
"filter_kicked": "Kicked",
"filter_banned": "Banned",
"sort_a_to_z": "A to Z",
"sort_z_to_a": "Z to A",
"sort_newest": "Newest",
"sort_oldest": "Oldest",
"perm_messages": "Messages",
"perm_send_messages": "Send Messages",
"perm_send_stickers": "Send Stickers",
"perm_send_reactions": "Send Reactions",
"perm_ping_room": "Ping @room",
"perm_pin_messages": "Pin Messages",
"perm_other_message_events": "Other Message Events",
"perm_calls": "Calls",
"perm_join_call": "Join Call",
"perm_moderation": "Moderation",
"perm_invite": "Invite",
"perm_kick": "Kick",
"perm_ban": "Ban",
"perm_delete_others_messages": "Delete Others' Messages",
"perm_delete_self_messages": "Delete Self Messages",
"perm_room_overview": "Room Overview",
"perm_room_avatar": "Room Avatar",
"perm_room_name": "Room Name",
"perm_room_topic": "Room Topic",
"perm_settings": "Settings",
"perm_change_room_access": "Change Room Access",
"perm_publish_address": "Publish Address",
"perm_change_all_permission": "Change All Permissions",
"perm_edit_power_levels": "Edit Power Levels",
"perm_enable_encryption": "Enable Encryption",
"perm_history_visibility": "History Visibility",
"perm_upgrade_room": "Upgrade Room",
"perm_other_settings": "Other Settings",
"perm_other": "Other",
"perm_manage_emojis_stickers": "Manage Emojis & Stickers",
"perm_change_server_acls": "Change Server ACLs",
"perm_modify_widgets": "Modify Widgets",
"founders": "Founders",
"founders_desc": "Founding members have all permissions and can only be changed during a room upgrade.",
"power_levels": "Power Levels",
"power_levels_desc": "Manage and customize incremental power levels for users.",
"new_power_level": "New Power Level",
"new_power_level_desc": "Create a new power level.",
"power_level_placeholder": "Bot",
"create": "Create",
"color": "Color",
"pick": "Pick",
"power": "Power",
"icon": "Icon",
"import": "Import",
"undo": "Undo",
"used_power_level": "Used Power Level",
"used_power_level_desc": "You have to remove its use before you can delete it.",
"changes_saved": "Changes saved! Apply when ready.",
"failed_to_apply": "Failed to apply changes! Please try again.",
"apply_changes": "Apply Changes",
"and_above": "& Above",
"users": "Users",
"default_power": "Default Power",
"default_power_desc": "Default power level for all users.",
"packs": "Packs",
"new_pack": "New Pack",
"new_pack_desc": "Add your own emoji and sticker pack to use in room.",
"no_packs": "No Packs",
"no_packs_desc": "There are no emoji or sticker packs to display at the moment.",
"view": "View",
"failed_to_remove_packs": "Failed to remove packs! Please try again.",
"delete_selected_packs": "Delete selected packs. ({{count}} selected)",
"enable_developer_tools": "Enable Developer Tools",
"room_id": "Room ID",
"room_id_desc": "Copy room ID to clipboard.",
"copy": "Copy",
"data": "Data",
"new_message_event": "New Message Event",
"new_message_event_desc": "Create and send a new message event within the room.",
"compose": "Compose",
"room_state": "Room State",
"room_state_desc": "State events of the room.",
"events": "Events",
"total": "Total: {{count}}",
"add_new": "Add New",
"default_key": "Default",
"account_data": "Account Data",
"account_data_desc": "Private personalization data stored within room.",
"state_event": "State Event",
"json_content": "JSON Content",
"state_event_type": "State Event Type",
"message_event_type": "Message Event Type",
"send": "Send",
"state_key_optional": "State Key (Optional)",
"pack": "Pack",
"images_usage": "Images Usage",
"images_usage_desc": "Select how the images are being used: as emojis, as stickers, or as both.",
"images": "Images",
"upload_images": "Upload Images",
"upload_images_desc": "Select images from your storage to upload them in pack.",
"select": "Select",
"pack_avatar": "Pack Avatar",
"attribution": "Attribution",
"shortcode": "Shortcode:",
"body": "Body:",
"usage_both": "Both",
"usage_sticker": "Sticker",
"usage_emoji": "Emoji",
"power_goku": "Goku",
"power_manager": "Manager",
"power_founder": "Founder",
"power_admin": "Admin",
"power_moderator": "Moderator",
"power_member": "Member",
"power_muted": "Muted",
"power_team": "Team"
} }
} }

View file

@ -609,5 +609,228 @@
"existing_space": "Существующее пространство", "existing_space": "Существующее пространство",
"add_room": "Добавить комнату", "add_room": "Добавить комнату",
"existing_room": "Существующая комната" "existing_room": "Существующая комната"
},
"RoomSettings": {
"general": "Основные",
"members": "Участники",
"permissions": "Права доступа",
"emojis_stickers": "Эмодзи и стикеры",
"developer_tools": "Инструменты разработчика",
"profile": "Профиль",
"edit": "Редактировать",
"unknown": "Неизвестно",
"avatar": "Аватар",
"upload": "Загрузить",
"reset": "Сбросить",
"remove": "Удалить",
"name": "Название",
"topic": "Тема",
"save": "Сохранить",
"cancel": "Отмена",
"options": "Настройки",
"addresses": "Адреса",
"advanced_options": "Дополнительные настройки",
"space_access": "Доступ к пространству",
"room_access": "Доступ к комнате",
"space_access_desc": "Изменить способ вступления в пространство.",
"room_access_desc": "Изменить способ вступления в комнату.",
"join_invite_only": "Только по приглашению",
"join_knock_invite": "Запрос и приглашение",
"join_space_members_or_knock": "Участники пространства или запрос",
"join_space_members": "Участники пространства",
"join_public": "Публичный",
"join_unsupported": "Не поддерживается",
"history_visibility": "Видимость истории сообщений",
"history_visibility_desc": "Изменения видимости истории применяются только к новым сообщениям и не затрагивают существующую историю.",
"visibility_after_invite": "После приглашения",
"visibility_after_join": "После вступления",
"visibility_all_messages": "Все сообщения",
"visibility_all_messages_guests": "Все сообщения (гости)",
"room_encryption": "Шифрование комнаты",
"encryption_enabled_desc": "Сообщения в этой комнате защищены сквозным шифрованием.",
"encryption_disabled_desc": "После включения шифрование невозможно отключить!",
"enabled": "Включено",
"enable": "Включить",
"enable_encryption": "Включить шифрование",
"enable_encryption_confirm": "Вы уверены? После включения шифрование невозможно отключить!",
"enable_e2e_encryption": "Включить E2E-шифрование",
"publish_to_directory": "Показывать в поиске",
"publish_space_desc": "Сделать пространство видимым в общем списке, чтобы другие пользователи могли его найти.",
"publish_room_desc": "Сделать комнату видимой в общем списке, чтобы другие пользователи могли её найти.",
"published_addresses": "Опубликованные адреса",
"published_addresses_desc": "Если доступ <b>публичный</b>, опубликованные адреса будут использоваться для присоединения.",
"no_addresses": "Нет адресов",
"no_addresses_hint": "Чтобы опубликовать адрес, его сначала нужно задать как локальный",
"main": "Основной",
"unset_main": "Снять основной",
"set_main": "Сделать основным",
"address_in_use": "Адрес уже занят!",
"published": "Опубликован",
"unpublish": "Снять публикацию",
"publish": "Опубликовать",
"delete": "Удалить",
"selected_count": "Выбрано: {{count}}",
"local_addresses": "Локальные адреса",
"local_addresses_desc": "Задайте локальный адрес, чтобы пользователи могли присоединиться через ваш сервер.",
"collapse": "Свернуть",
"expand": "Развернуть",
"loading": "Загрузка...",
"space_upgrade": "Обновление пространства",
"room_upgrade": "Обновление комнаты",
"upgrade": "Обновить",
"action_irreversible": "Это действие необратимо!",
"upgrade_space": "Обновить пространство",
"upgrade_room": "Обновить комнату",
"space_replaced": "Это пространство было заменено!",
"room_replaced": "Эта комната была заменена!",
"current_version": "Текущая версия: {{version}}.",
"old_space": "Старое пространство",
"old_room": "Старая комната",
"open_new_space": "Открыть новое пространство",
"open_new_room": "Открыть новую комнату",
"members_count": "{{count}} участников",
"search": "Поиск",
"no_results": "Ничего не найдено",
"results_count": "{{count}} результатов",
"scroll_to_top": "Наверх",
"no_membership_members": "Нет участников «{{filter}}»",
"filter_joined": "Вступившие",
"filter_invited": "Приглашённые",
"filter_left": "Вышедшие",
"filter_kicked": "Исключённые",
"filter_banned": "Забаненные",
"sort_a_to_z": "А — Я",
"sort_z_to_a": "Я — А",
"sort_newest": "Новые",
"sort_oldest": "Старые",
"perm_messages": "Сообщения",
"perm_send_messages": "Отправка сообщений",
"perm_send_stickers": "Отправка стикеров",
"perm_send_reactions": "Отправка реакций",
"perm_ping_room": "Упоминание @room",
"perm_pin_messages": "Закрепление сообщений",
"perm_other_message_events": "Прочие события сообщений",
"perm_calls": "Звонки",
"perm_join_call": "Присоединиться к звонку",
"perm_moderation": "Модерация",
"perm_invite": "Приглашение",
"perm_kick": "Исключение",
"perm_ban": "Бан",
"perm_delete_others_messages": "Удаление чужих сообщений",
"perm_delete_self_messages": "Удаление своих сообщений",
"perm_room_overview": "Обзор комнаты",
"perm_room_avatar": "Аватар комнаты",
"perm_room_name": "Название комнаты",
"perm_room_topic": "Тема комнаты",
"perm_settings": "Настройки",
"perm_change_room_access": "Изменение доступа к комнате",
"perm_publish_address": "Публикация адреса",
"perm_change_all_permission": "Изменение всех прав",
"perm_edit_power_levels": "Редактирование уровней власти",
"perm_enable_encryption": "Включение шифрования",
"perm_history_visibility": "Видимость истории",
"perm_upgrade_room": "Обновление комнаты",
"perm_other_settings": "Прочие настройки",
"perm_other": "Прочее",
"perm_manage_emojis_stickers": "Управление эмодзи и стикерами",
"perm_change_server_acls": "Изменение ACL серверов",
"perm_modify_widgets": "Изменение виджетов",
"founders": "Основатели",
"founders_desc": "Основатели имеют все права. Изменить их состав можно только при обновлении комнаты.",
"power_levels": "Уровни власти",
"power_levels_desc": "Управление и настройка уровней власти для пользователей.",
"new_power_level": "Новый уровень власти",
"power_level_placeholder": "Бот",
"new_power_level_desc": "Создать новый уровень власти.",
"create": "Создать",
"color": "Цвет",
"pick": "Выбрать",
"power": "Уровень",
"icon": "Иконка",
"import": "Импорт",
"undo": "Отменить",
"used_power_level": "Используемый уровень власти",
"used_power_level_desc": "Необходимо убрать его использование, прежде чем удалить.",
"changes_saved": "Изменения сохранены! Примените, когда будете готовы.",
"failed_to_apply": "Не удалось применить изменения! Попробуйте ещё раз.",
"apply_changes": "Применить изменения",
"and_above": "и выше",
"users": "Пользователи",
"default_power": "Уровень по умолчанию",
"default_power_desc": "Уровень власти по умолчанию для всех пользователей.",
"packs": "Паки",
"new_pack": "Новый пак",
"new_pack_desc": "Добавьте свой пак эмодзи и стикеров для использования в комнате.",
"no_packs": "Нет паков",
"no_packs_desc": "На данный момент нет паков эмодзи или стикеров для отображения.",
"view": "Открыть",
"failed_to_remove_packs": "Не удалось удалить паки! Попробуйте ещё раз.",
"delete_selected_packs": "Удалить выбранные паки. (Выбрано: {{count}})",
"enable_developer_tools": "Включить инструменты разработчика",
"room_id": "ID комнаты",
"room_id_desc": "Скопировать ID комнаты в буфер обмена.",
"copy": "Копировать",
"data": "Данные",
"new_message_event": "Новое событие сообщения",
"new_message_event_desc": "Создать и отправить новое событие сообщения в комнату.",
"compose": "Создать",
"room_state": "Состояние комнаты",
"room_state_desc": "State-события комнаты.",
"events": "События",
"total": "Всего: {{count}}",
"add_new": "Добавить",
"default_key": "По умолчанию",
"account_data": "Данные аккаунта",
"account_data_desc": "Персональные данные, хранящиеся в комнате.",
"state_event": "State-событие",
"json_content": "JSON-содержимое",
"state_event_type": "Тип state-события",
"message_event_type": "Тип события сообщения",
"send": "Отправить",
"state_key_optional": "State Key (необязательно)",
"pack": "Пак",
"images_usage": "Использование изображений",
"images_usage_desc": "Выберите, как используются изображения: как эмодзи, как стикеры или как и то, и другое.",
"images": "Изображения",
"upload_images": "Загрузить изображения",
"upload_images_desc": "Выберите изображения из хранилища для загрузки в пак.",
"select": "Выбрать",
"pack_avatar": "Аватар пака",
"attribution": "Авторство",
"shortcode": "Шорткод:",
"body": "Описание:",
"usage_both": "Оба",
"usage_sticker": "Стикер",
"usage_emoji": "Эмодзи",
"power_goku": "Гоку",
"power_manager": "Менеджер",
"power_founder": "Основатель",
"power_admin": "Админ",
"power_moderator": "Модератор",
"power_member": "Участник",
"power_muted": "Без голоса",
"power_team": "Команда"
} }
} }

View file

@ -14,6 +14,7 @@ import {
Scroll, Scroll,
config, config,
} from 'folds'; } from 'folds';
import { useTranslation } from 'react-i18next';
import { MatrixError } from 'matrix-js-sdk'; import { MatrixError } from 'matrix-js-sdk';
import { Cursor } from '../plugins/text-area'; import { Cursor } from '../plugins/text-area';
import { syntaxErrorPosition } from '../utils/dom'; import { syntaxErrorPosition } from '../utils/dom';
@ -47,6 +48,7 @@ function AccountDataEdit({
onCancel, onCancel,
onSave, onSave,
}: AccountDataEditProps) { }: AccountDataEditProps) {
const { t } = useTranslation();
const alive = useAlive(); const alive = useAlive();
const textAreaRef = useRef<HTMLTextAreaElement>(null); const textAreaRef = useRef<HTMLTextAreaElement>(null);
@ -121,7 +123,7 @@ function AccountDataEdit({
aria-disabled={submitting} aria-disabled={submitting}
> >
<Box shrink="No" direction="Column" gap="100"> <Box shrink="No" direction="Column" gap="100">
<Text size="L400">Account Data</Text> <Text size="L400">{t('RoomSettings.account_data')}</Text>
<Box gap="300"> <Box gap="300">
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
<Input <Input
@ -142,7 +144,7 @@ function AccountDataEdit({
disabled={submitting} disabled={submitting}
before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />} before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />}
> >
<Text size="B400">Save</Text> <Text size="B400">{t('RoomSettings.save')}</Text>
</Button> </Button>
<Button <Button
variant="Secondary" variant="Secondary"
@ -153,7 +155,7 @@ function AccountDataEdit({
onClick={onCancel} onClick={onCancel}
disabled={submitting} disabled={submitting}
> >
<Text size="B400">Cancel</Text> <Text size="B400">{t('RoomSettings.cancel')}</Text>
</Button> </Button>
</Box> </Box>
@ -165,7 +167,7 @@ function AccountDataEdit({
</Box> </Box>
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Box shrink="No"> <Box shrink="No">
<Text size="L400">JSON Content</Text> <Text size="L400">{t('RoomSettings.json_content')}</Text>
</Box> </Box>
<TextAreaComponent <TextAreaComponent
ref={textAreaRef} ref={textAreaRef}
@ -198,6 +200,7 @@ type AccountDataViewProps = {
onEdit: () => void; onEdit: () => void;
}; };
function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps) { function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps) {
const { t } = useTranslation();
return ( return (
<Box <Box
direction="Column" direction="Column"
@ -208,7 +211,7 @@ function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps)
> >
<Box shrink="No" gap="300" alignItems="End"> <Box shrink="No" gap="300" alignItems="End">
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Text size="L400">Account Data</Text> <Text size="L400">{t('RoomSettings.account_data')}</Text>
<Input <Input
variant="SurfaceVariant" variant="SurfaceVariant"
size="400" size="400"
@ -219,11 +222,11 @@ function AccountDataView({ type, defaultContent, onEdit }: AccountDataViewProps)
/> />
</Box> </Box>
<Button variant="Secondary" size="400" radii="300" onClick={onEdit}> <Button variant="Secondary" size="400" radii="300" onClick={onEdit}>
<Text size="B400">Edit</Text> <Text size="B400">{t('RoomSettings.edit')}</Text>
</Button> </Button>
</Box> </Box>
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Text size="L400">JSON Content</Text> <Text size="L400">{t('RoomSettings.json_content')}</Text>
<SequenceCard variant="SurfaceVariant"> <SequenceCard variant="SurfaceVariant">
<Scroll visibility="Always" size="300" hideTrack> <Scroll visibility="Always" size="300" hideTrack>
<TextViewerContent <TextViewerContent
@ -254,6 +257,7 @@ export function AccountDataEditor({
submitChange, submitChange,
requestClose, requestClose,
}: AccountDataEditorProps) { }: AccountDataEditorProps) {
const { t } = useTranslation();
const [data, setData] = useState<AccountDataInfo>({ const [data, setData] = useState<AccountDataInfo>({
type: type ?? '', type: type ?? '',
content: content ?? {}, content: content ?? {},
@ -290,7 +294,7 @@ export function AccountDataEditor({
onClick={requestClose} onClick={requestClose}
before={<Icon size="100" src={Icons.ArrowLeft} />} before={<Icon size="100" src={Icons.ArrowLeft} />}
> >
<Text size="T300">Developer Tools</Text> <Text size="T300">{t('RoomSettings.developer_tools')}</Text>
</Chip> </Chip>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">

View file

@ -15,6 +15,7 @@ import {
} from 'folds'; } from 'folds';
import { JoinRule } from 'matrix-js-sdk'; import { JoinRule } from 'matrix-js-sdk';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { stopPropagation } from '../utils/keyboard'; import { stopPropagation } from '../utils/keyboard';
import { getRoomIconSrc } from '../utils/room'; import { getRoomIconSrc } from '../utils/room';
@ -37,18 +38,20 @@ export const useJoinRuleIcons = (roomType?: string): JoinRuleIcons =>
); );
type JoinRuleLabels = Record<ExtendedJoinRules, string>; type JoinRuleLabels = Record<ExtendedJoinRules, string>;
export const useRoomJoinRuleLabel = (): JoinRuleLabels => export const useRoomJoinRuleLabel = (): JoinRuleLabels => {
useMemo( const { t } = useTranslation();
return useMemo(
() => ({ () => ({
[JoinRule.Invite]: 'Invite Only', [JoinRule.Invite]: t('RoomSettings.join_invite_only'),
[JoinRule.Knock]: 'Knock & Invite', [JoinRule.Knock]: t('RoomSettings.join_knock_invite'),
knock_restricted: 'Space Members or Knock', knock_restricted: t('RoomSettings.join_space_members_or_knock'),
[JoinRule.Restricted]: 'Space Members', [JoinRule.Restricted]: t('RoomSettings.join_space_members'),
[JoinRule.Public]: 'Public', [JoinRule.Public]: t('RoomSettings.join_public'),
[JoinRule.Private]: 'Invite Only', [JoinRule.Private]: t('RoomSettings.join_invite_only'),
}), }),
[] [t]
); );
};
type JoinRulesSwitcherProps<T extends ExtendedJoinRules[]> = { type JoinRulesSwitcherProps<T extends ExtendedJoinRules[]> = {
icons: JoinRuleIcons; icons: JoinRuleIcons;
@ -68,6 +71,7 @@ export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
disabled, disabled,
changing, changing,
}: JoinRulesSwitcherProps<T>) { }: JoinRulesSwitcherProps<T>) {
const { t } = useTranslation();
const [cords, setCords] = useState<RectCords>(); const [cords, setCords] = useState<RectCords>();
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => { const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
@ -138,7 +142,7 @@ export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
onClick={handleOpenMenu} onClick={handleOpenMenu}
disabled={disabled} disabled={disabled}
> >
<Text size="B300">{labels[value] ?? 'Unsupported'}</Text> <Text size="B300">{labels[value] ?? t('RoomSettings.join_unsupported')}</Text>
</Button> </Button>
</PopOut> </PopOut>
); );

View file

@ -1,5 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { as, Box, Text, config, Button, Menu, Spinner } from 'folds'; import { as, Box, Text, config, Button, Menu, Spinner } from 'folds';
import { useTranslation } from 'react-i18next';
import { import {
ImagePack, ImagePack,
ImageUsage, ImageUsage,
@ -33,6 +34,7 @@ export type ImagePackContentProps = {
export const ImagePackContent = as<'div', ImagePackContentProps>( export const ImagePackContent = as<'div', ImagePackContentProps>(
({ imagePack, canEdit, onUpdate, ...props }, ref) => { ({ imagePack, canEdit, onUpdate, ...props }, ref) => {
const { t } = useTranslation();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const [metaEditing, setMetaEditing] = useState(false); const [metaEditing, setMetaEditing] = useState(false);
@ -256,11 +258,11 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
{applyState.status === AsyncStatus.Error ? ( {applyState.status === AsyncStatus.Error ? (
<Text size="T200"> <Text size="T200">
<b>Failed to apply changes! Please try again.</b> <b>{t('RoomSettings.failed_to_apply')}</b>
</Text> </Text>
) : ( ) : (
<Text size="T200"> <Text size="T200">
<b>Changes saved! Apply when ready.</b> <b>{t('RoomSettings.changes_saved')}</b>
</Text> </Text>
)} )}
</Box> </Box>
@ -273,7 +275,7 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
disabled={!canApplyChanges || applying} disabled={!canApplyChanges || applying}
onClick={handleResetSavedChanges} onClick={handleResetSavedChanges}
> >
<Text size="B300">Reset</Text> <Text size="B300">{t('RoomSettings.reset')}</Text>
</Button> </Button>
<Button <Button
size="300" size="300"
@ -283,14 +285,14 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
before={applying && <Spinner variant="Success" fill="Solid" size="100" />} before={applying && <Spinner variant="Success" fill="Solid" size="100" />}
onClick={applyChanges} onClick={applyChanges}
> >
<Text size="B300">Apply Changes</Text> <Text size="B300">{t('RoomSettings.apply_changes')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>
</Menu> </Menu>
)} )}
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Pack</Text> <Text size="L400">{t('RoomSettings.pack')}</Text>
<SequenceCard <SequenceCard
style={{ padding: config.space.S300 }} style={{ padding: config.space.S300 }}
variant="SurfaceVariant" variant="SurfaceVariant"
@ -318,8 +320,8 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Images Usage" title={t('RoomSettings.images_usage')}
description="Select how the images are being used: as emojis, as stickers, or as both." description={t('RoomSettings.images_usage_desc')}
after={ after={
<UsageSwitcher <UsageSwitcher
usage={currentMeta.usage} usage={currentMeta.usage}
@ -332,7 +334,7 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
</Box> </Box>
{images.length === 0 && !canEdit ? null : ( {images.length === 0 && !canEdit ? null : (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Images</Text> <Text size="L400">{t('RoomSettings.images')}</Text>
{canEdit && ( {canEdit && (
<SequenceCard <SequenceCard
style={{ padding: config.space.S300 }} style={{ padding: config.space.S300 }}
@ -341,8 +343,8 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Upload Images" title={t('RoomSettings.upload_images')}
description="Select images from your storage to upload them in pack." description={t('RoomSettings.upload_images_desc')}
after={ after={
<Button <Button
variant="Secondary" variant="Secondary"
@ -353,7 +355,7 @@ export const ImagePackContent = as<'div', ImagePackContentProps>(
outlined outlined
onClick={() => pickFiles('image/*')} onClick={() => pickFiles('image/*')}
> >
<Text size="B300">Select</Text> <Text size="B300">{t('RoomSettings.select')}</Text>
</Button> </Button>
} }
/> />

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Box, IconButton, Text, Icon, Icons, Scroll, Chip } from 'folds'; import { Box, IconButton, Text, Icon, Icons, Scroll, Chip } from 'folds';
import { useTranslation } from 'react-i18next';
import { PackAddress } from '../../plugins/custom-emoji'; import { PackAddress } from '../../plugins/custom-emoji';
import { Page, PageHeader, PageContent } from '../page'; import { Page, PageHeader, PageContent } from '../page';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
@ -11,6 +12,7 @@ type ImagePackViewProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function ImagePackView({ address, requestClose }: ImagePackViewProps) { export function ImagePackView({ address, requestClose }: ImagePackViewProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = address && mx.getRoom(address.roomId); const room = address && mx.getRoom(address.roomId);
@ -25,7 +27,7 @@ export function ImagePackView({ address, requestClose }: ImagePackViewProps) {
onClick={requestClose} onClick={requestClose}
before={<Icon size="100" src={Icons.ArrowLeft} />} before={<Icon size="100" src={Icons.ArrowLeft} />}
> >
<Text size="T300">Emojis & Stickers</Text> <Text size="T300">{t('RoomSettings.emojis_stickers')}</Text>
</Chip> </Chip>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">

View file

@ -1,5 +1,6 @@
import React, { FormEventHandler, ReactNode, useMemo, useState } from 'react'; import React, { FormEventHandler, ReactNode, useMemo, useState } from 'react';
import { Badge, Box, Button, Chip, Icon, Icons, Input, Text } from 'folds'; import { Badge, Box, Button, Chip, Icon, Icons, Input, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { UsageSwitcher, useUsageStr } from './UsageSwitcher'; import { UsageSwitcher, useUsageStr } from './UsageSwitcher';
import { mxcUrlToHttp } from '../../utils/matrix'; import { mxcUrlToHttp } from '../../utils/matrix';
import * as css from './style.css'; import * as css from './style.css';
@ -30,6 +31,7 @@ export function ImageTile({
onDeleteToggle, onDeleteToggle,
deleted, deleted,
}: ImageTileProps) { }: ImageTileProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const getUsageStr = useUsageStr(); const getUsageStr = useUsageStr();
@ -71,7 +73,7 @@ export function ImageTile({
radii="Pill" radii="Pill"
onClick={() => onDeleteToggle?.(defaultShortcode)} onClick={() => onDeleteToggle?.(defaultShortcode)}
> >
{deleted ? <Text size="B300">Undo</Text> : <Icon size="50" src={Icons.Delete} />} {deleted ? <Text size="B300">{t('RoomSettings.undo')}</Text> : <Icon size="50" src={Icons.Delete} />}
</Chip> </Chip>
{!deleted && ( {!deleted && (
<Chip <Chip
@ -79,7 +81,7 @@ export function ImageTile({
radii="Pill" radii="Pill"
onClick={() => onEdit?.(defaultShortcode, image)} onClick={() => onEdit?.(defaultShortcode, image)}
> >
<Text size="B300">Edit</Text> <Text size="B300">{t('RoomSettings.edit')}</Text>
</Chip> </Chip>
)} )}
</Box> </Box>
@ -120,6 +122,7 @@ export function ImageTileEdit({
onCancel, onCancel,
onSave, onSave,
}: ImageTileEditProps) { }: ImageTileEditProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const defaultUsage = image.usage ?? packUsage; const defaultUsage = image.usage ?? packUsage;
@ -171,7 +174,7 @@ export function ImageTileEdit({
<Box as="form" onSubmit={handleSubmit} direction="Column" gap="200"> <Box as="form" onSubmit={handleSubmit} direction="Column" gap="200">
<Box direction="Column" className={css.ImagePackImageInputs}> <Box direction="Column" className={css.ImagePackImageInputs}>
<Input <Input
before={<Text size="L400">Shortcode:</Text>} before={<Text size="L400">{t('RoomSettings.shortcode')}</Text>}
defaultValue={image.shortcode} defaultValue={image.shortcode}
name="shortcodeInput" name="shortcodeInput"
variant="Secondary" variant="Secondary"
@ -181,7 +184,7 @@ export function ImageTileEdit({
autoFocus autoFocus
/> />
<Input <Input
before={<Text size="L400">Body:</Text>} before={<Text size="L400">{t('RoomSettings.body')}</Text>}
defaultValue={image.body} defaultValue={image.body}
name="bodyInput" name="bodyInput"
variant="Secondary" variant="Secondary"
@ -195,7 +198,7 @@ export function ImageTileEdit({
</Box> </Box>
<Box grow="Yes" /> <Box grow="Yes" />
<Button type="submit" variant="Success" size="300" radii="300"> <Button type="submit" variant="Success" size="300" radii="300">
<Text size="B300">Save</Text> <Text size="B300">{t('RoomSettings.save')}</Text>
</Button> </Button>
<Button <Button
type="reset" type="reset"
@ -205,7 +208,7 @@ export function ImageTileEdit({
radii="300" radii="300"
onClick={() => onCancel(defaultShortcode)} onClick={() => onCancel(defaultShortcode)}
> >
<Text size="B300">Cancel</Text> <Text size="B300">{t('RoomSettings.cancel')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>

View file

@ -13,6 +13,7 @@ import {
Chip, Chip,
} from 'folds'; } from 'folds';
import Linkify from 'linkify-react'; import Linkify from 'linkify-react';
import { useTranslation } from 'react-i18next';
import { mxcUrlToHttp } from '../../utils/matrix'; import { mxcUrlToHttp } from '../../utils/matrix';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { nameInitials } from '../../utils/common'; import { nameInitials } from '../../utils/common';
@ -31,13 +32,14 @@ type ImagePackAvatarProps = {
name?: string; name?: string;
}; };
function ImagePackAvatar({ url, name }: ImagePackAvatarProps) { function ImagePackAvatar({ url, name }: ImagePackAvatarProps) {
const { t } = useTranslation();
return ( return (
<Avatar size="500" className={ContainerColor({ variant: 'Secondary' })}> <Avatar size="500" className={ContainerColor({ variant: 'Secondary' })}>
{url ? ( {url ? (
<AvatarImage src={url} alt={name ?? 'Unknown'} /> <AvatarImage src={url} alt={name ?? t('RoomSettings.unknown')} />
) : ( ) : (
<AvatarFallback> <AvatarFallback>
<Text size="H2">{nameInitials(name ?? 'Unknown')}</Text> <Text size="H2">{nameInitials(name ?? t('RoomSettings.unknown'))}</Text>
</AvatarFallback> </AvatarFallback>
)} )}
</Avatar> </Avatar>
@ -50,6 +52,7 @@ type ImagePackProfileProps = {
onEdit?: () => void; onEdit?: () => void;
}; };
export function ImagePackProfile({ meta, canEdit, onEdit }: ImagePackProfileProps) { export function ImagePackProfile({ meta, canEdit, onEdit }: ImagePackProfileProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const avatarUrl = meta.avatar const avatarUrl = meta.avatar
@ -61,7 +64,7 @@ export function ImagePackProfile({ meta, canEdit, onEdit }: ImagePackProfileProp
<Box grow="Yes" direction="Column" gap="300"> <Box grow="Yes" direction="Column" gap="300">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text className={BreakWord} size="H5"> <Text className={BreakWord} size="H5">
{meta.name ?? 'Unknown'} {meta.name ?? t('RoomSettings.unknown')}
</Text> </Text>
{meta.attribution && ( {meta.attribution && (
<Text className={BreakWord} size="T200"> <Text className={BreakWord} size="T200">
@ -79,7 +82,7 @@ export function ImagePackProfile({ meta, canEdit, onEdit }: ImagePackProfileProp
onClick={onEdit} onClick={onEdit}
outlined outlined
> >
<Text size="B300">Edit</Text> <Text size="B300">{t('RoomSettings.edit')}</Text>
</Chip> </Chip>
</Box> </Box>
)} )}
@ -97,6 +100,7 @@ type ImagePackProfileEditProps = {
onSave: (meta: PackMetaReader) => void; onSave: (meta: PackMetaReader) => void;
}; };
export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfileEditProps) { export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfileEditProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const [avatar, setAvatar] = useState(meta.avatar); const [avatar, setAvatar] = useState(meta.avatar);
@ -147,7 +151,7 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil
<Box as="form" onSubmit={handleSubmit} direction="Column" gap="400"> <Box as="form" onSubmit={handleSubmit} direction="Column" gap="400">
<Box gap="400"> <Box gap="400">
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Text size="L400">Pack Avatar</Text> <Text size="L400">{t('RoomSettings.pack_avatar')}</Text>
{uploadAtom ? ( {uploadAtom ? (
<Box gap="200" direction="Column"> <Box gap="200" direction="Column">
<CompactUploadCardRenderer <CompactUploadCardRenderer
@ -166,7 +170,7 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil
radii="300" radii="300"
onClick={() => pickFile('image/*')} onClick={() => pickFile('image/*')}
> >
<Text size="B300">Upload</Text> <Text size="B300">{t('RoomSettings.upload')}</Text>
</Button> </Button>
{!avatar && meta.avatar && ( {!avatar && meta.avatar && (
<Button <Button
@ -177,7 +181,7 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil
radii="300" radii="300"
onClick={() => setAvatar(meta.avatar)} onClick={() => setAvatar(meta.avatar)}
> >
<Text size="B300">Reset</Text> <Text size="B300">{t('RoomSettings.reset')}</Text>
</Button> </Button>
)} )}
{avatar && ( {avatar && (
@ -189,7 +193,7 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil
radii="300" radii="300"
onClick={() => setAvatar(undefined)} onClick={() => setAvatar(undefined)}
> >
<Text size="B300">Remove</Text> <Text size="B300">{t('RoomSettings.remove')}</Text>
</Button> </Button>
)} )}
</Box> </Box>
@ -200,11 +204,11 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil
</Box> </Box>
</Box> </Box>
<Box direction="Inherit" gap="100"> <Box direction="Inherit" gap="100">
<Text size="L400">Name</Text> <Text size="L400">{t('RoomSettings.name')}</Text>
<Input name="nameInput" defaultValue={meta.name} variant="Secondary" radii="300" required /> <Input name="nameInput" defaultValue={meta.name} variant="Secondary" radii="300" required />
</Box> </Box>
<Box direction="Inherit" gap="100"> <Box direction="Inherit" gap="100">
<Text size="L400">Attribution</Text> <Text size="L400">{t('RoomSettings.attribution')}</Text>
<TextArea <TextArea
name="attributionTextArea" name="attributionTextArea"
defaultValue={meta.attribution} defaultValue={meta.attribution}
@ -214,7 +218,7 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil
</Box> </Box>
<Box gap="300"> <Box gap="300">
<Button type="submit" variant="Success" size="300" radii="300" disabled={uploadingAvatar}> <Button type="submit" variant="Success" size="300" radii="300" disabled={uploadingAvatar}>
<Text size="B300">Save</Text> <Text size="B300">{t('RoomSettings.save')}</Text>
</Button> </Button>
<Button <Button
type="reset" type="reset"
@ -224,7 +228,7 @@ export function ImagePackProfileEdit({ meta, onCancel, onSave }: ImagePackProfil
size="300" size="300"
radii="300" radii="300"
> >
<Text size="B300">Cancel</Text> <Text size="B300">{t('RoomSettings.cancel')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>

View file

@ -1,18 +1,20 @@
import React, { MouseEventHandler, useMemo, useState } from 'react'; import React, { MouseEventHandler, useMemo, useState } from 'react';
import { Box, Button, config, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text } from 'folds'; import { Box, Button, config, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { ImageUsage } from '../../plugins/custom-emoji'; import { ImageUsage } from '../../plugins/custom-emoji';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
export const useUsageStr = (): ((usage: ImageUsage[]) => string) => { export const useUsageStr = (): ((usage: ImageUsage[]) => string) => {
const { t } = useTranslation();
const getUsageStr = (usage: ImageUsage[]): string => { const getUsageStr = (usage: ImageUsage[]): string => {
const sticker = usage.includes(ImageUsage.Sticker); const sticker = usage.includes(ImageUsage.Sticker);
const emoticon = usage.includes(ImageUsage.Emoticon); const emoticon = usage.includes(ImageUsage.Emoticon);
if (sticker && emoticon) return 'Both'; if (sticker && emoticon) return t('RoomSettings.usage_both');
if (sticker) return 'Sticker'; if (sticker) return t('RoomSettings.usage_sticker');
if (emoticon) return 'Emoji'; if (emoticon) return t('RoomSettings.usage_emoji');
return 'Both'; return t('RoomSettings.usage_both');
}; };
return getUsageStr; return getUsageStr;
}; };

View file

@ -12,6 +12,7 @@ import {
config, config,
color, color,
} from 'folds'; } from 'folds';
import { useTranslation } from 'react-i18next';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css'; import { SequenceCardStyle } from '../styles.css';
@ -35,6 +36,7 @@ type DeveloperToolsProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function DeveloperTools({ requestClose }: DeveloperToolsProps) { export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
const { t } = useTranslation();
const [developerTools, setDeveloperTools] = useSetting(settingsAtom, 'developerTools'); const [developerTools, setDeveloperTools] = useSetting(settingsAtom, 'developerTools');
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
@ -88,7 +90,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
<Text size="H3" truncate> <Text size="H3" truncate>
Developer Tools {t('RoomSettings.developer_tools')}
</Text> </Text>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">
@ -103,7 +105,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
<PageContent> <PageContent>
<Box direction="Column" gap="700"> <Box direction="Column" gap="700">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Options</Text> <Text size="L400">{t('RoomSettings.options')}</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
@ -111,7 +113,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Enable Developer Tools" title={t('RoomSettings.enable_developer_tools')}
after={ after={
<Switch <Switch
variant="Primary" variant="Primary"
@ -129,8 +131,8 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Room ID" title={t('RoomSettings.room_id')}
description={`Copy room ID to clipboard. ("${room.roomId}")`} description={`${t('RoomSettings.room_id_desc')} ("${room.roomId}")`}
after={ after={
<Button <Button
onClick={() => copyToClipboard(room.roomId ?? '<NO_ROOM_ID_FOUND>')} onClick={() => copyToClipboard(room.roomId ?? '<NO_ROOM_ID_FOUND>')}
@ -140,7 +142,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
radii="300" radii="300"
outlined outlined
> >
<Text size="B300">Copy</Text> <Text size="B300">{t('RoomSettings.copy')}</Text>
</Button> </Button>
} }
/> />
@ -150,7 +152,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
{developerTools && ( {developerTools && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Data</Text> <Text size="L400">{t('RoomSettings.data')}</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
@ -159,8 +161,8 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="New Message Event" title={t('RoomSettings.new_message_event')}
description="Create and send a new message event within the room." description={t('RoomSettings.new_message_event_desc')}
after={ after={
<Button <Button
onClick={() => setComposeEvent({})} onClick={() => setComposeEvent({})}
@ -170,7 +172,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
radii="300" radii="300"
outlined outlined
> >
<Text size="B300">Compose</Text> <Text size="B300">{t('RoomSettings.compose')}</Text>
</Button> </Button>
} }
/> />
@ -182,8 +184,8 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Room State" title={t('RoomSettings.room_state')}
description="State events of the room." description={t('RoomSettings.room_state_desc')}
after={ after={
<Button <Button
onClick={() => setExpandState(!expandState)} onClick={() => setExpandState(!expandState)}
@ -200,15 +202,15 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
/> />
} }
> >
<Text size="B300">{expandState ? 'Collapse' : 'Expand'}</Text> <Text size="B300">{expandState ? t('RoomSettings.collapse') : t('RoomSettings.expand')}</Text>
</Button> </Button>
} }
/> />
{expandState && ( {expandState && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box justifyContent="SpaceBetween"> <Box justifyContent="SpaceBetween">
<Text size="L400">Events</Text> <Text size="L400">{t('RoomSettings.events')}</Text>
<Text size="L400">Total: {roomState.size}</Text> <Text size="L400">{t('RoomSettings.total', { count: roomState.size })}</Text>
</Box> </Box>
<CutoutCard> <CutoutCard>
<MenuItem <MenuItem
@ -221,7 +223,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="T200" truncate> <Text size="T200" truncate>
Add New {t('RoomSettings.add_new')}
</Text> </Text>
</Box> </Box>
</MenuItem> </MenuItem>
@ -275,7 +277,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="T200" truncate> <Text size="T200" truncate>
Add New {t('RoomSettings.add_new')}
</Text> </Text>
</Box> </Box>
</MenuItem> </MenuItem>
@ -298,7 +300,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="T200" truncate> <Text size="T200" truncate>
{stateKey ? `"${stateKey}"` : 'Default'} {stateKey ? `"${stateKey}"` : t('RoomSettings.default_key')}
</Text> </Text>
</Box> </Box>
</MenuItem> </MenuItem>
@ -319,8 +321,8 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Account Data" title={t('RoomSettings.account_data')}
description="Private personalization data stored within room." description={t('RoomSettings.account_data_desc')}
after={ after={
<Button <Button
onClick={() => setExpandAccountData(!expandAccountData)} onClick={() => setExpandAccountData(!expandAccountData)}
@ -337,15 +339,15 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
/> />
} }
> >
<Text size="B300">{expandAccountData ? 'Collapse' : 'Expand'}</Text> <Text size="B300">{expandAccountData ? t('RoomSettings.collapse') : t('RoomSettings.expand')}</Text>
</Button> </Button>
} }
/> />
{expandAccountData && ( {expandAccountData && (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box justifyContent="SpaceBetween"> <Box justifyContent="SpaceBetween">
<Text size="L400">Events</Text> <Text size="L400">{t('RoomSettings.events')}</Text>
<Text size="L400">Total: {accountData.size}</Text> <Text size="L400">{t('RoomSettings.total', { count: accountData.size })}</Text>
</Box> </Box>
<CutoutCard> <CutoutCard>
<MenuItem <MenuItem
@ -358,7 +360,7 @@ export function DeveloperTools({ requestClose }: DeveloperToolsProps) {
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="T200" truncate> <Text size="T200" truncate>
Add New {t('RoomSettings.add_new')}
</Text> </Text>
</Box> </Box>
</MenuItem> </MenuItem>

View file

@ -1,4 +1,5 @@
import React, { useCallback, useRef, useState, FormEventHandler, useEffect } from 'react'; import React, { useCallback, useRef, useState, FormEventHandler, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { MatrixError } from 'matrix-js-sdk'; import { MatrixError } from 'matrix-js-sdk';
import { import {
Box, Box,
@ -31,6 +32,7 @@ export type SendRoomEventProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventProps) { export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const alive = useAlive(); const alive = useAlive();
@ -114,7 +116,7 @@ export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventPro
onClick={requestClose} onClick={requestClose}
before={<Icon size="100" src={Icons.ArrowLeft} />} before={<Icon size="100" src={Icons.ArrowLeft} />}
> >
<Text size="T300">Developer Tools</Text> <Text size="T300">{t('RoomSettings.developer_tools')}</Text>
</Chip> </Chip>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">
@ -135,7 +137,7 @@ export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventPro
aria-disabled={submitting} aria-disabled={submitting}
> >
<Box shrink="No" direction="Column" gap="100"> <Box shrink="No" direction="Column" gap="100">
<Text size="L400">{composeStateEvent ? 'State Event Type' : 'Message Event Type'}</Text> <Text size="L400">{composeStateEvent ? t('RoomSettings.state_event_type') : t('RoomSettings.message_event_type')}</Text>
<Box gap="300"> <Box gap="300">
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
<Input <Input
@ -156,7 +158,7 @@ export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventPro
disabled={submitting} disabled={submitting}
before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />} before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />}
> >
<Text size="B400">Send</Text> <Text size="B400">{t('RoomSettings.send')}</Text>
</Button> </Button>
</Box> </Box>
@ -168,7 +170,7 @@ export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventPro
</Box> </Box>
{composeStateEvent && ( {composeStateEvent && (
<Box shrink="No" direction="Column" gap="100"> <Box shrink="No" direction="Column" gap="100">
<Text size="L400">State Key (Optional)</Text> <Text size="L400">{t('RoomSettings.state_key_optional')}</Text>
<Input <Input
variant="Background" variant="Background"
name="stateKeyInput" name="stateKeyInput"
@ -181,7 +183,7 @@ export function SendRoomEvent({ type, stateKey, requestClose }: SendRoomEventPro
)} )}
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Box shrink="No"> <Box shrink="No">
<Text size="L400">JSON Content</Text> <Text size="L400">{t('RoomSettings.json_content')}</Text>
</Box> </Box>
<TextAreaComponent <TextAreaComponent
ref={textAreaRef} ref={textAreaRef}

View file

@ -13,6 +13,7 @@ import {
Spinner, Spinner,
Button, Button,
} from 'folds'; } from 'folds';
import { useTranslation } from 'react-i18next';
import { MatrixError } from 'matrix-js-sdk'; import { MatrixError } from 'matrix-js-sdk';
import { Page, PageHeader } from '../../../components/page'; import { Page, PageHeader } from '../../../components/page';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
@ -41,6 +42,7 @@ type StateEventEditProps = {
requestClose: () => void; requestClose: () => void;
}; };
function StateEventEdit({ type, stateKey, content, requestClose }: StateEventEditProps) { function StateEventEdit({ type, stateKey, content, requestClose }: StateEventEditProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const alive = useAlive(); const alive = useAlive();
@ -118,7 +120,7 @@ function StateEventEdit({ type, stateKey, content, requestClose }: StateEventEdi
aria-disabled={submitting} aria-disabled={submitting}
> >
<Box shrink="No" direction="Column" gap="100"> <Box shrink="No" direction="Column" gap="100">
<Text size="L400">State Event</Text> <Text size="L400">{t('RoomSettings.state_event')}</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
@ -138,7 +140,7 @@ function StateEventEdit({ type, stateKey, content, requestClose }: StateEventEdi
disabled={submitting} disabled={submitting}
before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />} before={submitting && <Spinner variant="Primary" fill="Solid" size="300" />}
> >
<Text size="B300">Save</Text> <Text size="B300">{t('RoomSettings.save')}</Text>
</Button> </Button>
<Button <Button
variant="Secondary" variant="Secondary"
@ -148,7 +150,7 @@ function StateEventEdit({ type, stateKey, content, requestClose }: StateEventEdi
onClick={requestClose} onClick={requestClose}
disabled={submitting} disabled={submitting}
> >
<Text size="B300">Cancel</Text> <Text size="B300">{t('RoomSettings.cancel')}</Text>
</Button> </Button>
</Box> </Box>
} }
@ -163,7 +165,7 @@ function StateEventEdit({ type, stateKey, content, requestClose }: StateEventEdi
</Box> </Box>
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Box shrink="No"> <Box shrink="No">
<Text size="L400">JSON Content</Text> <Text size="L400">{t('RoomSettings.json_content')}</Text>
</Box> </Box>
<TextAreaComponent <TextAreaComponent
ref={textAreaRef} ref={textAreaRef}
@ -194,12 +196,13 @@ type StateEventViewProps = {
onEditContent?: (content: object) => void; onEditContent?: (content: object) => void;
}; };
function StateEventView({ content, eventJSONStr, onEditContent }: StateEventViewProps) { function StateEventView({ content, eventJSONStr, onEditContent }: StateEventViewProps) {
const { t } = useTranslation();
return ( return (
<Box direction="Column" style={{ padding: config.space.S400 }} gap="400"> <Box direction="Column" style={{ padding: config.space.S400 }} gap="400">
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Box gap="200" alignItems="End"> <Box gap="200" alignItems="End">
<Box grow="Yes"> <Box grow="Yes">
<Text size="L400">State Event</Text> <Text size="L400">{t('RoomSettings.state_event')}</Text>
</Box> </Box>
{onEditContent && ( {onEditContent && (
<Box shrink="No" gap="200"> <Box shrink="No" gap="200">
@ -210,7 +213,7 @@ function StateEventView({ content, eventJSONStr, onEditContent }: StateEventView
outlined outlined
onClick={() => onEditContent(content)} onClick={() => onEditContent(content)}
> >
<Text size="B300">Edit</Text> <Text size="B300">{t('RoomSettings.edit')}</Text>
</Chip> </Chip>
</Box> </Box>
)} )}
@ -241,6 +244,7 @@ export type StateEventEditorProps = StateEventInfo & {
}; };
export function StateEventEditor({ type, stateKey, requestClose }: StateEventEditorProps) { export function StateEventEditor({ type, stateKey, requestClose }: StateEventEditorProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const stateEvent = useStateEvent(room, type as unknown as StateEvent, stateKey); const stateEvent = useStateEvent(room, type as unknown as StateEvent, stateKey);
@ -271,7 +275,7 @@ export function StateEventEditor({ type, stateKey, requestClose }: StateEventEdi
onClick={requestClose} onClick={requestClose}
before={<Icon size="100" src={Icons.ArrowLeft} />} before={<Icon size="100" src={Icons.ArrowLeft} />}
> >
<Text size="T300">Developer Tools</Text> <Text size="T300">{t('RoomSettings.developer_tools')}</Text>
</Chip> </Chip>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">

View file

@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { ImagePack } from '../../../plugins/custom-emoji'; import { ImagePack } from '../../../plugins/custom-emoji';
import { ImagePackView } from '../../../components/image-pack-view'; import { ImagePackView } from '../../../components/image-pack-view';
@ -9,6 +10,7 @@ type EmojisStickersProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function EmojisStickers({ requestClose }: EmojisStickersProps) { export function EmojisStickers({ requestClose }: EmojisStickersProps) {
const { t } = useTranslation();
const [imagePack, setImagePack] = useState<ImagePack>(); const [imagePack, setImagePack] = useState<ImagePack>();
const handleImagePackViewClose = () => { const handleImagePackViewClose = () => {
@ -25,7 +27,7 @@ export function EmojisStickers({ requestClose }: EmojisStickersProps) {
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
<Text size="H3" truncate> <Text size="H3" truncate>
Emojis & Stickers {t('RoomSettings.emojis_stickers')}
</Text> </Text>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">

View file

@ -16,6 +16,7 @@ import {
IconButton, IconButton,
Menu, Menu,
} from 'folds'; } from 'folds';
import { useTranslation } from 'react-i18next';
import { MatrixError } from 'matrix-js-sdk'; import { MatrixError } from 'matrix-js-sdk';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { import {
@ -46,6 +47,7 @@ type CreatePackTileProps = {
roomId: string; roomId: string;
}; };
function CreatePackTile({ packs, roomId }: CreatePackTileProps) { function CreatePackTile({ packs, roomId }: CreatePackTileProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const alive = useAlive(); const alive = useAlive();
@ -97,8 +99,8 @@ function CreatePackTile({ packs, roomId }: CreatePackTileProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="New Pack" title={t('RoomSettings.new_pack')}
description="Add your own emoji and sticker pack to use in room." description={t('RoomSettings.new_pack_desc')}
> >
<Box <Box
style={{ marginTop: config.space.S200 }} style={{ marginTop: config.space.S200 }}
@ -108,7 +110,7 @@ function CreatePackTile({ packs, roomId }: CreatePackTileProps) {
alignItems="End" alignItems="End"
> >
<Box direction="Column" gap="100" grow="Yes"> <Box direction="Column" gap="100" grow="Yes">
<Text size="L400">Name</Text> <Text size="L400">{t('RoomSettings.name')}</Text>
<Input <Input
name="nameInput" name="nameInput"
required required
@ -130,7 +132,7 @@ function CreatePackTile({ packs, roomId }: CreatePackTileProps) {
disabled={creating} disabled={creating}
before={creating && <Spinner size="200" variant="Success" fill="Solid" />} before={creating && <Spinner size="200" variant="Success" fill="Solid" />}
> >
<Text size="B400">Create</Text> <Text size="B400">{t('RoomSettings.create')}</Text>
</Button> </Button>
</Box> </Box>
</SettingTile> </SettingTile>
@ -142,6 +144,7 @@ type RoomPacksProps = {
onViewPack: (imagePack: ImagePack) => void; onViewPack: (imagePack: ImagePack) => void;
}; };
export function RoomPacks({ onViewPack }: RoomPacksProps) { export function RoomPacks({ onViewPack }: RoomPacksProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const room = useRoom(); const room = useRoom();
@ -255,7 +258,7 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
outlined outlined
onClick={() => onViewPack(pack)} onClick={() => onViewPack(pack)}
> >
<Text size="B300">View</Text> <Text size="B300">{t('RoomSettings.view')}</Text>
</Button> </Button>
) )
} }
@ -267,7 +270,7 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
return ( return (
<> <>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Packs</Text> <Text size="L400">{t('RoomSettings.packs')}</Text>
{canEdit && <CreatePackTile roomId={room.roomId} packs={packs} />} {canEdit && <CreatePackTile roomId={room.roomId} packs={packs} />}
{packs.map(renderPack)} {packs.map(renderPack)}
{packs.length === 0 && ( {packs.length === 0 && (
@ -288,10 +291,10 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
}} }}
> >
<Text size="H5" align="Center"> <Text size="H5" align="Center">
No Packs {t('RoomSettings.no_packs')}
</Text> </Text>
<Text size="T200" align="Center"> <Text size="T200" align="Center">
There are no emoji or sticker packs to display at the moment. {t('RoomSettings.no_packs_desc')}
</Text> </Text>
</Box> </Box>
</SequenceCard> </SequenceCard>
@ -315,11 +318,11 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
{applyState.status === AsyncStatus.Error ? ( {applyState.status === AsyncStatus.Error ? (
<Text size="T200"> <Text size="T200">
<b>Failed to remove packs! Please try again.</b> <b>{t('RoomSettings.failed_to_remove_packs')}</b>
</Text> </Text>
) : ( ) : (
<Text size="T200"> <Text size="T200">
<b>Delete selected packs. ({removedPacks.length} selected)</b> <b>{t('RoomSettings.delete_selected_packs', { count: removedPacks.length })}</b>
</Text> </Text>
)} )}
</Box> </Box>
@ -332,7 +335,7 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
disabled={applyingChanges} disabled={applyingChanges}
onClick={handleCancelChanges} onClick={handleCancelChanges}
> >
<Text size="B300">Cancel</Text> <Text size="B300">{t('RoomSettings.cancel')}</Text>
</Button> </Button>
<Button <Button
size="300" size="300"
@ -342,7 +345,7 @@ export function RoomPacks({ onViewPack }: RoomPacksProps) {
before={applyingChanges && <Spinner variant="Critical" fill="Solid" size="100" />} before={applyingChanges && <Spinner variant="Critical" fill="Solid" size="100" />}
onClick={handleApplyChanges} onClick={handleApplyChanges}
> >
<Text size="B300">Delete</Text> <Text size="B300">{t('RoomSettings.delete')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>

View file

@ -15,6 +15,7 @@ import {
toRem, toRem,
} from 'folds'; } from 'folds';
import { MatrixError } from 'matrix-js-sdk'; import { MatrixError } from 'matrix-js-sdk';
import { useTranslation } from 'react-i18next';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SequenceCardStyle } from '../../room-settings/styles.css';
@ -39,6 +40,7 @@ type RoomPublishedAddressesProps = {
}; };
export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesProps) { export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
@ -61,19 +63,17 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Published Addresses" title={t('RoomSettings.published_addresses')}
description={ description={
<span> <span dangerouslySetInnerHTML={{ __html: t('RoomSettings.published_addresses_desc') }} />
If access is <b>Public</b>, Published addresses will be used to join by anyone.
</span>
} }
/> />
<CutoutCard variant="Surface" style={{ padding: config.space.S300 }}> <CutoutCard variant="Surface" style={{ padding: config.space.S300 }}>
{publishedAliases.length === 0 ? ( {publishedAliases.length === 0 ? (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">No Addresses</Text> <Text size="L400">{t('RoomSettings.no_addresses')}</Text>
<Text size="T200"> <Text size="T200">
To publish an address, it needs to be set as a local address first {t('RoomSettings.no_addresses_hint')}
</Text> </Text>
</Box> </Box>
) : ( ) : (
@ -86,7 +86,7 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr
</Text> </Text>
{alias === canonicalAlias && ( {alias === canonicalAlias && (
<Badge variant="Success" fill="Solid" size="500"> <Badge variant="Success" fill="Solid" size="500">
<Text size="L400">Main</Text> <Text size="L400">{t('RoomSettings.main')}</Text>
</Badge> </Badge>
)} )}
</Box> </Box>
@ -100,7 +100,7 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr
disabled={loading} disabled={loading}
onClick={() => setMain(undefined)} onClick={() => setMain(undefined)}
> >
<Text size="B300">Unset Main</Text> <Text size="B300">{t('RoomSettings.unset_main')}</Text>
</Chip> </Chip>
) : ( ) : (
<Chip <Chip
@ -110,7 +110,7 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr
disabled={loading} disabled={loading}
onClick={() => setMain(alias)} onClick={() => setMain(alias)}
> >
<Text size="B300">Set Main</Text> <Text size="B300">{t('RoomSettings.set_main')}</Text>
</Chip> </Chip>
)} )}
</Box> </Box>
@ -131,6 +131,7 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr
} }
function LocalAddressInput({ addLocalAlias }: { addLocalAlias: (alias: string) => Promise<void> }) { function LocalAddressInput({ addLocalAlias }: { addLocalAlias: (alias: string) => Promise<void> }) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const userId = mx.getSafeUserId(); const userId = mx.getSafeUserId();
const server = getMxIdServer(userId); const server = getMxIdServer(userId);
@ -183,14 +184,14 @@ function LocalAddressInput({ addLocalAlias }: { addLocalAlias: (alias: string) =
disabled={adding} disabled={adding}
before={adding && <Spinner size="100" variant="Success" fill="Solid" />} before={adding && <Spinner size="100" variant="Success" fill="Solid" />}
> >
<Text size="B400">Save</Text> <Text size="B400">{t('RoomSettings.save')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>
{addState.status === AsyncStatus.Error && ( {addState.status === AsyncStatus.Error && (
<Text style={{ color: color.Critical.Main }} size="T200"> <Text style={{ color: color.Critical.Main }} size="T200">
{(addState.error as MatrixError).httpStatus === 409 {(addState.error as MatrixError).httpStatus === 409
? 'Address is already in use!' ? t('RoomSettings.address_in_use')
: (addState.error as MatrixError).message} : (addState.error as MatrixError).message}
</Text> </Text>
)} )}
@ -207,6 +208,7 @@ function LocalAddressesList({
removeLocalAlias: (alias: string) => Promise<void>; removeLocalAlias: (alias: string) => Promise<void>;
canEditCanonical?: boolean; canEditCanonical?: boolean;
}) { }) {
const { t } = useTranslation();
const room = useRoom(); const room = useRoom();
const alive = useAlive(); const alive = useAlive();
@ -271,7 +273,7 @@ function LocalAddressesList({
{selectedAliases.length > 0 && ( {selectedAliases.length > 0 && (
<Box gap="200"> <Box gap="200">
<Box grow="Yes"> <Box grow="Yes">
<Text size="L400">{selectedAliases.length} Selected</Text> <Text size="L400">{t('RoomSettings.selected_count', { count: selectedAliases.length })}</Text>
</Box> </Box>
<Box shrink="No" gap="Inherit"> <Box shrink="No" gap="Inherit">
{canEditCanonical && {canEditCanonical &&
@ -287,7 +289,7 @@ function LocalAddressesList({
) )
} }
> >
<Text size="B300">Unpublish</Text> <Text size="B300">{t('RoomSettings.unpublish')}</Text>
</Chip> </Chip>
) : ( ) : (
<Chip <Chip
@ -301,7 +303,7 @@ function LocalAddressesList({
) )
} }
> >
<Text size="B300">Publish</Text> <Text size="B300">{t('RoomSettings.publish')}</Text>
</Chip> </Chip>
))} ))}
<Chip <Chip
@ -315,7 +317,7 @@ function LocalAddressesList({
) )
} }
> >
<Text size="B300">Delete</Text> <Text size="B300">{t('RoomSettings.delete')}</Text>
</Chip> </Chip>
</Box> </Box>
</Box> </Box>
@ -343,7 +345,7 @@ function LocalAddressesList({
<Box shrink="No" gap="100"> <Box shrink="No" gap="100">
{published && ( {published && (
<Badge variant="Success" fill="Soft" size="500"> <Badge variant="Success" fill="Soft" size="500">
<Text size="L400">Published</Text> <Text size="L400">{t('RoomSettings.published')}</Text>
</Badge> </Badge>
)} )}
</Box> </Box>
@ -360,6 +362,7 @@ function LocalAddressesList({
} }
export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissionsAPI }) { export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissionsAPI }) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
@ -380,8 +383,8 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Local Addresses" title={t('RoomSettings.local_addresses')}
description="Set local address so users can join through your homeserver." description={t('RoomSettings.local_addresses_desc')}
after={ after={
<Button <Button
type="button" type="button"
@ -396,7 +399,7 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
} }
> >
<Text as="span" size="B300" truncate> <Text as="span" size="B300" truncate>
{expand ? 'Collapse' : 'Expand'} {expand ? t('RoomSettings.collapse') : t('RoomSettings.expand')}
</Text> </Text>
</Button> </Button>
} }
@ -406,13 +409,13 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
{localAliasesState.status === AsyncStatus.Loading && ( {localAliasesState.status === AsyncStatus.Loading && (
<Box gap="100"> <Box gap="100">
<Spinner variant="Secondary" size="100" /> <Spinner variant="Secondary" size="100" />
<Text size="T200">Loading...</Text> <Text size="T200">{t('RoomSettings.loading')}</Text>
</Box> </Box>
)} )}
{localAliasesState.status === AsyncStatus.Success && {localAliasesState.status === AsyncStatus.Success &&
(localAliasesState.data.length === 0 ? ( (localAliasesState.data.length === 0 ? (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">No Addresses</Text> <Text size="L400">{t('RoomSettings.no_addresses')}</Text>
</Box> </Box>
) : ( ) : (
<LocalAddressesList <LocalAddressesList

View file

@ -18,6 +18,7 @@ import {
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { MatrixError } from 'matrix-js-sdk'; import { MatrixError } from 'matrix-js-sdk';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SequenceCardStyle } from '../../room-settings/styles.css';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
@ -35,6 +36,7 @@ type RoomEncryptionProps = {
permissions: RoomPermissionsAPI; permissions: RoomPermissionsAPI;
}; };
export function RoomEncryption({ permissions }: RoomEncryptionProps) { export function RoomEncryption({ permissions }: RoomEncryptionProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
@ -69,16 +71,16 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Room Encryption" title={t('RoomSettings.room_encryption')}
description={ description={
enabled enabled
? 'Messages in this room are protected by end-to-end encryption.' ? t('RoomSettings.encryption_enabled_desc')
: 'Once enabled, encryption cannot be disabled!' : t('RoomSettings.encryption_disabled_desc')
} }
after={ after={
enabled ? ( enabled ? (
<Badge size="500" variant="Success" fill="Solid" radii="300"> <Badge size="500" variant="Success" fill="Solid" radii="300">
<Text size="L400">Enabled</Text> <Text size="L400">{t('RoomSettings.enabled')}</Text>
</Badge> </Badge>
) : ( ) : (
<Button <Button
@ -90,7 +92,7 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
onClick={() => setPrompt(true)} onClick={() => setPrompt(true)}
before={enabling && <Spinner size="100" variant="Primary" fill="Solid" />} before={enabling && <Spinner size="100" variant="Primary" fill="Solid" />}
> >
<Text size="B300">Enable</Text> <Text size="B300">{t('RoomSettings.enable')}</Text>
</Button> </Button>
) )
} }
@ -121,7 +123,7 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">Enable Encryption</Text> <Text size="H4">{t('RoomSettings.enable_encryption')}</Text>
</Box> </Box>
<IconButton size="300" onClick={() => setPrompt(false)} radii="300"> <IconButton size="300" onClick={() => setPrompt(false)} radii="300">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@ -129,10 +131,10 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) {
</Header> </Header>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400"> <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Text priority="400"> <Text priority="400">
Are you sure? Once enabled, encryption cannot be disabled! {t('RoomSettings.enable_encryption_confirm')}
</Text> </Text>
<Button type="submit" variant="Primary" onClick={handleEnable}> <Button type="submit" variant="Primary" onClick={handleEnable}>
<Text size="B400">Enable E2E Encryption</Text> <Text size="B400">{t('RoomSettings.enable_e2e_encryption')}</Text>
</Button> </Button>
</Box> </Box>
</Dialog> </Dialog>

View file

@ -15,6 +15,7 @@ import {
import { HistoryVisibility, MatrixError } from 'matrix-js-sdk'; import { HistoryVisibility, MatrixError } from 'matrix-js-sdk';
import { RoomHistoryVisibilityEventContent } from 'matrix-js-sdk/lib/types'; import { RoomHistoryVisibilityEventContent } from 'matrix-js-sdk/lib/types';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SequenceCardStyle } from '../../room-settings/styles.css';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
@ -26,16 +27,18 @@ import { useStateEvent } from '../../../hooks/useStateEvent';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
const useVisibilityStr = () => const useVisibilityStr = () => {
useMemo( const { t } = useTranslation();
return useMemo(
() => ({ () => ({
[HistoryVisibility.Invited]: 'After Invite', [HistoryVisibility.Invited]: t('RoomSettings.visibility_after_invite'),
[HistoryVisibility.Joined]: 'After Join', [HistoryVisibility.Joined]: t('RoomSettings.visibility_after_join'),
[HistoryVisibility.Shared]: 'All Messages', [HistoryVisibility.Shared]: t('RoomSettings.visibility_all_messages'),
[HistoryVisibility.WorldReadable]: 'All Messages (Guests)', [HistoryVisibility.WorldReadable]: t('RoomSettings.visibility_all_messages_guests'),
}), }),
[] [t]
); );
};
const useVisibilityMenu = () => const useVisibilityMenu = () =>
useMemo( useMemo(
@ -52,6 +55,7 @@ type RoomHistoryVisibilityProps = {
permissions: RoomPermissionsAPI; permissions: RoomPermissionsAPI;
}; };
export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProps) { export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
@ -96,8 +100,8 @@ export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProp
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Message History Visibility" title={t('RoomSettings.history_visibility')}
description="Changes to history visibility will only apply to future messages. The visibility of existing history will have no effect." description={t('RoomSettings.history_visibility_desc')}
after={ after={
<PopOut <PopOut
anchor={menuAnchor} anchor={menuAnchor}

View file

@ -1,5 +1,6 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { color, Text } from 'folds'; import { color, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { JoinRule, MatrixError, RestrictedAllowType } from 'matrix-js-sdk'; import { JoinRule, MatrixError, RestrictedAllowType } from 'matrix-js-sdk';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
@ -41,6 +42,7 @@ type RoomJoinRulesProps = {
permissions: RoomPermissionsAPI; permissions: RoomPermissionsAPI;
}; };
export function RoomJoinRules({ permissions }: RoomJoinRulesProps) { export function RoomJoinRules({ permissions }: RoomJoinRulesProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const allowKnockRestricted = knockRestrictedSupported(room.getVersion()); const allowKnockRestricted = knockRestrictedSupported(room.getVersion());
@ -127,11 +129,11 @@ export function RoomJoinRules({ permissions }: RoomJoinRulesProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title={room.isSpaceRoom() ? 'Space Access' : 'Room Access'} title={room.isSpaceRoom() ? t('RoomSettings.space_access') : t('RoomSettings.room_access')}
description={ description={
room.isSpaceRoom() room.isSpaceRoom()
? 'Change how people can join the space.' ? t('RoomSettings.space_access_desc')
: 'Change how people can join the room.' : t('RoomSettings.room_access_desc')
} }
after={ after={
<JoinRulesSwitcher <JoinRulesSwitcher

View file

@ -13,6 +13,7 @@ import {
} from 'folds'; } from 'folds';
import React, { FormEventHandler, useCallback, useMemo, useState } from 'react'; import React, { FormEventHandler, useCallback, useMemo, useState } from 'react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { useTranslation } from 'react-i18next';
import Linkify from 'linkify-react'; import Linkify from 'linkify-react';
import classNames from 'classnames'; import classNames from 'classnames';
import { JoinRule, MatrixError } from 'matrix-js-sdk'; import { JoinRule, MatrixError } from 'matrix-js-sdk';
@ -59,6 +60,7 @@ export function RoomProfileEdit({
topic, topic,
onClose, onClose,
}: RoomProfileEditProps) { }: RoomProfileEditProps) {
const { t } = useTranslation();
const room = useRoom(); const room = useRoom();
const mx = useMatrixClient(); const mx = useMatrixClient();
const alive = useAlive(); const alive = useAlive();
@ -140,7 +142,7 @@ export function RoomProfileEdit({
<Box as="form" onSubmit={handleSubmit} direction="Column" gap="400"> <Box as="form" onSubmit={handleSubmit} direction="Column" gap="400">
<Box gap="400"> <Box gap="400">
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Text size="L400">Avatar</Text> <Text size="L400">{t('RoomSettings.avatar')}</Text>
{uploadAtom ? ( {uploadAtom ? (
<Box gap="200" direction="Column"> <Box gap="200" direction="Column">
<CompactUploadCardRenderer <CompactUploadCardRenderer
@ -160,7 +162,7 @@ export function RoomProfileEdit({
disabled={!canEditAvatar || submitting} disabled={!canEditAvatar || submitting}
onClick={() => pickFile('image/*')} onClick={() => pickFile('image/*')}
> >
<Text size="B300">Upload</Text> <Text size="B300">{t('RoomSettings.upload')}</Text>
</Button> </Button>
{!roomAvatar && avatar && ( {!roomAvatar && avatar && (
<Button <Button
@ -172,7 +174,7 @@ export function RoomProfileEdit({
disabled={!canEditAvatar || submitting} disabled={!canEditAvatar || submitting}
onClick={() => setRoomAvatar(avatar)} onClick={() => setRoomAvatar(avatar)}
> >
<Text size="B300">Reset</Text> <Text size="B300">{t('RoomSettings.reset')}</Text>
</Button> </Button>
)} )}
{roomAvatar && ( {roomAvatar && (
@ -185,7 +187,7 @@ export function RoomProfileEdit({
disabled={!canEditAvatar || submitting} disabled={!canEditAvatar || submitting}
onClick={() => setRoomAvatar(undefined)} onClick={() => setRoomAvatar(undefined)}
> >
<Text size="B300">Remove</Text> <Text size="B300">{t('RoomSettings.remove')}</Text>
</Button> </Button>
)} )}
</Box> </Box>
@ -210,7 +212,7 @@ export function RoomProfileEdit({
</Box> </Box>
</Box> </Box>
<Box direction="Inherit" gap="100"> <Box direction="Inherit" gap="100">
<Text size="L400">Name</Text> <Text size="L400">{t('RoomSettings.name')}</Text>
<Input <Input
name="nameInput" name="nameInput"
defaultValue={name} defaultValue={name}
@ -220,7 +222,7 @@ export function RoomProfileEdit({
/> />
</Box> </Box>
<Box direction="Inherit" gap="100"> <Box direction="Inherit" gap="100">
<Text size="L400">Topic</Text> <Text size="L400">{t('RoomSettings.topic')}</Text>
<TextArea <TextArea
name="topicTextArea" name="topicTextArea"
defaultValue={topic} defaultValue={topic}
@ -243,7 +245,7 @@ export function RoomProfileEdit({
disabled={uploadingAvatar || submitting} disabled={uploadingAvatar || submitting}
before={submitting && <Spinner size="100" variant="Success" fill="Solid" />} before={submitting && <Spinner size="100" variant="Success" fill="Solid" />}
> >
<Text size="B300">Save</Text> <Text size="B300">{t('RoomSettings.save')}</Text>
</Button> </Button>
<Button <Button
type="reset" type="reset"
@ -253,7 +255,7 @@ export function RoomProfileEdit({
size="300" size="300"
radii="300" radii="300"
> >
<Text size="B300">Cancel</Text> <Text size="B300">{t('RoomSettings.cancel')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>
@ -264,6 +266,7 @@ type RoomProfileProps = {
permissions: RoomPermissionsAPI; permissions: RoomPermissionsAPI;
}; };
export function RoomProfile({ permissions }: RoomProfileProps) { export function RoomProfile({ permissions }: RoomProfileProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const room = useRoom(); const room = useRoom();
@ -289,7 +292,7 @@ export function RoomProfile({ permissions }: RoomProfileProps) {
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Profile</Text> <Text size="L400">{t('RoomSettings.profile')}</Text>
<SequenceCard <SequenceCard
className={SequenceCardStyle} className={SequenceCardStyle}
variant="SurfaceVariant" variant="SurfaceVariant"
@ -311,7 +314,7 @@ export function RoomProfile({ permissions }: RoomProfileProps) {
<Box grow="Yes" direction="Column" gap="300"> <Box grow="Yes" direction="Column" gap="300">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text className={BreakWord} size="H5"> <Text className={BreakWord} size="H5">
{name ?? 'Unknown'} {name ?? t('RoomSettings.unknown')}
</Text> </Text>
{topic && ( {topic && (
<Text className={classNames(BreakWord, LineClamp3)} size="T200"> <Text className={classNames(BreakWord, LineClamp3)} size="T200">
@ -329,7 +332,7 @@ export function RoomProfile({ permissions }: RoomProfileProps) {
onClick={() => setEdit(true)} onClick={() => setEdit(true)}
outlined outlined
> >
<Text size="B300">Edit</Text> <Text size="B300">{t('RoomSettings.edit')}</Text>
</Chip> </Chip>
</Box> </Box>
)} )}

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Box, color, Spinner, Switch, Text } from 'folds'; import { Box, color, Spinner, Switch, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { JoinRule, MatrixError } from 'matrix-js-sdk'; import { JoinRule, MatrixError } from 'matrix-js-sdk';
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
@ -18,6 +19,7 @@ type RoomPublishProps = {
permissions: RoomPermissionsAPI; permissions: RoomPermissionsAPI;
}; };
export function RoomPublish({ permissions }: RoomPublishProps) { export function RoomPublish({ permissions }: RoomPublishProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
@ -46,11 +48,11 @@ export function RoomPublish({ permissions }: RoomPublishProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Publish to Directory" title={t('RoomSettings.publish_to_directory')}
description={ description={
room.isSpaceRoom() room.isSpaceRoom()
? 'List the space in the public directory to make it discoverable by others.' ? t('RoomSettings.publish_space_desc')
: 'List the room in the public directory to make it discoverable by others.' : t('RoomSettings.publish_room_desc')
} }
after={ after={
<Box gap="200" alignItems="Center"> <Box gap="200" alignItems="Center">

View file

@ -16,6 +16,7 @@ import {
Icons, Icons,
} from 'folds'; } from 'folds';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { MatrixError, Method } from 'matrix-js-sdk'; import { MatrixError, Method } from 'matrix-js-sdk';
import { RoomTombstoneEventContent } from 'matrix-js-sdk/lib/types'; import { RoomTombstoneEventContent } from 'matrix-js-sdk/lib/types';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
@ -41,6 +42,7 @@ import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { BreakWord } from '../../../styles/Text.css'; import { BreakWord } from '../../../styles/Text.css';
function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) { function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const alive = useAlive(); const alive = useAlive();
@ -103,7 +105,7 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
size="500" size="500"
> >
<Box grow="Yes"> <Box grow="Yes">
<Text size="H4">{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}</Text> <Text size="H4">{room.isSpaceRoom() ? t('RoomSettings.space_upgrade') : t('RoomSettings.room_upgrade')}</Text>
</Box> </Box>
<IconButton size="300" onClick={requestClose} radii="300"> <IconButton size="300" onClick={requestClose} radii="300">
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@ -111,10 +113,10 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
</Header> </Header>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400"> <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Text priority="400" style={{ color: color.Critical.Main }}> <Text priority="400" style={{ color: color.Critical.Main }}>
<b>This action is irreversible!</b> <b>{t('RoomSettings.action_irreversible')}</b>
</Text> </Text>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Options</Text> <Text size="L400">{t('RoomSettings.options')}</Text>
<RoomVersionSelector <RoomVersionSelector
versions={roomVersions?.available ? Object.keys(roomVersions.available) : ['1']} versions={roomVersions?.available ? Object.keys(roomVersions.available) : ['1']}
value={selectedRoomVersion} value={selectedRoomVersion}
@ -148,7 +150,7 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
disabled={upgrading} disabled={upgrading}
before={upgrading && <Spinner size="200" variant="Secondary" fill="Solid" />} before={upgrading && <Spinner size="200" variant="Secondary" fill="Solid" />}
> >
<Text size="B400">{room.isSpaceRoom() ? 'Upgrade Space' : 'Upgrade Room'}</Text> <Text size="B400">{room.isSpaceRoom() ? t('RoomSettings.upgrade_space') : t('RoomSettings.upgrade_room')}</Text>
</Button> </Button>
</Box> </Box>
</Dialog> </Dialog>
@ -163,6 +165,7 @@ type RoomUpgradeProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) { export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const { navigateRoom, navigateSpace } = useRoomNavigate(); const { navigateRoom, navigateSpace } = useRoomNavigate();
@ -213,12 +216,12 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title={room.isSpaceRoom() ? 'Upgrade Space' : 'Upgrade Room'} title={room.isSpaceRoom() ? t('RoomSettings.upgrade_space') : t('RoomSettings.upgrade_room')}
description={ description={
replacementRoom replacementRoom
? tombstoneContent.body || ? tombstoneContent.body ||
`This ${room.isSpaceRoom() ? 'space' : 'room'} has been replaced!` (room.isSpaceRoom() ? t('RoomSettings.space_replaced') : t('RoomSettings.room_replaced'))
: `Current version: ${roomVersion}.` : t('RoomSettings.current_version', { version: roomVersion })
} }
after={ after={
<Box alignItems="Center" gap="200"> <Box alignItems="Center" gap="200">
@ -231,7 +234,7 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
radii="300" radii="300"
onClick={handleOpenOldRoom} onClick={handleOpenOldRoom}
> >
<Text size="B300">{room.isSpaceRoom() ? 'Old Space' : 'Old Room'}</Text> <Text size="B300">{room.isSpaceRoom() ? t('RoomSettings.old_space') : t('RoomSettings.old_room')}</Text>
</Button> </Button>
)} )}
{replacementRoom ? ( {replacementRoom ? (
@ -242,7 +245,7 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
radii="300" radii="300"
onClick={handleOpenRoom} onClick={handleOpenRoom}
> >
<Text size="B300">{room.isSpaceRoom() ? 'Open New Space' : 'Open New Room'}</Text> <Text size="B300">{room.isSpaceRoom() ? t('RoomSettings.open_new_space') : t('RoomSettings.open_new_room')}</Text>
</Button> </Button>
) : ( ) : (
<Button <Button
@ -253,7 +256,7 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
disabled={!canUpgrade} disabled={!canUpgrade}
onClick={() => setPrompt(true)} onClick={() => setPrompt(true)}
> >
<Text size="B300">Upgrade</Text> <Text size="B300">{t('RoomSettings.upgrade')}</Text>
</Button> </Button>
)} )}
</Box> </Box>

View file

@ -21,6 +21,7 @@ import {
Text, Text,
toRem, toRem,
} from 'folds'; } from 'folds';
import { useTranslation } from 'react-i18next';
import { useVirtualizer } from '@tanstack/react-virtual'; import { useVirtualizer } from '@tanstack/react-virtual';
import { RoomMember } from 'matrix-js-sdk'; import { RoomMember } from 'matrix-js-sdk';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
@ -75,6 +76,7 @@ type MembersProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function Members({ requestClose }: MembersProps) { export function Members({ requestClose }: MembersProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const room = useRoom(); const room = useRoom();
@ -157,7 +159,7 @@ export function Members({ requestClose }: MembersProps) {
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
<Text size="H3" truncate> <Text size="H3" truncate>
{room.getJoinedMemberCount()} Members {t('RoomSettings.members_count', { count: room.getJoinedMemberCount() })}
</Text> </Text>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">
@ -182,7 +184,7 @@ export function Members({ requestClose }: MembersProps) {
before={<Icon size="200" src={Icons.Search} />} before={<Icon size="200" src={Icons.Search} />}
variant="SurfaceVariant" variant="SurfaceVariant"
size="500" size="500"
placeholder="Search" placeholder={t('RoomSettings.search')}
outlined outlined
after={ after={
result && ( result && (
@ -197,8 +199,8 @@ export function Members({ requestClose }: MembersProps) {
> >
<Text size="B300"> <Text size="B300">
{result.items.length === 0 {result.items.length === 0
? 'No Results' ? t('RoomSettings.no_results')
: `${result.items.length} Results`} : t('RoomSettings.results_count', { count: result.items.length })}
</Text> </Text>
</Chip> </Chip>
) )
@ -282,7 +284,7 @@ export function Members({ requestClose }: MembersProps) {
radii="Pill" radii="Pill"
outlined outlined
size="300" size="300"
aria-label="Scroll to Top" aria-label={t('RoomSettings.scroll_to_top')}
> >
<Icon src={Icons.ChevronTop} size="300" /> <Icon src={Icons.ChevronTop} size="300" />
</IconButton> </IconButton>
@ -295,7 +297,7 @@ export function Members({ requestClose }: MembersProps) {
{!fetchingMembers && !result && flattenTagMembers.length === 0 && ( {!fetchingMembers && !result && flattenTagMembers.length === 0 && (
<Text style={{ padding: config.space.S300 }} align="Center"> <Text style={{ padding: config.space.S300 }} align="Center">
{`No "${membershipFilter.name}" Members`} {t('RoomSettings.no_membership_members', { filter: membershipFilter.name })}
</Text> </Text>
)} )}

View file

@ -1,6 +1,7 @@
/* eslint-disable react/no-array-index-key */ /* eslint-disable react/no-array-index-key */
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Badge, Box, Button, Chip, config, Icon, Icons, Menu, Spinner, Text } from 'folds'; import { Badge, Box, Button, Chip, config, Icon, Icons, Menu, Spinner, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import produce from 'immer'; import produce from 'immer';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css'; import { SequenceCardStyle } from '../styles.css';
@ -34,6 +35,7 @@ export function PermissionGroups({
permissionGroups, permissionGroups,
canEdit, canEdit,
}: PermissionGroupsProps) { }: PermissionGroupsProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const alive = useAlive(); const alive = useAlive();
@ -114,7 +116,7 @@ export function PermissionGroups({
return ( return (
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Users</Text> <Text size="L400">{t('RoomSettings.users')}</Text>
<SequenceCard <SequenceCard
variant="SurfaceVariant" variant="SurfaceVariant"
className={SequenceCardStyle} className={SequenceCardStyle}
@ -122,8 +124,8 @@ export function PermissionGroups({
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Default Power" title={t('RoomSettings.default_power')}
description="Default power level for all users." description={t('RoomSettings.default_power_desc')}
after={ after={
<PowerSwitcher <PowerSwitcher
powerLevelTags={powerLevelTags} powerLevelTags={powerLevelTags}
@ -220,7 +222,7 @@ export function PermissionGroups({
<Text size="B300" truncate> <Text size="B300" truncate>
{tag.name} {tag.name}
</Text> </Text>
{value < maxPower && <Text size="T200">& Above</Text>} {value < maxPower && <Text size="T200">{t('RoomSettings.and_above')}</Text>}
</Chip> </Chip>
)} )}
</PowerSwitcher> </PowerSwitcher>
@ -249,11 +251,11 @@ export function PermissionGroups({
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
{applyState.status === AsyncStatus.Error ? ( {applyState.status === AsyncStatus.Error ? (
<Text size="T200"> <Text size="T200">
<b>Failed to apply changes! Please try again.</b> <b>{t('RoomSettings.failed_to_apply')}</b>
</Text> </Text>
) : ( ) : (
<Text size="T200"> <Text size="T200">
<b>Changes saved! Apply when ready.</b> <b>{t('RoomSettings.changes_saved')}</b>
</Text> </Text>
)} )}
</Box> </Box>
@ -266,7 +268,7 @@ export function PermissionGroups({
disabled={applyingChanges} disabled={applyingChanges}
onClick={resetChanges} onClick={resetChanges}
> >
<Text size="B300">Reset</Text> <Text size="B300">{t('RoomSettings.reset')}</Text>
</Button> </Button>
<Button <Button
size="300" size="300"
@ -276,7 +278,7 @@ export function PermissionGroups({
before={applyingChanges && <Spinner variant="Success" fill="Solid" size="100" />} before={applyingChanges && <Spinner variant="Success" fill="Solid" size="100" />}
onClick={handleApplyChanges} onClick={handleApplyChanges}
> >
<Text size="B300">Apply Changes</Text> <Text size="B300">{t('RoomSettings.apply_changes')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>

View file

@ -1,6 +1,7 @@
/* eslint-disable react/no-array-index-key */ /* eslint-disable react/no-array-index-key */
import React, { useState, MouseEventHandler, ReactNode } from 'react'; import React, { useState, MouseEventHandler, ReactNode } from 'react';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { import {
Box, Box,
Button, Button,
@ -108,6 +109,7 @@ type PowersProps = {
onEdit?: () => void; onEdit?: () => void;
}; };
export function Powers({ powerLevels, permissionGroups, onEdit }: PowersProps) { export function Powers({ powerLevels, permissionGroups, onEdit }: PowersProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const room = useRoom(); const room = useRoom();
@ -127,8 +129,8 @@ export function Powers({ powerLevels, permissionGroups, onEdit }: PowersProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Founders" title={t('RoomSettings.founders')}
description="Founding members has all permissions and can only be changed during upgrade." description={t('RoomSettings.founders_desc')}
/> />
<SettingTile> <SettingTile>
@ -155,8 +157,8 @@ export function Powers({ powerLevels, permissionGroups, onEdit }: PowersProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="Power Levels" title={t('RoomSettings.power_levels')}
description="Manage and customize incremental power levels for users." description={t('RoomSettings.power_levels_desc')}
after={ after={
onEdit && ( onEdit && (
<Box gap="200"> <Box gap="200">
@ -168,7 +170,7 @@ export function Powers({ powerLevels, permissionGroups, onEdit }: PowersProps) {
outlined outlined
onClick={onEdit} onClick={onEdit}
> >
<Text size="B300">Edit</Text> <Text size="B300">{t('RoomSettings.edit')}</Text>
</Button> </Button>
</Box> </Box>
) )

View file

@ -19,6 +19,7 @@ import {
Tooltip, Tooltip,
} from 'folds'; } from 'folds';
import { HexColorPicker } from 'react-colorful'; import { HexColorPicker } from 'react-colorful';
import { useTranslation } from 'react-i18next';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { IPowerLevels } from '../../../hooks/usePowerLevels'; import { IPowerLevels } from '../../../hooks/usePowerLevels';
@ -58,6 +59,7 @@ type EditPowerProps = {
onClose: () => void; onClose: () => void;
}; };
function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) { function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const roomToParents = useAtomValue(roomToParentsAtom); const roomToParents = useAtomValue(roomToParentsAtom);
@ -120,7 +122,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
<Box direction="Column" gap="300"> <Box direction="Column" gap="300">
<Box gap="200"> <Box gap="200">
<Box shrink="No" direction="Column" gap="100"> <Box shrink="No" direction="Column" gap="100">
<Text size="L400">Color</Text> <Text size="L400">{t('RoomSettings.color')}</Text>
<Box gap="200"> <Box gap="200">
<HexColorPickerPopOut <HexColorPickerPopOut
picker={<HexColorPicker color={tagColor} onChange={setTagColor} />} picker={<HexColorPicker color={tagColor} onChange={setTagColor} />}
@ -137,18 +139,18 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
radii="300" radii="300"
before={<PowerColorBadge color={tagColor} />} before={<PowerColorBadge color={tagColor} />}
> >
<Text size="B300">Pick</Text> <Text size="B300">{t('RoomSettings.pick')}</Text>
</Button> </Button>
)} )}
</HexColorPickerPopOut> </HexColorPickerPopOut>
</Box> </Box>
</Box> </Box>
<Box grow="Yes" direction="Column" gap="100"> <Box grow="Yes" direction="Column" gap="100">
<Text size="L400">Name</Text> <Text size="L400">{t('RoomSettings.name')}</Text>
<Input <Input
name="nameInput" name="nameInput"
defaultValue={tag?.name} defaultValue={tag?.name}
placeholder="Bot" placeholder={t('RoomSettings.power_level_placeholder')}
size="300" size="300"
variant="Secondary" variant="Secondary"
radii="300" radii="300"
@ -156,7 +158,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
/> />
</Box> </Box>
<Box style={{ maxWidth: toRem(74) }} grow="Yes" direction="Column" gap="100"> <Box style={{ maxWidth: toRem(74) }} grow="Yes" direction="Column" gap="100">
<Text size="L400">Power</Text> <Text size="L400">{t('RoomSettings.power')}</Text>
<Input <Input
defaultValue={power} defaultValue={power}
name="powerInput" name="powerInput"
@ -174,7 +176,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
</Box> </Box>
</Box> </Box>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Icon</Text> <Text size="L400">{t('RoomSettings.icon')}</Text>
{iconUploadAtom && !tagIconSrc ? ( {iconUploadAtom && !tagIconSrc ? (
<CompactUploadCardRenderer <CompactUploadCardRenderer
uploadAtom={iconUploadAtom} uploadAtom={iconUploadAtom}
@ -194,7 +196,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
fill="None" fill="None"
radii="300" radii="300"
> >
<Text size="B300">Remove</Text> <Text size="B300">{t('RoomSettings.remove')}</Text>
</Button> </Button>
</> </>
) : ( ) : (
@ -238,7 +240,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
radii="300" radii="300"
before={<Icon size="50" src={Icons.SmilePlus} />} before={<Icon size="50" src={Icons.SmilePlus} />}
> >
<Text size="B300">Pick</Text> <Text size="B300">{t('RoomSettings.pick')}</Text>
</Button> </Button>
</PopOut> </PopOut>
)} )}
@ -251,7 +253,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
fill="None" fill="None"
radii="300" radii="300"
> >
<Text size="B300">Import</Text> <Text size="B300">{t('RoomSettings.import')}</Text>
</Button> </Button>
</> </>
)} )}
@ -267,7 +269,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
radii="300" radii="300"
disabled={uploadingIcon} disabled={uploadingIcon}
> >
<Text size="B300">Save</Text> <Text size="B300">{t('RoomSettings.save')}</Text>
</Button> </Button>
<Button <Button
type="button" type="button"
@ -277,7 +279,7 @@ function EditPower({ maxPower, power, tag, onSave, onClose }: EditPowerProps) {
radii="300" radii="300"
onClick={onClose} onClick={onClose}
> >
<Text size="B300">Cancel</Text> <Text size="B300">{t('RoomSettings.cancel')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>
@ -289,6 +291,7 @@ type PowersEditorProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) { export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const room = useRoom(); const room = useRoom();
@ -365,7 +368,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
onClick={requestClose} onClick={requestClose}
before={<Icon size="100" src={Icons.ArrowLeft} />} before={<Icon size="100" src={Icons.ArrowLeft} />}
> >
<Text size="T300">Permissions</Text> <Text size="T300">{t('RoomSettings.permissions')}</Text>
</Chip> </Chip>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">
@ -381,7 +384,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
<Box direction="Column" gap="700"> <Box direction="Column" gap="700">
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box alignItems="Baseline" gap="200" justifyContent="SpaceBetween"> <Box alignItems="Baseline" gap="200" justifyContent="SpaceBetween">
<Text size="L400">Power Levels</Text> <Text size="L400">{t('RoomSettings.power_levels')}</Text>
<BetaNoticeBadge /> <BetaNoticeBadge />
</Box> </Box>
<SequenceCard <SequenceCard
@ -391,8 +394,8 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
gap="400" gap="400"
> >
<SettingTile <SettingTile
title="New Power Level" title={t('RoomSettings.new_power_level')}
description="Create a new power level." description={t('RoomSettings.new_power_level_desc')}
after={ after={
!createTag && ( !createTag && (
<Button <Button
@ -404,7 +407,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
outlined outlined
disabled={applyingChanges} disabled={applyingChanges}
> >
<Text size="B300">Create</Text> <Text size="B300">{t('RoomSettings.create')}</Text>
</Button> </Button>
) )
} }
@ -462,7 +465,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
disabled={applyingChanges} disabled={applyingChanges}
onClick={() => handleToggleDelete(power)} onClick={() => handleToggleDelete(power)}
> >
<Text size="B300">Undo</Text> <Text size="B300">{t('RoomSettings.undo')}</Text>
</Chip> </Chip>
) : ( ) : (
<Box shrink="No" alignItems="Center" gap="200"> <Box shrink="No" alignItems="Center" gap="200">
@ -471,13 +474,13 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
<Tooltip style={{ maxWidth: toRem(200) }}> <Tooltip style={{ maxWidth: toRem(200) }}>
{usedPowers.has(power) ? ( {usedPowers.has(power) ? (
<Box direction="Column"> <Box direction="Column">
<Text size="L400">Used Power Level</Text> <Text size="L400">{t('RoomSettings.used_power_level')}</Text>
<Text size="T200"> <Text size="T200">
You have to remove its use before you can delete it. {t('RoomSettings.used_power_level_desc')}
</Text> </Text>
</Box> </Box>
) : ( ) : (
<Text>Delete</Text> <Text>{t('RoomSettings.delete')}</Text>
)} )}
</Tooltip> </Tooltip>
} }
@ -506,7 +509,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
disabled={applyingChanges} disabled={applyingChanges}
onClick={() => setEdit(true)} onClick={() => setEdit(true)}
> >
<Text size="B300">Edit</Text> <Text size="B300">{t('RoomSettings.edit')}</Text>
</Chip> </Chip>
</Box> </Box>
) )
@ -536,11 +539,11 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
{applyState.status === AsyncStatus.Error ? ( {applyState.status === AsyncStatus.Error ? (
<Text size="T200"> <Text size="T200">
<b>Failed to apply changes! Please try again.</b> <b>{t('RoomSettings.failed_to_apply')}</b>
</Text> </Text>
) : ( ) : (
<Text size="T200"> <Text size="T200">
<b>Changes saved! Apply when ready.</b> <b>{t('RoomSettings.changes_saved')}</b>
</Text> </Text>
)} )}
</Box> </Box>
@ -553,7 +556,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
disabled={applyingChanges} disabled={applyingChanges}
onClick={resetChanges} onClick={resetChanges}
> >
<Text size="B300">Reset</Text> <Text size="B300">{t('RoomSettings.reset')}</Text>
</Button> </Button>
<Button <Button
size="300" size="300"
@ -565,7 +568,7 @@ export function PowersEditor({ powerLevels, requestClose }: PowersEditorProps) {
} }
onClick={handleApplyChanges} onClick={handleApplyChanges}
> >
<Text size="B300">Apply Changes</Text> <Text size="B300">{t('RoomSettings.apply_changes')}</Text>
</Button> </Button>
</Box> </Box>
</Box> </Box>

View file

@ -1,6 +1,7 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds'; import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { JoinRule } from 'matrix-js-sdk'; import { JoinRule } from 'matrix-js-sdk';
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page'; import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
@ -24,37 +25,39 @@ type RoomSettingsMenuItem = {
icon: IconSrc; icon: IconSrc;
}; };
const useRoomSettingsMenuItems = (): RoomSettingsMenuItem[] => const useRoomSettingsMenuItems = (): RoomSettingsMenuItem[] => {
useMemo( const { t } = useTranslation();
return useMemo(
() => [ () => [
{ {
page: RoomSettingsPage.GeneralPage, page: RoomSettingsPage.GeneralPage,
name: 'General', name: t('RoomSettings.general'),
icon: Icons.Setting, icon: Icons.Setting,
}, },
{ {
page: RoomSettingsPage.MembersPage, page: RoomSettingsPage.MembersPage,
name: 'Members', name: t('RoomSettings.members'),
icon: Icons.User, icon: Icons.User,
}, },
{ {
page: RoomSettingsPage.PermissionsPage, page: RoomSettingsPage.PermissionsPage,
name: 'Permissions', name: t('RoomSettings.permissions'),
icon: Icons.Lock, icon: Icons.Lock,
}, },
{ {
page: RoomSettingsPage.EmojisStickersPage, page: RoomSettingsPage.EmojisStickersPage,
name: 'Emojis & Stickers', name: t('RoomSettings.emojis_stickers'),
icon: Icons.Smile, icon: Icons.Smile,
}, },
{ {
page: RoomSettingsPage.DeveloperToolsPage, page: RoomSettingsPage.DeveloperToolsPage,
name: 'Developer Tools', name: t('RoomSettings.developer_tools'),
icon: Icons.Terminal, icon: Icons.Terminal,
}, },
], ],
[] [t]
); );
};
type RoomSettingsProps = { type RoomSettingsProps = {
initialPage?: RoomSettingsPage; initialPage?: RoomSettingsPage;

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { usePowerLevels } from '../../../hooks/usePowerLevels'; import { usePowerLevels } from '../../../hooks/usePowerLevels';
import { useRoom } from '../../../hooks/useRoom'; import { useRoom } from '../../../hooks/useRoom';
@ -20,6 +21,7 @@ type GeneralProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function General({ requestClose }: GeneralProps) { export function General({ requestClose }: GeneralProps) {
const { t } = useTranslation();
const room = useRoom(); const room = useRoom();
const powerLevels = usePowerLevels(room); const powerLevels = usePowerLevels(room);
const creators = useRoomCreators(room); const creators = useRoomCreators(room);
@ -31,7 +33,7 @@ export function General({ requestClose }: GeneralProps) {
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
<Text size="H3" truncate> <Text size="H3" truncate>
General {t('RoomSettings.general')}
</Text> </Text>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">
@ -47,19 +49,19 @@ export function General({ requestClose }: GeneralProps) {
<Box direction="Column" gap="700"> <Box direction="Column" gap="700">
<RoomProfile permissions={permissions} /> <RoomProfile permissions={permissions} />
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Options</Text> <Text size="L400">{t('RoomSettings.options')}</Text>
<RoomJoinRules permissions={permissions} /> <RoomJoinRules permissions={permissions} />
<RoomHistoryVisibility permissions={permissions} /> <RoomHistoryVisibility permissions={permissions} />
<RoomEncryption permissions={permissions} /> <RoomEncryption permissions={permissions} />
<RoomPublish permissions={permissions} /> <RoomPublish permissions={permissions} />
</Box> </Box>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Addresses</Text> <Text size="L400">{t('RoomSettings.addresses')}</Text>
<RoomPublishedAddresses permissions={permissions} /> <RoomPublishedAddresses permissions={permissions} />
<RoomLocalAddresses permissions={permissions} /> <RoomLocalAddresses permissions={permissions} />
</Box> </Box>
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Text size="L400">Advanced Options</Text> <Text size="L400">{t('RoomSettings.advanced_options')}</Text>
<RoomUpgrade permissions={permissions} requestClose={requestClose} /> <RoomUpgrade permissions={permissions} requestClose={requestClose} />
</Box> </Box>
</Box> </Box>

View file

@ -1,5 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds'; import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { useTranslation } from 'react-i18next';
import { Page, PageContent, PageHeader } from '../../../components/page'; import { Page, PageContent, PageHeader } from '../../../components/page';
import { useRoom } from '../../../hooks/useRoom'; import { useRoom } from '../../../hooks/useRoom';
import { usePowerLevels } from '../../../hooks/usePowerLevels'; import { usePowerLevels } from '../../../hooks/usePowerLevels';
@ -14,6 +15,7 @@ type PermissionsProps = {
requestClose: () => void; requestClose: () => void;
}; };
export function Permissions({ requestClose }: PermissionsProps) { export function Permissions({ requestClose }: PermissionsProps) {
const { t } = useTranslation();
const mx = useMatrixClient(); const mx = useMatrixClient();
const room = useRoom(); const room = useRoom();
const powerLevels = usePowerLevels(room); const powerLevels = usePowerLevels(room);
@ -41,7 +43,7 @@ export function Permissions({ requestClose }: PermissionsProps) {
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
<Text size="H3" truncate> <Text size="H3" truncate>
Permissions {t('RoomSettings.permissions')}
</Text> </Text>
</Box> </Box>
<Box shrink="No"> <Box shrink="No">

View file

@ -1,215 +1,217 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { MessageEvent, StateEvent } from '../../../../types/matrix/room'; import { MessageEvent, StateEvent } from '../../../../types/matrix/room';
import { PermissionGroup } from '../../common-settings/permissions'; import { PermissionGroup } from '../../common-settings/permissions';
export const usePermissionGroups = (isCallRoom: boolean): PermissionGroup[] => { export const usePermissionGroups = (isCallRoom: boolean): PermissionGroup[] => {
const { t } = useTranslation();
const groups: PermissionGroup[] = useMemo(() => { const groups: PermissionGroup[] = useMemo(() => {
const messagesGroup: PermissionGroup = { const messagesGroup: PermissionGroup = {
name: 'Messages', name: t('RoomSettings.perm_messages'),
items: [ items: [
{ {
location: { location: {
key: MessageEvent.RoomMessage, key: MessageEvent.RoomMessage,
}, },
name: 'Send Messages', name: t('RoomSettings.perm_send_messages'),
}, },
{ {
location: { location: {
key: MessageEvent.Sticker, key: MessageEvent.Sticker,
}, },
name: 'Send Stickers', name: t('RoomSettings.perm_send_stickers'),
}, },
{ {
location: { location: {
key: MessageEvent.Reaction, key: MessageEvent.Reaction,
}, },
name: 'Send Reactions', name: t('RoomSettings.perm_send_reactions'),
}, },
{ {
location: { location: {
notification: true, notification: true,
key: 'room', key: 'room',
}, },
name: 'Ping @room', name: t('RoomSettings.perm_ping_room'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomPinnedEvents, key: StateEvent.RoomPinnedEvents,
}, },
name: 'Pin Messages', name: t('RoomSettings.perm_pin_messages'),
}, },
{ {
location: {}, location: {},
name: 'Other Message Events', name: t('RoomSettings.perm_other_message_events'),
}, },
], ],
}; };
const callSettingsGroup: PermissionGroup = { const callSettingsGroup: PermissionGroup = {
name: 'Calls', name: t('RoomSettings.perm_calls'),
items: [ items: [
{ {
location: { location: {
state: true, state: true,
key: StateEvent.GroupCallMemberPrefix, key: StateEvent.GroupCallMemberPrefix,
}, },
name: 'Join Call', name: t('RoomSettings.perm_join_call'),
}, },
], ],
}; };
const moderationGroup: PermissionGroup = { const moderationGroup: PermissionGroup = {
name: 'Moderation', name: t('RoomSettings.perm_moderation'),
items: [ items: [
{ {
location: { location: {
action: true, action: true,
key: 'invite', key: 'invite',
}, },
name: 'Invite', name: t('RoomSettings.perm_invite'),
}, },
{ {
location: { location: {
action: true, action: true,
key: 'kick', key: 'kick',
}, },
name: 'Kick', name: t('RoomSettings.perm_kick'),
}, },
{ {
location: { location: {
action: true, action: true,
key: 'ban', key: 'ban',
}, },
name: 'Ban', name: t('RoomSettings.perm_ban'),
}, },
{ {
location: { location: {
action: true, action: true,
key: 'redact', key: 'redact',
}, },
name: 'Delete Others Messages', name: t('RoomSettings.perm_delete_others_messages'),
}, },
{ {
location: { location: {
key: MessageEvent.RoomRedaction, key: MessageEvent.RoomRedaction,
}, },
name: 'Delete Self Messages', name: t('RoomSettings.perm_delete_self_messages'),
}, },
], ],
}; };
const roomOverviewGroup: PermissionGroup = { const roomOverviewGroup: PermissionGroup = {
name: 'Room Overview', name: t('RoomSettings.perm_room_overview'),
items: [ items: [
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomAvatar, key: StateEvent.RoomAvatar,
}, },
name: 'Room Avatar', name: t('RoomSettings.perm_room_avatar'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomName, key: StateEvent.RoomName,
}, },
name: 'Room Name', name: t('RoomSettings.perm_room_name'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomTopic, key: StateEvent.RoomTopic,
}, },
name: 'Room Topic', name: t('RoomSettings.perm_room_topic'),
}, },
], ],
}; };
const roomSettingsGroup: PermissionGroup = { const roomSettingsGroup: PermissionGroup = {
name: 'Settings', name: t('RoomSettings.perm_settings'),
items: [ items: [
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomJoinRules, key: StateEvent.RoomJoinRules,
}, },
name: 'Change Room Access', name: t('RoomSettings.perm_change_room_access'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomCanonicalAlias, key: StateEvent.RoomCanonicalAlias,
}, },
name: 'Publish Address', name: t('RoomSettings.perm_publish_address'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomPowerLevels, key: StateEvent.RoomPowerLevels,
}, },
name: 'Change All Permission', name: t('RoomSettings.perm_change_all_permission'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.PowerLevelTags, key: StateEvent.PowerLevelTags,
}, },
name: 'Edit Power Levels', name: t('RoomSettings.perm_edit_power_levels'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomEncryption, key: StateEvent.RoomEncryption,
}, },
name: 'Enable Encryption', name: t('RoomSettings.perm_enable_encryption'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomHistoryVisibility, key: StateEvent.RoomHistoryVisibility,
}, },
name: 'History Visibility', name: t('RoomSettings.perm_history_visibility'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomTombstone, key: StateEvent.RoomTombstone,
}, },
name: 'Upgrade Room', name: t('RoomSettings.perm_upgrade_room'),
}, },
{ {
location: { location: {
state: true, state: true,
}, },
name: 'Other Settings', name: t('RoomSettings.perm_other_settings'),
}, },
], ],
}; };
const otherSettingsGroup: PermissionGroup = { const otherSettingsGroup: PermissionGroup = {
name: 'Other', name: t('RoomSettings.perm_other'),
items: [ items: [
{ {
location: { location: {
state: true, state: true,
key: StateEvent.PoniesRoomEmotes, key: StateEvent.PoniesRoomEmotes,
}, },
name: 'Manage Emojis & Stickers', name: t('RoomSettings.perm_manage_emojis_stickers'),
}, },
{ {
location: { location: {
state: true, state: true,
key: StateEvent.RoomServerAcl, key: StateEvent.RoomServerAcl,
}, },
name: 'Change Server ACLs', name: t('RoomSettings.perm_change_server_acls'),
}, },
{ {
location: { location: {
state: true, state: true,
key: 'im.vector.modular.widgets', key: 'im.vector.modular.widgets',
}, },
name: 'Modify Widgets', name: t('RoomSettings.perm_modify_widgets'),
}, },
], ],
}; };
@ -222,7 +224,7 @@ export const usePermissionGroups = (isCallRoom: boolean): PermissionGroup[] => {
roomSettingsGroup, roomSettingsGroup,
otherSettingsGroup, otherSettingsGroup,
]; ];
}, [isCallRoom]); }, [isCallRoom, t]);
return groups; return groups;
}; };

View file

@ -1,4 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { RoomMember } from 'matrix-js-sdk'; import { RoomMember } from 'matrix-js-sdk';
import { Membership } from '../../types/matrix/room'; import { Membership } from '../../types/matrix/room';
@ -21,32 +22,34 @@ export type MembershipFilterItem = {
filterFn: MembershipFilterFn; filterFn: MembershipFilterFn;
}; };
export const useMembershipFilterMenu = (): MembershipFilterItem[] => export const useMembershipFilterMenu = (): MembershipFilterItem[] => {
useMemo( const { t } = useTranslation();
return useMemo(
() => [ () => [
{ {
name: 'Joined', name: t('RoomSettings.filter_joined'),
filterFn: MembershipFilter.filterJoined, filterFn: MembershipFilter.filterJoined,
}, },
{ {
name: 'Invited', name: t('RoomSettings.filter_invited'),
filterFn: MembershipFilter.filterInvited, filterFn: MembershipFilter.filterInvited,
}, },
{ {
name: 'Left', name: t('RoomSettings.filter_left'),
filterFn: MembershipFilter.filterLeaved, filterFn: MembershipFilter.filterLeaved,
}, },
{ {
name: 'Kicked', name: t('RoomSettings.filter_kicked'),
filterFn: MembershipFilter.filterKicked, filterFn: MembershipFilter.filterKicked,
}, },
{ {
name: 'Banned', name: t('RoomSettings.filter_banned'),
filterFn: MembershipFilter.filterBanned, filterFn: MembershipFilter.filterBanned,
}, },
], ],
[] [t]
); );
};
export const useMembershipFilter = ( export const useMembershipFilter = (
index: number, index: number,

View file

@ -1,5 +1,6 @@
import { RoomMember } from 'matrix-js-sdk'; import { RoomMember } from 'matrix-js-sdk';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const MemberSort = { export const MemberSort = {
Ascending: (a: RoomMember, b: RoomMember) => Ascending: (a: RoomMember, b: RoomMember) =>
@ -19,28 +20,30 @@ export type MemberSortItem = {
sortFn: MemberSortFn; sortFn: MemberSortFn;
}; };
export const useMemberSortMenu = (): MemberSortItem[] => export const useMemberSortMenu = (): MemberSortItem[] => {
useMemo( const { t } = useTranslation();
return useMemo(
() => [ () => [
{ {
name: 'A to Z', name: t('RoomSettings.sort_a_to_z'),
sortFn: MemberSort.Ascending, sortFn: MemberSort.Ascending,
}, },
{ {
name: 'Z to A', name: t('RoomSettings.sort_z_to_a'),
sortFn: MemberSort.Descending, sortFn: MemberSort.Descending,
}, },
{ {
name: 'Newest', name: t('RoomSettings.sort_newest'),
sortFn: MemberSort.NewestFirst, sortFn: MemberSort.NewestFirst,
}, },
{ {
name: 'Oldest', name: t('RoomSettings.sort_oldest'),
sortFn: MemberSort.Oldest, sortFn: MemberSort.Oldest,
}, },
], ],
[] [t]
); );
};
export const useMemberSort = (index: number, memberSort: MemberSortItem[]): MemberSortItem => { export const useMemberSort = (index: number, memberSort: MemberSortItem[]): MemberSortItem => {
const item = memberSort[index] ?? memberSort[0]; const item = memberSort[index] ?? memberSort[0];
@ -61,7 +64,7 @@ export const useMemberPowerSort = (
return getPowerLevel(b.userId) - getPowerLevel(a.userId); return getPowerLevel(b.userId) - getPowerLevel(a.userId);
}, },
[creators] [creators, getPowerLevel]
); );
return sort; return sort;

View file

@ -1,5 +1,6 @@
import { Room } from 'matrix-js-sdk'; import { Room } from 'matrix-js-sdk';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { IPowerLevels } from './usePowerLevels'; import { IPowerLevels } from './usePowerLevels';
import { useStateEvent } from './useStateEvent'; import { useStateEvent } from './useStateEvent';
import { MemberPowerTag, StateEvent } from '../../types/matrix/room'; import { MemberPowerTag, StateEvent } from '../../types/matrix/room';
@ -45,76 +46,91 @@ export const getUsedPowers = (powerLevels: IPowerLevels): Set<number> => {
return powers; return powers;
}; };
const DEFAULT_TAGS: PowerLevelTags = { type TFunction = (key: string) => string;
const getDefaultTags = (t: TFunction): PowerLevelTags => ({
9001: { 9001: {
name: 'Goku', name: t('RoomSettings.power_goku'),
color: '#ff6a00', color: '#ff6a00',
}, },
150: { 150: {
name: 'Manager', name: t('RoomSettings.power_manager'),
color: '#ff6a7f', color: '#ff6a7f',
}, },
101: { 101: {
name: 'Founder', name: t('RoomSettings.power_founder'),
color: '#0000ff', color: '#0000ff',
}, },
100: { 100: {
name: 'Admin', name: t('RoomSettings.power_admin'),
color: '#0088ff', color: '#0088ff',
}, },
50: { 50: {
name: 'Moderator', name: t('RoomSettings.power_moderator'),
color: '#1fd81f', color: '#1fd81f',
}, },
0: { 0: {
name: 'Member', name: t('RoomSettings.power_member'),
color: '#91cfdf', color: '#91cfdf',
}, },
[-1]: { [-1]: {
name: 'Muted', name: t('RoomSettings.power_muted'),
color: '#888888', color: '#888888',
}, },
}; });
const generateFallbackTag = (powerLevelTags: PowerLevelTags, power: number): MemberPowerTag => { const generateFallbackTag = (powerLevelTags: PowerLevelTags, power: number, t: TFunction): MemberPowerTag => {
const highToLow = sortPowers(getPowers(powerLevelTags)); const highToLow = sortPowers(getPowers(powerLevelTags));
const tagPower = highToLow.find((p) => p < power); const tagPower = highToLow.find((p) => p < power);
const tag = typeof tagPower === 'number' ? powerLevelTags[tagPower] : undefined; const tag = typeof tagPower === 'number' ? powerLevelTags[tagPower] : undefined;
return { return {
name: tag ? `${tag.name} ${power}` : `Team ${power}`, name: tag ? `${tag.name} ${power}` : `${t('RoomSettings.power_team')} ${power}`,
}; };
}; };
const LEGACY_CINNY_POWER_LEVEL_TAGS = 'in.cinny.room.power_level_tags' as StateEvent; const LEGACY_CINNY_POWER_LEVEL_TAGS = 'in.cinny.room.power_level_tags' as StateEvent;
export const usePowerLevelTags = (room: Room, powerLevels: IPowerLevels): PowerLevelTags => { export const usePowerLevelTags = (room: Room, powerLevels: IPowerLevels): PowerLevelTags => {
const { t } = useTranslation();
const tagsEvent = useStateEvent(room, StateEvent.PowerLevelTags); const tagsEvent = useStateEvent(room, StateEvent.PowerLevelTags);
const legacyTagsEvent = useStateEvent(room, LEGACY_CINNY_POWER_LEVEL_TAGS); const legacyTagsEvent = useStateEvent(room, LEGACY_CINNY_POWER_LEVEL_TAGS);
const activeTagsEvent = tagsEvent ?? legacyTagsEvent; const activeTagsEvent = tagsEvent ?? legacyTagsEvent;
const powerLevelTags: PowerLevelTags = useMemo(() => { const powerLevelTags: PowerLevelTags = useMemo(() => {
const defaultTags = getDefaultTags(t);
const content = activeTagsEvent?.getContent<PowerLevelTags>(); const content = activeTagsEvent?.getContent<PowerLevelTags>();
const powerToTags: PowerLevelTags = { ...content }; const powerToTags: PowerLevelTags = { ...content };
const powers = getUsedPowers(powerLevels); const powers = getUsedPowers(powerLevels);
Array.from(powers).forEach((power) => { Array.from(powers).forEach((power) => {
if (powerToTags[power]?.name === undefined) { if (powerToTags[power]?.name === undefined) {
powerToTags[power] = DEFAULT_TAGS[power] ?? generateFallbackTag(DEFAULT_TAGS, power); powerToTags[power] = defaultTags[power] ?? generateFallbackTag(defaultTags, power, t);
} }
}); });
return powerToTags; return powerToTags;
}, [powerLevels, activeTagsEvent]); }, [powerLevels, activeTagsEvent, t]);
return powerLevelTags; return powerLevelTags;
}; };
const generateFallbackTagSimple = (powerLevelTags: PowerLevelTags, power: number): MemberPowerTag => {
const highToLow = sortPowers(getPowers(powerLevelTags));
const tagPower = highToLow.find((p) => p < power);
const tag = typeof tagPower === 'number' ? powerLevelTags[tagPower] : undefined;
return {
name: tag ? `${tag.name} ${power}` : `#${power}`,
};
};
export const getPowerLevelTag = ( export const getPowerLevelTag = (
powerLevelTags: PowerLevelTags, powerLevelTags: PowerLevelTags,
powerLevel: number powerLevel: number
): MemberPowerTag => { ): MemberPowerTag => {
const tag: MemberPowerTag | undefined = powerLevelTags[powerLevel]; const tag: MemberPowerTag | undefined = powerLevelTags[powerLevel];
return tag ?? generateFallbackTag(powerLevelTags, powerLevel); return tag ?? generateFallbackTagSimple(powerLevelTags, powerLevel);
}; };

View file

@ -1,8 +1,14 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { MemberPowerTag } from '../../types/matrix/room'; import { MemberPowerTag } from '../../types/matrix/room';
const DEFAULT_TAG: MemberPowerTag = { export const useRoomCreatorsTag = (): MemberPowerTag => {
name: 'Founder', const { t } = useTranslation();
color: '#0000ff', return useMemo(
() => ({
name: t('RoomSettings.power_founder'),
color: '#0000ff',
}),
[t]
);
}; };
export const useRoomCreatorsTag = (): MemberPowerTag => DEFAULT_TAG;