DEV Community

Cover image for I open-sourced a bilingual Bazi (八字 / Four Pillars) terminology DB + React chart component
Favori
Favori

Posted on

I open-sourced a bilingual Bazi (八字 / Four Pillars) terminology DB + React chart component

If you've ever tried to build software around Bazi (八字, also called Four Pillars of Destiny), you already know the pain. The terminology is scattered across a hundred sources, most of it Chinese-only. And every chart UI you can find is welded to one specific calculation engine — want to render a chart from a different engine, or show English labels, and you're rewriting the whole view layer.

So I built bazi-kit, an MIT-licensed monorepo that fixes the two layers underneath the calculation:

  • bazi-terms — a zero-dependency, bilingual (中文 / English) terminology database.
  • bazi-chart — a React component that renders a complete chart from whatever data you feed it.

The one design rule that ties them together: bazi-kit never calculates anything. It doesn't compute pillars, luck cycles, or element scores. It takes chart data you already have and turns it into clean, bilingual, structured output or UI. Bring your own engine.

Why split data & presentation away from calculation?

Almost every Bazi library couples three very different jobs into one blob: (1) the astronomical/calendar math, (2) the domain vocabulary, and (3) the rendering. That coupling is why the ecosystem feels stuck — you can't swap an engine without rewriting the UI, and you can't reuse the terminology without dragging in someone's calculation code.

bazi-kit deliberately open-sources only (2) and (3). The vocabulary and the presentation are generic infrastructure — every Bazi app needs them, and none of them are a secret. The interpretation logic (the part that actually reads a chart) stays in the product. That boundary is what makes the library safe to reuse: it's a presentation layer, not an oracle.

bazi-terms — a bilingual terminology database, zero dependencies

Every concept a chart uses is structured, translated data you can drop into an app, a blog glossary, or an i18n layer.

Category Export Size
Ten Heavenly Stems 天干 STEMS 10
Twelve Earthly Branches 地支 BRANCHES 12
Five Elements 五行 (+ cycles) ELEMENTS, ELEMENT_CYCLES 5
Ten Gods 十神 (+ Day Master) TEN_GODS 11
Twelve Growth Stages 十二长生 GROWTH_STAGES 12
Shen Sha 神煞 SHEN_SHA 38
Sixty Jiazi Na Yin 六十甲子纳音 NA_YIN 30 melodies / 60 pairs
Interactions 刑冲合会 INTERACTION_TYPES 8
General vocabulary 通用术语 GENERAL_TERMS 40+

Each term carries key, zh, en, an optional pinyin, and an English definition you can paste straight into a tooltip.

The workhorse is translate(), which never throws — unknown input is returned unchanged, so it's safe to pipe raw engine output straight through it:

import { translate, t, naYinOf, interactionFromEnum } from 'bazi-terms';

t('dayMaster');              // 'Day Master'   — translate a term KEY
translate('元男');            // 'Day Master'   — aliases resolve (元男/元女/日元)
translate('甲子');            // 'Sea Gold'     — Na Yin by sexagenary pair
translate('metal', 'zh');    // '金'
translate('something-else'); // 'something-else' — unknown passes through, no throw

naYinOf('甲子');              // { key: 'seaGold', en: 'Sea Gold', pairs: ['甲子','乙丑'] }
interactionFromEnum('CLASH');// { key: 'liuChong', en: 'Clash', ... }
Enter fullscreen mode Exit fullscreen mode

That "never throws" property matters more than it sounds. Most Bazi engines emit Chinese values for ten gods, Na Yin, growth stages and Shen Sha — even when their field names are English. Where engines differ (some use 元男 for the Day Master, some emit interaction enums like CLASH), bazi-terms normalizes them through aliases and dedicated resolvers, so one zh → key mapping serves them all.

bazi-chart — render any chart, in Chinese, English, or both

Feed <BaziChart /> your chart data. It auto-detects the input shape, normalizes it, and renders — no calculation, no configuration ceremony.

import { BaziChart } from 'bazi-chart';

// `data` = raw output from your engine; the shape is auto-detected.
export default function Reading({ engineOutput }) {
  return <BaziChart data={engineOutput} lang="both" theme="light" />;
}
Enter fullscreen mode Exit fullscreen mode
import { BaziChart, type NormalizedChart } from 'bazi-chart';

// …or a chart object you built / normalized yourself.
export default ({ chart }: { chart: NormalizedChart }) => (
  <BaziChart chart={chart} lang="en" theme="dark" />
);
Enter fullscreen mode Exit fullscreen mode

It renders the full picture: the Four Pillars (main star, stem/branch glyphs colour-coded by element, hidden stems and their ten gods, Na Yin, growth stage, void branches, Shen Sha), Five-Element score bars, the interactions panel (刑冲合会), the Luck Pillar (大运) timeline, and auxiliary palaces (命宫/身宫/胎元/胎息) when the source provides them. Sections a source doesn't emit are simply omitted.

Styling is inline-only — no CSS import, no build step, safe for SSR and any meta-framework (Next.js, Remix, Astro). Element colours follow the traditional Five-Element palette (wood=green, fire=red, earth=brown, metal=gold, water=blue), tuned for contrast in both light and dark themes.

The NormalizedChart contract

The reason bazi-chart can stay engine-agnostic is a single intermediate shape — NormalizedChart (meta, solarTime, dayMaster, pillars, fiveElements, daYun, interactions, extras). Adapters only reshape raw output and keep the original Chinese values; everything derivable (element, polarity, colour, translation) is resolved at render time via bazi-terms.

Two common community shapes are auto-detected — a top-level 八字 object, and a pillars map keyed by year/month/day/hour — and you can drive the adapters yourself when you want the normalized data without the component (for a custom UI, a table export, or an API response):

import { normalize, detectEngine } from 'bazi-chart';

detectEngine(engineOutput);      // 'baziObject' | 'pillarMap' | 'normalized' | 'unknown'
const chart = normalize(engineOutput); // -> NormalizedChart
// chart.pillars, chart.dayMaster, chart.daYun, chart.interactions, ...
Enter fullscreen mode Exit fullscreen mode

normalize() throws a clear error for unrecognized input instead of failing silently.

Install

npm install bazi-terms   # zero dependencies
npm install bazi-chart   # React component (react >= 17 peer; bazi-terms installed automatically)
Enter fullscreen mode Exit fullscreen mode

Both ship ESM + CJS builds with full TypeScript declarations.

What's next

bazi-kit is the open-source presentation layer underneath AskingMing, an AI Bazi-reading product I'm building — but the terminology and the chart component are meant to be useful to anyone building in this space, independent of that.

It's early (v0.1.0), so feedback is genuinely welcome:

  • Missing terminology, a wrong translation, or a Na Yin/Shen Sha edge case? File an issue.
  • Using a chart shape that isn't auto-detected? The adapter layer is small and PR-able.
  • Want a non-React renderer (Vue, Svelte, or plain HTML)? The NormalizedChart contract is designed to make that a separate, thin package.

GitHub: https://github.com/favkit/bazi-kit · npm: bazi-terms / bazi-chart

Top comments (0)