angle-up arrow-clockwise arrow-counterclockwise arrow-down-up arrow-left at calendar card-list chat check envelope folder house info-circle pencil people person person-plus phone plus question-circle search tag trash x

Уточнение многоязычия: добавление языкового запаса в качестве опции

11 сентября 2019 возле Peter

При отключенном откате языка элемент не отображается, если перевод недоступен.

post main image
unsplash.com/@miabaker

В одном из предыдущих постов я описал первую версию многоязычной базы данных, используемой на этом веб-сайте. Для каждой таблицы, содержащей поля, которые необходимо перевести, мы добавляем таблицу "трансляция" с этими полями. Я также реализовала языковую оплошность: если элемент, например, запись в блоге, не существует на выбранном языке, то отображается элемент языка (общесистемного) по умолчанию. Это хорошо работает, но теперь я хочу добавить дополнительный запасной вариант, позволяющий некоторым сообщениям появляться только на испанском языке, другим только на французском и т.д.

Мы могли бы легко реализовать это, изменив резервный язык на какой-нибудь неизвестный / несуществующий язык. Теперь, если мы добавим сообщение на испанском языке, сообщение на других языках будет недоступно, именно то, что мы хотим. Единственная проблема заключается в том, что у нас больше нет запасного языка. Это проблема?

Рассмотрим следующий случай. У нас есть должность на испанском языке, и мы хотим, чтобы несуществующая должность на французском языке была переведена на английский язык, используемый по умолчанию. Но мы не хотим, чтобы несуществующая должность на немецком языке возвращалась к языку по умолчанию. Это невозможно при нынешней реализации. Как часто будет происходить такое состояние? Не часто, но я могу себе представить, что это может случиться, и хочу подготовиться к этому. Так как же нам это реализовать?

Некоторые запасные варианты

Существует множество вариантов отката для многоязычной базы данных. Нам всегда нужно где-нибудь поменять поговорку:

  • возврат к пункту языка по умолчанию, если для выбранного языка нет перевода.
  • не отступать, что означает, что данный пункт не доступен на выбранном языке.

Для отступления у нас могут быть такие варианты как:

  • падение уровня записи, например, заголовок, описание
  • отступление с полевого уровня, например, название

Мы тоже можем иметь:

  • однократное отступление, например, существует только один язык отступления.
  • рекурсивное отступление, например, если нет на португальском языке Испанский язык, если нет на испанском языке Испанский язык Используется Английский язык

Отступающий язык может быть:

  • системный
  • на рекордном уровне
  • на местном уровне

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

Делая выбор

Вернемся к основам. Я хочу для этого сайта:

  • Поддержка нескольких языков
  • Существует системный язык по умолчанию
  • Существует системный язык отката.
  • Действием по умолчанию должно быть языковое отклонение.
  • Для каждого языкового элемента должна быть возможность отключить языковое отставание, делая его недоступным, если он не опубликован.

Я определил следующие языки:

  • language_selected: используемый язык
  • language_default: язык по умолчанию, статическая настройка в масштабе всей системы.
  • language_fallback: резервный язык, динамическая настройка для всей системы в зависимости от выбранного языка.

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

Пример №1: отступление от Франкфурта на Франкфурт-на-Майне

  • язык по умолчанию: en-US
  • выбранный язык: fr-BE
  • запасной язык: fr-FR

Пример №2: es-ES с резервированием до en-GB

  • язык по умолчанию: en-GB
  • выбранный язык: es-ES
  • запасной язык: en-GB

Осуществление

Я хочу, чтобы запросы не замедляли работу системы, но если это произойдет, мы всегда можем добавить 'кэширование результатов запросов' и/или препроцессирование запросов.

На данный момент существует две таблицы элементов содержимого:

  • Содержание
  • Перевод содержания

Мы всегда начинаем с выбора одной или нескольких записей из ContentItem. Если запись ContentItemTranslation отсутствует (или опубликована) для выбранного языка, то мы должны отступить, но только если для этого пункта включен режим отката. Время просмотреть запрос, используемый для извлечения элементов. Мы различаем две возможности:

  • ContentItemTranslation записи не существует.
  • существует запись ContentItemTranslation

В обоих случаях мы можем вступить в профсоюз, первая часть получает запись для выбранного языка, а вторая часть получает язык для языка по умолчанию. Сложность заключается в том, как мы отключаем отступление. Когда запись ContentItemTranslation существует, мы можем поставить флаг 'do_not_fallback' здесь, но что если записи ContentItemTranslation не существует? Тогда где-то нам нужен другой специфический флаг языка, и снова этот флаг может существовать, а может и не существовать.

Одним из способов сделать это является добавление другой таблицы ContentItemTranslationDoNotFallback, содержащей только один флаг = do_not_fallback. Как и ContentItemTranslation, записи ContentItemTranslationDoNotFallback могут присутствовать или отсутствовать. Тогда у нас есть три стола:

  • Содержание
  • Перевод содержания
  • Перевод контента без обратного отскока

Мы хотим, чтобы действием по умолчанию было откат к языку отката, если "записи перевода не существует". Все это выглядит довольно просто. Но это также вводит дополнительную запись новой таблицы, которая может присутствовать, а может и не присутствовать.

Другим способом достижения желаемого является добавление флага 'do_not_fallback' к записи перевода. Я имею в виду, если нам нужно добавить запись в новую таблицу ContentItemTranslationDoNotFallback для указания 'do_not_fallback', то почему бы не использовать запись таблицы ContentItemTranslation для этой цели? Если его нет, мы добавляем его, в противном случае мы просто используем его.

В этом случае действием по умолчанию все еще является переход на резервный язык, если запись 'translation' отсутствует. Теперь мы вставляем запись перевода не только тогда, когда у нас есть переводческая ценность, но и тогда, когда мы хотим отступить для этой записи. В записи перевода мы используем опубликованный / активный флаг, чтобы указать, существует ли перевод. В таблице ниже приведены действия для выбранного языка.


Содержание таблицы Перевод таблиц
публикация ContentItemTranslation
(Перевод содержания)
ContentItemTranslation
do_not_fallback
Действие
1 Не существует - - Нет переведенного значения, запасной вариант.
2 Существует Правда Правда или ложь. Использовать переведенную стоимость
3

Существует

Ложь Ложь Нет переведенного значения, запасной вариант.
4 Существует Ложь Правда Нет переведенного значения, нет запасного варианта, элемент недоступен.

SQL спрашивать

Ниже приведена реализация возможных SQL запросов. Обратите внимание, что я использую SELECT's и UNION здесь, которые обеспечивают легкий выбор объектов при использовании SQLAlchemy запроса. В текущей реализации у меня два флага, удаленные и статусные, которые должны быть False. Содержимое_item_parent_id = 0 означает основную запись перевода.

SET @language_fallback_id = 1;
SET @language_selected_id = 2;

### [1] = fallback: translation record does not exist

(
SELECT 
  ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE 
  ci.content_item_type = 1
  AND ci.content_item_parent_id = 0
  AND ci.published = 1
  AND cit.content_item_id = ci.id
  AND cit.content_item_parent_id = 0
  AND cit.published = 1
  AND cit.language_id = @language_fallback_id
  AND NOT EXISTS
    (
    # return 1 when exists
    SELECT 1 
    FROM content_item_translation cit2
    WHERE 
          cit2.content_item_id = ci.id
      AND cit2.content_item_parent_id = 0
      AND cit2.language_id = @language_selected_id
      )
)

UNION ALL

### [2] = use translation: translation record exists and published = True

(
SELECT 
  ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE 
  ci.content_item_type = 1
  AND ci.content_item_parent_id = 0
  AND ci.published = 1
  AND cit.content_item_id = ci.id
  AND cit.content_item_parent_id = 0
  AND cit.published = 1
  AND cit.language_id = @language_selected_id
)

UNION ALL

### [3] = fallback: translation record exists and published = False & do_not_fallback = False

(
SELECT 
  ci.id ci_id, cit.id cit_id, cit.title cit_title
FROM content_item ci, content_item_translation cit
WHERE 
  ci.content_item_type = 1
  AND ci.content_item_parent_id = 0
  AND ci.published = 1
  AND cit.content_item_id = ci.id
  AND cit.content_item_parent_id = 0
  AND cit.published = 1
  AND cit.language_id = @language_fallback_id
  AND EXISTS
    (
    # return 1 when published = False & do_not_fallback = False
    SELECT 1 
    FROM content_item ci2, content_item_translation cit2
    WHERE 
          ci2.id = ci.id
      AND ci2.content_item_type = 1
      AND ci2.content_item_parent_id = 0
      AND ci2.published = 1
      AND cit2.content_item_id = ci2.id
      AND cit2.content_item_parent_id = 0
      AND cit2.published = 0
      AND cit2.do_not_fallback = 0
      AND cit2.language_id = @language_selected_id
      )
)

ORDER BY ci_id DESC;

Ведь SQLAlchemy я решил на данный момент использовать необработанные квадратные листы для подсчета и пропуска страниц, а затем использовать эти листы для выбора объектов с помощью второго запроса. Да, я немного устал переводить на SQLAlchemy все времена, это занимает много времени и для чего? Тем не менее, я считаю, что использование может помочь значительно сократить код, но не всегда. Значит, это правильное использование.

Администратор

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

Опубликовано

Не опубликовано, запасной вариант.

Не опубликовано, нет запасного варианта, не доступно.

Резюме

Мы добавили опцию 'do_not_fallback' к элементам содержимого нашей базы данных. Это создало дополнительную сложность, но дополнительные накладные расходы выглядят не очень массивными. По сравнению с ситуацией, когда мы всегда возвращались к языку по умолчанию, изменения таковы:

  • Подсчет записей (позиций) больше не является подсчетом количества записей для языка по умолчанию.
  • Когда мы просматриваем элемент и переключаем язык на невозвратный элемент, не имеющий перевода, мы должны показать, что этот элемент недоступен на выбранном языке.
  • Функции "наиболее просматриваемые" (записи блога), "поиск" (записи блога) и т.д. теперь должны также фильтроваться по недоступным элементам.

Опять же, много работы, но я чувствую, что теперь это многоязычная функциональность, которую я могу использовать для оставшейся части этого проекта.

Ссылки / кредиты

Fallback
https://www.i18next.com/principles/fallback

Language Fallback
http://sitecore-masters.com/en/language-fallback/

Multi Language i18n & Localization in Pimcore
https://pimcore.com/docs/5.x/Development_Documentation/Multi_Language_i18n/index.html

Подробнее:
Multilanguage

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.