Logseq i18n Skill
When This Skill Applies
- Adding or editing user-facing strings in shipped UI
- Replacing hardcoded UI text with translations
- Adding, renaming, deduplicating, or removing keys in
src/resources/dicts/
- Reviewing code for i18n compliance
- Editing
notification/show! calls or translatable UI attributes
- Updating i18n tooling, docs, or lint configuration
Read These First
docs/i18n-key-naming.md for key ownership, reuse, and naming
.i18n-lint.toml for lint scope, covered helpers/attributes, exclusions,
and allowlists
src/main/frontend/context/i18n.cljs for the translation helper APIs
Use docs/contributing-to-translations.md only when the task is specifically
about locale contribution workflow.
Scope Rules
.i18n-lint.toml is the source of truth for which files and APIs are checked
for hardcoded UI text.
- Inside that scope, all shipped user-facing UI text must be internationalized.
- Console output does not need i18n. Keep out-of-scope developer-only
(Dev)
labels inline in code/config, not in translation dictionaries.
- If you introduce a new UI helper, alert API, translatable attribute, UI
namespace, or shipped surface, update
.i18n-lint.toml so lint coverage
stays accurate.
Use These Helpers
All translation helpers live in frontend.context.i18n.
| Helper |
Use for |
t |
Standard translation with preferred locale |
tt |
Try multiple keys and return the first existing translation |
t-en |
Force English text when UI output also needs English console/debug output |
interpolate-rich-text / interpolate-rich-text-node |
Replace placeholders with rich-text or hiccup fragments |
interpolate-sentence |
Keep a full sentence in one key while inserting placeholders and inline links |
replace-newlines-with-br |
Render translated newline characters as [:br] nodes |
locale-join-rich-text / locale-join-rich-text-node |
Join rich fragments with locale-aware separators |
locale-format-number / locale-format-date / locale-format-time |
Locale-aware formatting for dynamic values before translation |
Do not introduce parallel i18n helpers elsewhere unless the change also updates
the shared i18n API deliberately.
Core Rules
Rule 1: No hardcoded shipped UI text
If the text is user-facing and in .i18n-lint.toml scope, hardcoded literals in
buttons, labels, placeholders, tooltips, dialogs, notifications, empty states,
and similar UI are a bug.
Rule 2: Reuse keys by meaning, not by English text
Search src/resources/dicts/en.edn first. Reuse a key only when both match:
- semantic owner
- textual role
If the English text matches but the meaning differs, create a new key and follow
docs/i18n-key-naming.md.
Rule 3: English source lives in en.edn
- Add new English source text to
src/resources/dicts/en.edn.
- When introducing a new key for the first time, you must also add the
Simplified Chinese (
zh-CN) translation in the same change. English and
zh-CN are the two required locales for any new key.
- Add other non-English entries only when you are also providing actual translations.
- When renaming or removing keys, update affected locale files so stale keys do
not remain behind.
- Do not copy English into non-English locale files just to fill gaps. Tongue
falls back to
:en.
Rule 4: Keep complete sentences together
- Prefer one translation entry per complete sentence or message.
- Do not split rich text or linked text across multiple keys.
- Use
interpolate-sentence or interpolate-rich-text* when markup and word
order must stay together.
Rule 5: Prefer placeholders for plain dynamic text
Use placeholder strings like {1} and {2} for plain dynamic text. Format
arguments in the caller before passing them to t.
Rule 6: Function-valued translations are restricted
Use function values only when:
- the locale needs real logic such as conditional/plural behavior, or
- the translation must return hiccup rich text
When function values are necessary, only these are allowed inside the function
body:
Rule 7: Locale details matter
- Preserve emoji and icon glyphs from
en.edn exactly.
- Use punctuation natural to the locale.
- Pluralization is locale-specific. Do not force English singular/plural logic
onto every language.
Workflow
When adding or changing user-facing text:
- Use
.i18n-lint.toml to confirm the text is in i18n scope.
- Search
src/resources/dicts/en.edn for an exact semantic match.
- If no exact match exists, name the key with
docs/i18n-key-naming.md.
- If the naming guide still does not yield one clear key, stop and ask for
human guidance instead of guessing.
- Add or update the English source text in
en.edn.
- Replace the literal with the appropriate helper from
frontend.context.i18n.
- Add/update locale translations only where actual translations are being
supplied.
- If you introduced a new linted helper/attribute/surface, update
.i18n-lint.toml.
Validation
After changing keys:
bb lang:validate-translations
After changing shipped UI text:
bb lang:lint-hardcoded --git-changed
After editing dictionary files:
bb lang:format-dicts
bb lang:format-dicts is the canonical repo formatter for dictionary key
ordering and namespace spacing.
Common Mistakes
| Mistake |
Fix |
| Hardcoded UI string in a linted UI surface |
Move it into en.edn and use a helper from frontend.context.i18n |
| Reusing a key only because the English text matches |
Reuse only on exact semantic owner + role match |
| Copying English into non-English locale files |
Leave the key missing unless you are adding a real translation |
Using (fn ...) for plain placeholder text |
Use "..." with {1}, {2}, ... |
| Splitting one sentence across multiple keys |
Keep a single translation entry and interpolate into it |
Adding a new linted helper but not updating .i18n-lint.toml |
Extend the TOML config in the same change |
Editing dict files without running bb lang:format-dicts |
Run the formatter before finishing |
1---2name: logseq-i18n3description: Logseq i18n workflow for adding, renaming, reviewing, or editing translation keys and user-facing strings. Use when: writing UI code with hardcoded text, adding new user-facing strings, editing translation dict files, reviewing i18n compliance, working with notification/show!, adding translatable UI attributes, or any task involving src/resources/dicts/. Also use when the user mentions i18n, translation, localization, or hardcoded strings.4---56# Logseq i18n Skill78## When This Skill Applies910- Adding or editing user-facing strings in shipped UI11- Replacing hardcoded UI text with translations12- Adding, renaming, deduplicating, or removing keys in `src/resources/dicts/`13- Reviewing code for i18n compliance14- Editing `notification/show!` calls or translatable UI attributes15- Updating i18n tooling, docs, or lint configuration1617## Read These First18191. `docs/i18n-key-naming.md` for key ownership, reuse, and naming202. `.i18n-lint.toml` for lint scope, covered helpers/attributes, exclusions,21 and allowlists223. `src/main/frontend/context/i18n.cljs` for the translation helper APIs2324Use `docs/contributing-to-translations.md` only when the task is specifically25about locale contribution workflow.2627## Scope Rules2829- `.i18n-lint.toml` is the source of truth for which files and APIs are checked30 for hardcoded UI text.31- Inside that scope, all shipped user-facing UI text must be internationalized.32- Console output does not need i18n. Keep out-of-scope developer-only `(Dev)`33 labels inline in code/config, not in translation dictionaries.34- If you introduce a new UI helper, alert API, translatable attribute, UI35 namespace, or shipped surface, update `.i18n-lint.toml` so lint coverage36 stays accurate.3738## Use These Helpers3940All translation helpers live in `frontend.context.i18n`.4142| Helper | Use for |43|---|---|44| `t` | Standard translation with preferred locale |45| `tt` | Try multiple keys and return the first existing translation |46| `t-en` | Force English text when UI output also needs English console/debug output |47| `interpolate-rich-text` / `interpolate-rich-text-node` | Replace placeholders with rich-text or hiccup fragments |48| `interpolate-sentence` | Keep a full sentence in one key while inserting placeholders and inline links |49| `replace-newlines-with-br` | Render translated newline characters as `[:br]` nodes |50| `locale-join-rich-text` / `locale-join-rich-text-node` | Join rich fragments with locale-aware separators |51| `locale-format-number` / `locale-format-date` / `locale-format-time` | Locale-aware formatting for dynamic values before translation |5253Do not introduce parallel i18n helpers elsewhere unless the change also updates54the shared i18n API deliberately.5556## Core Rules5758### Rule 1: No hardcoded shipped UI text5960If the text is user-facing and in `.i18n-lint.toml` scope, hardcoded literals in61buttons, labels, placeholders, tooltips, dialogs, notifications, empty states,62and similar UI are a bug.6364### Rule 2: Reuse keys by meaning, not by English text6566Search `src/resources/dicts/en.edn` first. Reuse a key only when both match:6768- semantic owner69- textual role7071If the English text matches but the meaning differs, create a new key and follow72`docs/i18n-key-naming.md`.7374### Rule 3: English source lives in `en.edn`7576- Add new English source text to `src/resources/dicts/en.edn`.77- **When introducing a new key for the first time, you must also add the78 Simplified Chinese (`zh-CN`) translation in the same change.** English and79 `zh-CN` are the two required locales for any new key.80- Add other non-English entries only when you are also providing actual translations.81- When renaming or removing keys, update affected locale files so stale keys do82 not remain behind.83- Do not copy English into non-English locale files just to fill gaps. Tongue84 falls back to `:en`.8586### Rule 4: Keep complete sentences together8788- Prefer one translation entry per complete sentence or message.89- Do not split rich text or linked text across multiple keys.90- Use `interpolate-sentence` or `interpolate-rich-text*` when markup and word91 order must stay together.9293### Rule 5: Prefer placeholders for plain dynamic text9495Use placeholder strings like `{1}` and `{2}` for plain dynamic text. Format96arguments in the caller before passing them to `t`.9798### Rule 6: Function-valued translations are restricted99100Use function values only when:101102- the locale needs real logic such as conditional/plural behavior, or103- the translation must return hiccup rich text104105When function values are necessary, only these are allowed inside the function106body:107108- `str`109- `when`110- `if`111- `=`112113### Rule 7: Locale details matter114115- Preserve emoji and icon glyphs from `en.edn` exactly.116- Use punctuation natural to the locale.117- Pluralization is locale-specific. Do not force English singular/plural logic118 onto every language.119120## Workflow121122When adding or changing user-facing text:1231241. Use `.i18n-lint.toml` to confirm the text is in i18n scope.1252. Search `src/resources/dicts/en.edn` for an exact semantic match.1263. If no exact match exists, name the key with `docs/i18n-key-naming.md`.1274. If the naming guide still does not yield one clear key, stop and ask for128 human guidance instead of guessing.1295. Add or update the English source text in `en.edn`.1306. Replace the literal with the appropriate helper from131 `frontend.context.i18n`.1327. Add/update locale translations only where actual translations are being133 supplied.1348. If you introduced a new linted helper/attribute/surface, update135 `.i18n-lint.toml`.136137## Validation138139After changing keys:140141```bash142bb lang:validate-translations143```144145After changing shipped UI text:146147```bash148bb lang:lint-hardcoded --git-changed149```150151After editing dictionary files:152153```bash154bb lang:format-dicts155```156157`bb lang:format-dicts` is the canonical repo formatter for dictionary key158ordering and namespace spacing.159160## Common Mistakes161162| Mistake | Fix |163|---|---|164| Hardcoded UI string in a linted UI surface | Move it into `en.edn` and use a helper from `frontend.context.i18n` |165| Reusing a key only because the English text matches | Reuse only on exact semantic owner + role match |166| Copying English into non-English locale files | Leave the key missing unless you are adding a real translation |167| Using `(fn ...)` for plain placeholder text | Use `"..."` with `{1}`, `{2}`, ... |168| Splitting one sentence across multiple keys | Keep a single translation entry and interpolate into it |169| Adding a new linted helper but not updating `.i18n-lint.toml` | Extend the TOML config in the same change |170| Editing dict files without running `bb lang:format-dicts` | Run the formatter before finishing |