English | 日本語
Thanks for your interest in improving Kiritan! This guide gets you set up and explains how the project is put together. If anything here is unclear, opening an issue to ask is welcome.
You'll need Node.js >= 22.7. Then install the dependencies:
npm installCommit the lockfile generated by your package manager (npm).
This is an npm workspaces monorepo (see docs/DESIGN.md chapter 2.1):
| Package | Path | What it is |
|---|---|---|
kiritan |
packages/kiritan |
The CLI + build pipeline (config, stores, renderers, translate middlewares) |
@kiritan/runtime |
packages/runtime |
The minimal t(key, params) runtime, with no build-time dependencies |
@kiritan/deepl |
packages/deepl |
A DeepL translate middleware for translate.middlewares |
@kiritan/google-translate |
packages/google-translate |
A Google Cloud Translation middleware for translate.middlewares |
@kiritan/middleware |
packages/middleware |
A layer that turns any translate function into a translate middleware |
@kiritan/free-translate |
packages/free-translate |
Translate middlewares that need no API key, including self-hosted ones (LibreTranslate, Google Apps Script) |
otoneko1102.kiritan |
extensions/vscode |
VS Code extension (syntax highlighting for :::kiritan{...} blocks). Not an npm workspace member — see "Releasing" below. |
| — | extensions/vim |
Vim/Neovim plugin (the same directive highlighting, plus *.kiritanconfig filetype detection). Not published anywhere — installed directly from this repo via a plugin manager's rtp option. |
The design and rationale for everything above live in docs/DESIGN.md — read it before making a structural change.
The root README/CONTRIBUTING/DESIGN docs, and each package's README, are generated by Kiritan itself from a base/*.base.md source (e.g. base/README.base.md, packages/kiritan/base/README.base.md) — see docs/DESIGN.md chapter 4.2 for the :::kiritan{locale=...} block syntax used inside them. When you edit a base/*.base.md file, regenerate the outputs and commit them along with your change:
npm run docs:buildnpm run docs:check reports any source that's missing a translation or has gone stale (i.e. the source changed after the last generation) without writing anything — useful to run before committing. Generation also happens automatically as part of the release workflow (below), so a base/*.base.md change that ships alongside a version bump doesn't strictly need a manual regeneration first — but running it yourself keeps your PR's diff accurate and reviewable.
| Command | What it does |
|---|---|
npm run build |
Bundle ESM/CJS + type declarations with tsdown |
npm run test |
Run the tests once with vitest (npm run test:watch to keep them running) |
npm run test:coverage |
Run the tests with a coverage report |
npm run typecheck |
Type-check with tsc --noEmit |
npm run format |
Format the code, writing the fixes (Prettier) |
npm run format:check |
Check formatting without writing (Prettier) |
npm run lint |
Lint the code without writing fixes (ESLint) |
npm run lint:fix |
Lint the code, writing the fixes (ESLint) |
npm run check |
Format and lint, writing the fixes (Prettier + ESLint) |
npm run ci |
The same checks without writing — what CI runs |
npm run docs:build |
Regenerate every base/*.base.md-derived doc (see Generated docs above) |
npm run docs:check |
Report missing/stale generated docs without writing anything |
npm run docs:verify |
Check that every generated doc matches what docs:build would write (by hash), without writing anything |
build and typecheck fan out to every package under packages/*; test/format/lint already run across the whole workspace from the root.
Before opening a pull request, make sure the full set passes:
npm run ci && npm run typecheck && npm run test && npm run build- Formatting is Prettier (default settings, no overrides) and linting is ESLint
(
eslint.config.js—typescript-eslint's recommended rules, witheslint-config-prettierdisabling any ESLint rule that would conflict with Prettier's formatting). Runningnpm run checkbefore you commit handles both. - Tests live next to the code as
*.test.tsand run with vitest. - Comments and docs are in English and kept brief.
- Type-only imports use
import type(verbatimModuleSyntaxis on). - Prefer a maintained library to hand-written logic, and keep the dependency ESM/CJS-dual. Anything a consumer loads through the package's API (
kiritan's API,@kiritan/runtime,@kiritan/middlewareand the provider packages) must be a library whose latest release ships both ESM and CJS, sorequire()andimportboth work without bundling tricks; check the latest version'sexportsbefore adding one. The CLI is the exception, and may use ESM-only libraries such asyargs. The one standing exception in the API is theunified/remark-*stack inkiritan: it is ESM-only with no dual-format equivalent that can also stringify Markdown, so it is bundled intodist/(seetsdown.config.ts) and CJS consumers are unaffected. It stays until such an equivalent exists.
Keep each change focused and add tests for any new behaviour. There's no changeset or similar file to add — versioning happens entirely at release time (see below), not per PR.
Every npm package (kiritan, @kiritan/runtime, @kiritan/deepl, @kiritan/google-translate, @kiritan/middleware, @kiritan/free-translate) has its own workflow_dispatch GitHub Actions workflow — release.yml, release-runtime.yml, release-deepl.yml, release-google-translate.yml, release-middleware.yml, release-free-translate.yml — so releasing one package can never accidentally touch another. Each is a thin wrapper around a shared reusable workflow (_release-package.yml, not runnable on its own) that does the actual work.
From the Actions tab, run the package's workflow (e.g. release-runtime) with two inputs:
version: a semver bump (patch/minor/major/prerelease) or an explicit version (e.g.0.2.0), passed straight tonpm version.dist_tag: the npm dist-tag to publish under. Leave empty to auto-detect — the prerelease identifier (e.g.betafrom0.2.0-beta.0), orlatestfor a stable version.
The workflow bumps that package's package.json (no changelog file — GitHub's auto-generated release notes, from merged PRs, are used instead), regenerates every base/*.base.md-derived doc (kiritan build, folded into the same commit), publishes to npm with provenance via trusted publishing (OIDC — no NPM_TOKEN needed), pushes the version commit and a <package>@<version> tag, and creates a GitHub Release.
Trusted publishing must be configured once per package on npmjs.com:
package Settings → Publishing access → Trusted publishers → GitHub, pointing at that package's own workflow file (e.g. release.yml for kiritan, release-deepl.yml for @kiritan/deepl).
Since kiritan depends on @kiritan/runtime (currently ^0.1.0), bumping runtime's minor or major version doesn't automatically update Kiritan's dependency range — that's a manual follow-up PR when it happens, on purpose, so releasing runtime alone never touches Kiritan's package.json.
The VS Code extension (extensions/vscode) has its own release-vscode.yml instead, since it doesn't publish to npm — it takes only version, packages a .vsix via vsce, tags the release as kiritan-vscode@<version> and creates a GitHub Release with the .vsix attached, and additionally publishes to the VS Code Marketplace only if a VSCE_PAT repository secret is configured (Marketplace publishing isn't set up yet, so today this just produces a downloadable .vsix).
extensions/vscode lives outside packages/ and so is never picked up by the root "workspaces": ["packages/*"] glob — its package.json "name" is "kiritan", which would otherwise collide with the CLI package of the same name (an npm workspace can't have two packages sharing a name). Its build/test tooling (@vscode/vsce, vscode-textmate, vscode-oniguruma) lives in the root package.json's devDependencies instead, resolved the normal Node.js way when tsc/vitest/vsce run against it — no separate npm install step is needed for it.
The Vim/Neovim plugin (extensions/vim) has nothing to build or publish — users install it directly from this repo via a plugin manager's rtp option, so release-vim.yml only runs the test suite and then tags the commit as kiritan-vim@<version> and creates a GitHub Release (no attached asset), for anyone who wants to pin to a specific version instead of tracking main. There's no package.json here to hold a current version to bump from, so it finds the latest existing kiritan-vim@* tag and reuses npm version's own semver logic against a throwaway package.json — the same patch/minor/major/prerelease keyword or explicit-version version input as release.yml.
By contributing, you agree that your contributions are licensed under the WTFPL License.