Inline Constants Refactor Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Remove redundant constants files that extract text content. Inline page-level data into pages/components. Move hardcoded i18n strings to paraglide messages. Keep pure data constants (image paths, prices) in lib/data/ or lib/constants/ as appropriate.
Architecture:
lib/constants/reservation-form.ts— delete, move strings to paraglide messageslib/constants/venue-rental-page.ts— delete, inline intovenue-rental/page.tsx, strings to paraglidelib/data/venue-rental.ts— keep (image paths, purely visual data)- French Mentalist components — update to use paraglide
mfunction
Tech Stack: paraglide-js, Next.js 16, React Hook Form + Zod
File Map
| File | Action |
|---|---|
apps/frontend/lib/constants/reservation-form.ts | DELETE — replace with paraglide messages |
apps/frontend/lib/constants/venue-rental-page.ts | DELETE — inline + paraglide |
apps/frontend/messages/en.json | ADD french_mentalist and venue_rental keys |
apps/frontend/messages/vi.json | ADD same keys with Vietnamese |
apps/frontend/components/features/french-mentalist/reservation-form.tsx | UPDATE — use paraglide m directly |
apps/frontend/components/features/french-mentalist/use-reservation-form.ts | UPDATE — use paraglide m |
apps/frontend/components/features/french-mentalist/success-state.tsx | UPDATE — use paraglide m directly |
apps/frontend/components/features/french-mentalist/summary-card.tsx | UPDATE — use paraglide m directly |
apps/frontend/app/[locale]/(landing)/venue-rental/page.tsx | UPDATE — inline content + use paraglide m |
Task 1: Add french_mentalist reservation messages to paraglide
Files:
-
Modify:
apps/frontend/messages/en.json -
Modify:
apps/frontend/messages/vi.json -
Step 1: Add french_mentalist reservation keys to en.json
Find the last key in en.json, then add:
,
"french_mentalist_reservation_step1_title": "Your Information",
"french_mentalist_reservation_full_name": "Full Name",
"french_mentalist_reservation_full_name_ph": "Your full name",
"french_mentalist_reservation_gender": "Title",
"french_mentalist_reservation_gender_male": "Mr",
"french_mentalist_reservation_gender_female": "Ms",
"french_mentalist_reservation_gender_other": "Other",
"french_mentalist_reservation_email": "Email",
"french_mentalist_reservation_email_ph": "your@email.com",
"french_mentalist_reservation_phone": "Phone",
"french_mentalist_reservation_phone_ph": "+84 ...",
"french_mentalist_reservation_next": "Next",
"french_mentalist_reservation_back": "Back",
"french_mentalist_reservation_step": "Step",
"french_mentalist_reservation_of": "of",
"french_mentalist_reservation_step2_title": "Your Experience",
"french_mentalist_reservation_guests": "Number of Guests",
"french_mentalist_reservation_guests_select": "Select",
"french_mentalist_reservation_person": "person",
"french_mentalist_reservation_persons": "persons",
"french_mentalist_reservation_experience": "Which experience would you like?",
"french_mentalist_reservation_dinner_show": "Dinner & Show",
"french_mentalist_reservation_dinner_show_price": "1,200K VND",
"french_mentalist_reservation_dinner_show_desc": "Full dinner + mentalism show — per person",
"french_mentalist_reservation_show_only": "Show Only",
"french_mentalist_reservation_show_only_price": "450K VND",
"french_mentalist_reservation_show_only_desc": "Access to the mentalism show — per person",
"french_mentalist_reservation_step3_title": "Summary",
"french_mentalist_reservation_name": "Name",
"french_mentalist_reservation_experience_label": "Experience",
"french_mentalist_reservation_guests_label": "Guests",
"french_mentalist_reservation_total": "Total",
"french_mentalist_reservation_modify": "Modify",
"french_mentalist_reservation_reserve": "Reserve Now",
"french_mentalist_reservation_success_title": "Reservation",
"french_mentalist_reservation_success_title_em": "confirmed",
"french_mentalist_reservation_success_msg": "Thank you for your reservation. A confirmation email will be sent shortly.",
"french_mentalist_reservation_success_date": "Thursday, April 23 — 19:30",
"french_mentalist_reservation_success_note": "Get ready for an unforgettable evening at House of Legends.",
"french_mentalist_reservation_close": "Close",
"french_mentalist_reservation_please_enter_full_name": "Please enter your full name",
"french_mentalist_reservation_please_enter_email": "Please enter a valid email",
"french_mentalist_reservation_please_enter_phone": "Please enter your phone number",
"french_mentalist_reservation_please_select_guests": "Please select the number of guests",
"french_mentalist_reservation_please_choose_experience": "Please choose an experience",
"french_mentalist_reservation_your_info": "Your info",
"french_mentalist_reservation_exp_label": "Experience"- Step 2: Add french_mentalist reservation keys to vi.json
Same structure with Vietnamese values:
,
"french_mentalist_reservation_step1_title": "Thông Tin Của Bạn",
"french_mentalist_reservation_full_name": "Họ và Tên",
"french_mentalist_reservation_full_name_ph": "Họ và tên của bạn",
"french_mentalist_reservation_gender": "Danh xưng",
"french_mentalist_reservation_gender_male": "Anh",
"french_mentalist_reservation_gender_female": "Chị",
"french_mentalist_reservation_gender_other": "Khác",
"french_mentalist_reservation_email": "Email",
"french_mentalist_reservation_email_ph": "email@cuaban.com",
"french_mentalist_reservation_phone": "Điện Thoại",
"french_mentalist_reservation_phone_ph": "+84 ...",
"french_mentalist_reservation_next": "Tiếp Theo",
"french_mentalist_reservation_back": "Quay Lại",
"french_mentalist_reservation_step": "Bước",
"french_mentalist_reservation_of": "/",
"french_mentalist_reservation_step2_title": "Trải Nghiệm Của Bạn",
"french_mentalist_reservation_guests": "Số Khách",
"french_mentalist_reservation_guests_select": "Chọn",
"french_mentalist_reservation_person": "người",
"french_mentalist_reservation_persons": "người",
"french_mentalist_reservation_experience": "Bạn muốn trải nghiệm nào?",
"french_mentalist_reservation_dinner_show": "Bữa Tối & Show",
"french_mentalist_reservation_dinner_show_price": "1.200K VND",
"french_mentalist_reservation_dinner_show_desc": "Bữa tối đầy đủ + show ảo thuật tâm lý — mỗi người",
"french_mentalist_reservation_show_only": "Chỉ Show Diễn",
"french_mentalist_reservation_show_only_price": "450K VND",
"french_mentalist_reservation_show_only_desc": "Vào xem show ảo thuật tâm lý — mỗi người",
"french_mentalist_reservation_step3_title": "Tóm Tắt",
"french_mentalist_reservation_name": "Tên",
"french_mentalist_reservation_experience_label": "Trải nghiệm",
"french_mentalist_reservation_guests_label": "Số khách",
"french_mentalist_reservation_total": "Tổng cộng",
"french_mentalist_reservation_modify": "Chỉnh sửa",
"french_mentalist_reservation_reserve": "Đặt Chỗ Ngay",
"french_mentalist_reservation_success_title": "Đặt chỗ",
"french_mentalist_reservation_success_title_em": "thành công",
"french_mentalist_reservation_success_msg": "Cảm ơn bạn đã đặt chỗ. Email xác nhận sẽ được gửi trong thời gian sớm nhất.",
"french_mentalist_reservation_success_date": "Thứ Năm, 23 Tháng 4 — 19:30",
"french_mentalist_reservation_success_note": "Hãy sẵn sàng cho một buổi tối khó quên tại House of Legends.",
"french_mentalist_reservation_close": "Đóng",
"french_mentalist_reservation_please_enter_full_name": "Vui lòng nhập họ và tên",
"french_mentalist_reservation_please_enter_email": "Vui lòng nhập email hợp lệ",
"french_mentalist_reservation_please_enter_phone": "Vui lòng nhập số điện thoại",
"french_mentalist_reservation_please_select_guests": "Vui lòng chọn số khách",
"french_mentalist_reservation_please_choose_experience": "Vui lòng chọn trải nghiệm",
"french_mentalist_reservation_your_info": "Thông tin",
"french_mentalist_reservation_exp_label": "Trải nghiệm"- Step 3: Compile paraglide
cd apps/frontend && pnpm paraglide:compileExpected: no errors. Check src/paraglide/messages.ts (or .js) contains the new keys.
Task 2: Refactor use-reservation-form.ts to use paraglide
Files:
-
Modify:
apps/frontend/components/features/french-mentalist/use-reservation-form.ts -
Step 1: Update imports and remove getReservationTranslations
Replace:
import {
getReservationTranslations,
type ReservationLanguage,
} from "~/lib/constants/reservation-form";With:
import { m } from "~/src/paraglide/messages";- Step 2: Update the hook signature
The hook currently accepts { lang } and calls getReservationTranslations(lang). Change it to accept { lang } but use paraglide's m directly. Since paraglide's m function uses getLocale() internally, pass lang only where the hook needs to compute derived strings (like price display).
The t object returned by the hook currently has this shape — replace each property with a paraglide message call:
// OLD:
const t = getReservationTranslations(lang);
t.step1Title; // → m.french_mentalist_reservation_step1_title()
t.fullName; // → m.french_mentalist_reservation_full_name()
t.person; // → m.french_mentalist_reservation_person()
t.persons; // → m.french_mentalist_reservation_persons()Since experienceOptions builds price labels from t.dinnerShowPrice etc., these need lang-aware paraglide calls. But paraglide's m function is () => string — it returns the current locale string automatically. So you can just call m.french_mentalist_reservation_dinner_show_price() directly.
Updated hook:
"use client";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { type FrenchMentalistExperience } from "~/lib/constants/french-mentalist";
import { calculateFrenchMentalistTotal } from "~/lib/utils/pricing";
import { formatPrice } from "~/lib/utils/price";
import {
frenchMentalistReservationSchema,
type FrenchMentalistReservationData,
} from "~/lib/schemas/french-mentalist";
import { m } from "~/src/paraglide/messages";
export function useReservationForm() {
const [step, setStep] = useState(1);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const {
register,
setValue,
watch,
formState: { errors },
trigger,
} = useForm<FrenchMentalistReservationData>({
resolver: zodResolver(frenchMentalistReservationSchema),
defaultValues: {
fullName: "",
gender: undefined,
email: "",
phone: "",
guests: 1 as unknown as undefined,
experience: undefined,
},
mode: "onBlur",
});
const watchedData = watch() as FrenchMentalistReservationData;
const getTotal = () => {
if (!watchedData.experience || !watchedData.guests) return formatPrice(0);
return calculateFrenchMentalistTotal(
watchedData.experience as FrenchMentalistExperience,
watchedData.guests,
);
};
const resetAndClose = () => {
setStep(1);
setIsSuccess(false);
setIsSubmitting(false);
};
const onNextStep1 = async () => {
const valid = await trigger(["fullName", "gender", "email", "phone"]);
if (valid) setStep(2);
};
const onNextStep2 = async () => {
const valid = await trigger(["guests", "experience"]);
if (valid) setStep(3);
};
const onSubmit = async () => {
setIsSubmitting(true);
await new Promise((resolve) => setTimeout(resolve, 1500));
setIsSubmitting(false);
setIsSuccess(true);
};
const getGuestLabel = (count: number) => {
return count === 1
? m.french_mentalist_reservation_person()
: m.french_mentalist_reservation_persons();
};
const experienceOptions = [
{
value: "dinner-show",
label: m.french_mentalist_reservation_dinner_show(),
price: m.french_mentalist_reservation_dinner_show_price(),
description: m.french_mentalist_reservation_dinner_show_desc(),
},
{
value: "show-only",
label: m.french_mentalist_reservation_show_only(),
price: m.french_mentalist_reservation_show_only_price(),
description: m.french_mentalist_reservation_show_only_desc(),
},
];
return {
step,
setStep,
isSubmitting,
isSuccess,
watchedData,
getTotal,
onNextStep1,
onNextStep2,
onSubmit,
getGuestLabel,
resetAndClose,
form: { register, setValue, watch, formState: { errors }, trigger },
experienceOptions,
};
}Note: Removed lang prop and t object — components now use m directly.
Task 3: Update success-state.tsx to use paraglide
Files:
-
Modify:
apps/frontend/components/features/french-mentalist/success-state.tsx -
Step 1: Replace t prop with direct m calls
Remove:
import { getReservationTranslations } from "~/lib/constants/reservation-form";
interface SuccessStateProps {
t: ReturnType<typeof getReservationTranslations>;
onClose: () => void;
}Add:
import { m } from "~/src/paraglide/messages";
interface SuccessStateProps {
onClose: () => void;
}Then replace all t.X with m.french_mentalist_reservation_X():
t.successTitle→m.french_mentalist_reservation_success_title()t.successTitleEm→m.french_mentalist_reservation_success_title_em()t.successMsg→m.french_mentalist_reservation_success_msg()t.successDate→m.french_mentalist_reservation_success_date()t.successNote→m.french_mentalist_reservation_success_note()t.close→m.french_mentalist_reservation_close()
export function SuccessState({ onClose }: SuccessStateProps) {
return (
<div className="text-center py-10 px-5">
{/* ... */}
<h2 className="font-['Cormorant_Garamond'] text-[28px] font-normal text-[var(--color-french-text)] mb-4">
{m.french_mentalist_reservation_success_title()}{" "}
<em className="italic text-[var(--color-gold)]">{m.french_mentalist_reservation_success_title_em()}</em>
</h2>
<p className="text-[13px] text-[var(--color-french-muted)] font-light leading-relaxed mb-5">
{m.french_mentalist_reservation_success_msg()}
</p>
{/* ... */}
<strong className="font-['Cormorant_Garamond'] text-[16px] font-medium tracking-[1px] text-[var(--color-french-text)]">
{m.french_mentalist_reservation_success_date()}
</strong>
{/* ... */}
<p className="mt-4 text-[13px] text-[var(--color-french-muted)] italic font-light leading-relaxed">
{m.french_mentalist_reservation_success_note()}
</p>
<button onClick={onClose} ...>
{m.french_mentalist_reservation_close()}
</button>
</div>
);
}Task 4: Update summary-card.tsx to use paraglide
Files:
-
Modify:
apps/frontend/components/features/french-mentalist/summary-card.tsx -
Step 1: Replace t prop with direct m calls
Remove the t prop entirely:
import { m } from "~/src/paraglide/messages";
import type { FrenchMentalistReservationData } from "~/lib/schemas/french-mentalist";
interface SummaryCardProps {
watchedData: FrenchMentalistReservationData;
getTotal: () => string;
}Replace all t.X:
t.name→m.french_mentalist_reservation_name()t.email→m.french_mentalist_reservation_email()t.phone→m.french_mentalist_reservation_phone()t.experienceLabel→m.french_mentalist_reservation_experience_label()t.guestsLabel→m.french_mentalist_reservation_guests_label()t.total→m.french_mentalist_reservation_total()t.dinnerShow/t.showOnly→m.french_mentalist_reservation_dinner_show()/m.french_mentalist_reservation_show_only()t.person/t.persons→m.french_mentalist_reservation_person()/m.french_mentalist_reservation_persons()
export function SummaryCard({ watchedData, getTotal }: SummaryCardProps) {
const getExpLabel = () => {
return watchedData.experience === "dinner-show"
? m.french_mentalist_reservation_dinner_show()
: m.french_mentalist_reservation_show_only();
};
const getGuestLabel = (count: number) => {
return count === 1
? m.french_mentalist_reservation_person()
: m.french_mentalist_reservation_persons();
};
// ... rest unchanged
}Task 5: Update reservation-form.tsx to use paraglide
Files:
-
Modify:
apps/frontend/components/features/french-mentalist/reservation-form.tsx -
Step 1: Update imports and interface
Remove lang prop since we no longer need it (paraglide uses getLocale() internally):
import { m } from "~/src/paraglide/messages";
// Remove: import type { ReservationLanguage } from "~/lib/constants/reservation-form";
interface ReservationFormProps {
isOpen: boolean;
onClose: () => void;
// lang prop removed — uses paraglide getLocale() internally
}- Step 2: Remove lang from hook call
// OLD:
const { ... } = useReservationForm({ lang });
// NEW:
const { ... } = useReservationForm();- Step 3: Replace t.X with m.X throughout the JSX
All t.X references become m.french_mentalist_reservation_X():
t.step1Title→m.french_mentalist_reservation_step1_title()t.fullName→m.french_mentalist_reservation_full_name()t.fullNamePh→m.french_mentalist_reservation_full_name_ph()t.email→m.french_mentalist_reservation_email()t.emailPh→m.french_mentalist_reservation_email_ph()t.phone→m.french_mentalist_reservation_phone()t.phonePh→m.french_mentalist_reservation_phone_ph()t.back→m.french_mentalist_reservation_back()t.next→m.french_mentalist_reservation_next()t.step→m.french_mentalist_reservation_step()t.of→m.french_mentalist_reservation_of()t.step2Title→m.french_mentalist_reservation_step2_title()t.guests→m.french_mentalist_reservation_guests()t.guestsSelect→m.french_mentalist_reservation_guests_select()t.experience→m.french_mentalist_reservation_experience()t.pleaseSelectGuests→m.french_mentalist_reservation_please_select_guests()t.pleaseChooseExperience→m.french_mentalist_reservation_please_choose_experience()t.step3Title→m.french_mentalist_reservation_step3_title()t.modify→m.french_mentalist_reservation_modify()t.reserve→m.french_mentalist_reservation_reserve()t.yourInfo→m.french_mentalist_reservation_your_info()t.expLabel→m.french_mentalist_reservation_exp_label()t.pleaseEnterFullName→m.french_mentalist_reservation_please_enter_full_name()t.pleaseEnterEmail→m.french_mentalist_reservation_please_enter_email()t.pleaseEnterPhone→m.french_mentalist_reservation_please_enter_phone()
Also remove t from destructuring and remove experienceOptions from hook return (it's now built in the hook, so keep it in the hook).
- Step 4: Update SuccessState and SummaryCard calls
// OLD:
<SuccessState t={t} onClose={onClose} />
<SummaryCard watchedData={watchedData} t={t} getTotal={getTotal} />
// NEW:
<SuccessState onClose={onClose} />
<SummaryCard watchedData={watchedData} getTotal={getTotal} />- Step 5: Update the French Mentalist page to remove lang prop
apps/frontend/app/[locale]/(landing)/experiences/french-mentalist/page.tsx:
Remove lang prop from ReservationForm:
<ReservationForm
isOpen={showReservation}
onClose={() => setShowReservation(false)}
/>Task 6: Delete reservation-form.ts
Files:
-
Delete:
apps/frontend/lib/constants/reservation-form.ts -
Step 1: Verify no remaining imports
grep -r "reservation-form" apps/frontend --include="*.ts" --include="*.tsx"Expected: no matches (except the deleted file).
Task 7: Add venue_rental messages to paraglide
Files:
-
Modify:
apps/frontend/messages/en.json -
Modify:
apps/frontend/messages/vi.json -
Step 1: Add venue_rental keys to en.json
,
"venue_rental_hero_title": "Creative Venue Rental in Da Nang",
"venue_rental_hero_subtitle": "House of Legends offers a unique theatre venue available for rehearsals, filming, photography and creative events.",
"venue_rental_hero_cta_text": "Book the Venue",
"venue_rental_creative_space_title": "A Unique Creative Space",
"venue_rental_ideal_for_title": "Ideal For",
"venue_rental_ideal_for_subtitle": "The venue accommodates a variety of creative projects",
"venue_rental_venue_title": "The Venue",
"venue_rental_space_title": "The Space",
"venue_rental_space_subtitle": "House of Legends includes several distinct areas that can be used individually or together depending on the project.",
"venue_rental_space_performance_spaces_title": "Performance Spaces",
"venue_rental_space_audience_areas_title": "Audience & Hospitality Areas",
"venue_rental_space_support_spaces_title": "Support Spaces",
"venue_rental_rental_format_title": "Rental Format",
"venue_rental_rental_format_description": "Venue rental is typically available during non-programming hours. Typical rental duration: 2 to 4 hours. Longer rentals may be arranged depending on availability.",
"venue_rental_ideal_show_format_title": "Ideal Show Format",
"venue_rental_practical_info_cta_text": "Reserve Now",
"venue_rental_inquiry_form_title": "Venue Rental",
"venue_rental_inquiry_form_subtitle": "Tell us about your event and our team will contact you with a tailored proposal.",
"venue_rental_metadata_title": "Venue Rental | House of Legends",
"venue_rental_metadata_description": "Rent House of Legends in Da Nang — intimate theatre space for filming, photography, rehearsals, workshops and private creative events."- Step 2: Add venue_rental keys to vi.json
,
"venue_rental_hero_title": "Thuê Địa Điểm Sáng Tạo tại Da Nang",
"venue_rental_hero_subtitle": "House of Legends cung cấp một không gian sân khấu độc đáo phù hợp cho các buổi tập, quay phim, chụp ảnh và các sự kiện sáng tạo.",
"venue_rental_hero_cta_text": "Đặt Địa Điểm",
"venue_rental_creative_space_title": "Không Gian Sáng Tạo Độc Đáo",
"venue_rental_ideal_for_title": "Phù Hợp Cho",
"venue_rental_ideal_for_subtitle": "Địa điểm phù hợp với nhiều loại dự án sáng tạo",
"venue_rental_venue_title": "Địa Điểm",
"venue_rental_space_title": "Không Gian",
"venue_rental_space_subtitle": "House of Legends bao gồm nhiều khu vực khác nhau có thể được sử dụng riêng lẻ hoặc kết hợp tùy theo dự án.",
"venue_rental_space_performance_spaces_title": "Không Gian Biểu Diễn",
"venue_rental_space_audience_areas_title": "Khu Vực Khán Giả & Tiếp Khách",
"venue_rental_space_support_spaces_title": "Không Gian Hỗ Trợ",
"venue_rental_rental_format_title": "Hình Thức Thuê",
"venue_rental_rental_format_description": "Việc thuê địa điểm thường có sẵn trong giờ không có lịch trình. Thời gian thuê điển hình: 2 đến 4 giờ. Có thể thuê lâu hơn tùy theo tình trạng.",
"venue_rental_ideal_show_format_title": "Định Dạng Show Lý Tưởng",
"venue_rental_practical_info_cta_text": "Đặt Ngay",
"venue_rental_inquiry_form_title": "Thuê Địa Điểm",
"venue_rental_inquiry_form_subtitle": "Cho chúng tôi biết về sự kiện của bạn và đội ngũ sẽ liên hệ với bạn với một đề xuất phù hợp.",
"venue_rental_metadata_title": "Thuê Địa Điểm | House of Legends",
"venue_rental_metadata_description": "Thuê House of Legends tại Da Nang — không gian sân khấu thân mật để quay phim, chụp ảnh, tập, hội thảo và các sự kiện sáng tạo riêng tư."- Step 3: Compile paraglide
cd apps/frontend && pnpm paraglide:compileTask 8: Inline venue-rental-page.ts into venue-rental/page.tsx
Files:
-
Modify:
apps/frontend/app/[locale]/(landing)/venue-rental/page.tsx -
Delete:
apps/frontend/lib/constants/venue-rental-page.ts -
Step 1: Update imports
Remove:
import {
venueRentalPage,
venueRentalImages,
} from "~/lib/constants/venue-rental-page";Add:
import { m } from "~/src/paraglide/messages";Keep the venueRentalImages import — it's image paths (non-text data). But move it from ~/lib/constants/venue-rental-page to ~/lib/data/venue-rental if it's not already there.
Actually: venueRentalImages is in venue-rental-page.ts (the file we're deleting). Move it to venue-rental.ts (the data file).
In lib/data/venue-rental.ts, add:
export const venueRentalImages = {
heroBackground: "/images/venue-rental-hero-banner.jpg",
creativeSpace: "/images/venue-rental-1.jpg",
venue: "/images/landing-page/artist-carousel-1.jpg",
rentalFormatBackground: "/images/venue-rental-10.jpg",
practicalInfoBackground: "/images/landing-page/practical-info-stage.jpg",
inquiryFormBackground: "/images/landing-page/reservations-background.jpg",
};And in lib/constants/venue-rental-page.ts remove the venueRentalImages export (the file will be deleted).
Wait — venueRentalImages is only used in the page. Since we're deleting the constants file, we need somewhere to put venueRentalImages. Let me check: is venueRentalImages used anywhere else?
grep -r "venueRentalImages" apps/frontend --include="*.ts" --include="*.tsx"If it's only used in the page, add it to lib/data/venue-rental.ts and import from there.
- Step 2: Replace venueRentalPage.X with m.venue_rental_X() throughout the JSX
All references to venueRentalPage.hero.title, venueRentalPage.creativeSpace.title, etc. become m.venue_rental_hero_title(), etc.
For metadata export, use paraglide's message functions inline.
Example replacements:
venueRentalPage.hero.title→m.venue_rental_hero_title()venueRentalPage.hero.subtitle→m.venue_rental_hero_subtitle()venueRentalPage.hero.ctaText→m.venue_rental_hero_cta_text()venueRentalPage.hero.ctaHref→"#vr-inquiry"(this is a URL anchor, not translatable)venueRentalPage.creativeSpace.title→m.venue_rental_creative_space_title()venueRentalPage.idealFor.title→m.venue_rental_ideal_for_title()venueRentalPage.idealFor.subtitle→m.venue_rental_ideal_for_subtitle()venueRentalPage.venue.title→m.venue_rental_venue_title()venueRentalPage.theSpace.title→m.venue_rental_space_title()venueRentalPage.theSpace.subtitle→m.venue_rental_space_subtitle()venueRentalPage.theSpace.performanceSpacesTitle→m.venue_rental_space_performance_spaces_title()venueRentalPage.theSpace.audienceAreasTitle→m.venue_rental_space_audience_areas_title()venueRentalPage.theSpace.supportSpacesTitle→m.venue_rental_space_support_spaces_title()venueRentalPage.rentalFormat.title→m.venue_rental_rental_format_title()venueRentalPage.rentalFormat.description→m.venue_rental_rental_format_description()venueRentalPage.idealShowFormat.title→m.venue_rental_ideal_show_format_title()venueRentalPage.practicalInfo.ctaText→m.venue_rental_practical_info_cta_text()venueRentalPage.practicalInfo.ctaHref→"#vr-inquiry"(anchor)venueRentalPage.inquiryForm.title→m.venue_rental_inquiry_form_title()venueRentalPage.inquiryForm.subtitle→m.venue_rental_inquiry_form_subtitle()venueRentalPage.metadata.title→m.venue_rental_metadata_title()venueRentalPage.metadata.description→m.venue_rental_metadata_description()
For the metadata export at the bottom of the file:
export const metadata = {
title: m.venue_rental_metadata_title(),
description: m.venue_rental_metadata_description(),
};- Step 3: Delete venue-rental-page.ts
rm apps/frontend/lib/constants/venue-rental-page.tsTask 9: Verify build
- Step 1: Run type check and build
cd apps/frontend && pnpm build 2>&1 | head -50Expected: no TypeScript errors. paraglide compile should succeed.
Self-Review Checklist
-
reservation-form.tsdeleted — verify no remaining imports -
venue-rental-page.tsdeleted — verify no remaining imports - All
m.french_mentalist_reservation_*keys exist in both en.json and vi.json - All
m.venue_rental_*keys exist in both en.json and vi.json -
experienceOptionscorrectly built in hook (no moretdependency) -
SuccessStateandSummaryCardno longer receivetprop - French Mentalist page no longer passes
langtoReservationForm - Build succeeds