Перевод строк с html-разметкой в Vue I18n

Если в вашем Vue-приложении переводы работают через Vue-i18n, то вы наверняка сталкивались с ситуациями, в которых приходилось добавлять перевод для фразы, которая содержит html-разметку. Чаще всего это ссылки (тег a) или строковые контейнеры (тег span), позволяющие сделать акцент на определенных частях фразы. В таких ситуациях самым легким решением кажется добавить фразу вместе с разметкой в JSON с переводами, а вывод сделать через v-html. Давайте посмотрим какие с этим есть проблемы и как можно сделать лучше.

Содержание

  • Полезные ссылки
  • Проблемы решения с v-html

    Несмотря на кажущиеся безобидность и удобство, у решения с v-html есть ряд недостатков. Давайте рассмотрим их поближе.

    Наш код:

    {
      "payment_message": "Вы можете <a href='charge.php'>оплатить заказ</a> в течение <span>{minutes}</span> мин."
    }
    <template>
      <p v-html="t('payment_message', { minutes: 2 })"></p>
    </template>
    
    <script setup>
    import { useI18n } from 'vue-i18n';
    
    const { t } = useI18n();
    </script>

    Трудность с дальнейшей модификации

    Если бы ваша разметка была внутри компонента, то при необходимости ее немного изменить (например, поменять какой-то css-класс), вы бы просто поменяли компонент. В случае с разметкой внутри фразы в файлах локализации придется менять это во всех переводах. Получится количество изменений равное числу поддерживаемых языков.

    Необходимость экранирования некоторых символов

    Переводы могут содержать <, >, & и другие специальные символы, которые нужно экранировать или заменять на html enitities (&lt;, &gt;, &amp;) если вы используете v-html. Это делает переводы менее удобными для редактирования.

    Нельзя использовать компоненты вместо html-элементов

    Если в определенный момент вам нужно будет заменить какой-то тег, на Vue-компонент, то это не будет работать. Vue не может работать с фразой как с шаблоном.

    XSS-уязвимости

    Если вы используете v-html для вывода переведенной строки, злоумышленник может воспользоваться этим для внедрения вредоносного кода, при условии, что он имеет возможность изменять переводы. Такая опасность может возникнуть, если ваши переводы хранятся не в статическом файле, а передаются через API (хранятся в БД, например).

    Для того, чтобы уязвимостью воспользоваться надо также иметь проблемы с безопасностью в других местах (например, уязвимости интерфейса редактирования фраз). Но если мы плюем на безопасность здесь, плюем на безопасность там, то, вероятно, лазеек для злоумышленника будет в избытке.

    Решение 2. Разбиваем строку на части

    Описание решения

    И так, если мы выбрали не использовать v-html, то возможны некоторые варианты решений. Например, мы могли бы разбить строку на части, разметку расположить в коде компонента, а фразу собирать из частей вокруг тегов html-разметки.

    Предположим нам надо вывести следующую строку:

    Вы можете <a href="charge.php">оплатить заказ</a> в течение <span>2</span> мин.

    И тут нам понадобится не менее 4 отдельных фраз в файле с переводами, чтобы составить эту строку из частей:

    {
      "payment_message": {
        "text_before_link": "Вы можете",
        "link_text": "оплатить заказ",
        "text_after_link": "в течение",
        "time_unit": " мин."
      }
    }

    В итоге в компоненте строка формировалась бы так:

    <template>
      <p>
        {{ t("payment_message.text_before_link") }}
        <a href="charge.php">{{ t("payment_message.link_text") }}</a>
        {{ t("payment_message.text_after_link") }}
        <span>{{ minutes }}</span>{{ t("payment_message.time_unit") }}
      </p>
    </template>
    
    <script setup>
    import { ref } from "vue";
    import { useI18n } from "vue-i18n";
    
    const { t } = useI18n();
    const minutes = ref(2);
    </script>

    Плюсы и минусы решения

    Основные проблемы этого варианта:

    • Фрагментов строк слишком много, сложнее работать
    • Строка в JSON не представляет собой единое целое, прочитать такую строку при переводе сложнее
    • В некоторых языках правильный порядок слов (частей фразы) будет отличаться и эту проблему вы не решите

    Демонстрация последнего минуса:

    Вы можете <a href="charge.php">оплатить заказ</a> в течение <span>2</span> мин.
    <span>2</span>分以内に<a href="charge.php">注文を支払う</a>ことができます。

    Как такое поддерживать в этом варианте?

    Однако мы избавились от проблем v-html, что уже немалый плюс.

    Решение 3. Используем i18n-t

    Есть решение лишенное всех предыдущих недостатков — компонент i18n-t. Он позволят использовать синтаксис слотов, чтобы вставлять в основную фразу нужные части.

    Описание решения

    Посмотрим на наше примере.

    {
      "order_text": "оплатить заказ",
      "payment_message": "Вы можете {order_action} в течение {minutes} мин."
    }
    {
      "order_text": "注文を支払う",
      "payment_message": "{minutes}分以内に{order_action}ことができます。"
    }
    <template>
      <i18n-t keypath="payment_message" tag="p">
        <template #order_action>
          <a href="charge.php">{{ t("order_text") }}</a>
        </template>
        <template #minutes>
          <span>{{ minutes }}</span>
        </template>
      </i18n-t>
    </template>
    
    <script setup>
    import { ref } from "vue";
    import { useI18n } from "vue-i18n";
    
    const { t } = useI18n();
    const minutes = ref(2);
    </script>
    

    Как вы можете видеть, здесь есть основная фраза в payment_message, которая выводится благодаря i18n-t и указанию пути к фразе в keypath. А также здесь есть текст ссылки в order_text. Необязательный атрибут tag="p" указывает в какой тег нужно обернуть строку.

    Плюсы и минусы решения

    Здесь у нас сплошные плюсы. Решены проблемы предыдущих способов.

    • Нет XSS уязвимостей
    • Строка сохранила свою целостность в файлах локализации. Отдельно вынесен только текст ссылки. С этим легко работать
    • Строк не так много как в предыдущем варианте
    • Можно иметь свой порядок слов в любом языке
    • Если надо немного изменить разметку (например, добавить css-класс к ссылке), то это можно сделать в 1 месте — компоненте. Фразы в файлах с переводами меняться не будут

    Минус пока видно только один — нужно ознакомиться с тем как это работает и понимать как используются слоты.

    Полезные ссылки

    0 0 голоса
    Рейтинг статьи
    guest
    0 комментариев
    Старые
    Новые Популярные