Skip to content

feat(i18n): Arabic-first defaults + kill 68 ternaries + 32 raw date formatters + RTL polish + audit gate#8

Open
abdout wants to merge 7 commits into
mainfrom
feat/i18n-rtl-sweep
Open

feat(i18n): Arabic-first defaults + kill 68 ternaries + 32 raw date formatters + RTL polish + audit gate#8
abdout wants to merge 7 commits into
mainfrom
feat/i18n-rtl-sweep

Conversation

@abdout
Copy link
Copy Markdown
Contributor

@abdout abdout commented Apr 27, 2026

Summary

Phase B of the production-readiness push (plan: kun/.claude/plans/resume-the-development-in-abstract-rossum.md). Built on top of #6.

  • Arabic-first: i18n.defaultLocale flips enar. Cookie-pinned visitors stay where they were; only fresh, headerless visits land on /ar/*. Verified locally with curl -H "Accept-Language: en-US" / → 307 to /en (preserves preference) and bare / → 307 to /ar (new default).
  • 0 inline lang ternaries: 68 → 0. The audit's pre-Phase B baseline of 68 across transport/search/page.tsx (25), listings/page.tsx (12), transport/page.tsx (5), 17 generateMetadata blocks, and the rest of the long tail is now zero. Achieved by expanding pageMetadata.*, rental.property.{filters,pagination}, transport.search.{metadataTitle,pagination}, transport.{metadataTitle,routes.hoursFormat,routes.currency}, common.skipToContent in both en.json and ar.json (parity verified).
  • 0 raw toLocaleDateString() calls: 32 → 0. Every date display flows through formatDate(date, lang) from src/lib/i18n/formatters.ts. Admin tables receive lang as a prop from their server-page caller; client pages pull it from useLocale().
  • RTL parity: ChevronLeft/ChevronRight/ArrowLeft/ArrowRight in ui/carousel.tsx, ui/calendar.tsx, listings/pagination.tsx, [lang]/hosting/calendar/page.tsx, and the manager-application back-button now flip via rtl:rotate-180. New <DirectionAwareIcon> helper in ui/ for future code.
  • Touch targets: HeartButton and the <ReportIssue variant="icon" /> trigger now wrap their svg/lucide icon in a 44×44 hit area (h-11 w-11 inline-flex), per WCAG 2.5.5 AA.
  • Lenient useDictionary(): returns the bundled English JSON as a static fallback when no provider is mounted instead of throwing — fixes test fixtures, isolated demos, and the root error page.
  • Consolidated proxy: deleted the duplicated src/components/internationalization/middleware.ts (its localizationMiddleware export was never imported in Next 16 — proxy.ts is the live entry point).
  • pnpm i18n:check: new audit script grep-counts inline ternaries, raw date formatters, raw number formatters, and hardcoded English JSX with env-overridable budgets. Wired to lefthook pre-commit so the budget can only shrink.
  • Playwright SEO suite extended: asserts <html lang="ar" dir="rtl"> on /ar/*, <html lang="en" dir="ltr"> on /en/*, and that the report-issue widget is mounted on every locale-prefixed route.

Verification

  • pnpm typecheck clean.
  • pnpm i18n:check exits 0:
    • inline lang ternaries: 0 (budget 0)
    • raw toLocaleDateString calls: 0 (budget 0)
    • raw .toLocaleString() calls: 43 (budget 100 — number formatting, mostly currency in stable Sudanese-pound contexts; Phase D will tighten)
    • hardcoded English JSX (informational): 53
  • Cookieless / → 307 → /ar (Arabic-first), Accept-Language: en-US / → 307 → /en (preserves preference).
  • After deploy: smoke 12 routes in both locales, confirm directional icons flip, no clipped CTAs at 375×667.

Out of scope

  • Modular dictionary split (deferred — current monolithic 2,800-line dict is tractable).
  • Number-formatter migration (43 stable toLocaleString() sites; Phase D).
  • Hardcoded-English JSX cleanup (53 sites; Phase D).

Followups

  • Currency formatter audit will fold into Phase D's polish PR (set I18N_BUDGET_RAW_NUMBER=0 once cleaned).

Refs #7

abdout added 7 commits April 26, 2026 14:25
…d-start

The pg adapter holds long-lived TCP connections that Neon drops when its
serverless compute scales to zero, causing the next query to fail with
"Server has closed the connection". The neon serverless adapter speaks
HTTPS+WS and wakes the compute on demand. Detect Neon URLs and switch
automatically when DATABASE_URL_ADAPTER is unset.

Pairs with a backfill of the missing Location.createdAt/updatedAt columns
applied via Neon MCP — schema.prisma listed them but production never
received the ALTER TABLE, so every Listing.findMany() failed once the
nested location include resolved.

Refs #4
…apes, restructure meta

- Replace pathname.startsWith("/ar") heuristic with the existing
  reportIssue.* dictionary slice via useDictionary(). A single locale
  source keeps the widget consistent with every other localized surface.
- Use literal Arabic glyphs in fallback handling instead of \u escape
  sequences (carry-over from the previous edit; Arabic is now sourced
  from the dictionary anyway).
- Pull browser/viewport/direction into a nested `meta` object on the
  server action's input shape so the body fields are always emitted in
  the same canonical order — needed by the kun report-agent parser.
- Self-bootstrap the `report` GitHub label on 422 with color #d93f0b so
  the action works in any repo without manual seeding.

Refs #5
…link

Mounts <ReportIssue variant="icon"/> as a fixed-position floating button
in the locale-root layout so every one of the 117 locale-prefixed routes
exposes the report widget. Previously only two footer components carried
it, leaving auth, hosting, dashboard, transport, and admin surfaces with
no path to file a bug.

Adds the explicit Next 16 viewport export (width/initialScale/themeColor)
that mobile Safari needs to render at the correct DPR and to honour
prefers-color-scheme for the address bar.

Moves the bilingual skip-link literal at layout.tsx:74 to
common.skipToContent in both dictionaries — the only remaining inline
ternary in the locale-root layout.

Refs #5
…consolidated proxy

- Flip `i18n.defaultLocale` from `en` to `ar` per the global rule. The proxy
  already cookie-pins NEXT_LOCALE, so visitors with a prior English preference
  are not redirected; only fresh, headerless visits land on /ar/*.
- Make `useDictionary()` lenient — return the bundled English JSON as a
  static fallback when no DictionaryProvider is mounted instead of throwing,
  so test fixtures, isolated demos, and the root error page render strings.
- Drop the duplicated `src/components/internationalization/middleware.ts`.
  Its `localizationMiddleware` export was never imported (proxy.ts is the
  live entry point in Next 16) and the parallel logic was a footgun.
- Update `[lang]/layout.tsx` so the default-lang and default-config fallbacks
  reference `i18n.defaultLocale` instead of a hardcoded "en", and the
  hreflang `x-default` mirrors the same. Metadata API auto-emits the
  alternate hreflang links.
- Add `src/lib/i18n/date-locale.ts` (`dateLocaleFor` + `intlLocaleFor`) and
  `src/components/ui/direction-aware-icon.tsx` so future code has one place
  to look for "date-fns locale per app locale" and "icon flip on RTL".

Refs #7
…n, transport metadata, and skipToContent

Adds the keys the upcoming refactor needs so per-page generateMetadata,
filter panels, paginators, and the skip-link can pull from the dictionary
instead of inline `lang === 'ar' ? ... : ...` ternaries:

- `common.skipToContent` (replaces the bilingual literal at layout.tsx:74)
- `pageMetadata.<page>` objects with `title` + `description` + (optional)
  `subtitle` for help / host / hosting / hostingListings / transportHost /
  transportOffices / transportCheckout / landing / login / join / reset /
  managersProperties / tenantsFavorites / tenantsResidences
- `rental.property.filters` — `title`, `clearAll`, `any`, `priceRange`
- `rental.property.pagination` — `previous`, `next`, `pageOf`
- `transport.search.metadataTitle` + `metadataDescription` + `pagination`
- `transport.routes.hoursFormat` + `currency`
- `transport.metadataTitle` + `metadataDescription`

Both en.json and ar.json updated in parity.

Refs #7
…String calls

Every visible string and date now flows through the dictionary or through
the locale-aware formatter helpers. Concrete swaps:

- generateMetadata across 17 pages now pulls from `dictionary.pageMetadata.*`
  instead of `lang === 'ar' ? "..." : "..."` ternaries.
- `transport/page.tsx`, `transport/search/page.tsx`, `listings/page.tsx`,
  `transport/booking/checkout/content.tsx`, etc. drop their per-key
  ternary fallbacks now that the dictionary has the keys upstream.
- Date display sites — bookings, transport-host, dashboard, tenants,
  managers, admin tables, ApplicationCard — call `formatDate(date, lang)`
  from `src/lib/i18n/formatters.ts` instead of `new Date(x).toLocaleDateString()`.
  Admin tables receive `lang` as a prop from their server-page caller.
- Locale narrowing in client components (`useParams<{lang}>().lang === 'ar' ? 'ar' : 'en'`)
  is replaced by `useLocale()` which returns the typed `Locale`.
- Data-shape ternaries (category.labelAr/label, tw.rangeAr/En, DEMO_DATA.ar/en)
  are restructured to be locale-keyed objects so call sites do
  `obj[lang]` instead of branching.
- The decorative `data-day` attribute in `ui/calendar.tsx` switches to a
  stable ISO date so the value is locale-independent.

Closes the i18n anti-pattern audit's `inline lang ternaries` and
`raw toLocaleDateString calls` budgets at zero.

Refs #7
…t + lefthook gate

- `ui/carousel.tsx`, `ui/calendar.tsx`, `listings/pagination.tsx`,
  `[lang]/hosting/calendar/page.tsx`, and the manager-applications
  back-button each pick up `rtl:rotate-180` so their chevrons and arrows
  point the right way under RTL. Decorative arrows whose meaning is
  rotation-independent (e.g. the ticket-page absolute-positioned arrow)
  are intentionally left as-is.
- `HeartButton` and the `<ReportIssue variant="icon" />` trigger now wrap
  their svg/lucide icon in a 44x44 hit area (`h-11 w-11 inline-flex`),
  matching iOS HIG and WCAG 2.5.5 (Target Size, AA).
- New `scripts/i18n-anti-pattern-check.sh` greps for inline lang ternaries,
  raw toLocaleDateString, raw toLocaleString, and likely-hardcoded English
  JSX. Each metric has an env-overridable budget; the budget can only
  shrink. Wired as `pnpm i18n:check` and added to lefthook pre-commit.
- `tests/e2e/seo.spec.ts` extended with three new assertions: html
  lang+dir parity per locale (en/ltr, ar/rtl) and presence of the
  report-issue mount on every locale-prefixed surface.

Refs #7
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mkan Ready Ready Preview, Comment Apr 27, 2026 10:13am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant