['native' => 'Deutsch', 'name' => 'Deutsch', 'og' => 'de_DE'], 'en' => ['native' => 'English', 'name' => 'Englisch', 'og' => 'en_US'], 'fr' => ['native' => 'Français', 'name' => 'Französisch', 'og' => 'fr_FR'], 'it' => ['native' => 'Italiano', 'name' => 'Italienisch', 'og' => 'it_IT'], 'es' => ['native' => 'Español', 'name' => 'Spanisch', 'og' => 'es_ES'], 'nl' => ['native' => 'Nederlands', 'name' => 'Niederländisch', 'og' => 'nl_NL'], 'pl' => ['native' => 'Polski', 'name' => 'Polnisch', 'og' => 'pl_PL'], 'tr' => ['native' => 'Türkçe', 'name' => 'Türkisch', 'og' => 'tr_TR'], 'ru' => ['native' => 'Русский', 'name' => 'Russisch', 'og' => 'ru_RU'], 'ar' => ['native' => 'العربية', 'name' => 'Arabisch', 'og' => 'ar_AR'], ]; /** Sprachen, die von rechts nach links geschrieben werden. */ public const RTL = ['ar']; /** Mehr Sprachen als diese braucht keine Handwerkerseite, und jede kostet Pflege. */ public const MAX_EXTRA = 5; private ?string $main = null; public function __construct(private Connection $db, private SiteSettings $settings) { } /** Die Hauptsprache: die Sprache, in der Seiten gebaut werden. */ public function main(): string { if (null !== $this->main) { return $this->main; } try { $locale = $this->db->fetchOne('SELECT default_locale FROM website WHERE id = ?', ['default']); } catch (\Doctrine\DBAL\Exception) { $locale = false; } return $this->main = is_string($locale) && isset(self::CATALOG[$locale]) ? $locale : 'de'; } /** * Die weiteren Sprachen, in der eingestellten Reihenfolge. Leer bei einer einsprachigen * Website - und dann zeigt weder der Editor noch die Website etwas davon. * * @return list */ public function extra(): array { $stored = $this->settings->load()['languages'] ?? []; $main = $this->main(); return array_values(array_filter(is_array($stored) ? $stored : [], static fn (mixed $code): bool => is_string($code) && isset(self::CATALOG[$code]) && $code !== $main)); } /** @return list Hauptsprache zuerst */ public function all(): array { return [$this->main(), ...$this->extra()]; } public function isExtra(string $code): bool { return in_array($code, $this->extra(), true); } /** Für den Editor und die Einstellungen: Kennung, Name und Name in der Sprache selbst. */ public function describe(string $code): array { return ['code' => $code, 'name' => self::CATALOG[$code]['name'] ?? $code, 'native' => self::CATALOG[$code]['native'] ?? $code, 'rtl' => in_array($code, self::RTL, true)]; } public static function ogLocale(string $code): string { return self::CATALOG[$code]['og'] ?? $code; } /** * Legt die weiteren Sprachen fest. * * Eine Sprache wird zum ersten Teil der Adresse (/en/...). Gibt es schon eine Seite, die * genau so heißt, wäre sie danach nicht mehr erreichbar - das wird abgelehnt statt still * hingenommen. Eine entfernte Sprache verliert ihre Übersetzungen nicht: sie bleiben in den * Seiten liegen und sind wieder da, wenn die Sprache zurückkommt. * * @param list $codes */ public function save(array $codes): void { $main = $this->main(); $clean = []; foreach ($codes as $code) { $code = strtolower(trim((string) $code)); if ('' === $code || $code === $main) { continue; } if (!isset(self::CATALOG[$code])) { throw new InvalidPage(sprintf('Die Sprache „%s“ steht nicht zur Wahl.', $code)); } $clean[$code] = true; } $clean = array_keys($clean); if (count($clean) > self::MAX_EXTRA) { throw new InvalidPage(sprintf('Mehr als %d weitere Sprachen sind nicht vorgesehen.', self::MAX_EXTRA)); } foreach ($clean as $code) { if (in_array($code, PageRepository::RESERVED_SLUGS, true)) { continue; } $taken = $this->db->fetchOne('SELECT path FROM page WHERE website_id = ? AND (path = ? OR path LIKE ?) AND deleted_at IS NULL', ['default', $code, $code.'/%']); if (false !== $taken) { throw new InvalidPage(sprintf('Die Seite /%s belegt die Adresse, unter der %s erscheinen würde. Benenne sie zuerst um.', $code, self::CATALOG[$code]['name'])); } } $this->settings->saveLanguages($clean); } }