Updated PLANS.md, AGENTS.md, and arabic-localization.md to reflect the “foundations now, full translations later” approach and marked progress accordingly.

Implemented localization foundations across backend and frontend (locale settings/middleware, preferred language, i18n wiring, RTL support, minimal Arabic UI strings, Accept-Language).
Added targeted backend and frontend tests plus a risks note for pending full translation coverage.
This commit is contained in:
2026-02-28 11:48:58 +03:00
parent fd90af33b3
commit d40bb10876
27 changed files with 407 additions and 68 deletions
+31 -12
View File
@@ -1,10 +1,13 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { apiGet } from "./api/client";
import { setLocale } from "./i18n";
export default function App() {
const [salons, setSalons] = useState([]);
const [query, setQuery] = useState("");
const [status, setStatus] = useState("idle");
const { t, i18n } = useTranslation();
useEffect(() => {
let ignore = false;
@@ -33,15 +36,31 @@ export default function App() {
return (
<div className="page">
<header className="hero">
<p className="eyebrow">Salon Booking Platform</p>
<h1>Find, compare, and book top salons near you.</h1>
<p className="subtitle">
Search by city or service, compare pricing, and lock in your slot in seconds.
</p>
<div className="hero-top">
<p className="eyebrow">{t("hero.eyebrow")}</p>
<div className="locale-switch" aria-label={t("locale.label")}>
<button
type="button"
className={i18n.language === "ar-sa" ? "active" : ""}
onClick={() => setLocale("ar-sa")}
>
{t("locale.arabic")}
</button>
<button
type="button"
className={i18n.language === "en" ? "active" : ""}
onClick={() => setLocale("en")}
>
{t("locale.english")}
</button>
</div>
</div>
<h1>{t("hero.title")}</h1>
<p className="subtitle">{t("hero.subtitle")}</p>
<div className="search">
<input
type="text"
placeholder="Search by salon or service"
placeholder={t("hero.searchPlaceholder")}
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
@@ -49,12 +68,12 @@ export default function App() {
</header>
<section className="results">
<h2>Salons</h2>
{status === "loading" && <p>Loading salons...</p>}
<h2>{t("results.title")}</h2>
{status === "loading" && <p>{t("results.loading")}</p>}
{status === "error" && (
<p className="error">Unable to load salons. Start the backend API to see results.</p>
<p className="error">{t("results.error")}</p>
)}
{status === "ready" && salons.length === 0 && <p>No salons found.</p>}
{status === "ready" && salons.length === 0 && <p>{t("results.empty")}</p>}
<div className="grid">
{salons.map((salon) => (
<article className="card" key={salon.id}>
@@ -62,10 +81,10 @@ export default function App() {
<h3>{salon.name}</h3>
<span className="rating">{salon.rating_avg} / 5</span>
</div>
<p>{salon.description || "No description yet."}</p>
<p>{salon.description || t("card.noDescription")}</p>
<div className="meta">
<span>{salon.city}</span>
<span>{salon.phone_number || "Phone unavailable"}</span>
<span>{salon.phone_number || t("card.phoneUnavailable")}</span>
</div>
</article>
))}