Анализ механизмов локализации интерфейса приложений в Splunk

45bbp_6l7x3giweocj78ubd3lpa.jpeg
В данной статье мы рассмотрим основной механизм локализации интерфейса приложений Splunk (в т.ч. стандартных элементов приложения Search) — gettext internationalization and localization (i18n).
Возможности для перевода:

  • интерфейс Splunk;
  • дашборды (заголовки панелей и полей ввода);
  • выбор статических ресурсов (картинки, CSS и т.д.) на основе текущей локали, например: logo-ru_RU.gif или logo-en_GB.gif.


Для локализации необходимо проделать несколько шагов:

  1. Создать новую локаль в Splunk (при необходимости).
  2. Сформировать файлы-словари перевода.
  3. При помощи JavaScript подгрузить переведённые поля.


Также мы дополнительно переведём результаты поиска на примере уязвимостей OpenVAS, что хоть и не относится к самой системе перевода, однако хорошо дополняет общую картину.

Итак, перейдём к практике.

Создание новой локали в Splunk


Напомню, по умолчанию в Splunk используется en-US локаль, в этом можно убедиться в строке браузера: MYSERVER:8000/en-US/app/search (hint: если вручную поменять en-US на en-GB, то время и дата будут отображаться в более привычном виде).

Для начала сделаем новую локаль для России. Копируем существующую локаль:

sudo cp /opt/splunk/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/en_GB /opt/splunk/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/ru_RU -ru


Перезапускаем Splunk:

sudo /opt/splunk/bin/splunk restart


И проверяем:

MYSERVER:8000/ru-RU/app/launcher/home


Создание нового приложения


Создаём новое приложение с названием testapp (Name\Folder name: testapp):

MYSERVER:8000/ru-RU/manager/launcher/apps/local/_new?action=edit&ns=launcher


Создание файлов перевода


Теперь перейдём непосредственно к формированию файлов перевода.

Мы можем перевести существующее приложение, сделаем это для только что созданного:

sudo /opt/splunk/bin/splunk extract i18n -app testapp


После этого у нас появляется файл /opt/splunk/etc/apps/testapp/locale/messages.pot, который содержит в себе все поля, которые используются в приложении, т.к. у нас приложение новое, там будет только один элемент — ссылка на имя приложения.

Регистрируемся на сайте poeditor.com.

Добавляем русский язык (Add Language):

3i4sqrrxcfxmniaimpdfsuw9o0a.png

Создаём новый проект и импортируем (import terms) наш файл /opt/splunk/etc/apps/testapp/locale/messages.pot:

w8uk50aqj08kttq13xx4vu0ltja.png

Добавим несколько полей при помощи кнопки Add Term:

n6ycafzgagxcnaauwaqlr6cco1a.png

Справа от каждого из них есть кнопка добавления перевода:

uo5vm8ieehjc0p675u12s_7coee.png

Воспользуемся и переведём наши поля:

wp-2wnrwdhtp6mia6jrirupassu.png

Переходим в проект, выбираем русский язык и экспортируем в ДВА формата: .po и .mo:

jfllq5uassnzk6jyqekvybfxh3k.png

Создаём папку с соответствующей локалью и копируем туда наши файлы, переименовав их в messages.po и messages.mo соответственно:

sudo mkdir /opt/splunk/etc/apps/testapp/locale/ru_RU/LC_MESSAGES
suco cp messages.* /opt/splunk/etc/apps/testapp/locale/ru_RU/LC_MESSAGES


Перезагружаем Splunk:

sudo /opt/splunk/bin/splunk restart.


Заходим в наше приложение и убеждаемся, что отображаемое имя приложения переведено:
localhost:8000/ru-RU/app/testapp/search

gylrapttqgctmqkla4ljzopasdo.png

JS скрипт


Следующим шагом будет написание JS-скрипта, который будет заменять токены на соответствующие локали поля. Сразу заложим возможность перевода на несколько языков: русский и немецкий.

Создаём папку, откуда будет загружаться скрипт:

sudo mkdir /opt/splunk/etc/apps/testapp/appserver
sudo mkdir /opt/splunk/etc/apps/testapp/appserver/static


Пишем сам скрипт в /opt/splunk/etc/apps/testapp/appserver/static/dashTranslate.js:

require([
    'jquery',
    'underscore',
    'splunk.i18n',
    'splunkjs/mvc',
    'splunkjs/mvc/simplexml/ready!'
  ], function ($, _, i18n, mvc) {      
      var defaultTokens = mvc.Components.get("default");
      var envTokenModel = mvc.Components.getInstance("env");
      
      	   if (envTokenModel.get("locale") != "ru-RU" || envTokenModel.get("locale") != "de-DE") {
    	      defaultTokens.set("form.t_locale", "ru-RU"); // если текущая локаль не из поддерживаемого списка, будем переводить на стандартный язык (русский), это сделано только для перевода результатов поиска (забегая вперёд, скажу, что это будет сделано при помощи lookup полей, которые будут иметь суффикс в виде локали, в противном случае можно напрямую использовать $env::locale$ токен)
    } else {
      defaultTokens.set("form.t_locale", envTokenModel.get("locale")); // set locale token
    }
      
// если перевода для текущей локали нет, то будем использовать стандартное значение
      if (i18n._("openvasTitle") == "openvasTitle") {
        defaultTokens.set("form.t_openvasTitle", "OpenVAS Events"); // default value
      } else {
        defaultTokens.set("form.t_openvasTitle", i18n._("openvasTitle")); // translated value
      }
      
      //перевод названий столбцов. значение в виде имени переменной нас вполне устроит как стандартное значение, поэтому не будем задавать его отдельно
      defaultTokens.set("form.t_signature", i18n._("signature"));
      defaultTokens.set("form.t_description", i18n._("description"));
      defaultTokens.set("form.t_count", i18n._("count"));
  
    });


Делаем дашборд


Создаём новый дашборд и добавляем в него (исходники дашборда для ленивых в конце):

  • Input text — Name: count, Token: t_count
  • Input text — Name: openvasTitle, Token: t_openvasTitle
  • Input text — Name: signature, Token: t_signature
  • Input text — Name: description, Token: t_description


Без инпутов не будут работать form.* токены, что не позволит передавать полноценное состояние дашборда в URL. Чтобы они не мозолили глаза, добавляем «depends=»$nothing$» в каждый из них.

Panel, statistic table — Title: $t_openvasTitle$

index=openvas
| stats count by "NVT Name" Summary
| rename "NVT Name" AS $form.t_signature$ Summary AS $form.t_description$ count AS $form.t_count$


Panel, pie chart — Title: $t_openvasTitle$

index=openvas
| stats count by "NVT Name"
| rename "NVT Name" AS $form.t_signature$ count AS $form.t_count$


Добавляем наш скрипт на дашборд — меняем первую строку на:


Тут есть небольшая особенность — Splunk переводит известные ему поля (count, signature в нашем случае) в текстовых полях инпутов или заголовках.

Режим редактирования:

tgcybnohhco6n0vfd_bnyqsrh48.png

qvg9k10xhrneiowxzufqqfksgde.png

Обычный режим:

1j7_dbiavuylneueootpeh0ac6w.png

1nyy3hsdavyw0eqprdi31tjgutm.png

Однако надо держать в голове то, что при повторном редактировании панели выводиться будет перевод, и если случайно, например, поставить пробел, то в исходниках вместо переменной (openvasTitle или description в нашем примере) будет сохранён текст («События OpenVAS_» или «Описание_» в нашем примере). Плюс при редактировании не ясно, что это, на самом деле, перевод, поэтому лучше использовать токены ($form.t_openvasTitle$ и $form.t_description$ в нашем примере).

В режиме повторного редактирования: верхний заголовок — токен $form.t_openvasTitle$, нижний — текст openvasTitle.

cdtpnjxpmvnawvdnn-pr1xhmqpo.png

Как итог, у нас получился дашборд

j8uorimwsoisbczoyybivutrsj4.png

Ограничения


Есть два ограничения, о которых стоит знать:

  1. Нельзя переводить заголовки дашбордов.
  2. Нельзя задавать начальные значения токенов в init\set, т.к. выполняются после скрипта-перевода (можно вынести JS на более позднюю загрузку, но тогда придётся конвертировать в HTML).


Перевод результатов поиска


Для вывода конечным пользователям текстовых данных предлагается использовать lookup с переводом на любое количество языков. В этом примере мы как раз используем глобальный токен Splunk $env: local$. Идентификатором уязвимости является хеш, по которому мы подключаем перевод. Также отмечу, что если перевод будет пустой, то будет использовано начальное значение. Добавим новый static table в наш дашборд:

index="openvas"
| eval signature=replace(signature, "[\n\r]", " ")
| eval description=replace(description, "[\n\r]", " ")
| eval hash=md5(signature.description)
| stats count by signature description hash
| lookup OpenVAS_translate hash AS hash OUTPUT signature_$env:locale$ AS signature_$env:locale$ description_$env:locale$ AS description_$env:locale$
| eval signature=coalesce('signature_$env:locale$', signature) 
| eval description=coalesce('description_$env:locale$', description)
| fields - signature_$env:locale$ description_$env:locale$ count hash
| rename signature AS $form.t_signature$  description AS $form.t_description$


Файл OpenVAS_translate.csv выглядит следующим образом (для работы с лукапами советую использовать приложение Lookup Editor):

dacckuwho3nmyxhqsemxwespvzy.png

Таким образом, при наличии перевода он будет произведён исходя из текущей локали.
ru-RU:

iorhypqeutqf2j7et-xb9ptlxxw.png

de-DE:

9mevc2x8zndg9ia9ngz_rjzwbzo.png

Перевод стандартного интерфейса Splunk


Перевод интерфейса Splunk осуществляется аналогичным образом -необходимо перевести в poeditor файл $SPLUNK_HOME/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/messages.pot и скопировать два получившихся файла (messages.mo и messages.po) в папку:$SPLUNK_HOME/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/ru_RU/LC_MESSAGES/

Все, кого интересуют актуальные вопросы по Splunk, мы приглашаем присоединиться к нашему каналу в Telegram.

Список используемых источников:

docs.splunk.com/Documentation/Splunk/6.4.1/AdvancedDev/TranslateSplunk
docs.splunk.com/Documentation/Splunk/7.2.6/Viz/tokens
splunkonbigdata.com/2018/11/01/creating-a-splunk-locale
answers.splunk.com

Итоговый код дашборда:


  
  description
  
$form.t_openvasTitle$ openvasTitle index=openvas | stats count by "NVT Name" Summary | rename "NVT Name" AS $form.t_signature$ Summary AS $form.t_description$ count AS $form.t_count$ 0
$form.t_openvasTitle$ index=openvas | stats count by "NVT Name" | rename "NVT Name" AS $form.t_signature$ count AS $form.t_count$ 0 index="openvas" | eval signature=replace(signature, "[\n\r]", " ") | eval description=replace(description, "[\n\r]", " ") | eval hash=md5(signature.description) | stats count by signature description hash | lookup OpenVAS_translate hash AS hash OUTPUT signature_$env:locale$ AS signature_$env:locale$ description_$env:locale$ AS description_$env:locale$ | eval signature=coalesce('signature_$env:locale$', signature) | eval description=coalesce('description_$env:locale$', description) | fields - signature_$env:locale$ description_$env:locale$ count hash | rename signature AS $form.t_signature$ description AS $form.t_description$ 0

© Habrahabr.ru