php-spellcheck / spellcheck-symfony-bundle
Spell check Symfony translations and PHP code in CI. Hunspell/Aspell backends, ICU aware, baseline for legacy projects.
Package info
github.com/php-spellcheck/spellcheck-symfony-bundle
Type:symfony-bundle
pkg:composer/php-spellcheck/spellcheck-symfony-bundle
Requires
- php: >=8.1
- php-spellcheck/spellcheck-core: ^1.0
- symfony/config: ^5.4 || ^6.4 || ^7.0 || ^8.0
- symfony/console: ^5.4 || ^6.4 || ^7.0 || ^8.0
- symfony/dependency-injection: ^5.4 || ^6.4 || ^7.0 || ^8.0
- symfony/http-kernel: ^5.4 || ^6.4 || ^7.0 || ^8.0
- symfony/translation: ^5.4 || ^6.4 || ^7.0 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3
- matthiasnoback/symfony-config-test: ^6.2
- matthiasnoback/symfony-dependency-injection-test: ^6.2
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^10.5 || ^11.5 || ^12.0
- symfony/cache: ^5.4 || ^6.4 || ^7.0 || ^8.0
- symfony/filesystem: ^5.4 || ^6.4 || ^7.0 || ^8.0
- symfony/framework-bundle: ^5.4 || ^6.4 || ^7.0 || ^8.0
- symfony/yaml: ^5.4 || ^6.4 || ^7.0 || ^8.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-11 06:42:59 UTC
README
A Symfony bundle that spell checks every translation catalogue with the dictionary of its own locale, and the PHP code of the project (class names, methods, properties, parameters, constants, docblocks, comments) — from the console, or as a failing build in CI.
It is the Symfony integration of
php-spellcheck/spellcheck-core,
the framework-agnostic engine.
- Symfony 5.4 LTS, 6.4 LTS, 7.x and 8.x — PHP 8.1 up to 8.5 (Symfony 8.x itself needs PHP ≥ 8.4)
- Backends: hunspell, aspell, ext-pspell, or the pure PHP
wordlistbackend, which needs no system binary - ICU aware: plural and select messages are expanded, only the textual branches are checked
- Placeholders, HTML, markdown code, URLs, e-mails and paths are stripped without losing the column in the original message
- Baseline for legacy projects: only new typos fail the build
- Report formats for humans, GitHub Actions annotations, GitLab Code Quality, JSON and JUnit
- PSR-6 cached, deterministic output, lazy pipeline
Table of contents
- Install
- Minimal configuration
- Commands
- Adopting on an existing project
- Translation sources
- What is checked, and what is not
- Suppressing
- CI
- Extending
- Troubleshooting
- FAQ
- Related projects
- Contributing
Install
composer require --dev php-spellcheck/spellcheck-symfony-bundle
Without Flex, register the bundle in dev and test only:
// config/bundles.php return [ // ... PHPSpellcheck\SpellcheckBundle\PHPSpellcheckBundle::class => ['dev' => true, 'test' => true], ];
Install a backend (recommended: hunspell plus the dictionaries of your locales):
sudo apt-get install hunspell hunspell-it hunspell-en-us
Then check the environment:
bin/console spellcheck:doctor
Minimal configuration
# config/packages/php_spellcheck.yaml php_spellcheck: dictionaries: - '%kernel.project_dir%/.spellcheck/project.txt' cache: pool: cache.php_spellcheck translations: paths: ['%kernel.project_dir%/translations'] code: paths: ['%kernel.project_dir%/src']
Glob patterns in the paths
translations.paths and code.paths accept glob patterns, with the Finder
syntax: a single * stops at the directory separator, ** crosses it.
php_spellcheck: translations: paths: - '%kernel.project_dir%/translations' # Every module that ships its own catalogues. - '%kernel.project_dir%/src/*/translations' # Every catalogue under src, at any depth. - '%kernel.project_dir%/src/**/*.yml' code: paths: ['%kernel.project_dir%/src/**/Entity']
Patterns are matched against the file system at every run, not when the
container is built, so a new directory does not need a cache clear. The walk
starts at the last segment without a wildcard, so keep that prefix as deep as
possible: %kernel.project_dir%/**/translations scans vendor/ and var/ too.
A pattern that matches nothing is dropped; if no path is left, the run reports a
diagnostic instead of failing. dictionaries takes plain files only.
# config/packages/cache.yaml framework: cache: pools: cache.php_spellcheck: adapter: cache.adapter.filesystem
A filesystem pool is preferable to a shared Redis one: the results depend on the dictionaries installed on that particular machine.
Commands
| Command | Purpose |
|---|---|
spellcheck |
Every enabled source |
spellcheck:translations |
Catalogues only, with --locale and --domain |
spellcheck:code |
PHP only, accepts explicit paths |
spellcheck:baseline |
Records the current issues, --merge, --prune, --dry-run |
spellcheck:doctor |
Diagnoses backends, dictionaries, locale mapping |
spellcheck:dictionary:add |
Adds words, keeping the file sorted |
spellcheck:debug:fragments |
Shows what the pipeline produces |
Shared options: --format, --no-suggestions, --no-baseline, --no-cache,
--fail-on-warning, --ignore-warnings, --report-outdated, --config-profile.
Exit codes: 0 clean, 1 new issues, 2 configuration or environment error,
3 warnings only.
Adopting on an existing project
The first run on a real project reports a lot. That is expected, and the answer is not to weaken the rules:
bin/console spellcheck:baseline git add .spellcheck/baseline.json && git commit -m "Spellcheck baseline"
From then on only regressions fail the build. Shrink the baseline over time by
moving legitimate terms into the project dictionary and fixing the rest. Add
--report-outdated in CI so the baseline cannot quietly grow forever.
Translation sources
Two mutually exclusive modes, translations.source:
files (default) reads the translation files of the project directly. It
knows which file and line every message comes from, and never sees vendor
catalogues.
translator reads every catalogue known to the Translator, vendor messages
included. Useful to audit everything the user can actually see, but
MessageCatalogue does not record which file a message came from, so lines are
best effort. This is also why exclude_domains defaults to
['validators', 'security'] in this mode.
Note on Symfony 5.4: catalogues are loaded explicitly, locale by locale.
getCatalogues() only returns what happens to be loaded already, and it is not
part of TranslatorBagInterface before 6.1.
What is checked, and what is not
Placeholders (%name%, {{ var }}, {name}, :param, %s), HTML, markdown
code, URLs, e-mails and paths are removed before checking, without losing the
position: the reported column points at the offset in the original message.
ICU messages are expanded and only the textual branches are checked:
{count, plural, one {Hai una mela} other {Hai # mele}} checks the two
sentences, not count, plural, one, other or #. Legacy pipe plurals are
split and their intervals stripped.
In PHP, variable and string_literal are not checked by default: local
variables are often deliberate abbreviations, and string literals contain SQL,
regexes and service ids. Enable them with code.check if you want the noise.
Suppressing
In code:
// @spellcheck-ignore-file // @spellcheck-ignore-next-line $x = 1; // @spellcheck-ignore-line /* @spellcheck-disable */ ... /* @spellcheck-enable */ // @spellcheck-words Kbps Mbps idempotency
In catalogues there is no inline suppression on purpose: translation files are read by translators, not by developers. Use the project dictionary or the baseline.
CI
# GitHub Actions - run: php bin/console spellcheck --format=github --no-interaction
# GitLab CI spellcheck: script: - php bin/console spellcheck --format=gitlab > gl-code-quality-report.json artifacts: reports: codequality: gl-code-quality-report.json
The GitLab fingerprint is deliberately the same one the baseline uses, so GitLab deduplicates across pipelines exactly as the baseline does across runs.
Extending
Six extension points, autoconfigured when the container has autoconfiguration enabled:
| Tag | Interface |
|---|---|
php_spellcheck.source |
SourceInterface |
php_spellcheck.processor |
TextProcessorInterface |
php_spellcheck.tokenizer |
TokenizerInterface |
php_spellcheck.speller |
SpellerInterface |
php_spellcheck.reporter |
ReporterInterface |
php_spellcheck.filter |
MisspellingFilterInterface |
A custom reporter's getName() becomes a valid --format value.
Troubleshooting
The run times out with hunspell. Set
backend_options.terse_mode: false. Some builds do not emit the terminating
blank line in terse mode.
No line number on translation issues. You are in translator mode; switch
to files, or accept the logical location.
It is slow. Check the cache ... % hit line in the summary. Use
--no-suggestions in CI, where nobody reads them.
A word is not reported and I don't know why. spellcheck:debug:fragments --grep=<word> shows the text after the pipeline and the extracted tokens.
FAQ
How do I spell check Symfony translation files? Point
translations.paths at the catalogue directories and run
bin/console spellcheck:translations. Each catalogue is checked against the
dictionary of its own locale, so messages.it.yaml is read as Italian and
messages.en.yaml as English, in the same run.
Does it work without hunspell or aspell installed? Yes. Set
backend: wordlist and give it a word list: the pure PHP backend needs no
system binary, which is the usual choice on a locked down CI image.
Can it check PHP identifiers and comments, not only strings? Yes, that is
what spellcheck:code does. variable and string_literal are off by default
because local variables are often deliberate abbreviations and string literals
carry SQL, regexes and service ids; enable them with code.check.
How do I adopt it on a legacy project with thousands of typos? Record a
baseline with bin/console spellcheck:baseline and commit it. From then on
only regressions fail, and --report-outdated keeps the baseline from growing
silently.
Does it fail my pull request? Exit code 1 on new issues, 3 on warnings
only, 2 on a configuration or environment error. With
--format=github the issues show up as inline annotations on the diff.
Which locales are supported? Any locale for which a dictionary is
installed. spellcheck:doctor prints the mapping between your configured
locales and the dictionaries actually found on the machine.
Related projects
php-spellcheck/spellcheck-core— the engine: sources, processors, tokenizers, spellers, reporters, usable in any PHP project or framework.
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md for the local setup and the rules that are not negotiable, and SECURITY.md to report a vulnerability privately.
License
MIT © Raffaele Carelle.
