feat: i18n infrastructure with 4-locale support (en, fr, es, de)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
123
src/shared/data/useTranslatedData.ts
Normal file
123
src/shared/data/useTranslatedData.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* TabataFit Translated Data Hooks
|
||||
* Wraps raw data objects with t() lookups at render time
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Workout, Collection, Program } from '../types'
|
||||
import { CATEGORIES } from './index'
|
||||
|
||||
/** Convert an exercise/equipment name to a slug key for i18n lookup */
|
||||
function slugify(name: string): string {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.replace(/[()]/g, '')
|
||||
.replace(/&/g, 'and')
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/(^-|-$)/g, '')
|
||||
}
|
||||
|
||||
/** Translate a single workout's display strings */
|
||||
export function useTranslatedWorkout(workout: Workout | undefined) {
|
||||
const { t } = useTranslation('content')
|
||||
|
||||
return useMemo(() => {
|
||||
if (!workout) return undefined
|
||||
return {
|
||||
...workout,
|
||||
title: t(`workouts.${workout.id}`, { defaultValue: workout.title }),
|
||||
exercises: workout.exercises.map((ex) => ({
|
||||
...ex,
|
||||
name: t(`exercises.${slugify(ex.name)}`, { defaultValue: ex.name }),
|
||||
})),
|
||||
equipment: workout.equipment.map((item) =>
|
||||
t(`equipment.${slugify(item)}`, { defaultValue: item })
|
||||
),
|
||||
}
|
||||
}, [workout, t])
|
||||
}
|
||||
|
||||
/** Translate an array of workouts (titles only, for list views) */
|
||||
export function useTranslatedWorkouts<T extends { id: string; title: string }>(
|
||||
workouts: T[]
|
||||
) {
|
||||
const { t } = useTranslation('content')
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
workouts.map((w) => ({
|
||||
...w,
|
||||
title: t(`workouts.${w.id}`, { defaultValue: w.title }),
|
||||
})),
|
||||
[workouts, t]
|
||||
)
|
||||
}
|
||||
|
||||
/** Translate collections */
|
||||
export function useTranslatedCollections(collections: Collection[]) {
|
||||
const { t } = useTranslation('content')
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
collections.map((c) => ({
|
||||
...c,
|
||||
title: t(`collections.${c.id}.title`, { defaultValue: c.title }),
|
||||
description: t(`collections.${c.id}.description`, {
|
||||
defaultValue: c.description,
|
||||
}),
|
||||
})),
|
||||
[collections, t]
|
||||
)
|
||||
}
|
||||
|
||||
/** Translate programs */
|
||||
export function useTranslatedPrograms(programs: Program[]) {
|
||||
const { t } = useTranslation('content')
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
programs.map((p) => ({
|
||||
...p,
|
||||
title: t(`programs.${p.id}.title`, { defaultValue: p.title }),
|
||||
description: t(`programs.${p.id}.description`, {
|
||||
defaultValue: p.description,
|
||||
}),
|
||||
})),
|
||||
[programs, t]
|
||||
)
|
||||
}
|
||||
|
||||
/** Translate category labels */
|
||||
export function useTranslatedCategories() {
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
return useMemo(() => {
|
||||
const categoryKeyMap: Record<string, string> = {
|
||||
all: 'categories.all',
|
||||
'full-body': 'categories.fullBody',
|
||||
core: 'categories.core',
|
||||
'upper-body': 'categories.upperBody',
|
||||
'lower-body': 'categories.lowerBody',
|
||||
cardio: 'categories.cardio',
|
||||
}
|
||||
|
||||
return CATEGORIES.map((cat) => ({
|
||||
...cat,
|
||||
label: t(categoryKeyMap[cat.id] ?? cat.id, { defaultValue: cat.label }),
|
||||
}))
|
||||
}, [t])
|
||||
}
|
||||
|
||||
/** Translate a music vibe name */
|
||||
export function useMusicVibeLabel(vibe: string): string {
|
||||
const { t } = useTranslation('common')
|
||||
const vibeKeyMap: Record<string, string> = {
|
||||
electronic: 'musicVibes.electronic',
|
||||
'hip-hop': 'musicVibes.hipHop',
|
||||
pop: 'musicVibes.pop',
|
||||
rock: 'musicVibes.rock',
|
||||
chill: 'musicVibes.chill',
|
||||
}
|
||||
return t(vibeKeyMap[vibe] ?? vibe, { defaultValue: vibe })
|
||||
}
|
||||
Reference in New Issue
Block a user