mirror of
https://github.com/suitenumerique/messages.git
synced 2026-08-17 21:25:41 +02:00
🌐(frontend) internationalize missing strings
Some strings did not use `t()`. We know use that everywhere. In order to prevent the use of literal string as text, we enable the eslint rule `i18next/no-literal-string`. As this rule triggers warning for all material-icons span elements we replace all of them by Icon component provided by UI-Kit (which was technical debt)
This commit is contained in:
Generated
+25
@@ -55,6 +55,7 @@
|
||||
"@vitest/ui": "4.1.8",
|
||||
"deepmerge": "4.3.1",
|
||||
"eslint": "9.39.2",
|
||||
"eslint-plugin-i18next": "6.1.4",
|
||||
"eslint-plugin-react-hooks": "7.1.1",
|
||||
"i18next-cli": "1.34.1",
|
||||
"jsdom": "27.4.0",
|
||||
@@ -13905,6 +13906,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-i18next": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-i18next/-/eslint-plugin-i18next-6.1.4.tgz",
|
||||
"integrity": "sha512-BekXQu1VaVFkREepQGsntBYoKuPsf89V16n/s2xpKMtsw0/lDseds1wBhj3RtO1dKnHsfTPaG+xul1vF0R7LEQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"requireindex": "~1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-hooks": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
|
||||
@@ -21266,6 +21281,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requireindex": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz",
|
||||
"integrity": "sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.5"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"@vitest/ui": "4.1.8",
|
||||
"deepmerge": "4.3.1",
|
||||
"eslint": "9.39.2",
|
||||
"eslint-plugin-i18next": "6.1.4",
|
||||
"eslint-plugin-react-hooks": "7.1.1",
|
||||
"i18next-cli": "1.34.1",
|
||||
"jsdom": "27.4.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||
import { Icon } from "@gouvfr-lasuite/ui-kit";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ImportTaskRecap } from "@/hooks/use-task-status";
|
||||
import clsx from "clsx";
|
||||
@@ -26,9 +27,7 @@ export const StepCompleted = ({ onClose, recap }: StepCompletedProps) => {
|
||||
'importer-completed__badge',
|
||||
{ 'importer-completed__badge--warning': showFailureWarning }
|
||||
)}>
|
||||
<span className="material-icons">
|
||||
{showFailureWarning ? 'report_problem' : 'check'}
|
||||
</span>
|
||||
<Icon name={showFailureWarning ? 'report_problem' : 'check'} />
|
||||
</span>
|
||||
<div className="importer-completed__heading">
|
||||
<p className="importer-completed__title">{t('Import complete')}</p>
|
||||
@@ -56,13 +55,12 @@ export const StepCompleted = ({ onClose, recap }: StepCompletedProps) => {
|
||||
{t('Failed: {{count}} messages', { count: failure })}
|
||||
</span>
|
||||
{showFailureWarning && (
|
||||
<span
|
||||
className="material-icons importer-completed__percent-warning"
|
||||
<Icon
|
||||
name="report_problem"
|
||||
className="importer-completed__percent-warning"
|
||||
aria-label={t('High failure rate')}
|
||||
title={t('High failure rate')}
|
||||
>
|
||||
report_problem
|
||||
</span>
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@ import z from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||
import { Spinner } from "@gouvfr-lasuite/ui-kit";
|
||||
import { Icon, Spinner } from "@gouvfr-lasuite/ui-kit";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { importFileCreateResponse202, importImapCreateResponse202, useImportFileCreate, useImportImapCreate } from "@/features/api/gen";
|
||||
@@ -277,8 +277,8 @@ export const StepForm = ({ onUploading, onSuccess, onError, error, step }: StepF
|
||||
<RhfFileUploader
|
||||
name="archive_file"
|
||||
accept=".eml,.mbox,.pst"
|
||||
icon={<span className="material-icons">inventory_2</span>}
|
||||
fileSelectedIcon={<span className="material-icons">inventory_2</span>}
|
||||
icon={<Icon name="inventory_2" />}
|
||||
fileSelectedIcon={<Icon name="inventory_2" />}
|
||||
bigText={t('Drag and drop an archive here')}
|
||||
text={t('EML, MBOX or PST')}
|
||||
fullWidth
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Icon } from "@gouvfr-lasuite/ui-kit";
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -10,7 +11,7 @@ export const DropZone = ({ isHidden }: DropZoneProps) => {
|
||||
|
||||
return (
|
||||
<div className={clsx("attachment-uploader__dropzone", { "attachment-uploader__dropzone--hidden": isHidden })} aria-hidden={isHidden}>
|
||||
<span className="material-icons">file_download</span>
|
||||
<Icon name="file_download" />
|
||||
{t("Drop your attachments here")}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -679,14 +679,13 @@ export const MessageForm = ({
|
||||
name="to"
|
||||
label={t("To:")}
|
||||
autoFocus={mode === "forward"}
|
||||
// icon={<span className="material-icons">group</span>}
|
||||
text={form.formState.errors.to && !Array.isArray(form.formState.errors.to) ? form.formState.errors.to.message : t("Enter the email addresses of the recipients separated by commas")}
|
||||
textItems={Array.isArray(form.formState.errors.to) ? form.formState.errors.to?.map((error, index) => t(error!.message as string, { email: form.getValues('to')?.[index] })) : []}
|
||||
disabled={!canWriteMessages}
|
||||
rightText={
|
||||
<div className="form-field-options">
|
||||
<Button tabIndex={-1} type="button" size="nano" variant={showCCField ? "bordered" : "tertiary"} onClick={() => setShowCCField(!showCCField)} disabled={!canWriteMessages}>cc</Button>
|
||||
<Button tabIndex={-1} type="button" size="nano" variant={showBCCField ? "bordered" : "tertiary"} onClick={() => setShowBCCField(!showBCCField)} disabled={!canWriteMessages}>bcc</Button>
|
||||
<Button tabIndex={-1} type="button" size="nano" variant={showCCField ? "bordered" : "tertiary"} onClick={() => setShowCCField(!showCCField)} disabled={!canWriteMessages}>{t("cc")}</Button>
|
||||
<Button tabIndex={-1} type="button" size="nano" variant={showBCCField ? "bordered" : "tertiary"} onClick={() => setShowBCCField(!showBCCField)} disabled={!canWriteMessages}>{t("bcc")}</Button>
|
||||
</div> as unknown as string // TODO: Allow ReactNode as rightText in Cunningham
|
||||
}
|
||||
fullWidth
|
||||
@@ -699,7 +698,6 @@ export const MessageForm = ({
|
||||
<RhfContactComboBox
|
||||
name="cc"
|
||||
label={t("Copy: ")}
|
||||
// icon={<span className="material-icons">group</span>}
|
||||
text={form.formState.errors.cc && !Array.isArray(form.formState.errors.cc) ? t(form.formState.errors.cc.message as string) : t("Enter the email addresses of the recipients separated by commas")}
|
||||
textItems={Array.isArray(form.formState.errors.cc) ? form.formState.errors.cc?.map((error, index) => t(error!.message as string, { email: form.getValues('cc')?.[index] })) : []}
|
||||
disabled={!canWriteMessages}
|
||||
@@ -714,7 +712,6 @@ export const MessageForm = ({
|
||||
<RhfContactComboBox
|
||||
name="bcc"
|
||||
label={t("Blind copy: ")}
|
||||
// icon={<span className="material-icons">visibility_off</span>}
|
||||
text={form.formState.errors.bcc && !Array.isArray(form.formState.errors.bcc) ? t(form.formState.errors.bcc.message as string) : t("Enter the email addresses of the recipients separated by commas")}
|
||||
textItems={Array.isArray(form.formState.errors.bcc) ? form.formState.errors.bcc?.map((error, index) => t(error!.message as string, { email: form.getValues('bcc')?.[index] })) : []}
|
||||
disabled={!canWriteMessages}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ALL_MESSAGES_FOLDER, MAILBOX_FOLDERS } from "@/features/layouts/components/mailbox-panel/components/mailbox-list";
|
||||
import { SearchHelper } from "@/features/utils/search-helper";
|
||||
import { Label } from "@gouvfr-lasuite/ui-kit";
|
||||
import { Icon, Label } from "@gouvfr-lasuite/ui-kit";
|
||||
import { Button, Checkbox, Input, Select } from "@gouvfr-lasuite/cunningham-react";
|
||||
import { useId, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -111,7 +111,7 @@ type FolderOptionProps = {
|
||||
const FolderOption = ({ label, icon }: FolderOptionProps) => {
|
||||
return (
|
||||
<div className="search__filters-folder-option">
|
||||
<span className="material-icons">{icon}</span>
|
||||
<Icon name={icon} />
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -103,7 +103,7 @@ export const SearchInput = () => {
|
||||
title={t("Reset")}
|
||||
size="small"
|
||||
>
|
||||
<span className="material-icons">close</span>
|
||||
<Icon name="close" />
|
||||
<span className="c__offscreen">{t("Reset")}</span>
|
||||
</Button>
|
||||
)}
|
||||
@@ -114,7 +114,7 @@ export const SearchInput = () => {
|
||||
title={showFilters ? t("Close filters") : t("Open filters")}
|
||||
size="small"
|
||||
>
|
||||
<span className="material-icons">tune</span>
|
||||
<Icon name="tune" />
|
||||
<span className="c__offscreen">{showFilters ? t("Close filters") : t("Open filters")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -127,6 +127,7 @@ function AdminLayoutContent({
|
||||
|
||||
export function AdminLayout(props: AdminLayoutProps) {
|
||||
const { theme, variant } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<LayoutProvider>
|
||||
@@ -136,7 +137,7 @@ export function AdminLayout(props: AdminLayoutProps) {
|
||||
leftPanelContent={null}
|
||||
hideSearch
|
||||
hideLeftPanelOnDesktop={true}
|
||||
icon={<Link to="/"><img src={`/images/${theme}/app-logo-${variant}.svg`} alt="logo" height={40} /></Link>}
|
||||
icon={<Link to="/"><img src={`/images/${theme}/app-logo-${variant}.svg`} alt={t("logo")} height={40} /></Link>}
|
||||
>
|
||||
<AdminMailDomainProvider>
|
||||
<AdminLayoutContent {...props} />
|
||||
|
||||
+1
@@ -57,6 +57,7 @@ export const RhfColorPaletteField = (props: { name: string }) => {
|
||||
htmlFor="color"
|
||||
style={{ '--char-color': charColor } as React.CSSProperties}
|
||||
>
|
||||
{/* eslint-disable-next-line i18next/no-literal-string */}
|
||||
<span className="color-palette-field__input-of-hell__icon">a</span>
|
||||
<input
|
||||
type="color"
|
||||
|
||||
+3
-3
@@ -376,18 +376,18 @@ export const LabelItem = ({ level = 0, onEdit, canManage, defaultFoldState, ...l
|
||||
options={[
|
||||
{
|
||||
label: t('Edit'),
|
||||
icon: <span className="material-icons">edit</span>,
|
||||
icon: <Icon name="edit" />,
|
||||
callback: () => onEdit(label),
|
||||
},
|
||||
{
|
||||
label: t('Add a sub-label'),
|
||||
icon: <span className="material-icons">add</span>,
|
||||
icon: <Icon name="add" />,
|
||||
callback: () => onEdit({ name: `${label.name}/`, color: label.color }),
|
||||
showSeparator: true,
|
||||
},
|
||||
{
|
||||
label: t('Delete'),
|
||||
icon: <span className="material-icons">delete</span>,
|
||||
icon: <Icon name="delete" />,
|
||||
callback: handleDelete,
|
||||
variant: 'danger'
|
||||
},
|
||||
|
||||
+3
-3
@@ -306,7 +306,7 @@ const ThreadPanelTitle = ({ selectedThreadIds, isAllSelected, isSomeSelected, is
|
||||
},
|
||||
...([SelectionReadStatus.MIXED, SelectionReadStatus.UNREAD].includes(selectionReadStatus) ? [{
|
||||
label: markAllTooltip,
|
||||
icon: <span className="material-icons">drafts</span>,
|
||||
icon: <Icon name="drafts" />,
|
||||
callback: () => {
|
||||
markAsReadAt({
|
||||
threadIds: threadIdsToMark,
|
||||
@@ -320,7 +320,7 @@ const ThreadPanelTitle = ({ selectedThreadIds, isAllSelected, isSomeSelected, is
|
||||
}] : []),
|
||||
...([SelectionReadStatus.MIXED, SelectionReadStatus.READ, SelectionReadStatus.NONE].includes(selectionReadStatus) ? [{
|
||||
label: markAllUnreadLabel,
|
||||
icon: <span className="material-icons">mark_email_unread</span>,
|
||||
icon: <Icon name="mark_email_unread" />,
|
||||
callback: () => {
|
||||
// Close the open thread before the mutation so the visibility
|
||||
// observer cannot re-mark the newly-unread messages as read.
|
||||
@@ -363,7 +363,7 @@ const ThreadPanelTitle = ({ selectedThreadIds, isAllSelected, isSomeSelected, is
|
||||
<Tooltip content={t('More options')}>
|
||||
<Button
|
||||
onClick={() => setIsDropdownOpen(true)}
|
||||
icon={<span className="material-icons">more_vert</span>}
|
||||
icon={<Icon name="more_vert" />}
|
||||
variant="tertiary"
|
||||
aria-label={t('More options')}
|
||||
size="nano"
|
||||
|
||||
+2
-2
@@ -904,7 +904,7 @@ export const CalendarInvite = ({
|
||||
setIsSubmitting(false);
|
||||
addToast(
|
||||
<ToasterItem type="info">
|
||||
<span className="material-icons">check_circle</span>
|
||||
<Icon name="check_circle" />
|
||||
<span>
|
||||
{pendingActionIsRsvp
|
||||
? t("Response saved — the organizer will be notified")
|
||||
@@ -934,7 +934,7 @@ export const CalendarInvite = ({
|
||||
setPendingActionIsRsvp(false);
|
||||
addToast(
|
||||
<ToasterItem type="error">
|
||||
<span className="material-icons">error</span>
|
||||
<Icon name="error" />
|
||||
<span>{taskStatus.error ?? t("An unexpected error occurred.")}</span>
|
||||
</ToasterItem>,
|
||||
);
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@
|
||||
// so the visual styling bundled in ui-kit's style.css applies as-is.
|
||||
import { ReactNode, useState } from "react";
|
||||
import { Button, useCunningham } from "@gouvfr-lasuite/cunningham-react";
|
||||
import { Icon } from "@gouvfr-lasuite/ui-kit";
|
||||
import type { DropdownMenuOption, UserData } from "@gouvfr-lasuite/ui-kit";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AccessRoleDropdown } from "./access-role-dropdown";
|
||||
@@ -83,7 +84,7 @@ const InvitationUserSelectorItem = <UserType,>({
|
||||
color="neutral"
|
||||
size="nano"
|
||||
onClick={() => onRemoveUser?.(user)}
|
||||
icon={<span className="material-icons">close</span>}
|
||||
icon={<Icon name="close" />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+3
-2
@@ -30,6 +30,7 @@ import {
|
||||
type QuickSearchData,
|
||||
QuickSearchGroup,
|
||||
QuickSearchItemTemplate,
|
||||
Icon,
|
||||
ShareInvitationItem,
|
||||
type DropdownMenuOption,
|
||||
type UserData,
|
||||
@@ -71,7 +72,7 @@ const SearchUserItem = <UserType,>({ user }: SearchUserItemProps<UserType>) => {
|
||||
right={
|
||||
<div className="c__search-user-item-right">
|
||||
<span>{tc("components.share.item.add")}</span>
|
||||
<span className="material-icons">add</span>
|
||||
<Icon name="add" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@@ -533,7 +534,7 @@ const ShowMoreButton = ({ show, onShowMore }: ShowMoreButtonProps) => {
|
||||
<Button
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
icon={<span className="material-icons">arrow_downward</span>}
|
||||
icon={<Icon name="arrow_downward" />}
|
||||
onClick={onShowMore}
|
||||
>
|
||||
{tc("components.share.members.load_more")}
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ const ThreadMessageHeader = ({
|
||||
{message.is_trashed && (
|
||||
<Banner
|
||||
type="info"
|
||||
icon={<span className="material-icons">restore_from_trash</span>}
|
||||
icon={<Icon name="restore_from_trash" />}
|
||||
fullWidth
|
||||
actions={canEditThread ? [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { StatusEnum, useTasksRetrieve } from "@/features/api/gen";
|
||||
import { addToast, ToasterItem } from "@/features/ui/components/toaster";
|
||||
import soundbox from "@/features/utils/soundbox";
|
||||
import { Spinner } from "@gouvfr-lasuite/ui-kit";
|
||||
import { Icon, Spinner } from "@gouvfr-lasuite/ui-kit";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Id, toast } from "react-toastify";
|
||||
@@ -49,7 +49,7 @@ export const QueueMessage = ({ taskId, onSettled }: QueueMessageProps) => {
|
||||
toast.update(toastId, {
|
||||
render: (
|
||||
<ToasterItem type="error">
|
||||
<span className="material-icons">error</span>
|
||||
<Icon name="error" />
|
||||
<span>{t('The message could not be sent.')}</span>
|
||||
</ToasterItem>
|
||||
),
|
||||
@@ -71,7 +71,7 @@ export const QueueMessage = ({ taskId, onSettled }: QueueMessageProps) => {
|
||||
toast.update(toastId, {
|
||||
render: (
|
||||
<ToasterItem type="info">
|
||||
<span className="material-icons">check_circle</span>
|
||||
<Icon name="check_circle" />
|
||||
<span>{t('Message sent successfully')}</span>
|
||||
</ToasterItem>
|
||||
),
|
||||
@@ -83,7 +83,7 @@ export const QueueMessage = ({ taskId, onSettled }: QueueMessageProps) => {
|
||||
toast.update(toastId, {
|
||||
render: (
|
||||
<ToasterItem type="error">
|
||||
<span className="material-icons">error</span>
|
||||
<Icon name="error" />
|
||||
<span>{t('The message could not be sent.')}</span>
|
||||
</ToasterItem>
|
||||
),
|
||||
@@ -102,7 +102,7 @@ export const QueueMessage = ({ taskId, onSettled }: QueueMessageProps) => {
|
||||
toast.update(toastId, {
|
||||
render: (
|
||||
<ToasterItem type="warning">
|
||||
<span className="material-icons">schedule_send</span>
|
||||
<Icon name="schedule_send" />
|
||||
<span>{t('Sending is taking longer than expected. You can track your message in the Outbox.')}</span>
|
||||
</ToasterItem>
|
||||
),
|
||||
|
||||
@@ -56,7 +56,7 @@ export const LabelBadge = ({ label, removable = false, linkable = false, compact
|
||||
}
|
||||
}]}
|
||||
>
|
||||
<span className="material-icons">label_off</span>
|
||||
<Icon name="label_off" />
|
||||
<span>{t('Label "{{label}}" removed from this conversation.', { label: label.name })}</span>
|
||||
</ToasterItem>,
|
||||
{ toastId }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button } from "@gouvfr-lasuite/cunningham-react";
|
||||
import { Icon } from "@gouvfr-lasuite/ui-kit";
|
||||
import clsx from "clsx";
|
||||
import { useMemo } from "react";
|
||||
import { Slide, ToastContainer, ToastContentProps, toast } from "react-toastify";
|
||||
@@ -57,7 +58,7 @@ export const ToasterItem = ({
|
||||
color={buttonColor}
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
icon={action.icon && <span className="material-icons">{action.icon}</span>}
|
||||
icon={action.icon && <Icon name={action.icon} />}
|
||||
>{action.showLabel || !action.icon && action.label}</Button>
|
||||
))}
|
||||
{closeButton && (
|
||||
@@ -66,7 +67,7 @@ export const ToasterItem = ({
|
||||
color={buttonColor}
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
icon={<span className="material-icons">close</span>}
|
||||
icon={<Icon name="close" />}
|
||||
></Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable i18next/no-literal-string */
|
||||
import React from "react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
@@ -25,12 +25,12 @@ const HomePage = () => {
|
||||
hideLeftPanelOnDesktop
|
||||
leftPanelContent={<LeftPanel />}
|
||||
rightHeaderContent={<LanguagePicker />}
|
||||
icon={<img src={`/images/${theme}/app-logo-${variant}.svg`} alt="logo" height={40} />}
|
||||
icon={<img src={`/images/${theme}/app-logo-${variant}.svg`} alt={t("logo")} height={40} />}
|
||||
>
|
||||
<div id={SKIP_LINK_TARGET_ID} className="app__home">
|
||||
<HomeGutter>
|
||||
<Hero
|
||||
logo={<img src={`/images/${theme}/app-icon-${variant}.svg`} alt="Messages Logo" width={64} />}
|
||||
logo={<img src={`/images/${theme}/app-icon-${variant}.svg`} alt={t("Messages Logo")} width={64} />}
|
||||
title={t("Simple and intuitive messaging")}
|
||||
banner={`/images/banner-${variant}.webp`}
|
||||
subtitle={t("Send and receive your messages in an instant.")}
|
||||
|
||||
Reference in New Issue
Block a user