Leantime/leantime · info · RuntimeException

Plugin %s is not installed

Error message

Plugin %s is not installed

What it means

This warning is emitted by the artisan command `translations:check-unused` (app/Command/CheckTranslations.php:134). The command parses app/Language/en-US.ini into a key list, then greps every .php/.blade.php/.tpl.php/.html/.js file under app/ (excluding vendor/node_modules/.git/storage/cache) for literal usages of each key. Any key whose literal string never appears is reported as unused and counted via sprintf('Found %d unused translations:', ...). It is a hygiene report, not a runtime failure — but it is prone to false positives because it only matches literal key text.

Source

Thrown at app/Command/DisablePluginCommand.php:38

{
    /**
     * {@inheritdoc}
     */
    protected function configure(): void
    {
        $this->addArgument('plugin', InputArgument::REQUIRED, 'The plugin name');
    }

    /**
     * {@inheritdoc}
     */
    protected function executeCommand(): int
    {
        $name = $this->input->getArgument('plugin');
        $plugin = $this->getPlugin($name);

        if (! isset($plugin->id)) {
            throw new RuntimeException(sprintf('Plugin %s is not installed', $plugin->name));
        }

        if (! $plugin->enabled) {
            throw new RuntimeException(sprintf('Plugin %s is not enabled', $plugin->name));
        }

        if (! $this->confirm(sprintf('Disable plugin %s', $plugin->name))) {
            return Command::SUCCESS;
        }

        return $this->plugins->disablePlugin($plugin->id) ? Command::SUCCESS : Command::FAILURE;
    }
}

View on GitHub (pinned to 9a9f49f100)

Solutions

  1. Re-run with export to review the full list offline: `php bin/leantime translations:check-unused --export=storage/unused-translations.json`.
  2. Manually verify each reported key is truly dynamic before deleting: `rg -n "<key>" app/ public/ --glob '!vendor'` including JS assets built into public/dist.
  3. For dynamically-composed keys (e.g. `status_' . $status`), add the base prefix to an allowlist in your review process instead of deleting, or refactor the call sites to use a constant key so the scanner can see it.
  4. Delete genuinely dead keys from app/Language/en-US.ini (and the mirrored keys in other language INI files) in a dedicated commit.
  5. If keys are used only by a plugin, move them into the plugin's own language file rather than core en-US.ini.

Example fix

// before — dynamic key, scanner reports 'todo.status_3' as unused
$label = __('todo.status_' . $ticket['status']);

// after — explicit key the scanner (and readers) can see
$statusKeyMap = [1 => 'todo.status_open', 2 => 'todo.status_progress', 3 => 'todo.status_done'];
$label = __($statusKeyMap[$ticket['status']] ?? 'todo.status_unknown');
Defensive patterns

Strategy: validation

Validate before calling

// Before deleting a reported key, validate it is not used dynamically or in built assets
$key = 'todo.status_3';
$usedInCode = \Illuminate\Support\Facades\Process::run(
    ['grep', '-rq', $key, app_path(), public_path('dist')]
)->successful();
$usedDynamically = \Illuminate\Support\Facades\Process::run(
    ['grep', '-rq', "'todo.status_'", app_path()]
)->successful(); // dynamic base prefix found -> keep the family of keys
if (! $usedInCode && ! $usedDynamically) { /* safe to remove from en-US.ini */ }

Prevention

When it happens

Trigger: Running `php bin/leantime translations:check-unused` when: (1) a translation key is referenced dynamically (key built by string concatenation or `__($type . '.title')`) so the literal never appears in source; (2) the key is only used inside app/Plugins (scanned separately), tests, or excluded directories; (3) a feature was removed and its language keys were never pruned from en-US.ini; (4) the key is used in a file type the Finder does not include (it chains multiple ->name() calls, which the Symfony Finder treats as an OR of patterns per-name, so some patterns may not all apply as expected).

Common situations: Post-refactor cleanup after the .tpl.php → Blade migration (old template keys orphaned); after canvas-variant consolidation into the Blueprints domain left legacy keys behind; after deleting legacy REST API controllers that had their own labels; before a release when trimming the ~thousand-key INI files; export via `--export=unused.json` for bulk review.

Related errors


AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21). Data as JSON: /api/errors/83b303bdf2ead527. Report an issue: GitHub.