Bikram Sambat and Bilingual UI: Localization as Engineering

How PDP Shikshya implements Bikram Sambat calendar math and EN/नेपाली bilingual UI — real localization for a School ERP, not a translation afterthought.

Bikram Sambat and Bilingual UI: Localization as Engineering

At pdpspectra we build learning and operations platforms for an international audience, and one lesson repeats in every market: localization is an engineering problem, not a translation pass you bolt on at the end. The moment a product touches dates, scripts, and numerals, “support another language” stops being a string table and becomes calendar arithmetic, font rendering, and a state model for user preference.

This post is the engineering walkthrough of two features inside PDP Shikshya, our EdTech product: the Bikram Sambat (BS) calendar and the EN/नेपाली bilingual UI. Both are grounded in the actual codebase, and both are table stakes for a School ERP serving Nepali schools — a calendar that does not speak Bikram Sambat is not a calendar a Nepali school can plan around.

If you want the wider context first, start with the platform overview, the School Management System post, and the student data privacy deep-dive.

Why Bikram Sambat is not optional#

Nepal runs on Bikram Sambat. Holidays, exam schedules, fee cycles, the academic year itself — they are all set in BS, which currently runs about 56 to 57 years ahead of the Gregorian (AD) calendar. A school administrator does not think in “June 26, 2026”; they think in Asar 2083. If your School ERP can only render AD dates, every event entry becomes a mental conversion, and mental conversion is exactly where errors creep into a school’s term plan.

The hard part is that BS is not a fixed-offset calendar. You cannot add a constant number of days to an AD date and get the BS date. Month lengths vary year to year — a BS month can be 29, 30, 31, or 32 days — and those lengths are determined by astronomical tables, not a formula. That rules out the naive approach and pushes you toward either a maintained lookup table of days-per-month per year, or a vetted conversion library.

A calendar and clock on a desk in morning light

How the conversion actually works#

PDP Shikshya leans on the vetted nepali-date-converter library rather than hand-rolling the astronomical tables, and wraps it in a small, purpose-built helper module (frontend/src/lib/nepaliDate.js) so the rest of the app never touches the library directly. That wrapper is the entire BS surface, and it is deliberately small:

  • adIsoToBs(iso) takes an AD ISO date (YYYY-MM-DD) and returns a BS triplet — year, zero-indexed month, day.
  • bsToAdIso(by, bm, bd) goes the other way, converting a BS date back to an AD ISO string.
  • formatBs(iso, lang) renders an AD date as a BS display string in either script — Jestha 31, 2083 in English or ३१ जेठ २०८३ in Nepali.
  • bsDaysInMonth(by, bm) computes a month’s length by taking the AD delta between the first of that BS month and the first of the next, then dividing by the milliseconds in a day. No hardcoded month-length table to drift out of date.
  • bsMonthGrid(by, bm) lays a BS month out as calendar cells with the correct lead weekday, which is what the month view renders against.

The single most important architectural decision sits one layer down, in the data model. Events are always stored in AD (ISO YYYY-MM-DD) in the database. The CalendarEvent model carries a plain ISO date and an optional end_date. BS is a presentation concern, computed on the fly when the calendar renders, never persisted. That means there is exactly one canonical representation of an event date in storage, sorting and range queries stay trivial, and the BS layer can never disagree with the AD layer because the BS value is derived, not stored. When a staff member enters an event in BS mode, the date picker presents BS month/day/year dropdowns and calls bsToAdIso() to convert to the canonical AD string before it is saved.

Three views, two calendars#

The calendar ships List, Month, and Year views, and each is BS-aware. BS is the default system, but the school admin sets which calendar the school runs on — BS, AD, or BOTH — and that default is persisted in the tenant config. When a school chooses BOTH, the calendar shows the BS date with the AD date underneath, so a school mid-transition between the two systems is never forced to pick one. The List view groups upcoming events by BS month; the Month view renders a 7-by-6 grid with day numbers in the active script; the Year view lays all twelve months out compactly. Month names are curated in both scripts — ["बैशाख", "जेठ", "असार", …] in Nepali, ["Baishakh", "Jestha", "Asar", …] in English — rather than machine-transliterated, because the conventional Romanizations matter to the people reading them.

Bilingual UI: a script problem and a state problem#

The second half of real localization is the interface itself, and PDP Shikshya runs a full EN/नेपाली bilingual UI. The implementation is deliberately library-free: no i18next, no heavy framework, just a pair of nested key-value dictionaries and two small helpers.

On the frontend, frontend/src/i18n.js exports a STRINGS object with en and ne maps, and a useT(lang) hook that returns a lookup function with a sensible fallback chain — return the key in the active language, else fall back to English, else return the key itself so a missing string degrades to something legible rather than a blank. The backend mirrors this in backend/app/i18n.py with its own STRINGS dict and a t(key, lang) function, because some user-facing strings are generated server-side — the AI day-planner headers, the Socratic tutor’s out-of-scope deflection, escalation prompts. Those need to come back already localized, so the language has to be a first-class parameter on the request, not a thing the client patches up afterward.

The language toggle is the visible EN / ने control, and the preference behind it is persisted in two layers. It writes immediately to localStorage under pdp_lang so an unauthenticated visitor’s choice survives a reload, and for a signed-in user it lives on the profile as preferred_language, so the language follows them across devices the same way their tutor chat history does. On init the app resolves preference in priority order: the authenticated user’s stored preference first, then localStorage, then a default of English. It also sets document.documentElement.lang to ne or en so the <html lang> attribute is correct — which matters for screen readers and for the browser’s own behavior.

Two phones showing calendar interfaces side by side

Devanagari numerals, not just Devanagari words#

Translating labels is the obvious half. The half teams routinely miss is numerals. In a properly localized Nepali UI, 2083 should read २०८३, and a screen-time counter of 45 minutes should read ४५. PDP Shikshya handles this with a tiny neNum(n) function that maps each Arabic digit to its Devanagari counterpart through a ten-element lookup (["०", "१", "२", …]). It is applied at the display edge only — calendar day numbers, BS years in the date picker, a student’s points and streak counts, Digital Sunset screen-time minutes — while all data stays in Arabic numerals internally. The rule is the same one that governs the whole localization story: convert at the boundary, keep one canonical representation underneath.

A note on rendering: Devanagari prose like एक राम्रो शिक्षा प्रणाली (a good education system) renders through the system font stack, with the sans-serif fallback resolving to the platform’s bundled Devanagari face — Noto Sans Devanagari on modern devices. The UI is also built to hold up on the mid-range Android phones that dominate the market, with legacy bundles and CSS color fallbacks, because a bilingual UI that only renders on a flagship phone is not actually serving the school.

Why this is the right altitude for a School ERP#

It would be easy to treat BS support as a checkbox and bilingual UI as a spreadsheet of strings handed to a translator. Both shortcuts break in production. A fixed-offset BS calendar produces dates that are wrong by a day in the months where the table disagrees with the formula — and a school will notice the instant an exam lands on the wrong date. A string table that ignores numerals leaves २०८३-expecting users reading 2083 next to fully translated prose, which reads as half-finished. And localization that lives only on the client cannot localize the strings the AI generates server-side.

The thread through all of it is the same engineering discipline we bring to every build: one canonical representation in storage (AD dates, Arabic numerals, stable string keys), and localization computed at the presentation boundary. That keeps the data layer simple, makes the AI implementation behind the day-planner and tutor language-aware without special-casing, and means adding a third calendar view or a third language is a contained change rather than a rewrite. It is the same multi-tenant platform that runs the school’s attendance, routine, homework, and analytics — Operational Automation where localization is part of the foundation, not a coat of paint.

That is what real localization costs, and why it is worth doing properly. A Nepali school should be able to plan its year in Bikram Sambat, read its dashboard in नेपाली, and never once think about the date math or the script rendering underneath — because the engineering already did.


Building a product that has to speak more than one calendar and more than one script? See the platform at pdpshikshya.com, or talk to us about your build at pdpspectra.com. AI-powered. Data-driven. Built to ship.