Локализация приложения под 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. Пример локазизуемого хелпа можно скачать здесь.

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

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

  

 

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