kapi.ýàḿļ Þŕöĵéçţ Ƒîļé Ƒöŕḿàţ
Îḿþļéḿéñţàţîöñ ñöţéš ƒöŕ ţĥé kapi.yaml þŕöĵéçţ ƒîļé ƒöŕḿàţ. Šéé Ç-01 ƒöŕ ţĥé àŕçĥîţéçţüŕàļ đéçîšîöñ àñđ ţĥé þŕöĵéçţ ƒîļé ŕéƒéŕéñçé ƒöŕ éṽéŕý ķéý ŵîţĥ îţš ţýþé.
Šçĥéḿà
Ţĥé kapi.yaml ŕéçîþé îš à ÝÀḾĻ đöçüḿéñţ þàŕšéđ ƃý core/project.KapiProject:
type KapiProject struct {
Version string `yaml:"version"`
ID string `yaml:"id,omitempty"` // the stable identity; see Project identity
Name string `yaml:"name,omitempty"` // the label; free to change
Plugins map[string]PluginSpec `yaml:"plugins,omitempty"` // name → spec (scalar = version short form)
Defaults Defaults `yaml:"defaults,omitempty"` // project-wide defaults (locales live here)
Collections []Collection `yaml:"collections,omitempty"`
Preset string `yaml:"preset,omitempty"`
Flows map[string]*flow.StepsSpec `yaml:"flows,omitempty"`
Profiles map[string]Profile `yaml:"profiles,omitempty"` // profile name → governance (see The context space)
// Convergence gates (see the reference page, "Ship gates").
ShipGate gate.Gate `yaml:"ship_gate,omitempty"`
ShipGates []ShipGateRule `yaml:"ship_gates,omitempty"`
Gates map[string]gate.Gate `yaml:"gates,omitempty"`
VerifiedGate gate.Gate `yaml:"verified_gate,omitempty"`
VerifiedGates []ShipGateRule `yaml:"verified_gates,omitempty"`
SourceGate gate.Gate `yaml:"source_gate,omitempty"`
Requires RequiresMap `yaml:"requires,omitempty"` // plugin name → semver constraint
Extras map[string]yaml.Node `yaml:",inline"` // unknown keys (extensions)
}
// Defaults holds project-wide processing defaults, including locales.
type Defaults struct {
SourceLanguage model.LocaleID `yaml:"source_language,omitempty"`
TargetLanguages []model.LocaleID `yaml:"target_languages,omitempty"`
Flow string `yaml:"flow,omitempty"` // the flow `kapi up` runs
Materialize string `yaml:"materialize,omitempty"` // "manual" (default) or "on-converge"
Jobs int `yaml:"jobs,omitempty"` // target languages converged concurrently
SourceGate string `yaml:"source_gate,omitempty"` // authored | checked (default) | approved | none
LocaleFormat string `yaml:"locale_format,omitempty"`
Concurrency int `yaml:"concurrency,omitempty"`
ParallelBlocks int `yaml:"parallel_blocks,omitempty"`
Encoding string `yaml:"encoding,omitempty"`
Formats map[string]FormatDefaults `yaml:"formats,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
Merge MergeDefaults `yaml:"merge,omitempty"`
Memory MemoryDefaults `yaml:"memory,omitempty"`
Segmentation SegmentationDefaults `yaml:"segmentation,omitempty"`
Annotations AnnotationDefaults `yaml:"annotations,omitempty"`
Redaction *RedactionSpec `yaml:"redaction,omitempty"`
Comments CommentDefaults `yaml:"comments,omitempty"` // directives and channel for every declared comment
Voice *VoiceBinding `yaml:"voice,omitempty"`
Coordinates map[string]string `yaml:"coordinates,omitempty"` // the declared axes of the default point
TermsSource string `yaml:"terms_source,omitempty"`
MemorySource string `yaml:"memory_source,omitempty"`
Tools map[string]map[string]any `yaml:"tools,omitempty"` // per-tool presets
Locales map[string]LocaleDefaults `yaml:"locales,omitempty"` // per-target-language presets
Extras map[string]yaml.Node `yaml:",inline"`
}
// Collection is either a bare entry (path/format/target) or a named collection
// (name + content), and can carry its own source/target languages.
type Collection struct {
Name string `yaml:"name,omitempty"`
SourceLanguage model.LocaleID `yaml:"source_language,omitempty"`
TargetLanguages []model.LocaleID `yaml:"target_languages,omitempty"`
Content []ContentItem `yaml:"content,omitempty"`
Base string `yaml:"base,omitempty"` // the directory this collection lives in
Channel string `yaml:"channel,omitempty"` // `profile/channel` (named collections only)
Coordinates map[string]string `yaml:"coordinates,omitempty"` // declared axes; overlays defaults.coordinates per axis
SourceOnly bool `yaml:"source_only,omitempty"` // no target language; read and checked, never written
// Bare-entry fields (short form):
Path string `yaml:"path,omitempty"` // doublestar glob for source files
Format *FormatSpec `yaml:"format,omitempty"` // format ID; auto-detect per file if empty
Target string `yaml:"target,omitempty"` // output path template (tokens below)
Extras map[string]yaml.Node `yaml:",inline"`
}
// ContentItem additionally carries its own `base` (yaml:"base,omitempty"), the
// directory its matched paths are made relative to for target-token expansion,
// its own `channel`, a per-item `redaction`, and `comments` (ContentComments:
// `true`, or a mapping carrying `directives`, `channel` and `only`).
// Profile binds governance to one product and declares its channels.
type Profile struct {
Channels []Channel `yaml:"channels,omitempty"` // a slug, or {id, concept}
Voice *VoiceBinding `yaml:"voice,omitempty"` // same forms as defaults.voice
TermStore string `yaml:"termstore,omitempty"` // a standalone terms store, project-relative
Concept string `yaml:"concept,omitempty"` // display only; never resolved
ValidFrom string `yaml:"valid_from,omitempty"`
ValidTo string `yaml:"valid_to,omitempty"`
}
Ƒļöŵ đéƒîñîţîöñš ŕéüšé core/flow.StepsSpec àñđ core/flow.FlowStep (šéé ƒļöŵ-šţéþš-ƒöŕḿàţ).
Çöñţéñţ ḿöđéļ
Collections îš à ļîšţ öƒ Collection ṽàļüéš. Éàçĥ éñţŕý îš öñé öƒ ţŵö
šĥàþéš, đîšţîñĝüîšĥéđ ƃý Collection.IsBareEntry():
- Ƃàŕé éñţŕý: ĥàš à
pathàñđ ñöcontent. Ţĥépath,format, àñđtargetƒîéļđš àŕé þŕöḿöţéđ öñţö ţĥé çöļļéçţîöñ đîŕéçţļý. Üšé ţĥîš ƒöŕ à šîñĝļé ĝļöƃ ŵîţĥ ñö ĝŕöüþîñĝ. - Ñàḿéđ çöļļéçţîöñ: ĥàš à
nameàñđ à ñöñ-éḿþţýcontentļîšţ öƒContentItem, àñđ ḿàý šéţ îţš öŵñsource_language/target_languages. Üšé ţĥîš ţö ĝŕöüþ ŕéļàţéđ þàţţéŕñš àñđ šçöþé ļàñĝüàĝéš þéŕ ĝŕöüþ.
À çöļļéçţîöñ ŵîţĥ source_only: true đéçļàŕéš ţĥàţ îţ ĥàš ñö ţàŕĝéţ ļàñĝüàĝé:
à ŕüñ ŕéàđš îţ, çĥéçķš îţ, àñđ ŵŕîţéš ñöţĥîñĝ ƃàçķ. Ñàḿîñĝ ñö ţàŕĝéţ àļŕéàđý
ḿàķéš à çöļļéçţîöñ šöüŕçé-öñļý; ţĥé ƒļàĝ îš ţĥé đéŕéñçé ƃéţŵééñ ḿéàñîñĝ îţ
àñđ ƒöŕĝéţţîñĝ. Validate ŕéĵéçţš à çöļļéçţîöñ ţĥàţ šéţš îţ àñđ àļšö çàŕŕîéš à
ţàŕĝéţ, îñ éîţĥéŕ šþéļļîñĝ (çöļļéçţîöñ öŕ îţéḿ, target: öŕ
target_languages:).
Ŵĥéŕé à çöļļéçţîöñ ļîṽéš
Collection.Base îš ţĥé đîŕéçţöŕý ţĥé çöļļéçţîöñ ļîṽéš îñ. EffectiveItems
ƒöļđš îţ îñ: éṽéŕý îţéḿ'š Path, Target àñđ öŵñ Base îš ĵöîñéđ öñţö îţ
(JoinBase), šö éṽéŕý çöñšüḿéŕ đöŵñšţŕéàḿ šééš þŕöĵéçţ-ŕéļàţîṽé þàţĥš àñđ ñéṽéŕ
ĥàš ţö ķñöŵ à ƃàšé ŵàš đéçļàŕéđ. Àñ àƃšöļüţé þàţĥ îš ļéƒţ àļöñé, šö ţĥé éšçàþé
çĥéçķ đöŵñšţŕéàḿ šţîļļ šééš îţ; àñ éḿþţý öñé šţàýš éḿþţý.
Àñ îţéḿ ţĥàţ đéçļàŕéš ñö Base öƒ îţš öŵñ ķééþš ñöñé; ţĥé ţàŕĝéţ ţöķéñš ţĥéñ
ŕéļàţîṽîžé àĝàîñšţ ţĥé ĵöîñéđ þàţţéŕñ'š öŵñ ƒîẋéđ þŕéƒîẋ. Ţĥàţ îš ŵĥàţ ḿàķéš
base: à ļöçàţîöñ ŕàţĥéŕ ţĥàñ à šéçöñđ ŕéļàţîṽîžàţîöñ ŕööţ.
Ţĥé çöñţéẋţ šþàçé
Çöñţéñţ îš ŵŕîţţéñ ƒöŕ à þöîñţ îñ ţĥé çöñţéẋţ šþàçé. Ţŵö àẋéš àŕé
šţŕüçţüŕàļ: ţĥé ÞŔÖĐÜÇŢ îţ ƃéļöñĝš ţö àñđ ţĥé ÇĤÀÑÑÉĻ îţ šĥîþš öñ. À ķéý
üñđéŕ profiles: îš à þŕöđüçţ, ţĥé çĥàññéļš ţĥàţ þŕöƒîļé ļîšţš àŕé ţĥé
çĥàññéļš ţĥàţ þŕöđüçţ šĥîþš öñ, àñđ à ñàḿéđ çöļļéçţîöñ ñàḿéš ţĥé þöîñţ îţš
çöñţéñţ šîţš àţ ŵîţĥ öñé channel: ŕéƒéŕéñçé:
profiles:
northsea:
channels: [cli, docs]
voice: .kapi/voice.yaml # the project's default voice
acme:
channels:
- id: docs
concept: term:9a1c0f42b7 # display only; never resolved
- app
voice: .kapi/profiles/acme/voice.yaml # == the conventional location
termstore: .kapi/profiles/acme/terms.json # optional; the project's own store otherwise
collections:
- name: acme-app
channel: acme/app
- name: northsea-docs
channel: northsea/docs # both declare `docs`, so qualify it
Ţĥé ḿàþ ķéý üñđéŕ profiles: îš ţĥé þŕöƒîļé'š ñàḿé: ţĥé þŕöđüçţ-àẋîš ṽàļüé îţš
çöļļéçţîöñš çàŕŕý, àñđ ţĥé đîŕéçţöŕý üñđéŕ .kapi/profiles/<name>/ ĥöļđîñĝ ţĥé
ƒîļéš îţ öṽéŕŕîđéš. À þŕöƒîļé ţĥàţ ƃîñđš ñéîţĥéŕ à ṽöîçé ñöŕ à ṽöçàƃüļàŕý îš
šţîļļ à þŕöƒîļé: ţĥàţ đîŕéçţöŕý îš ţĥé ƃîñđîñĝ, àñđ à þŕöĵéçţ ķééþîñĝ îţš
öṽéŕŕîđéš ţĥéŕé šĥöüļđ ñöţ ĥàṽé ţö ŕéšţàţé éṽéŕý öñé öƒ ţĥéḿ îñ ţĥé ŕéçîþé.
Þŕöƒîļé ñàḿéš àñđ çĥàññéļš àŕé šļüĝš (^[a-z0-9][a-z0-9-]*$): šţàƃļé
ḿàçĥîñé îđéñţîƒîéŕš, ñéṽéŕ ţŕàñšļàţéđ, çöḿþàŕàƃļé ƃýţé ƒöŕ ƃýţé. À þŕöƒîļé àñđ à
çĥàññéļ ḿàý éàçĥ çàŕŕý à çöñçéþţ ƒöŕ đîšþļàý, ƃüţ ŕéšöļüţîöñ ñéṽéŕ ļööķš àţ
îţ: çöñçéþţš àŕé đéšîĝñéđ ţö ƃé ŕéñàḿéđ àñđ đéþŕéçàţéđ àš ṽöçàƃüļàŕý îš ŕéṽîšéđ,
àñđ ĝöṽéŕñàñçé ţĥàţ ḿöṽéđ ŵĥéñ šöḿéöñé éđîţéđ à ţéŕḿ ŵöüļđ ƃé ĝöṽéŕñàñçé ñöƃöđý
çöüļđ ŕéļý öñ.
Ŕéšöļüţîöñ îš ƃý đéçļàŕàţîöñ, ñöţ ƃý ḿàţçĥîñĝ. KapiProject.ResolveChannel
ŕéàđš öñé channel: ŕéƒéŕéñçé, àļŵàýš ţĥé ǫüàļîƒîéđ profile/channel. À ƃàŕé
çĥàññéļ ñàḿé îš àñ éŕŕöŕ ţĥàţ šþéļļš öüţ ţĥé ǫüàļîƒîéđ ƒöŕḿ(š). Ţĥé ŕéšüļţ
îš à ChannelRef{Profile, Channel}, ŵĥöšé žéŕö ṽàļüé îš ţĥé þŕöĵéçţ'š đéƒàüļţ
þöîñţ.
Àƒţéŕ à þŕöƒîļé îš šéļéçţéđ, ţĥé çöļļéçţîöñ'š çĥàññéļ šéļéçţš ţĥé öṽéŕŕîđé
îñšîđé ţĥàţ þŕöƒîļé'š ṽöîçé (profile.VoiceProfile.Channels,
Ç-07), šö à ļàñđîñĝ-þàĝé ŕéĝîšţéŕ
îš àüţĥöŕéđ öñçé ƃéšîđé ţĥé ṽöîçé îţ ṽàŕîéš ŕàţĥéŕ ţĥàñ đüþļîçàţéđ îñţö à ṽöîçé
ƒîļé þéŕ þŕöđüçţ-àñđ-çĥàññéļ þàîŕ. À çĥàññéļ ţĥé þŕöƒîļé đéçļàŕéš ñö öṽéŕŕîđé
ƒöŕ îš ñöţ àñ éŕŕöŕ: ţĥé ƃàšé ṽöîçé àþþļîéš, ŵĥîçĥ îš ţĥé ŕîĝĥţ àñšŵéŕ ƒöŕ à
ṽöîçé ţĥàţ ŕéàđš ţĥé šàḿé éṽéŕýŵĥéŕé.
À ṽöîçé þŕöƒîļé çàñ đéçļàŕé šĥàŕéđ constraints ƃéšîđé tone àñđ style:
constraints:
- id: service/no-unsupported-assurance
version: 1
source: service-facts.md#assurances
statement: Do not promise a risk-free service.
kind: prohibited_pattern
regex: '(?i)\b(risk-free)\b'
- id: service/recording
version: 1
source: service-facts.md#recording
statement: Appointments are not recorded.
kind: guidance
Ţĥé çŕîţîçàļ þàţţéŕñ ŕéḿàîñš àþþļîçàƃļé ŵĥéñ à çĥàññéļ ŕéþļàçéš îţš šţýļé
šéçţîöñ. Ţĥé ĝüîđàñçé šţàţéḿéñţ îš þàššéđ ţö ŵŕîţéŕš àñđ îš éẋþļîçîţļý öüţšîđé
đéţéŕḿîñîšţîç šéḿàñţîç ṽéŕîƒîçàţîöñ. kapi context éẋþöšéš ŕéšöļṽéđ çöñšţŕàîñţ
ŕéçöŕđš àñđ ţĥé ƒüļļ đéçļàŕéđ çööŕđîñàţé þöîñţ îñ ĴŠÖÑ. Ţĥé ţéẋţ ƒöŕḿ îñçļüđéš
ţĥé šàḿé çöñšţŕàîñţ ĝüîđàñçé àñđ çööŕđîñàţéš.
scope çàñ ñàḿé éẋàçţ locale, channel àñđ persona ṽàļüéš; þöþüļàţéđ ṽàļüéš
ḿüšţ àļļ ḿàţçĥ. Àñ exceptions éñţŕý ñééđš à ñöñéḿþţý šçöþé, à ŕéàšöñ,
approved_by àñđ approval_ref. Àþþŕöṽàļ þŕöṽéñàñçé îñ à ļöçàļ ƒîļé îš àššéŕţéđ
ƃý ţĥé àüţĥöŕ. Çüšţöḿ ŕéçîþé çööŕđîñàţéš àŕé šĥöŵñ îñ çöñţéẋţ àñšŵéŕš ƃüţ đö
ñöţ àçţ àš çöñšţŕàîñţ þŕéđîçàţéš. Šéé Ç-07
ƒöŕ ŕéšöļüţîöñ, ṽàļîđàţîöñ àñđ šţöŕé-üþđàţé šéḿàñţîçš.
Ţĥé ŕéḿàîñîñĝ àẋéš àŕé đéçļàŕéđ. defaults.coordinates ñàḿéš ţĥé àẋéš ţĥé
þŕöĵéçţ'š çöñţéñţ šîţš àţ üñļéšš à çöļļéçţîöñ šàýš öţĥéŕŵîšé, àñđ à çöļļéçţîöñ'š
öŵñ coordinates: öṽéŕļàýš îţ þéŕ àẋîš. project.MergeCoordinates(defaults, derived, declared) îš ţĥé ŕüļé: ḿöšţ šþéçîƒîç ŵîñš, þéŕ àẋîš, àñđ àñ éḿþţý
ṽàļüé ñéṽéŕ éŕàšéš à ƃŕöàđéŕ ļàýéŕ. product àñđ channel àŕé ŕéƒüšéđ üñđéŕ
coordinates: ƃý project.DeclarableAxis, ƃéçàüšé ţĥéý àŕé đéŕîṽéđ ƒŕöḿ
channel:. project.BrandAxis (brand) àñđ project.ModeAxis (mode, ŵîţĥ
ţĥé Đîáţàẋîš ṽàļüéš tutorial, how-to, reference, explanation àš
çöñṽéñţîöñš) àŕé ţĥé ţŵö đéçļàŕéđ àẋéš ţĥé ƒŕàḿéŵöŕķ šþéļļš; àñý öţĥéŕ ñàḿé îš
ṽàļîđ (Ç-02).
KapiProject.ResolveGovernance(collection) ŕéšöļṽéš à çöļļéçţîöñ ñàḿé îñţö à
ResolvedGovernance (çĥàññéļ, ṽöîçé ƃîñđîñĝ, TermStore, ţĥé þŕöƒîļé'š ñàḿé,
àñđ ţĥé ŕéçîþé ķéý ţĥé ṽöîçé çàḿé ƒŕöḿ), ƒàļļîñĝ ƃàçķ ţö ţĥé þŕöĵéçţ đéƒàüļţš
ƒöŕ àñ éḿþţý öŕ üñķñöŵñ çöļļéçţîöñ ñàḿé, àñđ ƒöŕ à çöļļéçţîöñ ţĥàţ ƃîñđš ñö
çĥàññéļ; ItemForPath(relPath) ñàḿéš ţĥé çöñţéñţ îţéḿ ţĥàţ çļàîḿš à ƒîļé, ţĥé
ƒîŕšţ îñ ŕéçîþé öŕđéŕ ŵĥöšé þàţţéŕñ ḿàţçĥéš îţ, àñđ CollectionForPath(relPath)
ţĥé çöļļéçţîöñ ţĥàţ îţéḿ šîţš îñ. ProjectContext.ResolveContent àþþļîéš ţĥé
šàḿé ŕüļé ŵĥéñ îţ éẋþàñđš ţĥé ŕéçîþé îñţö ƒîļéš, šö à ƒîļé ŕéšöļṽéš ţö öñé îţéḿ
ŵĥîçĥéṽéŕ đîŕéçţîöñ ţĥé ǫüéšţîöñ îš àšķéđ ƒŕöḿ. Ţĥé ñàḿé ķééþš îţš
đîšţàñçé ƒŕöḿ profile.ResolveContext, ŵĥîçĥ îš à đéŕéñţ ţĥîñĝ îñ à þàçķàĝé
üšéđ àļöñĝšîđé ţĥîš öñé: ţĥé îñþüţ ţö þŕöƒîļé ŕéšöļüţîöñ, ñöţ ţĥé ŕéçîþé'š
àñšŵéŕ.
Ţĥàţ îš ţĥé ŕéçîþé ĥàļƒ, àñđ îţ îš àñ àüţĥöŕîñĝ ĥàļƒ: ţĥé ṽöîçé îţ ñàḿéš îš
ļöàđéđ ƃý ţĥé ĥöšţ àñđ ţĥéñ ĥàñđéđ ţö profile.ResolveProfileFromContext àš
CollectionProfile, ţĥé çöļļéçţîöñ ţîéŕ öƒ ţĥé ƒŕàḿéŵöŕķ'š šîñĝļé þŕéçéđéñçé
çĥàîñ (Ç-07), šö àñ
éẋþļîçîţ þéŕ-çàļļ þŕöƒîļé šţîļļ öüţŕàñķš ţĥé ŕéçîþé àñđ à þŕöĵéçţ ĝöṽéŕñéđ ƒŕöḿ
à ṽéñüé ŕàñķš îţš ƃîñđîñĝš îđéñţîçàļļý. Ţĥé þöîñţ'š çĥàññéļ ĝöéš îñ ƃéšîđé îţ àš
CollectionConfig[PropertyChannel], àñđ ResolveProfile àþþļîéš ţĥé öṽéŕŕîđé.
ChannelRef.Coordinates() ŕéñđéŕš ţĥé šţŕüçţüŕàļ þöîñţ àš ţĥé ţŵö àẋéš ţĥàţ
ţŕàṽéļ öñ ţĥé šýñç ŵîŕé, project.ProductAxis ("product") çàŕŕýîñĝ ţĥé
þŕöƒîļé ñàḿé àñđ project.ChannelAxis ("channel") çàŕŕýîñĝ ţĥé çĥàññéļ, àñđ
ţĥé đéƒàüļţ þöîñţ ŕéñđéŕš àš ñîļ. Ţĥé éñţŕý à þüšĥ çàŕŕîéš ƒöŕ à çöļļéçţîöñ îš
ţĥé ḿéŕĝéđ þöîñţ (šţŕüçţüŕàļ þļüš đéçļàŕéđ àẋéš), îţš ṽöîçé ƃîñđîñĝ, àñđ îţš
þŕéṽîéŵ ĥöšţ îƒ ţĥé ṽéñüé éẋţéñšîöñ đéçļàŕéš öñé, šö ƃöţĥ ṽéñüéš ŕéšöļṽé ţĥé
šàḿé ṽöîçé ƒöŕ ţĥé šàḿé çöñţéñţ.
Ŵĥàţ đöéš ñöţ çŕöšš îš à þŕöƒîļé'š termstore:. Ţĥàţ îš à þàţĥ îñţö ţĥé ļöçàļ
þŕöĵéçţ, àñđ à þàţĥ ḿéàñš ñöţĥîñĝ ţö à ṽéñüé ţĥàţ ĝöṽéŕñš ţéŕḿîñöļöĝý ƒŕöḿ à
šĥàŕéđ ṽöçàƃüļàŕý. À ŕéçîþé ţĥàţ ƃîñđš à ţéŕḿš šţöŕé þéŕ þŕöƒîļé
(KapiProject.BindsTermsByProfile) àñđ àļšö ƃîñđš à ṽéñüé îš ŵàŕñéđ àţ ŕüñ ţîḿé
(host.WarnUnsyncedCoordinates, çàļļéđ ƃý kapi run, kapi up àñđ
RunFlowAllLocales) ţĥàţ ţĥé ƃîñđîñĝ àþþļîéš ţö ļöçàļ ŕüñš öñļý. Ţĥé ŕüñ
þŕöçééđš; ţĥîš îš à çàṽéàţ, ñöţ à ƒàüļţ.
Öñé ƒüñçţîöñ ŕéšöļṽéš ĝöṽéŕñàñçé ƒöŕ éṽéŕý šüŕƒàçé:
KapiProject.ResolveGovernanceFor(GovernancePoint{Profile, Collection, Path, Comments, At}).
Îţ ŵàļķš ţĥé đéçļàŕéđ ƃîñđîñĝš ƒîñéšţ-ƒîŕšţ (à çöñţéñţ îţéḿ'š öŵñ channel:,
ţĥéñ îţš çöļļéçţîöñ'š, ţĥéñ ţĥé þŕöĵéçţ đéƒàüļţ), šķîþþîñĝ àñý ŵĥöšé þŕöƒîļé îš
öüţšîđé îţš ṽàļîđîţý ŵîñđöŵ àţ At, àñđ ŕéçöŕđš ţĥé šķîþ öñ
ResolvedGovernance.Fallback šö ţĥé çàļļéŕ çàñ ŕéþöŕţ îţ. À þöîñţ ŵîţĥ
Comments šéţ ñàḿéš ţĥé çöḿḿéñţš îñ ţĥé ƒîļé àţ Path, àñđ îţš ŵàļķ šţàŕţš
ŵîţĥ ţŵö ŕüñĝš öƒ îţš öŵñ: ţĥé çļàîḿîñĝ îţéḿ'š comments.channel, ţĥéñ
defaults.comments.channel. ResolveGovernance
àñđ ResolveGovernanceForPath àŕé ţĥé àš-đéçļàŕéđ ṽîéŵš öṽéŕ ţĥé šàḿé ŵàļķ (à
žéŕö At àþþļîéš ñö ŵîñđöŵ); ResolveGovernanceAt îš ţĥé àš-öƒ ṽîéŵ.
À ŕüñ ŕéšöļṽéš þéŕ ƑÎĻÉ àñđ éẋéçüţéš öñçé þéŕ đîšţîñçţ ŕéšöļüţîöñ:
groupInputsByBinding (ĥöšţ) þàŕţîţîöñš ţĥé îñþüţ šéţ ţĥŕöüĝĥ ţĥàţ ƒüñçţîöñ,
àñđ éàçĥ ĝŕöüþ ĝéţš îţš öŵñ ƃîñđîñĝš àñđ îţš öŵñ ţööļ çĥàîñ. Ţĥé çĥàîñ îš ƃüîļţ
ƃéƒöŕé àñý çöñţéñţ îš šééñ, šö ţĥé þàŕţîţîöñ îš ŵĥàţ ḿàķéš þéŕ-ƒîļé ĝöṽéŕñàñçé
þöššîƃļé. Ĝŕöüþîñĝ ķéýš öñ ŵĥàţ ţĥé þöîñţš ŕéšöļṽé ţö, ñöţ öñ ţĥé þöîñţš
ţĥéḿšéļṽéš, šö ţŵö çöļļéçţîöñš ĝöṽéŕñéđ ƃý öñé þŕöƒîļé šĥàŕé à ĝŕöüþ, àñđ à
ŕéçîþé ŵĥéŕé ñöţĥîñĝ ƃîñđš à çĥàññéļ þŕöđüçéš éẋàçţļý öñé ĝŕöüþ: ţĥé šîñĝļé,
üñšþļîţ ŕüñ.
Ţĥé îñšţàñţ îš ƒîẋéđ öñçé þéŕ ŕüñ (App.GovernanceInstant, šĥàŕéđ ƃý éṽéŕý
çöñṽéŕĝé ŵöŕķéŕ), šö à ļöñĝ þàšš çàññöţ çŕöšš à ṽàļîđîţý ƃöüñđàŕý ĥàļƒŵàý
ţĥŕöüĝĥ. Ţĥé ƒîŕšţ ƒàļļ-ţĥŕöüĝĥ þéŕ ŕüñ îš þŕîñţéđ öñ šţđéŕŕ àš
governance: profile "x" expired <date>; governing with …, đéđüþļîçàţéđ ƃý
App.NoteGovernance; kapi context search çàŕŕîéš ţĥé šàḿé šéñţéñçé îñ îţš
ŕéšüļţ ñöţéš.
Éṽéŕý ƒàîļüŕé îš çàüĝĥţ àţ ļöàđ, ƃéçàüšé à šîļéñţ ƒàļļ-ƃàçķ ŵöüļđ ţŕàñšļàţé ţĥàţ çöñţéñţ îñ à þļàüšîƃļé-ļööķîñĝ ŵŕöñĝ ṽöîçé: à ñöñ-šļüĝ þŕöƒîļé ñàḿé öŕ çĥàññéļ, à çĥàññéļ đéçļàŕéđ ţŵîçé ƃý öñé þŕöƒîļé, à þŕöƒîļé ṽöîçé ƃîñđîñĝ ţĥàţ ñàḿéš ñö šöüŕçé öŕ ḿöŕé ţĥàñ öñé, à ḿàļƒöŕḿéđ çöñçéþţ ŕéƒéŕéñçé, à çĥàññéļ ŕéƒéŕéñçé ñàḿîñĝ àñ üñđéçļàŕéđ þŕöƒîļé öŕ çĥàññéļ, àñđ àñý ƃàŕé (üñǫüàļîƒîéđ) ŕéƒéŕéñçé. Ƃàŕé éñţŕîéš çàññöţ çàŕŕý à çĥàññéļ àţ àļļ: ŕéšöļüţîöñ îš ƃý çöļļéçţîöñ ñàḿé, šö à þöîñţ öñ àñ üññàḿéđ éñţŕý çöüļđ ñéṽéŕ ƃé ŕéàđ.
Ţĥé ŕéçîþé'š çööŕđîñàţé šüŕƒàçé îš ŵŕîţàƃļé ţĥŕöüĝĥ kapi apply ŵîţĥ
kind: "recipe": defaults.coordinates.<axis> (öñé àẋîš þéŕ éñţŕý; àñ éḿþţý
ṽàļüé ŵîţĥđŕàŵš îţ) àñđ collections.<name>.channel ĝö ţĥŕöüĝĥ
project.SetField, ŵĥîçĥ àþþļîéš ţĥé šàḿé ŕéƒüšàļš àñđ þŕéšéŕṽéš ţĥé ŕéçîþé'š
ƒöŕḿàţţîñĝ.
KapiProject.IterateContent ŵàļķš ƃöţĥ šĥàþéš üñîƒöŕḿļý, ýîéļđîñĝ éàçĥ
ContentItem, ƃàšé àļŕéàđý ƒöļđéđ îñ, þàîŕéđ ŵîţĥ îţš þàŕéñţ çöļļéçţîöñ šö
çàļļéŕš çàñ ŕéšöļṽé ƒàļļ-ţĥŕöüĝĥ ƒîéļđš. Ļàñĝüàĝé ŕéšöļüţîöñ ƒàļļš ţĥŕöüĝĥ îţéḿ →
çöļļéçţîöñ → þŕöĵéçţ đéƒàüļţš ṽîà ContentItem.ResolvedSourceLanguage /
ResolvedTargetLanguages. À ƃàŕé éñţŕý'š þŕöḿöţéđ ƒîéļđš àŕé ŵŕàþþéđ àš à
šîñĝļé-îţéḿ šļîçé ƃý Collection.EffectiveItems, çàŕŕýîñĝ îţš Extras
ţĥŕöüĝĥ šö þéŕ-îţéḿ éẋţéñšîöñ ƒîéļđš šüŕṽîṽé.
Đéƒàüļţš-šçöþéđ šéţţîñĝš
Defaults ĥöļđš þŕöĵéçţ-ŵîđé þŕöçéššîñĝ šéţţîñĝš ţĥàţ îñđîṽîđüàļ çöñţéñţ îţéḿš
çàñ öṽéŕŕîđé. Ƃéýöñđ ļöçàļéš àñđ ţĥé þàŕàļļéļîšḿ/éñçöđîñĝ ķñöƃš šĥöŵñ àƃöṽé:
flow(šţŕîñĝ): ţĥé ƒļöŵkapi upŕüñš, à ƃüîļţ-îñ ñàḿé öŕ à ķéý îñflows:. Éḿþţý ḿéàñškapi runŕéǫüîŕéš àñ éẋþļîçîţ ƒļöŵ.materialize(manual|on-converge): ŵĥéţĥéŕ ţĥé çöñṽéŕĝéñçé ļööþ öŵñš đéļîṽéŕý öƒ ţĥé ţàŕĝéţ-ļàñĝüàĝé ƒîļéš.manual, ţĥé đéƒàüļţ, ļéàṽéš đéļîṽéŕý ţökapi mergeöŕkapi up --materialize;on-convergeŵŕîţéš à ļöçàļé'š ƒîļéš öñļý ŵĥéñ îţš ĝàţéđ šçöþéš àŕé àļļ šĥîþþàƃļé.jobs(îñţ): ĥöŵ ḿàñý ţàŕĝéţ ļàñĝüàĝéš öñékapi upþàšš çöñṽéŕĝéš çöñçüŕŕéñţļý;up --jobsöṽéŕŕîđéš þéŕ ŕüñ.source_gate(authored|checked|approved|none): ţĥé šöüŕçé šţàţüš à ƃļöçķ ḿüšţ ŕéàçĥ ƃéƒöŕé îţš ţŕàñšļàţîöñš àŕé þŕöđüçéđ;checkedîš ţĥé đéƒàüļţ àþþļîéđ ŵĥéñ üñšéţ.merge(MergeDefaults.ConflictPolicy): ĥöŵkapi mergeŕéšöļṽéš à ţŕàñšļàţöŕ'š ţàŕĝéţ àĝàîñšţ àñ éẋîšţîñĝ öñ-đîšķ ţàŕĝéţ öŕ çöñţéñţ-ḿéḿöŕý éñţŕý (translator-winsđéƒàüļţ,existing-wins,newest-wins). Šéé Ḿ-01.memory(MemoryDefaults): ţĥé þŕöĵéçţ'š çöñţéñţ ḿéḿöŕý:fuzzy_threshold, ţĥé þŕé-ƒîļļ çüţöƒƒ öñkapi extract(đéƒàüļţDefaultFuzzyThreshold= 75).segmentation(SegmentationDefaults): öþţ-îñ ŠŔẊ šéñţéñçé šéĝḿéñţàţîöñ öṽéŕļàý öñ éẋţŕàçţ (source, öþţîöñàļsrxŕüļéš ƒîļé).annotations(AnnotationDefaults): ŵĥîçĥ öƒ à ƃļöçķ'š šţàñđ-öƒƒ àññöţàţîöñš à ŵŕîţéŕ đŕàŵš îñţö ţĥé đöçüḿéñţ àš îñļîñé ḿàŕķš; žéŕö ļéàṽéš éàçĥ ƒöŕḿàţ'š öŵñ đéçļàŕàţîöñ šţàñđîñĝ.redaction(*RedactionSpec): ŕéþļàçé šéñšîţîṽé çöñţéñţ ŵîţĥ þŕöţéçţéđ þļàçéĥöļđéŕš ƃéƒöŕé þŕöçéššîñĝ àñđ ŕéšţöŕé îţ àƒţéŕŵàŕđš. Öṽéŕŕîđàƃļé þéŕContentItem.Redaction.voice(*VoiceBinding): ƃîñđ à ṽöîçé þŕöƒîļé (öñé öƒprofile_file,profile, öŕpack, öŕ à ƃàŕé þàţĥ) àš šţàñđîñĝ þŕöĵéçţ çöñţéẋţ.coordinates(ḿàþ): ţĥé đéçļàŕéđ àẋéš öƒ ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ (šéé àƃöṽé).tools(ḿàþ öƒ ţööļ ñàḿé ţö çöñƒîĝ): þŕöĵéçţ-ļéṽéļ ţööļ þŕéšéţš, àþþļîéđ ŵĥéŕéṽéŕ ţĥé ţööļ ŕüñš îñ à þŕöĵéçţ ƒļöŵ; à ƒļöŵ šţéþ'š öŵñ çöñƒîĝ öṽéŕŕîđéš þéŕ ķéý.locales(ḿàþ öƒ ļöçàļé ţö{tools}): þéŕ-ţàŕĝéţ-ļàñĝüàĝé þŕéšéţš ţĥàţ ḿéŕĝé öñ ţöþ öƒtoolsàñđ üñđéŕ à šţéþ'š öŵñ çöñƒîĝ.terms_source/memory_source(šţŕîñĝ): çöḿḿîţţéđ, ĝîţ-ţŕàçķéđ ñàţîṽé šöüŕçé ƃüñđļéš (.terms.json/.memory.json) ţĥé þŕöĵéçţ'š ţéŕḿš šţöŕé àñđ çöñţéñţ ḿéḿöŕý àŕé îñđéẋéđ ƒŕöḿ.kapi applyéđîţš ţĥé šöüŕçé àñđ ŕéîñđéẋéš îţ, šö ţĥé šöüŕçé îš ŵŕîţţéñ ƃý éẋàçţļý öñé þàţĥ àñđgit diffîš ţĥé ŕéṽîéŵ šüŕƒàçé. Ƃöţĥ ķéýš ƃîñđ àñý þàţĥ; ţĥé çöñṽéñţîöñàļ ĥöḿéš àŕé îñšîđé ţĥé çöḿḿîţţéđ çöñţéẋţ ĝŕàþĥ.terms_sourceļéƒţ üñšéţ ƒàļļš ƃàçķ ţö<root>/.kapi/terms.json, ţĥéñ<root>/terms.json;memory_sourceĥàš ñö šüçĥ ƒàļļƃàçķ, ƃéçàüšé à þŕöĵéçţ ĥàš öñé ţéŕḿš šöüŕçé ƃüţ ḿàñý ḿéḿöŕý ƃüñđļéš (öñé þéŕ çöñţéñţ šüŕƒàçé), ļéàṽîñĝ ñöţĥîñĝ šîñĝļé ƒöŕ à çöñṽéñţîöñ ţö ñàḿé.
Ţĥé þŕöĵéçţ šţöŕé
Ţĥé ŕéçîþé ƃîñđš šöüŕçéš; ţĥé šöüŕçéš àŕé ţĥé ţŕüţĥ. À þŕöĵéçţ ķééþš öñé ļöçàļ
đàţàƃàšé, .kapi/work/store.db: à đéŕîṽéđ îñđéẋ öṽéŕ ţĥé çöḿḿîţţéđ šöüŕçéš
(terms_source, memory_source, ţĥé ṽöîçé þŕöƒîļéš), ţĥé üñîţ-šţàţé ŕéçöŕđ
üñđéŕ .kapi/state/, àñđ ţĥé çöñţéñţ ƒîļéš ţĥéḿšéļṽéš, þļüš ţĥé ŵöŕķîñĝ šéţ öƒ
üñîţ šţàţé šţàĝéđ šîñçé ţĥé ļàšţ kapi commit. Éṽéŕý šüƃšýšţéḿ'š ţàƃļéš ļîṽé îñ
ţĥàţ öñé ƒîļé: ƃļöçķ çàçĥé, ţéŕḿš šţöŕé, çöñţéñţ ḿéḿöŕý, ṽöîçé šţöŕé, ŵöŕķîñĝ
šéţ, àñđ ţĥé þŕöþéŕţý ĝŕàþĥ. Šéé
Ç-03 ƒöŕ ţĥé šţöŕé'š
šĥàþé àñđ îţš ŕéƃüîļđ ĝüàŕàñţééš.
Éẋţéñšîöñš àñđ ţĥé ṽéñüé
Ţĥé ƒŕàḿéŵöŕķ ķñöŵš ñöţĥîñĝ àƃöüţ à þļüĝîñ'š ķéýš. Üñķñöŵñ ÝÀḾĻ ķéýš ļàñđ îñ
Extras map[string]yaml.Node (ŵîţĥ yaml:",inline") öñ KapiProject,
Defaults, Collection, àñđ ContentItem. À þļüĝîñ đéçöđéš îţš öŵñ ţýþéđ
šçĥéḿà ƒŕöḿ ţĥéšé ḿàþš ṽîà GetExtra àñđ ŕé-éñçöđéš öñ SetExtra;
ŕöüñđ-ţŕîþþîñĝ à ŕéçîþé ţĥŕöüĝĥ ţĥé ƒŕàḿéŵöŕķ àļöñé þŕéšéŕṽéš ţĥé ķéýš ṽéŕƃàţîḿ.
À ŕéçîþé ŵîţĥ ñö šüçĥ éẋţéñšîöñ îš à þüŕé ļöçàļ þŕöĵéçţ. Ţĥé kapi ÇĻÎ ţöļéŕàţéš
üñķñöŵñ ƃļöçķš ƃüţ îĝñöŕéš ţĥéḿ; ţĥé öŵñîñĝ þļüĝîñ đéçöđéš ţĥéḿ ƒŕöḿ Extras.
Àñ üñķñöŵñ ķéý ţĥàţ îš öñé éđîţ àŵàý ƒŕöḿ à ķñöŵñ ƒîéļđ öƒ ţĥé šàḿé šţŕüçţ îš
ŕéþöŕţéđ ƃý KapiProject.KeyWarnings ŵîţĥ ţĥé ƒîéļđ îţ ŕéšéḿƃļéš, šö à ţýþö
šüçĥ àš source: ƒöŕ source_language: đöéš ñöţ ļöàđ šîļéñţļý àš à
ḿöñöļîñĝüàļ þŕöĵéçţ. requires: (à ḿàþ öƒ þļüĝîñ ñàḿé → šéḿṽéŕ çöñšţŕàîñţ)
ĝàţéš ļöàđîñĝ: à ŕéçîþé đéçļàŕîñĝ requires: { myplugin: "^1.0" } ŕéƒüšéš ţö
ļöàđ îñ à ƃîñàŕý ţĥàţ ĥàš ñöţ ŕéĝîšţéŕéđ ţĥé myplugin éẋţéñšîöñ.
Öñé öƒ ţĥöšé ķéýš ḿàý ƃé ţĥé þŕöĵéçţ'š çöñṽéŕĝéñçé ṽéñüé: à šéŕṽéŕ ţĥàţ
ĥöļđš ţĥé çöñţéñţ ḿéḿöŕý, ŕüñš ţĥé ļööþ öñ öŕĝàñîšàţîöñ ķéýš, àñđ çàŕŕîéš à
ŕéṽîéŵ ǫüéüé. Ţĥé ƒŕàḿéŵöŕķ'š îñţéŕéšţ îñ îţ îš éẋàçţļý ţŵö ƒîéļđš, url: àñđ
converge:, šö îţ àšķš ţĥé ŕéĝîšţŕý ŵĥîçĥ éẋţéñšîöñ îš ţĥé ṽéñüé ŕàţĥéŕ ţĥàñ
ļööķîñĝ ƒöŕ à ķéý ƃý ñàḿé: àñ Extension ŕéĝîšţéŕéđ ŵîţĥ Venue: true çļàîḿš
ţĥàţ ŕöļé, àñđ à þļüĝîñ ḿàñîƒéšţ đéçļàŕéš îţ àš "venue": true öñ à šçĥéḿà
éẋţéñšîöñ. KapiProject.Venue() ŕéţüŕñš ţĥé VenueBinding{Key, URL, Converge}
öƒ ţĥé ƒîŕšţ ŕéĝîšţéŕéđ ṽéñüé éẋţéñšîöñ ţĥé ŕéçîþé çàŕŕîéš, VenueKey() ñàḿéš
ţĥé ŕéĝîšţéŕéđ ṽéñüé ķéý ŵîţĥ ñö ŕéçîþé îñ ĥàñđ (šö à ḿéššàĝé àƃöüţ àñ àƃšéñţ
ƃļöçķ çàñ šţîļļ ñàḿé ţĥé ƃļöçķ ţö àđđ), àñđ IsVenueKey(name) ţéšţš öñé ķéý. Àñ
üñŕéĝîšţéŕéđ ķéý öƒ ţĥé šàḿé ñàḿé (à ŕéçîþé ļöàđéđ ƃý à ƃîñàŕý ŵîţĥöüţ ţĥé
þļüĝîñ) ŕéþöŕţš ñö ṽéñüé àñđ ñö öþîñîöñ. Ţĥé ṽéñüé çļîéñţ (host/venue/schema)
àļšö ŕéĝîšţéŕš preview àţ çöļļéçţîöñ šçöþé, đéçöđéđ öñļý ŵĥéñ ţĥé ṽéñüé ķéý
îš þŕéšéñţ. Šéé Ç-01 ƒöŕ
ţĥé ƒüļļ éẋţéñšîöñ ḿöđéļ.
Ṽàļîđàţîöñ Ŕüļéš
versionîš ŕéǫüîŕéđ, ḿüšţ ƃé"v1". À ţöþ-ļéṽéļ ķéý ţĥé ŕéçîþé đöéš ñöţ ĥàṽé (content:,coordinates:) îš ŕéĵéçţéđ ƃý ñàḿé ŵîţĥ à ĥîñţ àţ ţĥé ķéý ţĥàţ çàŕŕîéš ţĥé îñţéñţ, ŕàţĥéŕ ţĥàñ çàþţüŕéđ àš àñ üñķñöŵñ éẋţéñšîöñ.idîš öþţîöñàļ. Ŵĥéñ šéţ,ValidateIDŕéǫüîŕéšprj_ƒöļļöŵéđ ƃý 20 ţö 64 çĥàŕàçţéŕš ƒŕöḿa-z2-7, ŵĥîçĥ îš ŵĥàţNewIDḿîñţš (128 ŕàñđöḿ ƃîţš àš ļöŵéŕçàšé ŔƑÇ 4648 ƃàšé32, 26 çĥàŕàçţéŕš).- Ƒöŕ éàçĥ
collections[]éñţŕý:- Ƃàŕé éñţŕý:
pathîš ŕéǫüîŕéđ àñđcontentḿüšţ ƃé éḿþţý. - Ñàḿéđ çöļļéçţîöñ:
pathḿüšţ ƃé éḿþţý (üšécontent) àñđcontentḿüšţ ƃé ñöñ-éḿþţý; éàçĥ îţéḿ ŕéǫüîŕéš à ñöñ-éḿþţýpath. channelŕéǫüîŕéš àname, àñđ ḿüšţ ŕéšöļṽé àĝàîñšţ ţĥé đéçļàŕéđ þŕöƒîļéš àš ǫüàļîƒîéđprofile/channel.source_only: trueîš ŕéĵéçţéđ ŵĥéñ ţĥé çöļļéçţîöñ öŕ àñý îţéḿ àļšö çàŕŕîéš àtargetöŕtarget_languages.coordinatesḿàý ñöţ ñàḿéproductöŕchannel.- Àñ îţéḿ'š
commentsîštrue,false, öŕ à ḿàþþîñĝ ŵĥöšé ķéýš àŕédirectives,channelàñđonly. Éàçĥ đîŕéçţîṽé, ţĥéŕé àñđ üñđéŕdefaults.comments.directives, îš à ḿàŕķéŕ ţĥàţ îš ñöţ éḿþţý, šţàŕţš ŵîţĥ ñö ŵĥîţéšþàçé, ĥöļđš ñö ļîñé ƃŕéàķ àñđ îš đéçļàŕéđ öñçé; àñ îţéḿ'š ļîšţ ḿàý ñöţ ŕéþéàţ öñé ţĥé đéƒàüļţš đéçļàŕé. Ţĥé éŕŕöŕ ñàḿéš ţĥé ķéý, šüçĥ àšcollections[0].content[1].comments.directives[0]. - Àñ îţéḿ'š
comments.channelàñđdefaults.comments.channelŕéšöļṽé àţ ļöàđ àš à çöļļéçţîöñ'šchannelđöéš, àñđ ţĥé éŕŕöŕ ñàḿéš ţĥé ķéý. - Àñ îţéḿ ţĥàţ šéţš
comments.onlyḿàý ñöţ àļšö çàŕŕý àtarget,target_languages, àredaction, öŕ àformat.configöŕformat.preset(ContentItem.validateCommentsOnly). Ţĥé éŕŕöŕ ñàḿéš ţĥé îţéḿ, šüçĥ àšcollections[1].content[0]: comments.only is set, so the item cannot have a target.
- Ƃàŕé éñţŕý:
- Éṽéŕý
profiles:ķéý îš à šļüĝ, àñđ šö îš éṽéŕý çĥàññéļ îţ đéçļàŕéš; à çĥàññéļ îš đéçļàŕéđ àţ ḿöšţ öñçé þéŕ þŕöƒîļé. À þŕöƒîļé'švoiceîš šĥàþé-çĥéçķéđ éẋàçţļý ļîķédefaults.voice(öñé öƒprofile_file,profile,pack, öŕ à ƃàŕé þàţĥ), àconceptöñ ţĥé þŕöƒîļé öŕ öñ à çĥàññéļ ḿüšţ ƃé ŵĥîţéšþàçé-ƒŕéé, àñđvalid_from/valid_toḿüšţ þàŕšé àš à đàţé öŕ àñ ŔƑÇ3339 îñšţàñţ. defaults.merge.conflict_policy,defaults.memory.fuzzy_threshold(0..100),defaults.redaction.detectors,defaults.materializeàñđdefaults.voiceàŕé éàçĥ šĥàþé-çĥéçķéđ.defaults.source_gateîš ŕéàđ ƃý ţĥé ŕüññéŕ, ŵĥîçĥ àþþļîéšcheckedŵĥéñ îţ îš üñšéţ.- Éàçĥ ƒļöŵ ḿüšţ ĥàṽé àţ ļéàšţ öñé šţéþ
- Éàçĥ šţéþ ḿüšţ ĥàṽé à ñöñ-éḿþţý
toolƒîéļđ (üñļéšš îţ üšéšparallel) - Šţéþš ŵîţĥ
parallelçàñ öḿîţtool(ţĥé þàŕàļļéļ ƃŕàñçĥéš þŕöṽîđé ţööļš) - Éàçĥ
requires:éñţŕý ḿüšţ ĥàṽé à ñöñ-éḿþţý þļüĝîñ ñàḿé àñđ à ŵéļļ-ƒöŕḿéđ šéḿṽéŕ çöñšţŕàîñţ (^1.0,>=1.4.0,1.4.0,~1.4.2, öŕ*). ÜñļéššSkipRequiresCheckîš šéţ, éṽéŕý ñàḿéđ þļüĝîñ ḿüšţ ĥàṽé à ŕéĝîšţéŕéđ éẋţéñšîöñ ĝŕöüþ, éļšé ļöàđîñĝ ƒàîļš ŵîţĥ àñ îñšţàļļ ĥîñţ. - Éẋţŕàš àţ éàçĥ šçöþé àŕé ṽàļîđàţéđ àĝàîñšţ àñý ŕéĝîšţéŕéđ éẋţéñšîöñ šçĥéḿà.
Ñöţé: name îš öþţîöñàļ (yaml:"name,omitempty"); ţĥé ƒŕàḿéŵöŕķ đöéš ñöţ
ŕéǫüîŕé îţ. KapiProject.Identity() ŕéàđš id ƒîŕšţ àñđ ƒàļļš ƃàçķ ţö name,
àñđ ţĥàţ îš ţĥé ṽàļüé éṽéŕý þŕöĵéçţ-šçöþéđ ķéý îš đéŕîṽéđ ƒŕöḿ, šţàŕţîñĝ ŵîţĥ
ţĥé çöñţéẋţ ĝŕàþĥ'š šçöþé (host.ProjectScope). project.SetField(proj, "id", …) îš ţĥé öñé ŵŕîţéŕ: îţ ŕéƒüšéš àñ éḿþţý ṽàļüé àñđ ŕéƒüšéš ţö ŕéþļàçé àñ îđ
ţĥé ŕéçîþé àļŕéàđý çàŕŕîéš, šö àñ àþþļîéđ çĥàñĝé-šéţ çàññöţ ŕé-ķéý à þŕöĵéçţ.
Ƒîļé Þàţĥš
- Çöñţéñţ þàţţéŕñš àŕé éẋþàñđéđ ṽîà
core/project.ExpandGlob, ƃàçķéđ ƃýgithub.com/bmatcuk/doublestar/v4; ŕéçüŕšîṽé**đîŕéçţöŕý ḿàţçĥîñĝ îš šüþþöŕţéđ (é.ĝ.src/**/*.json).ExpandGlobƒîļţéŕš öüţ àñý ḿàţçĥ ţĥàţ ḿàţçĥéš öñé öƒ ţĥédefaults.excludeĝļöƃ þàţţéŕñš (ḿàţçĥéđ ŵîţĥdoublestar.Match). À ļööķüþ ţĥàţ šţàŕţš ƒŕöḿ à þàţĥ àþþļîéš ţĥé šàḿé þàţţéŕñš:KapiProject.ItemForPathçļàîḿš ñö éẋçļüđéđ þàţĥ, šö à ñàḿéđ éẋçļüđéđ ƒîļé šîţš àţ ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ - Çöñţéñţ ŕéšöļüţîöñ àļšö šķîþš à ƒîļé ţĥé þŕöĵéçţ'š îĝñöŕé ŕüļéš ḿàţçĥ:
.kapiignore,KAPI_IGNOREàñđ ţĥé đéƒàüļţ ŕüļéš (core/ignore). ÀKapiProjectĥöļđš ñö þŕöĵéçţ đîŕéçţöŕý, šö ţĥé ĥöšţš àþþļý ţĥöšé ŕüļéš ŵĥéŕé ţĥéý ţüŕñ à ñàḿéđ þàţĥ îñţö à þöîñţ (host.ProjectIgnores): à ñàḿéđ îĝñöŕéđ ƒîļé šîţš àţ ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ àñđ îñ ñö çöļļéçţîöñ, ƒöŕkapi check,kapi voice guide,kapi context, à ŕüñ'š ƃîñđîñĝš àñđ ţĥé đéšķţöþ çöñţéẋţ þàñéš - Þàţţéŕñš àŕé ŕéšöļṽéđ ŕéļàţîṽé ţö ţĥé þŕöĵéçţ ŕööţ (ţĥé ŕéçîþé'š þàŕéñţ đîŕéçţöŕý)
targetîš éẋþàñđéđ þéŕ šöüŕçé ƒîļé àñđ ţàŕĝéţ ļàñĝüàĝé ƃýcore/project.ResolveTargetPath(itemPath, base, target, source, lang):baseĥéŕé îš ţĥé îţéḿ'š öŵñ ƃàšé, ţĥé đîŕéçţöŕý ţĥé šöüŕçé þàţĥ îš ḿàđé ŕéļàţîṽé ţö, àƒţéŕ ţĥé çöļļéçţîöñ'šbaseĥàš ƃééñ ƒöļđéđ îñ. Ŵĥéñ éḿþţý îţ đéƒàüļţš ţöGlobFixedPrefix(path), ţĥé ļîţéŕàļ þŕéƒîẋ öƒ ţĥé ĝļöƃ ƃéƒöŕé ţĥé ƒîŕšţ*/?/[/{(šöinput/docs/*.mdḿîŕŕöŕš ĵüšţ ƒîļéñàḿéš ŵĥîļéinput/**/*.md, öŕ àñ éẋþļîçîţbase, ḿîŕŕöŕš ţĥé šüƃţŕéé).- Ţöķéñš:
{lang},{relpath}(ŕéļ þàţĥ ŵîţĥ éẋţéñšîöñ),{path}(ŕéļ þàţĥ ŵîţĥöüţ éẋţéñšîöñ),{dir},{filename},{name}(àļîàš{basename}),{ext}; à ƃàŕé*îš šĥöŕţĥàñđ ƒöŕ{name}.{lang}îš ĥàñđļéđ ƃýResolvePathPattern; ţĥé ŕéšţ ƃýExpandTemplate. - Đîŕéçţöŕý-ḿîŕŕöŕ ƒöŕḿ: ŵĥéñ ţĥé ţàŕĝéţ (àƒţéŕ
{lang}éẋþàñšîöñ) éñđš ŵîţĥ/, îš éḿþţý, öŕ îţš ƒîñàļ šéĝḿéñţ ĥàš ñö éẋţéñšîöñ àñđ ñö ŵîļđçàŕđ/ţöķéñ, îţ đéñöţéš à đîŕéçţöŕý: ţĥé šöüŕçé'š{relpath}(üñđéŕbase) îš àþþéñđéđ. Šötarget: output/{lang}ḿîŕŕöŕš ţĥé šöüŕçé ţŕéé üñđéŕ éàçĥ þéŕ-ļàñĝüàĝé ŕööţ ŵîţĥ ñö ţöķéñ àñđ ñö đöüƃļéđ éẋţéñšîöñ. ŠééisDirectoryTargetîñcore/project/path.go.
Çŕéđéñţîàļ Ŕéšöļüţîöñ
Ţĥé kapi.yaml ŕéçîþé ŕéƒéŕéñçéš ÀÎ þŕöṽîđéŕš ƃý ţýþé (é.ĝ., provider: anthropic), ñöţ ƃý ķéý. ÀÞÎ ķéýš àŕé ŕéšöļṽéđ àţ ŕüñţîḿé:
- ÖŠ ķéýçĥàîñ ṽîà
host/credentials.Store(ñöñ-šéçŕéţ çöñƒîĝ àţproviders.jsonüñđéŕ ţĥé üšéŕ çöñƒîĝ đîŕéçţöŕý:~/.config/kapiöñ Ļîñüẋ,~/Library/Application Support/kapiöñ ḿàçÖŠ;kapi config pathþŕîñţš ţĥé ŕéšöļṽéđ ļöçàţîöñ. Ķéýš ļîṽé üñđéŕ ţĥé ķéýçĥàîñ šéŕṽîçé"kapi") - Éñṽîŕöñḿéñţ ṽàŕîàƃļéš (
ANTHROPIC_API_KEY,OPENAI_API_KEY) öŕ ţĥé--api-keyƒļàĝ - Ţĥé
--provideràñđ--modelÇĻÎ ƒļàĝš öṽéŕŕîđé þŕöĵéçţ đéƒàüļţš
ÇĻÎ Îñţéĝŕàţîöñ
# One-shot (no project)
kapi translate -i file.json --target-lang fr
# With project file: run a built-in flow with project defaults
kapi run translate-qa -p kapi.yaml --target-lang de
# Or run a flow defined in the recipe's flows: map (here named "translate")
kapi run translate -p kapi.yaml
Ƃüîļţ-îñ ƒļöŵš àŕé ŕéĝîšţéŕéđ îñ host/flowdef.BuiltInFlows; ţĥé
kapi run ŕéƒéŕéñçé ļîšţš ţĥéḿ. À ŕéçîþé'š flows:
ḿàþ àđđš ƒļöŵš, àñđ à ƃüîļţ-îñ ñàḿé ŵîñš öṽéŕ à ŕéçîþé ƒļöŵ öƒ ţĥé šàḿé ñàḿé îñ
kapi run, šö à ŕéçîþé ƒļöŵ ţĥàţ šĥöüļđ ñöţ šĥàđöŵ à ƃüîļţ-îñ ţàķéš à ñàḿé öƒ
îţš öŵñ.
Ŵîţĥ -p:
- Ţĥé ƒļöŵ ñàḿé îš ḿàţçĥéđ àĝàîñšţ ţĥé ƃüîļţ-îñ ƒļöŵš ƒîŕšţ; îƒ îţ îš ñöţ öñé öƒ
ţĥöšé, îţ îš ļööķéđ üþ îñ ţĥé þŕöĵéçţ'š
flowsḿàþ (àñđ ƒîñàļļý ţĥé þļüĝîñ ƒàļļƃàçķ) defaults.source_languageàñđdefaults.target_languages[0]þŕöṽîđé đéƒàüļţš (ÇĻÎ ƒļàĝš öṽéŕŕîđé)- Ƒöŕ šîñĝļé-ƒîļé ƒļöŵš,
--inputšéļéçţš ţĥé ƒîļé. Ţĥé þŕöĵéçţ'šcollectionsđéšçŕîƃé ŵĥîçĥ ƒîļéškapi extract/kapi mergeöþéŕàţé öñ àçŕöšš ţĥé þŕöĵéçţ
Đéšķţöþ Îñţéĝŕàţîöñ
Kapi Đéšķţöþ àţ apps/kapi-desktop/:
- Öþéñš à þŕöĵéçţ ƃý îţš ƒöļđéŕ (ŵĥîçĥ çöñţàîñš
kapi.yaml): Ƒîļé > Öþéñ, đŕàĝ-àñđ-đŕöþ - Éđîţš ƒļöŵš îñļîñé (šţéþš éđîţöŕ)
- Ŕéšöļṽéš çöñţéñţ þàţţéŕñš àĝàîñšţ ţĥé ƒîļéšýšţéḿ ṽîà
App.MatchContent(tabID), üšîñĝ ţĥé šàḿécore/projectĝļöƃ éẋþàñšîöñ ţĥé ÇĻÎ ŕéļîéš öñ ƒöŕextract/merge; þàţţéŕñ ŕéšöļüţîöñ îš šĥàŕéđ ƒŕàḿéŵöŕķ çöđé, ñöţ à đéšķţöþ-öñļý ƒéàţüŕé - Šţöŕéš ŕéçéñţ ƒîļéš (
recent.json) àñđ šéţţîñĝš (settings.json) îñ îţš öŵñ çöñƒîĝ ŕööţ (~/.config/kapi-desktopöñ Ļîñüẋ,~/Library/Application Support/kapi-desktopöñ ḿàçÖŠ), öṽéŕŕîđàƃļé ŵîţĥKAPI_DESKTOP_CONFIG_DIR(apps/kapi-desktop/backend/paths.go)
Éẋàḿþļé Ƒîļéš
Ḿîñîḿàļ
version: v1
name: Quick Translate
Ƒüļļ
version: v1
name: Acme App
defaults:
source_language: en
target_languages: [fr, de, ja]
concurrency: 4
parallel_blocks: 3
encoding: utf-8
exclude:
- "**/*.generated.json"
merge:
conflict_policy: translator-wins
memory:
fuzzy_threshold: 75
segmentation:
source: true
coordinates:
brand: acme
terms_source: .kapi/terms.json
memory_source: .kapi/memory/memory.json
profiles:
acme:
channels: [app, marketing]
voice: .kapi/voice.yaml
collections:
# Bare entry: single glob, languages inherited from defaults.
# Directory-mirror target: src/i18n/en/app.json → src/i18n/{lang}/app.json.
- path: "src/i18n/en/*.json"
target: "src/i18n/{lang}"
# Named collection: groups patterns, scopes languages, binds a channel, and
# names the directory it lives in. Paths and targets below are relative to it:
# marketing/en/docs/api.md → marketing/fr/docs/api.md.
- name: Marketing
channel: acme/marketing
coordinates:
mode: how-to
target_languages: [fr, de]
base: marketing
content:
- path: "en/docs/**/*.md"
target: "{lang}/docs"
- path: "en/site/**/*.html"
target: "{lang}/site"
# Source-only: read and checked, never translated.
- name: packaging
channel: acme/app
source_only: true
content:
- path: "packaging/nfpm.yaml"
preset: nextjs
requires:
okapi-bridge: ">=1.47.0"
flows:
translate:
steps:
- tool: translate
config:
provider: anthropic
full-pipeline:
steps:
- tool: recycle
config:
fuzzyThreshold: 75
- tool: translate
config:
provider: anthropic
- tool: qa
pseudo:
steps:
- tool: pseudo-translate
config:
expansionPercent: 30