From cb9b87997993702131ca24d4d0e1fd45ef64805c Mon Sep 17 00:00:00 2001 From: Jonas Kohl Date: Sun, 15 Sep 2024 19:55:12 +0200 Subject: Begun i18n work & more --- .env.example | 4 + Dockerfile | 11 ++- src/application/i18n.php | 98 ++++++++++++++++++ src/application/messages/de.msg | 117 ++++++++++++++++++++++ src/application/messages/strings.mst | 111 +++++++++++++++++++++ src/application/mystic/forum/Database.php | 23 ++++- src/application/views/form_login.php | 2 +- src/application/views/form_register.php | 5 +- src/application/views/nav_guest.php | 4 +- src/index.php | 159 +++++++++++++++++++++--------- src/ui/site.css | 3 + 11 files changed, 477 insertions(+), 60 deletions(-) create mode 100644 src/application/i18n.php create mode 100644 src/application/messages/de.msg create mode 100644 src/application/messages/strings.mst diff --git a/.env.example b/.env.example index 0eba495..7682611 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,10 @@ +SECRET= POSTGRES_USER=postgres POSTGRES_DB=postgres POSTGRES_PASSWORD=postgres +MAILER_DSN= +MAILER_FROM= +PUBLIC_URL= #MYSTIC_FORUM_THEME=default #REGISTRATION_ENABLED=1 #MYSTIC_FORUM_TITLE= diff --git a/Dockerfile b/Dockerfile index 03f50b5..d422818 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,13 +10,18 @@ RUN apt update && apt install -y \ libjpeg62-turbo-dev \ libxmp-dev \ libfreetype6-dev \ - libpq-dev + libpq-dev \ + libicu-dev \ + ; RUN docker-php-ext-configure gd \ --with-webp \ --with-jpeg \ --with-freetype -RUN docker-php-ext-install gd zip pgsql -#RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer +RUN docker-php-ext-install gd zip pgsql intl +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer FROM base COPY ./000-default.conf /etc/apache2/sites-available/000-default.conf +#COPY ./src /var/www/html +#WORKDIR /var/www/html +#RUN composer install diff --git a/src/application/i18n.php b/src/application/i18n.php new file mode 100644 index 0000000..964439f --- /dev/null +++ b/src/application/i18n.php @@ -0,0 +1,98 @@ + + throw new Exception("i18n syntax error: $msg (in " . ($filename ?? "unknown") . ":" . ($line ?? 0) . ")"); + + $msgs = []; + $lines = explode("\n", $contents); + $currentId = ""; + $currentContext = ""; + $currentMessage = ""; + $isInMessage = false; + foreach ($lines as $i => $ln) { + $lnNum = $i + 1; + if (trim($ln) === "") + continue; + + switch ($ln[0]) { + case "#": + continue 2; + case ":": + if ($currentId !== "") { + if ($currentContext !== "") + $currentId = $currentContext . "\004" . $currentId; + $msgs[$currentId] ??= $currentMessage; + } + $currentId = json_decode(substr($ln, 2)); + $currentContext = ""; + $currentMessage = ""; + $isInMessage = false; + break; + case "=": + $isInMessage = true; + $currentMessage = json_decode(substr($ln, 2)); + break; + case "?": + if ($isInMessage) + $syntax_error("context must be defined before start of message", $lnNum); + $currentContext = json_decode(substr($ln, 2)); + break; + case " ": + if ($isInMessage) + $currentMessage .= json_decode(substr($ln, 2)); + else + $currentId .= json_decode(substr($ln, 2)); + break; + default: + $syntax_error("invalid start of line '" . $ln[0] . "' (0x" . str_pad(strtoupper(dechex(ord($ln[0]))), 2, "0", STR_PAD_LEFT) . ")", $lnNum); + break; + } + } + + if ($currentId !== "") { + if ($currentContext !== "") + $currentId = $currentContext . "\x7F" . $currentId; + $msgs[$currentId] ??= $currentMessage; + } + return $msgs; +} + +function i18n_locale(string $locale) { + global $__i18n_current_locale; + $__i18n_current_locale = $locale; +} + +function i18n_get(string $msgid, array $params = [], ?string $context = null): string { + global $__i18n_current_locale, $__i18n_msg_store; + + $key = $msgid; + if ($context !== null) + $key = $context . "\x7F" . $msgid; + + $msg = ($__i18n_msg_store[$__i18n_current_locale] ?? [])[$msgid] ?? $key; + + uksort($params, fn(string $a, string $b): int => strlen($b) <=> strlen($a)); + return str_replace( + array_map(fn(string $k): string => "%$k%", array_keys($params)), + array_values($params), + $msg + ); +} + +function __(string $msgid, array $params = [], ?string $context = null): string { + return i18n_get($msgid, $params, $context); +} + +foreach (scandir(MESSAGE_DIR) as $ent) { + $path = MESSAGE_DIR . "/" . $ent; + if ($ent[0] === "." || !is_file($path) || strcasecmp(pathinfo($ent, PATHINFO_EXTENSION), "msg") !== 0) + continue; + $lang = pathinfo($ent, PATHINFO_FILENAME); + $__i18n_msg_store[$lang] = i18n_parse(file_get_contents($path), $ent); +} diff --git a/src/application/messages/de.msg b/src/application/messages/de.msg new file mode 100644 index 0000000..5ee2f6c --- /dev/null +++ b/src/application/messages/de.msg @@ -0,0 +1,117 @@ +: "Log in" += "Anmelden" + +: "Register" += "Registrieren" + +: "Failed to connect to database!" += "Fehler bei Datenbankverbindung" + +: "Username or password incorrect!" += "Nutzername oder Passwort falsch!" + +: "Please activate your user account first!" += "Bitte aktivieren Sie Ihr Nutzerkonto zuerst!" + +: "Public registration disabled" += "Öffentliche Registrierung deaktiviert" + +: "Incorrect CAPTCHA text!" += "Ungültiger CAPTCHA-Text!" + +: "Passwords do not match!" += "Passwörter stimmen nicht überein!" + +: "Password too short! Your password must consist of 8 or more characters" += "Passwort zu kurz! Es muss aus mindestens acht Zeichen bestehen" + +: "Username has an invalid format" += "Der Nutzername weist ein ungültiges Format auf" + +: "Invalid email address" += "Ungültige E-Mail-Adresse" + +: "This username is already taken!" += "Dieser Nutzername ist bereits vergeben!" + +: "This email address is already in use!" += "Diese E-Mail-Adresse wird bereits verwendet!" + +: "Welcome to %forum_title%, %user_display_name%!\n" + "\n" + "Please activate your account by clicking the link below:\n" + "%activation_link%\n" + "\n" + "Kind regards,\n" + "%forum_copyright%" += "Willkommen bei %forum_title%, %user_display_name%!\n" + "\n" + "Bitte aktivieren Sie Ihr Nutzerkonto, indem Sie folgenen Link anklicken:\n" + "%activation_link%\n" + "" + "Mit freundlichen Grüßen,\n" + "%forum_copyright%" + +: "Your account has been created!\nPlease check your emails for an activation link!" += "Ihr Nutzerkonto wurde erstellt!\nBitte überprüfen Sie Ihre E-Mails auf einen Aktivierungslink!" + +: "Invalid token" += "Ungültiges Token" + +: "Invalid signature." += "Ungültige Signatur" + +: "Failed to update user" += "Konnte Nutzer nicht aktualisieren" + +: "Your account has been activated!\nPlease click %link%here%/link% to log in!" += "Ihr Nutzerkonto wurde aktiviert!\nBitte klicken Sie %link%hier%/link%, um sich anzumelden!" + +: "Too many attachments" += "Zu viele Anhänge" + +: "Individual file size exceeded" += "Anhang zu groß" + +: "Message too short or too long!" += "Nachricht zu kurz oder zu lang" + +: "Title too short or too long!" += "Titel zu kurz oder zu lang" + +: "New topic" += "Neues Thema" + +: "No user with name @%user_handle%" += "Kein Nutzer mit dem Namen @%user_handle%" + +: "No user exists with this id" += "Kein Nutzer existiert mit dieser ID" + +: "You can only change your username every 30 days!" += "Sie können Ihren Nutzernamen nur alle 30 Tage ändern!" + +: "Invalid username!" += "Ungültiger Nutzername!" + +: "Please upload an image to change your profile picture" += "Bitte laden Sie ein Bild hoch, um Ihr Profilbild zu ändern" + +: "Please upload a valid PNG or JPEG file" += "Bitte laden Sie eine gültige PNG- oder JPEG-Datei hoch" + +: "Failed to save changes" +? "Update profile" += "Konnte Änderungen nicht übernehmen" + +: "You must be logged in to view attachments" += "Sie müssen angemeldet sein, um Anhänge sehen zu können" + +: "No attachment exists with this id" += "Kein Anhang exsitier mit dieser ID" + +: "Attachment is not an image" += "Anhang ist kein Bild" + +: "Delete post" += "Beitrag löschen" diff --git a/src/application/messages/strings.mst b/src/application/messages/strings.mst new file mode 100644 index 0000000..6f6ec38 --- /dev/null +++ b/src/application/messages/strings.mst @@ -0,0 +1,111 @@ +: "Log in" += "" + +: "Register" += "" + +: "Failed to connect to database!" += "" + +: "Username or password incorrect!" += "" + +: "Please activate your user account first!" += "" + +: "Public registration disabled" += "" + +: "Incorrect CAPTCHA text!" += "" + +: "Passwords do not match!" += "" + +: "Password too short! Your password must consist of 8 or more characters" += "" + +: "Username has an invalid format" += "" + +: "Invalid email address" += "" + +: "This username is already taken!" += "" + +: "This email address is already in use!" += "" + +: "Welcome to %forum_title%, %user_display_name%!\n" + "\n" + "Please activate your account by clicking the link below:\n" + "%activation_link%\n" + "\n" + "Kind regards,\n" + "%forum_copyright%" += "" + +: "Your account has been created!\nPlease check your emails for an activation link!" += "" + +: "Invalid token" += "" + +: "Invalid signature." += "" + +: "Failed to update user" += "" + +: "Your account has been activated!\nPlease click %link%here%/link% to log in!" += "" + +: "Too many attachments" += "" + +: "Individual file size exceeded" += "" + +: "Message too short or too long!" += "" + +: "Title too short or too long!" += "" + +: "New topic" += "" + +: "No user with name @%user_handle%" += "" + +: "No user exists with this id" += "" + +: "You can only change your username every 30 days!" += "" + +: "Invalid username!" += "" + +: "Please upload an image to change your profile picture" += "" + +: "Please upload a valid PNG or JPEG file" += "" + +: "Failed to save changes" +? "Update profile" += "" + +: "You must be logged in to view attachments" += "" + +: "No attachment exists with this id" += "" + +: "Attachment is not an image" += "" + +: "Delete post" += "" diff --git a/src/application/mystic/forum/Database.php b/src/application/mystic/forum/Database.php index 7c9ac7a..6d633fa 100644 --- a/src/application/mystic/forum/Database.php +++ b/src/application/mystic/forum/Database.php @@ -333,15 +333,28 @@ class Database { return true; } - public function fetchWhere(Entity &$entity, string $columnName): bool { + public function fetchWhere(Entity &$entity, string|array $columnNames): bool { $entityClassName = get_class($entity); $tableName = self::getTableName($entityClassName); $reflClass = new ReflectionClass($entityClassName); $cols = self::getColumns($reflClass); - if (!isset($cols[$columnName])) - throw new \RuntimeException("Column $columnName does not exist!"); - $query = "SELECT * FROM $tableName WHERE $columnName = \$1 LIMIT 1;"; - $result = \pg_query_params($this->connection, $query, [ $entity->{$cols[$columnName]["propertyName"]} ]); + if (!is_array($columnNames)) { + $columnNames = [ $columnNames ]; + } + + $whereClause = []; + $columnValues = []; + $count = 0; + foreach ($columnNames as $columnName) { + ++$count; + if (!isset($cols[$columnName])) + throw new \RuntimeException("Column $columnName does not exist!"); + $whereClause []= "$columnName = \$$count"; + $columnValues []= self::stringifyValue($entity->{$cols[$columnName]["propertyName"]}, $cols[$columnName]["columnType"]); + } + $whereClause = implode(" AND ", $whereClause); + $query = "SELECT * FROM $tableName WHERE $whereClause LIMIT 1;"; + $result = \pg_query_params($this->connection, $query, $columnValues); if ($result === false) throw new \RuntimeException("Fetch failed: " . \pg_last_error($this->connection)); $row = \pg_fetch_assoc($result); diff --git a/src/application/views/form_login.php b/src/application/views/form_login.php index 27924e8..de8f28c 100644 --- a/src/application/views/form_login.php +++ b/src/application/views/form_login.php @@ -10,7 +10,7 @@ RequestUtils::clearLastForm(); ?>