mirror of
https://github.com/openvk/openvk
synced 2024-11-15 03:31:18 +03:00
Переводы
This commit is contained in:
parent
a2384cc231
commit
25e5da1e74
10 changed files with 488 additions and 3 deletions
|
@ -550,4 +550,227 @@ final class AdminPresenter extends OpenVKPresenter
|
|||
|
||||
$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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,6 +204,7 @@
|
|||
{var $menuLinksAvaiable = sizeof(OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links']) > 0 && $thisUser->getLeftMenuItemStatus('links')}
|
||||
<div n:if="$canAccessAdminPanel || $canAccessHelpdesk || $menuLinksAvaiable" class="menu_divider"></div>
|
||||
<a href="/admin" class="link" n:if="$canAccessAdminPanel" title="{_admin} [Alt+Shift+A]" accesskey="a">{_admin}</a>
|
||||
<a href="/translation.php" class="link" n:if="$canAccessAdminPanel" title="{_translations}">{_translations}</a>
|
||||
<a href="/support/tickets" class="link" n:if="$canAccessHelpdesk">{_helpdesk}
|
||||
{if $helpdeskTicketNotAnsweredCount > 0}
|
||||
(<b>{$helpdeskTicketNotAnsweredCount}</b>)
|
||||
|
|
|
@ -124,6 +124,9 @@
|
|||
<li>
|
||||
<a href="/admin/settings/tuning">{_admin_settings_tuning}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/translation">{_translations}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings/appearance">{_admin_settings_appearance}</a>
|
||||
</li>
|
||||
|
|
212
Web/Presenters/templates/Admin/Translation.xml
Normal file
212
Web/Presenters/templates/Admin/Translation.xml
Normal file
|
@ -0,0 +1,212 @@
|
|||
{extends "@layout.xml"}
|
||||
|
||||
{block title}
|
||||
{_translations}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
{include title}
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
<form class="aui">
|
||||
<div class="field-group">
|
||||
<label>{_language}</label>
|
||||
<select n:attr="style => $langs ? 'display: none;' : ''" id="lang-select" name="lang" class="select" onchange="onLanguageSelectChanged(this)">
|
||||
<option value="any" n:attr="selected => $activeLang === 'any'">{_s_any|firstUpper}</option>
|
||||
<option value="comma-separated">{_translation_comma_separated}</option>
|
||||
<option
|
||||
n:foreach="$languages as $language"
|
||||
n:attr="selected => $activeLang === $language['code']"
|
||||
value="{$language['code']}"
|
||||
>[{$language["code"]}] {$language["native_name"]}</option>
|
||||
</select>
|
||||
<form class="aui" style="display: flex;">
|
||||
<input value="{$langs}" n:attr="style => $langs ? '' : 'display: none;'" id="langs-comma-separated" autocomplete="off" class="text long-field" type="text" placeholder="{_translation_comma_separated_langs_placeholder}" name="langs"/>
|
||||
<input n:attr="value => $q" id="quickSearchInput" autocomplete="off" class="text long-field" type="text" placeholder="{_translation_search}" name="q" accesskey="Q"/>
|
||||
<button class="aui-button aui-button-primary" type="submit">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-search">{_header_search}</span>
|
||||
</button>
|
||||
<a n:attr="style => $langs ? '' : 'display: none;'" id="back-to-lang-select-btn" class="aui-button aui-button-primary" onclick="backToLangSelect(this)">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-arrow-left">{_select_language}</span>
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<form class="aui" style="margin: 0;" method="post" onsubmit="onKeyValueFormSubmit(this, 'new')">
|
||||
<div class="field-group">
|
||||
<select id="lang-new" name="lang" class="select">
|
||||
<option
|
||||
n:foreach="$languages as $language"
|
||||
n:attr="selected => $activeLang === $language['code']"
|
||||
value="{$language['code']}"
|
||||
>[{$language["code"]}] {$language["native_name"]}</option>
|
||||
</select>
|
||||
<input class="text long-field" type="text" id="key-new" name="key" value="" placeholder="{_translation_key}"/>
|
||||
<input class="text long-field" type="text" id="value-new" name="value" value="" placeholder="{_translation_value}"/>
|
||||
<button class="aui-button aui-button-primary" type="submit">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-add">{_save}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<form n:attr="id => $new_key ? ('key-l-' . $key['lang']) : ''" class="aui" n:foreach="$keys as $key" style="margin: 0;" method="post" onsubmit="onKeyValueFormSubmit(this, {$key['index']})">
|
||||
<div class="field-group" style="display: flex; gap: 8px;">
|
||||
<div style="width: 3em;">
|
||||
<img src="/assets/packages/static/openvk/img/flags/{$key['lang']}.gif" alt="{$key['lang']}" style="margin: 25% 0 0 50%;"/>
|
||||
</div>
|
||||
<input n:attr="lang => $new_key ? $key['lang'] : ''" class="text long-field {=$new_key ? 'key-l-name' : ''}" type="text" id="key-{$key['index']}" name="key" value="{$key['key']}" placeholder="{_translation_key}"/>
|
||||
<input n:attr="lang => $new_key ? $key['lang'] : ''" class="text long-field {=$new_key ? 'key-l-value' : ''}" type="text" id="value-{$key['index']}" name="value" value="{$key['value']}" placeholder="{_translation_value}"/>
|
||||
<input type="hidden" id="lang-{$key['index']}" value="{$key['lang']}" />
|
||||
<button class="aui-button aui-button-primary" type="submit">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-export">{_save}</span>
|
||||
</button>
|
||||
<a class="aui-button aui-button-primary" onclick="copyKey({$key['index']})">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-copy">{_copy}</span>
|
||||
</a>
|
||||
<a class="aui-button aui-button-primary" onclick="openKeyInNewTab({$key['key']})">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-world">{_translate}</span>
|
||||
</a>
|
||||
<a class="aui-button aui-button-primary" onclick="deleteKey({$key['index']})">
|
||||
<span class="aui-icon aui-icon-small aui-iconfont-delete">{_delete}</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
<center n:if="sizeof($keys) === 0" style="font-size: 24px;">
|
||||
<div>{_translation_nothing_found}. :(</div>
|
||||
<a onclick="openKeyInNewTab({str_replace('key:', '', $q)})">{tr("translation_start_translate_key", $q)}</a>
|
||||
</center>
|
||||
<button class="aui-button aui-button-primary" n:if="$new_key" onclick="saveAllKeys()">{_save_all}</button>
|
||||
<script>
|
||||
function onLanguageSelectChanged(e) {
|
||||
if (e.value !== 'comma-separated') {
|
||||
let q = {=urlencode($q)};
|
||||
window.location.href = "/admin/translation?lang=" + e.value + (q ? ("&q=" + q) : "");
|
||||
} else {
|
||||
document.getElementById('back-to-lang-select-btn').style.display = '';
|
||||
document.getElementById('langs-comma-separated').style.display = '';
|
||||
document.getElementById('lang-select').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyValueFormSubmit(e, index) {
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
const key = document.getElementById('key-' + index).value;
|
||||
const value = document.getElementById('value-' + index).value;
|
||||
const lang = document.getElementById('lang-' + index).value;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/admin/translate",
|
||||
data: {
|
||||
lang: lang,
|
||||
key: key,
|
||||
value: value,
|
||||
hash: {$csrfToken}
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search + {$scrollTo ? '"&s=" + window.scrollY' : ''};
|
||||
} else {
|
||||
alert(tr("error") + ": " + response.error);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function copyKey(index) {
|
||||
const key = document.getElementById('key-' + index).value;
|
||||
const value = document.getElementById('value-' + index).value;
|
||||
document.getElementById('key-new').value = key;
|
||||
document.getElementById('value-new').value = value;
|
||||
}
|
||||
|
||||
function deleteKey(index) {
|
||||
const key = document.getElementById('key-' + index).value;
|
||||
const value = document.getElementById('value-' + index).value;
|
||||
const lang = document.getElementById('lang-' + index).value;
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/admin/translate",
|
||||
data: {
|
||||
act: "delete",
|
||||
lang: lang,
|
||||
key: key,
|
||||
value: value,
|
||||
hash: {$csrfToken}
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search + {$scrollTo ? '"&s=" + window.scrollY' : ''};
|
||||
} else {
|
||||
alert(tr("error") + ": " + response.error);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function backToLangSelect(e) {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
document.getElementById('back-to-lang-select-btn').style.display = 'none';
|
||||
document.getElementById('langs-comma-separated').style.display = 'none';
|
||||
|
||||
const langSelect = document.getElementById('lang-select');
|
||||
langSelect.value = 'ru';
|
||||
langSelect.style.display = '';
|
||||
}
|
||||
|
||||
window.scrollTo(window.scrollX, {=$scrollTo});
|
||||
|
||||
function saveAllKeys() {
|
||||
if (!{=$new_key}) return false;
|
||||
|
||||
let keys = [];
|
||||
let values = [];
|
||||
let objects = [];
|
||||
|
||||
let _names = $(".key-l-name").map(function() {
|
||||
keys[$(this).attr('lang')] = this.value;
|
||||
}).get();
|
||||
|
||||
let _values = $(".key-l-value").map(function() {
|
||||
values[$(this).attr('lang')] = this.value;
|
||||
}).get();
|
||||
|
||||
let _languages = Object.keys(keys);
|
||||
_languages.map((language) => {
|
||||
if (language && keys[language] && values[language])
|
||||
objects.push(`${ language}:${ keys[language]}:${ values[language]}`);
|
||||
});
|
||||
|
||||
console.log(objects.join(";"));
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/admin/translate",
|
||||
data: {
|
||||
strings: objects.join(";"),
|
||||
hash: {$csrfToken}
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert(tr("error") + ": " + response.error);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function openKeyInNewTab(keyName) {
|
||||
window.location.href = '/admin/translation?lang=any&q=key:' + keyName;
|
||||
}
|
||||
</script>
|
||||
{/block}
|
|
@ -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}"
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" = "Сохранить все";
|
||||
|
|
Loading…
Reference in a new issue