Configuration

Tip

The configuration format is still evolving. Backwards compatibility is maintained where possible, but some keys may change between minor versions. Check the release notes when upgrading.

Language Check is configured via a .languagecheck.yaml file in your workspace root. YAML is preferred; .languagecheck.yml and .languagecheck.json are also supported.

Where the config is read from

Language Check reads one config per workspace, from the root of the workspace folder. It does not search up or down from the file being checked, so the closest config to a file is not necessarily the one that applies to it.

  • Only the root counts. A .languagecheck.yaml in a subfolder is not read, not even for the files inside that subfolder, and nothing from it is merged into the root config. To treat part of a project differently, use exclude, the per-language settings and rules in the root config.

  • One file wins, with no merging. If more than one config sits at the root, .languagecheck.yaml is used, then .languagecheck.yml, then .languagecheck.json. The others are ignored completely.

  • Multi-root workspaces use the config of the first folder, for every folder.

  • The command line reads the config from the directory it runs in. language-check check docs/ from the repository root uses the root config; running the same check from inside docs/ uses the config there, if there is one.

This is the model pyright and mypy use (one config per run), not the nearest-config-wins model of Prettier or ESLint’s flat config.

The editor says so when you open a config that is not in effect: a nested one, or one shadowed by a higher-precedence name at the root. It shows a single information note naming the file that is read, and none of the gutter status marks the config in effect gets.

Engines

Control which checking engines are active:

engines:
  # Bool shorthand — just enable/disable:
  harper: true
  languagetool: false

  # Nested config — engine-specific settings:
  harper:
    enabled: true
    dialect: "American"       # American | British | Canadian | Australian | Indian
    linters:
      LongSentences: false    # Disable specific Harper rules

  languagetool:
    enabled: true
    url: "http://localhost:8010"
    level: "picky"            # "default" or "picky" (stricter rules)
    mother_tongue: "de-DE"    # For false-friends detection
    disabled_rules:
      - WHITESPACE_RULE
    enabled_rules: []
    disabled_categories: []
    enabled_categories: []
    max_concurrent_requests: 8  # In-flight /v2/check requests; 1 = serial

  vale:
    enabled: true
    config: ".vale.ini"       # Path to Vale config (auto-detected if omitted)

  proselint:
    enabled: false
    config: "proselint.json"  # Path to proselint config (auto-detected if omitted)

  spell_language: "en-US"     # BCP-47 tag for checking language

Both bool shorthand (harper: true) and nested config (harper: { enabled: true, dialect: "British" }) are supported. Use the shorthand when you only need to toggle an engine; use the nested form when you need engine-specific settings.

Note

Harper is built in and requires no external installation. LanguageTool, Vale, and Proselint are external tools that must be installed separately and available on your $PATH. See the dedicated setup guides:

The inspector health tab in VS Code shows whether each engine’s binary was detected and provides install instructions when missing.

Enabled engines run one after another and their diagnostics overlay. (Each engine may work concurrently inside its own turn — LanguageTool overlaps its requests — but the engines themselves are sequential.)

An engine is skipped when it does not handle the language being checked, or the markup the document is written in. Harper is English-only; an external provider or WASM plugin declares both through languages and extensions, and declaring neither means it takes everything. spell_language is the document default — a passage that declares its own language is checked in that one instead, and each language group goes only to the engines that claim it.

When two engines report the same rule over the same byte range, the reports are merged into one: the highest severity wins, and the suggestions are taken round-robin, one from each engine in turn, so the top of the list is every engine’s best guess before any engine’s tail. Spans that differ even by a byte are left as separate diagnostics.

Rule Overrides

Change severity or disable individual rules:

rules:
  spelling.typo:
    severity: warning    # error | warning | info | hint | off
  grammar.article:
    severity: "off"      # Disable this rule entirely

Exclude Patterns

Glob patterns to skip during workspace checking:

exclude:
  - "node_modules/**"
  - ".git/**"
  - "vendor/**"
  - "*.min.js"

Auto-Fix Rules

Define custom find-and-replace rules applied automatically:

auto_fix:
  - find: "teh"
    replace: "the"
    description: "Fix common typo"
  - find: "colour"
    replace: "color"
    context: "American"    # Only apply when "American" appears in the text

Performance

Tune performance for large workspaces:

performance:
  high_performance_mode: false  # Only use Harper (skip LT and externals)
  debounce_ms: 300              # LSP debounce delay
  max_file_size: 1048576        # Skip files larger than 1MB (0 = unlimited)

Dictionaries

Five domain wordlists ship with the checker — software-terms, typescript, companies, jargon and mathematics — and all of them load by default. Turn off the ones you do not want by name:

dictionaries:
  disabled: [companies]           # keep the rest
  paths:
    - .languagecheck/project-terms.txt

Set bundled: false to switch all five off at once. See Configuration Schema for the full field reference and how hyphenated words are matched.

Morphology

One dictionary entry covers the words built on it, so you do not have to add algebra, algebras, subalgebra and algebraicity separately. This is on by default:

morphology:
  enabled: true                 # accept prefixed and derived forms
  inflections: true             # accept plurals and regular verb forms

Prefixed and derived forms are recognized by taking the flagged word apart and requiring what is left to be a real word — subalgebra yes, subxyzzy no. Inflections are generated instead, because the spelling rules only work forwards: occur plus -ed is occurred, and a checker that stripped -ed from occured would quietly accept a typo.

Misspellings stay visible because the engine’s own correction overrides the analysis: if it can suggest something one edit away from what you typed, you still get a squiggle. See Configuration Schema.

Full Example

Here’s a .languagecheck.yaml putting it all together:

engines:
  harper:
    enabled: true
    dialect: "American"
  languagetool:
    enabled: true
    url: "http://localhost:8010"
    level: "default"
  vale:
    enabled: true
    config: ".vale.ini"
  proselint: false
  spell_language: "en-US"
  external:
    - name: custom-checker
      command: ./my-checker
      extensions: [md, rst]

rules:
  spelling.typo:
    severity: warning
  grammar.article:
    severity: "off"

exclude:
  - "node_modules/**"
  - ".git/**"

auto_fix:
  - find: "teh"
    replace: "the"

performance:
  high_performance_mode: false
  debounce_ms: 300

dictionaries:
  disabled: [companies]
  paths:
    - .languagecheck/project-terms.txt