Skip to content

Feature Name Policy

Some KiwiDesk names are the same in every language — “App Bar”, “Space Bar”. Others are different in almost all of them — the layout modes. This page is the one place that says which is which, what each family requires of a translation, and why the two are enforced by opposite-shaped guards.

It exists because getting this wrong is invisible: prose naming a feature the interface does not call that reads perfectly to anyone who does not have both the string and the screen in front of them, and search made it worse — a destination name is now a breadcrumb segment, so an invented name appears on every hit inside that pane.

If you are translating, the rules are the two What it requires sections. If you are changing the policy or adding a name, read the rest.

Does this thing’s own label key ship untranslated in all eleven catalogs?

Yes → Family A. The name is the same everywhere. Add it to PRODUCT_NAMES in scripts/localization_guards.py.

No → Family B. Each locale decides, and only consistency with its own picker is enforced.

The test is about the catalogs, not about the word. The tempting alternative — does this name already have a domain-standard translation people know from other software? — reads well and mis-sorts both families:

namevocabulary test sayscatalogs saycorrect
Space BarB — スペースバー is standard… for the spacebar keyAA
TrackA — no domain-standard “Track layout” existsBB
drag.ghostA — reads like a coinageB (nine locales translate it: Silueta, Sagoma, ゴースト)B

Coinage-vs-borrowed is the explanation for how the catalogs came out that way. It is not the test, because it is arguable and the catalog question is checkable.

App Bar, Space Bar, and Sticky. bars.switch.app_bar and bars.switch.space_bar are Latin in all eleven catalogs, so the control every user taps says “Space Bar” whatever their language.

Sticky (#579) is the first single-word and the first added member — it did not arrive here by the sort question (de, ru, zh-Hant had translated it to “Fixierung”/“Закреплённое”/ “常駐”, so the catalog test read Family B), but by the product decision to coin it verbatim the way the bars were, which Core already reflects (design-decisions.md calls it “the settled user-facing term”). Before admitting a single word, the descriptive-occurrence check below was run: all 17 keys (21 sticky/Sticky occurrences) in en.json name this exact feature — there is no incidental “sticky” in ordinary prose to false-positive on (unlike app/space/bar, which are common words and needed the per-token glossary carve-outs). Its display tier is “Display Sticky” (German “Display-Sticky”), “display” being an ordinary qualifier that compounds per locale around the fixed “Sticky” atom.

Keep the name verbatim wherever the English carries it, and translate around it: “Mostra la App Bar”, “Couleurs de la Space Bar”.

  • One mention is enough. The check asks whether the name survives, never how often. If the English names a bar twice and your sentence reads better naming it once, that passes.
  • Keep each name the English carries. A string contrasting the two bars needs both, or the contrast collapses.
  • Capitalization is yours — “App bar” is fine.
  • Compounding is fine. German’s “Space-Bar-Farben” keeps the name; separators are flattened before comparison, so correct compounding is never punished.

Enforced by dropped_product_names, in every locale.

The natural reading is that this mirrors the English-residue guard, which is non-Latin-only. The two ask different questions. Residue asks whether a word was forgotten — a judgment about the sentence around it, and therefore script-sensitive. This asks whether a name the interface never translates was translated anyway, which is script-independent in the same way “KiwiDesk”, “Lua” and “BSP” are. _is_partly_translated already lists “App Bar” beside them as correctly all-English in a non-Latin catalog.

Adapting is worse in Japanese and Korean, not better: スペースバー and 스페이스바 are the ordinary words for the spacebar key, and neither script has capitalization to mark a proper noun, so the feature name would be indistinguishable from the key. English does not have that collision.

The check requires the name to be present, never to appear as often as in the English. Tightening it to parity reads like a strengthening and is a regression: dropping a redundant repetition of a proper noun is ordinary translation practice, so parity rejects correct work and pushes the translator toward a literal, worse sentence. The defect this guards against is a locale renaming the feature; a locale naming it fewer times has not renamed anything.

LocalizationProductNameGuardTests pins the distinction — a parity implementation fails two of its arguments.

There is no exemption file, and none is needed. The escape hatches, in order:

  1. Reword around the name. What every current translation does.
  2. If the name is redundant under its section header, delete it from the English. That lifts the obligation in every locale at once and improves the English — the GUI already works this way (SpaceBarGroups’ caption omits the name its header supplies), so the obligation is authored, not imposed. An opt-out would invert this, letting locales quietly diverge from an English redundancy nobody fixed.
  3. scripts/drop-key --locale <locale> <key> retires one locale’s value to the English fallback. A loud escape, which is the point.

Trade-off: a translator who wants the name gone entirely must change the English or drop the key. Accepted — the multi-mention strings contrast the two bars (“Space Bar sits at the screen edge, App Bar sits next to the windows”), where dropping one makes the sentence wrong rather than tighter. That is why presence is per-name.

Floating, Grid, Monocle, Scrolling, Stack, Track. (BSP is an initialism every locale keeps.)

The three CJK locales render them natively — ja モノクル, ko 트랙, zh-Hans 单窗. The seven Latin-script locales keep them English.

Match your own picker. If layout.monocle.name in your file says モノクル, the prose beside it says モノクル too, never “Monocle”. If your file keeps “Monocle”, so does your prose.

No name is required — there is no single right word to demand. Enforced by untranslated_mode_names, which requires the English name to be absent once your picker translates it, and skips your locale entirely when your picker is the English word.

Descriptive use is not naming, and case is how the guard tells them apart: it matches the capitalised form alone. This is a heuristic, not a rule the English followsen.json capitalises in labels that name no picker entry (“Stack position”, “Track limit”) and lower-cases some referential uses (“the track layout”). It is cheap and right on the corpus today; it is not a guarantee, and a name whose English is lower-cased everywhere would slip past it. So “la modalità floating” describes behaviour and is free, while “la disposizione Floating” names a picker entry that reads “Fluttuante” and is not.

Known blind spot: the check runs in one direction only. It fires where a picker is translated, which today means the three CJK locales. The seven that keep the English names skip themselves — which is what removes the need for an exemption list, and equally means nothing checks their prose against their own picker. The mirror defect is therefore invisible: a German caption reading “Schwebend” beside a picker reading “Floating” passes. Those locales are held by review, and by LocalizationModeNamePolicyTests only at the label level.

A mode name is not an ordinary label. It is a value the user types verbatim:

KiwiDesk.set_mode(1, "stack")
{"command":"set_mode","args":["1","grid"]}

A Spanish picker reading “Pila” above a config that only accepts "stack" breaks the link between what is shown and what is written. No other setting has that property, which is why translating mode names reads as helpful and is not.

The CJK locales are the exception for the same reason Family A has none: between two Latin-script languages the English word carries fine, but in a CJK sentence it is a foreign body.

LocalizationModeNamePolicyTests pins which locales are on which side, against the shipped catalogs.

It is forced, not stylistic:

Family AFamily B
the name isa coinage with no correct translationa word with a different correct form per locale
so the guard can demandthe English be presentonly that the English be absent
exemptions needednonenone — locales keeping English skip themselves

The symmetric form for Family B — demand the locale’s own translation be present — genuinely cannot work: a correct Spanish inflection like “las ventanas… flotarán” carries no noun “Flotante”, so it would flag correct copy.

Rejecting that shape is right. Concluding from it that no guard is possible does not follow, and is the expensive mistake here. This defect is invisible to a reviewer reading a language they do not speak, so a family left to review alone accumulates precisely the errors a guard would have named.

Apply the catalog question, then:

  • Family A — add it to PRODUCT_NAMES. Obligation scales with authored English mentions, not list length, so it binds only the keys whose English already contains it.
  • Family B — add its layout.<mode>.name key to MODE_NAME_KEYS.

A product-coinage decision may override a Family-B catalog reading — the catalog question sorts what already exists, but a name can be coined into Family A even when some locales had translated it, provided (i) the descriptive-occurrence check below passes and (ii) those locales are reharmonized to keep it verbatim in the same change, so the catalog question agrees afterward. That is exactly what #579 did for Sticky (de/ru/zh-Hant had translated it). The override is one-time, not a standing exception: once reharmonized, the catalogs are the test again.

A name that can occur for a different thing does not belong in Family A. The check is case-insensitive substring, so it cannot tell a referential mention from an unrelated one — and the substring reach means morphological variants inherit the obligation automatically (keybinding.make_unsticky “Make unsticky” already demands verbatim “Sticky”, which every locale honors). The two bar names are two-word coinages that only ever occur referentially, so the question never arises for them. Sticky is the single word where it had to be asked: a grep found all 17 keys (21 occurrences) carrying sticky/Sticky name this feature, none a different one. The real collision to keep re-checking is not “sticky note” — it is macOS’s own Sticky Keys accessibility feature: if a system_shortcut.* conflict string ever surfaces “Sticky Keys”, this guard would demand KiwiDesk’s “Sticky” be kept verbatim inside a correctly-localized Apple name (de “Einrastfunktion”), a false positive with no signpost back here. The corpus is clean today; when system-shortcut strings are added, re-grep system_shortcut.* for “Sticky Keys” specifically. That re-check is the price of a single-word Family A member — run it before adding one, and again when the shortcut corpus grows. A name built from ordinary words would fire on copy that was never naming the feature; a guard failing on correct copy is the one failure that makes an exemption file look necessary, so the rule holds: argue a name in, never add one to silence a hit.

  • Translating — the full translator guide, including the other five content guards.
  • Design Decisions — the rest of the settled product and UX decisions.

© 2026 KiwiCanopy