Gettext в приложениях Windows собранных CMake с использованием MinGW

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск

Содержание

Введение

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

После своего запуска, приложение просто будет выполнять печать в консоль строки "Привет, мир!" и завершаться.

Данное руководство не является панацеей или каким-то определенным рецептом при решении данной задачи, оно представляет собой альтернативный вариант и предназначено для краткого ознакомления читателя со стадиями процесса локализации и сборки программы.

Предполагается, что читатель имеет некоторые знания и навыки программирования.

Инструменты

Для решения нашей задачи нам понадобятся следующее программное обеспечение (ПО):

  1. ОС Microsoft Windows - операционная система семейств XP/Vista/7 и т.п.
  2. MinGW - порт пакета утилит и компиляторов GCC для Microsoft Windows.
  3. CMake - кроссплатформенная система автоматизации сборки программного обеспечения из исходного кода.
  4. Gettext - библиотека проекта GNU для интернационализации.

Получение и установка инструментов

Получение и установка опереционной системы MS Windows

Описывать этот процесс не имеет смысла, т.к. всем и так все ясно. :)

Получение и установка MinGW

Получение: Этот пакет программ распространяется бесплатно и скачать инсталляцию можно тут.

Установка: Для установки пакета программ запускаем исполняемый файл установщика и следуем его советам. Допустим, он установился в каталог: D:\MinGW.

Получение и установка CMake

Получение: Этот пакет программ распространяется бесплатно и скачать инсталляцию можно тут. Скачивать и использовать можно как версии 2.6.x так и версии 2.8.x, но лучше все-же использовать более свежую версию 2.8.x.

Установка: Для установки пакета программ запускаем исполняемый файл установщика и следуем его советам. Допустим, он установился в каталог: D:\CMake.

Получение и установка Gettext

Получение: Этот пакет программ распространяется бесплатно и скачать инсталляцию можно тут. Скачиваемый пакет является полным набором всех необходимых файлов для локализации приложения, т.е. содержит как RunTime компоненты, так и компоненты для разработки программ.

Установка: Для установки пакета программ запускаем исполняемый файл установщика и следуем его советам. Допустим, он установился в каталог: D:\GnuWin32. При этом, интересующими нас директориями будут являться:

  • D:\GnuWin32\bin - директория runtime компонентов
  • D:\GnuWin32\include - директория заголовочных файлов gettext для разрабтчика
  • D:\GnuWin32\bin - директория линкуемых библиотек gettext для разрабтчика

Подготовка к сборке

Создание дерева директории исходных кодов проекта

Итак, локализовать мы будем простое консольное приложение (которое сами же и напишем и соберем) на два языва: русский и немецкий. При этом, наш проект будет иметь такую структуру директорий: /SourceTestProject |->main.cpp |->/po | |->/de | |->/ru | |->CMakeLists.txt | |->CMakeLists.txt где

  • /SourceTestProject - директория исходных кодов нашего проекта
  • main.cpp - собственно код нашего приложения
  • /po - директория с каталогами, содержащими файлы исходных переводов на различные языки
  • /de, /ru - директории, которые содержат файлы переводов на русский и немецкий языки соответственно
  • CMakeLists.txt - файлы конфигурации сборки приложения для CMake

Создание директории сборки проекта

Сборку проекта с использованием CMake лучше всего производить вне директории нашего проекта, чтобы не замусоривать её, а также для исключения случайной модификации исходных файлов директории проекта. Да и вообще, сборка вне директории с исходным кодом является "правилом хорошего тона". Поэтому для сборки параллельно директории проекта, создадим директорию сборки: /.. |->/SourceTestProject |->/build-project где

  • /.. - какая-то родительская директория
  • /SourceTestProject - директория исходных кодов нашего проекта
  • /build-project - директория для сборки нашего проекта (в которой бумем его собирать)

Создание окружения сборки проекта

Для задания переменных окружения и путей к исполняемым файлам и библиотекам пакетов программ типа CMake, MinGW, Gettext и системным утилитам из System32, а также для исключения влияния этих переменных на другие, создадим для себя консоль сборки с необходимыми нам параметрами.

Для этого создадим файл MyBuildEnv.bat с таким содержимым:

@echo off
echo Setting up a MinGW/CMake/Gettext/Libiconv only environment...
set PATH=D:\Qt\2009.04\mingw\bin
set PATH=%PATH%;D:\CMake 2.6\bin
set PATH=%PATH%;D:\GnuWin32\bin
set PATH=%PATH%;%SystemRoot%\System32
set GETTEXT_INCLUDE_DIR=d:\GnuWin32\include
set GETTEXT_LIB_DIR=d:\GnuWin32\lib

где:

  • D:\MinGW\bin - путь к runtime компонентам компилятора MinGW
  • D:\CMake\bin - путь runtime компонентам сборщика CMake
  • D:\GnuWin32\bin - путь runtime компонентам транслятора Gettext
  • %SystemRoot%\System32 - путь системным runtime компонентам Windows
  • GETTEXT_INCLUDE_DIR=d:\GnuWin32\include - путь к заголовочным файлам Gettext (понадобится далее для сборки приложения)
  • GETTEXT_LIB_DIR=d:\GnuWin32\lib - путь к линкуемым библиотекам Gettext (понадобится далее для сборки приложения)

Скопируем/переместим этот файл куда нибудь, например, пусть он будет находится в корне диска D: D:\MyBuildEnv.bat

Теперь создадим на рабочем столе ярлык, указывающий на нашу консоль сборки в которой будут использованы переменные окружения из MyBuildEnv.bat

На вкладке "Общие" задаем имя нашей консоли, например:

  • My Env Build Command Prompt

На вкладке "Ярлык" пишем к примеру это:

  • Объект: C:\WINDOWS\system32\cmd.exe /K d:\MyBuildEnv.bat
  • Рабочая папка: D:\

И теперь, при клике по ярлыку "My Env Build Command Prompt" запустится настроеная под наши нужды консоль (командная строка) в которой мы будем производить компиляцию проекта.

Создание кода приложения

Весь код нашего приложения будет находится в одном единственном файле main.cpp.

Код main.cpp:

#include <iostream>
#include <libintl.h> 
#include <locale.h>
 
int main(int argc, char* argv[])
{
    std::string domain("test"); 
    std::string localedir("./locale"); 
 
    setlocale(LC_ALL, "");
    bindtextdomain(domain.c_str(), localedir.c_str());
    textdomain(domain.c_str());
    std::cout << gettext("Hello, world!") << std::endl;
    return 0;
}

где:

  • #include <libintl.h> - подключаемые заголовки Gettext
  • #include <locale.h> - подключаемые системные заголовки компилятора
  • #include <iostream> - подключаемые системные заголовки компилятора
  • domain("test") - домен которому мы присвоили имя "test" (этим именем у нас также будет назван и исполняемый файл test.exe нашего приложения)
  • string localedir("./locale") - это имя каталога в котором будут находится бинарные файлы переводов.
  • gettext("Hello, world!") - собственно строка, которую будем локализовать на разные языки.

Определение структуры и содержимого каталогов собранного приложения

Задаем имя и расположение директории локализаций по нашему усмотрению, но обычно (в Windows) лучше и проще чтобы она имела имя locale и находилась в корне каталога с исполняемым файлом приложения. (Да и у нас далее, в исходном коде приложения будет задано именно такое расположение каталога локализации). В данном случае речь идет о структуре каталогов уже готового (т.е. собранного приложения) и никоим образом это не относится к описанному в (<Создание дерева директории исходных кодов проекта>). Поддиректории в этой директории должны иметь определенную структуру, например: /InstallTestProject |-> test.exe |-> test1.dll (если необходима) |-> ... |-> testN.dll |->/locale

      |->/ru
      |   |->/LC_MESSAGES
      |            |->test.mo
      |->/de
      |   |->/LC_MESSAGES
      |            |->test.mo
      |
     и т.п.

где:

  • /InstallTestProject - имя директории с готовым проектом (например, установленным в "Program Files" и т.п.)
  • test.exe - исполняемый файл нашего приложения
  • test1.dll/testN.dll - разделяемые библиотеки нашего приложения (но в текущей задаче их у нас нет)
  • /ru, /de - директории в которых находятся откомпилированные gettext-ом переводы *.mo
  • /LC_MESSAGES - обязательная директория, имя которой должно быть таким как есть

Создание конфигурации сборки для CMake

Конфигурация для сборки проекта при помощи CMake будет содержаться всего в двух файлах CMakeLists.txt:

  • один находится в корне проекта с исходными кодами

/SourceTestProject/CMakeLists.txt

  • другой находится в корне директории /po c переводами

/po/CMakeLists.txt

Итак, рассмотрим содержимое этих файлов по очереди.

Содержимое файла /SourceTestProject/CMakeLists.txt

Ниже приведено содержимое файла с подробными комментариями:

# Задаем имя нашему проекту.
# При этом результирующий исполняемый файл приложения пусть тоже имеет такое имя, где:
# '''test''' - имя проекта
set( APP_TARGET test )
project( ${APP_TARGET} )
cmake_minimum_required( VERSION 2.6.0 )
 
# Здесь подключаем директории с заголовочными файлами,
# которые нам потребуются для сборки приложения.
# В данном случае нам нужны только заголовки от gettext, где:
# '''GETTEXT_INCLUDE_DIR''' - переменная окружения пути к заголовкам gettext, 
# которую мы установили, запустив *.bat файл.
include_directories( $ENV{GETTEXT_INCLUDE_DIR} )
 
# Здесь подключаем директории с дополнительными библиотеками,
# которые нам потребуются для линковки приложения.
# В данном случае нам нужны только библиотеки от gettext, где:
# '''GETTEXT_LIB_DIR''' - переменная окружения пути к библиотекам gettext, 
# которую мы установили, запустив *.bat файл.
link_directories( $ENV{GETTEXT_LIB_DIR} )
 
set( APP_SRCS main.cpp )
 
add_executable( ${APP_TARGET} ${APP_SRCS} )
 
# Указываем какие именно нам нужны библиотеки для линковки с приложением.
# В нашем случае это библиотека от gettext: '''intl'''.
target_link_libraries( ${APP_TARGET}  intl )
 
# Указываем имя директории (bin) куда установится исполняемый файл приложения
# после выполнения команды: '''$mingw32-make install'''.
install( TARGETS ${APP_TARGET} RUNTIME DESTINATION bin )
 
# Подключаем поддиректорию с файлами переводов.
add_subdirectory( po )

Содержимое файла /po/CMakeLists.txt

Ниже приведено содержимое файла с подробными комментариями:

set( DOMAIN  ${APP_TARGET} )
 
# Устанавливаем набор языков, на которые переведено наше приложение.
# В нашем случае это русский и немецкий (английский язык является
# языком приложения по умолчанию, поэтому переводить его не нужно).
# При этом, кодам имен локализаций у нас соответствуют имена 
# поддиректорий '''de, ru'''. Это сделано просто для упрощения и наглядности,
# а в принципе структуру поддиректорий и их названий можно делать какой угодно,
# главное в дальнейшем правильно написать скрипты для CMake. :)
set( LINGUAS de ru )
 
# Устанавливаем имя директории в которую будут устанавливаться уже готовые,
# откомпилированные с помощью gettext файлы переводов *.mo., где:
# '''locale''' - имя этой директории.
set( LOCALE_INSTALL_DIR locale )
 
# Задаем имя шаблона файла перевода *.pot.
set( POT_FILE ${CMAKE_CURRENT_BINARY_DIR}/messages.pot )
 
# Генерируем шаблон перевода, сканируя исходники приложения и
# извлекая всё что подлежит переводу.
file( GLOB _srcFiles ../*.cpp )
message( STATUS "src files: ${_srcFiles} ")
 
add_custom_target( translation ALL DEPENDS )
add_custom_command( TARGET translation
                    COMMAND xgettext --from-code CP1251 -C -a -o ${POT_FILE} ${_srcFiles}
)
 
# Пробегаем по всем именам директорий (локализациям) и обрабатываем их gettext-ом.
foreach( LANG ${LINGUAS})
    file( GLOB _poFile ${LANG}/*.po )
    if( EXISTS ${_poFile} )
        #message( STATUS " Po file: ${_poFile}" )
        set( PO_FILE_NEW ${CMAKE_CURRENT_BINARY_DIR}/${LANG}.po )
        set( GMO_FILE_NEW ${CMAKE_CURRENT_BINARY_DIR}/${LANG}.gmo )
        add_custom_command( TARGET translation
                            COMMAND msgmerge ${_poFile} ${POT_FILE} -o ${PO_FILE_NEW}
                            COMMAND msgfmt -c -o ${GMO_FILE_NEW} ${PO_FILE_NEW}
        )
        install(FILES ${GMO_FILE_NEW} DESTINATION ${LOCALE_INSTALL_DIR}/${LANG}/LC_MESSAGES RENAME ${DOMAIN}.mo )
 
    endif( EXISTS ${_poFile} )
endforeach( LANG )

Генерация бинарных файлов переводов происходит у нас в несколько этапов:

  1. Сначала из всех *.cpp файлов проекта извлекаются всё то, что подлежит переводу, и создается файл-шаблон *.pot (команда $xgettext).
  2. Далее, по очереди проверяется наличие готовых исходных файлов переводов *.po разных языков и, на их основе, и основе шаблона *.pot создаются обновленные файлы переводов *.po. При этом, исходные файлы переводов остаются нетронутыми. Это сделано для исключения модификации файлов в директории исходных кодов (команда $msgmerge).
  3. Далее, на основе каждого вновь созданного (обновленного) файла *.po создается результирующий бинарный файл перевода *.gmo ( команда $msgfmt).

Сборка приложения

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

Все ниже описанные операции выполняются в "нашей" консоли, которую мы предварительно настроили и запустили (см. тут).

Первоначальная генерация исходных переводов

Генерируем шаблон исходного файла локализации, предварительно перейдя в корневой каталог исходных кодов проекта:

cd <Путь к каталогу исходников проекта>\SourceTestProject
xgettext -C -a -o messages.pot *.cpp

После выполнения этой команды, в той директории где мы находимся появится файл шаблона messages.pot.

Теперь, нам необходимо из шаблона сгенерировать исходные файлы переводов для разных языков:

msginit -i messages.pot -o po\de\messages.po -l de
msginit -i messages.pot -o po\ru\messages.po -l ru

После выполнения данных команд, в директориях po\de и po\de появятся необходимые нам файлы.

Теперь файл шаблона нам не нужен и мы можем его удалить. Т.е. для распространения исходных кодов нашего проекта достаточно иметь только файлы переводов *.po (естественно и иметь *.cpp и CMakeLists.txt, это само собой разумеется).

Далее, произведем "перевод" сообщений в *.po файлах на требуемые нам языки. Для этого, редактируем их любым доступным образом и добавляем нужные сообщения:

  • для po\de\messages.po

... msgid "Hello, world!" msgstr "Hallo Welt!" ...

  • для po\ru\messages.po

... msgid "Hello, world!" msgstr "Привет, мир!" ...

Всё, теперь сохраняем все изменения. На этом перевод закончен, и можно приступить к сборке проекта.

Собираем проект

Сначала перейдем в пустую директорию сборки (которую мы заранее создали):

cd <Путь к директории сборки>/build-project

Теперь сама сборка:

{{{1}}}
mingw32-make

При успехе в консоль отобразится примерно следующее:

D:\SVN\lang_test>cd ..\build

D:\SVN\build>cmake ..\lang_test -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -- The C compiler identification is GNU -- The CXX compiler identification is GNU -- Check for working C compiler: D:/Qt/2009.04/mingw/bin/gcc.exe -- Check for working C compiler: D:/Qt/2009.04/mingw/bin/gcc.exe -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: D:/Qt/2009.04/mingw/bin/g++.exe -- Check for working CXX compiler: D:/Qt/2009.04/mingw/bin/g++.exe -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- src files: D:/SVN/lang_test/po/../main.cpp -- Configuring done -- Generating done -- Build files have been written to: D:/SVN/build

D:\SVN\build>mingw32-make Scanning dependencies of target test [100%] Building CXX object CMakeFiles/test.dir/main.cpp.obj Linking CXX executable test.exe [100%] Built target test Scanning dependencies of target translation D:/SVN/lang_test/po/../main.cpp:32: warning: Empty msgid. It is reserved by GNU gettext:

                                            gettext("") returns the header entry with
                                            meta information, not the empty string.

. done. msgfmt: D:/SVN/build/po/de.po: field `Project-Id-Version' still has initial default value . done. msgfmt: D:/SVN/build/po/ru.po: field `Project-Id-Version' still has initial default value [100%] Built target translation

D:\SVN\build>

При этом, автоматически сгенерируются:

  • исполняемый файл приложения test.exe
  • бинарные файлы переводов *.gmo

Установка проекта

Теперь, после того как мы собрали проект, мы можем установить его в какую нибудь директорию. Допустим, хотим установить в D:\install, для этого выполняем в консоли следующую команду:

$make DESTDIR=d:\install install

В итоге, в директории d:\install получим следующую структуру каталогов:

/install

   |->/Program Files
           |->/test
                 |->/bin
                 |    |->test.exe
                 |->/locale
                       |->/de
                       |    |->/LC_MESSAGES
                       |            |->test.mo
                       |->/ru
                           |->/LC_MESSAGES
                                   |->test.mo

Запуск приложения

Для запуска приложения необходимо, находять в нашей консоли сборки перейти в директорию с установленным проектом и выполнить файл test.exe

cd d:\install\Program Files\test
cd bin\test.exe

После выполнения в консоли увидим:

D:\SVN\inst\Program Files\test>bin\test.exe ¦ЁштхЄ, ьшЁ!

Крякозяблы "¦ЁштхЄ, ьшЁ!" и есть текст перевода "Привет, мир!", не пугайтесь! :)

Дело в том, что текст в консоли Windows выводится в кодировке CP866, но у нас файлы перевода сгенерированы для кодировки CP1251. Если бы мы делали GUI приложение, то текст в нем выводился бы корректно (в теории), но в данном случае нам необходимо выполнить ряд мер для устранения данного несоответствия, о чем, может быть, будет написано позже.

Важные замечания

  1. Так как мы линковали наше приложение с библиотеками от Gettext, то для правильной его работы необходимо, чтобы наше приложение имело к ним доступ, для чего можно поступить двумя путями:
    1. Или, в каталог с исполнеяемым файлом test.exe скопировать как минимум такие библиотеки Gettext как: libintl3.dll и libiconv2.dll. И в этом случае, наше приложение будет работать на любой машине где не установлен Gettext.
    2. Или, если на машине установлен Gettext, необходимо в переменных окружения прописать путь к runtime компонентам Gettext (т.е. путь к директории где находятся разделяемые библиотеки *dll Gettext-а , например, /bin).