| /* |
| * Copyright (c) 2010 Ævar Arnfjörð Bjarmason |
| */ |
| |
| #include "cache.h" |
| #include "exec-cmd.h" |
| #include "gettext.h" |
| #include "strbuf.h" |
| #include "utf8.h" |
| #include "config.h" |
| |
| #ifndef NO_GETTEXT |
| # include <locale.h> |
| # include <libintl.h> |
| # ifdef GIT_WINDOWS_NATIVE |
| |
| static const char *locale_charset(void) |
| { |
| const char *env = getenv("LC_ALL"), *dot; |
| |
| if (!env || !*env) |
| env = getenv("LC_CTYPE"); |
| if (!env || !*env) |
| env = getenv("LANG"); |
| |
| if (!env) |
| return "UTF-8"; |
| |
| dot = strchr(env, '.'); |
| return !dot ? env : dot + 1; |
| } |
| |
| # elif defined HAVE_LIBCHARSET_H |
| # include <libcharset.h> |
| # else |
| # include <langinfo.h> |
| # define locale_charset() nl_langinfo(CODESET) |
| # endif |
| #endif |
| |
| static const char *charset; |
| |
| /* |
| * Guess the user's preferred languages from the value in LANGUAGE environment |
| * variable and LC_MESSAGES locale category if NO_GETTEXT is not defined. |
| * |
| * The result can be a colon-separated list like "ko:ja:en". |
| */ |
| const char *get_preferred_languages(void) |
| { |
| const char *retval; |
| |
| retval = getenv("LANGUAGE"); |
| if (retval && *retval) |
| return retval; |
| |
| #ifndef NO_GETTEXT |
| retval = setlocale(LC_MESSAGES, NULL); |
| if (retval && *retval && |
| strcmp(retval, "C") && |
| strcmp(retval, "POSIX")) |
| return retval; |
| #endif |
| |
| return NULL; |
| } |
| |
| int use_gettext_poison(void) |
| { |
| static int poison_requested = -1; |
| if (poison_requested == -1) |
| poison_requested = git_env_bool("GIT_TEST_GETTEXT_POISON", 0); |
| return poison_requested; |
| } |
| |
| #ifndef NO_GETTEXT |
| static int test_vsnprintf(const char *fmt, ...) |
| { |
| char buf[26]; |
| int ret; |
| va_list ap; |
| va_start(ap, fmt); |
| ret = vsnprintf(buf, sizeof(buf), fmt, ap); |
| va_end(ap); |
| return ret; |
| } |
| |
| static void init_gettext_charset(const char *domain) |
| { |
| /* |
| This trick arranges for messages to be emitted in the user's |
| requested encoding, but avoids setting LC_CTYPE from the |
| environment for the whole program. |
| |
| This primarily done to avoid a bug in vsnprintf in the GNU C |
| Library [1]. which triggered a "your vsnprintf is broken" error |
| on Git's own repository when inspecting v0.99.6~1 under a UTF-8 |
| locale. |
| |
| That commit contains a ISO-8859-1 encoded author name, which |
| the locale aware vsnprintf(3) won't interpolate in the format |
| argument, due to mismatch between the data encoding and the |
| locale. |
| |
| Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at |
| this point, because it'd require auditing all the code that uses C |
| functions whose semantics are modified by LC_CTYPE. |
| |
| But only setting LC_MESSAGES as we do creates a problem, since |
| we declare the encoding of our PO files[2] the gettext |
| implementation will try to recode it to the user's locale, but |
| without LC_CTYPE it'll emit something like this on 'git init' |
| under the Icelandic locale: |
| |
| Bj? til t?ma Git lind ? /hlagh/.git/ |
| |
| Gettext knows about the encoding of our PO file, but we haven't |
| told it about the user's encoding, so all the non-US-ASCII |
| characters get encoded to question marks. |
| |
| But we're in luck! We can set LC_CTYPE from the environment |
| only while we call nl_langinfo and |
| bind_textdomain_codeset. That suffices to tell gettext what |
| encoding it should emit in, so it'll now say: |
| |
| Bjó til tóma Git lind í /hlagh/.git/ |
| |
| And the equivalent ISO-8859-1 string will be emitted under a |
| ISO-8859-1 locale. |
| |
| With this change way we get the advantages of setting LC_CTYPE |
| (talk to the user in his language/encoding), without the major |
| drawbacks (changed semantics for C functions we rely on). |
| |
| However foreign functions using other message catalogs that |
| aren't using our neat trick will still have a problem, e.g. if |
| we have to call perror(3): |
| |
| #include <stdio.h> |
| #include <locale.h> |
| #include <errno.h> |
| |
| int main(void) |
| { |
| setlocale(LC_MESSAGES, ""); |
| setlocale(LC_CTYPE, "C"); |
| errno = ENODEV; |
| perror("test"); |
| return 0; |
| } |
| |
| Running that will give you a message with question marks: |
| |
| $ LANGUAGE= LANG=de_DE.utf8 ./test |
| test: Kein passendes Ger?t gefunden |
| |
| The vsnprintf bug has been fixed since glibc 2.17. |
| |
| Then we could simply set LC_CTYPE from the environment, which would |
| make things like the external perror(3) messages work. |
| |
| See t/t0203-gettext-setlocale-sanity.sh's "gettext.c" tests for |
| regression tests. |
| |
| 1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530 |
| 2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po |
| */ |
| setlocale(LC_CTYPE, ""); |
| charset = locale_charset(); |
| bind_textdomain_codeset(domain, charset); |
| /* the string is taken from v0.99.6~1 */ |
| if (test_vsnprintf("%.*s", 13, "David_K\345gedal") < 0) |
| setlocale(LC_CTYPE, "C"); |
| } |
| |
| void git_setup_gettext(void) |
| { |
| const char *podir = getenv(GIT_TEXT_DOMAIN_DIR_ENVIRONMENT); |
| char *p = NULL; |
| |
| if (!podir) |
| podir = p = system_path(GIT_LOCALE_PATH); |
| |
| use_gettext_poison(); /* getenv() reentrancy paranoia */ |
| |
| if (!is_directory(podir)) { |
| free(p); |
| return; |
| } |
| |
| bindtextdomain("git", podir); |
| setlocale(LC_MESSAGES, ""); |
| setlocale(LC_TIME, ""); |
| init_gettext_charset("git"); |
| textdomain("git"); |
| |
| free(p); |
| } |
| |
| /* return the number of columns of string 's' in current locale */ |
| int gettext_width(const char *s) |
| { |
| static int is_utf8 = -1; |
| if (is_utf8 == -1) |
| is_utf8 = is_utf8_locale(); |
| |
| return is_utf8 ? utf8_strwidth(s) : strlen(s); |
| } |
| #endif |
| |
| int is_utf8_locale(void) |
| { |
| #ifdef NO_GETTEXT |
| if (!charset) { |
| const char *env = getenv("LC_ALL"); |
| if (!env || !*env) |
| env = getenv("LC_CTYPE"); |
| if (!env || !*env) |
| env = getenv("LANG"); |
| if (!env) |
| env = ""; |
| if (strchr(env, '.')) |
| env = strchr(env, '.') + 1; |
| charset = xstrdup(env); |
| } |
| #endif |
| return is_encoding_utf8(charset); |
| } |