Browse Source

feat(locales): add attribute value translations for exercise types, muscle groups, and equipment in English and French to enhance localization support
refactor(exercise-video-modal): utilize getAttributeValueLabel for displaying exercise attributes in the modal for improved translation handling
chore(dialog): update close button styles for better accessibility in dark mode
chore(attribute-value-translation): create a new module for mapping exercise attribute values to translation keys for better organization and maintainability

Mathias 1 month ago
parent
commit
d9d1ac33ea

+ 55 - 0
locales/en.ts

@@ -238,6 +238,61 @@ export default {
       repeat: "Repeat",
       delete: "Delete",
     },
+    attribute_value: {
+      bodyweight: "Bodyweight",
+      strength: "Strength",
+      powerlifting: "Powerlifting",
+      calisthenic: "Calisthenics",
+      plyometrics: "Plyometrics",
+      stretching: "Stretching",
+      strongman: "Strongman",
+      cardio: "Cardio",
+      stabilization: "Stabilization",
+      power: "Power",
+      resistance: "Resistance",
+      crossfit: "CrossFit",
+      weightlifting: "Weightlifting",
+      neck: "Neck",
+      lats: "Lats",
+      adductors: "Adductors",
+      abductors: "Abductors",
+      groin: "Groin",
+      full_body: "Full body",
+      rotator_cuff: "Rotator cuff",
+      hip_flexor: "Hip flexor",
+      achilles_tendon: "Achilles tendon",
+      fingers: "Fingers",
+      smith_machine: "Smith machine",
+      other: "Other",
+      ez_bar: "EZ bar",
+      machine: "Machine",
+      desk: "Desk",
+      none: "None",
+      cable: "Cable",
+      medicine_ball: "Medicine ball",
+      swiss_ball: "Swiss ball",
+      foam_roll: "Foam roll",
+      trx: "TRX",
+      box: "Box",
+      ropes: "Ropes",
+      spin_bike: "Spin bike",
+      step: "Step",
+      bosu: "BOSU",
+      tyre: "Tyre",
+      sandbag: "Sandbag",
+      pole: "Pole",
+      wall: "Wall",
+      bar: "Bar",
+      rack: "Rack",
+      car: "Car",
+      sled: "Sled",
+      chain: "Chain",
+      skierg: "SkiErg",
+      rope: "Rope",
+      na: "N/A",
+      isolation: "Isolation",
+      compound: "Compound",
+    },
   },
   commons: {
     signup_with: "Sign up with {provider}",

+ 55 - 0
locales/fr.ts

@@ -239,6 +239,61 @@ export default {
       repeat: "Répéter",
       delete: "Supprimer",
     },
+    attribute_value: {
+      bodyweight: "Poids du corps",
+      strength: "Force",
+      powerlifting: "Powerlifting",
+      calisthenic: "Calisthénie",
+      plyometrics: "Plyométrie",
+      stretching: "Étirement",
+      strongman: "Strongman",
+      cardio: "Cardio",
+      stabilization: "Stabilisation",
+      power: "Puissance",
+      resistance: "Résistance",
+      crossfit: "CrossFit",
+      weightlifting: "Haltérophilie",
+      neck: "Cou",
+      lats: "Grands dorsaux",
+      adductors: "Adducteurs",
+      abductors: "Abducteurs",
+      groin: "Aine",
+      full_body: "Corps entier",
+      rotator_cuff: "Coiffe des rotateurs",
+      hip_flexor: "Fléchisseur de hanche",
+      achilles_tendon: "Tendon d'Achille",
+      fingers: "Doigts",
+      smith_machine: "Smith machine",
+      other: "Autre",
+      ez_bar: "Barre EZ",
+      machine: "Machine",
+      desk: "Bureau",
+      none: "Aucun",
+      cable: "Câble",
+      medicine_ball: "Medecine ball",
+      swiss_ball: "Swiss ball",
+      foam_roll: "Foam roll",
+      trx: "TRX",
+      box: "Box",
+      ropes: "Cordes",
+      spin_bike: "Vélo de spinning",
+      step: "Step",
+      bosu: "BOSU",
+      tyre: "Pneu",
+      sandbag: "Sac de sable",
+      pole: "Barre verticale",
+      wall: "Mur",
+      bar: "Barre",
+      rack: "Rack",
+      car: "Voiture",
+      sled: "Luge",
+      chain: "Chaîne",
+      skierg: "SkiErg",
+      rope: "Corde",
+      na: "N/A",
+      isolation: "Isolation",
+      compound: "Polyarticulaire",
+    },
   },
   commons: {
     signup_with: "S'inscrire avec {provider}",

+ 1 - 1
src/components/ui/dialog.tsx

@@ -45,7 +45,7 @@ const DialogContent = React.forwardRef<
     >
       {children}
       <DialogPrimitive.Close className="data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-[18px] rounded-sm text-black transition-opacity hover:opacity-70 focus:outline-none disabled:pointer-events-none ltr:right-5 rtl:left-5 dark:text-white">
-        <X className="h-6 w-6 flex-none p-1 hover:rounded-lg hover:bg-gray-300" />
+        <X className="h-6 w-6 flex-none p-1 hover:rounded-lg hover:bg-gray-300 dark:hover:bg-gray-700" />
         <span className="sr-only">Close</span>
       </DialogPrimitive.Close>
     </DialogPrimitive.Content>

+ 16 - 8
src/features/workout-builder/ui/exercise-video-modal.tsx

@@ -1,7 +1,6 @@
-"use client";
-
 import { useI18n } from "locales/client";
 import { getYouTubeEmbedUrl } from "@/shared/lib/youtube";
+import { getAttributeValueLabel } from "@/shared/lib/attribute-value-translation";
 import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
 
 import type { ExerciseWithAttributes } from "../types";
@@ -13,7 +12,6 @@ interface ExerciseVideoModalProps {
 }
 
 export function ExerciseVideoModal({ open, onOpenChange, exercise }: ExerciseVideoModalProps) {
-  console.log("exercise:", exercise);
   const t = useI18n();
   const locale = typeof window !== "undefined" && window.navigator.language.startsWith("fr") ? "fr" : "en";
   const title = locale === "fr" ? exercise.name : exercise.nameEn || exercise.name;
@@ -45,20 +43,30 @@ export function ExerciseVideoModal({ open, onOpenChange, exercise }: ExerciseVid
           <DialogTitle className="text-lg md:text-xl font-bold flex flex-col gap-2">
             {title}
             <div className="flex flex-wrap gap-2 mt-2">
-              {type && <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.TYPE}`}>{type}</span>}
+              {type && (
+                <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.TYPE}`}>{getAttributeValueLabel(type, t)}</span>
+              )}
               {primaryMuscle && (
-                <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.PRIMARY_MUSCLE}`}>{primaryMuscle}</span>
+                <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.PRIMARY_MUSCLE}`}>
+                  {getAttributeValueLabel(primaryMuscle, t)}
+                </span>
               )}
               {secondaryMuscle && (
-                <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.SECONDARY_MUSCLE}`}>{secondaryMuscle}</span>
+                <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.SECONDARY_MUSCLE}`}>
+                  {getAttributeValueLabel(secondaryMuscle, t)}
+                </span>
               )}
               {equipment.length > 0 &&
                 equipment.map((eq) => (
                   <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.EQUIPMENT}`} key={eq}>
-                    {eq}
+                    {getAttributeValueLabel(eq, t)}
                   </span>
                 ))}
-              {mechanics && <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.MECHANICS_TYPE}`}>{mechanics}</span>}
+              {mechanics && (
+                <span className={`px-2 py-0.5 rounded text-xs font-medium ${badgeColors.MECHANICS_TYPE}`}>
+                  {getAttributeValueLabel(mechanics, t)}
+                </span>
+              )}
             </div>
           </DialogTitle>
         </DialogHeader>

+ 98 - 0
src/shared/lib/attribute-value-translation.ts

@@ -0,0 +1,98 @@
+import { ExerciseAttributeValueEnum } from "@prisma/client";
+
+import { TFunction } from "locales/client";
+
+/**
+ * Map enum values to translation keys for i18n
+ */
+export const ATTRIBUTE_VALUE_TRANSLATION_KEYS: Record<ExerciseAttributeValueEnum, string> = {
+  // Types d'exercices
+  BODYWEIGHT: "workout_builder.attribute_value.bodyweight",
+  STRENGTH: "workout_builder.attribute_value.strength",
+  POWERLIFTING: "workout_builder.attribute_value.powerlifting",
+  CALISTHENIC: "workout_builder.attribute_value.calisthenic",
+  PLYOMETRICS: "workout_builder.attribute_value.plyometrics",
+  STRETCHING: "workout_builder.attribute_value.stretching",
+  STRONGMAN: "workout_builder.attribute_value.strongman",
+  CARDIO: "workout_builder.attribute_value.cardio",
+  STABILIZATION: "workout_builder.attribute_value.stabilization",
+  POWER: "workout_builder.attribute_value.power",
+  RESISTANCE: "workout_builder.attribute_value.resistance",
+  CROSSFIT: "workout_builder.attribute_value.crossfit",
+  WEIGHTLIFTING: "workout_builder.attribute_value.weightlifting",
+
+  // Groupes musculaires
+  BICEPS: "workout_builder.muscles.biceps",
+  SHOULDERS: "workout_builder.muscles.shoulders",
+  CHEST: "workout_builder.muscles.chest",
+  BACK: "workout_builder.muscles.back",
+  GLUTES: "workout_builder.muscles.glutes",
+  TRICEPS: "workout_builder.muscles.triceps",
+  HAMSTRINGS: "workout_builder.muscles.hamstrings",
+  QUADRICEPS: "workout_builder.muscles.quadriceps",
+  FOREARMS: "workout_builder.muscles.forearms",
+  CALVES: "workout_builder.muscles.calves",
+  TRAPS: "workout_builder.muscles.traps",
+  ABDOMINALS: "workout_builder.muscles.abdominals",
+  NECK: "workout_builder.attribute_value.neck",
+  LATS: "workout_builder.attribute_value.lats",
+  ADDUCTORS: "workout_builder.attribute_value.adductors",
+  ABDUCTORS: "workout_builder.attribute_value.abductors",
+  OBLIQUES: "workout_builder.muscles.obliques",
+  GROIN: "workout_builder.attribute_value.groin",
+  FULL_BODY: "workout_builder.attribute_value.full_body",
+  ROTATOR_CUFF: "workout_builder.attribute_value.rotator_cuff",
+  HIP_FLEXOR: "workout_builder.attribute_value.hip_flexor",
+  ACHILLES_TENDON: "workout_builder.attribute_value.achilles_tendon",
+  FINGERS: "workout_builder.attribute_value.fingers",
+
+  // Équipements
+  DUMBBELL: "workout_builder.equipment.dumbbell.label",
+  KETTLEBELLS: "workout_builder.equipment.kettlebell.label",
+  BARBELL: "workout_builder.equipment.barbell.label",
+  SMITH_MACHINE: "workout_builder.attribute_value.smith_machine",
+  BODY_ONLY: "workout_builder.equipment.bodyweight.label",
+  OTHER: "workout_builder.attribute_value.other",
+  BANDS: "workout_builder.equipment.band.label",
+  EZ_BAR: "workout_builder.attribute_value.ez_bar",
+  MACHINE: "workout_builder.attribute_value.machine",
+  DESK: "workout_builder.attribute_value.desk",
+  PULLUP_BAR: "workout_builder.equipment.pullup_bar.label",
+  NONE: "workout_builder.attribute_value.none",
+  CABLE: "workout_builder.attribute_value.cable",
+  MEDICINE_BALL: "workout_builder.attribute_value.medicine_ball",
+  SWISS_BALL: "workout_builder.attribute_value.swiss_ball",
+  FOAM_ROLL: "workout_builder.attribute_value.foam_roll",
+  WEIGHT_PLATE: "workout_builder.equipment.plate.label",
+  TRX: "workout_builder.attribute_value.trx",
+  BOX: "workout_builder.attribute_value.box",
+  ROPES: "workout_builder.attribute_value.ropes",
+  SPIN_BIKE: "workout_builder.attribute_value.spin_bike",
+  STEP: "workout_builder.attribute_value.step",
+  BOSU: "workout_builder.attribute_value.bosu",
+  TYRE: "workout_builder.attribute_value.tyre",
+  SANDBAG: "workout_builder.attribute_value.sandbag",
+  POLE: "workout_builder.attribute_value.pole",
+  BENCH: "workout_builder.equipment.bench.label",
+  WALL: "workout_builder.attribute_value.wall",
+  BAR: "workout_builder.attribute_value.bar",
+  RACK: "workout_builder.attribute_value.rack",
+  CAR: "workout_builder.attribute_value.car",
+  SLED: "workout_builder.attribute_value.sled",
+  CHAIN: "workout_builder.attribute_value.chain",
+  SKIERG: "workout_builder.attribute_value.skierg",
+  ROPE: "workout_builder.attribute_value.rope",
+  NA: "workout_builder.attribute_value.na",
+
+  // Types de mécanique
+  ISOLATION: "workout_builder.attribute_value.isolation",
+  COMPOUND: "workout_builder.attribute_value.compound",
+};
+
+/**
+ * Get the localized label for an ExerciseAttributeValueEnum
+ */
+export function getAttributeValueLabel(value: ExerciseAttributeValueEnum, t: TFunction): string {
+  const key = ATTRIBUTE_VALUE_TRANSLATION_KEYS[value];
+  return key ? t(key as keyof typeof t) : value;
+}