diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index 85541bd2..1eca7964 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -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",
diff --git a/src/frontend/package.json b/src/frontend/package.json
index c2410c6d..163789fb 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -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",
diff --git a/src/frontend/src/features/controlled-modals/message-importer/step-completed.tsx b/src/frontend/src/features/controlled-modals/message-importer/step-completed.tsx
index aebae116..517511d3 100644
--- a/src/frontend/src/features/controlled-modals/message-importer/step-completed.tsx
+++ b/src/frontend/src/features/controlled-modals/message-importer/step-completed.tsx
@@ -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 }
)}>
-
- {showFailureWarning ? 'report_problem' : 'check'}
-
+
{t('Import complete')}
@@ -56,13 +55,12 @@ export const StepCompleted = ({ onClose, recap }: StepCompletedProps) => {
{t('Failed: {{count}} messages', { count: failure })}
{showFailureWarning && (
-
- report_problem
-
+ />
)}
)}
diff --git a/src/frontend/src/features/controlled-modals/message-importer/step-form.tsx b/src/frontend/src/features/controlled-modals/message-importer/step-form.tsx
index 0b8f8a24..72aeeb26 100644
--- a/src/frontend/src/features/controlled-modals/message-importer/step-form.tsx
+++ b/src/frontend/src/features/controlled-modals/message-importer/step-form.tsx
@@ -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
inventory_2}
- fileSelectedIcon={inventory_2 }
+ icon={ }
+ fileSelectedIcon={ }
bigText={t('Drag and drop an archive here')}
text={t('EML, MBOX or PST')}
fullWidth
diff --git a/src/frontend/src/features/forms/components/message-form/dropzone.tsx b/src/frontend/src/features/forms/components/message-form/dropzone.tsx
index d75dff46..87930b93 100644
--- a/src/frontend/src/features/forms/components/message-form/dropzone.tsx
+++ b/src/frontend/src/features/forms/components/message-form/dropzone.tsx
@@ -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 (
- file_download
+
{t("Drop your attachments here")}
);
diff --git a/src/frontend/src/features/forms/components/message-form/index.tsx b/src/frontend/src/features/forms/components/message-form/index.tsx
index 68ef3a91..54d3e21e 100644
--- a/src/frontend/src/features/forms/components/message-form/index.tsx
+++ b/src/frontend/src/features/forms/components/message-form/index.tsx
@@ -679,14 +679,13 @@ export const MessageForm = ({
name="to"
label={t("To:")}
autoFocus={mode === "forward"}
- // icon={group }
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={
- setShowCCField(!showCCField)} disabled={!canWriteMessages}>cc
- setShowBCCField(!showBCCField)} disabled={!canWriteMessages}>bcc
+ setShowCCField(!showCCField)} disabled={!canWriteMessages}>{t("cc")}
+ setShowBCCField(!showBCCField)} disabled={!canWriteMessages}>{t("bcc")}
as unknown as string // TODO: Allow ReactNode as rightText in Cunningham
}
fullWidth
@@ -699,7 +698,6 @@ export const MessageForm = ({
group}
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 = ({
visibility_off}
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}
diff --git a/src/frontend/src/features/forms/components/search-filters-form/index.tsx b/src/frontend/src/features/forms/components/search-filters-form/index.tsx
index 6717e66b..8de1f9cf 100644
--- a/src/frontend/src/features/forms/components/search-filters-form/index.tsx
+++ b/src/frontend/src/features/forms/components/search-filters-form/index.tsx
@@ -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 (
- {icon}
+
{label}
);
diff --git a/src/frontend/src/features/forms/components/search-input/index.tsx b/src/frontend/src/features/forms/components/search-input/index.tsx
index 2ba30cf6..0bab73eb 100644
--- a/src/frontend/src/features/forms/components/search-input/index.tsx
+++ b/src/frontend/src/features/forms/components/search-input/index.tsx
@@ -103,7 +103,7 @@ export const SearchInput = () => {
title={t("Reset")}
size="small"
>
- close
+
{t("Reset")}
)}
@@ -114,7 +114,7 @@ export const SearchInput = () => {
title={showFilters ? t("Close filters") : t("Open filters")}
size="small"
>
- tune
+
{showFilters ? t("Close filters") : t("Open filters")}
diff --git a/src/frontend/src/features/layouts/components/admin/admin-layout.tsx b/src/frontend/src/features/layouts/components/admin/admin-layout.tsx
index b4c7049a..1476ac56 100644
--- a/src/frontend/src/features/layouts/components/admin/admin-layout.tsx
+++ b/src/frontend/src/features/layouts/components/admin/admin-layout.tsx
@@ -127,6 +127,7 @@ function AdminLayoutContent({
export function AdminLayout(props: AdminLayoutProps) {
const { theme, variant } = useTheme();
+ const { t } = useTranslation();
return (
@@ -136,7 +137,7 @@ export function AdminLayout(props: AdminLayoutProps) {
leftPanelContent={null}
hideSearch
hideLeftPanelOnDesktop={true}
- icon={ }
+ icon={ }
>
diff --git a/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-labels/components/label-form-modal/components/color-palette-field/index.tsx b/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-labels/components/label-form-modal/components/color-palette-field/index.tsx
index d3d957ad..9f79c4e8 100644
--- a/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-labels/components/label-form-modal/components/color-palette-field/index.tsx
+++ b/src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-labels/components/label-form-modal/components/color-palette-field/index.tsx
@@ -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 */}
a
edit,
+ icon: ,
callback: () => onEdit(label),
},
{
label: t('Add a sub-label'),
- icon: add ,
+ icon: ,
callback: () => onEdit({ name: `${label.name}/`, color: label.color }),
showSeparator: true,
},
{
label: t('Delete'),
- icon: delete ,
+ icon: ,
callback: handleDelete,
variant: 'danger'
},
diff --git a/src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx b/src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx
index 0f6a78c3..67d78eaa 100644
--- a/src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx
+++ b/src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx
@@ -306,7 +306,7 @@ const ThreadPanelTitle = ({ selectedThreadIds, isAllSelected, isSomeSelected, is
},
...([SelectionReadStatus.MIXED, SelectionReadStatus.UNREAD].includes(selectionReadStatus) ? [{
label: markAllTooltip,
- icon: drafts ,
+ icon: ,
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: mark_email_unread ,
+ icon: ,
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
setIsDropdownOpen(true)}
- icon={more_vert }
+ icon={ }
variant="tertiary"
aria-label={t('More options')}
size="nano"
diff --git a/src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx b/src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx
index 0a2eb95e..7983ad82 100644
--- a/src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx
+++ b/src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx
@@ -904,7 +904,7 @@ export const CalendarInvite = ({
setIsSubmitting(false);
addToast(
- check_circle
+
{pendingActionIsRsvp
? t("Response saved — the organizer will be notified")
@@ -934,7 +934,7 @@ export const CalendarInvite = ({
setPendingActionIsRsvp(false);
addToast(
- error
+
{taskStatus.error ?? t("An unexpected error occurred.")}
,
);
diff --git a/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/invitation-user-selector.tsx b/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/invitation-user-selector.tsx
index 6d64a877..4a3d9df0 100644
--- a/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/invitation-user-selector.tsx
+++ b/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/invitation-user-selector.tsx
@@ -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 = ({
color="neutral"
size="nano"
onClick={() => onRemoveUser?.(user)}
- icon={close }
+ icon={ }
/>
);
diff --git a/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/share-modal.tsx b/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/share-modal.tsx
index f80c8336..ea6249f5 100644
--- a/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/share-modal.tsx
+++ b/src/frontend/src/features/layouts/components/thread-view/components/share-modal-extensions/share-modal.tsx
@@ -30,6 +30,7 @@ import {
type QuickSearchData,
QuickSearchGroup,
QuickSearchItemTemplate,
+ Icon,
ShareInvitationItem,
type DropdownMenuOption,
type UserData,
@@ -71,7 +72,7 @@ const SearchUserItem = ({ user }: SearchUserItemProps) => {
right={
{tc("components.share.item.add")}
- add
+
}
/>
@@ -533,7 +534,7 @@ const ShowMoreButton = ({ show, onShowMore }: ShowMoreButtonProps) => {
arrow_downward }
+ icon={ }
onClick={onShowMore}
>
{tc("components.share.members.load_more")}
diff --git a/src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-header.tsx b/src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-header.tsx
index c1157906..cfb56b8b 100644
--- a/src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-header.tsx
+++ b/src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-header.tsx
@@ -135,7 +135,7 @@ const ThreadMessageHeader = ({
{message.is_trashed && (
restore_from_trash}
+ icon={ }
fullWidth
actions={canEditThread ? [
{
diff --git a/src/frontend/src/features/providers/sent-box/queued-message.tsx b/src/frontend/src/features/providers/sent-box/queued-message.tsx
index e2be18c3..3b4c9f5e 100644
--- a/src/frontend/src/features/providers/sent-box/queued-message.tsx
+++ b/src/frontend/src/features/providers/sent-box/queued-message.tsx
@@ -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: (
- error
+
{t('The message could not be sent.')}
),
@@ -71,7 +71,7 @@ export const QueueMessage = ({ taskId, onSettled }: QueueMessageProps) => {
toast.update(toastId, {
render: (
- check_circle
+
{t('Message sent successfully')}
),
@@ -83,7 +83,7 @@ export const QueueMessage = ({ taskId, onSettled }: QueueMessageProps) => {
toast.update(toastId, {
render: (
- error
+
{t('The message could not be sent.')}
),
@@ -102,7 +102,7 @@ export const QueueMessage = ({ taskId, onSettled }: QueueMessageProps) => {
toast.update(toastId, {
render: (
- schedule_send
+
{t('Sending is taking longer than expected. You can track your message in the Outbox.')}
),
diff --git a/src/frontend/src/features/ui/components/label-badge/index.tsx b/src/frontend/src/features/ui/components/label-badge/index.tsx
index f9af61c8..4a53e6c0 100644
--- a/src/frontend/src/features/ui/components/label-badge/index.tsx
+++ b/src/frontend/src/features/ui/components/label-badge/index.tsx
@@ -56,7 +56,7 @@ export const LabelBadge = ({ label, removable = false, linkable = false, compact
}
}]}
>
- label_off
+
{t('Label "{{label}}" removed from this conversation.', { label: label.name })}
,
{ toastId }
diff --git a/src/frontend/src/features/ui/components/toaster/index.tsx b/src/frontend/src/features/ui/components/toaster/index.tsx
index 496b45e1..2fa222fd 100644
--- a/src/frontend/src/features/ui/components/toaster/index.tsx
+++ b/src/frontend/src/features/ui/components/toaster/index.tsx
@@ -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 && {action.icon} }
+ icon={action.icon && }
>{action.showLabel || !action.icon && action.label}
))}
{closeButton && (
@@ -66,7 +67,7 @@ export const ToasterItem = ({
color={buttonColor}
variant="tertiary"
size="small"
- icon={close }
+ icon={ }
>
)}
diff --git a/src/frontend/src/features/utils/text-helper/index.test.tsx b/src/frontend/src/features/utils/text-helper/index.test.tsx
index f5f6e734..9a7d7762 100644
--- a/src/frontend/src/features/utils/text-helper/index.test.tsx
+++ b/src/frontend/src/features/utils/text-helper/index.test.tsx
@@ -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";
diff --git a/src/frontend/src/routes/index.tsx b/src/frontend/src/routes/index.tsx
index 6133bbf4..cbb393a1 100644
--- a/src/frontend/src/routes/index.tsx
+++ b/src/frontend/src/routes/index.tsx
@@ -25,12 +25,12 @@ const HomePage = () => {
hideLeftPanelOnDesktop
leftPanelContent={ }
rightHeaderContent={ }
- icon={ }
+ icon={ }
>
}
+ logo={ }
title={t("Simple and intuitive messaging")}
banner={`/images/banner-${variant}.webp`}
subtitle={t("Send and receive your messages in an instant.")}