All projects

Kalaniot Center Veterinary Clinic - Multilingual Website + CMS

A legacy Classic ASP clinic site rebuilt as a Nuxt 4 site in 5 languages, with an admin panel that edits every text and image.

The goal

The clinic's site was an old Classic ASP application on hosting the clinic could not control or back up. The goal was a modern, responsive redesign that keeps everything the old site did - the full menu, the content, the contact form that emails the clinic and the existing search rankings - while letting the staff edit texts and images themselves, in Hebrew and in other languages, with proper SEO, GEO and AEO.

The solution

A pnpm monorepo with three apps and a shared package: a Nuxt 4 public site (SSR, @nuxtjs/i18n, Tailwind 4), an Express 5 + MySQL content API, and a Vue 3 admin panel served under /admin. Content is stored per locale with Hebrew as the source of truth and fallback, so every page, service, post and FAQ can be translated independently. The legacy site was extracted by script, imported through the admin API with a backup journal, and its URLs mapped for 301 redirects. Deployment runs from GitHub Actions to shared cPanel hosting through cPanel's HTTPS API, followed by an automated mobile and desktop QA pass on every page.

Highlights

  • Five languages with RTL and LTR, content translated per page
  • The clinic staff edits every text and image from a 20-screen admin panel
  • Legacy Classic ASP content migrated by script, with backups and a URL map for 301s
  • Automated QA on every page at phone and desktop width after each deploy

The challenge

The first scaffold had translated buttons but no locale dimension in the content, so /en and /ru would have served the same Hebrew text - duplicate content to a search engine - and several admin fields saved to keys the site never read.

Wrote a design spec first, fixed the broken field bindings, defined a canonical schema per section, then added per-locale content with a Hebrew fallback, one slug across languages and an hreflang cluster. Contract tests on the admin routes and an i18n parity test keep the two sides aligned.

Shared hosting with no FTP or SFTP open to the world, and GitHub runners have no fixed IP to whitelist.

The CI build pushes its output to a deploy-bundle branch and the server pulls it through a cron job triggered over cPanel's HTTPS API, with an incremental sync and safeguards so a failed deploy cannot take the site down.

The old site's content and its 23 indexed URLs carried the clinic's search rankings, on hosting that could not be backed up.

Archived the raw legacy pages and URL map, extracted the content by script, imported it through the admin API with snapshot and journal files, and added a parity check against the legacy site plus a redirects manager for the 301 mapping.

Mixed RTL and LTR content (phone numbers, emails, five languages) breaks layout in ways a build never catches.

A Playwright QA script runs after every deploy on every page at phone and desktop widths, failing on sideways scroll, unlinked phones or emails, Hebrew blocks laid out left to right, a wrong h1 count or broken images.

What was delivered

  • Nuxt 4 public site (SSR, 5 locales, section-based pages)
  • Express 5 + MySQL content API with JWT auth, uploads and encrypted SMTP settings
  • Vue 3 admin panel under /admin
  • Legacy content extraction and import tooling
  • GitHub Actions deploy to cPanel and post-deploy site QA
  • Design specs and implementation plans

Results

5

Languages

20

Admin screens

23

Legacy URLs mapped

Self-serve

Content editing

What it taught me

  • Translated buttons are not a multilingual site - the content itself needs a locale dimension, or the extra URLs hurt SEO
  • Fix field mappings before adding languages, otherwise every bug is multiplied by the number of locales
  • On locked-down shared hosting, the HTTPS control-panel API can be the only reliable deploy channel