Copyright (c) Prolog Development Center SPb

Application Frame. Поддержка словарей   

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

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

Необходимость использования словарей устанавливается в конфигурации приложения Bin\<имя приложения>AppData\Options.xml  
узел <fe_options>.<group>.<language> аттрибут use-dictionary.
Если в конфигурации приложения установлено, что словари не используются, то весь механизм словарей блокируется и приложение использует текстовые значения, установленные в коде приложения.

Структура файла словаря

Словарь вне приложения представляет собой XML-файл структура которого представлена ниже

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<language namespace="basic" version="10.06.2019">
  <language_list dk="Danish" eng="English" rus="Русский" />
  <row id="ribbon.default.section.basic" meaning="Help Section with About and Features commands">
    <dk>Help</dk>
    <rus>Помощь</rus>
    <eng>Help</eng>
  </row>
  <row id="ribbon.default.section.basic.tooltip" meaning="Ribbon.Section.Default tooltip, when no ribbon file exists">
    <dk>Help</dk>
    <rus>Помощь</rus>
    <eng>Help</eng>
  </row>
    ...

Аттрибут namespace узла language определяет имя пространства имен ("basic" в данном случае). Аттрибут version определяет имя версии правил формирования словаря. Имя версии представляется датой, когда данные правила были выработаны и применены ("10.06.2019" в данном случае).

Узел language_list  перечисляет языки, которые пространство имен basic поддерживает. При этом имя аттрибута (как в данном случае - eng, dk, rus) показывает как обозначается язык для соответствующего представления фразы. Значения аттрибута языка являются пояснениями.

Каждый узел row определяет одну фразу. Аттрибут id узла row  определяет идентификатор фразы. Аттрибут meaning  определяет смысловое содержание фразы и к чему эта фраза относится в пользовательском интерфейсе. Формальные правила для аттрибута meaning не установлены - это текст, смысл которого определяет пользователь или разработчик. Идентификатор фразы может быть любым текстовым значением, хотя в ядре Application Frame принято структурированное представление.

Подузлы узла row перечисляют как должна выглядеть фраза на соответствующем языке. Если язык всего один, то подузел для этого языка все равно должен существовать.

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

Общая схема работы со словарями

Общая схема работы со словарями выглядит так


Файлы словарей в xml представлении  хранятся в директории xxxAppData приложения. Каждый компонент пользовательского интерфейса "знает" имя файла, в котором содержится его namespace и само имя namespace.

Когда приложение стартует, если в конфигурации установлено использование словарей, FrontEnd приложения определяет какие namespace требуются для текущего содержимого пользовательского интерфейса.
FrontEnd и BackEnd знают какой язык пользовательского интерфейса в данный момент является рабочим (текущим).
FrontEnd пользуется текущей базой фактов для текущего языка. Если язык пользовательского интерфейса меняется, FrontEnd уничтожает базу текущих фактов, обращается к BackEnd за фактами текущего языка и процесс протекает как описано выше.
Если FrontEnd актуализирует новый элемент пользовательского интерфейса (например, форму или диалог) то работает все тот же механизм. Когда форма или диалог становятся неактуальными, FrontEnd может удалить базу соответствующих фактов и обратиться в BackEnd с требованием удаления соответствующего словаря.
Таким образом словари могут актуализироваться и удаляться по мере необходимости или оставаться актуальными пока приложение активно.

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

На стороне BackEnd все операции словаря обеспечиваются объектом класса be_Dictionary.
На стороне FrontEnd все операции словаря обеспечиваются объектом класса fe_Dictionary.

Application Frame всегда при старте актуализирует namespace "basic" и использует файл Basic_Dictionary.xml.
Если разработчик приложения не желает создавать динамически загружаемые словари, он может пользоваться всего одним словарем, в котором содержатся  все фразы приложения.
Если файла Basic_Dictionary.xml нет, то namespace "basic" внутри FrontEnd создается только для одного базового языка из фактов, хранящихся в пакете Common\AppFrontEnd\defaultDictionary\defaultDictionary.pack. Файл Basic_Dictionary.xml при этом создается автоматически для возможности последующей модификации.

Надо отметить, что если для заданного namespace словарь в виде XML файла существует, то XML-файл  будет проверен на соответствие набору идентификаторов. Если обнаруживается, что используется новый идентификатор, то он будет добавлен в файл словаря и будут добавлены значения для всех поддерживаемых языков. Фразы для языков, отличных от базовового будут повторять значения базового языка. Пользователь затем может редактировать все фразы для всех языков.

Детали реализации и применения

Одной из возможностей поддержания словаря является сопровождение файлов пакета базового словаря Common\AppFrontEnd\defaultDictionary\defaultDictionary.pack
Здесь важно представлять структуру данных пакета
Основная информация содержится в файлах defaultDictionary.i и defaultDictionary.pro

В файле defaultDictionary.i записаны константы идентификаторов текстовых фраз

constants

    ribbon_default_section_basic_C = "ribbon.default.section.basic".
    ribbon_default_section_basic_tooltip_C = "ribbon.default.section.basic.tooltip".

В файле defaultDictionary.pro записаны:

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

facts

    nameSpace_P : string := "basic".
    fileName_P : string := @"febeAppData\Basic_Dictionary.xml".

а далее записаны собственно определения фраз, имеющих эти идентификаторы, определенные в файле
defaultDictionary.i.

facts
    item_F : (string ItemID, string ItemString, string Meaning).

clauses
    item_F(ribbon_default_section_basic_C, "Help", "Help Section with About and Features commands").
    item_F(ribbon_default_section_basic_tooltip_C, "Help", "Ribbon.Section.Default tooltip, when no ribbon file exists").

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

Загрузка и использование словарей

Для того, чтобы BackEnd загрузил нужный словарь из XML-файла FrontEnd использует предикат

    add_DictionaryNameSpace:(string NameSpace,string DictionaryXmlFile).

Тогда нужный словарь появится и в объекте класса be_Dictionary и в объекте класса fe_Dictionary (для текущего языка).

Кроме того словарь, уже загруженный в BackEnd может со стороны FrontEnd быть запрошен предикатом


    fe_CoreTasks():getDictionary(<NameSpace>)

Наконец, предикатом

    fe_CoreTasks():getDictionary()

все словари, загруженные в BackEnd будут загружены во FrontEnd в объект класса fe_Dictionary

В результате нужный словарь для текущего языка появится в объекте класса fe_Dictionary и получение фразы выполняется
простейшим запросом

    Phrase=fe_Dictionary():getStringByKey(string::concat(<NameSpace>,@"\",<идентификатор фразы>),<значение по умолчанию>)


Здесь делается обращение к объекту fe_Dictionary, а в качестве префикса в идентификаторе текста используется <NameSpace>\.
Последний параметр предиката getStringByKey устанавливает значение по умолчанию, которое будет использоваться, если в словаре не найдется определение текстового фрагмента по заданному идентификатору.

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

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

С этой целью в состав Application Frame включен шаблон класса словаря SpbVipTools\AppData\SourceTemplates\spbDictionary\SpbDictionary.pack

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

В качестве примера посмотрим не очень простую процедуру создания и персонального словаря для элемента пользовательского интерфейса класса Dialog (пакет словаря optionsDlg_Dictionary.pack уже создан).

В самом диалоге создание объекта приводит к созданию объекта конкретного словаря и его сохранение здесь же


    new(Parent, FrontEnd, _SettingsList) :-
        fe_Connector::new(FrontEnd),
        dialog::new(Parent),
        generatedInitialize(),
        coreDictionary_P:=optionsDlg_Dictionary::new(). <- создается объект словаря

А вот как этот диалог создается и используется извне:


clauses
    editOptions(PerformParams):-
        Dialog=fe_Options::new(convert(window,fe_AppWindow()),frontEnd_P,PerformParams),
        % подготовка словаря для диалога
        Dialog:dictionary_P:=fe_Dictionary(), <- помещаем в диалог ссылку на объект fe_Dictionary
        fe_CoreTasks():initCoreDictionary(Dialog:coreDictionary_P), <- выполняем загрузку словаря из BackEnd, если его нет, то
                                                                                                BackEnd запросит факты словаря из созданного диалога и создаст
                                                                                                файл словаря
        Dialog:initData(), <- устанавливаются все текстовые заголовки
        Dialog:show().

В объекте диалога с использованием загруженного словаря предикатом initData() текстовые фразы будут расставлены на элементы UI.

clauses
    initData():-
        if fe_Dictionary():useDictionary_P=useDictionaryNo_C then
            %  Если словарь не используется, то ничего не делается
        else
            NameSpace=coreDictionary_P:nameSpace_P,
            setText(fe_Dictionary():getStringByKey(string::concat(NameSpace,@"\",dialog_options_title_C),"Options")),
            cancel_ctl:setText(fe_Dictionary():getStringByKey(string::concat(NameSpace,@"\",dialog_options_pb_Cancel_C),"Cancel")),
            ok_ctl:setText(fe_Dictionary():getStringByKey(string::concat(NameSpace,@"\",dialog_options_pb_ok_C),"Ok"))
        end if.

В предикате initData() элементам управления (в данном случае заголовку и кнопкам Cancel и Ok) устанавливаются новые значения текста.