localize home

This commit is contained in:
heaven 2026-04-14 00:30:08 +03:00
parent 7f4a013eb5
commit fc8346e50d
8 changed files with 137 additions and 43 deletions

View file

@ -310,5 +310,42 @@
"event": "event",
"open": "Open",
"home": "Home"
},
"Home": {
"home": "Home",
"mark_as_read": "Mark as Read",
"no_rooms": "No Rooms",
"no_rooms_desc": "You do not have any rooms yet.",
"create_room": "Create Room",
"create_room_subtitle": "Build a room for real-time conversations.",
"explore_community": "Explore Community Rooms",
"join_with_address": "Join by Address",
"join_address_desc": "Enter an address to join a room or space. Addresses look like:",
"address": "Address",
"invalid_address": "Invalid Address",
"message_search": "Message Search",
"rooms": "Rooms",
"type": "Type",
"access": "Access",
"name": "Name",
"topic_optional": "Topic (Optional)",
"options": "Options",
"advanced_options": "Advanced Options",
"e2e_encryption": "End-to-End Encryption",
"e2e_encryption_desc": "Once this feature is enabled, it can't be disabled after the room is created.",
"knock_to_join": "Knock to Join",
"knock_to_join_desc": "Anyone can send a request to join this room.",
"allow_federation": "Allow Federation",
"allow_federation_desc": "Users from other servers can join.",
"rate_limited": "Server rate-limited your request for {{minutes}} minutes!",
"create": "Create",
"notifications": "Notifications",
"invite": "Invite",
"copy_link": "Copy Link",
"room_settings": "Room Settings",
"leave_room": "Leave Room",
"toggle_chat": "Toggle Chat",
"live_count": "{{count}} Live",
"open": "Open"
}
}

View file

@ -310,5 +310,42 @@
"event": "событие",
"open": "Открыть",
"home": "Главная"
},
"Home": {
"home": "Главная",
"mark_as_read": "Отметить прочитанным",
"no_rooms": "Нет комнат",
"no_rooms_desc": "У вас ещё нет комнат.",
"create_room": "Создать комнату",
"create_room_subtitle": "Создайте комнату для общения в реальном времени.",
"explore_community": "Обзор комнат сообщества",
"join_with_address": "Присоединиться по адресу",
"join_address_desc": "Введите адрес комнаты или пространства. Адреса выглядят так:",
"address": "Адрес",
"invalid_address": "Неверный адрес",
"message_search": "Поиск сообщений",
"rooms": "Комнаты",
"type": "Тип",
"access": "Доступ",
"name": "Название",
"topic_optional": "Тема (необязательно)",
"options": "Параметры",
"advanced_options": "Дополнительные параметры",
"e2e_encryption": "Сквозное шифрование",
"e2e_encryption_desc": "После включения эту функцию нельзя отключить после создания комнаты.",
"knock_to_join": "Запрос на вступление",
"knock_to_join_desc": "Любой может отправить запрос на вступление в эту комнату.",
"allow_federation": "Разрешить федерацию",
"allow_federation_desc": "Пользователи с других серверов могут присоединяться.",
"rate_limited": "Сервер ограничил частоту запросов на {{minutes}} мин.!",
"create": "Создать",
"notifications": "Уведомления",
"invite": "Пригласить",
"copy_link": "Копировать ссылку",
"room_settings": "Настройки комнаты",
"leave_room": "Покинуть комнату",
"toggle_chat": "Переключить чат",
"live_count": "{{count}} В эфире",
"open": "Открыть"
}
}

View file

@ -1,4 +1,5 @@
import React, { FormEventHandler, useState } from 'react';
import { useTranslation } from 'react-i18next';
import FocusTrap from 'focus-trap-react';
import {
Dialog,
@ -26,6 +27,7 @@ type JoinAddressProps = {
onCancel: () => void;
};
export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
const { t } = useTranslation();
const [invalid, setInvalid] = useState(false);
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
@ -80,7 +82,7 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
size="500"
>
<Box grow="Yes">
<Text size="H4">Join with Address</Text>
<Text size="H4">{t('Home.join_with_address')}</Text>
</Box>
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
@ -95,7 +97,7 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
>
<Box direction="Column" gap="200">
<Text priority="400" size="T300">
Enter public address to join the community. Addresses looks like:
{t('Home.join_address_desc')}
</Text>
<Text as="ul" size="T200" priority="300" style={{ paddingLeft: config.space.S400 }}>
<li>#community:server</li>
@ -104,7 +106,7 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
</Text>
</Box>
<Box direction="Column" gap="100">
<Text size="L400">Address</Text>
<Text size="L400">{t('Home.address')}</Text>
<Input
size="500"
autoFocus
@ -115,12 +117,12 @@ export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
/>
{invalid && (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>Invalid Address</b>
<b>{t('Home.invalid_address')}</b>
</Text>
)}
</Box>
<Button type="submit" variant="Primary">
<Text size="B400">Open</Text>
<Text size="B400">{t('Home.open')}</Text>
</Button>
</Box>
</Dialog>

View file

@ -1,4 +1,5 @@
import React, { FormEventHandler, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MatrixError, Room, JoinRule } from 'matrix-js-sdk';
import {
Box,
@ -70,6 +71,7 @@ export function CreateRoomForm({
space,
onCreate,
}: CreateRoomFormProps) {
const { t } = useTranslation();
const mx = useMatrixClient();
const alive = useAlive();
@ -162,7 +164,7 @@ export function CreateRoomForm({
<Box as="form" onSubmit={handleSubmit} grow="Yes" direction="Column" gap="500">
{!space && (
<Box direction="Column" gap="100">
<Text size="L400">Type</Text>
<Text size="L400">{t('Home.type')}</Text>
<CreateRoomTypeSelector
value={type}
onSelect={setType}
@ -172,7 +174,7 @@ export function CreateRoomForm({
</Box>
)}
<Box direction="Column" gap="100">
<Text size="L400">Access</Text>
<Text size="L400">{t('Home.access')}</Text>
<CreateRoomAccessSelector
value={access}
onSelect={setAccess}
@ -182,7 +184,7 @@ export function CreateRoomForm({
/>
</Box>
<Box shrink="No" direction="Column" gap="100">
<Text size="L400">Name</Text>
<Text size="L400">{t('Home.name')}</Text>
<Input
required
before={<Icon size="100" src={getCreateRoomAccessToIcon(access, type)} />}
@ -196,7 +198,7 @@ export function CreateRoomForm({
/>
</Box>
<Box shrink="No" direction="Column" gap="100">
<Text size="L400">Topic (Optional)</Text>
<Text size="L400">{t('Home.topic_optional')}</Text>
<TextArea
name="topicTextAria"
size="500"
@ -210,7 +212,7 @@ export function CreateRoomForm({
<Box shrink="No" direction="Column" gap="100">
<Box gap="200" alignItems="End">
<Text size="L400">Options</Text>
<Text size="L400">{t('Home.options')}</Text>
<Box grow="Yes" justifyContent="End">
<Chip
radii="Pill"
@ -218,7 +220,7 @@ export function CreateRoomForm({
onClick={() => setAdvance(!advance)}
type="button"
>
<Text size="T200">Advanced Options</Text>
<Text size="T200">{t('Home.advanced_options')}</Text>
</Chip>
</Box>
</Box>
@ -245,8 +247,8 @@ export function CreateRoomForm({
gap="500"
>
<SettingTile
title="End-to-End Encryption"
description="Once this feature is enabled, it can't be disabled after the room is created."
title={t('Home.e2e_encryption')}
description={t('Home.e2e_encryption_desc')}
after={
<Switch
variant="Primary"
@ -265,8 +267,8 @@ export function CreateRoomForm({
gap="500"
>
<SettingTile
title="Knock to Join"
description="Anyone can send request to join this room."
title={t('Home.knock_to_join')}
description={t('Home.knock_to_join_desc')}
after={
<Switch
variant="Primary"
@ -288,8 +290,8 @@ export function CreateRoomForm({
gap="500"
>
<SettingTile
title="Allow Federation"
description="Users from other servers can join."
title={t('Home.allow_federation')}
description={t('Home.allow_federation_desc')}
after={
<Switch
variant="Primary"
@ -316,9 +318,11 @@ export function CreateRoomForm({
<Text size="T300" style={{ color: color.Critical.Main }}>
<b>
{error instanceof MatrixError && error.name === ErrorCode.M_LIMIT_EXCEEDED
? `Server rate-limited your request for ${millisecondsToMinutes(
(error.data.retry_after_ms as number | undefined) ?? 0
)} minutes!`
? t('Home.rate_limited', {
minutes: millisecondsToMinutes(
(error.data.retry_after_ms as number | undefined) ?? 0
),
})
: error.message}
</b>
</Text>
@ -333,7 +337,7 @@ export function CreateRoomForm({
disabled={disabled}
before={loading && <Spinner variant="Primary" fill="Solid" size="200" />}
>
<Text size="B500">Create</Text>
<Text size="B500">{t('Home.create')}</Text>
</Button>
</Box>
</Box>

View file

@ -1,4 +1,5 @@
import React, { MouseEventHandler, forwardRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Room } from 'matrix-js-sdk';
import {
Avatar,
@ -67,6 +68,7 @@ type RoomNavItemMenuProps = {
};
const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
({ room, requestClose, notificationMode }, ref) => {
const { t } = useTranslation();
const mx = useMatrixClient();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
@ -121,7 +123,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
disabled={!unread}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Mark as Read
{t('Home.mark_as_read')}
</Text>
</MenuItem>
<RoomNotificationModeSwitcher roomId={room.roomId} value={notificationMode}>
@ -140,7 +142,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
onClick={handleOpen}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Notifications
{t('Home.notifications')}
</Text>
</MenuItem>
)}
@ -159,7 +161,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
disabled={!canInvite}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Invite
{t('Home.invite')}
</Text>
</MenuItem>
<MenuItem
@ -169,7 +171,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
radii="300"
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Copy Link
{t('Home.copy_link')}
</Text>
</MenuItem>
<MenuItem
@ -179,7 +181,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
radii="300"
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Room Settings
{t('Home.room_settings')}
</Text>
</MenuItem>
</Box>
@ -198,7 +200,7 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
aria-pressed={promptLeave}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Leave Room
{t('Home.leave_room')}
</Text>
</MenuItem>
{promptLeave && (
@ -218,13 +220,14 @@ const RoomNavItemMenu = forwardRef<HTMLDivElement, RoomNavItemMenuProps>(
);
function CallChatToggle() {
const { t } = useTranslation();
const [chat, setChat] = useAtom(callChatAtom);
return (
<IconButton
onClick={() => setChat(!chat)}
aria-pressed={chat}
aria-label="Toggle Chat"
aria-label={t('Home.toggle_chat')}
variant="Background"
fill="None"
size="300"
@ -251,6 +254,7 @@ export function RoomNavItem({
notificationMode,
linkPath,
}: RoomNavItemProps) {
const { t } = useTranslation();
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [hover, setHover] = useState(false);
@ -370,7 +374,7 @@ export function RoomNavItem({
{room.isCallRoom() && callMembers.length > 0 && (
<Badge variant="Critical" fill="Solid" size="400">
<Text as="span" size="L400" truncate>
{callMembers.length} Live
{t('Home.live_count', { count: callMembers.length })}
</Text>
</Badge>
)}

View file

@ -1,5 +1,6 @@
import React from 'react';
import { Box, Icon, Icons, Scroll, IconButton } from 'folds';
import { useTranslation } from 'react-i18next';
import {
Page,
PageContent,
@ -14,6 +15,7 @@ import { CreateRoomForm } from '../../../features/create-room';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
export function HomeCreateRoom() {
const { t } = useTranslation();
const screenSize = useScreenSizeContext();
const { navigateRoom } = useRoomNavigate();
@ -41,8 +43,8 @@ export function HomeCreateRoom() {
<Box direction="Column" gap="700">
<PageHero
icon={<Icon size="600" src={Icons.Hash} />}
title="Create Room"
subTitle="Build a Room for Real-Time Conversations."
title={t('Home.create_room')}
subTitle={t('Home.create_room_subtitle')}
/>
<CreateRoomForm onCreate={navigateRoom} />
</Box>

View file

@ -1,5 +1,6 @@
import React, { MouseEventHandler, forwardRef, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
Avatar,
Box,
@ -70,6 +71,7 @@ type HomeMenuProps = {
requestClose: () => void;
};
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
const { t } = useTranslation();
const orphanRooms = useHomeRooms();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
@ -92,7 +94,7 @@ const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, re
aria-disabled={!unread}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Mark as Read
{t('Home.mark_as_read')}
</Text>
</MenuItem>
</Box>
@ -101,6 +103,7 @@ const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, re
});
function HomeHeader() {
const { t } = useTranslation();
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
@ -117,7 +120,7 @@ function HomeHeader() {
<Box alignItems="Center" grow="Yes" gap="300">
<Box grow="Yes">
<Text size="H4" truncate>
Home
{t('Home.home')}
</Text>
</Box>
<Box>
@ -153,6 +156,7 @@ function HomeHeader() {
}
function HomeEmpty() {
const { t } = useTranslation();
const navigate = useNavigate();
return (
@ -161,19 +165,19 @@ function HomeEmpty() {
icon={<Icon size="600" src={Icons.Hash} />}
title={
<Text size="H5" align="Center">
No Rooms
{t('Home.no_rooms')}
</Text>
}
content={
<Text size="T300" align="Center">
You do not have any rooms yet.
{t('Home.no_rooms_desc')}
</Text>
}
options={
<>
<Button onClick={() => navigate(getHomeCreatePath())} variant="Secondary" size="300">
<Text size="B300" truncate>
Create Room
{t('Home.create_room')}
</Text>
</Button>
<Button
@ -183,7 +187,7 @@ function HomeEmpty() {
size="300"
>
<Text size="B300" truncate>
Explore Community Rooms
{t('Home.explore_community')}
</Text>
</Button>
</>
@ -195,6 +199,7 @@ function HomeEmpty() {
const DEFAULT_CATEGORY_ID = makeNavCategoryId('home', 'room');
export function Home() {
const { t } = useTranslation();
const mx = useMatrixClient();
useNavToActivePathMapper('home');
const scrollRef = useRef<HTMLDivElement>(null);
@ -250,7 +255,7 @@ export function Home() {
</Avatar>
<Box as="span" grow="Yes">
<Text as="span" size="Inherit" truncate>
Create Room
{t('Home.create_room')}
</Text>
</Box>
</Box>
@ -269,7 +274,7 @@ export function Home() {
</Avatar>
<Box as="span" grow="Yes">
<Text as="span" size="Inherit" truncate>
Join with Address
{t('Home.join_with_address')}
</Text>
</Box>
</Box>
@ -304,7 +309,7 @@ export function Home() {
</Avatar>
<Box as="span" grow="Yes">
<Text as="span" size="Inherit" truncate>
Message Search
{t('Home.message_search')}
</Text>
</Box>
</Box>
@ -319,7 +324,7 @@ export function Home() {
data-category-id={DEFAULT_CATEGORY_ID}
onClick={handleCategoryClick}
>
Rooms
{t('Home.rooms')}
</RoomNavCategoryButton>
</NavCategoryHeader>
<div

View file

@ -1,6 +1,7 @@
import React, { MouseEventHandler, forwardRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text, config, toRem } from 'folds';
import { useTranslation } from 'react-i18next';
import { useAtomValue } from 'jotai';
import FocusTrap from 'focus-trap-react';
import { useOrphanRooms } from '../../../state/hooks/roomList';
@ -31,6 +32,7 @@ type HomeMenuProps = {
requestClose: () => void;
};
const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, ref) => {
const { t } = useTranslation();
const orphanRooms = useHomeRooms();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const unread = useRoomsUnread(orphanRooms, roomToUnreadAtom);
@ -53,7 +55,7 @@ const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, re
aria-disabled={!unread}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Mark as Read
{t('Home.mark_as_read')}
</Text>
</MenuItem>
</Box>
@ -62,6 +64,7 @@ const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, re
});
export function HomeTab() {
const { t } = useTranslation();
const navigate = useNavigate();
const mx = useMatrixClient();
const screenSize = useScreenSizeContext();
@ -95,7 +98,7 @@ export function HomeTab() {
return (
<SidebarItem active={homeSelected}>
<SidebarItemTooltip tooltip="Home">
<SidebarItemTooltip tooltip={t('Home.home')}>
{(triggerRef) => (
<SidebarAvatar
as="button"