Локализация приложения под S60. Добавление хелпа. Динамическая локализация приложения и хелпа.

Локализация приложения под S60.

Добавление хелпа. Динамическая локализация приложения и хелпа.

Автор: Дмитрий Тарасов

Источник: dtarasov.ru

Введение

В
рамках этой статьи я предлагаю рассмотреть реализацию функционала,
который хоть и не является критичным с точки зрения функциональности
ПО, но является при этом неотъемлемым аттрибутом любого коммерческого
ПО. Если конкретно, то мы рассмотрим процесс локализации приложения и
создания локализуемого хелпа. И то, и другое являются необходимыми
требованиями Nokia к приложениям, которые рассматриваются на предмет
помещения в каталог программ Download!. Поэтому если вы заинтересованы
в разработке коммерчески успешного ПО (ну не одни трояны же писать,
правда?), то от решения этих задач никуда не деться.

Локализация приложения

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

RESOURCE TBUF r_buy_header_text
{
 buf = text_reginfo_expired_header;
}

Здесь тип ресурса TBUF указывает на то, что ресурс r_buy_hader_text
является строкой, содержащейся в строковой константе
text_reginfo_expired_header. При этом загрузка строки в дескриптор из
кода выглядит примерно так:


HBufC* str = StringLoader::LoadLC(R_BUY_HEADER_TEXT);
//работаем со строкой, например, показываем ее в окне диалога
CleanupStack::PopAndDestroy();//удаляем строку после того, как она становится не нужна

Возникает вполне закономерный вопрос: зачем было так сильно
заморачиваться с ресурсами и использовать HBufC? Дело в том, что
text_reginfo_expired_header может содержать текст на любом языке, и
длина строки может быть совершенно разной в зависимости от конкретной
локали. Поэтому при загрузке строки в heap из кода используется
статический метод LoadLC класса StringLoader, определяющий размер
строки, выделающий память для str и помещающий его в CleanupStack.
Предположим теперь, что в нашем приложении мы хотим сделать поддержку
двух языков – русского и английского (для любого количества локалей
процедура точно такая же). Для этого нам необходимо создать по
отдельному текстовому файлу, содержащему строковые константы для
каждого языка в отдельности.  Файл с текстовыми константами на
английском назовем OurApp.l01, а на русском – OurApp.l16. 01 и 16 – это
коды английского и русского языков соответственно. При этом OurApp.l01
будет содержать набор строк вида:

#define text_reginfo_expired_header "Please buy full version”

А OurApp.l16 строк вида:

#define text_reginfo_expired_header "Пожалуйста, приобретите полную версию”

После этого необходимо подключить локализованные файлы к основному файлу ресурсов (OurApp.rss):

#ifdef LANGUAGE_01
#include "OurApp.l01"
#endif
#ifdef LANGUAGE_16
#include "OurApp.l16"
#endif

Данный код указывает использовать файл OurApp.l01, если в смартфоне
выбран английский в качестве основного языка и, соответственно,
OurApp.l16, если русский. Далее необходимо добавить директиву,
указывающую на поддерживаемые языки в *.mmp – файл приложения:

LANG 01 16

И модифицировать *.pkg – файл программы, добавив следующие строки:


;Languages
&01, 16
; UID is the app's UID
#{"Our App","Our App"},(0x20016BXXX),1,60,0
[0x101F7961], 0, 0, 0,{"S60ProductID","S60ProductID"}
;Localized vendor
%{"Dmitriy Tarasov","Dmitriy Tarasov"}
:"Dmitriy Tarasov"

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


{
"..\epoc32\data\z\resource\apps\OurApp.r01”
"..\epoc32\data\z\resource\apps\OurApp.r16"                                

}-"!:\resource\apps\OurApp.rsc”

Таким образом, если в момент установки в мобильном устройстве будет
выбран русский, то язык интерфейса программы будет русским. Если
английским – то английским. При этом, если в момент установки будет
выбран какой-либо третий язык, то появится диалог выбора языка
установки.

Добавление хелпа в приложение

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

1) В корне проекта создаем
директорию help, в которую будем складывать требуемые для создания
справки файлы.
2) Модифируем файл проекта bld.inf следующим образом:


PRJ_MMPFILES
gnumakefile ..\help\build_help.mk
OurApp.mmp

Здесь мы указали на то, что в процессе импорта проекта перед созданием,
собственно, файлов проекта выполняется обработка файла build_help.mk,
который имеет следующее содержание:

3) Build_help.mk:

do_nothing :
@rem do_nothing
MAKMAKE :
cshlpcmp OurApp.cshlp
ifeq (WINS,$(findstring WINS, $(PLATFORM)))
copy OurApp.hlp $(EPOCROOT)epoc32\$(PLATFORM)\c\resource\help
endif
BLD : do_nothing
CLEAN :
del OurApp.hlp
del OurApp.hlp.hrh
LIB : do_nothing
CLEANLIB : do_nothing
RESOURCE : do_nothing  
FREEZE : do_nothing
SAVESPACE : do_nothing
RELEASABLES :
@echo OurApp.hlp
FINAL : do_nothing


Если говорить кратко, то здесь мы компилируем hlp – файл из
файла-исходника OurApp.cshlp, имеющего следующее содержание:

4) OurApp.cshlp:

<?xml version="1.0"?>
<!DOCTYPE cshproj SYSTEM "/epoc32/tools/cshlpcmp/dtd/cshproj.dtd">
<?xml:stylesheet href="/epoc32/tools/cshlpcmp/xsl/CSHproj.xsl" title="CS-Help project" type="text/xsl"?>
<cshproj>
<helpfileUID>0x2002XXXX</helpfileUID>
<directories>
<input></input>
<output></output>
<graphics></graphics>
<working>temp\</working>
</directories>
<files>
<source>
<xmlfile>OurApp.xml</xmlfile>
</source>
<destination>OurApp.hlp</destination>
<customization>custom.xml</customization>
</files>
</cshproj>

Важно отметить, что здесь 0х2002ХХХХ – это UID хелпа, который
необходимо получить, запросив у SymibanSigned. При этом UID хелпа
должен отличаться от UID3 приложения.
Файл OurApp.xml, собственно, и содержит разметку и текст хелпа, а файл
custom.xml несет вспомогательную функцию оформления, здесь мы его не
приводим, его можно найти в примерах реализации справки из SDK или здесь .

5) Файл OurApp.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE asptml SYSTEM "/epoc32/tools/cshlpcmp/dtd/asptml.dtd">
<?xml:stylesheet href="/epoc32/tools/cshlpcmp/xsl/asptml.xsl" title="asptml" type="text/xsl"?>
<asptml>
<uid value="0x2002XXXX"/>
<topic>
<category>OurApp</category>
<topictitle>Использование OurApp</topictitle>
<synonyms>Использование OurApp</synonyms>
<context contextUID="OurApp_Information"/>
<index>Использование OurApp</index>
<p>А вот сюда мы пишем много полезного текста</p>
</topic>
</asptml>


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

6) Подключаем в MMP – файл нужную библиотеку:

LIBRARY hlplch.lib

7) Добавляем в код приложения команду вызова справки и функцию HelpContextL().
В AppUi.h:

private:
CArrayFix<TCoeHelpContext>* HelpContextL() const;

В AppUi.cpp:


CArrayFix <TCoeHelpContext>* CBlackListAppUi::HelpContextL() const
{
CArrayFixFlat <TCoeHelpContext>* array =
new (ELeave) CArrayFixFlat <TCoeHelpContext>(1);
CleanupStack::PushL(array);
array->AppendL(TCoeHelpContext(KUidHelpFile, KBlackList_Information));
CleanupStack::Pop(array);
return array;
}

В коде выполенния команды вызова справки пишем:


case EClientHelp:
{
CArrayFix <TCoeHelpContext>* buf = CCoeAppUi::AppHelpContextL();
HlpLauncher::LaunchHelpApplicationL(iEikonEnv->WsSession(), buf);
break;
}


8) Добавляем в pkg – файл строку:


"..epoc32\OurApp\help\OurApp.hlp"-"!:\resource\help\OurApp.hlp"

Файл OurApp.hlp является скомпилированным файлом справки и собирается в
процессе выполнения импорта проекта. Таким образом, если вы хотите
внести изменения в хелп, то придется делать ре-импорт всего проекта.

Несмотря на довольно тривиальную задачу интеграции справки в
приложение, процесс этот сопровождается рядом не самых тривиальных
проблем. Так, например, в большинстве статей и в документации Symbian
для разметки текста рекомендуется использовать не XML, а RTF – файлы.
Проблема в том, что этот метод по неведомой причине работает только в
случае, если UID, назначаемый файлу справки находится в пределах так
называемого Protected Range, а UID3 приложения – в пределах Unprotected
Range. При этом, очевидно, что для сертификации приложения, необходимо
чтобы UID3 был как раз в пределах Protected Range. Нестыковочка
получается. Поэтому рекомендуется, все-таки, придерживаться описанной
выше процедуры, чтобы сэкономить себе как минимум одну бессонную ночь.

Динамическая локализация приложения и хелпа

Рассмотренный
выше пример локализации предполагал, что язык приложения выбирается
один раз при установке. Требования Nokia к публикуемым Download!
приложениям же обязывают локализацию меняться динамически в зависимости
от смены пользователем локали мобильного устройства. То есть если
выбран, например, русский язык – язык интерфейса должен быть русским,
если пользователь сменит язык на английский – то и приложение должно
сменить язык на английский. Добиться этого можно просто скопировав в
устройство все скомпилированные файлы ресурсов, подготоваливаемые в
процессе сборки приложения:


"..epoc32\data\z\resource\apps\OurApp.r01"-"!:\resource\apps\OurApp.r01"
"..epoc32\data\z\resource\apps\OurApp.r16"-"!:\resource\apps\OurApp.r16"


Как видно, от предыдущего варианта этот отличается тем, что в
устройство копируются оба файла ресурсов, вместо одного в зависимости
от локали смартфона. Кстати, скомпилированные локализованные файлы
вообще всегда имеют расширение, оканчивающееся кодом языка. Например, в
случае, если мы хотим, чтобы язык хелпа также менялся динамически при
смене пользователем языка приложения, то расширения локализованных
файлов будут *.h01 и *.h16 для английской и русской подсказок
соответственно. Кроме того, для каждой локали необходимо создать
отдельный xml – файл и компилируемый файл cshlp. Пример локазизуемого хелпа можно скачать здесь.

Результат описанных манипуляций должен выглядеть примерно так:

В устройстве выбран русский язык:

  

В устройстве выбран английский язык: