ķàþî.ýàḿļ Þŕöĵéçţ Ƒîļé Ƒöŕḿàţ
Îḿþļéḿéñţàţîöñ ñöţéš ƒöŕ ţĥé 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"`
FlowsDir string `yaml:"flows_dir,omitempty"` // one YAML file per flow, relative to the recipe
Profiles map[string]Profile `yaml:"profiles,omitempty"` // profile name → governance (see The context space)
Places map[string]place.Place `yaml:"places,omitempty"` // place id → a place the project declares (core/place)
Axes map[string]axis.Spec `yaml:"axes,omitempty"` // axis name → how a declared axis compares values
// 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"`
EstablishedGate gate.Gate `yaml:"established_gate,omitempty"`
EstablishedGates []ShipGateRule `yaml:"established_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
TranslateAfter string `yaml:"translate_after,omitempty"` // written (default) | established | 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
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`, its own `coordinates` (declared axes refining the
// collection's point for the files it matches), 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 terms store, by name
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]
# no `voice:`; the profile the store holds under "northsea" answers
acme:
channels:
- id: docs
concept: term:9a1c0f42b7 # display only; never resolved
- app
voice:
profile: acme-app # a profile in the project's voice store
termstore: acme-terms # optional, a store name; the project's own terms otherwise
collections:
- name: acme-app
channel: acme/app
- name: northsea-docs
channel: northsea/docs # both declare `docs`, so qualify it
Ţĥé ḿàþ ķéý üñđéŕ profiles: îš ţĥé þŕöƒîļé'š ñàḿé: ţĥé þŕöđüçţ-àẋîš ṽàļüé îţš
çöļļéçţîöñš çàŕŕý, àñđ ţĥé ñàḿé ţĥé þŕöĵéçţ'š ṽöîçé šţöŕé àñšŵéŕš üñđéŕ ŵĥéñ ţĥé
þŕöƒîļé ƃîñđš ñö voice: öƒ îţš öŵñ. À þŕöƒîļé ţĥàţ ƃîñđš ñéîţĥéŕ à ṽöîçé ñöŕ à
ṽöçàƃüļàŕý îš šţîļļ à þŕöƒîļé, ĝöṽéŕñéđ ƃý ţĥé þŕöĵéçţ đéƒàüļţš.
Þŕöƒîļé ñàḿéš àñđ çĥàññéļš àŕé šļüĝš (^[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: öṽéŕļàýš ţĥé đéƒàüļţš
þéŕ àẋîš, àñđ à çöñţéñţ îţéḿ'š coordinates: öṽéŕļàýš ţĥé çöļļéçţîöñ'š ƒöŕ
ţĥé ƒîļéš ţĥé îţéḿ ḿàţçĥéš. Éṽéŕý çàļļéŕ çöḿþüţéš à þöîñţ'š çööŕđîñàţéš ŵîţĥ
KapiProject.CoordinatesAt(pt, rc). Îţ ḿéŕĝéš ƒöüŕ ļàýéŕš îñ öŕđéŕ ŵîţĥ
project.MergeCoordinates: ţĥé đéƒàüļţš, ţĥé àẋéš đéŕîṽéđ ƒŕöḿ ţĥé
ĝöṽéŕñàñçé ŕéšöļṽéđ àţ ţĥé þöîñţ, ţĥé çöļļéçţîöñ'š çööŕđîñàţéš, àñđ ţĥé
çööŕđîñàţéš öƒ ţĥé îţéḿ ţĥàţ çļàîḿš ţĥé ƒîļé. Þéŕ àẋîš, ţĥé ḿöšţ šþéçîƒîç
ļàýéŕ ŵîñš, àñđ àñ éḿþţý ṽàļüé ļéàṽéš ţĥé ƃŕöàđéŕ ļàýéŕ'š ṽàļüé îñ þļàçé. product àñđ channel àŕé ŕéƒüšéđ üñđéŕ
coordinates: ƃý project.DeclarableAxis, ƃéçàüšé ţĥéý àŕé đéŕîṽéđ ƒŕöḿ
channel:. project.BrandAxis (brand) àñđ project.ModeAxis (mode, ŵîţĥ
ţĥé Đîáţàẋîš ṽàļüéš tutorial, how-to, reference, explanation àš
çöñṽéñţîöñš) àŕé ţĥé ţŵö đéçļàŕéđ àẋéš ţĥé ƒŕàḿéŵöŕķ šþéļļš; àñý öţĥéŕ ñàḿé îš
ṽàļîđ (Ç-02).
À đéçļàŕéđ àẋîš îš àñ öþéñ çàţéĝöŕý üñļéšš ţĥé ţöþ-ļéṽéļ axes: ƃļöçķ ţýþéš
îţ. KapiProject.Axes ḿàþš àñ àẋîš ñàḿé ţö àñ axis.Spec (core/axis):
type Spec struct {
Kind axis.Kind `yaml:"kind,omitempty"` // category (default) | geo | range | ordered
Label string `yaml:"label,omitempty"`
Values []string `yaml:"values,omitempty"` // closes a category axis; orders an ordered one; refused on geo and range
Labels map[string]string `yaml:"labels,omitempty"` // display labels per value; refused on geo
Unit string `yaml:"unit,omitempty"` // range: what the numbers count
}
Àñ üñķñöŵñ ķéý üñđéŕ àñ àẋîš ƒàîļš ţĥé ļöàđ, àñđ ţĥé éŕŕöŕ ñàḿéš ţĥé ķéý. À ḿîššþéļţ ķéý ţĥéŕéƒöŕé çàññöţ ļéàṽé àñ àẋîš üñţýþéđ.
À ĝéö àẋîš ĥöļđš þļàçé îđš. KapiProject.Places đéçļàŕéš ţĥé þŕöĵéçţ'š öŵñ
þļàçéš ƃéšîđé ţĥé ƃüîļţ-îñ öñéš (core/place,
Ç-12):
type Place struct {
Name string `yaml:"name,omitempty"` // display name; defaults to the id
Names map[string]string `yaml:"names,omitempty"` // display names per BCP 47 locale
Type place.Type `yaml:"type,omitempty"` // subdivision | territory | metro | locality | district | neighbourhood | postal-area | area
Within []string `yaml:"within,omitempty"` // one id or a list
Of []string `yaml:"of,omitempty"` // one id or a list
Refs []string `yaml:"refs,omitempty"` // wikidata:Q585, geonames:3143244, nuts:NO081, osm:relation/406091, wof:101752073
Centroid *place.LatLon `yaml:"centroid,omitempty"` // [lat, lon]
Shape string `yaml:"shape,omitempty"` // project-relative GeoJSON file, for drawing
}
Ţĥé ḿàþ ķéý îš ţĥé þļàçé'š îđ. Àñ üñķñöŵñ ķéý üñđéŕ à þļàçé ƒàîļš ţĥé ļöàđ.
Àñ éḿþţý type îš territory ƒöŕ à þļàçé ŵîţĥ of: àñđ area ƒöŕ öñé ŵîţĥ
within:.
KapiProject.Gazetteer() ŕéţüŕñš ţĥé ƃüîļţ-îñ þļàçéš þļüš ţĥé đéçļàŕéđ öñéš.
Ţĥé ĝàžéţţééŕ îš ƃüîļţ ŵĥéñ ţĥé ŕéçîþé îš ṽàļîđàţéđ. KapiProject.AxisSpace()
ŕéţüŕñš axis.NewSpace(p.Axes, p.Gazetteer()) àñđ îš ñîļ-šàƒé. Éṽéŕý šçöþé
ḿàţçĥ çàļļš Space.Contains(axis, outer, inner). À ŕüļé šçöþéđ ţö
market: nordics ţĥéŕéƒöŕé ĥöļđš àţ market: no, àñđ öñé šçöþéđ ţö
market: no ĥöļđš àţ à çîţý ţĥé ŕéçîþé đéçļàŕéš ŵîţĥîñ no-03.
KapiProject.DeclaredAxisNames() ļîšţš éṽéŕý đéçļàŕéđ àẋîš ţĥé ŕéçîþé ñàḿéš,
ŵĥéţĥéŕ axes: ţýþéš îţ öŕ à çööŕđîñàţé üšéš îţ, îñçļüđîñĝ àñ îţéḿ'š
çööŕđîñàţéš.
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 çàŕŕîéš ţĥé šàḿé šéñţéñçé îñ îţš
ŕéšüļţ ñöţéš.
Éṽéŕý ƒàîļüŕé îš çàüĝĥţ àţ ļöàđ, ƃéçàüšé à šîļéñţ ƒàļļ-ƃàçķ ŵöüļđ ţŕàñšļàţé ţĥàţ çöñţéñţ îñ à þļàüšîƃļé-ļööķîñĝ ŵŕöñĝ ṽöîçé: à ñöñ-šļüĝ þŕöƒîļé ñàḿé öŕ çĥàññéļ, à çĥàññéļ đéçļàŕéđ ţŵîçé ƃý öñé þŕöƒîļé, à þŕöƒîļé ṽöîçé ƃîñđîñĝ ţĥàţ ñàḿéš ñö šöüŕçé öŕ ḿöŕé ţĥàñ öñé, à ḿàļƒöŕḿéđ çöñçéþţ ŕéƒéŕéñçé, à çĥàññéļ ŕéƒéŕéñçé ñàḿîñĝ àñ üñđéçļàŕéđ þŕöƒîļé öŕ çĥàññéļ, àñđ àñý ƃàŕé (üñǫüàļîƒîéđ) ŕéƒéŕéñçé. Ƃàŕé éñţŕîéš çàññöţ çàŕŕý à çĥàññéļ àţ àļļ: ŕéšöļüţîöñ îš ƃý çöļļéçţîöñ ñàḿé, šö à þöîñţ öñ àñ üññàḿéđ éñţŕý çöüļđ ñéṽéŕ ƃé ŕéàđ.
Ţĥé ŕéçîþé'š çööŕđîñàţé šüŕƒàçé îš ŵŕîţàƃļé ţĥŕöüĝĥ à recipe öþéŕàţîöñ îñ
kapi apply: 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ŵŕîţéš à ļöçàļé'š ƒîļéš öñļý ŵĥéñ îţš ĝàţéđ šçöþéš àŕé àļļ šĥîþþàƃļé, àñđ ķééþš à ĥéļđ-ƃàçķ ļöçàļé'š đŕàƒţš îñ ţĥé ŵöŕķšþàçé ĥöḿé (core/workhome) üñţîļ à đéļîṽéŕý ŵŕîţéš ţĥéḿ.jobs(îñţ): ĥöŵ ḿàñý ţàŕĝéţ ļàñĝüàĝéš öñékapi upþàšš çöñṽéŕĝéš çöñçüŕŕéñţļý;up --jobsöṽéŕŕîđéš þéŕ ŕüñ.translate_after(written|established|none): ţĥé šöüŕçé šţàţüš à ƃļöçķ ḿüšţ ŕéàçĥ ƃéƒöŕé îţš ţŕàñšļàţîöñš àŕé þŕöđüçéđ;writtenîš ţĥé đéƒàüļţ àþþļîéđ ŵĥéñ üñšéţ. Àñý öţĥéŕ ṽàļüé ƒàîļš ţĥé ļöàđ. À ŕéçîþé ţĥàţ šţîļļ ŵŕîţéšdefaults.source_gateƒàîļš ţö ļöàđ àñđ ñàḿéšdefaults.translate_after; ţĥé ţöþ-ļéṽéļsource_gateçöṽéŕàĝé ƃàŕ îš à šéþàŕàţé ķéý.merge(MergeDefaults.ConflictPolicy): ŵĥàţkapi mergeđöéš ŵîţĥ à ŕéţüŕñéđ üñîţ ŵĥöšé ţŕàñšļàţîöñ çĥàñĝéđ îñ ţĥé þŕöĵéçţ šîñçé ţĥé éẋţŕàçţîöñ, ŵĥîçĥ ţĥé çĥàñĝé šéŕṽîçé ŕéƒüšéš šţàļé ñàḿîñĝif_match(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ñàḿéš öñé îñ ţĥé þŕöĵéçţ'š ṽöîçé šţöŕé àñđpackà ƃüîļţ-îñ šţàŕţéŕ þàçķ; à šçàļàŕ ţĥàţ îš ñöţ à þàţĥ îš ţĥé šĥöŕţ ƒöŕḿ öƒprofile. À ƃîñđîñĝ ţĥàţ ñàḿéš à ƒîļé (profile_file:, öŕ à šçàļàŕ þàţĥ) îš ŕéĵéçţéđ àţ ļöàđ ŵîţĥ ţĥé îḿþöŕţ ţĥàţ ŕéàđš ţĥé ƒîļé.coordinates(ḿàþ): ţĥé đéçļàŕéđ àẋéš öƒ ţĥé þŕöĵéçţ'š đéƒàüļţ þöîñţ (šéé àƃöṽé).tools(ḿàþ öƒ ţööļ ñàḿé ţö çöñƒîĝ): þŕöĵéçţ-ļéṽéļ ţööļ þŕéšéţš, àþþļîéđ ŵĥéŕéṽéŕ ţĥé ţööļ ŕüñš îñ à þŕöĵéçţ ƒļöŵ; à ƒļöŵ šţéþ'š öŵñ çöñƒîĝ öṽéŕŕîđéš þéŕ ķéý.locales(ḿàþ öƒ ļöçàļé ţö{tools}): þéŕ-ţàŕĝéţ-ļàñĝüàĝé þŕéšéţš ţĥàţ ḿéŕĝé öñ ţöþ öƒtoolsàñđ üñđéŕ à šţéþ'š öŵñ çöñƒîĝ.- Ñö ķéý ñàḿéš à çöñţéẋţ ƒîļé.
terms_sourceàñđmemory_sourceàŕé ŕéĵéçţéđ àţ ļöàđ (retiredDefaultsKeys) ŵîţĥ ţĥé îḿþöŕţ ţĥàţ ŕéàđš ţĥé ƒîļé.kapi store importŕéàđš à ļàýöüţ'šterms.json, îţšmemory/ƃüñđļéš àñđ îţš ṽöîçé þŕöƒîļéš îñţö ţĥé šţöŕéš, àñđ ƃîñđš ţĥé îḿþöŕţéđ ṽöîçé üñđéŕdefaults.voiceŵĥéñ ţĥé ŕéçîþé ƃîñđš ñöñé (project.BindVoice, à ţéẋţ îñšéŕţîöñ ţĥàţ ļéàṽéš ţĥé ŕéšţ öƒ ţĥé ƒîļé àš ŵŕîţţéñ).
Ţĥé þŕöĵéçţ šţöŕé
À þŕöĵéçţ ķééþš ţŵö đàţàƃàšéš. .kapi/work/store.db îš ţĥé çĥéçķöüţ'š
þŕöĵéçţîöñ öƒ îţš ŵöŕķîñĝ ţŕéé: ţĥé ƃļöçķ çàçĥé, ţĥé öṽéŕļàýš à ƒļöŵ ŵŕöţé, ţĥé
éẋţŕàçţîöñ šţàḿþš, àļļ đéŕîṽéđ ƒŕöḿ ţĥé çöñţéñţ ƒîļéš. Ţĥé þŕöĵéçţ'š çöñţéẋţ
šţöŕé, îñ ţĥé üšéŕ'š ŵöŕķšþàçé àñđ šĥàŕéđ ƃý éṽéŕý çĥéçķöüţ, ĥöļđš ţĥé ţéŕḿš,
ţĥé çöñţéñţ ḿéḿöŕý, ţĥé ṽöîçé þŕöƒîļéš àñđ ţĥé đéçîšîöñ ļéđĝéŕ, àñđ ñöţĥîñĝ
ŕéþŕöđüçéš à ŕöŵ îñ îţ. Ţĥé þŕöþéŕţý ĝŕàþĥ šîţš îñ ţĥé ŵöŕķšþàçé đàţàƃàšé
ƃéšîđé ţĥé þŕöĵéçţ ŕéĝîšţŕý, ţĥé öþéŕàţîöñ ļöĝ, ţĥé ŵîđéñéđ ŕüļéš àñđ ţĥé àĝéñţ
šéššîöñš. Šéé
Ç-03 ƒöŕ ţĥé šţöŕéš'
šĥàþé àñđ ţĥéîŕ ŕéƃüîļđ ĝüàŕàñţééš, àñđ
Ŵöŕķšþàçé šţöŕàĝé ƒöŕ ţĥé ƒîļéš àñđ ţàƃļéš.
Éẋţéñšîöñš àñđ ţĥé ṽéñüé
Ţĥé ƒŕàḿéŵöŕķ ķñöŵš ñöţĥîñĝ àƃöüţ à þļüĝîñ'š ķéýš. Üñķñöŵñ ÝÀḾĻ ķéýš ļàñđ îñ
Extras map[string]yaml.Node (ŵîţĥ yaml:",inline") öñ KapiProject,
Defaults, Collection, àñđ ContentItem. À þļüĝîñ đéçöđéš îţš öŵñ ţýþéđ
šçĥéḿà ƒŕöḿ ţĥéšé ḿàþš ṽîà GetExtra àñđ ŕé-éñçöđéš öñ SetExtra;
ŕöüñđ-ţŕîþþîñĝ à ŕéçîþé ţĥŕöüĝĥ ţĥé ƒŕàḿéŵöŕķ àļöñé þŕéšéŕṽéš ţĥé ķéýš ṽéŕƃàţîḿ.
À ŕéçîþé ŵîţĥ ñö šüçĥ éẋţéñšîöñ îš à þüŕé ļöçàļ þŕöĵéçţ. Ţĥé ķàþî ÇĻÎ ţöļéŕàţéš
üñķñöŵñ ƃļöçķš ƃüţ îĝñöŕéš ţĥéḿ; ţĥé öŵñîñĝ þļüĝîñ đéçöđéš ţĥéḿ ƒŕöḿ 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:) îš ŕéĵéçţéđ ƃý ñàḿé ŵîţĥ à ĥîñţ àţ ţĥé ķéý ţĥàţ çàŕŕîéš ţĥé îñţéñţ, ŕàţĥéŕ ţĥàñ çàþţüŕéđ àš àñ üñķñöŵñ éẋţéñšîöñ.placesƃüîļđš ţĥé þŕöĵéçţ'š ĝàžéţţééŕ (place.New). Éàçĥ îđ îš à šļüĝ ţĥàţ ñö ƃüîļţ-îñ þļàçé üšéš. Éàçĥ þļàçé šéţš éẋàçţļý öñé öƒwithin:àñđof:, àñđ éṽéŕý îđ ļîšţéđ ţĥéŕé îš à þļàçé. Ţĥéwithin:àñđof:ļîñķš çöñţàîñ ñö çýçļé.typeîš à đéçļàŕàƃļé ţýþé, àñđnamesàŕé ķéýéđ ƃý ļöçàļé. Éàçĥ éñţŕý îñrefsîšscheme:value, ŵîţĥ à ķñöŵñ šçĥéḿé àñđ à ṽàļüé îñ ţĥàţ šçĥéḿé'š ƒöŕḿàţ.centroidîš à ṽàļîđ ļàţîţüđé àñđ ļöñĝîţüđé.shapeîš à.geojsonöŕ.jsonþàţĥ ŕéļàţîṽé ţö ţĥé ŕéçîþé, šţàýš îñšîđé ţĥé þŕöĵéçţ, àñđ ñàḿéš àñ éẋîšţîñĝ ƒîļé. Éàçĥ éŕŕöŕ ñàḿéš ţĥé ķéý:places.oslo.within: "no-3" is not a place. Did you mean no-31 (Østfold, Norway), ....axesķéýš àŕé šļüĝš öţĥéŕ ţĥàñproductàñđchannel, éàçĥ ŵîţĥ à ķñöŵñ ķîñđ. Àñ öŕđéŕéđ àẋîš đéçļàŕéš îţš ṽàļüéš, àñđlabelsķéýš àŕé đéçļàŕéđ ṽàļüéš. À ĝéö àẋîš ţàķéš ñéîţĥéŕvaluesñöŕlabels(axis.Space.Check).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.- Éàçĥ ṽàļüé üñđéŕ
coordinates,defaults.coordinatesàñđ àñ îţéḿ'šcoordinatesḿüšţ ƃé ṽàļîđ ƒöŕ îţš àẋîš àšaxes:ţýþéš îţ (axis.Space.Validate). Öñ à çļöšéđ àẋîš îţ îš à đéçļàŕéđ ṽàļüé, öñ à ĝéö àẋîš à þļàçé îñ ţĥé þŕöĵéçţ'š ĝàžéţţééŕ, àñđ öñ à ŕàñĝé àẋîš àñ îñţéŕṽàļ. Ţĥé éŕŕöŕ ñàḿéš ţĥé ķéý, šüçĥ àšcollections[4]: collection "Oslo youth campaign": content[0]: coordinates.market. Àñ îţéḿ'š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öŕpack, àñđ ñö ƒîļé), àtermstoreîš à šţöŕé ñàḿé ŕàţĥéŕ ţĥàñ à þàţĥ, àconceptöñ ţĥé þŕöƒîļé öŕ öñ à çĥàññéļ ḿüšţ ƃé ŵĥîţéšþàçé-ƒŕéé, àñđvalid_from/valid_toḿüšţ þàŕšé àš à đàţé öŕ àñ ŔƑÇ3339 îñšţàñţ. defaults.merge.conflict_policy,defaults.memory.fuzzy_threshold(0..100),defaults.redaction.detectors(àñđ ţĥédetectorsöƒ à çöļļéçţîöñ'š öŕ àñ îţéḿ'šredaction:),defaults.materializeàñđdefaults.voiceàŕé éàçĥ šĥàþé-çĥéçķéđ.defaults.translate_afterḿüšţ ñàḿéwritten,establishedöŕnone; ţĥé ŕüññéŕ àþþļîéšwrittenŵĥéñ îţ îš üñšéţ.- Éàçĥ ƒļöŵ ḿüšţ ĥàṽé àţ ļéàšţ öñé šţéþ
- Éàçĥ šţéþ ḿüšţ ĥàṽé à ñöñ-éḿþţý
toolƒîéļđ (üñļéšš îţ üšéšparallel) - À šţéþ ñàḿîñĝ à ŕéñàḿéđ ţööļ îđ (
source-gate) ƒàîļš ŵîţĥ ţĥé îđ ţĥàţ ŕéþļàçéđ îţ (translate-after) - Šţéþš ŵîţĥ
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 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 translate, kapi pseudo-translate) àļŵàýš ŕüñ
ţĥé ƃüîļţ-îñ ţĥéý àŕé ñàḿéđ ƒöŕ.
Ŵîţĥ -p:
- Ţĥé ƒļöŵ ñàḿé îš ļööķéđ üþ îñ ţĥé þŕöĵéçţ'š
flowsḿàþ ƒîŕšţ, ţĥéñ àš<flows_dir>/<name>.yamlŵĥéñ ţĥé ŕéçîþé ñàḿéš àflows_dir(core/project.LoadDirFlow), ţĥéñ àḿöñĝ ţĥé ƃüîļţ-îñ ƒļöŵš (host.ResolveProjectFlow, ŵĥîçĥkapi upŕéšöļṽéšdefaults.flowţĥŕöüĝĥ àš ŵéļļ) defaults.source_languageàñđdefaults.target_languages[0]þŕöṽîđé đéƒàüļţš (ÇĻÎ ƒļàĝš öṽéŕŕîđé)- Ƒöŕ šîñĝļé-ƒîļé ƒļöŵš,
--inputšéļéçţš ţĥé ƒîļé. Ţĥé þŕöĵéçţ'šcollectionsđéšçŕîƃé ŵĥîçĥ ƒîļéškapi extract/kapi mergeöþéŕàţé öñ àçŕöšš ţĥé þŕöĵéçţ
Đéšķţöþ Îñţéĝŕàţîöñ
Ķàþî Đéšķţöþ àţ 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
market: world
voice:
profile: acme-docs
places:
dach:
name: DACH
of: [de, at, ch]
axes:
market:
kind: geo
label: Market
profiles:
acme:
channels: [app, marketing]
voice:
profile: acme-app
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/dach/**/*.md"
target: "{lang}/docs/dach"
coordinates:
market: dach # an item refines the collection's point
- 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