Не могли бы вы привести пример использования GNU gettext для локализации следующей программы
#include <iostream>
#include <string>
#include <libintl.h>
int main() {
std::string name = "foo";
std::cout << gettext(name) << "\n";
return 0;
}
при следующей структуре проекта?
project/
main.cpp
CMakeLists.txt
lang/
en_US.po
de_DE.po
fr_FR.po
ru_RU.po
...
Существующие руководства почти полностью написаны на C и очень неясны. Также действительно непонятно, как генерировать файлы .pot с заданной структурой проекта.
В этом случае $ xgettext сгенерирует пустой файл. Обходной путь:
// in main()
std::string_view name = gettext("foo");
std::cout << name << "\n";





как генерировать файлы .pot
Вы пишете их вручную или генерируете с помощью msginit. Это то же самое, что и .po, просто другое название документации.
Не могли бы вы привести пример использования GNU gettext для локализации следующей программы
Следующий:
cat >./CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.11)
project(trans)
include(CTest)
add_executable(main main.cpp)
file(GLOB ffs "lang/*.po")
set(TEXTDOMAIN myprogram)
make_directory(${CMAKE_CURRENT_BINARY_DIR}/locale)
foreach(ff IN LISTS ffs)
get_filename_component(lang ${ff} NAME_WE)
make_directory(${CMAKE_CURRENT_BINARY_DIR}/locale/${lang})
make_directory(${CMAKE_CURRENT_BINARY_DIR}/locale/${lang}/LC_MESSAGES)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/locale/${lang}/LC_MESSAGES/${TEXTDOMAIN}.mo
DEPENDS
${ff}
COMMAND msgfmt -o
${CMAKE_CURRENT_BINARY_DIR}/locale/${lang}/LC_MESSAGES/${TEXTDOMAIN}.mo
${ff}
)
add_custom_target(gen_${lang} ALL DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/locale/${lang}/LC_MESSAGES/${TEXTDOMAIN}.mo
)
add_test(NAME ${lang} COMMAND main)
set_property(TEST ${lang} APPEND PROPERTY ENVIRONMENT
TEXTDOMAINDIR=${CMAKE_CURRENT_BINARY_DIR}/locale
LANGUAGE=${lang}
)
set_tests_properties(${lang} PROPERTIES
PASS_REGULAR_EXPRESSION "${lang} foo"
)
endforeach()
EOF
cat >./main.cpp <<EOF
#include <iostream>
#include <string>
#include <libintl.h>
#include <cstdlib>
#include <clocale>
int main() {
setlocale(LC_ALL, "");
const char *textdomainstr = "myprogram";
const char *textdomaindir = getenv("TEXTDOMAINDIR");
if (textdomaindir) {
bindtextdomain(textdomainstr, textdomaindir);
}
textdomain(textdomainstr);
//
std::string name = "foo";
std::cout << gettext(name.c_str()) << "\n";
}
EOF
cat >./lang/de_DE.po <<EOF
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
msgid "foo"
msgstr "de_DE foo"
EOF
cat >./lang/en_US.po <<EOF
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
msgid "foo"
msgstr "en_US foo"
EOF
cat >./lang/pl_PL.po <<EOF
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
msgid "foo"
msgstr "pl_PL foo"
EOF
Результат:
+ cmake -H. -B./_build --no-warn-unused-cli -DCMAKE_VERBOSE_MAKEFILE=1 -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DCMAKE_C_FLAGS=-Wall -ggdb3 -Wno-unused-function -fsanitize=address -fsanitize=undefined -fsanitize=pointer-compare -fsanitize=pointer-subtract -DCMAKE_CXX_FLAGS=-Wall -ggdb3 -Wno-unused-function -fsanitize=address -fsanitize=undefined -fsanitize=pointer-compare -fsanitize=pointer-subtract -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=bin -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=lib -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=lib -G Ninja
Not searching for unused variables given on the command line.
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /dev/shm/.1000.home.tmp.dir/_build
+ cmake --build ./_build --parallel --verbose
[1/5] cd /dev/shm/.1000.home.tmp.dir/_build && msgfmt -o /dev/shm/.1000.home.tmp.dir/_build/locale/de_DE/LC_MESSAGES/myprogram.mo /dev/shm/.1000.home.tmp.dir/lang/de_DE.po
[2/5] cd /dev/shm/.1000.home.tmp.dir/_build && msgfmt -o /dev/shm/.1000.home.tmp.dir/_build/locale/en_US/LC_MESSAGES/myprogram.mo /dev/shm/.1000.home.tmp.dir/lang/en_US.po
[3/5] cd /dev/shm/.1000.home.tmp.dir/_build && msgfmt -o /dev/shm/.1000.home.tmp.dir/_build/locale/pl_PL/LC_MESSAGES/myprogram.mo /dev/shm/.1000.home.tmp.dir/lang/pl_PL.po
[4/5] /usr/bin/c++ -Wall -ggdb3 -Wno-unused-function -fsanitize=address -fsanitize=undefined -fsanitize=pointer-compare -fsanitize=pointer-subtract -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /dev/shm/.1000.home.tmp.dir/main.cpp
[5/5] : && /usr/bin/c++ -Wall -ggdb3 -Wno-unused-function -fsanitize=address -fsanitize=undefined -fsanitize=pointer-compare -fsanitize=pointer-subtract CMakeFiles/main.dir/main.cpp.o -o bin/main && :
+ cd ./_build && ctest -V
UpdateCTestConfiguration from :/dev/shm/.1000.home.tmp.dir/_build/DartConfiguration.tcl
Parse Config file:/dev/shm/.1000.home.tmp.dir/_build/DartConfiguration.tcl
UpdateCTestConfiguration from :/dev/shm/.1000.home.tmp.dir/_build/DartConfiguration.tcl
Parse Config file:/dev/shm/.1000.home.tmp.dir/_build/DartConfiguration.tcl
Test project /dev/shm/.1000.home.tmp.dir/_build
Constructing a list of tests
Done constructing a list of tests
Updating test list for fixtures
Added 0 tests to meet fixture requirements
Checking test dependency graph...
Checking test dependency graph end
test 1
Start 1: de_DE
1: Test command: /dev/shm/.1000.home.tmp.dir/_build/bin/main
1: Working Directory: /dev/shm/.1000.home.tmp.dir/_build
1: Environment variables:
1: TEXTDOMAINDIR=/dev/shm/.1000.home.tmp.dir/_build/locale
1: LANGUAGE=de_DE
1: Test timeout computed to be: 1500
1: de_DE foo
1/3 Test #1: de_DE ............................ Passed 0.01 sec
test 2
Start 2: en_US
2: Test command: /dev/shm/.1000.home.tmp.dir/_build/bin/main
2: Working Directory: /dev/shm/.1000.home.tmp.dir/_build
2: Environment variables:
2: TEXTDOMAINDIR=/dev/shm/.1000.home.tmp.dir/_build/locale
2: LANGUAGE=en_US
2: Test timeout computed to be: 1500
2: en_US foo
2/3 Test #2: en_US ............................ Passed 0.01 sec
test 3
Start 3: pl_PL
3: Test command: /dev/shm/.1000.home.tmp.dir/_build/bin/main
3: Working Directory: /dev/shm/.1000.home.tmp.dir/_build
3: Environment variables:
3: TEXTDOMAINDIR=/dev/shm/.1000.home.tmp.dir/_build/locale
3: LANGUAGE=pl_PL
3: Test timeout computed to be: 1500
3: pl_PL foo
3/3 Test #3: pl_PL ............................ Passed 0.01 sec
100% tests passed, 0 tests failed out of 3
Total Test time (real) = 0.04 sec
как создать файлы .pot с заданной структурой проекта
Я всегда понимал, что .pot файлы — это .po файлы. Они одинаковые. Вы их пишете, или позволяете msginit сгенерировать их из файла шаблона, где этот файл шаблона также был написан вами, или сгенерирован каким-то скриптом. Я понял, что это намек в документации типа msgmerge, что это разные файлы, типа .pot имеет только один перевод и msgmerge обновляет его в .po файле.
Вы должны сгенерировать .mo файлы в правильной <lang>/LC_MESSAGES/<textdomain>.mo директории и структуре имен.
На первый взгляд все в порядке, но этот файл CMake не создает исходный файл /lang/main.pot (или аналогичный); также я борюсь с использованием xgettext, так как он генерирует файл без каких-либо строк для перевода
Я не понимаю вашего комментария. At a glance it seems fine что на первый взгляд кажется неправильным? CMake file does not generate /lang/main.pot (or similar) initial file ? С чего бы это? Что такое «исходный файл main.pot»? also i'm struggling with xgettext usage, as it generates a file without any strings for translation Конечно, xgettext для gettext("A CONSTANT STRING"), а не для разбора кода C++.
Руководство GNU: «При запуске нового перевода переводчик создает файл с именем LANG.po как копию файла шаблона package.pot с изменениями в начальных комментариях (в начале файла) и в записи заголовка ( первая запись, ближе к началу файла)". Я предполагаю, что CMake может создать этот файл .pot
Да, вы, как сопровождающий программы, предоставляете файл шаблона другим переводчикам для выполнения переводов. Вы пишете файл вручную или генерируете, например, с помощью xgettext. Это тот же файл, только msgstr "" пустой материал для заполнения. Как github.com/util-linux/util-linux/blob/master/po/util-linux.pot . Выложенный вами код, как вы заметили, не совместим с xgettext. Измените код на gettext("foo") или напишите свой собственный генератор, или сделайте вручную.
Это то, с чем я борюсь, потому что xgettext у меня не работает, поскольку он генерирует пустой файл шаблона.
? Да потому что xgettext не для gettext(some code), а для gettext("some constant string"). Это простой грубый парсер регулярных выражений. Действительно, gettext() в любом случае следует всегда использовать с постоянными строками, но в вашем коде было std::string.
структура каталогов важна. Это
en_US/LC_MESSAGES.gettext(name)??name is astd::string`...