Could you provide an example of GNU gettext usage to localize the following program
#include <iostream>
#include <string>
#include <libintl.h>
int main() {
std::string name = "foo";
std::cout << gettext(name) << "\n";
return 0;
}
assuming the following project structure?
project/
main.cpp
CMakeLists.txt
lang/
en_US.po
de_DE.po
fr_FR.po
ru_RU.po
...
Existing guides are almost explicitly in C and really obscure. Also it is really unclear how to generate .pot files with the given project structure
Edit
$ xgettext will generate empty file in this case. Workaround:
// in main()
std::string_view name = gettext("foo");
std::cout << name << "\n";
CodePudding user response:
how to generate .pot files
You write them by hand or generate with msginit. It's the same as .po
, just a different name for documentation.
Could you provide an example of GNU gettext usage to localize the following program
The following:
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>=2 && n<=4 && (n0<10 || n0>=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>=2 && n<=4 && (n0<10 || n0>=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>=2 && n<=4 && (n0<10 || n0>=20) ? 1 : 2);\n"
msgid "foo"
msgstr "pl_PL foo"
EOF
Results in:
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
how to generate .pot files with the given project structure
I always understood that .pot
files are .po
files. They are the same. You write them, or let msginit
generate them from a template file, where this template file was also written by you, or generated by some script. I understood it's a hint in documentation like msgmerge
that those are different files, like .pot
has only one translation and msgmerge
updates it in .po
file.
You have to generate .mo
files in proper <lang>/LC_MESSAGES/<textdomain>.mo
directory and name structure.