https://github.com/logseq/logseq-i18n-lint
AST-based i18n linting for Clojure/ClojureScript projects.
https://github.com/logseq/logseq-i18n-lint
Last synced: 22 days ago
JSON representation
AST-based i18n linting for Clojure/ClojureScript projects.
- Host: GitHub
- URL: https://github.com/logseq/logseq-i18n-lint
- Owner: logseq
- License: mit
- Created: 2026-04-15T02:54:14.000Z (about 1 month ago)
- Default Branch: master
- Last Pushed: 2026-04-17T04:38:00.000Z (about 1 month ago)
- Last Synced: 2026-04-17T06:29:01.613Z (about 1 month ago)
- Language: Rust
- Size: 92.8 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# logseq-i18n-lint
AST-level i18n checks for hardcoded UI strings, unused translation keys, and missing translation keys in Clojure/ClojureScript source code.
## Overview
`logseq-i18n-lint` analyzes Clojure/ClojureScript source files at the AST level to find hardcoded UI strings that should be internationalized. Unlike regex-based approaches, it understands the code structure — hiccup vectors, function calls, attribute maps — to accurately detect strings that are displayed to users.
## Features
- **AST-level analysis** — Custom Clojure/ClojureScript parser, not regex matching
- **8 detection categories** — hiccup-text, hiccup-attr, alert-text, str-concat, format-string, conditional-text, fn-arg-text, let-text
- **Unused key detection** — Find translation keys defined in dictionaries but never referenced in code
- **Missing key detection** — Find translation keys referenced in code but absent from the dictionary
- **Auto-fix** — Remove unused keys from all dictionary files with `--fix`
- **DB-ident key derivation** — Automatically resolves built-in property/class keys from db-ident definitions
- **Configurable exclusions** — Allow lists, regex patterns, ignore context functions
- **Git integration** — Check only changed files with `--git-changed`
- **Parallel processing** — Uses rayon for fast multi-file analysis
- **Cross-platform** — Prebuilt binaries for Windows, macOS, Linux (x64 & ARM64)
- **Two output formats** — Aligned table with Unicode support, or compact for CI
## Installation
### Download prebuilt binary
Download from [Releases](https://github.com/logseq/logseq-i18n-lint/releases/latest) for your platform.
### Build from source
```bash
cargo install --git https://github.com/logseq/logseq-i18n-lint
```
Or clone and build:
```bash
git clone https://github.com/logseq/logseq-i18n-lint
cd logseq-i18n-lint
cargo build --release
```
## Quick Start
```bash
# Run lint on the project root
logseq-i18n-lint lint
# Use a custom config
logseq-i18n-lint -c .i18n-lint.toml lint
# Check only git-changed files
logseq-i18n-lint lint --git-changed
# Compact output for CI
logseq-i18n-lint lint -f compact
# Warn only (exit 0 even if issues found)
logseq-i18n-lint lint --warn-only
# Check for unused translation keys
logseq-i18n-lint check-keys
# Remove unused keys from all dictionaries
logseq-i18n-lint check-keys --fix
# Check for keys used in code but missing from the dictionary
logseq-i18n-lint check-missing
```
> **Note:** The configuration flag `-c` is a global flag and must come **before** the subcommand:
> `logseq-i18n-lint -c .i18n-lint.toml lint`
## Configuration
Create `.i18n-lint.toml` in your project root. If not present, built-in defaults are used.
```toml
# Path to the project root, relative to the directory that contains the
# executable. Resolution is always based on the executable location, so
# the result is the same no matter which directory you run the binary from.
# Leave empty when the executable is placed at the project root;
# set to ".." when it lives in a subdirectory such as bin/.
project_root = ""
# Shared settings used by both subcommands
include_dirs = ["src"]
file_extensions = ["clj", "cljs", "cljc"]
# Translation functions — calls to these are never flagged
i18n_functions = ["t", "i18n/t"]
# Alert/notification functions — first keyword arg is a translation key
alert_functions = ["notification/show!"]
# UI component functions — keyword args are translation keys
ui_functions = []
ui_namespaces = []
# Hiccup attribute names whose string values are flagged
ui_attributes = ["placeholder", "title", "aria-label", "alt", "label"]
# ── lint settings ──────────────────────────────────────────────────────────────
[lint]
exclude_patterns = ["**/test/**", "**/node_modules/**"]
text_preview_length = 60
allow_strings = ["Logseq"]
allow_patterns = ["^https?://"]
exception_functions = ["throw"]
pure_functions = []
format_functions = ["format", "goog.string/format"]
ignore_context_functions = [
"js/console.log",
"log/debug",
"prn",
"re-pattern",
"ns",
]
# ── check-keys settings ────────────────────────────────────────────────────────
# Both `check-keys` and `check-missing` read from this section.
# check-missing only uses: primary_dict, always_used_key_patterns, ignore_key_namespaces.
[check-keys]
dicts_dir = "src/resources/dicts"
primary_dict = "src/resources/dicts/en.edn"
always_used_key_patterns = ["^:command\\."]
ignore_key_namespaces = []
translation_key_attributes = ["i18n-key", "prompt-key", "title-key"]
# Built-in db-ident definitions (one entry per source file)
[[check-keys.db_ident_defs]]
file = "deps/db/src/logseq/db/frontend/property.cljs"
def = "built-in-properties"
[[check-keys.db_ident_defs]]
file = "deps/db/src/logseq/db/frontend/class.cljs"
def = "built-in-classes"
```
## CLI Reference
```
logseq-i18n-lint [GLOBAL_OPTIONS] [COMMAND_OPTIONS]
Commands:
lint Detect hardcoded UI strings
check-keys Check for unused translation keys (defined in dictionary but not used in code)
check-missing Check for missing translation keys (used in code but not defined in dictionary)
Global Options:
-c, --config Configuration file path [default: .i18n-lint.toml]
-v, --verbose Verbose output
-h, --help Print help
-V, --version Print version
lint Options:
-f, --format Output format: table|compact [default: table]
-w, --warn-only Warn only, do not exit with error code
-g, --git-changed Only check git-changed files (include_dirs and exclude_patterns still apply)
check-keys Options:
--fix Remove unused keys from all dictionary files
```
## Detection Categories
| Type | Description | Example |
|------|-------------|---------|
| `hiccup-text` | Direct text children of hiccup vectors | `[:div "Hello"]` |
| `hiccup-attr` | UI text in hiccup attributes | `{:placeholder "Search..."}` |
| `fn-arg-text` | UI function string arguments | `(ui/button "Submit")` |
| `str-concat` | String concatenation in UI context | `(str "Error: " msg)` |
| `conditional-text` | Text in conditionals in UI context | `(if x "Yes" "No")` |
| `format-string` | Format strings in UI context | `(goog.string/format "Found %d")` |
| `let-text` | let binding in UI context | `(let [x "Untitled"] [:div x])` |
| `alert-text` | Alert/notification messages | `(notification/show! "Done")` |
> **Deep dive:** See [docs/hardcoded-string-detection.md](docs/hardcoded-string-detection.md) for how each
> rule works, automatic skip filters, known limitations, and configuration tips.
>
> For unused and missing key detection, see [docs/unused-key-detection.md](docs/unused-key-detection.md).
## Output Formats
### Table mode (default)
```
┌──────────────────┬─────────────────────────┬──────┬────────────────────────────┐
│ Type │ File │ Line │ Text │
├──────────────────┼─────────────────────────┼──────┼────────────────────────────┤
│ hiccup-text │ src/frontend/editor.cljs│ 48 │ "No matched commands" │
│ hiccup-attr │ src/frontend/search.cljs│ 92 │ "Search pages..." │
└──────────────────┴─────────────────────────┴──────┴────────────────────────────┘
Found 2 hardcoded strings in 317 files (hiccup-text: 1, hiccup-attr: 1)
```
### Compact mode
```
[hiccup-text] src/frontend/editor.cljs:48 "No matched commands"
[hiccup-attr] src/frontend/search.cljs:92 "Search pages..."
Found 2 hardcoded strings in 317 files (hiccup-text: 1, hiccup-attr: 1)
```
## Development
```bash
# Build
cargo build
# Run tests
cargo test
# Run clippy
cargo clippy
# Run benchmarks
cargo bench
# Build release
cargo build --release
```
## License
MIT