[#82] added cozy-ui for internationnalisation (#267)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2025-11-06 15:46:58 +01:00
committed by GitHub
parent c1f37e3ebd
commit 36bc027d6d
49 changed files with 12003 additions and 5002 deletions
+8 -6
View File
@@ -11,6 +11,7 @@ import {
} from "@mui/material";
import { CalendarEvent } from "../../features/Events/EventsTypes";
import { useState } from "react";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
export function EditModeDialog({
type,
@@ -23,6 +24,7 @@ export function EditModeDialog({
event: CalendarEvent;
eventAction: (type: "solo" | "all" | undefined) => void;
}) {
const { t } = useI18n();
const [typeOfAction, setTypeOfAction] = useState<"solo" | "all" | undefined>(
"solo"
);
@@ -37,8 +39,8 @@ export function EditModeDialog({
return (
<Dialog open={Boolean(type)} onClose={handleClose}>
<DialogTitle>
{type === "edit" && "Update the reccurent event"}
{type === "attendance" && "Update the participation status"}
{type === "edit" && t("editModeDialog.updateRecurrentEvent")}
{type === "attendance" && t("editModeDialog.updateParticipationStatus")}
</DialogTitle>
<DialogContent>
<RadioGroup
@@ -50,19 +52,19 @@ export function EditModeDialog({
<FormControlLabel
value="solo"
control={<Radio />}
label="This event"
label={t("editModeDialog.thisEvent")}
/>
<FormControlLabel
value="all"
control={<Radio />}
label="All the events"
label={t("editModeDialog.allEvents")}
/>
</RadioGroup>
</DialogContent>
<DialogActions>
<ButtonGroup>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleEvent}>Ok</Button>
<Button onClick={handleClose}>{t("common.cancel")}</Button>
<Button onClick={handleEvent}>{t("common.ok")}</Button>
</ButtonGroup>
</DialogActions>
</Dialog>
+3 -1
View File
@@ -1,5 +1,6 @@
import { MenuItem } from "@mui/material";
import { CalendarEvent } from "../../features/Events/EventsTypes";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
export default function EventDuplication({
onClose,
@@ -10,13 +11,14 @@ export default function EventDuplication({
event: CalendarEvent;
onOpenDuplicate?: () => void;
}) {
const { t } = useI18n();
return (
<MenuItem
onClick={() => {
onOpenDuplicate?.();
}}
>
Duplicate event
{t("eventDuplication.duplicateEvent")}
</MenuItem>
);
}
+97 -48
View File
@@ -33,6 +33,7 @@ import {
} from "../../utils/videoConferenceUtils";
import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete";
import { CalendarItemList } from "../Calendar/CalendarItemList";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
import { FieldWithLabel } from "./components/FieldWithLabel";
import { DateTimeFields } from "./components/DateTimeFields";
import { useAllDayToggle } from "./hooks/useAllDayToggle";
@@ -84,7 +85,7 @@ interface EventFormFieldsProps {
isOpen?: boolean;
// Data
userPersonnalCalendars: Calendars[];
userPersonalCalendars: Calendars[];
timezoneList: {
zones: string[];
browserTz: string;
@@ -145,7 +146,7 @@ export default function EventFormFields({
showRepeat,
setShowRepeat,
isOpen = false,
userPersonnalCalendars,
userPersonalCalendars,
timezoneList,
onStartChange,
onEndChange,
@@ -155,6 +156,8 @@ export default function EventFormFields({
showValidationErrors = false,
onHasEndDateChangedChange,
}: EventFormFieldsProps) {
const { t } = useI18n();
// Internal state for 4 separate fields
const [startDate, setStartDate] = React.useState("");
const [startTime, setStartTime] = React.useState("");
@@ -406,15 +409,15 @@ export default function EventFormFields({
<FieldWithLabel
label={
<>
Title <span style={{ color: "red" }}>*</span>
{t("event.form.title")} <span style={{ color: "red" }}>*</span>
</>
}
isExpanded={showMore}
>
<TextField
fullWidth
label={!showMore ? "Title" : ""}
placeholder="Add title"
label={!showMore ? t("event.form.title") : ""}
placeholder={t("event.form.titlePlaceholder")}
value={title}
onChange={(e) => {
setTitle(e.target.value);
@@ -439,18 +442,21 @@ export default function EventFormFields({
color: "text.secondary",
}}
>
Add description
{t("event.form.addDescription")}
</Button>
</Box>
</FieldWithLabel>
)}
{showDescription && (
<FieldWithLabel label="Description" isExpanded={showMore}>
<FieldWithLabel
label={t("event.form.description")}
isExpanded={showMore}
>
<TextField
fullWidth
label={!showMore ? "Description" : ""}
placeholder="Add description"
label={!showMore ? t("event.form.description") : ""}
placeholder={t("event.form.descriptionPlaceholder")}
value={description}
onChange={(e) => setDescription(e.target.value)}
size="small"
@@ -471,7 +477,7 @@ export default function EventFormFields({
</FieldWithLabel>
)}
<FieldWithLabel label="Date & Time" isExpanded={showMore}>
<FieldWithLabel label={t("event.form.dateTime")} isExpanded={showMore}>
<DateTimeFields
startDate={startDate}
startTime={startTime}
@@ -496,7 +502,7 @@ export default function EventFormFields({
control={
<Checkbox checked={allday} onChange={handleAllDayToggle} />
}
label="All day"
label={t("event.form.allDay")}
/>
<FormControlLabel
control={
@@ -528,7 +534,7 @@ export default function EventFormFields({
}}
/>
}
label="Repeat"
label={t("event.form.repeat")}
/>
<TimezoneAutocomplete
value={timezone}
@@ -538,7 +544,7 @@ export default function EventFormFields({
showIcon={true}
width={240}
size="small"
placeholder="Select timezone"
placeholder={t("event.form.timezonePlaceholder")}
/>
</Box>
</FieldWithLabel>
@@ -554,11 +560,17 @@ export default function EventFormFields({
</FieldWithLabel>
)}
<FieldWithLabel label="Participants" isExpanded={showMore}>
<FieldWithLabel
label={t("event.form.participants")}
isExpanded={showMore}
>
<AttendeeSelector attendees={attendees} setAttendees={setAttendees} />
</FieldWithLabel>
<FieldWithLabel label="Video meeting" isExpanded={showMore}>
<FieldWithLabel
label={t("event.form.videoMeeting")}
isExpanded={showMore}
>
<Box display="flex" gap={1} alignItems="center">
<Button
startIcon={<VideocamIcon />}
@@ -570,7 +582,7 @@ export default function EventFormFields({
display: hasVideoConference ? "none" : "flex",
}}
>
Add Visio conference
{t("event.form.addVisioConference")}
</Button>
{hasVideoConference && meetingLink && (
@@ -585,14 +597,14 @@ export default function EventFormFields({
mr: 1,
}}
>
Join Visio conference
{t("event.form.joinVisioConference")}
</Button>
<IconButton
onClick={handleCopyMeetingLink}
size="small"
sx={{ color: "primary.main" }}
aria-label="Copy meeting link"
title="Copy meeting link"
aria-label={t("event.form.copyMeetingLink")}
title={t("event.form.copyMeetingLink")}
>
<CopyIcon />
</IconButton>
@@ -600,8 +612,8 @@ export default function EventFormFields({
onClick={handleDeleteVideoConference}
size="small"
sx={{ color: "error.main" }}
aria-label="Remove video conference"
title="Remove video conference"
aria-label={t("event.form.removeVideoConference")}
title={t("event.form.removeVideoConference")}
>
<DeleteIcon />
</IconButton>
@@ -610,11 +622,11 @@ export default function EventFormFields({
</Box>
</FieldWithLabel>
<FieldWithLabel label="Location" isExpanded={showMore}>
<FieldWithLabel label={t("event.form.location")} isExpanded={showMore}>
<TextField
fullWidth
label={!showMore ? "Location" : ""}
placeholder="Add location"
label={!showMore ? t("event.form.location") : ""}
placeholder={t("event.form.locationPlaceholder")}
value={location}
onChange={(e) => setLocation(e.target.value)}
size="small"
@@ -622,65 +634,102 @@ export default function EventFormFields({
/>
</FieldWithLabel>
<FieldWithLabel label="Calendar" isExpanded={showMore}>
<FieldWithLabel label={t("event.form.calendar")} isExpanded={showMore}>
<FormControl fullWidth margin="dense" size="small">
{!showMore && (
<InputLabel id="calendar-select-label">Calendar</InputLabel>
<InputLabel id="calendar-select-label">
{t("event.form.calendar")}
</InputLabel>
)}
<Select
labelId="calendar-select-label"
value={calendarid ?? ""}
label={!showMore ? "Calendar" : ""}
label={!showMore ? t("event.form.calendar") : ""}
displayEmpty
onChange={(e: SelectChangeEvent) =>
handleCalendarChange(e.target.value)
}
>
{CalendarItemList(userPersonnalCalendars)}
{CalendarItemList(userPersonalCalendars)}
</Select>
</FormControl>
</FieldWithLabel>
{showMore && (
<>
<FieldWithLabel label="Notification" isExpanded={showMore}>
<FieldWithLabel
label={t("event.form.notification")}
isExpanded={showMore}
>
<FormControl fullWidth margin="dense" size="small">
<Select
labelId="notification"
value={alarm}
onChange={(e: SelectChangeEvent) => setAlarm(e.target.value)}
>
<MenuItem value={""}>No Notification</MenuItem>
<MenuItem value={"-PT1M"}>1 minute</MenuItem>
<MenuItem value={"-PT5M"}>2 minutes</MenuItem>
<MenuItem value={"-PT10M"}>10 minutes</MenuItem>
<MenuItem value={"-PT15M"}>15 minutes</MenuItem>
<MenuItem value={"-PT30M"}>30 minutes</MenuItem>
<MenuItem value={"-PT1H"}>1 hours</MenuItem>
<MenuItem value={"-PT2H"}>2 hours</MenuItem>
<MenuItem value={"-PT5H"}>5 hours</MenuItem>
<MenuItem value={"-PT12H"}>12 hours</MenuItem>
<MenuItem value={"-PT1D"}>1 day</MenuItem>
<MenuItem value={"-PT2D"}>2 days</MenuItem>
<MenuItem value={"-PT1W"}>1 week</MenuItem>
<MenuItem value="">{t("event.form.notifications.")}</MenuItem>
<MenuItem value="-PT1M">
{t("event.form.notifications.-PT1M")}
</MenuItem>
<MenuItem value="-PT5M">
{t("event.form.notifications.-PT5M")}
</MenuItem>
<MenuItem value="-PT10M">
{t("event.form.notifications.-PT10M")}
</MenuItem>
<MenuItem value="-PT15M">
{t("event.form.notifications.-PT15M")}
</MenuItem>
<MenuItem value="-PT30M">
{t("event.form.notifications.-PT30M")}
</MenuItem>
<MenuItem value="-PT1H">
{t("event.form.notifications.-PT1H")}
</MenuItem>
<MenuItem value="-PT2H">
{t("event.form.notifications.-PT2H")}
</MenuItem>
<MenuItem value="-PT5H">
{t("event.form.notifications.-PT5H")}
</MenuItem>
<MenuItem value="-PT12H">
{t("event.form.notifications.-PT12H")}
</MenuItem>
<MenuItem value="-PT1D">
{t("event.form.notifications.-PT1D")}
</MenuItem>
<MenuItem value="-PT2D">
{t("event.form.notifications.-PT2D")}
</MenuItem>
<MenuItem value="-PT1W">
{t("event.form.notifications.-PT1W")}
</MenuItem>
</Select>
</FormControl>
</FieldWithLabel>
<FieldWithLabel label="Show me as" isExpanded={showMore}>
<FieldWithLabel
label={t("event.form.showMeAs")}
isExpanded={showMore}
>
<FormControl fullWidth margin="dense" size="small">
<Select
labelId="busy"
value={busy}
onChange={(e: SelectChangeEvent) => setBusy(e.target.value)}
>
<MenuItem value={"TRANSPARENT"}>Free</MenuItem>
<MenuItem value={"OPAQUE"}>Busy </MenuItem>
<MenuItem value={"TRANSPARENT"}>
{t("event.form.free")}
</MenuItem>
<MenuItem value={"OPAQUE"}>{t("event.form.busy")}</MenuItem>
</Select>
</FormControl>
</FieldWithLabel>
<FieldWithLabel label="Visible to" isExpanded={showMore}>
<FieldWithLabel
label={t("event.form.visibleTo")}
isExpanded={showMore}
>
<ToggleButtonGroup
value={eventClass}
exclusive
@@ -693,11 +742,11 @@ export default function EventFormFields({
>
<ToggleButton value="PUBLIC" sx={{ width: "140px" }}>
<PublicIcon sx={{ mr: 1, fontSize: "16px" }} />
All
{t("event.form.visibleAll")}
</ToggleButton>
<ToggleButton value="PRIVATE" sx={{ width: "140px" }}>
<LockIcon sx={{ mr: 1, fontSize: "16px" }} />
Participants
{t("event.form.visibleParticipants")}
</ToggleButton>
</ToggleButtonGroup>
</FieldWithLabel>
+35 -12
View File
@@ -15,6 +15,7 @@ import {
} from "@mui/material";
import { useEffect, useState } from "react";
import { RepetitionObject } from "../../features/Events/EventsTypes";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
export default function RepeatEvent({
repetition,
@@ -27,6 +28,7 @@ export default function RepeatEvent({
setRepetition: Function;
isOwn?: boolean;
}) {
const { t } = useI18n();
const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
const day = new Date(eventStart);
// derive endOption based on repetition
@@ -60,12 +62,25 @@ export default function RepeatEvent({
});
};
const getDayLabel = (day: string) => {
const dayMap: { [key: string]: string } = {
MO: t("event.repeat.days.monday"),
TU: t("event.repeat.days.tuesday"),
WE: t("event.repeat.days.wednesday"),
TH: t("event.repeat.days.thursday"),
FR: t("event.repeat.days.friday"),
SA: t("event.repeat.days.saturday"),
SU: t("event.repeat.days.sunday"),
};
return dayMap[day] || day;
};
return (
<Box>
<Stack>
{/* Interval */}
<Box display="flex" alignItems="center" gap={2} mb={2}>
<Typography>Repeat every</Typography>
<Typography>{t("event.repeat.repeatEvery")}</Typography>
<TextField
type="number"
value={repetition.interval ?? 1}
@@ -104,10 +119,18 @@ export default function RepeatEvent({
}
}}
>
<MenuItem value={"daily"}>Day(s)</MenuItem>
<MenuItem value={"weekly"}>Week(s)</MenuItem>
<MenuItem value={"monthly"}>Month(s)</MenuItem>
<MenuItem value={"yearly"}>Year(s)</MenuItem>
<MenuItem value={"daily"}>
{t("event.repeat.frequency.days")}
</MenuItem>
<MenuItem value={"weekly"}>
{t("event.repeat.frequency.weeks")}
</MenuItem>
<MenuItem value={"monthly"}>
{t("event.repeat.frequency.months")}
</MenuItem>
<MenuItem value={"yearly"}>
{t("event.repeat.frequency.years")}
</MenuItem>
</Select>
</FormControl>
</Box>
@@ -116,7 +139,7 @@ export default function RepeatEvent({
{repetition.freq === "weekly" && (
<Box>
<Typography variant="body2" gutterBottom>
Repeat on:
{t("event.repeat.repeatOn")}
</Typography>
<FormGroup row>
{days.map((day) => (
@@ -129,7 +152,7 @@ export default function RepeatEvent({
onChange={() => handleDayChange(day)}
/>
}
label={day}
label={getDayLabel(day)}
/>
))}
</FormGroup>
@@ -139,7 +162,7 @@ export default function RepeatEvent({
{/* End options */}
<Box>
<Typography variant="body2" gutterBottom>
End:
{t("event.repeat.end.label")}
</Typography>
<RadioGroup
value={endOption}
@@ -170,7 +193,7 @@ export default function RepeatEvent({
disabled={!isOwn}
value="never"
control={<Radio />}
label="Never"
label={t("event.repeat.end.never")}
/>
<FormControlLabel
@@ -179,7 +202,7 @@ export default function RepeatEvent({
control={<Radio />}
label={
<Box display="flex" alignItems="center" gap={1}>
After
{t("event.repeat.end.after")}
<TextField
type="number"
size="small"
@@ -195,7 +218,7 @@ export default function RepeatEvent({
inputProps={{ min: 1, "data-testid": "occurrences-input" }}
disabled={!isOwn || endOption !== "after"}
/>
occurrences
{t("event.repeat.end.occurrences")}
</Box>
}
/>
@@ -206,7 +229,7 @@ export default function RepeatEvent({
control={<Radio />}
label={
<Box display="flex" alignItems="center" gap={1}>
On
{t("event.repeat.end.on")}
<TextField
type="date"
inputProps={{ "data-testid": "end-date" }}
@@ -6,8 +6,10 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs, { Dayjs } from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
import { LONG_DATE_FORMAT } from "../utils/dateTimeFormatters";
import "dayjs/locale/fr";
import "dayjs/locale/en";
dayjs.extend(customParseFormat);
/**
@@ -54,15 +56,21 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
onEndDateChange,
onEndTimeChange,
}) => {
const { t, lang } = useI18n();
const isExpanded = showMore;
const shouldShowEndDateNormal = allday || !!showEndDate;
const shouldShowFullFieldsInNormal = !allday && hasEndDateChanged;
const showSingleDateField =
!isExpanded && !shouldShowEndDateNormal && !shouldShowFullFieldsInNormal;
const startDateLabel = showSingleDateField ? "Date" : "Start Date";
const startDateLabel = showSingleDateField
? t("dateTimeFields.date")
: t("dateTimeFields.startDate");
return (
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="en">
<LocalizationProvider
dateAdapter={AdapterDayjs}
adapterLocale={lang ?? "en"}
>
<Box
display="flex"
flexDirection="column"
@@ -73,7 +81,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
<Box sx={{ maxWidth: "300px", width: "48%" }}>
<DatePicker
label="Start Date"
label={t("dateTimeFields.startDate")}
format={LONG_DATE_FORMAT}
value={startDate ? dayjs(startDate) : null}
onChange={(newValue) => {
@@ -99,7 +107,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
{!allday && (
<Box sx={{ width: "110px" }}>
<TimePicker
label="Start Time"
label={t("dateTimeFields.startTime")}
ampm={false}
value={startTime ? dayjs(startTime, "HH:mm") : null}
onChange={(newValue) => {
@@ -127,7 +135,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
<Box sx={{ maxWidth: "300px", width: "48%" }}>
<DatePicker
label="End Date"
label={t("dateTimeFields.endDate")}
format={LONG_DATE_FORMAT}
value={endDate ? dayjs(endDate) : null}
onChange={(newValue) => {
@@ -154,7 +162,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
{!allday && (
<Box sx={{ width: "110px" }}>
<TimePicker
label="End Time"
label={t("dateTimeFields.endTime")}
ampm={false}
value={endTime ? dayjs(endTime, "HH:mm") : null}
onChange={(newValue) => {
@@ -185,7 +193,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
<Box display="flex" gap={1} flexDirection="row" alignItems="center">
<Box sx={{ maxWidth: "300px", width: "48%" }}>
<DatePicker
label="Start Date"
label={t("dateTimeFields.startDate")}
format={LONG_DATE_FORMAT}
value={startDate ? dayjs(startDate) : null}
onChange={(newValue) => {
@@ -210,7 +218,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
</Box>
<Box sx={{ maxWidth: "300px", width: "48%" }}>
<DatePicker
label="End Date"
label={t("dateTimeFields.endDate")}
format={LONG_DATE_FORMAT}
value={endDate ? dayjs(endDate) : null}
onChange={(newValue) => {
@@ -264,7 +272,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
</Box>
<Box sx={{ maxWidth: "110px" }}>
<TimePicker
label="Start Time"
label={t("dateTimeFields.startTime")}
ampm={false}
value={startTime ? dayjs(startTime, "HH:mm") : null}
onChange={(newValue) => {
@@ -290,7 +298,7 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
</Box>
<Box sx={{ maxWidth: "110px" }}>
<TimePicker
label="End Time"
label={t("dateTimeFields.endTime")}
ampm={false}
value={endTime ? dayjs(endTime, "HH:mm") : null}
onChange={(newValue) => {
+3 -2
View File
@@ -17,6 +17,7 @@ import { formatDateToYYYYMMDDTHHMMSS } from "../../../utils/dateUtils";
export function renderAttendeeBadge(
a: userAttendee,
key: string,
t: Function,
isFull?: boolean,
isOrganizer?: boolean
) {
@@ -28,7 +29,7 @@ export function renderAttendeeBadge(
) : null;
if (!isFull) {
return <Avatar {...stringAvatar(a.cn || a.cal_address)} />;
return <Avatar key={key} {...stringAvatar(a.cn || a.cal_address)} />;
} else {
return (
<Box
@@ -77,7 +78,7 @@ export function renderAttendeeBadge(
</Typography>
{isOrganizer && (
<Typography variant="caption" color="text.secondary">
Organizer
{t("event.organizer")}
</Typography>
)}
</Box>