Skip to main content

Terminology

neokapi manages terminology with a concept-oriented model inspired by the TBX (TermBase eXchange) standard: language-neutral concepts group multi-locale terms, each carrying a lifecycle status and optional grammatical metadata. The same model backs the kapi terms commands, the term-check, dnt-check and term-extract pipeline tools, and the terms/ Go library.

Concept-oriented model​

A concept is a language-neutral knowledge unit. It carries a domain and a definition, and groups terms across locales. Each term has a lifecycle status, and a locale may hold several terms (a preferred form plus admitted variants).

Concept: "cloud storage"Domain"infrastructure"Definition"Remote file storage accessed via internet"Term · en"cloud storage"← preferredTerm · fr"stockage cloud"← preferredTerm · fr"stockage en nuage"← admittedTerm · de"Cloud-Speicher"← preferredTerm · ja"クラウドストレージ"← preferred

Unlike a flat list of source→target pairs, a concept can hold several terms per locale, drive enforcement by status, and carry metadata on one language-neutral unit.

Term lifecycle statuses​

StatusMeaningUsage
preferredThe recommended termAlways suggest to translators
approvedAccepted for useValid alternative
admittedAllowed but not recommendedShow with lower priority
deprecatedBeing phased outWarn when found in translations
proposedUnder review, not yet approvedShow as suggestion with caveat
forbiddenMust not be usedFlag as an error in checks

Every "write this, not that" rule is a term. A use of a forbidden or competitor term in source content fails a check, and a use of a deprecated (retired) term reports. A concept marked advisory makes every such use report without failing. kapi context review --keep <id> --advisory and kapi terms import --advisory set the marking, as do an x-advisory descrip in TBX, an advisory column in CSV, and "advisory": true on a kind: term change-set entry.

Concept relations​

A terms store persists typed, directed relations between concepts, so a renamed product points at its replacement and a deprecated term points at the one to use instead. The relation vocabulary is aligned with SKOS:

CategoryLabelsMeaning
Hierarchybroader, narrowerA parent/child concept relationship
Compositionpart-of, has-partA whole/component relationship
AssociationrelatedA non-hierarchical association
Successionreplaced-byA concept superseded by another
Guidanceuse-insteadA discouraged term points at a preferred one
Cross-schemeexact-match, close-matchEquivalence across schemes
StancecompetitorA competitor's term

Each edge is a concept pointing at another under one of those labels:

utilizeforbiddenuse-insteadusepreferred
web storedeprecatedreplaced-bymarketplacepreferred
cloud storagebroaderinfrastructure

A relation is a first-class record with an ID, a source and target concept, a type from the vocabulary above, an optional note, and an optional validity (below). The terms store validates that the type is known and that both concepts exist before persisting an edge.

Relation and term validity​

A relation, and an individual term, may carry a validity: a half-open time interval [valid-from, valid-to) plus a set of free-form tags. A query supplies a scope (a point in time and a set of tags) and only edges and terms whose validity matches the scope are returned. A nil validity is unbounded (it matches every scope); a nil scope applies no filtering.

With validity, one terms store answers scope-dependent questions: which terms were preferred as of last quarter, or which relations hold within a given market. Tags are open-ended (the framework assigns them no meaning); a caller chooses a tag vocabulary (for example a market key) and uses it consistently.

Status transitions​

A term's status changes over its lifetime. ValidateTransition(from, to) accepts any transition between known statuses (history is the guard), while IsGovernedTransition(from, to) reports whether a change is consequential enough to deserve review: any transition to forbidden or preferred, or any transition from forbidden. The framework only classifies. An application built on it sets the review a governed transition needs.

Storage backends​

Two backends ship in the terms/ package, both thread-safe (RWMutex-protected) and implementing the full Terminology interface:

  1. In-memory (terms.NewInMemoryStore): fast and ephemeral, used for session-scoped batch processing.
  2. SQLite (terms.NewSQLiteStore): persistent file-based storage for CLI workflows, with fuzzy matching via SQL-based Levenshtein distance.

The Terminology interface also accommodates server-side backends for multi-user deployments with project scoping, terminology streams, and workspace isolation.

CLI usage​

Resource location​

All kapi terms commands (except list) accept these mutually exclusive flags:

FlagResolves toExample
--name <n>~/.config/kapi/terms/<n>.db--name project-terms
--local./terms.db (current directory)--local
--file <path>Explicit file path--file /shared/terms.db
(no flag)Same as --local

Databases are created on demand if they don't exist. terms/ and terms.db are the on-disk names for a named or loose terms store. ~/.config/kapi is the user config directory on Linux, and resolves to ~/Library/Application Support/kapi on macOS. kapi config path prints the resolved location.

With no flag inside a project, the terms store is instead a set of tables in the project's own store, which kapi store import fills from a terms bundle; see Memory & terms storage.

# Import terms
kapi terms import terms.json --name project-terms

# Export terms
kapi terms export --name project-terms --format bundle -o terms.json

# Look up a term by its text or a form it declares (exact, or --fuzzy)
kapi terms lookup "encryption" --name project-terms -s en -t fr
kapi terms lookup "authenticating users" -s en -t fr --fuzzy

# Search concepts, view statistics, list named terms stores
kapi terms search "auth" -s en --limit 50
kapi terms stats --name project-terms
kapi terms list

# Inside a project: where the extracted content uses a term or a concept
kapi terms occurrences "content memory"
kapi terms occurrences c-dashboard --locale nb --collection docs

The kapi terms commands cover import, export, lookup, search, occurrences, statistics, and listing. Inside a project, a term decision also lands through kapi apply: a term operation (action, term, locale, status, replacement, replaces, do_not_translate) is written to the project's terms store, and kapi terms export writes the store out as a bundle a pull request can review.

You author concept relations in Kapi Desktop. It opens a per-concept dashboard over a local terms store: the @neokapi/concept-ui component, which shows a concept's terms, geography, constraints, a local relations widget and a timeline. There you add, retype, scope and remove edges. Each edge is a ConceptRelation record in the terms store, read through the Go API below.

Pipeline integration​

Three pipeline tools bring terminology into the flow:

  • term-check scans each Block's source text for the terms a store or a rule list carries, attaches the matches as term annotations (source term, target suggestions, positions, status), and, where a target exists, checks that it uses the expected rendering. Violations are reported as findings with expected-versus-actual detail, and the tool exits non-zero on them, so it also works as a gate. It takes its rules from the project's terms store (--termstore outside a project) or from term_rules: in a flow step's config: the same shape as every word rule, including the terms: a voice file carries (one term, its replacement, an optional advisory, and optionally a concept_id that ties the rule to a concept here). A concept marked do-not-translate yields a rule with no replacement, and the check fails a target that does not keep its term verbatim. A term written in the source's inline code, a quoted kapi command, an example command line or a flag name owes no rendering, because code keeps its words in a translation; the prose around it is checked as usual. A source word rule leaves code out with scope: prose instead. A rule marked advisory: true reports without failing; any other rule fails. A rule resolved from a store carries no marking, so it fails by default. The source term matches in its own casing only when the tool or the rule sets case_sensitive.
  • dnt-check (do-not-translate) fails a target where a term listed under --terms (product names, trademarks, code identifiers) was translated, transliterated or dropped. The translate step masks those spans, so the model never sees them and cannot translate them. A concept the terms store marks do-not-translate travels a different route: the translate step asks for it in the prompt as a term to keep verbatim, and term-check fails a target that does not keep it.
  • term-extract surfaces candidate terms from the source, for a curator to approve into the store.

Two further stages exist only in the Go library: term-lookup and term-enforce (terms/tool.go, NewTermLookupTool and NewTermEnforceTool). term-lookup scans a Block's source text and attaches the matches as TermAnnotation entries; term-enforce checks a translated block against the expected terminology and records the outcome as block properties (term-enforce-passed, term-enforce-errors, term-enforce-violations). Neither is a registered tool: a recipe cannot name them and kapi tools does not list them. The runner appends both automatically behind any registered tool whose schema declares that it requires terms, whenever a terms store is open, so a flow step gets the lookup and the enforcement without naming either.

Go library​

Interface​

type Terminology interface {
AddConcept(concept Concept) error
GetConcept(id string) (Concept, bool)
DeleteConcept(id string) error
Lookup(sourceText string, opts LookupOptions) []TermMatch
LookupAll(sourceText string, opts LookupOptions) []TermMatch
Search(query string, sourceLocale, targetLocale model.LocaleID, offset, limit int) ([]Concept, int)

// Relations between concepts, optionally validity-scoped.
AddRelation(rel ConceptRelation) error
DeleteRelation(id string) error
RelationsOf(conceptID string, scope *graph.Scope) []ConceptRelation // both directions
ListRelations(scope *graph.Scope) []ConceptRelation

Count() int
Concepts() []Concept
Close() error
}

(Every method, and the import/export helpers below, takes a context.Context as its first argument and returns an error as its last in the real API; both are elided here for readability.)

Lookup finds the best match for a single term. LookupAll scans running text and returns every term occurrence with positions. The term-check tool, the term-lookup library stage and editor suggestions all use it. By default LookupAll matches case-insensitively (terminology should be recognized regardless of capitalization); set CaseSensitive to override.

Key types​

type Concept struct {
ID string
Domain string // subject area (security, ui, marketing)
Definition string // language-neutral description
Terms []Term
Properties map[string]string // extensible metadata
CreatedAt time.Time
UpdatedAt time.Time
}

type Term struct {
Text string
Locale model.LocaleID
Status model.TermStatus // preferred, approved, admitted, deprecated, proposed, forbidden
PartOfSpeech string
Gender string
Note string
Validity *graph.Validity // optional time + tag scope (nil = unbounded)
}

type ConceptRelation struct {
ID string
SourceID string
TargetID string
RelationType string // a SKOS-aligned label: broader, use-instead, replaced-by, …
Note string
Validity *graph.Validity // optional time + tag scope (nil = unbounded)
CreatedAt time.Time
}

type TermMatch struct {
Concept Concept
Term Term // the matched source term
Score float64 // 0.0-1.0
MatchType model.MatchStrategy // exact, normalized, fuzzy
Position model.TextRange // position in source text
}

type LookupOptions struct {
SourceLocale model.LocaleID
TargetLocale model.LocaleID
CaseSensitive bool
MinScore float64 // minimum fuzzy score (default 0.8)
MatchModes []model.MatchStrategy
Domains []string // restrict to specific domains
StatusFilter []model.TermStatus // only return terms with these statuses
}

Concept helpers: SourceTerm(locale), TargetTerms(locale), PreferredTerm(locale).

Example​

package main

import (
"fmt"

"github.com/neokapi/neokapi/core/model"
"github.com/neokapi/neokapi/terms"
)

func main() {
tb := terms.NewInMemoryStore()
defer tb.Close()

tb.AddConcept(terms.Concept{
ID: "c1",
Domain: "security",
Definition: "Process of encoding information",
Terms: []terms.Term{
{Text: "encryption", Locale: "en", Status: model.TermPreferred},
{Text: "chiffrement", Locale: "fr", Status: model.TermPreferred},
},
})

matches := tb.LookupAll(
"The encryption module handles end-to-end encryption",
terms.LookupOptions{SourceLocale: "en", TargetLocale: "fr"},
)
for _, m := range matches {
fmt.Printf("Found %q at [%d:%d] → %s (%s)\n",
m.Term.Text, m.Position.Start, m.Position.End,
m.Concept.TargetTerms("fr")[0].Text, m.Term.Status)
}
}

Import / export​

// JSON preserves the full concept-oriented structure
count, err := terms.ImportJSON(tb, reader)
err = terms.ExportJSON(tb, writer, "Project Terms")

// CSV is a flat source/target form with optional metadata
opts := terms.CSVImportOptions{
SourceLocale: "en", TargetLocale: "fr", Domain: "general", HasHeader: true,
}
count, err = terms.ImportCSV(tb, reader, opts)
err = terms.ExportCSV(tb, writer, "en", "fr", true)

CSV columns are source,target,domain (domain optional). JSON carries the full concept structure:

{
"name": "Project Terms",
"version": "1.0",
"concepts": [
{
"id": "c1",
"domain": "security",
"definition": "Encryption where only endpoints can decrypt",
"terms": [
{ "text": "end-to-end encryption", "locale": "en", "status": "preferred" },
{ "text": "chiffrement de bout en bout", "locale": "fr", "status": "preferred" }
]
}
]
}

Terminology and content memory​

Terminology and content memory are separate systems that answer different questions:

  • Content memory: "How was this sentence rendered before?" (segment pairs).
  • Terminology: "What is the correct term for this concept?" (multi-locale knowledge units).

Both write to the Block annotation system, so any downstream tool or editor sees memory matches and term matches.