Šķîþ ţö ḿàîñ çöñţéñţ

ķàþî.ýàḿļ Þŕöĵéçţ Ƒîļé Ƒöŕḿàţ

Îḿþļéḿéñţàţîöñ ñöţéš ƒöŕ ţĥé 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), ñöţ ƃý ķéý. ÀÞÎ ķéýš àŕé ŕéšöļṽéđ àţ ŕüñţîḿé:

  1. ÖŠ ķéýçĥàîñ ṽîà host/credentials.Store (ñöñ-šéçŕéţ çöñƒîĝ àţ providers.json üñđéŕ ţĥé üšéŕ çöñƒîĝ đîŕéçţöŕý: ~/.config/kapi öñ Ļîñüẋ, ~/Library/Application Support/kapi öñ ḿàçÖŠ; kapi config path þŕîñţš ţĥé ŕéšöļṽéđ ļöçàţîöñ. Ķéýš ļîṽé üñđéŕ ţĥé ķéýçĥàîñ šéŕṽîçé "kapi")
  2. Éñṽîŕöñḿéñţ ṽàŕîàƃļéš (ANTHROPIC_API_KEY, OPENAI_API_KEY) öŕ ţĥé --api-key ƒļàĝ
  3. Ţĥé --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