From 0b6251767edb3abe7896debc9561b9d27cccdbb5 Mon Sep 17 00:00:00 2001 From: Matteo Gheza Date: Sat, 19 Mar 2022 00:29:27 +0100 Subject: [PATCH] Initial backend translation support --- backend/alerts.php | 20 +++---- backend/apiRouter.php | 24 ++++----- backend/translations/en.php | 49 +++++++++++++++++ backend/translations/it.php | 46 ++++++++++++++++ backend/utils.php | 104 ++++++++++++++++++++++++++++++++---- 5 files changed, 212 insertions(+), 31 deletions(-) create mode 100644 backend/translations/en.php create mode 100644 backend/translations/it.php diff --git a/backend/alerts.php b/backend/alerts.php index 4c9bb7d..a30fd71 100644 --- a/backend/alerts.php +++ b/backend/alerts.php @@ -93,7 +93,7 @@ function updateAlertMessages($alert, $crew=null, $alertDeleted = false) { if(!is_null($message_id) && !is_null($chat_id)) { $Bot->sendMessage([ "chat_id" => $chat_id, - "text" => "Allerta rimossa.\nPartecipazione non più richiesta.", + "text" => __("alerts.alert_removed"), "reply_to_message_id" => $message_id ]); try { @@ -160,7 +160,7 @@ function updateAlertMessages($alert, $crew=null, $alertDeleted = false) { if((!is_null($message_id) || !is_null($chat_id)) && $member["response"] === "waiting") { $Bot->sendMessage([ "chat_id" => $chat_id, - "text" => "Numero minimo vigili richiesti raggiunto.\nPartecipazione non più richiesta.", + "text" => __("alerts.alert_completed"), "reply_to_message_id" => $message_id ]); try { @@ -192,7 +192,7 @@ function setAlertResponse($response, $userId, $alertId) { if(!$alert["enabled"]) return; $crew = json_decode($alert["crew"], true); - $messageText = $response ? "🟢 Partecipazione accettata." : "🔴 Partecipazione rifiutata."; + $messageText = $response ? __("alerts.accepted") : __("alerts.rejected"); foreach($crew as &$member) { if($member["id"] == $userId) { @@ -272,20 +272,20 @@ function alertsRouter (FastRoute\RouteCollector $r) { requireLogin(); $users->online_time_update(); if(!$users->hasRole(Role::SUPER_EDITOR)) { - apiResponse(["status" => "error", "message" => "Access denied"]); + apiResponse(["status" => "error", "message" => __("access_denied")]); return; } try { $crew_members = callsList($_POST["type"]); } catch (NoChiefAvailableException) { - apiResponse(["status" => "error", "message" => "Nessun caposquadra disponibile. Contattare i vigili manualmente."]); + apiResponse(["status" => "error", "message" => __("alerts.no_chief_available")]); return; } catch (NoDriverAvailableException) { - apiResponse(["status" => "error", "message" => "Nessun autista disponibile. Contattare i vigili manualmente."]); + apiResponse(["status" => "error", "message" => __("alerts.no_drivers_available")]); return; } catch (NotEnoughAvailableUsersException) { - apiResponse(["status" => "error", "message" => "Nessun utente disponibile. Distaccamento non operativo."]); + apiResponse(["status" => "error", "message" => __("alerts.not_enough_available_users")]); return; } @@ -340,7 +340,7 @@ function alertsRouter (FastRoute\RouteCollector $r) { requireLogin(); $alert = $db->selectRow("SELECT * FROM `".DB_PREFIX."_alerts` WHERE `id` = :id", [":id" => $vars["id"]]); if(is_null($alert)) { - apiResponse(["error" => "alert not found"]); + apiResponse(["error" => __("alerts.alert_not_found")]); return; } $alert["crew"] = json_decode($alert["crew"], true); @@ -359,7 +359,7 @@ function alertsRouter (FastRoute\RouteCollector $r) { requireLogin(); $users->online_time_update(); if(!$users->hasRole(Role::SUPER_EDITOR)) { - apiResponse(["status" => "error", "message" => "Access denied"]); + apiResponse(["status" => "error", "message" => __("access_denied")]); return; } $db->update( @@ -390,7 +390,7 @@ function alertsRouter (FastRoute\RouteCollector $r) { requireLogin(); $users->online_time_update(); if(!$users->hasRole(Role::SUPER_EDITOR)) { - apiResponse(["status" => "error", "message" => "Access denied"]); + apiResponse(["status" => "error", "message" => __("access_denied")]); return; } $db->update( diff --git a/backend/apiRouter.php b/backend/apiRouter.php index 276edcf..2671d1c 100644 --- a/backend/apiRouter.php +++ b/backend/apiRouter.php @@ -274,7 +274,7 @@ function apiRouter (FastRoute\RouteCollector $r) { $users->online_time_update(); if(!$users->hasRole(Role::SUPER_EDITOR) && (int) $_POST["id"] !== $users->auth->getUserId()){ statusCode(401); - apiResponse(["status" => "error", "message" => "You don't have permission to change other users availability", "t" => $users->auth->getUserId()]); + apiResponse(["status" => "error", "message" => __("other_user_availability_change_forbidden"), "t" => $users->auth->getUserId()]); return; } $user_id = is_numeric($_POST["id"]) ? $_POST["id"] : $users->auth->getUserId(); @@ -383,32 +383,32 @@ function apiRouter (FastRoute\RouteCollector $r) { global $users; try { $token = $users->loginAndReturnToken($_POST["username"], $_POST["password"]); - logger("Login effettuato"); + logger(__("log_messages.new_login")); apiResponse(["status" => "success", "access_token" => $token]); } catch (\Delight\Auth\InvalidEmailException $e) { statusCode(401); - apiResponse(["status" => "error", "message" => "Wrong email address"]); + apiResponse(["status" => "error", "message" => __("login.wrong_email")]); } catch (\Delight\Auth\InvalidPasswordException $e) { statusCode(401); - apiResponse(["status" => "error", "message" => "Wrong password"]); + apiResponse(["status" => "error", "message" => __("login.wrong_password")]); } catch (\Delight\Auth\EmailNotVerifiedException $e) { statusCode(401); - apiResponse(["status" => "error", "message" => "Email not verified"]); + apiResponse(["status" => "error", "message" => __("login.email_not_confirmed")]); } catch (\Delight\Auth\UnknownUsernameException $e) { statusCode(401); - apiResponse(["status" => "error", "message" => "Wrong username"]); + apiResponse(["status" => "error", "message" => __("login.wrong_username")]); } catch (\Delight\Auth\TooManyRequestsException $e) { statusCode(401); - apiResponse(["status" => "error", "message" => "Too many requests"]); + apiResponse(["status" => "error", "message" => __("too_many_requests")]); } catch (Exception $e) { statusCode(401); - apiResponse(["status" => "error", "message" => "Unknown error", "error" => $e]); + apiResponse(["status" => "error", "message" => __("unknown_error"), "error" => $e]); } } ); @@ -421,7 +421,7 @@ function apiRouter (FastRoute\RouteCollector $r) { if(!$users->hasRole(Role::SUPER_ADMIN)) { statusCode(401); - apiResponse(["status" => "error", "message" => "You don't have permission to impersonate"]); + apiResponse(["status" => "error", "message" => __("impersonate_user_forbidden")]); return; } @@ -431,15 +431,15 @@ function apiRouter (FastRoute\RouteCollector $r) { } catch (\Delight\Auth\UnknownIdException $e) { statusCode(400); - apiResponse(["status" => "error", "message" => "Wrong user ID"]); + apiResponse(["status" => "error", "message" => __("login.wrong_userid")]); } catch (\Delight\Auth\EmailNotVerifiedException $e) { statusCode(400); - apiResponse(["status" => "error", "message" => "Email not verified"]); + apiResponse(["status" => "error", "message" => __("login.email_not_confirmed")]); } catch (Exception $e) { statusCode(400); - apiResponse(["status" => "error", "message" => "Unknown error", "error" => $e]); + apiResponse(["status" => "error", "message" => __("unknown_error"), "error" => $e]); } } ); diff --git a/backend/translations/en.php b/backend/translations/en.php new file mode 100644 index 0000000..d439052 --- /dev/null +++ b/backend/translations/en.php @@ -0,0 +1,49 @@ + [ + //TODO: save logs as translation string in DB, then translate it when serving them + //(and return empty string if translation is not found) + "new_login" => "New login", + "availability_schedules_updated" => "Availability schedules updated", + "user_added" => "User added", + "user_updated" => "User updated", + "user_removed" => "User removed", + "service_added" => "Service added", + "service_updated" => "Service updated", + "service_removed" => "Service removed", + "training_added" => "Training added", + "training_updated" => "Training updated", + "training_removed" => "Training removed", + "availability_changed_to" => "Availability changed to \"%s\"", + ], + "login" => [ + "wrong_email" => "Wrong email", + "wrong_password" => "Wrong password", + "wrong_username" => "Wrong username", + "wrong_userid" => "Wrong userid", + "email_not_confirmed" => "Email not confirmed" + ], + "alerts" => [ + "alert_removed" => "Alerta removed.\\nAvailability not requested anymore.", + "alert_completed" => "Minimum number of members required reached.\\nParticipation not requested anymore.", + "accepted" => "🟢 Accepted.", + "rejected" => "🔴 Reject.", + "no_chief_available" => "No chief available. Contact the members manually.", + "no_driver_available" => "No driver available. Contact the members manually.", + "not_enough_users_available" => "Not enough users available. Contact the members manually.", + "alert_not_found" => "Alert not found", + ], + "telegram_bot" => [ + //TODO: select Telegram bot language from user's language + "available_support" => "🧯 Available for support", + "available_full" => "🚒 Available with full team", + "not_available" => "⚠️ Not available" + ], + "other_user_availability_change_forbidden" => "You don't have permission to change other users availability", + "impersonate_user_forbidden" => "You don't have permission to impersonate other users", + "too_many_requests" => "Too many requests", + "unknown_error" => "Unknown error", + "access_denied" => "Access denied", + "available" => "available", + "not_available" => "not available", +]; diff --git a/backend/translations/it.php b/backend/translations/it.php new file mode 100644 index 0000000..59ba23f --- /dev/null +++ b/backend/translations/it.php @@ -0,0 +1,46 @@ + [ + "new_login" => "Nuovo accesso", + "availability_schedules_updated" => "Programmazione disponibilità aggiornata", + "user_added" => "Utente aggiunto", + "user_updated" => "Utente aggiornato", + "user_removed" => "Utente rimosso", + "service_added" => "Servizio aggiunto", + "service_updated" => "Servizio aggiornato", + "service_removed" => "Servizio rimosso", + "training_added" => "Esercitazione aggiunta", + "training_updated" => "Esercitazione aggiornata", + "training_removed" => "Esercitazione rimossa", + "availability_changed_to" => "Disponibilità cambiata a \"%s\"", + ], + "login" => [ + "wrong_email" => "Email errata", + "wrong_password" => "Password errata", + "wrong_username" => "Nome utente errato", + "wrong_userid" => "ID utente errato", + "email_not_confirmed" => "Email non confermata" + ], + "alerts" => [ + "alert_removed" => "Allerta rimossa.\\nDisponibilità non più richiesta.", + "alert_completed" => "Numero minimo vigili richiesti raggiunto.\nPartecipazione non più richiesta.", + "accepted" => "🟢 Partecipazione accettata.", + "rejected" => "🔴 Partecipazione rifiutata.", + "no_chief_available" => "Nessun caposquadra disponibile. Contattare i vigili manualmente.", + "no_driver_available" => "Nessun autista disponibile. Contattare i vigili manualmente.", + "not_enough_users_available" => "Non ci sono abbastanza utenti disponibili. Contattare i vigili manualmente.", + "alert_not_found" => "Allerta non trovata", + ], + "telegram_bot" => [ + "available_support" => "🧯 Distaccamento operativo per supporto", + "available_full" => "🚒 Distaccamento operativo con squadra completa", + "not_available" => "⚠️ Distaccamento non operativo" + ], + "other_user_availability_change_forbidden" => "Non hai il permesso di cambiare la disponibilità di altri utenti", + "impersonate_user_forbidden" => "Non hai il permesso di impersonare altri utenti", + "too_many_requests" => "Troppe richieste", + "unknown_error" => "Errore sconosciuto", + "access_denied" => "Accesso negato", + "available" => "disponibile", + "not_available" => "non disponibile", +]; diff --git a/backend/utils.php b/backend/utils.php index f81f094..4ac4874 100644 --- a/backend/utils.php +++ b/backend/utils.php @@ -196,7 +196,7 @@ class Users if($chief == 1) { $this->auth->admin()->addRoleForUserById($userId, Role::SUPER_EDITOR); } - logger("User added", $userId, $inserted_by); + logger(__("log_messages.user_created"), $userId, $inserted_by); return $userId; } else { return false; @@ -223,7 +223,7 @@ class Users DB_PREFIX."_profiles", ["id" => $id] ); - logger("User removed", null, $removed_by); + logger(__("log_messages.user_removed"), null, $removed_by); } public function online_time_update($id=null){ @@ -336,7 +336,7 @@ class Availability { public function change($availability, $user_id, $is_manual_mode=true) { - if($is_manual_mode) logger("Disponibilità cambiata in ".($availability ? '"disponibile"' : '"non disponibile"'), $user_id, $this->users->auth->getUserId()); + if($is_manual_mode) logger(sprintf(__("availability_changed_to"), __($availability ? 'available' : 'not_available')), $user_id, $this->users->auth->getUserId()); $change_values = ["available" => $availability]; if($is_manual_mode) $change_values["manual_mode"] = 1; @@ -350,11 +350,11 @@ class Availability { if(!$this->users->isHidden($user_id)) { $available_users_count = $this->db->selectValue("SELECT COUNT(id) FROM `".DB_PREFIX."_profiles` WHERE `available` = 1 AND `hidden` = 0"); if($available_users_count === 5) { - sendTelegramNotification("🚒 Distaccamento operativo con squadra completa"); + sendTelegramNotification(__("telegram_bot.available_full")); } else if($available_users_count < 2) { - sendTelegramNotification("⚠️ Distaccamento non operativo"); + sendTelegramNotification(__("telegram_bot.not_available")); } else if($available_users_count < 5) { - sendTelegramNotification("🧯 Distaccamento operativo per supporto"); + sendTelegramNotification(__("telegram_bot.available_support")); } } @@ -454,7 +454,7 @@ class Services { $serviceId = $this->db->getLastInsertId(); $this->increment_counter($chief.",".$drivers.",".$crew); - logger("Service added"); + logger(__("log_messages.service_added")); return $serviceId; } @@ -471,7 +471,7 @@ class Services { DB_PREFIX."_services", ["id" => $id] ); - logger("Intervento eliminato"); + logger(__("log_messages.service_deleted")); return true; } @@ -610,7 +610,7 @@ class Schedules { public function update($schedules, $profile="default") { //TODO implement multiple profiles //TODO implement holidays - logger("Aggiornata programmazione orari disponibilità"); + logger(__("log_messages.availability_schedules_updated")); if(empty($this->get($profile))) { return $this->db->insert( DB_PREFIX."_schedules", @@ -626,8 +626,94 @@ class Schedules { } } +class Translations +{ + public $loaded_languages = ["en", "it"]; + public $default_language = "en"; + public $language = null; + public $client_languages = ["en"]; + public $loaded_translations = []; + public $filename = ""; + + public function client_languages() + { + if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $client_languages = $_SERVER['HTTP_ACCEPT_LANGUAGE']; + } else { + $client_languages = "en-US;q=0.5,en;q=0.3"; + } + if(strpos($client_languages, ';') == false) { + if(strpos($client_languages, '-') !== false) { + return [substr($client_languages, 0, 5)]; + } else { + return [substr($client_languages, 0, 2)]; + } + } else { + $client_languages = explode(",", $client_languages); + $tmp_languages = []; + foreach($client_languages as $language){ + if(strpos($language, ';') == false) { + $tmp_languages[$language] = 1; + } else { + $tmp_languages[explode(";q=", $language)[0]] = (float) explode(";q=", $language)[1]; + } + } + arsort($tmp_languages); + return array_keys($tmp_languages); + } + } + + public function __construct($force_language = false) + { + $this->client_languages = $this->client_languages(); + if(isset($_COOKIE["forceLanguage"]) && in_array($_COOKIE["forceLanguage"], $this->loaded_languages)){ + $this->language = $_COOKIE["forceLanguage"]; + } else if($force_language && in_array($force_language, $this->loaded_languages)){ + $this->language = $force_language; + } else { + foreach($this->client_languages as $language){ + if(in_array($language, $this->loaded_languages) && $this->language == null) { + $this->language = $language; + } + } + if($this->language == null) { + $this->language = "en"; + } + } + $this->filename = "translations/".$this->language.".php"; + if (file_exists($this->filename)) { + $this->loaded_translations = require($this->filename); + } else { + throw new Exception("Language file not found"); + } + } + + public function translate($string) + { + if(strpos($string, ".") !== false) { + $string = explode(".", $string); + if (!array_key_exists($string[1], $this->loaded_translations[$string[0]])) { + throw new Exception('string does not exist'); + } + return $this->loaded_translations[$string[0]][$string[1]]; + } else { + if (!array_key_exists($string, $this->loaded_translations)) { + throw new Exception('string does not exist'); + } + return $this->loaded_translations[$string]; + } + } +} + $users = new Users($db, $auth); $availability = new Availability($db, $users); $places = new Places($cache, $users, $db); $services = new Services($db, $users, $places); $schedules = new Schedules($db, $users); +$translations = new Translations(); + +function __(string $string) +{ + global $translations; + return $translations->translate($string); +}