plans
2026-05-08
2026 05 08 Inline Constants

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 messages
  • lib/constants/venue-rental-page.ts — delete, inline into venue-rental/page.tsx, strings to paraglide
  • lib/data/venue-rental.ts — keep (image paths, purely visual data)
  • French Mentalist components — update to use paraglide m function

Tech Stack: paraglide-js, Next.js 16, React Hook Form + Zod


File Map

FileAction
apps/frontend/lib/constants/reservation-form.tsDELETE — replace with paraglide messages
apps/frontend/lib/constants/venue-rental-page.tsDELETE — inline + paraglide
apps/frontend/messages/en.jsonADD french_mentalist and venue_rental keys
apps/frontend/messages/vi.jsonADD same keys with Vietnamese
apps/frontend/components/features/french-mentalist/reservation-form.tsxUPDATE — use paraglide m directly
apps/frontend/components/features/french-mentalist/use-reservation-form.tsUPDATE — use paraglide m
apps/frontend/components/features/french-mentalist/success-state.tsxUPDATE — use paraglide m directly
apps/frontend/components/features/french-mentalist/summary-card.tsxUPDATE — use paraglide m directly
apps/frontend/app/[locale]/(landing)/venue-rental/page.tsxUPDATE — 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:compile

Expected: 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.successTitlem.french_mentalist_reservation_success_title()
  • t.successTitleEmm.french_mentalist_reservation_success_title_em()
  • t.successMsgm.french_mentalist_reservation_success_msg()
  • t.successDatem.french_mentalist_reservation_success_date()
  • t.successNotem.french_mentalist_reservation_success_note()
  • t.closem.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.namem.french_mentalist_reservation_name()
  • t.emailm.french_mentalist_reservation_email()
  • t.phonem.french_mentalist_reservation_phone()
  • t.experienceLabelm.french_mentalist_reservation_experience_label()
  • t.guestsLabelm.french_mentalist_reservation_guests_label()
  • t.totalm.french_mentalist_reservation_total()
  • t.dinnerShow / t.showOnlym.french_mentalist_reservation_dinner_show() / m.french_mentalist_reservation_show_only()
  • t.person / t.personsm.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.step1Titlem.french_mentalist_reservation_step1_title()
  • t.fullNamem.french_mentalist_reservation_full_name()
  • t.fullNamePhm.french_mentalist_reservation_full_name_ph()
  • t.emailm.french_mentalist_reservation_email()
  • t.emailPhm.french_mentalist_reservation_email_ph()
  • t.phonem.french_mentalist_reservation_phone()
  • t.phonePhm.french_mentalist_reservation_phone_ph()
  • t.backm.french_mentalist_reservation_back()
  • t.nextm.french_mentalist_reservation_next()
  • t.stepm.french_mentalist_reservation_step()
  • t.ofm.french_mentalist_reservation_of()
  • t.step2Titlem.french_mentalist_reservation_step2_title()
  • t.guestsm.french_mentalist_reservation_guests()
  • t.guestsSelectm.french_mentalist_reservation_guests_select()
  • t.experiencem.french_mentalist_reservation_experience()
  • t.pleaseSelectGuestsm.french_mentalist_reservation_please_select_guests()
  • t.pleaseChooseExperiencem.french_mentalist_reservation_please_choose_experience()
  • t.step3Titlem.french_mentalist_reservation_step3_title()
  • t.modifym.french_mentalist_reservation_modify()
  • t.reservem.french_mentalist_reservation_reserve()
  • t.yourInfom.french_mentalist_reservation_your_info()
  • t.expLabelm.french_mentalist_reservation_exp_label()
  • t.pleaseEnterFullNamem.french_mentalist_reservation_please_enter_full_name()
  • t.pleaseEnterEmailm.french_mentalist_reservation_please_enter_email()
  • t.pleaseEnterPhonem.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:compile

Task 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.titlem.venue_rental_hero_title()
  • venueRentalPage.hero.subtitlem.venue_rental_hero_subtitle()
  • venueRentalPage.hero.ctaTextm.venue_rental_hero_cta_text()
  • venueRentalPage.hero.ctaHref"#vr-inquiry" (this is a URL anchor, not translatable)
  • venueRentalPage.creativeSpace.titlem.venue_rental_creative_space_title()
  • venueRentalPage.idealFor.titlem.venue_rental_ideal_for_title()
  • venueRentalPage.idealFor.subtitlem.venue_rental_ideal_for_subtitle()
  • venueRentalPage.venue.titlem.venue_rental_venue_title()
  • venueRentalPage.theSpace.titlem.venue_rental_space_title()
  • venueRentalPage.theSpace.subtitlem.venue_rental_space_subtitle()
  • venueRentalPage.theSpace.performanceSpacesTitlem.venue_rental_space_performance_spaces_title()
  • venueRentalPage.theSpace.audienceAreasTitlem.venue_rental_space_audience_areas_title()
  • venueRentalPage.theSpace.supportSpacesTitlem.venue_rental_space_support_spaces_title()
  • venueRentalPage.rentalFormat.titlem.venue_rental_rental_format_title()
  • venueRentalPage.rentalFormat.descriptionm.venue_rental_rental_format_description()
  • venueRentalPage.idealShowFormat.titlem.venue_rental_ideal_show_format_title()
  • venueRentalPage.practicalInfo.ctaTextm.venue_rental_practical_info_cta_text()
  • venueRentalPage.practicalInfo.ctaHref"#vr-inquiry" (anchor)
  • venueRentalPage.inquiryForm.titlem.venue_rental_inquiry_form_title()
  • venueRentalPage.inquiryForm.subtitlem.venue_rental_inquiry_form_subtitle()
  • venueRentalPage.metadata.titlem.venue_rental_metadata_title()
  • venueRentalPage.metadata.descriptionm.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.ts

Task 9: Verify build

  • Step 1: Run type check and build
cd apps/frontend && pnpm build 2>&1 | head -50

Expected: no TypeScript errors. paraglide compile should succeed.


Self-Review Checklist

  • reservation-form.ts deleted — verify no remaining imports
  • venue-rental-page.ts deleted — 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
  • experienceOptions correctly built in hook (no more t dependency)
  • SuccessState and SummaryCard no longer receive t prop
  • French Mentalist page no longer passes lang to ReservationForm
  • Build succeeds