{"thread":{"id":"24181","subject":"[PATCH v5] Add infrastructure for translating Git with gettext","startedAt":"2010-06-23T22:32:18Z","lastAt":"2010-06-23T22:32:18Z","messageCount":1,"participants":["Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":5,"patchTotal":null},"messages":[{"id":"144112","messageId":"1277332338-8486-1-git-send-email-avarab@gmail.com","threadId":"24181","inReplyTo":null,"subject":"[PATCH v5] Add infrastructure for translating Git with gettext","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-06-23T22:32:18Z","receivedAt":"2010-06-23T22:32:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"All of the interface messages in Git core are currently hardcoded in\nEnglish. Change that by optionally enabling translation of the core C,\nShell and Perl programs via GNU or SunOS gettext. If you set the\nappropriate LC_* variables Git will speak your language, provided that\nsomeone has submitted a translation.\n\nIf gettext isn't available, or if Git is compiled with\nNO_GETTEXT=YesPlease, then Git fall back on its previous behavior of\nonly speaking English. When using ./configure the autoconf script will\nauto-detect if the gettext libraries are installed and act\nappropriately.\n\nWith NO_GETTEXT=YesPlease gettext support will be #defined away for C\nprograms. For Shell and Perl programs we rely on the git message\ncatalog not being available. That's a reasonable assumption since then\nthe message catalog won't be installed on the system during make\ninstall.\n\nThe gettext wrappers that are provided in the patch are only the bare\nminimum required to begin translation work. In particular I haven't\nadded wrappers for the gettext functions that enable plural support,\nor those that provide message context (msgctxt).\n\nThose can be added later. The intent is to start with a small subset\nand see what we need later, not to start with something that's\nunnecessarily large right away.\n\nImplementation and usage notes:\n\n * General:\n\n   Gettext .mo files will be installed and looked for in the standard\n   $(prefix)/share/locale path. GIT_TEXTDOMAINDIR can also be set to\n   override that, but that's only intended to be used to test Git\n   itself.\n\n * Perl:\n\n   Perl code that wants to be localized should use the new Git::I18n\n   module. It imports a __ function into the caller's package by\n   default.\n\n   Instead of using the high level Locale::TextDomain interface I've\n   opted to use the low-level (equivalent to the C interface)\n   Locale::Messages module, which Locale::TextDomain itself uses.\n\n   Locale::TextDomain does a lot of redundant work we don't need, and\n   some of it would potentially introduce bugs. It tries to set the\n   $TEXTDOMAIN based on package of the caller, and has its own\n   hardcoded paths where it'll search for messages.\n\n   I found it easier just to completely avoid it rather than try to\n   circumvent its behavior. In any case, this is an issue wholly\n   internal Git::I18N. Its guts can be changed later if that's deemed\n   necessary.\n\n   See <AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LWnVd@mail.gmail.com>\n   for a further elaboration on this topic.\n\n * Shell:\n\n   Shell code that's to be localized should use the new git-sh-i18n\n   library. It's just a wrapper for the system's gettext.sh.\n\n   If gettext.sh isn't available we'll fall back gettext(1) if it's\n   available. The latter is available without the former on Solaris,\n   which has its own non-GNU gettext implementation. We also need to\n   emulate eval_gettext() there.\n\n   If neither are present we'll use a dumb printf(1) fall-through\n   wrapper.\n\n   I originally tried to detect if the system supported `echo -n' but\n   I found this to be a waste of time. My benchmarks on Linux, Solaris\n   and FreeBSD reveal that printf(1) is fast enough, especially since\n   we aren't calling gettext() from within any tight loops, and\n   unlikely to ever do so.\n\nThis series has been tested by me on Ubuntu 10.04, Debian testing,\nFreeBSD 8.1 and SunOS 5.10, and by others on Mac OS X 10.6.3 (with\nXcode 3.2.2) and openSUSE Factory (11.3, milestone 7).\n\nSunOS has its own non-GNU gettext implementation which this patch\nsupports, although that may change in the future if it turns out that\nwe need some GNU libintl features that SunOS doesn't provide.\n\nThis patch is based on work by Jeff Epler <jepler@unpythonic.net> who\ndid the initial Makefile / C work, and a lot of comments from the Git\nmailing list, including Jonathan Nieder, Jakub Narebski, Johannes\nSixt, Peter Krefting, Junio C Hamano, Thomas Rast and others.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nTested-By: John Wiegley <johnw@newartisans.com>\nTested-by: Graham Anderson <graham.anderson@gmail.com>\n---\n\nHere's another version of the Gettext series for git. This time I've\nomitted the \"Add initial C, Shell and Perl gettext translations\" patch\ndue to its conflict with other topics. That part can be submitted once\nthis is in and Git stops moving under our feet.\n\nThis applies cleanly to master/next/pu. The conflicts were in Makefile\nwith a recent gitweb series.\n\n .gitignore                   |    2 +\n INSTALL                      |   12 ++++\n Makefile                     |   69 ++++++++++++++++++++-\n config.mak.in                |    2 +\n configure.ac                 |   12 ++++\n daemon.c                     |    3 +\n fast-import.c                |    3 +\n gettext.c                    |   22 +++++++\n gettext.h                    |   18 ++++++\n git-sh-i18n.sh               |   71 +++++++++++++++++++++\n git.c                        |    3 +\n http-backend.c               |    3 +\n http-fetch.c                 |    3 +\n http-push.c                  |    3 +\n imap-send.c                  |    3 +\n perl/Git/I18N.pm             |   91 +++++++++++++++++++++++++++\n perl/Makefile                |    3 +-\n perl/Makefile.PL             |   14 ++++-\n po/.gitignore                |    1 +\n po/is.po                     |   47 ++++++++++++++\n shell.c                      |    3 +\n show-index.c                 |    3 +\n t/t0200-gettext.sh           |  138 ++++++++++++++++++++++++++++++++++++++++++\n t/t0200/test.c               |   13 ++++\n t/t0200/test.perl            |   14 ++++\n t/t0200/test.sh              |   14 ++++\n t/t0201-gettext-fallbacks.sh |   50 +++++++++++++++\n t/t0202-gettext-perl.sh      |   23 +++++++\n t/t0202/test.pl              |  104 +++++++++++++++++++++++++++++++\n t/test-lib.sh                |    2 +\n upload-pack.c                |    3 +\n 31 files changed, 748 insertions(+), 4 deletions(-)\n create mode 100644 gettext.c\n create mode 100644 gettext.h\n create mode 100644 git-sh-i18n.sh\n create mode 100644 perl/Git/I18N.pm\n create mode 100644 po/.gitignore\n create mode 100644 po/is.po\n create mode 100755 t/t0200-gettext.sh\n create mode 100644 t/t0200/test.c\n create mode 100644 t/t0200/test.perl\n create mode 100644 t/t0200/test.sh\n create mode 100755 t/t0201-gettext-fallbacks.sh\n create mode 100755 t/t0202-gettext-perl.sh\n create mode 100644 t/t0202/test.pl\n\ndiff --git a/.gitignore b/.gitignore\nindex 14e2b6b..6c2b193 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -125,6 +125,7 @@\n /git-rm\n /git-send-email\n /git-send-pack\n+/git-sh-i18n\n /git-sh-setup\n /git-shell\n /git-shortlog\n@@ -204,3 +205,4 @@\n *.pdb\n /Debug/\n /Release/\n+/share/\ndiff --git a/INSTALL b/INSTALL\nindex 61086ab..9c9a5fa 100644\n--- a/INSTALL\n+++ b/INSTALL\n@@ -93,6 +93,18 @@ Issues of note:\n \t  history graphically, and in git-gui.  If you don't want gitk or\n \t  git-gui, you can use NO_TCLTK.\n \n+\t- A gettext library is used by default for localizing Git. The\n+\t  primary target is GNU libintl, but the Solaris gettext\n+\t  implementation also works.\n+\n+\t  We need a gettext.h on the system for C code, gettext.sh (or\n+\t  Solaris gettext(1)) for shell scripts, and libintl-perl for Perl\n+\t  programs.\n+\n+\t  Set NO_GETTEXT to disable localization support and make Git only\n+\t  use English. Under autoconf the configure script will do this\n+\t  automatically if it can't find libintl on the system.\n+\n  - Some platform specific issues are dealt with Makefile rules,\n    but depending on your specific installation, you may not\n    have all the libraries/tools needed, or you may have\ndiff --git a/Makefile b/Makefile\nindex 9aca8a1..c97cbd8 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -34,6 +34,15 @@ all::\n # Define NO_EXPAT if you do not have expat installed.  git-http-push is\n # not built, and you cannot push using http:// and https:// transports.\n #\n+# Define NO_GETTEXT if you don't want to build with Git with gettext\n+# support. Building it requires GNU libintl or another gettext\n+# implementation, and additionally libintl-perl at runtime.\n+#\n+# Define NEEDS_LIBINTL if you haven't set NO_GETTEXT and your system\n+# needs to be explicitly linked to -lintl. It's defined automatically\n+# on platforms where we don't expect glibc (Linux, Hurd,\n+# GNU/kFreeBSD), which includes libintl.\n+#\n # Define EXPATDIR=/foo/bar if your expat header and library files are in\n # /foo/bar/include and /foo/bar/lib directories.\n #\n@@ -281,6 +290,7 @@ infodir = share/info\n gitexecdir = libexec/git-core\n sharedir = $(prefix)/share\n gitwebdir = $(sharedir)/gitweb\n+localedir = $(sharedir)/locale\n template_dir = share/git-core/templates\n htmldir = share/doc/git-doc\n ifeq ($(prefix),/usr)\n@@ -294,7 +304,7 @@ lib = lib\n # DESTDIR=\n pathsep = :\n \n-export prefix bindir sharedir sysconfdir gitwebdir\n+export prefix bindir sharedir sysconfdir gitwebdir localedir\n \n CC = gcc\n AR = ar\n@@ -308,6 +318,8 @@ TCL_PATH = tclsh\n TCLTK_PATH = wish\n PTHREAD_LIBS = -lpthread\n PTHREAD_CFLAGS =\n+XGETTEXT = xgettext\n+MSGFMT = msgfmt\n \n export TCL_PATH TCLTK_PATH\n \n@@ -369,6 +381,7 @@ SCRIPT_SH += git-web--browse.sh\n SCRIPT_LIB += git-mergetool--lib\n SCRIPT_LIB += git-parse-remote\n SCRIPT_LIB += git-sh-setup\n+SCRIPT_LIB += git-sh-i18n\n \n SCRIPT_PERL += git-add--interactive.perl\n SCRIPT_PERL += git-difftool.perl\n@@ -534,6 +547,7 @@ LIB_H += userdiff.h\n LIB_H += utf8.h\n LIB_H += xdiff-interface.h\n LIB_H += xdiff/xdiff.h\n+LIB_H += gettext.h\n \n LIB_OBJS += abspath.o\n LIB_OBJS += advice.o\n@@ -575,6 +589,9 @@ LIB_OBJS += entry.o\n LIB_OBJS += environment.o\n LIB_OBJS += exec_cmd.o\n LIB_OBJS += fsck.o\n+ifndef NO_GETTEXT\n+LIB_OBJS += gettext.o\n+endif\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hash.o\n@@ -747,6 +764,14 @@ EXTLIBS =\n # Platform specific tweaks\n #\n \n+# Platform specific defaults. Where we'd only like some feature on the\n+# minority of systems, e.g. if linking to a library isn't needed\n+# because its features are included in the GNU C library.\n+ifndef NO_GETTEXT\n+\t# Systems that use GNU gettext and glibc are the exception\n+\tNEEDS_LIBINTL = YesPlease\n+endif\n+\n # We choose to avoid \"if .. else if .. else .. endif endif\"\n # because maintaining the nesting to match is a pain.  If\n # we had \"elif\" things would have been much nicer...\n@@ -762,11 +787,13 @@ ifeq ($(uname_S),Linux)\n \tNO_STRLCPY = YesPlease\n \tNO_MKSTEMPS = YesPlease\n \tHAVE_PATHS_H = YesPlease\n+\tNEEDS_LIBINTL =\n endif\n ifeq ($(uname_S),GNU/kFreeBSD)\n \tNO_STRLCPY = YesPlease\n \tNO_MKSTEMPS = YesPlease\n \tHAVE_PATHS_H = YesPlease\n+\tNEEDS_LIBINTL =\n endif\n ifeq ($(uname_S),UnixWare)\n \tCC = cc\n@@ -958,6 +985,7 @@ ifeq ($(uname_S),GNU)\n \tNO_STRLCPY=YesPlease\n \tNO_MKSTEMPS = YesPlease\n \tHAVE_PATHS_H = YesPlease\n+\tNEEDS_LIBINTL =\n endif\n ifeq ($(uname_S),IRIX)\n \tNO_SETENV = YesPlease\n@@ -1456,6 +1484,14 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT\n \texport GIT_TEST_CMP_USE_COPIED_CONTEXT\n endif\n \n+ifdef NO_GETTEXT\n+\tCOMPAT_CFLAGS += -DNO_GETTEXT\n+endif\n+\n+ifdef NEEDS_LIBINTL\n+\tEXTLIBS += -lintl\n+endif\n+\n ifeq ($(TCLTK_PATH),)\n NO_TCLTK=NoThanks\n endif\n@@ -1485,6 +1521,7 @@ ifndef V\n \tQUIET_BUILT_IN = @echo '   ' BUILTIN $@;\n \tQUIET_GEN      = @echo '   ' GEN $@;\n \tQUIET_LNCP     = @echo '   ' LN/CP $@;\n+\tQUIET_MSGFMT   = @echo '   ' MSGFMT $@;\n \tQUIET_SUBDIR0  = +@subdir=\n \tQUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \\\n \t\t\t $(MAKE) $(PRINT_DIR) -C $$subdir\n@@ -1513,7 +1550,9 @@ template_dir_SQ = $(subst ','\\'',$(template_dir))\n htmldir_SQ = $(subst ','\\'',$(htmldir))\n prefix_SQ = $(subst ','\\'',$(prefix))\n gitwebdir_SQ = $(subst ','\\'',$(gitwebdir))\n+sharedir_SQ = $(subst ','\\'',$(sharedir))\n \n+LOCALEDIR_SQ = $(subst ','\\'',$(localedir))\n SHELL_PATH_SQ = $(subst ','\\'',$(SHELL_PATH))\n PERL_PATH_SQ = $(subst ','\\'',$(PERL_PATH))\n PYTHON_PATH_SQ = $(subst ','\\'',$(PYTHON_PATH))\n@@ -1563,7 +1602,7 @@ ifndef NO_TCLTK\n \t$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all\n endif\n ifndef NO_PERL\n-\t$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all\n+\t$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all\n endif\n ifndef NO_PYTHON\n \t$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all\n@@ -1609,6 +1648,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \\\n     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \\\n     -e 's|@@DIFF@@|$(DIFF_SQ)|' \\\n     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \\\n+    -e 's|@@LOCALEDIR@@|$(LOCALEDIR_SQ)|g' \\\n     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \\\n     -e $(BROKEN_PATH_FIX) \\\n     $@.sh >$@+\n@@ -1936,6 +1976,21 @@ cscope:\n \t$(RM) cscope*\n \t$(FIND) . -name '*.[hcS]' -print | xargs cscope -b\n \n+pot:\n+\t$(XGETTEXT) --add-comments --keyword=_ --keyword=N_ --output=po/git.pot --language=C $(C_OBJ:o=c) t/t0200/test.c\n+\t$(XGETTEXT) --add-comments --join-existing --output=po/git.pot --language=Shell $(SCRIPT_SH) t/t0200/test.sh\n+\t$(XGETTEXT) --add-comments --join-existing --keyword=__ --output=po/git.pot --language=Perl $(SCRIPT_PERL) t/t0200/test.perl\n+\n+POFILES := $(wildcard po/*.po)\n+MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))\n+MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES))\n+ifndef NO_GETTEXT\n+all:: $(MOFILES)\n+endif\n+share/locale/%/LC_MESSAGES/git.mo: po/%.po\n+\t@mkdir -p $(dir $@)\n+\t$(QUIET_MSGFMT)$(MSGFMT) -o $@ $<\n+\n ### Detect prefix changes\n TRACK_CFLAGS = $(subst ','\\'',$(ALL_CFLAGS)):\\\n              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)\n@@ -1965,6 +2020,7 @@ endif\n ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT\n \t@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@\n endif\n+\t@echo NO_GETTEXT=\\''$(subst ','\\'',$(subst ','\\'',$(NO_GETTEXT)))'\\' >>$@\n \n ### Detect Tck/Tk interpreter path changes\n ifndef NO_TCLTK\n@@ -2056,6 +2112,11 @@ install: all\n \t$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'\n \t$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'\n \t$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'\n+ifndef NO_GETTEXT\n+\t$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sharedir_SQ)/locale'\n+\t(cd share && tar cf - locale) | \\\n+\t\t(cd '$(DESTDIR_SQ)$(sharedir_SQ)' && umask 022 && tar xof -)\n+endif\n \t$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install\n ifndef NO_PERL\n \t$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install\n@@ -2204,6 +2265,10 @@ ifndef NO_TCLTK\n \t$(MAKE) -C git-gui clean\n endif\n \t$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS\n+ifndef NO_GETTEXT\n+\t$(RM) po/git.pot\n+\t$(RM) -r share/\n+endif\n \n .PHONY: all install clean strip\n .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell\ndiff --git a/config.mak.in b/config.mak.in\nindex b4e65c3..011b103 100644\n--- a/config.mak.in\n+++ b/config.mak.in\n@@ -34,9 +34,11 @@ NO_CURL=@NO_CURL@\n NO_EXPAT=@NO_EXPAT@\n NO_LIBGEN_H=@NO_LIBGEN_H@\n HAVE_PATHS_H=@HAVE_PATHS_H@\n+NO_GETTEXT=@NO_GETTEXT@\n NEEDS_LIBICONV=@NEEDS_LIBICONV@\n NEEDS_SOCKET=@NEEDS_SOCKET@\n NEEDS_RESOLV=@NEEDS_RESOLV@\n+NEEDS_LIBINTL=@NEEDS_LIBINTL@\n NEEDS_LIBGEN=@NEEDS_LIBGEN@\n NO_SYS_SELECT_H=@NO_SYS_SELECT_H@\n NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@\ndiff --git a/configure.ac b/configure.ac\nindex 5601e8b..202e820 100644\n--- a/configure.ac\n+++ b/configure.ac\n@@ -600,6 +600,12 @@ AC_CHECK_LIB([c], [basename],\n AC_SUBST(NEEDS_LIBGEN)\n test -n \"$NEEDS_LIBGEN\" && LIBS=\"$LIBS -lgen\"\n \n+AC_CHECK_LIB([c], [gettext],\n+[NEEDS_LIBINTL=],\n+[NEEDS_LIBINTL=YesPlease])\n+AC_SUBST(NEEDS_LIBINTL)\n+test -n \"$NEEDS_LIBINTL\" && LIBS=\"$LIBS -lintl\"\n+\n ## Checks for header files.\n AC_MSG_NOTICE([CHECKS for header files])\n #\n@@ -777,6 +783,12 @@ AC_CHECK_HEADER([paths.h],\n [HAVE_PATHS_H=])\n AC_SUBST(HAVE_PATHS_H)\n #\n+# Define NO_GETTEXT if you don't have libintl.h\n+AC_CHECK_HEADER([libintl.h],\n+[NO_GETTEXT=],\n+[NO_GETTEXT=YesPlease])\n+AC_SUBST(NO_GETTEXT)\n+#\n # Define NO_STRCASESTR if you don't have strcasestr.\n GIT_CHECK_FUNC(strcasestr,\n [NO_STRCASESTR=],\ndiff --git a/daemon.c b/daemon.c\nindex e22a2b7..784f897 100644\n--- a/daemon.c\n+++ b/daemon.c\n@@ -3,6 +3,7 @@\n #include \"exec_cmd.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n+#include \"gettext.h\"\n \n #include <syslog.h>\n \n@@ -975,6 +976,8 @@ int main(int argc, char **argv)\n \tgid_t gid = 0;\n \tint i;\n \n+\tgit_setup_gettext();\n+\n \tgit_extract_argv0_path(argv[0]);\n \n \tfor (i = 1; i < argc; i++) {\ndiff --git a/fast-import.c b/fast-import.c\nindex 1e5d66e..6497b0c 100644\n--- a/fast-import.c\n+++ b/fast-import.c\n@@ -156,6 +156,7 @@ Format of STDIN stream:\n #include \"csum-file.h\"\n #include \"quote.h\"\n #include \"exec_cmd.h\"\n+#include \"gettext.h\"\n \n #define PACK_ID_BITS 16\n #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)\n@@ -2904,6 +2905,8 @@ int main(int argc, const char **argv)\n \n \tgit_extract_argv0_path(argv[0]);\n \n+\tgit_setup_gettext();\n+\n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage(fast_import_usage);\n \ndiff --git a/gettext.c b/gettext.c\nnew file mode 100644\nindex 0000000..7ae5cae\n--- /dev/null\n+++ b/gettext.c\n@@ -0,0 +1,22 @@\n+#include \"exec_cmd.h\"\n+#include <locale.h>\n+#include <libintl.h>\n+#include <stdlib.h>\n+\n+extern void git_setup_gettext(void) {\n+\tchar *podir;\n+\tchar *envdir = getenv(\"GIT_TEXTDOMAINDIR\");\n+\n+\tif (envdir) {\n+\t\t(void)bindtextdomain(\"git\", envdir);\n+\t} else {\n+\t\tpodir = (char *)system_path(\"share/locale\");\n+\t\tif (!podir) return;\n+\t\t(void)bindtextdomain(\"git\", podir);\n+\t\tfree(podir);\n+\t}\n+\n+\t(void)setlocale(LC_MESSAGES, \"\");\n+\t(void)setlocale(LC_CTYPE, \"\");\n+\t(void)textdomain(\"git\");\n+}\ndiff --git a/gettext.h b/gettext.h\nnew file mode 100644\nindex 0000000..e02939a\n--- /dev/null\n+++ b/gettext.h\n@@ -0,0 +1,18 @@\n+#ifndef GETTEXT_H\n+#define GETTEXT_H\n+\n+#ifdef NO_GETTEXT\n+static inline void git_setup_gettext(void) {}\n+#else\n+extern void git_setup_gettext(void);\n+#endif\n+\n+#define N_(s) (s)\n+#ifdef NO_GETTEXT\n+#define _(s) (s)\n+#else\n+#include <libintl.h>\n+#define _(s) gettext(s)\n+#endif\n+\n+#endif\ndiff --git a/git-sh-i18n.sh b/git-sh-i18n.sh\nnew file mode 100644\nindex 0000000..698a000\n--- /dev/null\n+++ b/git-sh-i18n.sh\n@@ -0,0 +1,71 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason\n+#\n+# This is Git's interface to gettext.sh. Use it right after\n+# git-sh-setup as:\n+#\n+#   . git-sh-setup\n+#   . git-sh-i18n\n+#\n+#   # For constant interface messages:\n+#   gettext \"A message for the user\"; echo\n+#\n+#   # To interpolate variables:\n+#   details=\"oh noes\"\n+#   eval_gettext \"An error occured: \\$details\"; echo\n+#\n+# See \"info '(gettext)sh'\" for the full manual.\n+\n+# Export the TEXTDOMAIN* data that we need for Git\n+TEXTDOMAIN=git\n+export TEXTDOMAIN\n+if [ -z \"$GIT_TEXTDOMAINDIR\" ]\n+then\n+\tTEXTDOMAINDIR=\"@@LOCALEDIR@@\"\n+else\n+\tTEXTDOMAINDIR=\"$GIT_TEXTDOMAINDIR\"\n+fi\n+export TEXTDOMAINDIR\n+\n+if test -z \"$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS\" && type gettext.sh >/dev/null 2>&1\n+then\n+\t# This is GNU libintl's gettext.sh, we don't need to do anything\n+\t# else than setting up the environment and loading gettext.sh\n+\tGIT_INTERNAL_GETTEXT_SH_SCHEME=gnu\n+\texport GIT_INTERNAL_GETTEXT_SH_SCHEME\n+\n+\t# Try to use libintl's gettext.sh, or fall back to English if we\n+\t# can't.\n+\t. gettext.sh\n+elif test -z \"$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS\" && test \"$(gettext -h 2>&1)\" = \"-h\"\n+then\n+\t# We don't have gettext.sh, but there's a gettext binary in our\n+\t# path. This is probably Solaris or something like it which has a\n+\t# gettext implementation that isn't GNU libintl.\n+\tGIT_INTERNAL_GETTEXT_SH_SCHEME=solaris\n+\texport GIT_INTERNAL_GETTEXT_SH_SCHEME\n+\n+\t# Solaris has a gettext(1) but no eval_gettext(1)\n+\teval_gettext () {\n+\t\tgettext_out=$(gettext \"$1\")\n+\t\tgettext_eval=\"printf '%s' \\\"$gettext_out\\\"\"\n+\t\tprintf \"%s\" \"`eval \\\"$gettext_eval\\\"`\"\n+\t}\n+else\n+\t# Since gettext.sh isn't available we'll have to define our own\n+\t# dummy pass-through functions.\n+\n+\t# Tell our tests that we don't have the real gettext.sh\n+\tGIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough\n+\texport GIT_INTERNAL_GETTEXT_SH_SCHEME\n+\n+\tgettext () {\n+\t\tprintf \"%s\" \"$1\"\n+\t}\n+\n+\teval_gettext () {\n+\t\tgettext_eval=\"printf '%s' \\\"$1\\\"\"\n+\t\tprintf \"%s\" \"`eval \\\"$gettext_eval\\\"`\"\n+\t}\n+fi\ndiff --git a/git.c b/git.c\nindex 99f0363..d749eab 100644\n--- a/git.c\n+++ b/git.c\n@@ -3,6 +3,7 @@\n #include \"cache.h\"\n #include \"quote.h\"\n #include \"run-command.h\"\n+#include \"gettext.h\"\n \n const char git_usage_string[] =\n \t\"git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\\n\"\n@@ -490,6 +491,8 @@ int main(int argc, const char **argv)\n \tif (!cmd)\n \t\tcmd = \"git-help\";\n \n+\tgit_setup_gettext();\n+\n \t/*\n \t * \"git-xxxx\" is the same as \"git xxxx\", but we obviously:\n \t *\ndiff --git a/http-backend.c b/http-backend.c\nindex 44ce6bb..ecbcc68 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -7,6 +7,7 @@\n #include \"run-command.h\"\n #include \"string-list.h\"\n #include \"url.h\"\n+#include \"gettext.h\"\n \n static const char content_type[] = \"Content-Type\";\n static const char content_length[] = \"Content-Length\";\n@@ -550,6 +551,8 @@ int main(int argc, char **argv)\n \tchar *cmd_arg = NULL;\n \tint i;\n \n+\tgit_setup_gettext();\n+\n \tgit_extract_argv0_path(argv[0]);\n \tset_die_routine(die_webcgi);\n \ndiff --git a/http-fetch.c b/http-fetch.c\nindex 762c750..b889c36 100644\n--- a/http-fetch.c\n+++ b/http-fetch.c\n@@ -2,6 +2,7 @@\n #include \"exec_cmd.h\"\n #include \"http.h\"\n #include \"walker.h\"\n+#include \"gettext.h\"\n \n static const char http_fetch_usage[] = \"git http-fetch \"\n \"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url\";\n@@ -24,6 +25,8 @@ int main(int argc, const char **argv)\n \tint get_verbosely = 0;\n \tint get_recover = 0;\n \n+\tgit_setup_gettext();\n+\n \tgit_extract_argv0_path(argv[0]);\n \n \twhile (arg < argc && argv[arg][0] == '-') {\ndiff --git a/http-push.c b/http-push.c\nindex c9bcd11..fc2c5f7 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -10,6 +10,7 @@\n #include \"remote.h\"\n #include \"list-objects.h\"\n #include \"sigchain.h\"\n+#include \"gettext.h\"\n \n #include <expat.h>\n \n@@ -1791,6 +1792,8 @@ int main(int argc, char **argv)\n \tstruct remote *remote;\n \tchar *rewritten_url = NULL;\n \n+\tgit_setup_gettext();\n+\n \tgit_extract_argv0_path(argv[0]);\n \n \trepo = xcalloc(sizeof(*repo), 1);\ndiff --git a/imap-send.c b/imap-send.c\nindex 1a577a0..c8fb1f9 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -25,6 +25,7 @@\n #include \"cache.h\"\n #include \"exec_cmd.h\"\n #include \"run-command.h\"\n+#include \"gettext.h\"\n #ifdef NO_OPENSSL\n typedef void *SSL;\n #else\n@@ -1535,6 +1536,8 @@ int main(int argc, char **argv)\n \n \tgit_extract_argv0_path(argv[0]);\n \n+\tgit_setup_gettext();\n+\n \tif (argc != 1)\n \t\tusage(imap_send_usage);\n \ndiff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm\nnew file mode 100644\nindex 0000000..5918d68\n--- /dev/null\n+++ b/perl/Git/I18N.pm\n@@ -0,0 +1,91 @@\n+package Git::I18N;\n+use 5.006002;\n+use strict;\n+use warnings;\n+use Exporter;\n+use base 'Exporter';\n+\n+our $VERSION = '0.01';\n+\n+our @EXPORT = qw(__);\n+our @EXPORT_OK = @EXPORT;\n+\n+sub __bootstrap_locale_messages {\n+\tour $TEXTDOMAIN = 'git';\n+\tour $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';\n+\n+\trequire POSIX;\n+\tPOSIX->import(qw(setlocale));\n+\t# Non-core prerequisite module\n+\trequire Locale::Messages;\n+\tLocale::Messages->import(qw(:locale_h :libintl_h));\n+\n+\tsetlocale(LC_MESSAGES(), '');\n+\tsetlocale(LC_CTYPE(), '');\n+\ttextdomain($TEXTDOMAIN);\n+\tbindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);\n+\n+\treturn;\n+}\n+\n+BEGIN\n+{\n+\t# Used by our test script to see if it should test fallbacks or\n+\t# not.\n+\tour $__HAS_LIBRARY = 1;\n+\n+\tlocal $@;\n+\teval { __bootstrap_locale_messages() };\n+\tif ($@) {\n+\t\t# Tell test.pl that we couldn't load the gettext library.\n+\t\t$Git::I18N::__HAS_LIBRARY = 0;\n+\n+\t\t# Just a fall-through no-op\n+\t\t*__ = sub ($) { $_[0] };\n+\t} else {\n+\t\t*__ = \\&Locale::Messages::gettext;\n+\t}\n+}\n+\n+1;\n+\n+__END__\n+\n+=head1 NAME\n+\n+Git::I18N - Perl interface to Git's Gettext localizations\n+\n+=head1 SYNOPSIS\n+\n+\tuse Git::I18N;\n+\n+\tprint __(\"Welcome to Git!\\n\");\n+\n+\tprintf __(\"The following error occured: %s\\n\"), $error;\n+\n+=head1 DESCRIPTION\n+\n+Git's internal Perl interface to gettext via L<Locale::Messages>. If\n+L<Locale::Messages> can't be loaded (it's not a core module) we\n+provide stub passthrough fallbacks.\n+\n+This is a distilled interface to gettext, see C<info '(gettext)Perl'>\n+for the full interface. This module implements only a small part of\n+it.\n+\n+=head1 FUNCTIONS\n+\n+=head2 __($)\n+\n+L<Locale::Messages>'s gettext function if all goes well, otherwise our\n+passthrough fallback function.\n+\n+=head1 AUTHOR\n+\n+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>\n+\n+=head1 COPYRIGHT\n+\n+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>\n+\n+=cut\ndiff --git a/perl/Makefile b/perl/Makefile\nindex 4ab21d6..4e624ff 100644\n--- a/perl/Makefile\n+++ b/perl/Makefile\n@@ -5,6 +5,7 @@ makfile:=perl.mak\n \n PERL_PATH_SQ = $(subst ','\\'',$(PERL_PATH))\n prefix_SQ = $(subst ','\\'',$(prefix))\n+localedir_SQ = $(subst ','\\'',$(localedir))\n \n ifndef V\n \tQUIET = @\n@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile\n \techo '\techo $(instdir_SQ)' >> $@\n else\n $(makfile): Makefile.PL ../GIT-CFLAGS\n-\t$(PERL_PATH) $< PREFIX='$(prefix_SQ)'\n+\t$(PERL_PATH) $< PREFIX='$(prefix_SQ)' --localedir='$(localedir_SQ)'\n endif\n \n # this is just added comfort for calling make directly in perl dir\ndiff --git a/perl/Makefile.PL b/perl/Makefile.PL\nindex 0b9deca..456d45b 100644\n--- a/perl/Makefile.PL\n+++ b/perl/Makefile.PL\n@@ -1,4 +1,12 @@\n+use strict;\n+use warnings;\n use ExtUtils::MakeMaker;\n+use Getopt::Long;\n+\n+# Sanity: die at first unknown option\n+Getopt::Long::Configure qw/ pass_through /;\n+\n+GetOptions(\"localedir=s\" => \\my $localedir);\n \n sub MY::postamble {\n \treturn <<'MAKE_FRAG';\n@@ -16,7 +24,10 @@ endif\n MAKE_FRAG\n }\n \n-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');\n+my %pm = (\n+\t'Git.pm' => '$(INST_LIBDIR)/Git.pm',\n+\t'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',\n+);\n \n # We come with our own bundled Error.pm. It's not in the set of default\n # Perl modules so install it if it's not available on the system yet.\n@@ -33,6 +44,7 @@ WriteMakefile(\n \tNAME            => 'Git',\n \tVERSION_FROM    => 'Git.pm',\n \tPM\t\t=> \\%pm,\n+\tPM_FILTER\t=> qq[\\$(PERL) -pe \"s<\\\\Q++LOCALEDIR++\\\\E><$localedir>\"],\n \tMAKEFILE\t=> 'perl.mak',\n \tINSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'\n );\ndiff --git a/po/.gitignore b/po/.gitignore\nnew file mode 100644\nindex 0000000..221000e\n--- /dev/null\n+++ b/po/.gitignore\n@@ -0,0 +1 @@\n+/*.pot\ndiff --git a/po/is.po b/po/is.po\nnew file mode 100644\nindex 0000000..95739f1\n--- /dev/null\n+++ b/po/is.po\n@@ -0,0 +1,47 @@\n+msgid \"\"\n+msgstr \"\"\n+\"Project-Id-Version: Git\\n\"\n+\"PO-Revision-Date: 2010-06-05 19:06 +0000\\n\"\n+\"Language-Team: Git Mailing List <git@vger.kernel.org>\\n\"\n+\"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\\n\"\n+\"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\\n\"\n+\"MIME-Version: 1.0\\n\"\n+\"Content-Type: text/plain; charset=UTF-8\\n\"\n+\"Content-Transfer-Encoding: 8bit\\n\"\n+\n+#: t/t0200/test.c:4\n+msgid \"See git help COMMAND for more information on a specific command.\"\n+msgstr \"Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun.\"\n+\n+#. TRANSLATORS: This is a test. You don't need to translate it.\n+#: t/t0200/test.c:9\n+msgid \"TEST: A C test string\"\n+msgstr \"TILRAUN: C tilraunastrengur\"\n+\n+#. TRANSLATORS: This is a test. You don't need to translate it.\n+#: t/t0200/test.c:12\n+#, c-format\n+msgid \"TEST: A C test string %s\"\n+msgstr \"TILRAUN: C tilraunastrengur %s\"\n+\n+#. TRANSLATORS: This is a test. You don't need to translate it.\n+#: t/t0200/test.sh:8\n+msgid \"TEST: A Shell test string\"\n+msgstr \"TILRAUN: Skeljartilraunastrengur\"\n+\n+#. TRANSLATORS: This is a test. You don't need to translate it.\n+#: t/t0200/test.sh:11\n+#, sh-format\n+msgid \"TEST: A Shell test $variable\"\n+msgstr \"TILRAUN: Skeljartilraunastrengur með breytunni $variable\"\n+\n+#. TRANSLATORS: This is a test. You don't need to translate it.\n+#: t/t0200/test.perl:8\n+msgid \"TEST: A Perl test string\"\n+msgstr \"TILRAUN: Perl tilraunastrengur\"\n+\n+#. TRANSLATORS: This is a test. You don't need to translate it.\n+#: t/t0200/test.perl:11\n+#, perl-format\n+msgid \"TEST: A Perl test variable %s\"\n+msgstr \"TILRAUN: Perl tilraunastrengur með breytunni %s\"\ndiff --git a/shell.c b/shell.c\nindex e4864e0..ba27c6b 100644\n--- a/shell.c\n+++ b/shell.c\n@@ -2,6 +2,7 @@\n #include \"quote.h\"\n #include \"exec_cmd.h\"\n #include \"strbuf.h\"\n+#include \"gettext.h\"\n \n static int do_generic_cmd(const char *me, char *arg)\n {\n@@ -51,6 +52,8 @@ int main(int argc, char **argv)\n \tstruct commands *cmd;\n \tint devnull_fd;\n \n+\tgit_setup_gettext();\n+\n \t/*\n \t * Always open file descriptors 0/1/2 to avoid clobbering files\n \t * in die().  It also avoids not messing up when the pipes are\ndiff --git a/show-index.c b/show-index.c\nindex 4c0ac13..c2f5448 100644\n--- a/show-index.c\n+++ b/show-index.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"pack.h\"\n+#include \"gettext.h\"\n \n static const char show_index_usage[] =\n \"git show-index < <packed archive index>\";\n@@ -11,6 +12,8 @@ int main(int argc, char **argv)\n \tunsigned int version;\n \tstatic unsigned int top_index[256];\n \n+\tgit_setup_gettext();\n+\n \tif (argc != 1)\n \t\tusage(show_index_usage);\n \tif (fread(top_index, 2 * 4, 1, stdin) != 1)\ndiff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh\nnew file mode 100755\nindex 0000000..98fbc4d\n--- /dev/null\n+++ b/t/t0200-gettext.sh\n@@ -0,0 +1,138 @@\n+#!/bin/sh\n+\n+test_description='Gettext support for Git'\n+\n+. ./test-lib.sh\n+\n+GIT_TEXTDOMAINDIR=\"$GIT_EXEC_PATH/share/locale\"\n+GIT_PO_PATH=\"$GIT_EXEC_PATH/po\"\n+export GIT_TEXTDOMAINDIR GIT_PO_PATH\n+\n+. \"$GIT_EXEC_PATH\"/git-sh-i18n\n+\n+test_expect_success \"sanity: \\$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)\" '\n+    test -n \"$GIT_INTERNAL_GETTEXT_SH_SCHEME\"\n+'\n+\n+test_expect_success 'sanity: $TEXTDOMAIN is git' '\n+    test $TEXTDOMAIN = \"git\"\n+'\n+\n+# Basic xgettext() extraction tests on po/*.po. Doesn't need gettext support\n+test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '\n+    ! grep \"A Perl string xgettext will not get\" \"$GIT_PO_PATH\"/is.po\n+'\n+\n+test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '\n+    grep \"TRANSLATORS: This is a test\" \"$TEST_DIRECTORY\"/t0200/* | wc -l >expect &&\n+    grep \"TRANSLATORS: This is a test\" \"$GIT_PO_PATH\"/is.po  | wc -l >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '\n+    ! grep \"This is a phony\" \"$GIT_PO_PATH\"/is.po &&\n+    ! grep \"the above comment\" \"$GIT_PO_PATH\"/is.po\n+'\n+\n+if test_have_prereq GETTEXT; then\n+\ttest_expect_success 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '\n+    test -d \"$TEXTDOMAINDIR\" &&\n+    test \"$TEXTDOMAINDIR\" = \"$GIT_TEXTDOMAINDIR\"\n+'\n+\n+\ttest_expect_success 'sanity: Icelandic locale was compiled' '\n+    test -f \"$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo\"\n+'\n+else\n+\ttest_expect_success \"sanity: \\$TEXTDOMAINDIR doesn't exists with NO_GETTEXT=YesPlease\" '\n+    ! test -d \"$TEXTDOMAINDIR\" &&\n+    test \"$TEXTDOMAINDIR\" = \"$GIT_TEXTDOMAINDIR\"\n+'\n+fi\n+\n+# We can go no further without actual gettext support\n+if ! test_have_prereq GETTEXT || test $GIT_INTERNAL_GETTEXT_SH_SCHEME = \"fallthrough\"; then\n+\tsay \"Skipping the rest of the gettext tests, Git was compiled with NO_GETTEXT=YesPlease\"\n+\ttest_done\n+fi\n+\n+# The remaining tests are locale sensitive. They'll fail if the\n+# example is_IS locale isn't installed on e.g a fresh Debian system\n+if test -z \"$TEST_GIT_GETTEXT_EXHAUSTIVE\"; then\n+\tsay 'Skipping unportable gettext() tests which depend on is_IS locale, set TEST_GIT_GETTEXT_EXHAUSTIVE=1 to enable'\n+\n+\ttest_done\n+fi \n+\n+# TODO: When we have more locales, generalize this to test them\n+# all. Maybe we'll need a dir->locale map for that.\n+test_expect_success 'sanity: gettext(\"\") metadata is OK' '\n+    # Return value may be non-zero\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"\" >zero-expect &&\n+    grep \"Project-Id-Version: Git\" zero-expect &&\n+    grep \"Git Mailing List <git@vger.kernel.org>\" zero-expect &&\n+    grep \"Content-Type: text/plain; charset=UTF-8\" zero-expect &&\n+    grep \"Content-Transfer-Encoding: 8bit\" zero-expect\n+'\n+\n+test_expect_success 'sanity: gettext(unknown) is passed through' '\n+    printf \"This is not a translation string\"  >expect &&\n+    gettext \"This is not a translation string\" >actual &&\n+    eval_gettext \"This is not a translation string\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+# xgettext from C\n+test_expect_success 'xgettext: C extraction of _() and N_() strings' '\n+    printf \"TILRAUN: C tilraunastrengur\" >expect &&\n+    printf \"\\n\" >>expect &&\n+    printf \"Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun.\" >>expect &&\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"TEST: A C test string\" >actual &&\n+    printf \"\\n\" >>actual &&\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"See git help COMMAND for more information on a specific command.\" >>actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'xgettext: C extraction with %s' '\n+    printf \"TILRAUN: C tilraunastrengur %%s\" >expect &&\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"TEST: A C test string %s\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+# xgettext from Shell\n+test_expect_success 'xgettext: Shell extraction' '\n+    printf \"TILRAUN: Skeljartilraunastrengur\" >expect &&\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"TEST: A Shell test string\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'xgettext: Shell extraction with $variable' '\n+    printf \"TILRAUN: Skeljartilraunastrengur með breytunni a var i able\" >x-expect &&\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 variable=\"a var i able\" eval_gettext \"TEST: A Shell test \\$variable\" >x-actual &&\n+    test_cmp x-expect x-actual\n+'\n+\n+# xgettext from Perl\n+test_expect_success 'xgettext: Perl extraction' '\n+    printf \"TILRAUN: Perl tilraunastrengur\" >expect &&\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"TEST: A Perl test string\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'xgettext: Perl extraction with %s' '\n+    printf \"TILRAUN: Perl tilraunastrengur með breytunni %%s\" >expect &&\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"TEST: A Perl test variable %s\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'sanity: No gettext(\"\") data for fantasy locale' '\n+    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext \"\" >fantasy-locale &&\n+    ! test -s fantasy-locale\n+'\n+\n+test_expect_success 'sanity: Some gettext(\"\") data for real locale' '\n+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext \"\" >real-locale &&\n+    test -s real-locale\n+'\n+\n+test_done\ndiff --git a/t/t0200/test.c b/t/t0200/test.c\nnew file mode 100644\nindex 0000000..93373b3\n--- /dev/null\n+++ b/t/t0200/test.c\n@@ -0,0 +1,13 @@\n+/* This is a phony C program that's only here to test xgettext message extraction */\n+\n+const char help[] =\n+\tN_(\"See 'git help COMMAND' for more information on a specific command.\");\n+\n+int main(void)\n+{\n+\t/* TRANSLATORS: This is a test. You don't need to translate it. */\n+\tputs(_(\"TEST: A C test string\"));\n+\n+\t/* TRANSLATORS: This is a test. You don't need to translate it. */\n+\tprintf(_(\"TEST: A C test string %s\"), \"variable\");\n+}\ndiff --git a/t/t0200/test.perl b/t/t0200/test.perl\nnew file mode 100644\nindex 0000000..36fba34\n--- /dev/null\n+++ b/t/t0200/test.perl\n@@ -0,0 +1,14 @@\n+# This is a phony Perl program that's only here to test xgettext\n+# message extraction\n+\n+# so the above comment won't be folded into the next one by xgettext\n+1;\n+\n+# TRANSLATORS: This is a test. You don't need to translate it.\n+print __(\"TEST: A Perl test string\");\n+\n+# TRANSLATORS: This is a test. You don't need to translate it.\n+printf __(\"TEST: A Perl test variable %s\"), \"moo\";\n+\n+# TRANSLATORS: If you see this, Git has a bug\n+print _\"TEST: A Perl string xgettext will not get\";\ndiff --git a/t/t0200/test.sh b/t/t0200/test.sh\nnew file mode 100644\nindex 0000000..022d607\n--- /dev/null\n+++ b/t/t0200/test.sh\n@@ -0,0 +1,14 @@\n+# This is a phony Shell program that's only here to test xgettext\n+# message extraction\n+\n+# so the above comment won't be folded into the next one by xgettext\n+echo\n+\n+# TRANSLATORS: This is a test. You don't need to translate it.\n+gettext \"TEST: A Shell test string\"\n+\n+# TRANSLATORS: This is a test. You don't need to translate it.\n+eval_gettext \"TEST: A Shell test \\$variable\"\n+\n+# TRANSLATORS: If you see this, Git has a bug\n+_(\"TEST: A Shell string xgettext won't get\")\ndiff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh\nnew file mode 100755\nindex 0000000..ea1ca9d\n--- /dev/null\n+++ b/t/t0201-gettext-fallbacks.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='Gettext Shell fallbacks'\n+\n+. ./test-lib.sh\n+\n+GIT_TEXTDOMAINDIR=\"$GIT_EXEC_PATH/share/locale\"\n+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease\n+\n+export GIT_TEXTDOMAINDIR GIT_INTERNAL_GETTEXT_TEST_FALLBACKS\n+\n+. \"$GIT_EXEC_PATH\"/git-sh-i18n\n+\n+test_expect_success \"sanity: \\$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)\" '\n+    test -n \"$GIT_INTERNAL_GETTEXT_SH_SCHEME\"\n+'\n+\n+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '\n+    test -n \"$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS\"\n+'\n+\n+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME\" is fallthrough' '\n+    test \"$GIT_INTERNAL_GETTEXT_SH_SCHEME\" = \"fallthrough\"\n+'\n+\n+test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '\n+    printf \"test\" >expect &&\n+    gettext \"test\" >actual &&\n+    test_cmp expect actual &&\n+    printf \"test more words\" >expect &&\n+    gettext \"test more words\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' '\n+    printf \"test\" >expect &&\n+    eval_gettext \"test\" >actual &&\n+    test_cmp expect actual &&\n+    printf \"test more words\" >expect &&\n+    eval_gettext \"test more words\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' '\n+    printf \"test YesPlease\" >expect &&\n+    eval_gettext \"test \\$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS\" >actual &&\n+    test_cmp expect actual\n+'\n+\n+test_done\ndiff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh\nnew file mode 100755\nindex 0000000..3a37b23\n--- /dev/null\n+++ b/t/t0202-gettext-perl.sh\n@@ -0,0 +1,23 @@\n+#!/bin/sh\n+\n+test_description='Perl gettext interface (Git::I18N)'\n+. ./test-lib.sh\n+\n+GIT_TEXTDOMAINDIR=\"$GIT_EXEC_PATH/share/locale\"\n+export GIT_TEXTDOMAINDIR\n+\n+if ! test_have_prereq PERL; then\n+\tsay 'skipping perl interface tests, perl not available'\n+\ttest_done\n+fi\n+\n+\"$PERL_PATH\" -MTest::More -e 0 2>/dev/null || {\n+\tsay \"Perl Test::More unavailable, skipping test\"\n+\ttest_done\n+}\n+\n+test_external_without_stderr \\\n+    'Perl Git::I18N API' \\\n+    \"$PERL_PATH\" \"$TEST_DIRECTORY\"/t0202/test.pl\n+\n+test_done\ndiff --git a/t/t0202/test.pl b/t/t0202/test.pl\nnew file mode 100644\nindex 0000000..a952fb2\n--- /dev/null\n+++ b/t/t0202/test.pl\n@@ -0,0 +1,104 @@\n+#!/usr/bin/perl\n+use 5.006002;\n+use lib (split(/:/, $ENV{GITPERLLIB}));\n+use warnings;\n+use strict;\n+use Test::More tests => 9;\n+use Git::I18N;\n+use POSIX qw(:locale_h);\n+\n+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;\n+\n+ok(1, \"Testing Git::I18N version $Git::I18N::VERSION with \" .\n+\t ($has_gettext_library\n+\t  ? \"Locale::Messages version $Locale::Messages::VERSION\"\n+\t  : \"NO Perl gettext library\"));\n+ok(1, \"Git::I18N is located at $INC{'Git/I18N.pm'}\");\n+\n+ok($Git::I18N::VERSION, 'sanity: Git::I18N defines a $VERSION');\n+{\n+\tmy $exports = @Git::I18N::EXPORT;\n+\tok($exports, \"sanity: Git::I18N has $exports export(s)\");\n+}\n+is_deeply(\\@Git::I18N::EXPORT, \\@Git::I18N::EXPORT_OK, \"sanity: Git::I18N exports everything by default\");\n+\n+# prototypes\n+{\n+\t# Add prototypes here when modifying the public interface to add\n+\t# more gettext wrapper functions.\n+\tmy %prototypes = (qw(\n+\t\t__\t$\n+    ));\n+\twhile (my ($sub, $proto) = each %prototypes) {\n+\t\tis(prototype(\\&{\"Git::I18N::$sub\"}), $proto, \"sanity: $sub has a $proto prototype\");\n+\t}\n+}\n+\n+# Test basic passthrough in the C locale\n+{\n+\tlocal $ENV{LANGUAGE} = 'C';\n+\tlocal $ENV{LC_ALL}   = 'C';\n+\tlocal $ENV{LANG} = 'C';\n+\n+\tmy ($got, $expect) = (('TEST: A Perl test string') x 2);\n+\n+\tis(__($got), $expect, \"Passing a string through __() in the C locale works\");\n+}\n+\n+# Test a basic message on different locales\n+SKIP: {\n+    unless ($ENV{TEST_GIT_GETTEXT_EXHAUSTIVE}) {\n+        # Can't reliably test __() with a non-C locales because the\n+        # required locales may not be installed on the system.\n+        #\n+        # We test for these anyway as part of the shell\n+        # tests. Skipping these here will eliminate failures on odd\n+        # platforms with incomplete locale data.\n+\n+        skip \"Set TEST_GIT_GETTEXT_EXHAUSTIVE=1 to enable exhaustive Git::I18N locale tests\", 2;\n+    }\n+\n+\tmy $test = sub {\n+\t\tmy ($got, $expect, $msg, $locale) = @_;\n+\t\t# Maybe this system doesn't have the locale we're trying to\n+\t\t# test.\n+\t\tmy $locale_ok = setlocale(LC_ALL, $locale);\n+\t\tis(__($got), $expect, \"$msg a gettext library + <$locale> locale <$got> turns into <$expect>\");\n+\t};\n+\n+\tmy $env_C = sub {\n+\t\t$ENV{LANGUAGE} = 'C';\n+\t\t$ENV{LC_ALL}   = 'C';\n+\t};\n+\n+\tmy $env_is = sub {\n+\t\t$ENV{LANGUAGE} = 'is';\n+\t\t$ENV{LC_ALL}   = 'is_IS.UTF-8';\n+\t};\n+\n+\t# Translation's the same as the original\n+\tmy ($got, $expect) = (('TEST: A Perl test string') x 2);\n+\n+\tif ($has_gettext_library) {\n+\t\t{\n+\t\t\tlocal %ENV; $env_C->();\n+\t\t\t$test->($got, $expect, \"With\", 'C');\n+\t\t}\n+\n+\t\t{\n+\t\t\tmy ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');\n+\t\t\tlocal %ENV; $env_is->();\n+\t\t\t$test->($got, $expect, \"With\", 'is_IS.UTF-8');\n+\t\t}\n+\t} else {\n+\t\t{\n+\t\t\tlocal %ENV; $env_C->();\n+\t\t\t$test->($got, $expect, \"Without\", 'C');\n+\t\t}\n+\n+\t\t{\n+\t\t\tlocal %ENV; $env_is->();\n+\t\t\t$test->($got, $expect, \"Without\", 'is');\n+\t\t}\n+\t}\n+}\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 367f053..d0f1524 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -37,6 +37,7 @@ ORIGINAL_TERM=$TERM\n # For repeatability, reset the environment to known value.\n LANG=C\n LC_ALL=C\n+LANGUAGE=C\n PAGER=cat\n TZ=UTC\n TERM=dumb\n@@ -854,6 +855,7 @@ esac\n \n test -z \"$NO_PERL\" && test_set_prereq PERL\n test -z \"$NO_PYTHON\" && test_set_prereq PYTHON\n+test -z \"$NO_GETTEXT\" && test_set_prereq GETTEXT\n \n # test whether the filesystem supports symbolic links\n ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS\ndiff --git a/upload-pack.c b/upload-pack.c\nindex dc464d7..ece9a4b 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -10,6 +10,7 @@\n #include \"revision.h\"\n #include \"list-objects.h\"\n #include \"run-command.h\"\n+#include \"gettext.h\"\n \n static const char upload_pack_usage[] = \"git upload-pack [--strict] [--timeout=nn] <dir>\";\n \n@@ -686,6 +687,8 @@ int main(int argc, char **argv)\n \tint i;\n \tint strict = 0;\n \n+\tgit_setup_gettext();\n+\n \tgit_extract_argv0_path(argv[0]);\n \tread_replace_refs = 0;\n \n-- \n1.7.1.251.g92a7\n"}]}