Глава 10
Локализация приложений
После того как весь код отлажен и написано руководство, перед разработчиком встает задача "донести" написанное до пользователей, говорящих на разных языках. Чем сложнее становятся современные программные продукты, тем более запутан этот процесс. Эта глава расскажет вам об одном аспекте пользовательского интерфейса, который нужно знать, прежде чем отпускать свою программу "в свободное плавание". Речь идет о способах перевода всей информации в приложении на другие языки.
Многоязычные приложения. Создание библиотек ресурсов
Согласно корпоративной стратегии Inprise. вопросам создания приложений, работающих в многоязычной среде, уделено в последних версиях Delphi большое внимание. Проблема перевода приложения на другие языки не так проста, как кажется с виду, и может отнять значительную часть времени, выделенного на разработку проекта.
Адаптация приложения к работе на различных языках и с использованием различных национальных настроек называется интернационализацией. Собственно процесс внесения национальных изменений (перевод текста ресурсов, смена форматов представления данных) называется локализацией.
Точный смысл термина локализация (locale) вовсе не идентичен понятию "страна". Он складывается из двух частей: настроек на особенности конкретной страны и на используемые в ней языки. Для некоторых стран при локализации Windows используются по два языка или разновидности, например, в Сербии и Норвегии. Также верно и обратное — один язык (в частности, английский, испанский, арабский) применяется во многих странах. При этом национальные особенности (система мер, терминология, способ сортировки, форматы представления денежных единиц и даты/времени) в этих странах отличаются. Поэтому название локализации состоит из трех букв: две обозначают язык и одна — страну. В программах для Windows вы можете встретить разновидности английского: ENLJ (США), ENG (Великобритания) и еще с десяток. Для России (локализация RUS) эта двойственность отсутствует.
Традиционный способ разделить программный код и элементы пользовательского интерфейса — это использование ресурсов. Со средствами разработки от Inprise, начиная еще с Borland Pascal for Windows, поставляются инструменты для работы с ресурсами, такие, как Resource Workshop. В Delphi сама форма, по сути дела, является ресурсом, с которым можно работать прямо в визуальной среде; для специализированных графических ресурсов предназначена утилита Image Editor.
Предлагаемая Inprise методика локализации заключается в следующем. Все зависящие от языка ресурсы объединяются в динамические библиотеки ресурсов, которые затем распространяются вместе с приложением. При запуске в новой среде на компьютере пользователя специальный код инициализации приложения считывает системные языковые настройки и затем загружает соответствующую библиотеку. Процесс этот полностью скрыт от разработчика и не требует добавления никакого кода — надо только осуществить перевод нужных ресурсов.
В Delphi 5 специально для решения задач локализации появился целый комплекс средств под общим названием Integrated Translation Environment (ITE). Он состоит из трех составных частей: Мастера создания динамических библиотек ресурсов (Resource DLL Wizard), Менеджера переводов (Translation Manager) и Репозитория переводов (Translation Repository). Им, в основном, и посвящена данная глава.
Мастер создания динамических библиотек ресурсов
В Delphi процесс интернационализации легче всего автоматизировать путем создания так называемых динамических библиотек ресурсов. На это есть специальный мастер, вызываемый через пункт New... меню File (рис. 10.1). Можно вызвать его и другим путем — через пункт меню Project\Languages\Add.
Мастер создания библиотек ресурсов работает только в том случае, если имеется открытый проект (который и предстоит интернационализировать). Перед вызовом мастера убедитесь, что в этом проекте все строки, которые вы выводите на экран и собираетесь переводить, объявлены через ключевое слово resourcestring. Они должны использоваться в программе: если они просто объявлены, но не используются, оптимизирующий компилятор Delphi уберет ссылки на них и в библиотеки ресурсов они не попадут.
Первым выводимым Мастером окном будет приветствие и краткое описание возможностей. Далее мастер показывает диалоговое окно, в котором следует выбрать проекты, подлежащие интернационализации (рис. 10.2). Термин базовый язык (Base Language, третий столбец) означает код языка, который будет присвоен основным файлам проекта; по умолчанию базовый язык берется из текущих настроек операционной системы.
Рис. 10.1. Вызов мастера создания библиотек ресурсов
Рис. 10.2. Мастер создания библиотек ресурсов. Диалоговое окно выбора проектов
Рис. 10.3. Выбор языков, на которые будет осуществлен перевод
После этого пользователь получает возможность выбрать языки, на которые он будет переводить ресурсы (рис. 10.3), а также название и папку для локализованных файлов.
Вы можете внести в создаваемый локализованный проект и другие виды ресурсов, которые могут иметь национальный характер — значки, изображения, звуки и т. п. Все они должны быть описаны в файлах с расширением rc (или dfm), которые добавляются на следующем этапе работы мастера.
Мастер автоматически выносит в библиотеку два вида ресурсов — формы приложения и текстовые строки. При этом он создает следующие файлы:
Результатом компиляции станет динамическая библиотека с именем имя проекта имя локализации, в данном случае PROJECT1 . UKR. Эту библиотеку
нужно поместить и распространять вместе с ЕХЕ-файлом проекта. При запуске приложения, написанного на Delphi 5, библиотека времени выполнения производит попытку настройки. Если на целевом компьютере установлен тот национальный интерфейс, который соответствует расширению динамической библиотеки ресурсов (в данном случае ukr), то все локализованные вами ресурсы будут загружены из нее.
После создания проекта библиотеки (*.dpr) перед глазами открывается такой весьма скупой код, содержащийся в нем:
// Do not edit. This file is machine generated by the Resource DLL Wizard.
library Project1;
($R *.RES)
(ITE) {DFMFileType) fUnitl.dfm}
{ITE}{(RCFileTypel (Projectl_DRC.re}
($R 'Unitl.dfm' Forml:TForm)
{$R 'Projectl_DRC.res' 'Projectl_DRC.rc'}
{$E UKR}
begin
end.
Две директивы $r указывают на те ресурсы, ради которых все и "затевалось". Директива $е указывает на расширение файла, которое будет присвоено динамической библиотеке после компиляции.
Для ресурсов-текстовых строк в язык Object Pascal введено новое ключевое слово — resourcestring. Строки-константы, описанные с его помощью, будут собраны вместе в таблицу ресурсов. Если вы хотите упростить проблему локализации вашего приложения, замените везде выражения вида
ShowMessage( ' Ошибка открытия файла' );
на такие:
resourcestring sOpenError = ' Ошибка открытия файла ';
ShowMessage( sOpenError ) ;
Целесообразно собрать все ресурсные строки вашей программы в одном месте; лучше всего для этого выделить отдельный модуль. Так поступили и разработчики VCL — найдите модули consts.pas, dbconsts.pas, BDECONST.PAS, OLECONST.PAS, MIDCONST.PAS, MXCONSTS.РАS, WEBCONST.РАS в прилагаемых к Delphi исходных текстах. Кроме строковых констант, описанных через ключевое слово resourcestring, там ничего нет. Для того чтобы перевести все сообщения среды выполнения Delphi на русский, вам достаточно перевести эти модули с помощью ite и затем включить их национальную версию в состав вашего проекта.
Использование Менеджера переводов
В версии 5 среды Delphi значительно усовершенствованы инструменты для автоматизации подготовки многоязычных версий. После выполнения мастеpa создания динамических библиотек ресурсов управление автоматически передается Менеджеру переводов (рис. 10.4). Специально для его вызова предназначен также пункт Translation manager меню View.
Рис. 10.4. Внешний вид Менеджера переводов (Translation Manager)
Окно Менеджера переводов состоит из двух частей. В левой части приводится список всех проектов на разных языках и сосредоточены административные функции. Для каждого проекта предусмотрены две папки, соответствующие разновидностям локализуемых ресурсов: Формы (Forms) и Скрипты ресурсов (Resource scripts).
В правой части содержатся более подробные данные. Остановимся и мы на них более подробно. Если в левой части выделен проект на национальном языке или его составная часть (Папки с формами или скриптами), то в правой части вы увидите статистическую информацию: сколько ресурсов переведено, сколько — нет, общее количество и процент готовности.
Если в левой части выделен конкретный ресурс, в правой появляется таблица с теми свойствами и значениями, которые подлежат интернационализации. Такая таблица показана на рисунке 10.4.
В эту таблицу Менеджера переводов попадают не все свойства визуальных компонентов, а только те, которые собственно и поддаются локализации. Что это за свойства? Их легко увидеть в Инспекторе объектов, если переключить его в режим отображения свойств по категориям. Нужные нам свойства находятся в категории LocalizabIe (рис. 10.5).
На рисунке видно, что помимо шрифта и надписей, в состав свойств, меняющихся в зависимости от языка, входят также размеры, значки, файл справки и т. п. У многих компонентов в'Delphi 5 есть свойства RiniModp и ParentBiDiMode, описывающие начертание текста. Они позволяют располагать его справа налево, как это принято в ряде ближневосточных языков.
Поскольку написание приложений для стран Востока является для нашей страны редкостью, в книге данный вопрос не рассматривается.
При разработке пользовательского интерфейса имейте в виду, что длина надписей (в буквах) на национальных языках может сильно отличаться от их длины на английском, вплоть до двукратного удлинения. Учтите это при расположении компонентов типа TLabel, TEdit и т. п. Где возможно, позвольте компонентам автоматически масштабироваться.
Рис. 10.5. Локализуемые свойства компонента TForm
Серым цветом в таблице Менеджера переводов отображаются нередактируемые столбцы, белым — редактируемые. Если вы хотите управлять составом и сортировкой столбцов, в меню Actions можно настроить параметры их отображения. Назначение столбцов таково.
В столбце ID указывается идентификатор ресурса. Для строковых констант в этой колонке отображается имя константы. Например, если вы увидели идентификатор sysConst_szeroDivide, то в файле с расширением drc можно найти соответствующую этой константе строку "Floating point division by zero". Это имя образуется из имени модуля, где описана константа, и имени самой константы, разделенных символом подчеркивания. Для компонентов на формах ID образуется по другому правилу. Если вы видите ID, начинающийся с о — это свойство объекта (см. рис. 10.4). Буква i будет означать, что объект унаследован с другой формы, a l — с кадра (Frame). Таким образом, идентификатор o:Formi.Font.Name соответствует имени шрифта формы Formi.
Следующая колонка показывает значения всех свойств для базового языка проекта. В проекте RichEdit этот язык — английский, а если вы создаете новый проект в русскоязычной версии Windows, то этот язык будет русским. Новые (переведенные на тот или иной язык) значения содержатся в столбце с именем этого языка.
Столбец Status показывает состояние свойства; значение Translated означает, что перевод осуществлен. У него есть три возможных значения: Translated, Untranslated и Auto Translated. Любое изменение свойства устанавливает этот флаг в Translated. Если вы не хотите реально изменять строку, можно просто переустановить свойство щелчком в данной колонке. Значение Auto Translated устанавливается, если перевод осуществлен при помощи Репозитория переводов (см. следующий раздел).
Столбцы Created и Modified создаются и автоматически поддерживаются системой. Наконец, если вы хотите прокомментировать значение свойства, в вашем распоряжении столбец Comment.
В таблице Менеджера переводов достаточно удобно редактировать текстовые строки, тем более что для этого есть специальный двухоконный редактор (он вызывается из панели инструментов Менеджера переводов, рис. 10.6). Но вот другие виды свойств — перечислимые (как например Font.charset), числовые — редактировать не очень удобно. А свойство icon (значки) редактировать практически невозможно — значок представлен в двоичном виде. В этом случае редактировать формы нужно при помощи стандартных возможностей среды Delphi и Инспектора объектов, а затем сохранить изменения и вернуться в Менеджер переводов. При этом свойство получит флаг Translated.
Рис. 10.6. Такой редактор встроен в Менеджер переводов
Что делать, если вы отлаживаете ресурсы сразу для нескольких стран, а требуемая локализация не установлена у вас на компьютере? В пятой версии Delphi упрощен процесс переключения между языками при отладке. Теперь для этого предусмотрен пункт главного меню Delphi Project\ Languages\SetActive. Если все библиотеки DLL скомпилированы, то простым переключением в этом пункте меню вы можете просмотреть работу каждой из них. Если выбрано значение none, то приложение запускается с тем видом ресурсов, который соответствует текущим настройкам Windows. Разработчиками Inprise предусмотрен и другой выход из этой ситуации. Вы должны внести в ключ системного реестра hkey_current_user\
Software\Borland\Locales элемент данных вида "имя_приложения"="имя_локализации". Например, если вы хотите испытать интерфейс описываемого приложения на украинском языке, элемент данных должен выглядеть так:
[HKEY_CURRENT_USER\Software\Borland\Locales] "d:\\Program Files\\Borland\\Delphi5\\Project5
\\projectl.exe"="UKR"
В этом случае, независимо от реально установленных национальных настроек компьютера, будет загружен украиноязычный интерфейс.
Можно переключать национальные интерфейсы и "на ходу", во время работы приложения. Пример этого приведен в приложении RICHEDIT, поставляемом среди примеров с Delphi (папка demos\richedit). В нем при помощи меню вы можете выбрать один из трех языков, и тут же подгрузится необходимая динамическая библиотека ресурсов. Если вы всерьез занимаетесь проблемами интернационализации, советуем обратить на этот пример самое пристальное внимание. Функции, которые осуществляют это (модуль reinit.pas). можно без изменений позаимствовать и внести в свой проект.
Использование Репозитория переводов
Для централизованного хранения переведенных на другие языки ресурсов в Delphi 5 есть специальное средство — Репозиторий переводов (Translation Repository, рис. 10.7). Эта утилита позволяет собирать переведенные строковые ресурсы из проектов и в дальнейшем использовать их во вновь создаваемых проектах.
Репозиторий создает специальный файл с расширением drs, который хранится в папке исполняемых файлов Delphi (Delphi \Bin). Вы можете создавать и редактировать свои собственные DRS-файлы, использовать и распространять их, а также пользоваться файлами, созданными другими разработчиками.
Функции Репозитория переводов на сегодняшний день достаточно малы: централизованно сохранять строки с возможностью потом загрузить их, а также предоставить возможности экспорта/импорта.
Какова же польза от Репозитория? Когда вы начинаете локализацию нового проекта и добавляете в него новый язык, происходит просмотр содержимого Репозитория в поисках требуемых строк. Поиск осуществляется по идентификатору, точнее, по его числовому значению. Если строка с таким идентификатором уже имеется, ее переведенное значение автоматически добавля-
ется в ваш проект, напротив этой строки в Менеджере переводов в поле Status поднимается флажок AutoTranslated.
Рис. 10.7. Внешний вид Репозитория переводов
Вы можете один раз перевести константы из часто используемых файлов констант (consts.pas, sysconst.раз, DBCONSTs.раз и др.) или найти уже готовые файлы переводов в Интернете.
Строки в Репозиторий попадают из проекта так: в Менеджере переводов вы выделяете одну или несколько строк (со строковыми ресурсами!), нажимаете правую кнопку мыши и во всплывающем меню выбираете пункт "Repository\Add strings to Repository". В ряде случаев пункт Repository недоступен. Это может быть по двум причинам: среди выбранных вами ресурсов есть не только строки, и в случае совпадения исходного и целевого языков. Если вы хотите иметь англоязычную и русскую версии вашего приложения, нужно в Мастере создания библиотек ресурсов выбрать в качестве базового языка английский, а потом добавить к нему русскую DLL. В противном случае при работе в русской версии Windows строки на английском будут трактоваться как русские.
В проекте Richedit есть строки из модулей SysConst, Consts, ComStrs, уже переведенные на французский и немецкий. Если загрузить их в Репозиторий переводов, то это может послужить первым вкладом в вашу коллекцию ресурсов.
Импорт и экспорт данных предусмотрен в формате xml. Этот быстро набирающий популярность формат имеет хорошую перспективу стать стандартом де-факто для обмена данными.
Информация о версии вашего продукта
Если ваш программный продукт дорос до определенной степени зрелости, пора снабжать его информацией о версии. Это особенно важно, если он уже устанавливается и используется где-то, и у пользователей могут возникнуть вопросы. Когда вашу телефонную линию разрывают разные люди, и каждый сбивчиво пытается объяснить, что в такой-то формочке не работает такая-то кнопочка, нужно с холодным рассудком понять, что именно они имеют в виду. И лучшей подсказкой может послужить полная информация о версии, имеющейся у пользователя.
Вставить в ваш проект информацию о версии поможет сама Delphi. Вызвав пункт Options меню Project, на странице Version Info вы увидите структуру, которая и будет ее содержать (рис. 10.8). Для того чтобы в ваш проект попала эта структура, нужно поднять флажок Include version information in project.
Рис. 10.8. На странице Version Info вы можете задать информацию о текущей версии продукта
Что это за структура? Информация о версии помещается в особый вид ресурсов и компилируется в исполняемый файл или динамическую библиотеку проекта. Если вы запустите любой редактор ресурсов и загрузите в него какое-то приложение, помимо ресурсов Bitmap, Icon, Cursor, вы можете увидеть и тип Version. Такой тип ресурсов представляет из себя структуру двоичных данных, состоящую из поля фиксированной длины (fixeddfileinfo), поля данных локализации (translation) и набора строк переменной длины. То, что вы видите на рисунке, соответствует полям этой структуры.
Но что необходимо указать в этих полях? Чем FileVersion отличается от ProductVersion? Некоторые поля структуры являются обязательными, некоторые — нет. Обязательные поля приведены в табл. 10.1.
Таблица 10.1. Обязательные поля для информации о версии
Название поля |
Описание |
CompanyName |
Название компании-производителя ПО |
FileDescription |
Описание файла для пользователя. Может применяться при программной установке |
FileVersion |
Версия файла |
ProductName |
Название продукта, в составе которого распространяется файл |
ProductVersion |
Версия продукта, в составе которого распространяется файл |
OriginalFilename |
Имя файла (без указания пути к нему) |
InternalName |
Внутреннее имя файла (модуля). Может совпадать с Original-Filename |
Для примера рассмотрим информацию о версии из файла DELPHI32.EXE:
CompanyName=Inprise Corporation
FileDescription==Delphi-32 Development Environment
FileVersion=5.0.5.62 InternalName=DELPHI32
LegaiCopyright=Copyright © 1996,99
Inprise Corporation OriginalFilename=DELPHI32.EXE
ProductName=Client/Server Suite
ProductVersion=5.0
Обратите внимание на флажок Auto-increment build number. Это очень удобная возможность автоматически обновлять информацию о номере версии. При каждой полной перекомпиляции проекта (Project/Build) последняя цифра номера версии файла будет автоматически увеличиваться на единицу. Предыдущие три вам придется править вручную, но это должно происходить намного реже.
Имейте в виду также поле LocaleID. Оно устанавливается Delphi в соответствии с языковыми настройками операционной системы. Для русскоязычных версий Windows его значение будет равно $0419, для американских — $0409. Эти значения описаны выше.
Delphi дает возможность автоматически разместить информацию в приложении, но не дает способа извлечь ее оттуда. Для решения этой задачи нужно прибегнуть к использованию функций Windows API. Решается она в три
действия:
function VerQueryvalue(pBlock: Pointer; IpSubBlock: PChar; var
IplpBuffer: Pointer; var puLen: UINT): BOOL;
Параметр pBlock должен указывать на прочитанную из файла информацию о версии. Для того чтобы получить значения строк, соответствующих показанным на рис. 10.8, в параметре IpSubBlock нужно передать строку вида
StringFileInfo.
Листинг 10.1 Метод, считывающий информацию о версии из ресурсов
procedure TForml.Button2Click(Sender: TObject);
const
InfoNum = 10;
InfoStr : array [I..InfoNum] of String =
('CompanyName', 'FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'LegalTradeMarks', 'Original-Filename', 'ProductName', 'ProductVersion', 'Comments') ;
var
S, sLangID : String;
Len, n : cardinal;
i : Integer;
Buf : PChar;
Value : PChar;
begin
if not OpenDialogI.Execute then Exit;
S := OpenDialogI.FileName;
n := GetFileVersionInfoSize(PChar(S),n );
if n > 0 then begin Buf := AllocMem(n);
Meitiol. Lines. Add (' FileVersionInfoSize='+IntToStr(n));
GetFileVersionInfo(PChar(S),0,n,Buf);
if VerQueryvalue( Buf,
'\VarFile.[nfo\Translation', pointer(Value), n ) then
i := PDword( Value )" else
i := $04E40409;
sLangID := '\StringFileInfo\' + IntToHex(LoWord( i ), 4) + IntToHex(HiWord( i ), 4) + '\';
for i:=l to InfoNum do
if VerfiueryValue (Buf, PChar (sLangID + In-foStr[i]),Pointer(Value),Len) then
Memol.Lines.Add(InfoStr[i]+'='+Value) ;
FreeMem(Buf,n) ;
end
else
Memol.Lines.Add('No FileVersionInfo found');
end;
Целесообразно сразу, в одном месте извлечь всю информацию при помощи функции VerQueryValue.
Имеется достаточное количество компонентов, созданных независимыми разработчиками для работы с информацией о версии — там все ее поля представлены в виде отдельных свойств.
Резюме
В этой главе изложено то, что вам придется делать "на закуску" вашего большого проекта. К сожалению, все впечатление от прекрасно написанной программы можно смазать, упустив маленькие детали при ее оформлении. Поэтому, если вы собираетесь вынести ее на суд пользователя, со всем вниманием отнеситесь к предложенным здесь советам и решениям.