/languages/.yaml` and * `user/plugins//languages.yaml` (single-file multi-lang format), buckets * keys by enabled-vs-disabled provenance, and returns the keys present only in * the disabled bucket. Keys also contributed by an enabled plugin are kept — * the enabled plugin owns them, even if a disabled plugin happens to ship the * same key. * * The result is cached per-language for the request lifecycle since the * underlying YAML files don't change mid-request. */ final class DisabledPluginLangIndex { /** @var array> */ private array $cache = []; public function __construct(private readonly Grav $grav) { } /** * @return array flat translation keys (e.g. `PLUGIN_ADMIN.ADD_FOLDER`) */ public function disabledOnlyKeys(string $lang): array { if (isset($this->cache[$lang])) { return $this->cache[$lang]; } $plugins = $this->grav['plugins']; $config = $this->grav['config']; $locator = $this->grav['locator']; $enabled = []; $disabled = []; foreach ($plugins as $plugin) { $name = $plugin->name; $resolved = $locator->findResource("plugin://{$name}"); if (!$resolved || !is_dir($resolved)) { continue; } $keys = $this->collectPluginLangKeys($resolved, $lang); if (empty($keys)) { continue; } $isEnabled = (bool) $config->get("plugins.{$name}.enabled", false); foreach ($keys as $k) { if ($isEnabled) { $enabled[$k] = true; } else { $disabled[$k] = true; } } } $result = array_keys(array_diff_key($disabled, $enabled)); $this->cache[$lang] = $result; return $result; } /** * True if `$key` is contributed only by disabled plugins for `$lang`. */ public function isDisabledOnly(string $key, string $lang): bool { return in_array($key, $this->disabledOnlyKeys($lang), true); } /** * @return array */ private function collectPluginLangKeys(string $pluginDir, string $lang): array { $keys = []; $perLangFile = "{$pluginDir}/languages/{$lang}.yaml"; if (is_file($perLangFile)) { $data = $this->safeParseYaml($perLangFile); if (is_array($data)) { foreach (array_keys(Utils::arrayFlattenDotNotation($data)) as $k) { $keys[$k] = true; } } } $singleFile = "{$pluginDir}/languages.yaml"; if (is_file($singleFile)) { $data = $this->safeParseYaml($singleFile); $langData = is_array($data) ? ($data[$lang] ?? null) : null; if (is_array($langData)) { foreach (array_keys(Utils::arrayFlattenDotNotation($langData)) as $k) { $keys[$k] = true; } } } return array_keys($keys); } private function safeParseYaml(string $file): mixed { try { return Yaml::parse(file_get_contents($file)); } catch (\Throwable) { return null; } } }