setlocale () для LC_MESSAGES к несуществующей локали не работает

Для проекта встроенного программного обеспечения я добавляю поддержку переводов, и, поскольку мы работаем со встроенным Linux, я выбрал libc gettext(). У нас нет никаких определений локали, поэтому я пытаюсь установить только LC_MESSAGES локаль на желаемую локаль:

setlocale(LC_MESSAGES, "fake");

(Я использую имя fake с файлом fake.mo, чтобы сделать псевдоперевод, прежде чем я доберусь до нужного перевода).

Это отлично работает при статической связи, возвращает дескриптор локали, bindtextdomain() и друзья работают нормально, и я получаю из него свою "переведенную" строку:

setlocale() returned "fake"
current textdomain is "ewe"
current base directory is "/opt/btech/probe/share/locale/WA"
current LC_MESSAGES locale is "fake"
gettext("Error") ==> "Ḗřřǿř"

Теперь, когда я компилирую это динамически, это не работает. Ни на целевом устройстве, ни локально на моем ПК (файлы установлены одинаково). Вызов setlocale() завершается ошибкой, возвращая указатель NULL и устанавливая для errno значение ENOENT (файл не найден). В момент setlocale() я не указал bindtextdomain() на то, где находятся мои файлы, но переключение вызовов не помогает.

Я что-то делаю неправильно, мой рабочий пример сверху неправильный и не должен работать? Нужны ли мне определения локали для всего, что я вызываю setlocale(), даже для LC_MESSAGES?

Это источник тестового двоичного файла:

#include <libintl.h>
#include <locale.h>
#include <stdio.h>

int main()
{
    const char *l = setlocale(LC_MESSAGES, "fake");

    printf("setlocale() returned \"%s\"\n", l);

    bind_textdomain_codeset("ewe", "UTF-8");
    bindtextdomain("ewe", "/opt/btech/probe/share/locale/WA");
    textdomain("ewe");

    printf("current textdomain is \"%s\"\n", textdomain(NULL));
    printf("current base directory is \"%s\"\n", bindtextdomain(textdomain(NULL), NULL));
    printf("current LC_MESSAGES locale is \"%s\"\n", setlocale(LC_MESSAGES, NULL));
    printf("gettext(\"Error\") ==> \"%s\"\n", gettext("Error"));

    return 0;
}

Это результат при динамической компиляции (для цели или хоста):

setlocale() returned "(null)"
current textdomain is "ewe"
current base directory is "/opt/btech/probe/share/locale/WA"
current LC_MESSAGES locale is "C"
gettext("Error") ==> "Error"

EDIT: компиляция тестового двоичного файла как статического на моем хосте (x64 Linux) также заставляет его работать, поэтому в статической компиляции есть что-то особенное.

Дополнительный вопрос: могу ли я заставить gettext загружать конкретный файл mo напрямую? В основном я хотел бы иметь замену для bindtextdomain(), которая вместо этого принимает аргумент имени файла.

ИЗМЕНИТЬ 2: Итак, я в конце концов нашел этот пост, в котором говорилось, что я могу заставить gettext() загрузить любой перевод, если сначала у меня есть действующий setlocale() звонок. Итак, мой текущий обходной путь состоит в том, чтобы фактически создать /usr/lib/locale/locale-archive, содержащий только en_US локаль, вызывая setlocale(LC_MESSAGES, "en_US"); setenv("LANGUAGE", "fake");, что в конечном итоге приводит к загрузке правильного каталога сообщений. По-прежнему кажется уродливым обходным решением, и я до сих пор не понимаю, почему статическая ссылка работает без нее.


person nafmo    schedule 26.04.2016    source источник
comment
Разве не лучше использовать setlocale(LC_MESSAGES, ""); с правильными переменными среды?   -  person jdarthenay    schedule 26.04.2016
comment
Это встроенная система, в которой я работаю без установленных переменных среды. Я загружаю язык, который нужно использовать (то есть языковой стандарт, который нужно установить) из файла конфигурации при запуске программы.   -  person nafmo    schedule 26.04.2016
comment
Выполнение сначала setenv("LC_MESSAGES", "fake") и замена вызова setlocale на setlocale(LC_MESSAGES, "") по-прежнему не работают так же, как выполнение прямого вызова setlocale().   -  person nafmo    schedule 26.04.2016


Ответы (1)


У меня была аналогичная проблема (встроенная система, в которой у меня нет особого контроля над корневой файловой системой), и я нашел, что это обходное решение работает:

  • Поместите все переводы в /<mydir>/lang/
  • SYS_LC_MESSAGES можно сгенерировать с помощью localedef. Я сделал один из локали C в своей системе и скопировал его в каждый целевой каталог.

    mkdir output
    localedef -f UTF-8 -i /usr/share/i18n/locales/C  output/mylocale
    cp output/mylocale/LC_MESSAGES/SYS_LC_MESSAGES <mydir>/lang/ENG/LC_MESSAGES/
    
  • Установите LOCPATH на <mydir>
  • Выполняйте отладку с помощью strace, чтобы увидеть, какие файлы он пытается открыть.

Это мой последний макет файла:

<mydir>
├── lang
│   ├── ENG
│   │   └── LC_MESSAGES
│   │       ├── SYS_LC_MESSAGES
│   │       └── mac.mo
│   ├── FRE
│   │   └── LC_MESSAGES
│   │       ├── SYS_LC_MESSAGES
│   │       └── mac.mo
│   ├── GER
│   │   └── LC_MESSAGES
│   │       ├── SYS_LC_MESSAGES
│   │       └── mac.mo
│   ├── ITA
│   │   └── LC_MESSAGES
│   │       ├── SYS_LC_MESSAGES
│   │       └── mac.mo
│   ├── SPA
│   │   └── LC_MESSAGES
│   │       ├── SYS_LC_MESSAGES
│   │       └── mac.mo

Это фрагмент кода:

putenv("LOCPATH=/<mydir>/lang");
setlocale(LC_ALL, "");  
setlocale(LC_MESSAGES, "ENG");
bindtextdomain("mac", "/<mydir>/lang");
textdomain("mac");      
gettext("Hello world");  

Это хитрость, правильным решением было бы правильно сгенерировать локали.

person Matteo Nardi    schedule 24.10.2018