26 KiB
Logseq i18n Key Naming Standard
Purpose
This document defines how to name new i18n keys in src/resources/dicts/en.edn.
Goal: given any new user-facing string, this document should let you determine its key name directly.
Secondary goal: keep prefixes converged. A name that is slightly less "pure" but stays inside an existing owner is usually better than creating a new low-density root or singleton dotted subdomain.
This standard is intended to be deterministic. After applying it, you should be able to choose one reasonable key name. If you still cannot determine the name, treat that as a gap in the standard rather than guessing:
- AI agents must stop and ask for human guidance.
- Developers are encouraged to report the gap to the Logseq team so the standard can be clarified.
Audience:
- developers adding or renaming English keys
- AI agents reviewing or generating i18n changes
Non-English locale contributors should not invent or rename keys. They should use contributing-to-translations.md instead.
This document is only about key naming and reuse. Rules for placeholders, hiccup, punctuation, locale fallback, linting, and helper selection live in dev-practices.md.
Developer-only (Dev) labels are out of scope for this document. Keep them as
inline English labels next to the developer UI/command definition instead of
adding translation keys.
Key Shape
Use this shape:
:<root>[.<subdomain>]/<leaf>
Rules:
<root>chooses the semantic owner<subdomain>is optional and used only for a stable subfeature, workflow, or representation<leaf>describes the text's role inside that owner<root>names are singular semantic owners- All segments use kebab-case
Examples:
:ui/close
:page.delete/confirm-title
:nav.all-pages/title
:settings.editor/show-brackets
:plugin.install-from-file/success
:view.table/sort-ascending
:cmdk.action/open
:mobile.toolbar/undo
Before Naming a New Key
- Search
src/resources/dicts/en.edn. - Reuse a key only when both match:
- semantic owner
- textual role
- If the English text matches but the owner or role differs, create a new key.
Examples:
- toolbar
"Bold"and command"Bold"are different keys - dialog
"Close"and window"Close"are different keys - reusable
"Copied!"feedback may share anotification/*key only when it is intentionally cross-domain
Step 1: Choose the Owner
The namespace must be chosen by owner, not by file path, not by component name, and not by where the text happens to be rendered.
There are only 5 owner classes.
1. Interaction Systems
Use when the text belongs to an interaction registry or interaction subsystem.
| Namespace | Use for |
|---|---|
command.* |
Built-in command descriptions |
shortcut.category |
Shortcut help categories |
keymap |
Keybinding editor text |
cmdk |
Command palette text |
Use this class when:
- the text is attached to a command id
- the text names a shortcut group
- the text belongs to rebinding/conflict/chord UI
- the text belongs only to command palette behavior
For command.* keys:
- the subdomain is the command group name
- for built-in command descriptions, mirror the command id namespace even when the command opens another surface or workflow; the owner is still the command registry entry
- use a stable semantic group name, not an implementation placeholder
- avoid new opaque or self-referential groups such as
command.command command.command-palette/*is valid for descriptions attached to:command-palette/*ids; reservecmdk.*for command-palette UI copy itself
Examples:
:command.editor/bold
:command.graph/open
:command.page/toggle-favorite
:command.shell/run
:shortcut.category/block-editing
:keymap/search-placeholder
:cmdk.action/open
2. Shared Primitives
Use only when the wording keeps the same meaning across unrelated domains.
| Namespace | Use for |
|---|---|
ui |
Generic actions and states |
nav |
Global destinations and route-level constraints |
notification |
Reusable cross-feature feedback and notification-center shell controls |
search |
Generic search vocabulary |
select |
Generic picker vocabulary |
format |
Formatting vocabulary |
color |
Color vocabulary |
Qualification rules:
- removing product context does not change the meaning
- the wording can be reused in multiple unrelated domains
- the wording does not name a specific entity or workflow
- the wording stays natural with the same grammatical role across locales; if callers need different inflection, gender, number, part of speech, or label-vs-status behavior, do not force one shared key just because English matches
- if the text names a product entity such as
graph,page, orserver, use that product domain instead - do not use
notificationfor feature-specific toast text just because it is shown vianotification/show!; the delivery mechanism does not determine owner
Examples:
:ui/close
:ui/save
:nav/home
:notification/copied
:search/no-result
:select/default-prompt
:format/bold
:color/red
3. Product Domains
This is the default class. Most keys should be here.
Use when the text belongs to a first-class product feature, entity, or workflow.
The 5 groups below are taxonomy buckets, not a priority order. Do not infer ownership priority from subsection order. When multiple product domains seem plausible, use the conflict rules below.
3.1 Workspace and content domains
graphfilepageblocknodejournallibrarydateeditorreferencepropertyclassviewqueryiconassetpdfflashcard
3.2 Data movement and publishing domains
importexportpublish
3.3 Customization, AI, and extensibility domains
settingsthemepluginaiyoutubezoteroserverstorage
ai is a reserved owner for future built-in AI feature UI. Current AI-related
settings copy may still live under settings.ai/*.
3.4 Account, cloud, and security domains
accountsynccollaborationencryption
3.5 Support, diagnostics, and lifecycle domains
onboardinghelpbug-reportshellprofilerupdaterdeeplink
Product domain boundaries
Use this table to resolve common conflicts.
| Namespace | Owns | Does not own |
|---|---|---|
graph |
Graph lifecycle, graph switching, graph-level state, graph visualization entry points | Individual pages, blocks, raw files |
file |
Raw file browser, file metadata, file-level errors | Graph switching, page semantics, import workflow |
page |
Page metadata and page-level workflows | Active editing mechanics |
block |
Block as stored content entity | Active editing session behavior |
node |
Generic node vocabulary intentionally shared across page/block/tag/property-like entities | Page-only, block-only, property-only, or class-only workflows |
editor |
Active authoring behavior: selection, cursor actions, paste, heading changes, inline creation | Command registry text, page metadata, property schema |
journal |
Journal-only behavior | Generic page behavior |
library |
Library page copy and library-specific add/remove flows | Generic page search or generic page metadata outside the library feature |
date |
Relative-date vocabulary, natural-language date phrases, and date-only labeling/parsing copy | Journal-only workflows, editor command registries |
reference |
Backlinks, linked references, block refs, page refs | Generic search or editing text |
property |
Property schema, values, choices, dialogs, validation | Query semantics and result presentation |
class |
Class/tag schema and class-specific configuration | Generic property schema |
query |
Query definition, query source, query inputs, live-query semantics | Table sorting, grouping, row selection |
view |
Result presentation, table controls, grouping, sorting, columns, selection, representation modes | Query source semantics or property schema |
icon |
Icon picker, emoji/icon browsing, icon-search tabs and counts | Generic search vocabulary or generic select/picker wording outside icon picking |
asset |
Attachments and embedded media assets | Generic file browser or export |
pdf |
PDF viewer and PDF-specific reading/annotation behavior | Generic asset browsing or generic reference behavior |
flashcard |
Card review, card study flow, card-specific review UI | Generic editor actions or generic query/view controls |
import |
Import workflows, import source parsing, import options, and import-specific validation/feedback | Export, publish, or generic file browser wording |
export |
Export workflows, export format/options, export progress, and export-specific feedback | Import flows, publish lifecycle, or generic file browser wording |
publish |
Publish and unpublish flows, publish access settings, and publish status/failure messages | Generic export formats/backups, sync state, or account identity |
settings |
Built-in settings shell copy: settings sections, built-in setting labels, descriptions, and feedback about changing built-in settings | Child feature workflows or subsystem state merely rendered inside settings |
theme |
Theme selection and theme-specific customization | Generic settings |
plugin |
Plugin lifecycle, marketplace, plugin configuration, install/update/remove flows | Built-in settings or theme selection |
ai |
Semantic search, embedding model selection, model download states, and other built-in AI feature UI | Generic settings scaffolding or non-AI search vocabulary |
youtube |
YouTube-specific embed and timestamp behavior | Generic asset/video wording or generic mobile warnings |
zotero |
Built-in Zotero integration, Zotero attachment access, Zotero-linked or imported file affordances, and Zotero-specific defaults | Generic file browser, generic import/export, or plugin lifecycle |
server |
Local HTTP API, MCP, local server setup and diagnostics | Cloud sync or account identity |
storage |
Local persistence, sqlite/local-db storage errors, recycle UI and recycle storage constraints | Cloud sync lifecycle or file browser UI |
account |
Login, identity, plan, membership, billing-facing account state, and account-authentication actions such as resetting the account password | Graph sync state or passwords/keys that gate encrypted data |
sync |
Graph sync, storage usage, invitations, remote graph lifecycle | Login identity or password management |
collaboration |
Collaborators, participants, collaboration-only permissions and presence | Generic sync storage accounting |
encryption |
Passwords, keypairs, encrypted graph access, and key reset flows for encrypted data | Login/billing identity state or account-authentication actions |
onboarding |
First-run setup and initial import/graph setup | General settings or ongoing help |
help |
Help hub copy: documentation, handbook, shortcut help, and community/support entry points | Child workflows launched from help, such as bug-reporting |
bug-report |
Bug reporting, diagnostics, issue helpers | General help navigation |
shell |
Built-in shell command runner UI and its workflow | Built-in command descriptions or generic terminal wording outside the shell runner feature |
profiler |
Built-in profiling and diagnostics UI for developers or advanced users | Bug reporting copy, generic settings, or runtime performance logs |
updater |
App-release update lifecycle: checking, availability, download/install progress, restart/install actions, and updater-specific errors/status | Settings-shell copy, plugin-update UI, or other container/entry-point copy |
deeplink |
logseq:// or deep-link open flows and deep-link resolution errors |
Generic navigation labels or route names |
Product domain conflict rules
When multiple product domains could plausibly own the same text, apply these rules in order:
- Choose the narrowest stable owner that names the feature, entity, integration, or workflow itself.
- Container or hub owners own only their own shell copy. They do not own child feature copy just because it is rendered there.
- Status, progress, result, validation, and error copy belongs to the subsystem or workflow emitting that state.
- Render location, launch point, or current screen does not determine owner.
- If the same feature text can appear in multiple places, keep one feature-owned key instead of forking container-specific duplicates.
Conflict examples:
:settings.general/check-for-updates
:updater/checking-for-updates
:help.shortcuts/title
:bug-report.inspector/title
More product-domain examples:
:page/delete
:page.validation/name-no-hash
:page.convert/cant-be-block
:editor/remove-heading
:editor.slash/node-reference
:date.nlp/today
:node/built-in-cant-delete-error
:property/default-value
:view.table/sort-ascending
:plugin/install
:settings.editor/show-brackets
:sync/invitation-sent
:encryption/reset-password
:bug-report.inspector/title
4. Shell Surfaces
Use only when the meaning depends on the shell surface itself.
| Namespace | Use for |
|---|---|
header |
Header-only actions and labels |
sidebar.left |
Left sidebar shell affordances |
sidebar.right |
Right sidebar shell affordances |
context-menu |
Context menu-only affordances |
window |
Window chrome actions |
Use this class when:
- moving the text to another surface would change its meaning
- the text describes pane controls, sidebar controls, or window chrome
- the text is not reused in another surface or runtime with the same meaning
Do not use a surface namespace for:
- feature titles rendered inside a surface
- route or destination labels rendered inside a surface
- domain workflows that happen to be launched from a surface
- text that already appears with the same meaning in another surface; move it to
ui,nav, or the feature owner
Examples:
:header/go-back
:sidebar.left/favorites
:sidebar.right/close
:context-menu/set-icon
:window/minimize
5. Platform Runtimes
Use only when the text exists because one runtime has a unique implementation.
| Namespace | Use for |
|---|---|
mobile |
Mobile-only runtime behavior |
electron |
Electron-only runtime behavior |
Use this class when:
- the workflow exists only on one runtime
- the wording refers to a native/runtime-only capability
Examples:
:mobile.tab/graphs
:mobile.settings/version
:electron/new-window
:electron/add-to-dictionary
Step 2: Apply the Decision Tree
Choose the first matching branch and stop.
- Is the text owned by an interaction system? Use
command.*,shortcut.category,keymap, orcmdk. - Is the text a shared primitive reused across unrelated domains? Use
ui,nav,notification,search,select,format, orcolor. - Is the text owned by a product domain? Use the matching product domain
namespace. If multiple product domains seem possible, apply
Product domain conflict rulesand then stop. - Is the text owned by a shell surface? Use
header,sidebar.left,sidebar.right,context-menu, orwindow. - Is the text runtime-exclusive? Use
mobileorelectron.
If none fits, define a new product domain only when the feature has a clear, long-lived product boundary. Otherwise, keep the nearest existing product domain and use a more specific leaf.
Owner Constraints
- Do not create roots from implementation modules or component files such as
outliner,content, orviews. - Do not create plural owner roots such as
flashcardsorviews. Use the singular owner. - Do not use implementation acronyms such as
e2eewhen the product-facing owner isencryption. - Do not use implementation state holders such as
stateas owners. Use the semantic feature owner such asjournal.default-query/*. - Do not use a surface owner for a destination label. Use
nav/*or the feature owner. - Do not use a container or hub owner such as
settingsorhelpfor child feature text just because the feature is rendered there. - Do not use a validator or storage engine as owner for a domain rule. Validation copy belongs to the constrained domain.
- Treat a new root with fewer than ~5 plausible near-term keys as a smell, not a goal. Small roots are acceptable only when they name a first-class product feature, entity, or integration with a clear independent boundary.
- When keeping a new root, update this taxonomy in the same change so the
standard stays aligned with
src/resources/dicts/en.edn. - Not every existing key in
en.ednis a good naming precedent. Prefer this standard even when some legacy keys remain unchanged for compatibility.
Examples:
:reference.filter/title
:help.handbook/title
:page.validation/name-blank
:property.choice/already-exists
:class.validation/extends-cycle
Established Namespace Notes
These namespaces already exist in en.edn and are acceptable patterns, but
they have specific reuse guidance.
| Namespace | Status | Guidance |
|---|---|---|
property.built-in, class.built-in |
Intentional | Stable built-in schema vocabularies under the property and class owners. |
block.macro, property.repeat-recur-unit |
Intentional | Stable representation/enum groups. This pattern is acceptable when the subdomain names a real user-facing concept. |
Step 3: Decide Whether a Subdomain Is Needed
Use a dotted subdomain only for one of these 4 cases.
1. Stable section
Examples:
:nav.all-pages/title
:settings/account
:settings.editor/show-brackets
2. Stable workflow
Examples:
:page.delete/confirm-title
:page.delete/warning
:page.delete/success
:plugin.install-from-file/title
:editor.slash/group-basic
3. Stable representation or mode
Examples:
:view.table/default-title
:view.table/sort-ascending
:mobile.toolbar/undo
:server.status/running
:cmdk.action/open
4. Stable validator, conversion, or settings section
Examples:
:page.validation/name-no-hash
:page.convert/cant-be-block
:property.choice/already-exists
:settings/account
:help.shortcuts/title
Rules:
- use
.validation/when the message is the direct result of a validation check, constraint violation, or failed precondition - use an existing workflow subdomain such as
.convert/or.delete/for workflow-specific actions, confirmations, and blockers - do not create a narrower workflow-variant subdomain when an existing workflow already owns the text
- for built-in settings tab labels, use flat keys such as
:settings/general; reserve:settings.<section>/*for copy inside that settings section - choose compact concept names for subdomains; do not copy a long UI label phrase into a subdomain when a shorter stable concept name exists
- prefer a flat key when the dotted subdomain would only contain one string for now
- if an owner already has a flat canonical key for the concept, prefer flat
role-suffixed siblings such as
about-titleorauto-update-check-feedbackover introducing a dotted subdomain just to add another role - a flat leaf with a structured suffix such as
about-titleorterms-titleis acceptable when the same owner already needs the base leaf such asaboutortermsfor a different role, and creating a dotted singleton namespace would be worse - create a new dotted subdomain only when at least one of these is true:
- the namespace already has sibling keys
- the workflow or section clearly needs multiple roles such as
title+desc,confirm-title+confirm-desc, orempty+empty-desc - the flat leaf would become less readable than the dotted form
- a prefix with 2 to 4 keys is often healthy; the main smell is a singleton
dotted subdomain shape such as
help.about/*orgraph.delete-local/*
Good examples:
:page.convert/tag-to-page-action
:page.convert/tag-to-page-confirm-desc
:property.validation/invalid-name
:help/about-title
:help/about
:graph/delete-local-confirm-desc
Do not use a subdomain for:
- component names
- implementation names
- generic layout slices
- words like
main,section,btn, ormodalwhen they are only implementation layout terms and not real user-facing modes, surfaces, or scopes
Bad shapes for new keys:
:<owner>.main/*:command.command/*
Step 4: Choose the Leaf
The leaf describes the text's role inside its owner.
1. Canonical labels
Use a bare subject or action when the text is the canonical label itself.
Examples:
:ui/save
:page/backlinks
:property/default-value
:plugin/install
2. Structured role suffixes
Use these suffixes consistently.
| Suffix | Use for |
|---|---|
title |
Panel, section, page, dialog, modal title |
desc |
Supporting description |
label |
Control, nav, picker, or form label when it is not the title |
prompt |
Short picker or flow prompt |
placeholder |
Input placeholder |
hint |
Short inline help |
tip |
Advice or explanatory tip |
tooltip |
Hover text |
empty |
Empty-state heading or label |
empty-desc |
Empty-state description |
confirm-title |
Confirmation title |
confirm-desc |
Confirmation body |
success |
Success feedback |
error |
Error feedback |
warning |
Warning feedback |
feedback |
Neutral or severity-agnostic feedback |
count |
Parameterized count text |
action |
Action label when a bare verb would be ambiguous |
Additional rules:
- use
desc, notdescription, for the textual role suffix - this does not ban the literal word
Descriptionwhen it is the product term being named - use
prompt, notmessage, for short chooser, picker, or action-sheet instructions - use
labelwhen the text prefixes an inline value such as an ID, date, or selected item - do not split one sentence into
*-prefixand*-suffixkeys; keep a single translation entry and insert links, shortcuts, or styled fragments with placeholders - use
errororwarningfor failure feedback; prefer*-erroror*-warningover*-failed - for success, error, and warning feedback, prefer an action or condition stem
such as
update-success,unpublish-error, orinvalid-date-warninginstead of past-tense English likeupdatedorfailed - do not mechanically shorten a leaf just because one word also appears in the
owner; keep the action or condition name when it distinguishes a workflow or
condition inside that owner, for example
:publish/publish-error,:import/zip-import-error, or:date/invalid-date-warning - use
feedbackwhen the same toast or callout may appear with varying severity, or when the severity is incidental to the wording - use
statusonly when the text names a status field, status value, or status representation in the product model; do not usestatusas a catch-all suffix for post-action toasts - when the base concept is already a fixed product term, keep it intact even if
the role suffix repeats an English word, for example
:ui/error-boundary-error
Examples:
:help.shortcuts/label
:graph.switch/select-prompt
:nav.all-pages/title
:server.config/port-label
:graph/delete-local-confirm-desc
:plugin/auto-update-check-feedback
:property/update-success
:publish/unpublish-error
:plugin.install-from-file/success
:graph.switch/empty-desc
:page.convert/tag-to-page-action
Use error or warning for failure feedback, based on the feedback severity
shown in the UI. Do not use failure as a leaf.
Reuse Rules
Do not reuse a key only because the English text matches.
Reuse a key only when both are the same:
- semantic owner
- textual role
Examples:
- toolbar
"Bold"and command"Bold"are different keys - dialog
"Close"and window"Close"are different keys "Copied!"may be shared only if it is intentionally a reusable cross-domain notification- settings-shell
"Check for updates"and updater-state"Checking for updates"are different keys because the owner differs
When two keys are truly duplicates:
- keep the key that already follows the standard
- deprecate the duplicate key
- do not merge keys when one message carries extra workflow or domain-specific detail
Naming Workflow
For every new string:
- Identify the owner with the decision tree.
- Choose the root namespace from the owner taxonomy.
- Add a subdomain only if the string belongs to a stable section, workflow, or representation.
- Choose the leaf from the role rules.
- Search
src/resources/dicts/en.ednfor an existing key with the same owner and role. - Reuse only on exact semantic match.
- If the new name would create a new root or a singleton dotted subdomain, justify why convergence would be worse without it.
- Add the English source text to
src/resources/dicts/en.edn. - After editing dict files, run
bb lang:format-dicts.
Canonical Examples
| Need | Correct key |
|---|---|
| Generic dialog close button | :ui/close |
| Header back button tooltip | :header/go-back |
| Window close button | :window/close |
| Graph local deletion confirmation body | :graph/delete-local-confirm-desc |
| Page name validation error | :page.validation/name-no-hash |
Active editor action "Remove heading" |
:editor/remove-heading |
| Built-in node delete validation | :node/built-in-cant-delete-error |
| Property name input placeholder | :property/name-placeholder |
| Recycle item restore action | :storage.recycle/restore |
| Recycle page deletion metadata | :storage.recycle/page-deleted-at |
| Graph switch picker prompt | :graph.switch/select-prompt |
| Export copied page data feedback | :export/page-data-copied |
| Live query table title | :view.table/live-query-title |
| Table sort ascending action | :view.table/sort-ascending |
| Plugin install-from-file success | :plugin.install-from-file/success |
| Command palette open action | :cmdk.action/open |
| Mobile-only graph tab | :mobile.tab/graphs |
| Server running status | :server.status/running |