1/* 2 * Copyright (c) 2010 Ævar Arnfjörð Bjarmason 3 */ 4 5#include"git-compat-util.h" 6#include"gettext.h" 7#include"strbuf.h" 8#include"utf8.h" 9 10#ifndef NO_GETTEXT 11# include <locale.h> 12# include <libintl.h> 13# ifdef HAVE_LIBCHARSET_H 14# include <libcharset.h> 15# else 16# include <langinfo.h> 17# define locale_charset() nl_langinfo(CODESET) 18# endif 19#endif 20 21#ifdef GETTEXT_POISON 22intuse_gettext_poison(void) 23{ 24static int poison_requested = -1; 25if(poison_requested == -1) 26 poison_requested =getenv("GIT_GETTEXT_POISON") ?1:0; 27return poison_requested; 28} 29#endif 30 31#ifndef NO_GETTEXT 32static inttest_vsnprintf(const char*fmt, ...) 33{ 34char buf[26]; 35int ret; 36va_list ap; 37va_start(ap, fmt); 38 ret =vsnprintf(buf,sizeof(buf), fmt, ap); 39va_end(ap); 40return ret; 41} 42 43static const char*charset; 44static voidinit_gettext_charset(const char*domain) 45{ 46/* 47 This trick arranges for messages to be emitted in the user's 48 requested encoding, but avoids setting LC_CTYPE from the 49 environment for the whole program. 50 51 This primarily done to avoid a bug in vsnprintf in the GNU C 52 Library [1]. which triggered a "your vsnprintf is broken" error 53 on Git's own repository when inspecting v0.99.6~1 under a UTF-8 54 locale. 55 56 That commit contains a ISO-8859-1 encoded author name, which 57 the locale aware vsnprintf(3) won't interpolate in the format 58 argument, due to mismatch between the data encoding and the 59 locale. 60 61 Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at 62 this point, because it'd require auditing all the code that uses C 63 functions whose semantics are modified by LC_CTYPE. 64 65 But only setting LC_MESSAGES as we do creates a problem, since 66 we declare the encoding of our PO files[2] the gettext 67 implementation will try to recode it to the user's locale, but 68 without LC_CTYPE it'll emit something like this on 'git init' 69 under the Icelandic locale: 70 71 Bj? til t?ma Git lind ? /hlagh/.git/ 72 73 Gettext knows about the encoding of our PO file, but we haven't 74 told it about the user's encoding, so all the non-US-ASCII 75 characters get encoded to question marks. 76 77 But we're in luck! We can set LC_CTYPE from the environment 78 only while we call nl_langinfo and 79 bind_textdomain_codeset. That suffices to tell gettext what 80 encoding it should emit in, so it'll now say: 81 82 Bjó til tóma Git lind í /hlagh/.git/ 83 84 And the equivalent ISO-8859-1 string will be emitted under a 85 ISO-8859-1 locale. 86 87 With this change way we get the advantages of setting LC_CTYPE 88 (talk to the user in his language/encoding), without the major 89 drawbacks (changed semantics for C functions we rely on). 90 91 However foreign functions using other message catalogs that 92 aren't using our neat trick will still have a problem, e.g. if 93 we have to call perror(3): 94 95 #include <stdio.h> 96 #include <locale.h> 97 #include <errno.h> 98 99 int main(void) 100 { 101 setlocale(LC_MESSAGES, ""); 102 setlocale(LC_CTYPE, "C"); 103 errno = ENODEV; 104 perror("test"); 105 return 0; 106 } 107 108 Running that will give you a message with question marks: 109 110 $ LANGUAGE= LANG=de_DE.utf8 ./test 111 test: Kein passendes Ger?t gefunden 112 113 The vsnprintf bug has been fixed since glibc 2.17. 114 115 Then we could simply set LC_CTYPE from the environment, which would 116 make things like the external perror(3) messages work. 117 118 See t/t0203-gettext-setlocale-sanity.sh's "gettext.c" tests for 119 regression tests. 120 121 1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530 122 2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po 123 */ 124setlocale(LC_CTYPE,""); 125 charset =locale_charset(); 126bind_textdomain_codeset(domain, charset); 127/* the string is taken from v0.99.6~1 */ 128if(test_vsnprintf("%.*s",13,"David_K\345gedal") <0) 129setlocale(LC_CTYPE,"C"); 130} 131 132voidgit_setup_gettext(void) 133{ 134const char*podir =getenv("GIT_TEXTDOMAINDIR"); 135 136if(!podir) 137 podir = GIT_LOCALE_PATH; 138bindtextdomain("git", podir); 139setlocale(LC_MESSAGES,""); 140init_gettext_charset("git"); 141textdomain("git"); 142} 143 144/* return the number of columns of string 's' in current locale */ 145intgettext_width(const char*s) 146{ 147static int is_utf8 = -1; 148if(is_utf8 == -1) 149 is_utf8 = !strcmp(charset,"UTF-8"); 150 151return is_utf8 ?utf8_strwidth(s) :strlen(s); 152} 153#endif