i18n vocabulaire exos (fr/pt-BR) + Web Push rappels de série
Build & Deploy / build (push) Successful in 17s
Build & Deploy / build (push) Successful in 17s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Traduction du vocabulaire contrôlé des exercices (zones, équipement, muscles, catégories).
|
||||
* La DB reste en anglais (canonique) ; l'API traduit à la sortie selon la locale.
|
||||
* Map à plat term(anglais, minuscule) => traduction. Repli = terme anglais si absent.
|
||||
*/
|
||||
return [
|
||||
'fr' => [
|
||||
// zones / catégories
|
||||
'back' => 'dos', 'cardio' => 'cardio', 'chest' => 'pectoraux', 'lower arms' => 'avant-bras',
|
||||
'lower legs' => 'mollets', 'neck' => 'cou', 'shoulders' => 'épaules', 'upper arms' => 'bras',
|
||||
'upper legs' => 'cuisses', 'waist' => 'abdominaux',
|
||||
// équipement
|
||||
'assisted' => 'assisté', 'band' => 'élastique', 'barbell' => 'barre', 'body weight' => 'poids du corps',
|
||||
'bosu ball' => 'bosu', 'cable' => 'poulie', 'dumbbell' => 'haltère', 'elliptical machine' => 'vélo elliptique',
|
||||
'ez barbell' => 'barre EZ', 'hammer' => 'hammer', 'kettlebell' => 'kettlebell',
|
||||
'leverage machine' => 'machine à levier', 'medicine ball' => 'medecine ball',
|
||||
'olympic barbell' => 'barre olympique', 'resistance band' => 'bande de résistance', 'roller' => 'rouleau',
|
||||
'rope' => 'corde', 'skierg machine' => 'skierg', 'sled machine' => 'traîneau', 'smith machine' => 'smith machine',
|
||||
'stability ball' => 'swiss ball', 'stationary bike' => "vélo d'appartement", 'stepmill machine' => 'stepper',
|
||||
'tire' => 'pneu', 'trap bar' => 'trap bar', 'upper body ergometer' => 'ergomètre bras',
|
||||
'weighted' => 'lesté', 'wheel roller' => 'roue abdominale',
|
||||
// muscles cibles
|
||||
'abductors' => 'abducteurs', 'abs' => 'abdominaux', 'adductors' => 'adducteurs', 'biceps' => 'biceps',
|
||||
'calves' => 'mollets', 'cardiovascular system' => 'système cardiovasculaire', 'delts' => 'deltoïdes',
|
||||
'forearms' => 'avant-bras', 'glutes' => 'fessiers', 'hamstrings' => 'ischio-jambiers', 'lats' => 'grand dorsal',
|
||||
'levator scapulae' => 'élévateur de la scapula', 'pectorals' => 'pectoraux', 'quads' => 'quadriceps',
|
||||
'serratus anterior' => 'dentelé antérieur', 'spine' => 'colonne vertébrale', 'traps' => 'trapèzes',
|
||||
'triceps' => 'triceps', 'upper back' => 'haut du dos',
|
||||
// muscles secondaires (compléments)
|
||||
'abdominals' => 'abdominaux', 'ankle stabilizers' => 'stabilisateurs de cheville', 'ankles' => 'chevilles',
|
||||
'brachialis' => 'brachial', 'chest ' => 'pectoraux', 'core' => 'gainage', 'deltoids' => 'deltoïdes',
|
||||
'feet' => 'pieds', 'grip muscles' => 'muscles de préhension', 'groin' => 'aine', 'hands' => 'mains',
|
||||
'hip flexors' => 'fléchisseurs de hanche', 'inner thighs' => 'intérieur des cuisses',
|
||||
'latissimus dorsi' => 'grand dorsal', 'lower abs' => 'bas des abdominaux', 'lower back' => 'bas du dos',
|
||||
'obliques' => 'obliques', 'quadriceps' => 'quadriceps', 'rear deltoids' => 'deltoïdes postérieurs',
|
||||
'rhomboids' => 'rhomboïdes', 'rotator cuff' => 'coiffe des rotateurs', 'shins' => 'tibias',
|
||||
'soleus' => 'soléaire', 'sternocleidomastoid' => 'sterno-cléido-mastoïdien', 'trapezius' => 'trapèze',
|
||||
'upper chest' => 'haut des pectoraux', 'wrist extensors' => 'extenseurs du poignet',
|
||||
'wrist flexors' => 'fléchisseurs du poignet', 'wrists' => 'poignets',
|
||||
],
|
||||
|
||||
'pt-BR' => [
|
||||
// zonas / categorias
|
||||
'back' => 'costas', 'cardio' => 'cardio', 'chest' => 'peito', 'lower arms' => 'antebraços',
|
||||
'lower legs' => 'panturrilhas', 'neck' => 'pescoço', 'shoulders' => 'ombros', 'upper arms' => 'braços',
|
||||
'upper legs' => 'coxas', 'waist' => 'abdômen',
|
||||
// equipamento
|
||||
'assisted' => 'assistido', 'band' => 'faixa elástica', 'barbell' => 'barra', 'body weight' => 'peso corporal',
|
||||
'bosu ball' => 'bola bosu', 'cable' => 'cabo', 'dumbbell' => 'halter', 'elliptical machine' => 'elíptico',
|
||||
'ez barbell' => 'barra EZ', 'hammer' => 'hammer', 'kettlebell' => 'kettlebell',
|
||||
'leverage machine' => 'máquina de alavanca', 'medicine ball' => 'bola medicinal',
|
||||
'olympic barbell' => 'barra olímpica', 'resistance band' => 'faixa de resistência', 'roller' => 'rolo',
|
||||
'rope' => 'corda', 'skierg machine' => 'skierg', 'sled machine' => 'trenó', 'smith machine' => 'máquina smith',
|
||||
'stability ball' => 'bola de estabilidade', 'stationary bike' => 'bicicleta ergométrica',
|
||||
'stepmill machine' => 'escada ergométrica', 'tire' => 'pneu', 'trap bar' => 'barra hexagonal',
|
||||
'upper body ergometer' => 'ergômetro de braços', 'weighted' => 'com peso', 'wheel roller' => 'roda abdominal',
|
||||
// músculos alvo
|
||||
'abductors' => 'abdutores', 'abs' => 'abdômen', 'adductors' => 'adutores', 'biceps' => 'bíceps',
|
||||
'calves' => 'panturrilhas', 'cardiovascular system' => 'sistema cardiovascular', 'delts' => 'deltoides',
|
||||
'forearms' => 'antebraços', 'glutes' => 'glúteos', 'hamstrings' => 'posteriores da coxa', 'lats' => 'dorsais',
|
||||
'levator scapulae' => 'levantador da escápula', 'pectorals' => 'peitorais', 'quads' => 'quadríceps',
|
||||
'serratus anterior' => 'serrátil anterior', 'spine' => 'coluna', 'traps' => 'trapézios',
|
||||
'triceps' => 'tríceps', 'upper back' => 'parte superior das costas',
|
||||
// músculos secundários (complementos)
|
||||
'abdominals' => 'abdominais', 'ankle stabilizers' => 'estabilizadores do tornozelo', 'ankles' => 'tornozelos',
|
||||
'brachialis' => 'braquial', 'core' => 'core', 'deltoids' => 'deltoides', 'feet' => 'pés',
|
||||
'grip muscles' => 'músculos de preensão', 'groin' => 'virilha', 'hands' => 'mãos',
|
||||
'hip flexors' => 'flexores do quadril', 'inner thighs' => 'parte interna das coxas',
|
||||
'latissimus dorsi' => 'grande dorsal', 'lower abs' => 'abdômen inferior', 'lower back' => 'lombar',
|
||||
'obliques' => 'oblíquos', 'quadriceps' => 'quadríceps', 'rear deltoids' => 'deltoides posteriores',
|
||||
'rhomboids' => 'romboides', 'rotator cuff' => 'manguito rotador', 'shins' => 'canelas',
|
||||
'soleus' => 'sóleo', 'sternocleidomastoid' => 'esternocleidomastóideo', 'trapezius' => 'trapézio',
|
||||
'upper chest' => 'parte superior do peito', 'wrist extensors' => 'extensores do punho',
|
||||
'wrist flexors' => 'flexores do punho', 'wrists' => 'punhos',
|
||||
],
|
||||
];
|
||||
@@ -30,6 +30,8 @@ class ExercisesController
|
||||
->orderBy('name')
|
||||
->paginate((int) $request->query('per_page', self::DEFAULT_PER_PAGE));
|
||||
|
||||
ExerciseListResource::$locale = $this->resolveLocale($request);
|
||||
|
||||
return ExerciseListResource::collection($exercises);
|
||||
}
|
||||
|
||||
@@ -39,14 +41,24 @@ class ExercisesController
|
||||
return new ExerciseResource($exercise, $this->resolveLocale($request));
|
||||
}
|
||||
|
||||
/** GET /api/exercises/facets — valeurs distinctes pour alimenter les filtres du front. */
|
||||
public function facets(): JsonResponse
|
||||
/**
|
||||
* GET /api/exercises/facets — valeurs distinctes pour les filtres.
|
||||
* Chaque valeur = { value: anglais (canonique, envoyé au filtre), label: traduit }.
|
||||
*/
|
||||
public function facets(Request $request): JsonResponse
|
||||
{
|
||||
$locale = $this->resolveLocale($request);
|
||||
|
||||
$facet = fn (string $column) => Exercise::query()
|
||||
->whereNotNull($column)->distinct()->orderBy($column)->pluck($column)
|
||||
->map(fn ($v) => ['value' => $v, 'label' => Exercise::localizeTerm($v, $locale)])
|
||||
->values();
|
||||
|
||||
return response()->json([
|
||||
'body_parts' => Exercise::query()->whereNotNull('body_part')->distinct()->orderBy('body_part')->pluck('body_part')->values(),
|
||||
'equipments' => Exercise::query()->whereNotNull('equipment')->distinct()->orderBy('equipment')->pluck('equipment')->values(),
|
||||
'targets' => Exercise::query()->whereNotNull('target')->distinct()->orderBy('target')->pluck('target')->values(),
|
||||
'categories' => Exercise::query()->whereNotNull('category')->distinct()->orderBy('category')->pluck('category')->values(),
|
||||
'body_parts' => $facet('body_part'),
|
||||
'equipments' => $facet('equipment'),
|
||||
'targets' => $facet('target'),
|
||||
'categories' => $facet('category'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,29 @@
|
||||
|
||||
namespace Functional\Exercises\Http\Resources;
|
||||
|
||||
use Functional\Exercises\Models\Exercise;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* Forme allégée d'un exercice pour les listes (GET /api/exercises).
|
||||
* Forme allégée d'un exercice pour les listes (GET /api/exercises), libellés localisés.
|
||||
* La locale est posée par le contrôleur avant `collection()` (collection() n'accepte pas d'argument par item).
|
||||
*/
|
||||
class ExerciseListResource extends JsonResource
|
||||
{
|
||||
public static string $locale = 'fr';
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$l = self::$locale;
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
'body_part' => $this->body_part,
|
||||
'equipment' => $this->equipment,
|
||||
'target' => $this->target,
|
||||
'body_part' => Exercise::localizeTerm($this->body_part, $l),
|
||||
'equipment' => Exercise::localizeTerm($this->equipment, $l),
|
||||
'target' => Exercise::localizeTerm($this->target, $l),
|
||||
'image_url' => $this->image_url,
|
||||
'gif_url' => $this->gif_url,
|
||||
];
|
||||
|
||||
@@ -17,16 +17,20 @@ class ExerciseResource extends JsonResource
|
||||
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$l = $this->locale;
|
||||
$model = $this->resource;
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
'category' => $this->category,
|
||||
'equipment' => $this->equipment,
|
||||
'target' => $this->target,
|
||||
'muscle_group' => $this->muscle_group,
|
||||
'secondary_muscles' => $this->secondary_muscles ?? [],
|
||||
'instructions' => $this->resource->instructionsFor($this->locale),
|
||||
'body_part' => $model::localizeTerm($this->body_part, $l),
|
||||
'category' => $model::localizeTerm($this->category, $l),
|
||||
'equipment' => $model::localizeTerm($this->equipment, $l),
|
||||
'target' => $model::localizeTerm($this->target, $l),
|
||||
'muscle_group' => $model::localizeTerm($this->muscle_group, $l),
|
||||
'secondary_muscles' => $model::localizeTerms($this->secondary_muscles, $l),
|
||||
'instructions' => $model->instructionsFor($l),
|
||||
'image_url' => $this->image_url,
|
||||
'gif_url' => $this->gif_url,
|
||||
'attribution' => $this->attribution,
|
||||
|
||||
@@ -82,6 +82,24 @@ class Exercise extends Model
|
||||
return [];
|
||||
}
|
||||
|
||||
/** Traduit un terme du vocabulaire contrôlé (zone/équipement/muscle) selon la locale. Repli = anglais. */
|
||||
public static function localizeTerm(?string $value, string $locale): ?string
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$map = config('exercise_i18n.' . $locale, []);
|
||||
|
||||
return $map[mb_strtolower($value)] ?? $value;
|
||||
}
|
||||
|
||||
/** @param array<int,string>|null $values @return array<int,string> */
|
||||
public static function localizeTerms(?array $values, string $locale): array
|
||||
{
|
||||
return array_values(array_map(fn ($v) => self::localizeTerm($v, $locale), $values ?? []));
|
||||
}
|
||||
|
||||
/** Recherche plein-texte simple (nom / muscle cible / équipement). */
|
||||
public function scopeSearch(Builder $query, ?string $term): Builder
|
||||
{
|
||||
|
||||
@@ -28,5 +28,6 @@ class ExercisesServiceProvider extends LayerServiceProvider
|
||||
public function register(): void
|
||||
{
|
||||
$this->mergeConfigFrom(__DIR__ . '/../../config/exercises.php', 'exercises');
|
||||
$this->mergeConfigFrom(__DIR__ . '/../../config/exercise_i18n.php', 'exercise_i18n');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user