Juri Shutenko Personal Homepage. Visual FoxPro.

Приложения с многоязычными интерфейсами пользователя (MUI). Часть U.

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

Для удобства пользователя во всех приложениях обеспечивается переключение языка интерфейса "на лету" с одним ограничением - в случае, если язык интерфейса не совпадает с языком системы, вместо системного меню используется загрузочная форма, из которой производится запуск отдельных модулей приложения. При этом выполняется одно условие, которое, по моему скромному разумению, должно иметь каждое приложение, использующее поддержку многоязычия пользовательского интерфейса - приложение запускается в том языке, который пользователь либо установил для себя в качестве стартового, либо, если в опциях приложения не отмечен чек-бокс - "Start in Default Language", в языке, в котором пользователь последний раз завершил работу с приложением. Организовать все это достаточно просто и в этом цикле статей я разберу мои принципы построения многоязычных приложений.

Формы и элементы управления, поддерживающие MUI - MUI Enabled Forms and Controls

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

Формы с поддержкой MUI

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

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

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

  • fvc_LanguagesList - символьного типа
  • fvc_CurrentMUILanguage - символьного типа
  • fvl_UseMUI - логического типа

Далее. Совершенно очевидно, что понадобятся таблицы трансляций. Что нужно транслировать в многоязычном интерфейсе? Ну, во-первых, заголовки - captions - элеметов управления - controls. (Далее по тексту статей я буду применять сокращение ЭУ). Во-вторых, сообщения пользователю, пояснения, выводимые посредством ToolTips и тому подобное. Для выполнения этого требования лучше использовать раздельные таблицы, структуру и содержание которых рассмотрим чуть позже.

Если посмотреть на ЭУ, доступные в Visual FoxPro, то список элементов, заголовки которых должны транслироваться в используемый пользователем язык интерфейса, включает в себя следующие (в алфавитном порядке):

  • Checkbox
  • CommandButton
  • Header - (grid)
  • Label
  • OptionButton
  • Page

Необходимое отступление.
 
В своих приложениях я строго придерживаюсь соглашения по наименованию, предложенного разработчиками рыжего хищника, которое дополнено своими правилами, связанными, кроме всего прочего, с использованием динамических форм. Например, командная кнопка, назначением которой является вызов процедуры для сохранения данных (обработка сохранения может производиться в теле метода, ассоциированного с событием Click) во всех моих приложениях будет называться "cmd_Save", а checkbox может называться "chk_UseServerAliasServerNameInstead". Такое название может показаться длинным, однако само название уже содержит в себе caption для случая, если имя ЭУ не найдено в таблице трансляции. Например, контейнер для формы FTP клиента выглядит в дизайнере форм так:

Container for FTP Client

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

Вернемся к таблицам. Очевидно, что такие таблицы должны поставляться с приложением, но должны быть редактируемыеми, то есть на соответствующей закладке проекта они должны быть помечены как "excluded". Кроме того, если приложение предполагает модификацию/обновление в дальнейшем, то в приложении мы не должны нигде жестко привязываться к ним. Поэтому в классе формы необходимо предусмотреть свойства, в которых будут храниться имена таблиц трансляции. Имена эти могут изменяться, но данные о них поставляются в специальном ini-файле, которые предназначен исключетельно для записи параметров, связанных с поддержкой многоязычия в интерфейсе. В моих формах это свойства:

  • fvc_CaptionsTranslationTable
  • fvc_MessagesTranslationTable

Как я уже оговорился в отступлении, в приложениях используется специальный класс, на базе "custom", в котором сосредоточена вся обработка поддержки MUI, так как нет смысла плодить одни и те же процедуры поддержки в пригодных для этого ЭУ.

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

В качестве идентификаторов языка я использую общепринятые сокращения, например:

  • en - English
  • ru - Русский
  • et - Eesti

Второе свойство - fvc_CurrentMUILanguage используется для хранения текущего языка интерфейса пользователя. Установка этого свойства осуществляется либо через нажатия соответствующих этикеток на специальной полоске, размещаемой в самом верху формы, либо через пункт меню - "Options->Change UI language". Для этого свойства определен метод Assign, в теле которого производится вызов соответствующей процедуры, которая последовательно меняет заголовки элементов управления. Хочу отметить, что использование метода SetAll формы в данном случае не приемлемо, так как может вызвать критическую ошибку.

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

В класс формы введен пользовательский метод - udp_SetMUIProperties, который вызывается в методе, ассоциированном с событием load формы и которому передается единственный параметр - значение свойства fvl_UseMUI формы.

Фрагмент кода для load формы выглядит так:

...
This.udp_SetMUIProperties(This.fvl_UseMUI)
...

Начальные строки кода пользовательского метода udp_SetMUIProperties выглядят так:

Lparameters lplNeedToSetMUIProperties
If !lplNeedToSetMUIProperties
   Return
Endif

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

А вот в случае, если свойство fvl_UseMUI формы установлено в True (.T.), то начинается процесс инициализации поддержки MUI. Как это происходит в моих приложениях?

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

Я не прописываю жестко имени класса, объект инстанции которого необходимо ввести в форму по причине обеспечения возможности обновления приложения. Класс может быть определен в дополнительном EXE-файле, который загружается с сайта при необходимости обновления. Здесь можно было бы использовать функцию AVCXCLASSES(), появившуюся еще в VFP 7.0 (что я и делал до определенного момента времени), но проще получить название класса обработчика из одной из таблиц описания приложения, в которую сведена информация о доступных классах и месте их размещения. Поскольку таблица описания приложения открывается в главном файле основного приложения, то при использовании приложения-загрузчика мы всегда имеем гарантию того, что работаем с самой свежей версией библиотеки классов.

Для хранения имени обработчика в классе формы введено пользовательское свойство fvo_MUIHandler, которому присваивается объектная ссылка на объект обработчика.

Аналогично вводу обработчика, в форму вводится контейнер, содержащий элементы управления, служащие для переключения языка интерфейса. Одновременно с этим, размер формы изменяется - ее высота увеличивается на высоту контейнера переключателя и, одновременно, на эту же величину изменяется свойство Top всех расположенных на форме ЭУ.

С первичными процессами формы разобрались. Что теперь с ЭУ? Здесь все достаточно просто. При инициализации они просто переписывают значение свойства fvl_UseMUI формы в свое добавленное пользовательское свойство cvl_UseMUI и если оно установлено в True(.T.), то вызывается функция udf_ChangeCaption, которой в качестве параметра передается ссылка на самого себя. Функция ищет в таблице трансляции заголовок, соответсвующий имени объекта, и устанавливает его. Если функция вернула False(.F.), причиной чему может служить отсутствие перевода, то вызывается функция глобального обработчика, определенного в главном файле, которая производит разбор имени объекта и устанавливает его заголовок в соответствии с приведенным именем.

На период отладки приложения мною используется возможность оперативного вмешательства в процесс установки заголовка. Осуществляется это следующим образом. Если функция udf_ChangeCaption обработчика MUI вернула False(.F.), то в случае, если язык интерфейса установлен, например, в "Russian", то вызывается маленькая форма, в которой я ввожу перевод для заголовка указанного ЭУ, который будет сохранен в таблице трансляции заголовков. Если же язык интерфейса установлен в используемый по умолчанию (English), то после разбора имени объекта вызывается родная функция Visual FoxPro Inputbox() и в случае, если я скорректировал заголовок объекта, то новое значение записывается в таблицу трансляции.

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

Cелектор для быстрого перехода на сайты, связанные с Visual FoxPro.