// // aegis - project change supervisor // Copyright (C) 1995, 2002-2006, 2008, 2010, 2012, 2014 Peter Miller // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or (at // your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // #include #include #include #include // for snprintf #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG enum state_ty { state_uninitialized, state_C, state_human }; static state_ty state; #endif // DEBUG static bool setlocale_is_working; static int message_catalog_set; // // This is the lost of locale names to try if the default "" setlocale fails. // static const char *const trial_locales[] = { "en_US", "en", "en_US.UTF-8", "en_US.utf8", "en_US.ISO8859-1", "en_US.iso8859-1", }; #if defined(HAVE_SETLOCALE) && defined(HAVE_GETTEXT) // // NAME // setlocale_wrapper - common setlocale processing // // DESCRIPTION // The setlocale_wrapper function is used to call the setlocale // function, with defualt arguments of // // setlocale(LC_ALL, "") // // The function return true (non-zero) if setlocale succeeds, // and false (zero) if setlocale fails. // // When setlocale returns "C" or "POSIX" this is considered a // failure, because it will result in the short form of the error // messages being used. The return value will be 0 in these // cases, too. // static bool setlocale_wrapper(void) { // // At this time (Oct-2004) Cygwin only partially implements // localisation. Returning a hard coded "yes" here at least results // in long error messages for now. // #ifndef __CYGWIN__ const char *cp = setlocale(LC_ALL, ""); if (!cp) return false; if (0 == strcmp("C", cp) || 0 == strcmp("POSIX", cp)) return false; #endif // !defined(__CYGWIN__) return true; } #endif #if HAVE_SETLOCALE && HAVE_GETTEXT static const char package[] = "aegis"; #endif static const char *nlsdir; // // NAME // language_init - initialize language functions // // DESCRIPTION // The language_init function must be called at the start of the // program, to set the various locale features. // // This function must be called after the setuid initialization. // If you forget to call me, all bets are off. // void language_init(void) { // // Protect against multiple invokation. // #ifdef DEBUG if (state != state_uninitialized) fatal_raw("language_init() called more than once (bug)"); state = state_C; #endif // // Set the locale to the default (as defined by the environment // variables) and set the message domain information. // nlsdir = getenv("AEGIS_MESSAGE_LIBRARY"); if (!nlsdir || !*nlsdir) nlsdir = configured_nlsdir(); else message_catalog_set = 1; setlocale_is_working = false; #ifdef HAVE_SETLOCALE #ifdef HAVE_GETTEXT setlocale_is_working = setlocale_wrapper(); if (!setlocale_is_working) { char *lc_all; size_t j; // // Remember the LC_ALL value in case we have to reset it. // lc_all = getenv("LC_ALL"); if (lc_all) lc_all = mem_copy_string(lc_all); // // Try the list of alternatives for the English messages. // // We set the LC_ALL environment variable, because it trumps // the LC_xxx and LANG environment variables. // for (j = 0; j < SIZEOF(trial_locales); ++j) { env_set("LC_ALL", trial_locales[j]); setlocale_is_working = setlocale_wrapper(); if (setlocale_is_working) break; } // // If we didn't find a working locale, restore the LC_ALL setting. // if (!setlocale_is_working && lc_all) { env_set("LC_ALL", lc_all); mem_free(lc_all); } else env_unset("LC_ALL"); } bindtextdomain(package, nlsdir); textdomain(package); #endif // HAVE_GETTEXT // // set the main body of the program use use the C locale // setlocale(LC_ALL, "C"); #endif // HAVE_SETLOCALE } // // NAME // language_human - set for human conversation // // DESCRIPTION // The language_human function must be called to change the general // mode over to the default locale (usually dictated by the LANG // environment variable, et al). // // The language_human and language_C functions MUST bracket human // interactions, otherwise the mostly-english C locale will be // used. The default locale through-out the program is otherwise // assumed to be C. // void language_human(void) { if (!setlocale_is_working) return; #ifdef DEBUG switch (state) { case state_uninitialized: fatal_raw("you must call language_init() in main (bug)"); case state_human: fatal_raw("unbalanced language_human() call (bug)"); case state_C: break; } state = state_human; #endif #ifdef HAVE_SETLOCALE #ifdef HAVE_GETTEXT // // only need to flap the locale about like this // if we are using the gettext function // setlocale(LC_ALL, ""); #endif // HAVE_GETTEXT #endif // HAVE_SETLOCALE } // // NAME // language_C - set for program conversation // // DESCRIPTION // The language_C function must be called to restore the locale to // C, so that all the non-human stuff will work. // // The language_human and language_C functions MUST bracket human // interactions, otherwise the mostly-english C locale will be // used. The default locale through-out the program is otherwise // assumed to be C. // void language_C(void) { if (!setlocale_is_working) return; #ifdef DEBUG switch (state) { case state_uninitialized: fatal_raw("you must call language_init() in main (bug)"); case state_C: fatal_raw("unbalanced language_C() call (bug)"); case state_human: break; } state = state_C; #endif #ifdef HAVE_SETLOCALE #ifdef HAVE_GETTEXT // // only need to flap the locale about like this // if we are using the gettext function // setlocale(LC_ALL, "C"); #endif // HAVE_GETTEXT #endif // HAVE_SETLOCALE } #if HAVE_GETTEXT static const char * lang_set(void) { const char *cp; cp = getenv("LC_ALL"); if (cp && *cp) return cp; cp = getenv("LC_MESSAGES"); if (cp && *cp) return cp; cp = getenv("LANG"); if (cp && *cp) return cp; return 0; } #endif static void should_be_here(const char *path) { const char *cp = path; while (*cp == '/') ++cp; for (;;) { const char *slash = strchr(cp, '/'); if (!slash) { // // This is the last component, so check the path itself. // struct stat st; if (stat(path, &st) != 0) nerror("stat(\"%s\")", path); if (!S_ISREG(st.st_mode)) error_raw("the path \"%s\" is not a regular file", path); else if (access(path, R_OK) != 0) nerror("access(\"%s\", R_OK)", path); else error_raw("The translations file \"%s\" looks OK", path); return; } size_t len = slash - path; char path2[2000]; memcpy(path2, path, len); path2[len] = '\0'; struct stat st; if (stat(path2, &st) != 0) { if (errno == ENOENT) error_raw("the \"%s\" directory does not exist", path2); else nerror("stat(\"%s\")", path2); return; } if (!S_ISDIR(st.st_mode)) { error_raw("the path \"%s\" isn't a directory", path2); return; } if (!(st.st_mode & 1)) { error_raw("the \"%s\" directory is not world searchable", path2); return; } cp = slash + 1; } } void language_check_translations(void) { #ifndef SOURCE_FORGE_HACK #if HAVE_GETTEXT static int done; if (!done) { done = 1; if (!message_catalog_set) { // // Only whinge about this stuff if they *haven't* messed // with the AEGIS_MESSAGE_LIBRARY environment variable. // if (setlocale_is_working) { // To silence a warning from xgettext static const char the_empty_string[1] = { '\0' }; language_human(); char *cp = gettext(the_empty_string); // Empty msgid. It is reserved by GNU gettext: // gettext("") returns the header entry with meta // information, not the empty string. if (!cp || !*cp) { const char *lang = lang_set(); if (lang) { error_raw ( "Warning: You are seeing the short form " "of the error messages. The message " "catalogues may not have been installed " "correctly, or you may need to check " "the settings of your LC_ALL and LANG " "environment variables." ); error_raw ( "The language setting detected is \"%s\".", lang ); // // See if we can figure out what is wrong by // walking the path, inspired by libexplain. // char path[2000]; snprintf ( path, sizeof(path), "%s/%s/LC_MESSAGES/%s.mo", nlsdir, lang, package ); error_raw ( "The message translation file is expected to " "be located at \"%s\".", path ); error_raw ( "The translation source file, if there is one, " "would be in the Aegis tarball as " "\"lib/%s/LC_MESSAGES/%s.po\", use msgfmt(1) " "to generate the .mo file from the .po file.", lang, package ); should_be_here(path); const char *underscore = strchr(lang, '_'); if (underscore) { int lang_len = underscore - lang; snprintf ( path, sizeof(path), "%s/%.*s/LC_MESSAGES/%s.mo", nlsdir, lang_len, lang, package ); error_raw ( "The message translation file could also " "be located at \"%s\", if you set " "LANG=\"%.*s\".", path, lang_len, lang ); error_raw ( "The translation source file, if there is one, " "would be in the Aegis tarball as " "\"lib/%.*s/LC_MESSAGES/%s.po\"", lang_len, lang, package ); should_be_here(path); } } else { error_raw ( "Warning: You are seeing the short form " "of the error messages. Longer and " "more informative error messages are " "available using the English message " "catalogue available via the GNU Gettext " "facilities; use the command \"LANG=en_US; " "export LANG\" to enable them. The " "message catalogues (including the English " "catalogue) may not have been installed " "correctly. A number of alternate message " "catalogues in other languages are also " "available." ); } } language_C(); } else { error_raw ( "Warning: The setlocale() function failed. " "This results in you seeing the short form of the " "error messages. You may need to check the settings " "of your LC_ALL and LANG environment variables." #ifdef HAVE_LOCALE_PROG " Use the \"locale -a\" command to obtain a list " "of valid locales." #ifndef HAVE_LOCALE_GEN_PROG " If the list is very short, you may have a glibc " "install problem." #endif #endif #ifdef HAVE_LOCALE_GEN_PROG " See the locale-gen(8) man page for how to create " "additional locales." #endif ); } } } #endif // HAVE_GETTEXT #endif // !SOURCE_FORGE_HACK } // vim: set ts=8 sw=4 et :