Published 2026-02-20
How I Built an English Website With an LMS Platform for Video Courses Based on a Czech WordPress Site
The site is built in Next.js and includes a public part and an LMS platform with courses in the members' section.
For the new English website it wasn’t enough to just translate the texts from Czech. The goal wasn’t a copy of the original site, but a standalone platform: a public website, a blog, a course catalog, sign-in, payments, a members’ section, a lesson player, and, on top of that, the preparation of English videos.
In this article I focus mainly on the technical implementation.
The application foundation
The site is built on Next.js 15 with the App Router and React 19. I split the structure into two main parts.
The public part contains the landing page, blog, courses, contact, and legal pages. The members’ section holds purchased courses, lesson playback, and account settings.
Sign-in runs through Clerk. Middleware guards all pages under /dashboard/*. When an unauthenticated user reaches them, it sends them to sign-in and remembers the original address so that after signing in they return to where they were headed.
Courses, payments, and access
The data is in PostgreSQL via Drizzle ORM. The schema covers courses, modules, lessons, reviews, customers, purchases, and lesson-watching progress. Supabase serves as the database environment.
I connected payments through Stripe Checkout. At purchase, information about the course and the customer is stored with the Stripe session. After payment, a webhook arrives and creates access to the course.
Here a small but important problem arises: the user can return from Stripe to the site before the webhook finishes. The player page therefore briefly retries checking whether access already exists, and only then lets the user into the course.
Video player
The video part is built on the YouTube IFrame API and youtube-nocookie.com. But it wasn’t just about embedding an iframe. The custom player handles:
- controls,
- resuming from the last position,
- playback speed,
- fullscreen,
- keyboard shortcuts,
- online/offline state,
- saving progress roughly every 10 seconds.
Once you want to track progress on videos, switch lessons, and serve content only to signed-in students, a plain embed code quickly turns into an important part of the application.
A blog without an external editorial system
The blog is built on files. Articles are markdown files with metadata in the header. The server loads them, extracts the title, date, and other fields, renders the content via react-markdown, and builds search with Fuse.js.
This solution is simple and, for this type of site, practical. There’s no need for an external CMS, the content is under version control, and it deploys together with the application.
English as the primary language, not a toggle
This wasn’t a classic multilingual solution with a toggle. The English version is the application’s primary language: html lang="en", Clerk in English, Stripe in English, and English text in the interface.
For the blog it was mainly a content migration. The Czech article is saved as a backup, the English version goes in its place, and the titles, slugs, categories, tags, and internal links are translated. Then the search index is rebuilt.
The hardest part wasn’t the website, but the videos
The most labor-intensive part was preparing the English videos. Dubbing isn’t just translating the text. I had to handle terminology, timing, voice quality, and continuity with the movement in the video.
The English site, in the end, isn’t just a translated page. It’s a product of its own, with content, payments, a course, a player, and operational details that have to hold together.