Repository: refugies-info/karfur
Pull request #2789: Jeremie/ri 660 soucis rtl sur page recherche + sens du carrousel 
Author: login: ledjay
Created: 2025-03-17T14:40:41Z | Merged: 2025-03-17T17:26:35Z



Diff:
diff --git a/apps/client/public/locales/fr/common.json b/apps/client/public/locales/fr/common.json
index 158658d1a3..9c8dbdb9e2 100644
--- a/apps/client/public/locales/fr/common.json
+++ b/apps/client/public/locales/fr/common.json
@@ -178,6 +178,8 @@
     "share": "Partager la fiche",
     "updated": "Mise à jour",
     "summary": "Sommaire",
+    "departements": "Départements",
+    "demarche": "Démarche",
     "importantInformations": "Informations importantes",
     "sectionWhatAndWhy": "C'est quoi et pourquoi c'est intéressant ?",
     "sectionWhat": "C'est quoi ?",
diff --git a/apps/client/src/__tests__/__snapshots__/index.test.tsx.snap b/apps/client/src/__tests__/__snapshots__/index.test.tsx.snap
index e06a1760d5..1b85e896cc 100644
--- a/apps/client/src/__tests__/__snapshots__/index.test.tsx.snap
+++ b/apps/client/src/__tests__/__snapshots__/index.test.tsx.snap
@@ -266,6 +266,7 @@ exports[`homepage renders homepage 1`] = `
       aria-label="Homepage.infoTypeDemarche"
       aria-roledescription="carousel"
       class="relative w-full max-md:pb-14 mb-20"
+      dir="ltr"
       role="region"
     >
       <div
@@ -304,8 +305,8 @@ exports[`homepage renders homepage 1`] = `
       <div
         aria-atomic="true"
         aria-live="polite"
-        class="noscrollbar m-auto flex gap-4 pr-4 touch-pan-x snap-x snap-mandatory overflow-x-auto scroll-smooth scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::WebkitScrollbar]:hidden scroll-pl-[max(1rem,calc((100vw-76rem)/2))] pl-[max(1rem,calc((100vw-76rem)/2))]"
-        style="will-change: transform; scroll-snap-type: x mandatory; scroll-behavior: smooth; cursor: grab;"
+        class="noscrollbar m-auto flex gap-4 touch-pan-x snap-x snap-mandatory overflow-x-auto scroll-smooth scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::WebkitScrollbar]:hidden scroll-ps-[max(1rem,calc((100vw-76rem)/2))] ps-[max(1rem,calc((100vw-76rem)/2))] ltr:pr-4 rtl:pl-4"
+        style="will-change: transform; scroll-snap-type: x mandatory; scroll-behavior: smooth; cursor: grab; direction: ltr;"
       >
         <div
           aria-current="true"
@@ -438,7 +439,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:r6:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -588,7 +589,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:r7:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -738,7 +739,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:r8:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -883,7 +884,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:r9:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -1033,7 +1034,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:ra:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -1178,7 +1179,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rb:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -1328,7 +1329,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rc:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -1473,7 +1474,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rd:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -1618,7 +1619,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:re:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -1768,7 +1769,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rf:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -1913,7 +1914,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rg:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -2063,7 +2064,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rh:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -2208,7 +2209,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:ri:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -2353,7 +2354,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rj:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -2498,7 +2499,7 @@ exports[`homepage renders homepage 1`] = `
                       class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                       id="fr-badge-:rk:"
                     >
-                      Démarche
+                      Dispositif.demarche
                     </p>
                   </li>
                 </ul>
@@ -2524,6 +2525,7 @@ exports[`homepage renders homepage 1`] = `
       aria-label="Homepage.infoTypeDispositif"
       aria-roledescription="carousel"
       class="relative w-full max-md:pb-14 mb-20"
+      dir="ltr"
       role="region"
     >
       <div
@@ -2562,8 +2564,8 @@ exports[`homepage renders homepage 1`] = `
       <div
         aria-atomic="true"
         aria-live="polite"
-        class="noscrollbar m-auto flex gap-4 pr-4 touch-pan-x snap-x snap-mandatory overflow-x-auto scroll-smooth scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::WebkitScrollbar]:hidden scroll-pl-[max(1rem,calc((100vw-76rem)/2))] pl-[max(1rem,calc((100vw-76rem)/2))]"
-        style="will-change: transform; scroll-snap-type: x mandatory; scroll-behavior: smooth; cursor: grab;"
+        class="noscrollbar m-auto flex gap-4 touch-pan-x snap-x snap-mandatory overflow-x-auto scroll-smooth scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::WebkitScrollbar]:hidden scroll-ps-[max(1rem,calc((100vw-76rem)/2))] ps-[max(1rem,calc((100vw-76rem)/2))] ltr:pr-4 rtl:pl-4"
+        style="will-change: transform; scroll-snap-type: x mandatory; scroll-behavior: smooth; cursor: grab; direction: ltr;"
       >
         <div
           aria-current="true"
diff --git a/apps/client/src/components/Backend/screens/UserFavorites/__tests__/__snapshots__/UserFavorites.component.test.tsx.snap b/apps/client/src/components/Backend/screens/UserFavorites/__tests__/__snapshots__/UserFavorites.component.test.tsx.snap
index b4cea4e4be..ce2cab857e 100644
--- a/apps/client/src/components/Backend/screens/UserFavorites/__tests__/__snapshots__/UserFavorites.component.test.tsx.snap
+++ b/apps/client/src/components/Backend/screens/UserFavorites/__tests__/__snapshots__/UserFavorites.component.test.tsx.snap
@@ -777,7 +777,7 @@ exports[`UserFavorites should render correctly when 3 favorites 1`] = `
                     class="fr-badge fr-badge--sm fr-badge--no-icon badge_demarche"
                     id="fr-badge-:r2:"
                   >
-                    Démarche
+                    Dispositif.demarche
                   </p>
                 </li>
               </ul>
diff --git a/apps/client/src/components/Pages/homepage/Sections/Hero/WhiteWave.tsx b/apps/client/src/components/Pages/homepage/Sections/Hero/WhiteWave.tsx
index 7e426f84aa..7a362b1dad 100644
--- a/apps/client/src/components/Pages/homepage/Sections/Hero/WhiteWave.tsx
+++ b/apps/client/src/components/Pages/homepage/Sections/Hero/WhiteWave.tsx
@@ -14,7 +14,7 @@ function WhiteWave({ className }: { className: string }) {
       />
       <defs>
         <filter id="drop-shadow" x="-50%" y="0%" width="200%" height="200%">
-          <feDropShadow dx="0" dy="-4.167" stdDeviation="10.416" flood-color="rgba(0,0,0,0.20)" />
+          <feDropShadow dx="0" dy="-4.167" stdDeviation="10.416" floodColor="rgba(0,0,0,0.20)" />
         </filter>
       </defs>
     </svg>
diff --git a/apps/client/src/components/UI/DispositifCard/DispositifCard.tsx b/apps/client/src/components/UI/DispositifCard/DispositifCard.tsx
index f7a412d713..12478ddc64 100644
--- a/apps/client/src/components/UI/DispositifCard/DispositifCard.tsx
+++ b/apps/client/src/components/UI/DispositifCard/DispositifCard.tsx
@@ -44,7 +44,7 @@ const DispositifCard = (props: Props) => {
   const isOnline = props.dispositif.metadatas?.location === "online";
 
   const badge = useMemo((): { className: string; text: string | null } => {
-    if (!isDispositif) return { text: "Démarche", className: styles.badge_demarche };
+    if (!isDispositif) return { text: t("Dispositif.demarche", "Démarche"), className: styles.badge_demarche };
     const location = props.dispositif.metadatas?.location;
     if (!location) return { text: "Lieu d'action", className: styles.badge_department };
     if (!Array.isArray(location)) {
diff --git a/apps/client/src/pages/index.tsx b/apps/client/src/pages/index.tsx
index 9ce39248c0..6079ca8db0 100644
--- a/apps/client/src/pages/index.tsx
+++ b/apps/client/src/pages/index.tsx
@@ -19,7 +19,7 @@ import WorkTogether from "~/components/Pages/homepage/Sections/WorkTogether";
 import { HelpNotice } from "~/components/Pages/recherche/HelpNotice";
 import SEO from "~/components/Seo";
 import DispositifCard from "~/components/UI/DispositifCard";
-import { useWindowSize } from "~/hooks";
+import { useRTL, useWindowSize } from "~/hooks";
 import { getLanguageFromLocale } from "~/lib/getLanguageFromLocale";
 import isInBrowser from "~/lib/isInBrowser";
 import { Event } from "~/lib/tracking";
@@ -42,6 +42,7 @@ const Homepage = (props: Props) => {
   const dispatch = useDispatch();
   const { t } = useTranslation();
   const { isMobile } = useWindowSize();
+  const isRTL = useRTL();
 
   useEffect(() => {
     dispatch(fetchNeedsActionCreator());
@@ -76,6 +77,7 @@ const Homepage = (props: Props) => {
 
       <Carrousel
         className="mb-20"
+        dir={isRTL ? "rtl" : "ltr"}
         texts={{
           title: t("Homepage.infoTypeDemarche", "{{count}} démarches administratives expliquées", {
             count: props.contentStatistics.nbDemarches || 0,
@@ -93,6 +95,7 @@ const Homepage = (props: Props) => {
 
       <Carrousel
         className="mb-20"
+        dir={isRTL ? "rtl" : "ltr"}
         texts={{
           title: t("Homepage.infoTypeDispositif", "{{count}} dispositifs dans toute la France", {
             count: props.contentStatistics.nbDispositifs || 0,
diff --git a/apps/client/src/scss/components/contentCard.module.scss b/apps/client/src/scss/components/contentCard.module.scss
index 57ca189fea..77e8be44d5 100644
--- a/apps/client/src/scss/components/contentCard.module.scss
+++ b/apps/client/src/scss/components/contentCard.module.scss
@@ -85,9 +85,17 @@ $shadowHeight: 1.4rem;
     aspect-ratio: 16/9;
     background-color: var(--background-action-high-grey-hover);
   }
+
+  :global(.fr-badges-group) {
+    @include rtl {
+      right: 0;
+      left: auto;
+    }
+  }
 }
 
 /* Badges */
+
 .badge_department {
   background-color: var(--background-contrast-purple-glycine);
   color: var(--text-action-high-purple-glycine);
@@ -138,6 +146,10 @@ $shadowHeight: 1.4rem;
 
       line-height: 1em !important;
       color: var(--text-action-high-blue-france);
+
+      @include rtl {
+        transform: rotate(180deg);
+      }
     }
   }
 }
@@ -146,11 +158,13 @@ $shadowHeight: 1.4rem;
 .favorite {
   position: absolute;
   top: u(2);
-  right: u(3);
   z-index: 5;
 
+  @include ltr {
+    right: u(3);
+  }
+
   @include rtl {
-    right: inherit;
     left: u(3);
   }
 }
@@ -189,8 +203,15 @@ $shadowHeight: 1.4rem;
   border: 1px solid var(--border-default-grey);
   background: white;
   top: (u(-6) + u(-6)) - 1; /* padding top + half of image size - border top */
-  right: u(-2);
   z-index: 1;
+
+  @include ltr {
+    right: u(-2);
+  }
+
+  @include rtl {
+    left: u(-2);
+  }
 }
 
 .title {
diff --git a/packages/ui/src/components/Carrousel.tsx b/packages/ui/src/components/Carrousel.tsx
index 46c8769943..261b522276 100644
--- a/packages/ui/src/components/Carrousel.tsx
+++ b/packages/ui/src/components/Carrousel.tsx
@@ -21,15 +21,17 @@ type CarrouselProps = {
   children: React.ReactNode;
   className?: string;
   seeMoreUrl?: string;
+  dir?: "ltr" | "rtl";
 };
 
-export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselProps) => {
+export const Carrousel = ({ texts, children, className, seeMoreUrl, dir = "ltr" }: CarrouselProps) => {
   const scrollContainerRef = useRef<HTMLDivElement>(null);
   const slideRefs = useRef<(HTMLDivElement | null)[]>([]);
   const [currentSlide, setCurrentSlide] = useState(0);
   const [canScrollNext, setCanScrollNext] = useState(true);
   const [canScrollPrev, setCanScrollPrev] = useState(false);
   const childrenArray = useMemo(() => React.Children.toArray(children), [children]);
+
   const t: Required<CarrouselTexts> = useMemo(
     () => ({
       ...defaultTexts,
@@ -72,38 +74,22 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
   const checkScrollability = useCallback(() => {
     if (!scrollContainerRef.current) return;
     const container = scrollContainerRef.current;
-    const hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 1; // -1 for rounding errors
-    const hasReachedStart = container.scrollLeft <= 0;
-    setCanScrollNext(!hasReachedEnd);
-    setCanScrollPrev(!hasReachedStart);
-  }, []);
 
-  useEffect(() => {
-    const container = scrollContainerRef.current;
-    if (!container) return;
+    const isRtl = dir === "rtl";
 
-    const handleResize = () => {
-      handleScroll();
-      checkScrollability();
-    };
+    let hasReachedStart, hasReachedEnd;
 
-    // Only use ResizeObserver if it's available in the browser
-    let observer: ResizeObserver | undefined;
-    if (typeof window !== "undefined" && typeof ResizeObserver !== "undefined") {
-      observer = new ResizeObserver(handleResize);
-      observer.observe(container);
+    if (isRtl) {
+      hasReachedStart = container.scrollLeft > -10; // Close to 0 (right edge)
+      hasReachedEnd = Math.abs(container.scrollLeft) >= container.scrollWidth - container.clientWidth - 10; // Close to max negative (left edge)
+    } else {
+      hasReachedStart = container.scrollLeft < 10; // Close to 0 (left edge)
+      hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 10; // Close to max (right edge)
     }
 
-    // Fallback to window resize event
-    window.addEventListener("resize", handleResize);
-    container.addEventListener("scroll", handleResize);
-
-    return () => {
-      observer?.disconnect();
-      window.removeEventListener("resize", handleResize);
-      container.removeEventListener("scroll", handleResize);
-    };
-  }, [handleScroll, checkScrollability]);
+    setCanScrollNext(!hasReachedEnd); // Can scroll "next" (left) if not at left edge
+    setCanScrollPrev(!hasReachedStart); // Can scroll "prev" (right) if not at right edge
+  }, [dir]);
 
   const scrollToSlide = useCallback(
     (targetSlide: number, useSmooth = true) => {
@@ -178,6 +164,33 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
     [currentSlide, childrenArray.length, scrollToSlide],
   );
 
+  useEffect(() => {
+    const container = scrollContainerRef.current;
+    if (!container) return;
+
+    const handleResize = () => {
+      handleScroll();
+      checkScrollability();
+    };
+
+    // Only use ResizeObserver if it's available in the browser
+    let observer: ResizeObserver | undefined;
+    if (typeof window !== "undefined" && typeof ResizeObserver !== "undefined") {
+      observer = new ResizeObserver(handleResize);
+      observer.observe(container);
+    }
+
+    // Fallback to window resize event
+    window.addEventListener("resize", handleResize);
+    container.addEventListener("scroll", handleResize);
+
+    return () => {
+      observer?.disconnect();
+      window.removeEventListener("resize", handleResize);
+      container.removeEventListener("scroll", handleResize);
+    };
+  }, [handleScroll, checkScrollability]);
+
   return (
     <section
       className={cn("relative w-full max-md:pb-14", className)}
@@ -185,6 +198,7 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
       aria-roledescription="carousel"
       aria-label={t.title || "Carousel de contenu"}
       onKeyDown={handleKeyDown}
+      dir={dir}
     >
       <div className="container mx-auto mb-8 flex w-full gap-4 lg:justify-between">
         {t.title && <h2 className="!mb-0 w-full !text-2xl font-bold max-sm:pr-[30%]">{t.title}</h2>}
@@ -193,7 +207,7 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
             aria-label={`${t.prev} (${prevSlide + 1} / ${childrenArray.length}`}
             onClick={handlePrevClick}
             priority="tertiary"
-            iconId="fr-icon-arrow-left-line"
+            iconId={dir === "rtl" ? "fr-icon-arrow-right-line" : "fr-icon-arrow-left-line"}
             title={t.prev}
             disabled={!canScrollPrev}
           />
@@ -202,7 +216,7 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
             onClick={handleNextClick}
             disabled={!canScrollNext}
             priority="tertiary"
-            iconId="fr-icon-arrow-right-line"
+            iconId={dir === "rtl" ? "fr-icon-arrow-left-line" : "fr-icon-arrow-right-line"}
             title={t.next}
           />
           {seeMoreUrl && (
@@ -216,10 +230,11 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
       <div
         ref={scrollContainerRef}
         className={cn(
-          "noscrollbar m-auto flex gap-4 pr-4",
+          "noscrollbar m-auto flex gap-4",
           "touch-pan-x snap-x snap-mandatory overflow-x-auto scroll-smooth",
           "scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::WebkitScrollbar]:hidden",
-          "scroll-pl-[max(1rem,calc((100vw-76rem)/2))] pl-[max(1rem,calc((100vw-76rem)/2))]", // Keep only the padding-left, remove scroll-padding-left
+          "scroll-ps-[max(1rem,calc((100vw-76rem)/2))] ps-[max(1rem,calc((100vw-76rem)/2))]",
+          "ltr:pr-4 rtl:pl-4",
         )}
         aria-live="polite"
         aria-atomic="true"
@@ -235,6 +250,7 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
           "cursor": "grab",
           // @ts-ignore
           "&:active": { cursor: "grabbing" },
+          "direction": dir, // Set the direction explicitly
         }}
       >
         {React.Children.map(children, (child, index) => (
diff --git a/packages/ui/src/css/dsfr-customisations.css b/packages/ui/src/css/dsfr-customisations.css
new file mode 100644
index 0000000000..c174720f51
--- /dev/null
+++ b/packages/ui/src/css/dsfr-customisations.css
@@ -0,0 +1,18 @@
+/* stylelint-disable selector-class-pattern */
+@layer dsfr {
+  [dir="rtl"] {
+    .fr-btn--icon-right[class^="fr-icon-"]::after,
+    .fr-btn--icon-right[class*=" fr-icon-"]::after,
+    .fr-btn--icon-right[class^="fr-fi-"]::after,
+    .fr-btn--icon-right[class*=" fr-fi-"]::after {
+      margin-inline: 0.5rem -0.125rem;
+    }
+
+    .fr-btn[class^="fr-icon-"]:not([class*="fr-btn--icon-"])::before,
+    .fr-btn[class*=" fr-icon-"]:not([class*="fr-btn--icon-"])::before,
+    .fr-btn[class^="fr-fi-"]:not([class*="fr-btn--icon-"])::before,
+    .fr-btn[class*=" fr-fi-"]:not([class*="fr-btn--icon-"])::before {
+      margin-inline: 0 0.5rem;
+    }
+  }
+}
diff --git a/packages/ui/src/css/globals-ui.css b/packages/ui/src/css/globals-ui.css
index 7f4ff8de72..1110ad881a 100644
--- a/packages/ui/src/css/globals-ui.css
+++ b/packages/ui/src/css/globals-ui.css
@@ -1,5 +1,6 @@
 @import "tailwindcss";
 @import "./dsfr-tokens.css";
+@import "./dsfr-customisations.css";
 @import "./container.css";
 @source "../components/";
 


Reviews:
user: login: gemini-code-assist[bot]
body: Hello @ledjay, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

## Summary of Changes
This pull request addresses issues related to the RTL (right-to-left) layout on the search page and the direction of the carousel. It includes changes to the French localization file, updates to snapshot tests, and modifications to the `DispositifCard` component and the homepage (`index.tsx`) to handle RTL layout. The changes ensure that the carousel and other UI elements are displayed correctly in RTL languages.

### Highlights
* **RTL Support**: The carousel now supports RTL layouts by adding a `dir` attribute and updating styles to handle RTL languages correctly. This includes flipping the direction of the navigation arrows and adjusting the positioning of elements.
* **Localization**: The French localization file (`common.json`) has been updated to include new translations for 'Départements' and 'Démarche'.
* **Component Updates**: The `DispositifCard` component has been updated to use the translated value for 'Démarche' from the localization file.
* **Snapshot Tests**: Snapshot tests have been updated to reflect the changes in the UI, including the addition of the `dir` attribute and the updated 'Démarche' text.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **apps/client/public/locales/fr/common.json**
  * Added translations for 'departements' and 'demarche'.
* **apps/client/src/__tests__/__snapshots__/index.test.tsx.snap**
  * Updated snapshots to include the `dir` attribute for RTL support in the carousel.
  * Updated snapshots to reflect the change in the 'Démarche' badge text to 'Dispositif.demarche'.
* **apps/client/src/pages/index.tsx**
  * Imported the `useRTL` hook.
  * Used the `useRTL` hook to determine the layout direction.
  * Added the `dir` attribute to the carousel components to support RTL layouts.
* **apps/client/src/scss/components/contentCard.module.scss**
  * Added RTL support for the badges group, favorite icon, and title positioning.
* **packages/ui/src/components/Carrousel.tsx**
  * Added a `dir` prop to the `Carrousel` component to support RTL layouts.
  * Updated the carousel to use the correct arrow icons based on the layout direction.
  * Updated the carousel styles to handle RTL layouts correctly.
* **packages/ui/src/css/dsfr-customisations.css**
  * Added RTL specific styles for DSFR components.
* **packages/ui/src/css/globals-ui.css**
  * Imported the `dsfr-customisations.css` file.
* **src/components/UI/DispositifCard/DispositifCard.tsx**
  * Updated the 'Démarche' badge text to use the translated value from the localization file.

</details>


<details>

<summary><b>Using Gemini Code Assist</b></summary>
<br>

The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.

<b>Invoking Gemini</b>

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands.

Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments.
Help | `/gemini help` | Displays a list of available commands.

<b>Customization</b>

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/review-github-code).

<b>Limitations & Feedback</b>

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments to provide feedback.

</details>

<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>


[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/review-github-code). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).

---

**Trivia time!**

Which languages are written from right to left?

<details>
<summary><b>Click here for the answer</b></summary>
<br>
Some of the most prominent languages written from right to left include Arabic, Hebrew, Persian, and Urdu.

</details>

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T14:40:52Z
user: login: gemini-code-assist[bot]
body: ## Code Review
This pull request addresses RTL issues on the search page and adjusts the carousel direction. The changes include locale updates, style adjustments, and component modifications to ensure proper rendering in RTL layouts. Overall, the changes seem well-structured and address the intended issues.

### Summary of Findings
* **Translation Key Consistency**: In DispositifCard.tsx, the translation key 'Dispositif.demarche' is used. Ensure this key is consistently used across the application for the same concept to maintain consistency and avoid confusion.
* **CSS Class Naming**: In contentCard.module.scss, consider using more descriptive CSS class names to improve maintainability and readability. For example, instead of `.badge_department`, a more specific name reflecting the badge's purpose might be beneficial.
* **Snapshot Updates**: The snapshot updates seem extensive. It's important to verify that these changes accurately reflect the intended behavior and that no unintended side effects have been introduced.

### Merge Readiness
The pull request appears to address the intended RTL and carousel direction issues effectively. However, it's crucial to verify the snapshot updates and ensure translation key consistency before merging. I am unable to approve this pull request, and recommend that others review and approve this code before merging. At a minimum, the above issues should be addressed before merging.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T14:41:21Z
user: login: kaaloo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T16:24:31Z
user: login: kaaloo
body: The cards are not displayed exactly as in LTR languages.

![image](https://github.com/user-attachments/assets/438912ef-a245-48f1-ab9a-38198faeb9e6)


![image](https://github.com/user-attachments/assets/c40b7eb5-ae79-4fc7-9bdf-96f0ff1885d2)

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T16:31:52Z
user: login: ledjay
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T17:00:46Z
user: login: kaaloo
body: Looks great !
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:26:05Z

Review comments:
diff_hunk: @@ -44,7 +44,7 @@ const DispositifCard = (props: Props) => {
   const isOnline = props.dispositif.metadatas?.location === "online";
 
   const badge = useMemo((): { className: string; text: string | null } => {
-    if (!isDispositif) return { text: "Démarche", className: styles.badge_demarche };
+    if (!isDispositif) return { text: t("Dispositif.demarche", "Démarche"), className: styles.badge_demarche };
path: apps/client/src/components/UI/DispositifCard/DispositifCard.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

Consider whether the translation key `Dispositif.demarche` is the most appropriate here, and whether it's used consistently throughout the codebase. If this badge is always meant to represent a 'Démarche', it might be better to use a more generic key in `common.json` and use that here.

```typescript
    if (!isDispositif) return { text: t("common.demarche", "Démarche"), className: styles.badge_demarche };
```
created_at: 2025-03-17T14:41:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,38 +74,22 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
   const checkScrollability = useCallback(() => {
     if (!scrollContainerRef.current) return;
     const container = scrollContainerRef.current;
-    const hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 1; // -1 for rounding errors
-    const hasReachedStart = container.scrollLeft <= 0;
-    setCanScrollNext(!hasReachedEnd);
-    setCanScrollPrev(!hasReachedStart);
-  }, []);
 
-  useEffect(() => {
-    const container = scrollContainerRef.current;
-    if (!container) return;
+    const isRtl = dir === "rtl";
 
-    const handleResize = () => {
-      handleScroll();
-      checkScrollability();
-    };
+    let hasReachedStart, hasReachedEnd;
 
-    // Only use ResizeObserver if it's available in the browser
-    let observer: ResizeObserver | undefined;
-    if (typeof window !== "undefined" && typeof ResizeObserver !== "undefined") {
-      observer = new ResizeObserver(handleResize);
-      observer.observe(container);
+    if (isRtl) {
+      hasReachedStart = container.scrollLeft > -10; // Close to 0 (right edge)
+      hasReachedEnd = Math.abs(container.scrollLeft) >= container.scrollWidth - container.clientWidth - 10; // Close to max negative (left edge)
+    } else {
+      hasReachedStart = container.scrollLeft < 10; // Close to 0 (left edge)
+      hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 10; // Close to max (right edge)
     }
 
-    // Fallback to window resize event
-    window.addEventListener("resize", handleResize);
-    container.addEventListener("scroll", handleResize);
-
-    return () => {
-      observer?.disconnect();
-      window.removeEventListener("resize", handleResize);
-      container.removeEventListener("scroll", handleResize);
-    };
-  }, [handleScroll, checkScrollability]);
+    setCanScrollNext(!hasReachedEnd); // Can scroll "next" (left) if not at left edge
+    setCanScrollPrev(!hasReachedStart); // Can scroll "prev" (right) if not at right edge
+  }, [dir]);
path: packages/ui/src/components/Carrousel.tsx
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

This section calculates scrollability based on the `dir` prop. Consider extracting this logic into a separate helper function for better readability and testability.

```typescript
    const { hasReachedStart, hasReachedEnd } = useMemo(() => {
      if (!scrollContainerRef.current) return { hasReachedStart: false, hasReachedEnd: false };
      const container = scrollContainerRef.current;
      const isRtl = dir === "rtl";

      if (isRtl) {
        return {
          hasReachedStart: container.scrollLeft > -10, // Close to 0 (right edge)
          hasReachedEnd: Math.abs(container.scrollLeft) >= container.scrollWidth - container.clientWidth - 10, // Close to max negative (left edge)
        };
      } else {
        return {
          hasReachedStart: container.scrollLeft < 10, // Close to 0 (left edge)
          hasReachedEnd: container.scrollLeft + container.clientWidth >= container.scrollWidth - 10, // Close to max (right edge)
        };
      }
    }, [dir]);

    setCanScrollNext(!hasReachedEnd); // Can scroll "next" (left) if not at left edge
    setCanScrollPrev(!hasReachedStart); // Can scroll "prev" (right) if not at right edge
```
created_at: 2025-03-17T14:41:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,38 +74,22 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
   const checkScrollability = useCallback(() => {
     if (!scrollContainerRef.current) return;
     const container = scrollContainerRef.current;
-    const hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 1; // -1 for rounding errors
-    const hasReachedStart = container.scrollLeft <= 0;
-    setCanScrollNext(!hasReachedEnd);
-    setCanScrollPrev(!hasReachedStart);
-  }, []);
 
-  useEffect(() => {
-    const container = scrollContainerRef.current;
-    if (!container) return;
+    const isRtl = dir === "rtl";
 
-    const handleResize = () => {
-      handleScroll();
-      checkScrollability();
-    };
+    let hasReachedStart, hasReachedEnd;
 
-    // Only use ResizeObserver if it's available in the browser
-    let observer: ResizeObserver | undefined;
-    if (typeof window !== "undefined" && typeof ResizeObserver !== "undefined") {
-      observer = new ResizeObserver(handleResize);
-      observer.observe(container);
+    if (isRtl) {
+      hasReachedStart = container.scrollLeft > -10; // Close to 0 (right edge)
+      hasReachedEnd = Math.abs(container.scrollLeft) >= container.scrollWidth - container.clientWidth - 10; // Close to max negative (left edge)
+    } else {
+      hasReachedStart = container.scrollLeft < 10; // Close to 0 (left edge)
+      hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 10; // Close to max (right edge)
     }
 
-    // Fallback to window resize event
-    window.addEventListener("resize", handleResize);
-    container.addEventListener("scroll", handleResize);
-
-    return () => {
-      observer?.disconnect();
-      window.removeEventListener("resize", handleResize);
-      container.removeEventListener("scroll", handleResize);
-    };
-  }, [handleScroll, checkScrollability]);
+    setCanScrollNext(!hasReachedEnd); // Can scroll "next" (left) if not at left edge
+    setCanScrollPrev(!hasReachedStart); // Can scroll "prev" (right) if not at right edge
+  }, [dir]);
path: packages/ui/src/components/Carrousel.tsx
user: login: kaaloo
body: This code looks super clean @ledjay , WDYT ?
created_at: 2025-03-17T16:24:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,38 +74,22 @@ export const Carrousel = ({ texts, children, className, seeMoreUrl }: CarrouselP
   const checkScrollability = useCallback(() => {
     if (!scrollContainerRef.current) return;
     const container = scrollContainerRef.current;
-    const hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 1; // -1 for rounding errors
-    const hasReachedStart = container.scrollLeft <= 0;
-    setCanScrollNext(!hasReachedEnd);
-    setCanScrollPrev(!hasReachedStart);
-  }, []);
 
-  useEffect(() => {
-    const container = scrollContainerRef.current;
-    if (!container) return;
+    const isRtl = dir === "rtl";
 
-    const handleResize = () => {
-      handleScroll();
-      checkScrollability();
-    };
+    let hasReachedStart, hasReachedEnd;
 
-    // Only use ResizeObserver if it's available in the browser
-    let observer: ResizeObserver | undefined;
-    if (typeof window !== "undefined" && typeof ResizeObserver !== "undefined") {
-      observer = new ResizeObserver(handleResize);
-      observer.observe(container);
+    if (isRtl) {
+      hasReachedStart = container.scrollLeft > -10; // Close to 0 (right edge)
+      hasReachedEnd = Math.abs(container.scrollLeft) >= container.scrollWidth - container.clientWidth - 10; // Close to max negative (left edge)
+    } else {
+      hasReachedStart = container.scrollLeft < 10; // Close to 0 (left edge)
+      hasReachedEnd = container.scrollLeft + container.clientWidth >= container.scrollWidth - 10; // Close to max (right edge)
     }
 
-    // Fallback to window resize event
-    window.addEventListener("resize", handleResize);
-    container.addEventListener("scroll", handleResize);
-
-    return () => {
-      observer?.disconnect();
-      window.removeEventListener("resize", handleResize);
-      container.removeEventListener("scroll", handleResize);
-    };
-  }, [handleScroll, checkScrollability]);
+    setCanScrollNext(!hasReachedEnd); // Can scroll "next" (left) if not at left edge
+    setCanScrollPrev(!hasReachedStart); // Can scroll "prev" (right) if not at right edge
+  }, [dir]);
path: packages/ui/src/components/Carrousel.tsx
user: login: ledjay
body: @kaaloo unfortunately I tried the gemini way and it doesn't update as expected
created_at: 2025-03-17T17:00:46Z
author_association: COLLABORATOR

PR comments:
user: login: linear[bot]
created_at: 2025-03-17T14:40:44Z
body: <p><a href="https://linear.app/refugiesinfo/issue/RI-660/soucis-rtl-sur-page-recherche">RI-660 Soucis RTL sur page recherche</a></p>
author_association: NONE
user: login: kaaloo
created_at: 2025-03-17T16:32:47Z
body: @ledjay for the snapshot conflict, you can merge with dev and update the snapshot again.
author_association: CONTRIBUTOR
user: login: ledjay
created_at: 2025-03-17T17:22:57Z
body: @kaaloo fixed the rtl padding for large screens
author_association: COLLABORATOR