| # Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| # file LICENSE.rst or https://cmake.org/licensing for details. | |
| #[=======================================================================[.rst: | |
| FindGettext | |
| ----------- | |
| Finds the GNU gettext tools and provides commands for producing multi-lingual | |
| messages: | |
| .. code-block:: cmake | |
| find_package(Gettext [<version>] [...]) | |
| GNU gettext is a system for internationalization (i18n) and localization | |
| (l10n), consisting of command-line tools and a runtime library (``libintl``). | |
| This module finds the gettext tools (such as ``msgmerge`` and ``msgfmt``), | |
| while the runtime library can be found using the separate :module:`FindIntl` | |
| module, which abstracts ``libintl`` handling across various implementations. | |
| Common file types used with gettext: | |
| POT files | |
| Portable Object Template (``.pot``) files used as the source template for | |
| translations. | |
| PO files | |
| Portable Object (``.po``) files containing human-readable translations. | |
| MO files | |
| Machine Object (``.mo``) files compiled from ``.po`` files for runtime use. | |
| Result Variables | |
| ^^^^^^^^^^^^^^^^ | |
| This module defines the following variables: | |
| ``Gettext_FOUND`` | |
| .. versionadded:: 3.3 | |
| Boolean indicating whether (the requested version of) gettext was found. | |
| ``Gettext_VERSION`` | |
| .. versionadded:: 4.2 | |
| The version of gettext found. | |
| Cache Variables | |
| ^^^^^^^^^^^^^^^ | |
| The following cache variables may also be set: | |
| ``GETTEXT_MSGMERGE_EXECUTABLE`` | |
| The full path to the ``msgmerge`` tool for merging message catalog and | |
| template. | |
| ``GETTEXT_MSGFMT_EXECUTABLE`` | |
| The full path to the ``msgfmt`` tool for compiling message catalog to a binary | |
| format. | |
| Commands | |
| ^^^^^^^^ | |
| This module provides the following commands to work with gettext in CMake, if | |
| gettext is found: | |
| .. command:: gettext_process_po_files | |
| Creates a build rule that processes one or more ``.po`` translation files | |
| into binary ``.mo`` files for a specified translatable language locale: | |
| .. code-block:: cmake | |
| gettext_process_po_files( | |
| <language> | |
| [ALL] | |
| [INSTALL_DESTINATION <destdir>] | |
| PO_FILES <po-files>... | |
| ) | |
| This command defines a custom target that compiles the given ``<po-files>`` | |
| into ``.mo`` files for the specified ``<language>``. On first invocation, | |
| it also creates a global custom target named ``pofiles``, to which all | |
| subsequent invocations contribute. This target can be used to build all | |
| translation files collectively or referenced in other CMake commands. | |
| This command should be invoked separately for each language locale to | |
| generate the appropriate ``.mo`` files per locale. | |
| The arguments are: | |
| ``<language>`` | |
| The target translatable language locale of the PO files. | |
| This string is typically formatted as a locale identifier (e.g., ``de_DE`` | |
| for German as used in Germany, or ``de_AT`` for German as used in Austria, | |
| etc.). The part before the underscore specifies the language, and the | |
| part after specifies the country or regional variant. In some cases, a | |
| shorter form using only the language code (e.g., ``de``) may also be used. | |
| ``ALL`` | |
| This option adds the generated target to the default CMake build target so | |
| that translations are built by default. | |
| ``INSTALL_DESTINATION <destdir>`` | |
| Specifies the installation directory for the generated ``.mo`` files at | |
| the install phase. If specified, files are installed to: | |
| ``<destdir>/<language>/LC_MESSAGES/*.mo``. If not specified, files are | |
| not installed. | |
| ``PO_FILES <po-files>...`` | |
| A list of one or more ``.po`` translation files to be compiled into | |
| ``.mo`` files at build phase for the specified ``<language>``. Relative | |
| paths will be interpreted relative to the current source directory | |
| (:variable:`CMAKE_CURRENT_SOURCE_DIR`). | |
| .. command:: gettext_process_pot_file | |
| Creates a build rule that processes a gettext Portable Object Template | |
| (``.pot``) file and associated ``.po`` files into compiled gettext Machine | |
| Object (``.mo``) files: | |
| .. code-block:: cmake | |
| gettext_process_pot_file( | |
| <pot-file> | |
| [ALL] | |
| [INSTALL_DESTINATION <destdir>] | |
| LANGUAGES <languages>... | |
| ) | |
| This command defines a custom target named ``potfiles`` that compiles the | |
| given ``<pot-file>`` and language-specific ``.po`` files into binary ``.mo`` | |
| files for each specified language. The corresponding ``<language>.po`` | |
| files must exist in the current binary directory | |
| (:variable:`CMAKE_CURRENT_BINARY_DIR`) before this command is invoked. | |
| The arguments are: | |
| ``<pot-file>`` | |
| The path to the gettext Portable Object Template file (``.pot``) serving | |
| as the source for translations. If given as a relative path, it will be | |
| interpreted relative to the current source directory | |
| (:variable:`CMAKE_CURRENT_SOURCE_DIR`). | |
| ``ALL`` | |
| Adds the generated target to the default CMake build target so that the | |
| files are built by default. | |
| ``INSTALL_DESTINATION <destdir>`` | |
| Specifies the installation directory for the generated ``.mo`` files at | |
| the install phase. If specified, files are installed to: | |
| ``<destdir>/<language>/LC_MESSAGES/<pot-base-filename>.mo``. If not | |
| specified, files are not installed. | |
| ``LANGUAGES <languages>...`` | |
| A list of one or more translatable language locales (e.g., ``en_US``, | |
| ``fr``, ``de_DE``, ``zh_CN``, etc.). | |
| .. command:: gettext_create_translations | |
| Creates a build rule that processes a given ``.pot`` template file and | |
| associated ``.po`` translation files into compiled Machine Object (``.mo``) | |
| files: | |
| .. code-block:: cmake | |
| gettext_create_translations(<pot-file> [ALL] <po-files>...) | |
| This command defines a custom target named ``translations`` that compiles | |
| the specified ``<pot-file>`` and ``<po-files>`` into binary ``.mo`` files. | |
| It also automatically adds an install rule for the generated ``.mo`` files, | |
| installing them into the default | |
| ``share/locale/<language>/LC_MESSAGES/<pot-base-filename>.mo`` path during | |
| the install phase. | |
| The arguments are: | |
| ``<pot-file>`` | |
| The path to the gettext Portable Object Template file (``.pot``) serving | |
| as the source for translations. If given as a relative path, it will be | |
| interpreted relative to the current source directory | |
| (:variable:`CMAKE_CURRENT_SOURCE_DIR`). | |
| ``ALL`` | |
| Adds the generated target to the default CMake build target so that | |
| translations are created by default during the build. | |
| ``<po-files>...`` | |
| A list of one or more translation source files in ``.po`` format, whose | |
| filenames must follow the format ``<language>.po``. Relative paths will | |
| be interpreted relative to the current source directory | |
| (:variable:`CMAKE_CURRENT_SOURCE_DIR`). | |
| .. note:: | |
| For better control over build and installation behavior, use | |
| :command:`gettext_process_po_files` instead. | |
| Deprecated Variables | |
| ^^^^^^^^^^^^^^^^^^^^ | |
| The following variables are provided for backward compatibility: | |
| ``GETTEXT_FOUND`` | |
| .. deprecated:: 4.2 | |
| Use ``Gettext_FOUND``, which has the same value. | |
| Boolean indicating whether (the requested version of) gettext was found. | |
| ``GETTEXT_VERSION_STRING`` | |
| .. deprecated:: 4.2 | |
| Superseded by the ``Gettext_VERSION``. | |
| The version of gettext found. | |
| Examples | |
| ^^^^^^^^ | |
| Examples: Finding gettext | |
| """"""""""""""""""""""""" | |
| Finding the GNU gettext tools: | |
| .. code-block:: cmake | |
| find_package(Gettext) | |
| Or, finding gettext and specifying a minimum required version: | |
| .. code-block:: cmake | |
| find_package(Gettext 0.21) | |
| Or, finding gettext and making it required (if not found, processing stops | |
| with an error message): | |
| .. code-block:: cmake | |
| find_package(Gettext REQUIRED) | |
| Example: Working With gettext in CMake | |
| """""""""""""""""""""""""""""""""""""" | |
| When starting with gettext, ``.pot`` file is considered to be created manually. | |
| For example, using a ``xgettext`` tool on the provided ``main.cxx`` source | |
| code file: | |
| .. code-block:: c++ | |
| :caption: ``main.cxx`` | |
| :emphasize-lines: 18 | |
| #include <iostream> | |
| #include <libintl.h> | |
| #include <locale.h> | |
| int main() | |
| { | |
| // Set locale from environment | |
| setlocale(LC_ALL, ""); | |
| // Bind the text domain | |
| const char* dir = std::getenv("TEXTDOMAINDIR"); | |
| if (!dir) { | |
| dir = "/usr/local/share/locale"; | |
| } | |
| bindtextdomain("MyApp", dir); | |
| textdomain("MyApp"); | |
| std::cout << gettext("Hello, World") << std::endl; | |
| return 0; | |
| } | |
| The ``xgettext`` tool extracts all strings from ``gettext()`` calls in provided | |
| source code and creates translatable strings: | |
| .. code-block:: console | |
| $ xgettext -o MyApp.pot main.cxx | |
| Translatable files can be initialized by the project manually using | |
| ``msginit`` tool: | |
| .. code-block:: console | |
| $ mkdir -p locale/de_DE | |
| $ msginit -l de_DE.UTF8 -o locale/de_DE/MyApp.po -i MyApp.pot --no-translator | |
| which creates a human-readable file that can be translated into a desired | |
| language (adjust as needed): | |
| .. code-block:: po | |
| :caption: ``locale/de_DE/MyApp.po`` | |
| :emphasize-lines: 9 | |
| msgid "" | |
| msgstr "" | |
| "Language: de\n" | |
| "Content-Type: text/plain; charset=UTF-8\n" | |
| "Content-Transfer-Encoding: 8bit\n" | |
| "Plural-Forms: nplurals=2; plural=(n != 1);\n" | |
| msgid "Hello, World" | |
| msgstr "Hallo, Welt" | |
| In CMake, the :command:`gettext_process_po_files` command provided by this | |
| module automatically creates the needed ``.mo`` files that application loads | |
| at runtime depending on the system environment variables such as ``LANG``. | |
| In the following example, also the :module:`GNUInstallDirs` module is used | |
| to provide the ``CMAKE_INSTALL_LOCALEDIR`` variable: | |
| .. code-block:: cmake | |
| :caption: ``CMakeLists.txt`` | |
| :emphasize-lines: 9-14 | |
| cmake_minimum_required(VERSION 3.24) | |
| project(GettextExample) | |
| include(GNUInstallDirs) | |
| find_package(Gettext) | |
| if(Gettext_FOUND) | |
| foreach(language IN ITEMS de_DE) | |
| gettext_process_po_files( | |
| ${language} | |
| ALL | |
| PO_FILES locale/${language}/MyApp.po | |
| INSTALL_DESTINATION ${CMAKE_INSTALL_LOCALEDIR} | |
| ) | |
| endforeach() | |
| endif() | |
| add_executable(example main.cxx) | |
| # Find and link Intl library to use gettext() from libintl.h | |
| find_package(Intl) | |
| target_link_libraries(example PRIVATE Intl::Intl) | |
| install(TARGETS example) | |
| .. code-block:: console | |
| $ cmake -B build | |
| $ cmake --build build | |
| $ DESTDIR=$(pwd)/stage cmake --install build | |
| To utilize the translations, the ``de_DE`` locale needs to be enabled on the | |
| system (see ``locale -a``). And then the localized output can be run: | |
| .. code-block:: console | |
| $ LANG=de_DE.UTF-8 TEXTDOMAINDIR=./stage/usr/local/share/locale \ | |
| ./stage/usr/local/bin/example | |
| Example: Processing POT File | |
| """""""""""""""""""""""""""" | |
| The :command:`gettext_process_pot_file` command processes ``.po`` translation | |
| files located in the current binary directory into ``.mo`` files: | |
| .. code-block:: cmake | |
| :caption: ``CMakeLists.txt`` | |
| find_package(Gettext) | |
| if(Gettext_FOUND) | |
| set(languages de_DE fr zh_CN) | |
| foreach(language IN LISTS languages) | |
| configure_file(locale/${language}.po ${language}.po COPYONLY) | |
| endforeach() | |
| gettext_process_pot_file( | |
| MyApp.pot | |
| ALL | |
| INSTALL_DESTINATION ${CMAKE_INSTALL_LOCALEDIR} | |
| LANGUAGES ${languages} | |
| ) | |
| endif() | |
| Example: Creating Translations | |
| """""""""""""""""""""""""""""" | |
| Using a simplified :command:`gettext_create_translations` command to create | |
| ``.mo`` files: | |
| .. code-block:: cmake | |
| :caption: ``CMakeLists.txt`` | |
| find_package(Gettext) | |
| if(Gettext_FOUND) | |
| gettext_create_translations( | |
| MyApp.pot | |
| ALL | |
| locale/de_DE.po | |
| locale/fr.po | |
| locale/zh_CN.po | |
| ) | |
| endif() | |
| See Also | |
| ^^^^^^^^ | |
| * The :module:`FindIntl` module to find the Gettext runtime library (libintl). | |
| #]=======================================================================] | |
| find_program(GETTEXT_MSGMERGE_EXECUTABLE msgmerge) | |
| find_program(GETTEXT_MSGFMT_EXECUTABLE msgfmt) | |
| if(GETTEXT_MSGMERGE_EXECUTABLE) | |
| execute_process(COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} --version | |
| OUTPUT_VARIABLE gettext_version | |
| ERROR_QUIET | |
| OUTPUT_STRIP_TRAILING_WHITESPACE | |
| RESULT_VARIABLE _gettext_msgmerge_version_result) | |
| get_filename_component(msgmerge_name ${GETTEXT_MSGMERGE_EXECUTABLE} NAME) | |
| get_filename_component(msgmerge_namewe ${GETTEXT_MSGMERGE_EXECUTABLE} NAME_WE) | |
| if(gettext_version MATCHES "^(${msgmerge_name}|${msgmerge_namewe}) \\([^\\)]*\\) ([0-9\\.]+[^ \n]*)") | |
| set(Gettext_VERSION "${CMAKE_MATCH_2}") | |
| set(GETTEXT_VERSION_STRING "${Gettext_VERSION}") | |
| endif() | |
| unset(gettext_version) | |
| unset(msgmerge_name) | |
| unset(msgmerge_namewe) | |
| endif() | |
| include(FindPackageHandleStandardArgs) | |
| find_package_handle_standard_args(Gettext | |
| REQUIRED_VARS GETTEXT_MSGMERGE_EXECUTABLE GETTEXT_MSGFMT_EXECUTABLE | |
| VERSION_VAR Gettext_VERSION) | |
| function(_GETTEXT_GET_UNIQUE_TARGET_NAME _name _unique_name) | |
| set(propertyName "_GETTEXT_UNIQUE_COUNTER_${_name}") | |
| get_property(currentCounter GLOBAL PROPERTY "${propertyName}") | |
| if(NOT currentCounter) | |
| set(currentCounter 1) | |
| endif() | |
| set(${_unique_name} "${_name}_${currentCounter}" PARENT_SCOPE) | |
| math(EXPR currentCounter "${currentCounter} + 1") | |
| set_property(GLOBAL PROPERTY ${propertyName} ${currentCounter}) | |
| endfunction() | |
| macro(GETTEXT_CREATE_TRANSLATIONS _potFile _firstPoFileArg) | |
| # make it a real variable, so we can modify it here | |
| set(_firstPoFile "${_firstPoFileArg}") | |
| set(_gmoFiles) | |
| get_filename_component(_potName ${_potFile} NAME) | |
| string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" _potBasename ${_potName}) | |
| get_filename_component(_absPotFile ${_potFile} ABSOLUTE) | |
| set(_addToAll) | |
| if(${_firstPoFile} STREQUAL "ALL") | |
| set(_addToAll "ALL") | |
| set(_firstPoFile) | |
| endif() | |
| foreach(_currentPoFile ${_firstPoFile} ${ARGN}) | |
| get_filename_component(_absFile ${_currentPoFile} ABSOLUTE) | |
| get_filename_component(_abs_PATH ${_absFile} PATH) | |
| get_filename_component(_lang ${_absFile} NAME_WE) | |
| set(_gmoFile ${CMAKE_CURRENT_BINARY_DIR}/${_lang}.gmo) | |
| add_custom_command( | |
| OUTPUT ${_gmoFile} | |
| COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} --quiet --update --backup=none ${_absFile} ${_absPotFile} | |
| COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${_gmoFile} ${_absFile} | |
| DEPENDS ${_absPotFile} ${_absFile} | |
| ) | |
| install(FILES ${_gmoFile} DESTINATION share/locale/${_lang}/LC_MESSAGES RENAME ${_potBasename}.mo) | |
| set(_gmoFiles ${_gmoFiles} ${_gmoFile}) | |
| endforeach() | |
| if(NOT TARGET translations) | |
| add_custom_target(translations) | |
| endif() | |
| _GETTEXT_GET_UNIQUE_TARGET_NAME(translations uniqueTargetName) | |
| add_custom_target(${uniqueTargetName} ${_addToAll} DEPENDS ${_gmoFiles}) | |
| add_dependencies(translations ${uniqueTargetName}) | |
| endmacro() | |
| function(GETTEXT_PROCESS_POT_FILE _potFile) | |
| set(_gmoFiles) | |
| set(_options ALL) | |
| set(_oneValueArgs INSTALL_DESTINATION) | |
| set(_multiValueArgs LANGUAGES) | |
| cmake_parse_arguments(_parsedArguments "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) | |
| get_filename_component(_potName ${_potFile} NAME) | |
| string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" _potBasename ${_potName}) | |
| get_filename_component(_absPotFile ${_potFile} ABSOLUTE) | |
| foreach(_lang ${_parsedArguments_LANGUAGES}) | |
| set(_poFile "${CMAKE_CURRENT_BINARY_DIR}/${_lang}.po") | |
| set(_gmoFile "${CMAKE_CURRENT_BINARY_DIR}/${_lang}.gmo") | |
| add_custom_command( | |
| OUTPUT "${_poFile}" | |
| COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} --quiet --update --backup=none ${_poFile} ${_absPotFile} | |
| DEPENDS ${_absPotFile} | |
| ) | |
| add_custom_command( | |
| OUTPUT "${_gmoFile}" | |
| COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${_gmoFile} ${_poFile} | |
| DEPENDS ${_absPotFile} ${_poFile} | |
| ) | |
| if(_parsedArguments_INSTALL_DESTINATION) | |
| install(FILES ${_gmoFile} DESTINATION ${_parsedArguments_INSTALL_DESTINATION}/${_lang}/LC_MESSAGES RENAME ${_potBasename}.mo) | |
| endif() | |
| list(APPEND _gmoFiles ${_gmoFile}) | |
| endforeach() | |
| if(NOT TARGET potfiles) | |
| add_custom_target(potfiles) | |
| endif() | |
| _GETTEXT_GET_UNIQUE_TARGET_NAME( potfiles uniqueTargetName) | |
| if(_parsedArguments_ALL) | |
| add_custom_target(${uniqueTargetName} ALL DEPENDS ${_gmoFiles}) | |
| else() | |
| add_custom_target(${uniqueTargetName} DEPENDS ${_gmoFiles}) | |
| endif() | |
| add_dependencies(potfiles ${uniqueTargetName}) | |
| endfunction() | |
| function(GETTEXT_PROCESS_PO_FILES _lang) | |
| set(_options ALL) | |
| set(_oneValueArgs INSTALL_DESTINATION) | |
| set(_multiValueArgs PO_FILES) | |
| set(_gmoFiles) | |
| cmake_parse_arguments(_parsedArguments "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) | |
| foreach(_current_PO_FILE ${_parsedArguments_PO_FILES}) | |
| get_filename_component(_name ${_current_PO_FILE} NAME) | |
| string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" _basename ${_name}) | |
| set(_gmoFile ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.gmo) | |
| add_custom_command(OUTPUT ${_gmoFile} | |
| COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${_gmoFile} ${_current_PO_FILE} | |
| WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" | |
| DEPENDS ${_current_PO_FILE} | |
| ) | |
| if(_parsedArguments_INSTALL_DESTINATION) | |
| install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${_basename}.gmo DESTINATION ${_parsedArguments_INSTALL_DESTINATION}/${_lang}/LC_MESSAGES/ RENAME ${_basename}.mo) | |
| endif() | |
| list(APPEND _gmoFiles ${_gmoFile}) | |
| endforeach() | |
| if(NOT TARGET pofiles) | |
| add_custom_target(pofiles) | |
| endif() | |
| _GETTEXT_GET_UNIQUE_TARGET_NAME(pofiles uniqueTargetName) | |
| if(_parsedArguments_ALL) | |
| add_custom_target(${uniqueTargetName} ALL DEPENDS ${_gmoFiles}) | |
| else() | |
| add_custom_target(${uniqueTargetName} DEPENDS ${_gmoFiles}) | |
| endif() | |
| add_dependencies(pofiles ${uniqueTargetName}) | |
| endfunction() |