localize room settings

This commit is contained in:
v.lagerev 2026-04-14 21:27:03 +03:00
parent 45dc4fa8cf
commit 148120a1d4
33 changed files with 836 additions and 291 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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