v1.0.0.0
StocketBase
- <?php
-
- declare(strict_types=1);
-
- require_once __DIR__ . '/config.php';
- require_once __DIR__ . '/database.php';
- require_once __DIR__ . '/site-front.php';
-
- final class Languages
- {
- public const CODE_PATTERN = '/^[a-z]{2,3}(?:-[a-z]{2,4})?$/';
-
- private const TRANSLATION_FIELDS = ['title', 'excerpt', 'content_blocks', 'seo_title', 'seo_description'];
-
- private static ?string $current = null;
- private static ?array $rows = null;
- private static array $stringCache = [];
-
- public static function enabled(): bool
- {
- return (SiteFront::settings()['multilang_enabled'] ?? '0') === '1';
- }
-
- public static function isValidCode(string $code): bool
- {
- return preg_match(self::CODE_PATTERN, $code) === 1;
- }
-
- public static function defaultCode(): string
- {
- $code = strtolower(trim((string) (SiteFront::settings()['multilang_default_code'] ?? 'en')));
-
- return self::isValidCode($code) ? $code : 'en';
- }
-
- public static function defaultName(): string
- {
- $name = trim((string) (SiteFront::settings()['multilang_default_name'] ?? ''));
-
- return $name !== '' ? $name : 'English';
- }
-
- public static function all(): array
- {
- if (self::$rows !== null) {
- return self::$rows;
- }
-
- try {
- self::$rows = Database::site()->query('SELECT code, name, sort_order, is_active FROM site_languages ORDER BY sort_order ASC, code ASC')->fetchAll();
- } catch (PDOException $e) {
- self::$rows = [];
- }
-
- return self::$rows;
- }
-
- public static function active(): array
- {
- return array_values(array_filter(self::all(), static fn(array $row): bool => (int) $row['is_active'] === 1));
- }
-
- public static function activeCodes(): array
- {
- return array_column(self::active(), 'code');
- }
-
- public static function find(string $code): ?array
- {
- foreach (self::all() as $row) {
- if ($row['code'] === $code) {
- return $row;
- }
- }
-
- return null;
- }
-
- public static function resetCache(): void
- {
- self::$rows = null;
- self::$current = null;
- self::$stringCache = [];
- }
-
- public static function current(): string
- {
- if (self::$current !== null) {
- return self::$current;
- }
-
- self::$current = '';
- if (!self::enabled()) {
- return '';
- }
-
- $requested = strtolower(trim((string) ($_GET['stocketbase_lang'] ?? '')));
- if ($requested !== '' && self::isValidCode($requested) && in_array($requested, self::activeCodes(), true)) {
- self::$current = $requested;
- }
-
- return self::$current;
- }
-
- public static function basePath(): string
- {
- return rtrim((string) Config::get('APP_BASE_PATH', ''), '/');
- }
-
- public static function prefix(): string
- {
- $code = self::current();
-
- return $code === '' ? '' : '/' . $code;
- }
-
- public static function htmlLang(): string
- {
- $code = self::current();
-
- return $code !== '' ? $code : (self::enabled() ? self::defaultCode() : 'en');
- }
-
- public static function homeUrl(string $code): string
- {
- return self::basePath() . ($code === '' ? '/' : '/' . $code . '/');
- }
-
- public static function switchUrl(string $code): string
- {
- $uri = (string) ($_SERVER['REQUEST_URI'] ?? '/');
- $path = (string) (parse_url($uri, PHP_URL_PATH) ?? '/');
- $query = (string) (parse_url($uri, PHP_URL_QUERY) ?? '');
-
- $base = self::basePath();
- if ($base !== '' && str_starts_with($path, $base)) {
- $path = substr($path, strlen($base));
- }
-
- $current = self::current();
- if ($current !== '' && preg_match('#^/' . preg_quote($current, '#') . '(?=/|$)#', $path) === 1) {
- $path = substr($path, strlen($current) + 1);
- }
-
- if ($path === '' || $path[0] !== '/') {
- $path = '/' . $path;
- }
-
- $target = $code === '' ? $path : '/' . $code . ($path === '/' ? '/' : $path);
-
- return $base . $target . ($query !== '' ? '?' . $query : '');
- }
-
- public static function options(): array
- {
- $options = [['code' => '', 'name' => self::defaultName(), 'hreflang' => self::defaultCode()]];
- foreach (self::active() as $row) {
- $options[] = ['code' => $row['code'], 'name' => $row['name'], 'hreflang' => $row['code']];
- }
-
- return $options;
- }
-
- public static function absolutizeUrls(string $html): string
- {
- if (self::current() === '') {
- return $html;
- }
-
- $base = self::basePath();
- $html = preg_replace('~(\s(?:src|href|poster|data-src)=["\'])(?:\./)?(media/|assets/)~i', '$1' . $base . '/$2', $html) ?? $html;
-
- return preg_replace('~(url\(\s*["\']?)(?:\./)?(media/|assets/)~i', '$1' . $base . '/$2', $html) ?? $html;
- }
-
- public static function renderAlternates(): string
- {
- if (!self::enabled() || empty(self::active())) {
- return '';
- }
-
- $origin = rtrim((string) Config::get('APP_URL', ''), '/');
- $html = '';
- foreach (self::options() as $option) {
- $html .= '<link rel="alternate" hreflang="' . htmlspecialchars($option['hreflang'], ENT_QUOTES) . '" href="' . htmlspecialchars($origin . self::switchUrl($option['code']), ENT_QUOTES) . '">' . "\n";
- }
- $html .= '<link rel="alternate" hreflang="x-default" href="' . htmlspecialchars($origin . self::switchUrl(''), ENT_QUOTES) . '">' . "\n";
-
- return $html;
- }
-
- public static function renderSwitcher(): string
- {
- $settings = SiteFront::settings();
- if (!self::enabled() || ($settings['multilang_switcher'] ?? '1') !== '1' || empty(self::active())) {
- return '';
- }
-
- $current = self::current();
- $label = Language::get('language_switcher_label', 'Language');
- $options = self::options();
- $currentName = self::defaultName();
- foreach ($options as $option) {
- if ($option['code'] === $current) {
- $currentName = $option['name'];
- }
- }
-
- $html = '<details class="site-language-switcher" data-language-switcher>'
- . '<summary aria-label="' . htmlspecialchars($label, ENT_QUOTES) . '"><span>' . htmlspecialchars($currentName) . '</span>'
- . '<svg class="icon icon-sm site-language-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9l6 6 6-6"></path></svg></summary>'
- . '<ul class="site-language-menu">';
- foreach ($options as $option) {
- $isCurrent = $option['code'] === $current;
- $html .= '<li><a href="' . htmlspecialchars(self::switchUrl($option['code']), ENT_QUOTES) . '" hreflang="' . htmlspecialchars($option['hreflang'], ENT_QUOTES) . '" lang="' . htmlspecialchars($option['hreflang'], ENT_QUOTES) . '"'
- . ($isCurrent ? ' aria-current="true" class="is-current"' : '') . '>' . htmlspecialchars($option['name']) . '</a></li>';
- }
-
- return $html . '</ul></details>';
- }
-
- public static function stringsPath(string $code): string
- {
- return dirname(__DIR__) . '/translations/language-' . $code . '.php';
- }
-
- public static function strings(string $code): array
- {
- if (!self::isValidCode($code)) {
- return [];
- }
-
- if (isset(self::$stringCache[$code])) {
- return self::$stringCache[$code];
- }
-
- $path = self::stringsPath($code);
- $loaded = is_file($path) ? require $path : [];
- self::$stringCache[$code] = is_array($loaded) ? $loaded : [];
-
- return self::$stringCache[$code];
- }
-
- public static function saveStrings(string $code, array $strings): bool
- {
- if (!self::isValidCode($code)) {
- return false;
- }
-
- $base = Language::base();
- $clean = [];
- foreach ($base as $key => $unused) {
- $value = trim(strip_tags((string) ($strings[$key] ?? '')));
- if ($value !== '') {
- $clean[$key] = $value;
- }
- }
-
- $export = "<?php\n\ndeclare(strict_types=1);\n\nreturn [\n";
- foreach ($clean as $key => $value) {
- $export .= ' ' . var_export($key, true) . ' => ' . var_export($value, true) . ",\n";
- }
- $export .= "];\n";
-
- $path = self::stringsPath($code);
- $temporary = $path . '.tmp' . bin2hex(random_bytes(4));
- if (file_put_contents($temporary, $export) === false) {
- return false;
- }
-
- if (!@rename($temporary, $path)) {
- @unlink($temporary);
-
- return false;
- }
-
- unset(self::$stringCache[$code]);
-
- return true;
- }
-
- public static function copyMissingStrings(string $code): int
- {
- if (!self::isValidCode($code)) {
- return 0;
- }
-
- $base = Language::base();
- $current = self::strings($code);
- $added = 0;
- foreach ($base as $key => $value) {
- if (!isset($current[$key]) || trim((string) $current[$key]) === '') {
- $current[$key] = $value;
- $added++;
- }
- }
-
- if ($added > 0 && !self::saveStrings($code, $current)) {
- return 0;
- }
-
- return $added;
- }
-
- public static function missingStringCount(string $code): int
- {
- $current = self::strings($code);
- $missing = 0;
- foreach (Language::base() as $key => $unused) {
- if (!isset($current[$key]) || trim((string) $current[$key]) === '') {
- $missing++;
- }
- }
-
- return $missing;
- }
-
- public static function addLanguage(string $code, string $name): ?string
- {
- $code = strtolower(trim($code));
- $name = trim(strip_tags($name));
-
- if (!self::isValidCode($code)) {
- return 'Language code must be 2-3 letters, optionally with a region such as pt-br.';
- }
- if ($name === '' || mb_strlen($name) > 60) {
- return 'Language name is required (up to 60 characters).';
- }
- if ($code === self::defaultCode()) {
- return 'This is already the original language of the site.';
- }
- if (self::find($code) !== null) {
- return 'This language already exists.';
- }
-
- $order = count(self::all()) + 1;
- $statement = Database::site()->prepare('INSERT INTO site_languages (code, name, sort_order, is_active) VALUES (:code, :name, :sort, 0)');
- $statement->execute(['code' => $code, 'name' => $name, 'sort' => $order]);
- self::resetCache();
-
- return null;
- }
-
- public static function setSetting(string $key, string $value): void
- {
- $statement = Database::site()->prepare(
- 'INSERT INTO site_settings (setting_key, setting_value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)'
- );
- $statement->execute(['key' => $key, 'value' => $value]);
- }
-
- public static function translation(string $type, int $id, string $code): ?array
- {
- try {
- $statement = Database::site()->prepare(
- 'SELECT * FROM content_translations WHERE entity_type = :type AND entity_id = :id AND lang = :lang LIMIT 1'
- );
- $statement->execute(['type' => $type, 'id' => $id, 'lang' => $code]);
- } catch (PDOException $e) {
- return null;
- }
-
- return $statement->fetch() ?: null;
- }
-
- public static function translatedIds(string $type, string $code): array
- {
- try {
- $statement = Database::site()->prepare(
- "SELECT entity_id FROM content_translations WHERE entity_type = :type AND lang = :lang AND title <> ''"
- );
- $statement->execute(['type' => $type, 'lang' => $code]);
- } catch (PDOException $e) {
- return [];
- }
-
- return array_map('intval', $statement->fetchAll(PDO::FETCH_COLUMN));
- }
-
- public static function saveTranslation(string $type, int $id, string $code, array $fields): void
- {
- $values = [];
- foreach (self::TRANSLATION_FIELDS as $field) {
- $values[$field] = (string) ($fields[$field] ?? '');
- }
-
- $statement = Database::site()->prepare(
- 'INSERT INTO content_translations (entity_type, entity_id, lang, title, excerpt, content_blocks, seo_title, seo_description)
- VALUES (:type, :id, :lang, :title, :excerpt, :content_blocks, :seo_title, :seo_description)
- ON DUPLICATE KEY UPDATE title = VALUES(title), excerpt = VALUES(excerpt), content_blocks = VALUES(content_blocks),
- seo_title = VALUES(seo_title), seo_description = VALUES(seo_description)'
- );
- $statement->execute(['type' => $type, 'id' => $id, 'lang' => $code] + $values);
- }
-
- public static function deleteTranslation(string $type, int $id, string $code): void
- {
- $statement = Database::site()->prepare('DELETE FROM content_translations WHERE entity_type = :type AND entity_id = :id AND lang = :lang');
- $statement->execute(['type' => $type, 'id' => $id, 'lang' => $code]);
- }
-
- private static function batch(string $type, array $ids, string $code): array
- {
- $ids = array_values(array_unique(array_filter(array_map('intval', $ids))));
- if (empty($ids)) {
- return [];
- }
-
- try {
- $statement = Database::site()->prepare(
- 'SELECT * FROM content_translations WHERE entity_type = ? AND lang = ? AND entity_id IN (' . implode(',', array_fill(0, count($ids), '?')) . ')'
- );
- $statement->execute(array_merge([$type, $code], $ids));
- } catch (PDOException $e) {
- return [];
- }
-
- $map = [];
- foreach ($statement->fetchAll() as $row) {
- $map[(int) $row['entity_id']] = $row;
- }
-
- return $map;
- }
-
- private static function overlay(array $row, ?array $translation, array $fieldMap): array
- {
- if ($translation === null) {
- return $row;
- }
-
- foreach ($fieldMap as $column => $translationField) {
- $value = (string) ($translation[$translationField] ?? '');
- if (trim($value) === '') {
- continue;
- }
-
- if ($translationField === 'content_blocks') {
- $decoded = json_decode($value, true);
- if (!is_array($decoded) || empty($decoded['blocks'])) {
- continue;
- }
- }
-
- $row[$column] = $value;
- }
-
- return $row;
- }
-
- public static function articles(array $rows): array
- {
- $code = self::current();
- if ($code === '' || empty($rows)) {
- return $rows;
- }
-
- $map = self::batch('article', array_column($rows, 'id'), $code);
- $categoryIds = array_column($rows, 'category_id');
- $categoryMap = self::batch('category', $categoryIds, $code);
- $fieldMap = ['title' => 'title', 'excerpt' => 'excerpt', 'content_blocks' => 'content_blocks', 'seo_title' => 'seo_title', 'seo_description' => 'seo_description'];
-
- foreach ($rows as $index => $row) {
- if (!isset($row['id'])) {
- continue;
- }
- $rows[$index] = self::overlay($row, $map[(int) $row['id']] ?? null, $fieldMap);
- if (!empty($row['category_id']) && isset($categoryMap[(int) $row['category_id']]) && array_key_exists('category_name', $row)) {
- $categoryTitle = trim((string) $categoryMap[(int) $row['category_id']]['title']);
- if ($categoryTitle !== '') {
- $rows[$index]['category_name'] = $categoryTitle;
- }
- }
- }
-
- return $rows;
- }
-
- public static function article(array $row): array
- {
- return self::articles([$row])[0];
- }
-
- public static function hasTranslation(string $type, int $id): bool
- {
- $code = self::current();
- if ($code === '') {
- return false;
- }
-
- $translation = self::batch($type, [$id], $code)[$id] ?? null;
-
- return $translation !== null && trim((string) $translation['title']) !== '';
- }
-
- public static function categories(array $rows, string $nameKey = 'name', string $descriptionKey = 'description'): array
- {
- $code = self::current();
- if ($code === '' || empty($rows)) {
- return $rows;
- }
-
- $map = self::batch('category', array_column($rows, 'id'), $code);
- foreach ($rows as $index => $row) {
- $rows[$index] = self::overlay($row, $map[(int) ($row['id'] ?? 0)] ?? null, [$nameKey => 'title', $descriptionKey => 'excerpt']);
- }
-
- return $rows;
- }
-
- public static function pages(array $rows): array
- {
- $code = self::current();
- if ($code === '' || empty($rows)) {
- return $rows;
- }
-
- $map = self::batch('page', array_column($rows, 'id'), $code);
- foreach ($rows as $index => $row) {
- $rows[$index] = self::overlay($row, $map[(int) ($row['id'] ?? 0)] ?? null, ['title' => 'title', 'content_blocks' => 'content_blocks', 'seo_title' => 'seo_title', 'seo_description' => 'seo_description']);
- }
-
- return $rows;
- }
-
- public static function applySiteTexts(array $settings): array
- {
- $code = self::current();
- if ($code === '') {
- return $settings;
- }
-
- $row = self::translation('site', 0, $code);
- if ($row === null) {
- return $settings;
- }
-
- foreach (['site_name' => 'title', 'site_description' => 'excerpt', 'seo_title' => 'seo_title', 'seo_description' => 'seo_description'] as $key => $field) {
- $value = trim((string) ($row[$field] ?? ''));
- if ($value !== '') {
- $settings[$key] = $value;
- }
- }
-
- return $settings;
- }
-
- public static function titles(string $type, array $ids): array
- {
- $code = self::current();
- if ($code === '' || empty($ids)) {
- return [];
- }
-
- $titles = [];
- foreach (self::batch($type, $ids, $code) as $id => $row) {
- $title = trim((string) $row['title']);
- if ($title !== '') {
- $titles[$id] = $title;
- }
- }
-
- return $titles;
- }
-
- public static function footerColumns(array $columns): array
- {
- $code = self::current();
- if ($code === '' || empty($columns)) {
- return $columns;
- }
-
- $row = self::translation('footer', 0, $code);
- if ($row === null) {
- return $columns;
- }
-
- $decoded = json_decode((string) $row['content_blocks'], true);
- $translated = is_array($decoded) && is_array($decoded['columns'] ?? null) ? $decoded['columns'] : [];
-
- foreach ($columns as $index => $column) {
- $entry = $translated[$index] ?? null;
- if (!is_array($entry)) {
- continue;
- }
-
- $title = trim((string) ($entry['title'] ?? ''));
- if ($title !== '') {
- $columns[$index]['title'] = $title;
- }
-
- foreach (($column['links'] ?? []) as $linkIndex => $link) {
- $label = trim((string) ($entry['links'][$linkIndex] ?? ''));
- if ($label !== '') {
- $columns[$index]['links'][$linkIndex]['label'] = $label;
- }
- }
- }
-
- return $columns;
- }
-
- public static function navOriginals(): array
- {
- try {
- $rows = Database::site()->query('SELECT id, item_type, ref_id, label FROM nav_items ORDER BY sort_order ASC, id ASC')->fetchAll();
- } catch (PDOException $e) {
- return [];
- }
-
- $originals = [];
- foreach ($rows as $row) {
- $label = trim((string) ($row['label'] ?? ''));
- if ($label === '' && in_array($row['item_type'], ['category', 'page'], true)) {
- $lookup = Database::site()->prepare($row['item_type'] === 'category' ? 'SELECT name FROM categories WHERE id = :id' : 'SELECT title FROM pages WHERE id = :id');
- $lookup->execute(['id' => (int) $row['ref_id']]);
- $label = trim((string) $lookup->fetchColumn());
- }
- if ($label !== '') {
- $originals[(int) $row['id']] = $label;
- }
- }
-
- return $originals;
- }
-
- public static function siteOriginals(): array
- {
- require_once __DIR__ . '/footer-links.php';
-
- $settings = Database::site()->query('SELECT setting_key, setting_value FROM site_settings')->fetchAll(PDO::FETCH_KEY_PAIR);
- $columns = [];
- foreach (FooterLinks::decodeColumns((string) ($settings['footer_columns'] ?? '[]')) as $column) {
- $columns[] = [
- 'title' => (string) ($column['title'] ?? ''),
- 'links' => array_map(static fn($link): string => (string) ($link['label'] ?? ''), $column['links'] ?? []),
- ];
- }
-
- return [
- 'title' => (string) ($settings['site_name'] ?? Config::get('APP_NAME', 'StocketBase')),
- 'excerpt' => (string) ($settings['site_description'] ?? ''),
- 'seo_title' => (string) ($settings['seo_title'] ?? ''),
- 'seo_description' => (string) ($settings['seo_description'] ?? ''),
- 'nav' => self::navOriginals(),
- 'footer' => $columns,
- ];
- }
-
- public static function siteValues(string $code): array
- {
- $site = self::translation('site', 0, $code) ?? [];
- $nav = [];
- foreach (array_keys(self::navOriginals()) as $id) {
- $row = self::translation('nav', $id, $code);
- $nav[$id] = $row !== null ? (string) $row['title'] : '';
- }
-
- $footerRow = self::translation('footer', 0, $code);
- $decoded = $footerRow !== null ? json_decode((string) $footerRow['content_blocks'], true) : null;
-
- return [
- 'title' => (string) ($site['title'] ?? ''),
- 'excerpt' => (string) ($site['excerpt'] ?? ''),
- 'seo_title' => (string) ($site['seo_title'] ?? ''),
- 'seo_description' => (string) ($site['seo_description'] ?? ''),
- 'nav' => $nav,
- 'footer' => is_array($decoded) && is_array($decoded['columns'] ?? null) ? $decoded['columns'] : [],
- ];
- }
-
- public static function saveSiteValues(string $code, array $values): void
- {
- $title = mb_substr(trim((string) ($values['title'] ?? '')), 0, 255);
- $excerpt = trim((string) ($values['excerpt'] ?? ''));
- $seoTitle = mb_substr(trim((string) ($values['seo_title'] ?? '')), 0, 255);
- $seoDescription = mb_substr(trim((string) ($values['seo_description'] ?? '')), 0, 500);
-
- if ($title === '' && $excerpt === '' && $seoTitle === '' && $seoDescription === '') {
- self::deleteTranslation('site', 0, $code);
- } else {
- self::saveTranslation('site', 0, $code, ['title' => $title, 'excerpt' => $excerpt, 'content_blocks' => '', 'seo_title' => $seoTitle, 'seo_description' => $seoDescription]);
- }
-
- foreach (array_keys(self::navOriginals()) as $id) {
- $label = mb_substr(trim((string) ($values['nav'][$id] ?? '')), 0, 255);
- if ($label === '') {
- self::deleteTranslation('nav', $id, $code);
- } else {
- self::saveTranslation('nav', $id, $code, ['title' => $label]);
- }
- }
-
- $columns = [];
- $hasFooter = false;
- foreach (self::siteOriginals()['footer'] as $index => $original) {
- $columnTitle = mb_substr(trim((string) ($values['footer'][$index]['title'] ?? '')), 0, 120);
- $links = [];
- foreach (array_keys($original['links']) as $linkIndex) {
- $links[$linkIndex] = mb_substr(trim((string) ($values['footer'][$index]['links'][$linkIndex] ?? '')), 0, 120);
- $hasFooter = $hasFooter || $links[$linkIndex] !== '';
- }
- $hasFooter = $hasFooter || $columnTitle !== '';
- $columns[$index] = ['title' => $columnTitle, 'links' => $links];
- }
-
- if ($hasFooter) {
- self::saveTranslation('footer', 0, $code, ['title' => 'footer', 'content_blocks' => (string) json_encode(['columns' => $columns], JSON_UNESCAPED_UNICODE)]);
- } else {
- self::deleteTranslation('footer', 0, $code);
- }
- }
-
- public static function copySiteMissing(string $code): int
- {
- $originals = self::siteOriginals();
- $values = self::siteValues($code);
- $added = 0;
-
- foreach (['title', 'excerpt', 'seo_title', 'seo_description'] as $field) {
- if (trim($values[$field]) === '' && trim($originals[$field]) !== '') {
- $values[$field] = $originals[$field];
- $added++;
- }
- }
-
- foreach ($originals['nav'] as $id => $label) {
- if (trim((string) ($values['nav'][$id] ?? '')) === '') {
- $values['nav'][$id] = $label;
- $added++;
- }
- }
-
- foreach ($originals['footer'] as $index => $column) {
- if (trim((string) ($values['footer'][$index]['title'] ?? '')) === '' && trim($column['title']) !== '') {
- $values['footer'][$index]['title'] = $column['title'];
- $added++;
- }
- foreach ($column['links'] as $linkIndex => $label) {
- if (trim((string) ($values['footer'][$index]['links'][$linkIndex] ?? '')) === '' && trim($label) !== '') {
- $values['footer'][$index]['links'][$linkIndex] = $label;
- $added++;
- }
- }
- }
-
- if ($added > 0) {
- self::saveSiteValues($code, $values);
- }
-
- return $added;
- }
-
- public static function onlyTranslated(array $rows): array
- {
- if (!self::requiresTranslation() || empty($rows)) {
- return $rows;
- }
-
- $translated = self::batch('article', array_column($rows, 'id'), self::current());
-
- return array_values(array_filter($rows, static fn(array $row): bool => isset($translated[(int) ($row['id'] ?? 0)]) && trim((string) $translated[(int) $row['id']]['title']) !== ''));
- }
-
- public static function requiresTranslation(): bool
- {
- return self::current() !== '';
- }
- }
-