From 25e5da1e74ebf9e29bc88b2c450e8a0fc83626f6 Mon Sep 17 00:00:00 2001 From: n1rwana Date: Tue, 25 Jul 2023 23:00:37 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=BE=D0=B4?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Web/Presenters/AdminPresenter.php | 223 ++++++++++++++++++ Web/Presenters/templates/@layout.xml | 1 + Web/Presenters/templates/Admin/@layout.xml | 3 + .../templates/Admin/Translation.xml | 212 +++++++++++++++++ Web/routes.yml | 6 + locales/en.strings | 20 ++ locales/eo.strings | 2 +- locales/ru.strings | 20 ++ locales/ru_old.strings | 2 +- locales/ru_sov.strings | 2 +- 10 files changed, 488 insertions(+), 3 deletions(-) create mode 100644 Web/Presenters/templates/Admin/Translation.xml diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index ff21612b2..6ce81d6ea 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -550,4 +550,227 @@ function renderChandlerUser(string $UUID): void $this->redirect("/admin/users/id" . $user->getId()); } + + function renderTranslation(): void + { + $lang = $this->queryParam("lang") ?? "ru"; + $q = $this->queryParam("q"); + $lines = []; + $new_key = true; + + if ($lang === "any" || $this->queryParam("langs")) { + if (!$q || trim($q) === "") { + $this->flashFail("err", tr("translation_enter_query_first")); + return; + } + + $locales = $this->queryParam("langs") ? explode(",", $this->queryParam("langs")) : array_filter(scandir(__DIR__ . "/../../locales/"), function ($file) { + return preg_match('/\.strings$/', $file); + }); + + $_locales = []; + foreach ($locales as $locale) + $_locales[] = explode(".", $locale)[0]; + + foreach ($locales as $locale) { + $handle = fopen(__DIR__ . "/../../locales/$locale" . ($this->queryParam("langs") ? ".strings" : ''), "r"); + if ($handle) { + $i = 0; + while (($line = fgets($handle)) !== false) { + $i++; + if (preg_match('/"(.*)" = "(.*)"(;)?/', $line, $matches)) { + $val = ["index" => $i, "key" => $matches[1], "lang" => explode(".", $locale)[0], "value" => $matches[2]]; + if (!in_array($val["key"], ["__locale", "__WinEncoding", "__transNames"])) { + if ($q) { + if (str_contains($q, "key:")) { + continue; + } else if (str_contains($q, "value:")) { + $_exact_value_match = preg_match('/value:(.*)/', $q, $_value_matches); + if ($_exact_value_match && $_value_matches[1] !== $val["value"]) { + continue; + } + } else { + if (!str_contains(mb_strtolower($line), mb_strtolower($q))) { + continue; + } + } + } + $lines[] = $val; + } + } + } + fclose($handle); + $new_key = false; + } else { + $this->flash("err", tr("translation_locale_file_not_found")); + } + } + + if (str_contains($q, "key:")) { + $_exact_key_match = preg_match('/key:(.*)/', $q, $_key_matches); + if ($_exact_key_match && $_key_matches[1]) { + $i = 0; + $used_langs = []; + foreach ($_locales as $locale) { + if ($i === sizeof($_locales)) break; + $handle = fopen(__DIR__ . "/../../locales/$locale.strings", "r"); + $value = ""; + if ($handle) { + while (($line = fgets($handle)) !== false) { + if (preg_match('/"(' . $_key_matches[1] . ')" = "(.*)"(;)?/', $line, $matches)) { + $value = $matches[2]; + } + $new_key = isset($value); + + } + fclose($handle); + } + + if (!in_array($locale, $used_langs)) { + $lines[] = ["index" => $i, "key" => $_key_matches[1], "lang" => $locale, "value" => $value]; + } + $used_langs[] = $locale; + $i++; + } + } + } + } else { + $new_key = false; + $handle = fopen(__DIR__ . "/../../locales/$lang.strings", "r"); + if ($handle) { + $i = 0; + while (($line = fgets($handle)) !== false) { + $i++; + if (preg_match('/"(.*)" = "(.*)"(;)?/', $line, $matches)) { + $val = ["index" => $i, "key" => $matches[1], "lang" => $lang, "value" => $matches[2]]; + if (!in_array($val["key"], ["__locale", "__WinEncoding", "__transNames"])) { + if ($q) { + if (str_contains($q, "key:")) { + $_exact_key_match = preg_match('/key:(.*)/', $q, $_key_matches); + if ($_exact_key_match && $_key_matches[1] !== $val["key"]) { + continue; + } + } else if (str_contains($q, "value:")) { + $_exact_value_match = preg_match('/value:(.*)/', $q, $_value_matches); + if ($_exact_value_match && $_value_matches[1] !== $val["value"]) { + continue; + } + } else { + if (!str_contains(mb_strtolower($line), mb_strtolower($q))) { + continue; + } + } + } + $lines[] = $val; + } + } + } + fclose($handle); + } else { + $this->flash("err", tr("translation_locale_file_not_found")); + } + } + + $this->template->languages = getLanguages(); + $this->template->activeLang = $lang; + $this->template->keys = $lines; + $this->template->q = str_replace('"', '', $q); + $this->template->scrollTo = $this->queryParam("s"); + $this->template->langs = $this->queryParam("langs"); + $this->template->new_key = $new_key; + } + + function renderTranslateKey(): void + { + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $this->assertNoCSRF(); + + if (empty($this->postParam("strings"))) { + $lang = $this->postParam("lang"); + $key = $this->postParam("key"); + $value = addslashes($this->postParam("value")); + + $handle = fopen(__DIR__ . "/../../locales/$lang.strings", "c"); + if ($handle) { + if ($this->postParam("act") !== "delete") { + $file = file_get_contents(__DIR__ . "/../../locales/$lang.strings"); + if ($file) { + $handle = fopen(__DIR__ . "/../../locales/$lang.strings", "c"); + if (preg_match('/"(' . $key . ')" = "(.*)";/', $file)) { + $replacement = rtrim(preg_replace('/"(' . $key . ')" = "(.*)";/', '"$1" = "' . $value . '";', $file), ""); + + if (file_put_contents(__DIR__ . "/../../locales/$lang.strings", $replacement)) { + fclose($handle); + $this->returnJson(["success" => true]); + } else { + fclose($handle); + $this->returnJson(["success" => false, "error" => tr("translation_file_writing_error")]); + } + } else { + $file .= "\"$key\" = \"$value\";\n"; + if (fwrite($handle, $file)) { + fclose($handle); + $this->returnJson(["success" => true]); + } else { + fclose($handle); + $this->returnJson(["success" => false, "error" => tr("translation_file_writing_error")]); + } + } + } else { + $this->returnJson(["success" => false, "error" => tr("translation_locale_file_not_found")]); + } + } else { + $file = file(__DIR__ . "/../../locales/$lang.strings"); + $new_file = []; + foreach ($file as &$line) { + if (!preg_match('/"(' . $key . ')" = "(' . $value . ')";/', $line)) { + $new_file[] = $line; + } + } + file_put_contents(__DIR__ . "/../../locales/$lang.strings", implode("", $new_file)); + fclose($handle); + $this->returnJson(["success" => true]); + } + } else { + $this->returnJson(["success" => false, "error" => tr("translation_file_reading_error")]); + } + } else { + $objects = explode(";", $this->postParam("strings")); + if (sizeof($objects) < 2) { + $this->returnJson(["success" => false, "error" => tr("translation_enter_at_least_two_values")]); + } + + $succ = 0; + foreach ($objects as $object) { + $data = explode(":", $object); + $lang = $data[0]; + $key = $data[1]; + $value = addslashes($data[2]); + + $file = file_get_contents(__DIR__ . "/../../locales/$lang.strings"); + if ($file) { + $handle = fopen(__DIR__ . "/../../locales/$lang.strings", "c"); + if ($handle) { + if (preg_match('/"(' . $key . ')" = "(.*)";/', $file)) { + $replacement = preg_replace('/"(' . $key . ')" = "(.*)";/', '"$1" = "' . $value . '";', $file); + if (file_put_contents(__DIR__ . "/../../locales/$lang.strings", $replacement)) { + $succ++; + } + } else { + $file .= "\"$key\" = \"$value\";\n"; + if (fwrite($handle, $file)) { + $succ++; + } + } + fclose($handle); + } + } + } + + $this->returnJson(["success" => true, "count" => $succ]); + } + } else { + $this->notFound(); + } + } } diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 1718c4990..a0958cba5 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -204,6 +204,7 @@ {var $menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')} {_admin} + {_translations} {_helpdesk} {if $helpdeskTicketNotAnsweredCount > 0} ({$helpdeskTicketNotAnsweredCount}) diff --git a/Web/Presenters/templates/Admin/@layout.xml b/Web/Presenters/templates/Admin/@layout.xml index 7b1a30f3a..9f1951bfd 100644 --- a/Web/Presenters/templates/Admin/@layout.xml +++ b/Web/Presenters/templates/Admin/@layout.xml @@ -124,6 +124,9 @@
  • {_admin_settings_tuning}
  • +
  • + {_translations} +
  • {_admin_settings_appearance}
  • diff --git a/Web/Presenters/templates/Admin/Translation.xml b/Web/Presenters/templates/Admin/Translation.xml new file mode 100644 index 000000000..edff59e0d --- /dev/null +++ b/Web/Presenters/templates/Admin/Translation.xml @@ -0,0 +1,212 @@ +{extends "@layout.xml"} + +{block title} + {_translations} +{/block} + +{block heading} + {include title} +{/block} + +{block content} +
    +
    + + + + + + + + {_select_language} + + +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    + {$key['lang']} +
    + + + + + + {_copy} + + + {_translate} + + + {_delete} + +
    +
    +
    +
    {_translation_nothing_found}. :(
    + {tr("translation_start_translate_key", $q)} +
    + + +{/block} \ No newline at end of file diff --git a/Web/routes.yml b/Web/routes.yml index d1a0e7aef..0377c73e7 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -325,6 +325,12 @@ routes: handler: "Admin->bannedLink" - url: "/admin/bannedLink/id{num}/unban" handler: "Admin->unbanLink" + - url: "/admin/translation" + handler: "Admin->translation" + - url: "/translation.php" + handler: "Admin->translation" + - url: "/admin/translate" + handler: "Admin->translateKey" - url: "/upload/photo/{text}" handler: "VKAPI->photoUpload" - url: "/method/{text}.{text}" diff --git a/locales/en.strings b/locales/en.strings index 4aaa4483a..3e4fe5e66 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -1537,3 +1537,23 @@ "mobile_like" = "Like"; "mobile_user_info_hide" = "Hide"; "mobile_user_info_show_details" = "Show details"; + +"translation_enter_query_first" = "First enter the query"; +"translation_locale_file_not_found" = "Locale file not found"; +"translation_file_writing_error" = "Error when writing to a file"; +"translation_file_reading_error" = "Error when reading a file"; +"translation_enter_at_least_two_values" = "Enter values for at least two locales"; +"translations" = "Translations"; +"translation_you_are_creating_a_new_key" = "You are creating a new key"; +"translation_you_are_creating_a_new_key_description" = "Set a value for at least two languages and click \"Save All\""; +"language" = "Language"; +"translation_comma_separated" = "Comma-separated"; +"translation_comma_separated_langs_placeholder" = "Language codes separated by commas (ru,en,...)"; +"translation_search" = "Search (key:NAME) to create a key"; +"translation_key" = "Key"; +"translation_value" = "Value"; +"copy" = "Copy"; +"translate" = "Translate"; +"translation_nothing_found" = "Nothing was found"; +"translation_start_translate_key" = "Start key $1 translation"; +"save_all" = "Save all"; diff --git a/locales/eo.strings b/locales/eo.strings index b57d998f2..b16533f07 100644 --- a/locales/eo.strings +++ b/locales/eo.strings @@ -673,4 +673,4 @@ "edit_action" = "Ŝanĝi"; "warning" = "Averto"; -"question_confirm" = "Ĉi tiu ago ne povas esti malfarita. Ĉu vi vere volas fari ĝin?"; \ No newline at end of file +"question_confirm" = "Ĉi tiu ago ne povas esti malfarita. Ĉu vi vere volas fari ĝin?"; diff --git a/locales/ru.strings b/locales/ru.strings index 6faa5e2e2..55d87a405 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -1430,3 +1430,23 @@ "mobile_like" = "Нравится"; "mobile_user_info_hide" = "Скрыть"; "mobile_user_info_show_details" = "Показать подробнее"; + +"translation_enter_query_first" = "Сначала введите запрос"; +"translation_locale_file_not_found" = "Файл с локалью не найден"; +"translation_file_writing_error" = "Ошибка при записи в файл"; +"translation_file_reading_error" = "Ошибка при чтении файла"; +"translation_enter_at_least_two_values" = "Введите значения хотя бы для двух локалей"; +"translations" = "Переводы"; +"translation_you_are_creating_a_new_key" = "Вы создаете новый ключ"; +"translation_you_are_creating_a_new_key_description" = "Задайте значение как минимум для двух языков и нажмите \"Сохранить все\""; +"language" = "Язык"; +"translation_comma_separated" = "Через запятую"; +"translation_comma_separated_langs_placeholder" = "Коды языков через запятую (ru,en,...)"; +"translation_search" = "Поиск (key:ИМЯ чтобы создать ключ)"; +"translation_key" = "Ключ"; +"translation_value" = "Значение"; +"copy" = "Скопировать"; +"translate" = "Перевести"; +"translation_nothing_found" = "Ничего не нашлось"; +"translation_start_translate_key" = "Начать перевод ключа $1"; +"save_all" = "Сохранить все"; diff --git a/locales/ru_old.strings b/locales/ru_old.strings index d0917f651..c1a570d9a 100644 --- a/locales/ru_old.strings +++ b/locales/ru_old.strings @@ -773,4 +773,4 @@ "reset" = "Сбросъ"; "closed_group_post" = "Это высказыванiе изъ закрытого общѣства"; -"deleted_target_comment" = "Этотъ отзыв принадлѣжит к удалѣнному высказыванiю"; \ No newline at end of file +"deleted_target_comment" = "Этотъ отзыв принадлѣжит к удалѣнному высказыванiю"; diff --git a/locales/ru_sov.strings b/locales/ru_sov.strings index 903910a13..bab16e382 100644 --- a/locales/ru_sov.strings +++ b/locales/ru_sov.strings @@ -978,4 +978,4 @@ "reset" = "Сброс"; "closed_group_post" = "Эта запись из закрытого собрания"; -"deleted_target_comment" = "Этот отзыв надлежит к удалённой записи"; \ No newline at end of file +"deleted_target_comment" = "Этот отзыв надлежит к удалённой записи"; From 7311be3faa3fc1805363a2aafe7d3d26df303783 Mon Sep 17 00:00:00 2001 From: n1rwana Date: Tue, 25 Jul 2023 23:07:54 +0300 Subject: [PATCH 2/2] Update Translation.xml --- Web/Presenters/templates/Admin/Translation.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Web/Presenters/templates/Admin/Translation.xml b/Web/Presenters/templates/Admin/Translation.xml index edff59e0d..a1e672b17 100644 --- a/Web/Presenters/templates/Admin/Translation.xml +++ b/Web/Presenters/templates/Admin/Translation.xml @@ -68,7 +68,7 @@ {_translate} - + {_delete}