Многие разработчики мечтают о всемирной популярности своих приложений, но почти никто не создает локализованных версий своих приложений, ошибочно полагая, что программа должна сначала завоевать популярность. Локализованные версии – это первый шаг к завоеванию мирового господства!
В этой статье я расскажу о создании локализованных версий приложений и, подробнее, о локализации следующих ресурсов:
- диалоговые окна;
- меню;
- информация о версии;
- таблицы сообщений;
- таблицы строк (см. примечание)
Вообще-то, если вам нужно иметь в одном модуле строки на нескольких языках, то лучше использовать таблицы сообщений. Но об этом ниже.
Кроме того, в качестве бонуса, я покажу, как работать с локализованными значками и курсорами! Но сначала…
Несколько замечаний по локализации приложений
- Если вы хотите создать действительно многоязычное приложение, то нужно использовать Unicode-версии функций Win32 API. Для этого при сборке проекта необходимо определить символ UNICODE (и _UNICODE, если вы используете CRT). Кстати, для работы со строками в Unicode не рекомендуется использовать функции CRT. Используйте их аналоги из Win32 API.
- Но помните, что собранное таким образом приложение будет работать только на Windows NT/2K/XP.
- Для диалоговых окон используйте только шрифт “MS Shell Dlg”. Это фиктивный шрифт, который соответствует реальному, установленному в системе шрифту и способному отображать слова на выбранном пользователем, в настройках Windows, языке. В Resource Editor выбрать этот шрифт нельзя. Меняйте прямо в .rc файле, на работоспособности редактора ресурсов это не отразится.
- Если в ресурсах будут использоваться символы национальных алфавитов (например, умляуты для немецкого языка), то сначала нужно перекодировать .rc файл проекта из Windows-кодировки в Unicode. В Windows 2000 для этого можно использовать блокнот. Нужно открыть файл, выбрать «Сохранить как», кодировка – Unicode. Именно так и сделано в демонстрационном приложении. Правда, после этого он перестанет открываться в Resource Editor.
Во многих примерах из этой статьи будет использоваться функция LoadResourceLang. Вот ее исходный текст:
Как прошить Xiaomi Mi8 (china version) через TWRP на локализованную версию с Google Pay
HGLOBAL LoadResourceLang(LPCTSTR resType, DWORD resID) < HINSTANCE hAppInstance=GetModuleHandle(NULL); HRSRC hrRes=FindResourceEx(hAppInstance, resType, MAKEINTRESOURCE(resID), GetUserDefaultLangID()); if (!hrRes) < hrRes=FindResourceEx(hAppInstance, resType, MAKEINTRESOURCE(resID), MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); >return LoadResource(hAppInstance, hrRes); >
Как видите, ничего сложного. Она просто загружает ресурс указанного типа с указанным идентификатором. При этом загружается ресурс на основном языке, который пользователь выбрал в настройках Windows (Панель управления > Языки и стандарты). Если такой ресурс загрузить не удается, функция пытается загрузить ресурс на английском языке.
Элементарно. Локализованная заставка сериала
Диалоговые окна
Для создания локализованного диалогового окна нужно указать в его свойствах требуемый язык и набрать все строки в этом диалоговом окне на выбранном языке. Для загрузки диалогового окна с нужным языком необходимо написать следующий код:
DialogBoxIndirect(hAppInstance, (LPDLGTEMPLATE)LoadResourceLang(RT_DIALOG, IDD_DIALOG_ID), hParent,
Сначала мы находим ресурс диалога на нужном нам языке, затем загружаем его и передаем функции DialogBoxIndirect(Param) как указатель на структуру DLGTEMPLATE. Хитрость состоит в том, что ресурсы диалоговых окон лежат внутри файла в виде корректно сформированных структур DLGTEMPLATE, поэтому не нужно прикладывать никаких дополнительных усилий для создания диалога из указателя на ресурс.
Если вы используете MFC, то в классе, производном от CDialog необходимо перегрузить метод DoModal и написать в нём следующий код (всё остальное оставьте без изменений):
HRSRC hrRes=FindResourceEx(hAppInstance, resType, m_lpszTemplateName, GetUserDefaultLangID()); if (!hrRes) < hrRes=FindResourceEx(hAppInstance, resType, m_lpszTemplateName, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); >if (!CreateIndirect( (LPDLGTEMPLATE)LoadResource(hAppInstance, hrRes), m_pParentWnd)) return IDCANCEL; return CDialog::DoModal();
Меню
Работа с меню на разных языках во многом аналогична работе с диалоговыми окнами. Вот пример кода, загружающего локализованное меню:
HMENU hMenu=LoadMenuIndirect(LoadResourceLang(RT_MENU, IDR_MAIN));
Как вы, наверное, уже догадались, ресурс меню представляет собой корректную структуру MENUTEMPLATE. Поэтому работа с локализованными меню также не должна вызвать затруднений.
Информация о версии
Поскольку приложению редко приходится работать со своей «информацией о версии» (обычно это делают специальные утилиты), я не буду описывать её получение. Скажу лишь, что Win32 API предоставляет набор функций, которые позволяют сделать это без особых ухищрений.
Для добавления в приложение локализованной «информации о версии» вам следует добавить не новый ресурс Version Info, а в уже существующий добавить новый Version Info Block и выбрать для него требуемый язык.
Таблицы сообщений
Таблицы сообщений нельзя назвать широко используемым ресурсом. Так произошло потому, что Resource Editor из комплекта Visual Studio не поддерживает их создание и редактирование. Но, с другой стороны, этот тип ресурса широко применяется в самой Windows и, в отличие от таблиц строк, напрямую поддерживает локализацию. Рассмотрим этот ресурс подробнее.
Таблицы сообщений во многом похожи на таблицы строк, но, в отличие от последних, позволяют сопоставить каждому идентификатору несколько текстовых строк, отличающихся языком. Кроме того, в строке могут встречаться специальные комбинации символов %n (где n — число от 1 до 99), которые при загрузке строки (при помощи функции FormatMessage) могут автоматически заменяться предоставляемыми вами строками или числами.
На что именно будет заменена та или иная комбинация символов, вы можете указать, написав такую последовательность символов: %n!x!, где x – один из форматных символов функции printf. Например, если вы хотите, чтобы на место первой специальной комбинации символов было подставлено беззнаковое число, напишите следующее: %1!u!. По умолчанию считается, что на место всех спецпоследовательностей будут подставляться строки.
Исходный файл таблицы строк обрабатывается компилятором mc (Message Compiler), который в результате своей работы создает:
- .h-файл с идентификаторами строк для включения в проект;
- .rc-файл для включения в .rc-файл вашего проекта;
- несколько .bin-файлов (точнее столько, на скольких языках имеются строки в исходном файле).
Формат исходного файла для компилятора mc я описывать не буду, так как он очень прост. Подробнее вы можете ознакомиться с ним здесь.
Расскажу о том, что делать с заголовочными файлами, полученными после компиляции таблицы строк. h-файл нужно включить в тот же файл, в который включен resource.h (в сам resource.h его включать не стоит). Это предоставит вам доступ к числовым идентификаторам сообщений.
rc-файл нужно включить в rc-файл вашего проекта с помощью директивы #include. Сделать это лучше ближе к началу файла (например, сразу после строки #undef APSTUDIO_READONLY_SYMBOLS, если она у вас есть). После этого в Resource Editor вы увидите новый Custom Resource. Только не пытайтесь открывать даже папку этого ресурса! Иначе Resource Editor вставит все содержимое bin-файлов прямо в rc-файл вашего проекта, после чего он перестанет компилироваться.
После того как вы проделали эти нехитрые манипуляции, можете приступать к использованию содержимого таблицы сообщений. Для того, чтобы загрузить текст сообщения на определенном языке, необходимо написать следующий код:
LPTSTR buff=NULL; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE, hAppInstance, IDM_MSG_ID, GetUserDefaultLangID(), (LPTSTR) // . // Используем buff // . LocalFree(buff);
Если же в вашем сообщении есть спецпоследовательности и вы хотите произвести подстановку, то напишите следующий код:
BYTE argArr[sizeof(DWORD)+sizeof(LPCTSTR)]; BYTE *pCurr=pArr; (*(DWORD*)pCurr)=10; pCurr+=sizeof(DWORD); (*(LPCTSTR*)pCurr)=TEXT(«aaa»); LPTSTR buff=NULL; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ARGUMENT_ARRAY, hAppInstance,IDM_MSG_ID, GetUserDefaultLangID(), (LPTSTR)pArr); // . // Используем buff // . LocalFree(buff);
Такой хитрый способ передачи значений используется потому, что паскалевская конвенция вызова (__stdcall), которая используется в Win32 API, не позволяет передавать переменное число аргументов.
Таблицы строк
Вот мы и подошли к самому неприятному моменту. Дело в том, что Win32 API принципиально не поддерживает работу с таблицами строк на нескольких языках, поэтому нам придется собственноручно написать аналог функции LoadString:
WORD LoadStringLang(UINT strID, LPTSTR destStr /*=NULL*/, WORD strLen /*=0*/) < LPCWSTR str=(LPCWSTR)LoadResourceLang(RT_STRING, 1+(strID >> 4)); if (!str) return 0; for (WORD strPos=0; strPos < (strID strPos++) str+=*str+1; if (!strLen) return *str; if (!destStr) return 0; #ifdef _UNICODE lstrcpyn(destStr, str+1, min(strLen, *str+1)); #else WideCharToMultiByte(CP_ACP, 0, str+1, *str+1, destStr, strLen, NULL, NULL); #endif destStr[min(strLen-1, *str)]=TEXT(‘