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 знают
какой язык пользовательского интерфейса в данный момент является
рабочим (текущим).
- Для
каждого namespace
по мере его предстоящего применения FrontEnd
обращается к BackEnd c запросом на загрузку соответствующего файла.
- BackEnd
загружает в свою память полную копию запрошенного словаря в виде
xml-документа (для всех языков) и отвечает уведомлением о готовности
словаря.
- FrontEnd затем запрашивает у BackEnd словарь для текущего
языка.
- BackEnd
собирает термы в формате <идентификатор фразы> - <
значение
фразы> для текущего языка и в виде списка возвращает их во
FrontEnd.
- FrontEnd формирует базу фактов для соответствующего namespace.
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) устанавливаются новые значения
текста.