Project file
A Kapi project is a portable .kapi YAML recipe at a project's root that captures a localization workflow: source and target languages, content file patterns, tool pipelines (flows), plugin requirements, and processing defaults. It carries no credentials or state, so it is always safe to commit. For when to use a project versus an ad-hoc run, see Modes & bindings.
Discovery
Like git, kapi walks up the directory tree from where you run it and uses the nearest .kapi recipe — running in project mode with that project's languages, defaults, and flows. An explicit -p <path> overrides discovery, and KAPI_NO_PROJECT=1 disables it so the CLI runs ad-hoc.
Format
version: v1
name: My App Localization
content:
- path: "src/locales/en/*.json"
target: "src/locales/{lang}" # mirror each matched file under the per-language dir
preset: nextjs
plugins:
okapi: "^1.47.0"
flows:
translate:
steps:
- tool: ai-translate
config:
provider: anthropic
model: <provider-model-id>
translate-and-qa:
steps:
- tool: ai-translate
config:
provider: anthropic
- tool: qa-check
pseudo:
steps:
- tool: pseudo-translate
config:
expansionPercent: 30
defaults:
source_language: en-US
target_languages:
- fr-FR
- de-DE
- ja-JP
concurrency: 4
parallel_blocks: 3
encoding: utf-8
exclude:
- "**/*.generated.json"
merge:
conflict_policy: translator-wins
tm:
fuzzy_threshold: 75
segmentation:
source: true
termbase: glossary/terms.db
Fields
Required
| Field | Type | Description |
|---|---|---|
version | string | Schema version, must be "v1" |
name | string | Project display name |
Optional
| Field | Type | Description |
|---|---|---|
content | ContentEntry[] | File patterns to process (see ContentEntry) |
preset | string | A framework preset name (e.g., nextjs, react-intl) applied to formats |
plugins | map | Plugin requirements keyed by name (e.g., okapi: "^1.47.0") |
requires | map | Plugin name → semver constraint that gates loading; the recipe refuses to load without a registered plugin |
flows | map | Named flow definitions (see Flow Steps) |
defaults | Defaults | Processing defaults (see Defaults; source/target languages live here) |
ContentEntry
| Field | Type | Description |
|---|---|---|
path | string | Glob pattern for source files (required) |
base | string | Directory a matched file's path is made relative to; defaults to the glob's fixed prefix |
format | string | Format ID; auto-detected per file if omitted |
target | string | Output path template (see Source and target paths) |
In a named collection, base may be set once on the collection and is inherited
by every item that does not set its own.
Source and target paths
path is a doublestar glob: **
matches across directories (e.g. src/**/*.json) and {a,b,c} matches any of
the listed alternatives (e.g. input/store/*.{json,yaml,html}). One glob can
therefore cover a whole directory of mixed content; the format is auto-detected
per file unless you pin format. Patterns resolve relative to the project root.
base is the directory a matched file's path is made relative to. It defaults to
the glob's fixed prefix — the literal part of path before the first wildcard
(input/docs/ for input/docs/**/*.md). The relative path drives the {relpath}
/ {path} / {dir} tokens and how much of the source tree a directory-mirror
target reproduces.
target is the output-path template, expanded per source file and target
language. The supported tokens are:
| Token | Meaning | Example (base input/, source input/docs/api.md) |
|---|---|---|
{lang} | Target language | fr-FR |
{relpath} | Source path relative to base, with extension | docs/api.md |
{path} | Source path relative to base, without extension | docs/api |
{dir} | Directory portion of {relpath} (empty at the root) | docs |
{filename} | Filename with extension | api.md |
{name} | Filename without extension (alias {basename}) | api |
{ext} | Extension without the dot | md |
A bare * is legacy shorthand for {name}.
Directory-mirror (the easy default). When target ends with /, or its last
segment has no extension and no wildcard/token, it names a directory and the
source's {relpath} (under base) is mirrored beneath it. So target: output/{lang} reproduces the source tree under each per-language root —
input/docs/api.md → output/fr-FR/docs/api.md — with no * and no risk of a
doubled extension:
content:
- name: Docs
base: input
items:
- path: "input/**/*.md"
target: "output/{lang}"
Token template (the powerful form). When you need to reshape the layout, use tokens explicitly:
content:
- path: "src/locales/en/*.json"
target: "src/locales/{lang}/{name}.{ext}" # api.json → src/locales/fr-FR/api.json
Defaults
defaults holds project-wide processing settings that individual content items can override. Several of these are governed by AD-017.
| Field | Type | Description |
|---|---|---|
source_language | string | BCP-47 source locale (e.g., en-US) |
target_languages | string[] | BCP-47 target locales |
locale_format | string | Locale style for the {lang} placeholder: bcp-47 (default) or posix |
concurrency | int | Number of files to process in parallel |
parallel_blocks | int | Number of blocks to process in parallel within a file |
encoding | string | Input file encoding (default: utf-8) |
exclude | string[] | Glob patterns skipped during content scanning (e.g. **/*.generated.json) |
formats | map | Per-format preset, config, and detection priority overrides (see formats) |
merge | MergeDefaults | How kapi merge resolves a translator's target against an existing target or TM entry (see merge) |
tm | TMDefaults | TM pre-fill on kapi extract and write-back on kapi merge (see tm) |
segmentation | SegmentationDefaults | Opt-in sentence-level segmentation overlay applied on extract (see segmentation) |
redaction | RedactionSpec | Replace sensitive content with protected placeholders before processing and restore it afterwards (see redaction) |
brand_voice | BrandVoiceBinding | Bind a brand voice profile as standing project context (see brand_voice) |
termbase | string | Path to a glossary/termbase, resolved relative to the project root; used for project-scoped term enforcement with no --termbase flag |
Individual content items can override redaction per entry (ContentItem.redaction).
formats
defaults.formats configures formats by name, so a single wildcard content item
(path: "input/*", no format:) can let each file's engine be auto-detected
while you still tune individual formats in one place. Each entry takes:
| Field | Type | Description |
|---|---|---|
preset | string | A named format preset applied to this format |
config | map | Reader option overrides for this format (merged under, and overridable by, a content item's format.config) |
priority | int | Detection priority — higher wins when several formats claim the same extension |
priority is the engine-pin escape hatch. Most extensions map to one format, but
some are claimed by several: .srt by both okf_vtt and okf_regex, .txt by
several plain-text engines. Bumping the preferred one steers auto-detection
without naming a format on every item:
plugins:
okapi-bridge: "*"
defaults:
formats:
okf_vtt:
priority: 110 # .srt → WebVTT engine (beats okf_regex)
okf_json:
config:
useFullKeyPath: true
content:
- name: Source Files
base: input
items:
- path: "input/*" # every file; engine auto-detected, two pinned above
target: "output/{lang}"
Plugin formats already outrank built-in ones by default (priority 100 vs 50), so
declaring okapi-bridge is enough for input/* to resolve to the okf_*
engines; priority only matters to break ties between formats from the same
source.
merge
| Field | Type | Description |
|---|---|---|
conflict_policy | string | translator-wins (default), existing-wins, or newest-wins |
tm
| Field | Type | Description |
|---|---|---|
fuzzy_threshold | int | Minimum fuzzy match score (0–100) to pre-fill the target on extract (default: 75) |
read | string[] | Additional read-only TM files consulted during pre-fill; writes always go to the project TM |
segmentation
| Field | Type | Description |
|---|---|---|
source | bool | Toggle sentence-level segmentation of source text on extract |
srx | string | Optional SRX rules file; built-in default rules are used when empty |
redaction
The sensitive term list lives in a separate rules file referenced by rules (so it can be gitignored); this spec points at it and selects detection backends.
| Field | Type | Description |
|---|---|---|
enabled | bool | Turn redaction on for extract/merge |
rules | string | Path to a redaction rules YAML file |
detectors | string[] | Detection backends: rules and/or entities |
placeholder | string | Override the visible stand-in template, e.g. [REDACTED:{category}] |
brand_voice
Bind a brand voice profile under defaults.brand_voice. Exactly one source is expected.
| Field | Type | Description |
|---|---|---|
profile_file | string | Path to a standalone profile YAML, resolved relative to the project root |
profile | string | Name of a profile in the local brand store |
pack | string | Name of a built-in starter pack |
Flow Steps
Each flow contains an ordered list of steps. See Flow Steps Format for the full specification.
flows:
my-flow:
steps:
- tool: ai-translate # tool name (required)
config: # tool-specific config (optional)
provider: anthropic
label: "Translate" # display label (optional)
Steps can also define parallel branches:
flows:
parallel-qa:
steps:
- parallel:
- tool: qa-check
- tool: ai-qa
Key Properties
- No credentials — API keys are never stored in Kapi project files. They come from the OS keychain (Kapi Desktop) or environment variables (CLI).
- No state — No sync cursors, caches, or timestamps. Kapi project files are always clean and safe to commit.
- Portable — Save anywhere, have multiple per directory, share via git.
- CLI-compatible —
kapi run flowname -p file.kapi
Using with Kapi CLI
# Run a flow from a Kapi project
kapi run translate -p translation.kapi
# Override defaults with CLI flags
kapi run translate -p translation.kapi --target-lang de --provider openai
# One-shot mode still works without a project
kapi ai-translate -i file.json --target-lang fr
Using with Kapi Desktop
- File > Open to load a Kapi project
- File > New to create one from scratch
- File > Save / Save As for standard document operations
- Double-click a Kapi project to open it in Kapi Desktop (macOS)
- Drag and drop Kapi projects onto the app window
See also
- AD-008: Project model — the architectural decision behind the
.kapirecipe and its extension mechanism. - .kapi Project File Format — the implementation note: the
KapiProject,Defaults, andContentCollectionstruct layouts, the named-collection content shape, validation rules, and how platform extensions decode unknown keys. - Flow Steps Format — the full step specification used by
flows.