All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] Gettext support for Git
@ 2010-06-06 17:47 Ævar Arnfjörð Bjarmason
  2010-06-06 17:47 ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
  2010-06-06 17:47 ` [PATCH 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
  0 siblings, 2 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-06 17:47 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	Ævar Arnfjörð Bjarmason

This patch series implements gettext support for git, making it
possible to localize it.

It been through 8 RFC iterations, gathering lots of changes along the
way. I think it's now ready to be submitted for inclusion.

This submission is exactly equivalent to the RFC v8, aside from
modifying the commit messages.

Ævar Arnfjörð Bjarmason (2):
  Add infrastructure for translating Git with gettext
  Add initial C, Shell and Perl gettext translations

 .gitignore                   |    2 +
 INSTALL                      |    8 ++
 Makefile                     |   69 ++++++++++++++++++-
 config.mak.in                |    2 +
 configure.ac                 |   12 +++
 daemon.c                     |    3 +
 fast-import.c                |    3 +
 gettext.c                    |   21 ++++++
 gettext.h                    |   18 +++++
 git-pull.sh                  |   16 +++--
 git-send-email.perl          |    3 +-
 git-sh-i18n.sh               |   47 +++++++++++++
 git.c                        |    3 +
 http-backend.c               |    3 +
 http-fetch.c                 |    3 +
 http-push.c                  |    3 +
 imap-send.c                  |    3 +
 perl/Git/I18N.pm             |   91 +++++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 ++++-
 po/.gitignore                |    1 +
 po/is.po                     |   70 +++++++++++++++++++
 shell.c                      |    3 +
 show-index.c                 |    3 +
 t/t0200-gettext.sh           |  154 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   13 ++++
 t/t0200/test.perl            |   14 ++++
 t/t0200/test.sh              |   14 ++++
 t/t0201-gettext-fallbacks.sh |   42 +++++++++++
 t/t0202-gettext-perl.sh      |   20 ++++++
 t/t0202/test.pl              |  104 ++++++++++++++++++++++++++++
 t/test-lib.sh                |    2 +
 upload-pack.c                |    3 +
 wt-status.c                  |  107 +++++++++++++++--------------
 34 files changed, 812 insertions(+), 65 deletions(-)
 create mode 100644 gettext.c
 create mode 100644 gettext.h
 create mode 100644 git-sh-i18n.sh
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/.gitignore
 create mode 100644 po/is.po
 create mode 100755 t/t0200-gettext.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0201-gettext-fallbacks.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 1/2] Add infrastructure for translating Git with gettext
  2010-06-06 17:47 [PATCH 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
@ 2010-06-06 17:47 ` Ævar Arnfjörð Bjarmason
  2010-06-07 10:02   ` Jakub Narebski
                     ` (2 more replies)
  2010-06-06 17:47 ` [PATCH 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
  1 sibling, 3 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-06 17:47 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	Ævar Arnfjörð Bjarmason

All of the interface messages in Git core are currently hardcoded in
English. Change that by optionally enabling translation of the core C,
Shell and Perl programs via GNU gettext. If you set the appropriate
LC_* variables Git will speak your language, provided that someone has
submitted a translation.

If gettext isn't available, or if Git is compiled with
NO_GETTEXT=YesPlease, then Git fall back on its previous behavior of
only speaking English. When using ./configure the autoconf script will
auto-detect if the gettext libraries are installed and act
appropriately.

With NO_GETTEXT=YesPlease gettext support will be #defined away for C
programs. For Shell and Perl programs we rely on the git message
catalog not being available. That's a reasonable assumption since then
the message catalog wont 't be installed on the system during make
install.

The gettext wrappers that are provided in the patch are only the bare
minimum required to begin translation work. In particular I haven't
added wrappers for the gettext functions that enable plural support,
or those that provide message context (msgctxt).

Those can be added later. The intent is to start with a small subset
and see what we need later, not to start with something that's
unnecessarily large right away.

Implementation and usage notes:

 * General:

   All the translated Git utilities are translated under the same
   textdomain(3) ("git"). This means that translators will see all
   interface messages under one big namespace, as opposed to e.g. one
   names per core utility.

   Gettext .mo files will be installed and looked for in the standard
   $(prefix)/share/locale path. GIT_TEXTDOMAINDIR can also be set to
   override that, but that's only intended to be used to test Git
   itself.

 * Perl:

   Perl code that wants to be localized should use the new Git::I18n
   module. It imports a __ function into the caller's package by
   default.

   Instead of using the high level Locale::TextDomain interface I've
   opted to use the low-level (equivalent to the C interface)
   Locale::Messages module, which Locale::TextDomain itself uses.

   Locale::TextDomain does a lot of redundant work we don't need, and
   some of it would potentially introduce bugs. It tries to set the
   $TEXTDOMAIN based on package of the caller, and has its own
   hardcoded paths where it'll search for messages.

   I found it easier just to completely avoid it rather than try to
   circumvent its behavior. In any case, this is an issue wholly
   internal Git::I18N. Its guts can be changed later if that's deemed
   necessary.

   See <AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LWnVd@mail.gmail.com>
   for a further elaboration on this topic.

 * Shell:

   Shell code that's to be localized should use the new git-sh-i18n
   library. It's just a wrapper for the system's gettext.sh.

   If gettext.sh isn't available we'll fall back on a dumb printf(1)
   fall-through wrapper.

   I originally tried to detect if the system supported `echo -n' but
   I found this to be a waste of time. My benchmarks on Linux, Solaris
   and FreeBSD reveal that printf(1) is fast enough, especially since
   we aren't calling gettext() from within any tight loops.

This patch is based on work by Jeff Epler <jepler@unpythonic.net> who
did the initial Makefile / C work, and a lot of comments from the Git
mailing list, including Jonathan Nieder, Jakub Narebski, Johannes
Sixt, Peter Krefting and others.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 .gitignore                   |    2 +
 INSTALL                      |    8 +++
 Makefile                     |   69 ++++++++++++++++++++++-
 config.mak.in                |    2 +
 configure.ac                 |   12 ++++
 daemon.c                     |    3 +
 fast-import.c                |    3 +
 gettext.c                    |   21 +++++++
 gettext.h                    |   18 ++++++
 git-sh-i18n.sh               |   47 ++++++++++++++++
 git.c                        |    3 +
 http-backend.c               |    3 +
 http-fetch.c                 |    3 +
 http-push.c                  |    3 +
 imap-send.c                  |    3 +
 perl/Git/I18N.pm             |   91 ++++++++++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 ++++-
 po/.gitignore                |    1 +
 po/is.po                     |   47 ++++++++++++++++
 shell.c                      |    3 +
 show-index.c                 |    3 +
 t/t0200-gettext.sh           |  126 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   13 ++++
 t/t0200/test.perl            |   14 +++++
 t/t0200/test.sh              |   14 +++++
 t/t0201-gettext-fallbacks.sh |   42 ++++++++++++++
 t/t0202-gettext-perl.sh      |   20 +++++++
 t/t0202/test.pl              |  104 ++++++++++++++++++++++++++++++++++
 t/test-lib.sh                |    2 +
 upload-pack.c                |    3 +
 31 files changed, 696 insertions(+), 4 deletions(-)
 create mode 100644 gettext.c
 create mode 100644 gettext.h
 create mode 100644 git-sh-i18n.sh
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/.gitignore
 create mode 100644 po/is.po
 create mode 100755 t/t0200-gettext.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0201-gettext-fallbacks.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl

diff --git a/.gitignore b/.gitignore
index 14e2b6b..6c2b193 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,6 +125,7 @@
 /git-rm
 /git-send-email
 /git-send-pack
+/git-sh-i18n
 /git-sh-setup
 /git-shell
 /git-shortlog
@@ -204,3 +205,4 @@
 *.pdb
 /Debug/
 /Release/
+/share/
diff --git a/INSTALL b/INSTALL
index 61086ab..f30d5bd 100644
--- a/INSTALL
+++ b/INSTALL
@@ -93,6 +93,14 @@ Issues of note:
 	  history graphically, and in git-gui.  If you don't want gitk or
 	  git-gui, you can use NO_TCLTK.
 
+	- The GNU "libintl" library is used by default for localizing
+	  Git. It needs a gettext.h on the system for C code, gettext.sh
+	  for shell scripts, and libintl-perl for Perl programs.
+
+	  Set NO_GETTEXT to disable localization support and make Git only
+	  use English. Under autoconf the configure script will do this
+	  automatically if it can't find libintl on the system.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
diff --git a/Makefile b/Makefile
index d5d6565..0ddf6e8 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,15 @@ all::
 # Define NO_EXPAT if you do not have expat installed.  git-http-push is
 # not built, and you cannot push using http:// and https:// transports.
 #
+# Define NO_GETTEXT if you don't want to build with Git with gettext
+# support. Building it requires GNU libintl, and additionally
+# libintl-perl at runtime.
+#
+# Define NEEDS_LIBINTL if you haven't set NO_GETTEXT and your system
+# needs to be explicitly linked to -lintl. It's defined automatically
+# on platforms where we don't expect glibc (Linux, Hurd,
+# GNU/kFreeBSD), which includes libintl.
+#
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
@@ -272,6 +281,7 @@ mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
 sharedir = $(prefix)/share
+localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ifeq ($(prefix),/usr)
@@ -285,7 +295,7 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir
+export prefix bindir sharedir sysconfdir localedir
 
 CC = gcc
 AR = ar
@@ -297,6 +307,8 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
+XGETTEXT = xgettext
+MSGFMT = msgfmt
 
 export TCL_PATH TCLTK_PATH
 
@@ -358,6 +370,7 @@ SCRIPT_SH += git-web--browse.sh
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
 SCRIPT_LIB += git-sh-setup
+SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
@@ -523,6 +536,7 @@ LIB_H += userdiff.h
 LIB_H += utf8.h
 LIB_H += xdiff-interface.h
 LIB_H += xdiff/xdiff.h
+LIB_H += gettext.h
 
 LIB_OBJS += abspath.o
 LIB_OBJS += advice.o
@@ -564,6 +578,9 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+ifndef NO_GETTEXT
+LIB_OBJS += gettext.o
+endif
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -735,6 +752,14 @@ EXTLIBS =
 # Platform specific tweaks
 #
 
+# Platform specific defaults. Where we'd only like some feature on the
+# minority of systems, e.g. if linking to a library isn't needed
+# because its features are included in the GNU C library.
+ifndef NO_GETTEXT
+	# Systems that use GNU gettext and glibc are the exception
+	NEEDS_LIBINTL = YesPlease
+endif
+
 # We choose to avoid "if .. else if .. else .. endif endif"
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
@@ -743,11 +768,13 @@ ifeq ($(uname_S),Linux)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),UnixWare)
 	CC = cc
@@ -917,6 +944,7 @@ ifeq ($(uname_S),GNU)
 	NO_STRLCPY=YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),IRIX)
 	NO_SETENV = YesPlease
@@ -1386,6 +1414,14 @@ ifdef USE_NED_ALLOCATOR
        COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
 endif
 
+ifdef NO_GETTEXT
+	COMPAT_CFLAGS += -DNO_GETTEXT
+endif
+
+ifdef NEEDS_LIBINTL
+	EXTLIBS += -lintl
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1415,6 +1451,7 @@ ifndef V
 	QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
 	QUIET_GEN      = @echo '   ' GEN $@;
 	QUIET_LNCP     = @echo '   ' LN/CP $@;
+	QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
 	QUIET_SUBDIR0  = +@subdir=
 	QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
 			 $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1442,7 +1479,9 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
 prefix_SQ = $(subst ','\'',$(prefix))
+sharedir_SQ = $(subst ','\'',$(sharedir))
 
+LOCALEDIR_SQ = $(subst ','\'',$(localedir))
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
@@ -1491,7 +1530,7 @@ ifndef NO_TCLTK
 	$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
 ifndef NO_PERL
-	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
 endif
 ifndef NO_PYTHON
 	$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1536,6 +1575,7 @@ $(RM) $@ $@+ && \
 sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's|@@LOCALEDIR@@|$(LOCALEDIR_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
@@ -1868,6 +1908,21 @@ cscope:
 	$(RM) cscope*
 	$(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 
+pot:
+	$(XGETTEXT) --add-comments --keyword=_ --keyword=N_ --output=po/git.pot --language=C $(C_OBJ:o=c) t/t0200/test.c
+	$(XGETTEXT) --add-comments --join-existing --output=po/git.pot --language=Shell $(SCRIPT_SH) t/t0200/test.sh
+	$(XGETTEXT) --add-comments --join-existing --keyword=__ --output=po/git.pot --language=Perl $(SCRIPT_PERL) t/t0200/test.perl
+
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES))
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+share/locale/%/LC_MESSAGES/git.mo: po/%.po
+	@mkdir -p $(dir $@)
+	$(QUIET_MSGFMT)$(MSGFMT) -o $@ $<
+
 ### Detect prefix changes
 TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
@@ -1889,6 +1944,7 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
 	@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
 	@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+	@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1980,6 +2036,11 @@ install: all
 	$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
+ifndef NO_GETTEXT
+	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sharedir_SQ)/locale'
+	(cd share && tar cf - locale) | \
+		(cd '$(DESTDIR_SQ)$(sharedir_SQ)' && umask 022 && tar xof -)
+endif
 	$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 ifndef NO_PERL
 	$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
@@ -2127,6 +2188,10 @@ ifndef NO_TCLTK
 	$(MAKE) -C git-gui clean
 endif
 	$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
+ifndef NO_GETTEXT
+	$(RM) po/git.pot
+	$(RM) -r share/
+endif
 
 .PHONY: all install clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
diff --git a/config.mak.in b/config.mak.in
index 0d4b64d..c49072c 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -32,9 +32,11 @@ NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
 NO_LIBGEN_H=@NO_LIBGEN_H@
 HAVE_PATHS_H=@HAVE_PATHS_H@
+NO_GETTEXT=@NO_GETTEXT@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
 NEEDS_RESOLV=@NEEDS_RESOLV@
+NEEDS_LIBINTL=@NEEDS_LIBINTL@
 NEEDS_LIBGEN=@NEEDS_LIBGEN@
 NO_SYS_SELECT_H=@NO_SYS_SELECT_H@
 NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@
diff --git a/configure.ac b/configure.ac
index 71038fc..74879b4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -559,6 +559,12 @@ AC_CHECK_LIB([c], [basename],
 AC_SUBST(NEEDS_LIBGEN)
 test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
+AC_CHECK_LIB([c], [gettext],
+[NEEDS_LIBINTL=],
+[NEEDS_LIBINTL=YesPlease])
+AC_SUBST(NEEDS_LIBINTL)
+test -n "$NEEDS_LIBINTL" && LIBS="$LIBS -lintl"
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -730,6 +736,12 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# Define NO_GETTEXT if you don't have libintl.h
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
diff --git a/daemon.c b/daemon.c
index a90ab10..7f4691c 100644
--- a/daemon.c
+++ b/daemon.c
@@ -3,6 +3,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 #include <syslog.h>
 
@@ -974,6 +975,8 @@ int main(int argc, char **argv)
 	gid_t gid = 0;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	for (i = 1; i < argc; i++) {
diff --git a/fast-import.c b/fast-import.c
index 129a786..6947f7a 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -156,6 +156,7 @@ Format of STDIN stream:
 #include "csum-file.h"
 #include "quote.h"
 #include "exec_cmd.h"
+#include "gettext.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -2904,6 +2905,8 @@ int main(int argc, const char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(fast_import_usage);
 
diff --git a/gettext.c b/gettext.c
new file mode 100644
index 0000000..407fbc0
--- /dev/null
+++ b/gettext.c
@@ -0,0 +1,21 @@
+#include "exec_cmd.h"
+#include <libintl.h>
+#include <stdlib.h>
+
+extern void git_setup_gettext(void) {
+	char *podir;
+	char *envdir = getenv("GIT_TEXTDOMAINDIR");
+
+	if (envdir) {
+		(void)bindtextdomain("git", envdir);
+	} else {
+		podir = (char *)system_path("share/locale");
+		if (!podir) return;
+		(void)bindtextdomain("git", podir);
+		free(podir);
+	}
+
+	(void)setlocale(LC_MESSAGES, "");
+	(void)setlocale(LC_CTYPE, "");
+	(void)textdomain("git");
+}
diff --git a/gettext.h b/gettext.h
new file mode 100644
index 0000000..e02939a
--- /dev/null
+++ b/gettext.h
@@ -0,0 +1,18 @@
+#ifndef GETTEXT_H
+#define GETTEXT_H
+
+#ifdef NO_GETTEXT
+static inline void git_setup_gettext(void) {}
+#else
+extern void git_setup_gettext(void);
+#endif
+
+#define N_(s) (s)
+#ifdef NO_GETTEXT
+#define _(s) (s)
+#else
+#include <libintl.h>
+#define _(s) gettext(s)
+#endif
+
+#endif
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
new file mode 100644
index 0000000..d4963e9
--- /dev/null
+++ b/git-sh-i18n.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+# This is Git's interface to gettext.sh. Use it right after
+# git-sh-setup as:
+#
+#   . git-sh-setup
+#   . git-sh-i18n
+#
+#   # For constant interface messages:
+#   gettext "A message for the user"; echo
+#
+#   # To interpolate variables:
+#   details="oh noes"
+#   eval_gettext "An error occured: \$details"; echo
+#
+# See "info '(gettext)sh'" for the full manual.
+
+# Try to use libintl's gettext.sh, or fall back to English if we
+# can't.
+. gettext.sh
+
+if test $? -eq 0 && test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+then
+	TEXTDOMAIN=git
+	export TEXTDOMAIN
+	if [ -z "$GIT_TEXTDOMAINDIR" ]
+	then
+		TEXTDOMAINDIR="@@LOCALEDIR@@"
+	else
+		TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+	fi
+	export TEXTDOMAINDIR
+else
+	# Since gettext.sh isn't available we'll have to define our own
+	# dummy pass-through functions.
+
+	gettext () {
+		printf "%s" "$1"
+	}
+
+	eval_gettext () {
+		gettext_eval="printf '%s' \"$1\""
+		printf "%s" "`eval \"$gettext_eval\"`"
+	}
+fi
diff --git a/git.c b/git.c
index 99f0363..d749eab 100644
--- a/git.c
+++ b/git.c
@@ -3,6 +3,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "run-command.h"
+#include "gettext.h"
 
 const char git_usage_string[] =
 	"git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n"
@@ -490,6 +491,8 @@ int main(int argc, const char **argv)
 	if (!cmd)
 		cmd = "git-help";
 
+	git_setup_gettext();
+
 	/*
 	 * "git-xxxx" is the same as "git xxxx", but we obviously:
 	 *
diff --git a/http-backend.c b/http-backend.c
index d1e83d0..b6d9bd5 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -6,6 +6,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "string-list.h"
+#include "gettext.h"
 
 static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
@@ -605,6 +606,8 @@ int main(int argc, char **argv)
 	char *cmd_arg = NULL;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	set_die_routine(die_webcgi);
 
diff --git a/http-fetch.c b/http-fetch.c
index 762c750..b889c36 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -2,6 +2,7 @@
 #include "exec_cmd.h"
 #include "http.h"
 #include "walker.h"
+#include "gettext.h"
 
 static const char http_fetch_usage[] = "git http-fetch "
 "[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url";
@@ -24,6 +25,8 @@ int main(int argc, const char **argv)
 	int get_verbosely = 0;
 	int get_recover = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	while (arg < argc && argv[arg][0] == '-') {
diff --git a/http-push.c b/http-push.c
index 415b1ab..ba0338c 100644
--- a/http-push.c
+++ b/http-push.c
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "list-objects.h"
 #include "sigchain.h"
+#include "gettext.h"
 
 #include <expat.h>
 
@@ -1791,6 +1792,8 @@ int main(int argc, char **argv)
 	struct remote *remote;
 	char *rewritten_url = NULL;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	repo = xcalloc(sizeof(*repo), 1);
diff --git a/imap-send.c b/imap-send.c
index 9d0097c..4f5f269 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -25,6 +25,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "run-command.h"
+#include "gettext.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #else
@@ -1535,6 +1536,8 @@ int main(int argc, char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(imap_send_usage);
 
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644
index 0000000..5918d68
--- /dev/null
+++ b/perl/Git/I18N.pm
@@ -0,0 +1,91 @@
+package Git::I18N;
+use 5.006002;
+use strict;
+use warnings;
+use Exporter;
+use base 'Exporter';
+
+our $VERSION = '0.01';
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+	our $TEXTDOMAIN = 'git';
+	our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+	require POSIX;
+	POSIX->import(qw(setlocale));
+	# Non-core prerequisite module
+	require Locale::Messages;
+	Locale::Messages->import(qw(:locale_h :libintl_h));
+
+	setlocale(LC_MESSAGES(), '');
+	setlocale(LC_CTYPE(), '');
+	textdomain($TEXTDOMAIN);
+	bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+	return;
+}
+
+BEGIN
+{
+	# Used by our test script to see if it should test fallbacks or
+	# not.
+	our $__HAS_LIBRARY = 1;
+
+	local $@;
+	eval { __bootstrap_locale_messages() };
+	if ($@) {
+		# Tell test.pl that we couldn't load the gettext library.
+		$Git::I18N::__HAS_LIBRARY = 0;
+
+		# Just a fall-through no-op
+		*__ = sub ($) { $_[0] };
+	} else {
+		*__ = \&Locale::Messages::gettext;
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+	use Git::I18N;
+
+	print __("Welcome to Git!\n");
+
+	printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
diff --git a/perl/Makefile b/perl/Makefile
index 4ab21d6..4e624ff 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -5,6 +5,7 @@ makfile:=perl.mak
 
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
 
 ifndef V
 	QUIET = @
@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	echo '	echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-	$(PERL_PATH) $< PREFIX='$(prefix_SQ)'
+	$(PERL_PATH) $< PREFIX='$(prefix_SQ)' --localedir='$(localedir_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index 0b9deca..456d45b 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
 use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
 
 sub MY::postamble {
 	return <<'MAKE_FRAG';
@@ -16,7 +24,10 @@ endif
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+my %pm = (
+	'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+	'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+);
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
@@ -33,6 +44,7 @@ WriteMakefile(
 	NAME            => 'Git',
 	VERSION_FROM    => 'Git.pm',
 	PM		=> \%pm,
+	PM_FILTER	=> qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
 	MAKEFILE	=> 'perl.mak',
 	INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..221000e
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1 @@
+/*.pot
diff --git a/po/is.po b/po/is.po
new file mode 100644
index 0000000..95739f1
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,47 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: t/t0200/test.c:4
+msgid "See git help COMMAND for more information on a specific command."
+msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:9
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:12
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
diff --git a/shell.c b/shell.c
index e4864e0..ba27c6b 100644
--- a/shell.c
+++ b/shell.c
@@ -2,6 +2,7 @@
 #include "quote.h"
 #include "exec_cmd.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 static int do_generic_cmd(const char *me, char *arg)
 {
@@ -51,6 +52,8 @@ int main(int argc, char **argv)
 	struct commands *cmd;
 	int devnull_fd;
 
+	git_setup_gettext();
+
 	/*
 	 * Always open file descriptors 0/1/2 to avoid clobbering files
 	 * in die().  It also avoids not messing up when the pipes are
diff --git a/show-index.c b/show-index.c
index 4c0ac13..c2f5448 100644
--- a/show-index.c
+++ b/show-index.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pack.h"
+#include "gettext.h"
 
 static const char show_index_usage[] =
 "git show-index < <packed archive index>";
@@ -11,6 +12,8 @@ int main(int argc, char **argv)
 	unsigned int version;
 	static unsigned int top_index[256];
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(show_index_usage);
 	if (fread(top_index, 2 * 4, 1, stdin) != 1)
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
new file mode 100755
index 0000000..b54e062
--- /dev/null
+++ b/t/t0200-gettext.sh
@@ -0,0 +1,126 @@
+#!/bin/sh
+
+test_description='Gettext support for Git'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_PO_PATH="$GIT_EXEC_PATH/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+    test $TEXTDOMAIN = "git"
+'
+
+if test_have_prereq GETTEXT; then
+	test_expect_success 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+    test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+	test_expect_success 'sanity: Icelandic locale was compiled' '
+    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+else
+	test_expect_success "sanity: \$TEXTDOMAINDIR doesn't exists with NO_GETTEXT=YesPlease" '
+    test_expect_failure test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+fi
+
+# Basic xgettext() extraction tests on po/*.po. Doesn't need gettext support
+test_expect_success 'xgettext: Perl _() strings are not extracted' '
+    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext: Comment extraction with --add-comments' '
+    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
+    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Comment extraction with --add-comments stops at statements' '
+    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+# We can go no further without actual gettext support
+if ! test_have_prereq GETTEXT; then
+	say "Skipping the rest of the gettext tests, Git was compiled with NO_GETTEXT=YesPlease"
+	test_done
+fi
+
+test_expect_success 'sanity: No gettext("") data for fantasy locale' '
+    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
+    test_expect_failure test -s real-locale
+'
+
+test_expect_success 'sanity: Some gettext("") data for real locale' '
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
+    test -s fantasy-locale
+'
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success 'sanity: gettext("") metadata is OK' '
+    # Return value may be non-zero
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > zero-expect &&
+    grep "Project-Id-Version: Git" zero-expect &&
+    grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+    grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+    grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success 'sanity: gettext(unknown) is passed through' '
+    printf "This is not a translation string"  > expect &&
+    gettext "This is not a translation string" > actual &&
+    eval_gettext "This is not a translation string" > actual &&
+    test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success 'xgettext: C extraction of _() and N_() strings' '
+    printf "TILRAUN: C tilraunastrengur" > expect &&
+    printf "\n" >> expect &&
+    printf "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun." >> expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string" > actual &&
+    printf "\n" >> actual &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "See git help COMMAND for more information on a specific command." >> actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: C extraction with %s' '
+    printf "TILRAUN: C tilraunastrengur %%s" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string %s" > actual &&
+    test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success 'xgettext: Shell extraction' '
+    printf "TILRAUN: Skeljartilraunastrengur" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Shell test string" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Shell extraction with $variable' '
+    printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" > x-expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 variable="a var i able" eval_gettext "TEST: A Shell test \$variable" > x-actual &&
+    test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success 'xgettext: Perl extraction' '
+    printf "TILRAUN: Perl tilraunastrengur" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test string" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Perl extraction with %s' '
+    printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test variable %s" > actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644
index 0000000..93373b3
--- /dev/null
+++ b/t/t0200/test.c
@@ -0,0 +1,13 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+	N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	puts(_("TEST: A C test string"));
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: A C test string %s"), "variable");
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644
index 0000000..36fba34
--- /dev/null
+++ b/t/t0200/test.perl
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644
index 0000000..022d607
--- /dev/null
+++ b/t/t0200/test.sh
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
new file mode 100755
index 0000000..b4bc1df
--- /dev/null
+++ b/t/t0201-gettext-fallbacks.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='Gettext Shell fallbacks'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+
+export GIT_TEXTDOMAINDIR GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+	test_expect_failure test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
+    printf "test" > expect &&
+    gettext "test" > actual &&
+    test_cmp expect actual &&
+    printf "test more words" > expect &&
+    gettext "test more words" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' '
+    printf "test" > expect &&
+    eval_gettext "test" > actual &&
+    test_cmp expect actual &&
+    printf "test more words" > expect &&
+    eval_gettext "test more words" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' '
+    printf "test YesPlease" > expect &&
+    eval_gettext "test \$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" > actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755
index 0000000..9b075b1
--- /dev/null
+++ b/t/t0202-gettext-perl.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+test_description='Perl gettext interface (Git::I18N)'
+. ./test-lib.sh
+
+if ! test_have_prereq PERL; then
+	say 'skipping perl interface tests, perl not available'
+	test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+	say "Perl Test::More unavailable, skipping test"
+	test_done
+}
+
+test_external_without_stderr \
+    'Perl Git::I18N API' \
+    "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644
index 0000000..4e9a0dc
--- /dev/null
+++ b/t/t0202/test.pl
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+use 5.006002;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use warnings;
+use strict;
+use Test::More tests => 9;
+use Git::I18N;
+use POSIX qw(:locale_h);
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N version $Git::I18N::VERSION with " .
+	 ($has_gettext_library
+	  ? "Locale::Messages version $Locale::Messages::VERSION"
+	  : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+ok($Git::I18N::VERSION, 'sanity: Git::I18N defines a $VERSION');
+{
+	my $exports = @Git::I18N::EXPORT;
+	ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+	# Add prototypes here when modifying the public interface to add
+	# more gettext wrapper functions.
+	my %prototypes = (qw(
+		__	$
+    ));
+	while (my ($sub, $proto) = each %prototypes) {
+		is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+	}
+}
+
+# Test basic passthrough in the C locale
+{
+	local $ENV{LANGUAGE} = 'C';
+	local $ENV{LC_ALL}   = 'C';
+	local $ENV{LANG} = 'C';
+
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+    unless ($ENV{TEST_GIT_I18N_EXHAUSTIVE}) {
+        # Can't reliably test __() with a non-C locales because the
+        # required locales may not be installed on the system.
+        #
+        # We test for these anyway as part of the shell
+        # tests. Skipping these here will eliminate failures on odd
+        # platforms with incomplete locale data.
+
+        skip "Set TEST_GIT_I18N_EXHAUSTIVE=1 to enable exhaustive Git::I18N locale tests", 2;
+    }
+
+	my $test = sub {
+		my ($got, $expect, $msg, $locale) = @_;
+		# Maybe this system doesn't have the locale we're trying to
+		# test.
+		my $locale_ok = setlocale(LC_ALL, $locale);
+		is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+	};
+
+	my $env_C = sub {
+		$ENV{LANGUAGE} = 'C';
+		$ENV{LC_ALL}   = 'C';
+	};
+
+	my $env_is = sub {
+		$ENV{LANGUAGE} = 'is';
+		$ENV{LC_ALL}   = 'is_IS.UTF-8';
+	};
+
+	# Translation's the same as the original
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	if ($has_gettext_library) {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "With", 'C');
+		}
+
+		{
+			my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+			local %ENV; $env_is->();
+			$test->($got, $expect, "With", 'is_IS.UTF-8');
+		}
+	} else {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "Without", 'C');
+		}
+
+		{
+			local %ENV; $env_is->();
+			$test->($got, $expect, "Without", 'is');
+		}
+	}
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 454880a..ae63316 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -37,6 +37,7 @@ ORIGINAL_TERM=$TERM
 # For repeatability, reset the environment to known value.
 LANG=C
 LC_ALL=C
+LANGUAGE=C
 PAGER=cat
 TZ=UTC
 TERM=dumb
@@ -845,6 +846,7 @@ esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # test whether the filesystem supports symbolic links
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
diff --git a/upload-pack.c b/upload-pack.c
index dc464d7..ece9a4b 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -10,6 +10,7 @@
 #include "revision.h"
 #include "list-objects.h"
 #include "run-command.h"
+#include "gettext.h"
 
 static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -686,6 +687,8 @@ int main(int argc, char **argv)
 	int i;
 	int strict = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	read_replace_refs = 0;
 
-- 
1.7.1.243.gda92d6.dirty

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 2/2] Add initial C, Shell and Perl gettext translations
  2010-06-06 17:47 [PATCH 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
  2010-06-06 17:47 ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
@ 2010-06-06 17:47 ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-06 17:47 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	Ævar Arnfjörð Bjarmason

Change the git status, git pull, and git send-email commands to have
at least one translatable string. Each command uses a different core
language, so this makes a good example of how C, Shell and Perl
programs can be translated using gettext.

Since this introduces translation into the real Git tools more tests
can be added to check if they translations actually work for real core
tools.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-pull.sh         |   16 ++++---
 git-send-email.perl |    3 +-
 po/is.po            |   23 +++++++++++
 t/t0200-gettext.sh  |   28 +++++++++++++
 wt-status.c         |  107 ++++++++++++++++++++++++++-------------------------
 5 files changed, 116 insertions(+), 61 deletions(-)

diff --git a/git-pull.sh b/git-pull.sh
index 1a4729f..0d95722 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -9,6 +9,7 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 . git-sh-setup
+. git-sh-i18n
 set_reflog_action "pull $*"
 require_work_tree
 cd_to_toplevel
@@ -121,8 +122,8 @@ error_on_no_merge_candidates () {
 	do
 		case "$opt" in
 		-t|--t|--ta|--tag|--tags)
-			echo "Fetching tags only, you probably meant:"
-			echo "  git fetch --tags"
+			gettext "Fetching tags only, you probably meant:"; echo
+			gettext "  git fetch --tags"; echo
 			exit 1
 		esac
 	done
@@ -154,11 +155,12 @@ error_on_no_merge_candidates () {
 		echo "a branch. Because this is not the default configured remote"
 		echo "for your current branch, you must specify a branch on the command line."
 	elif [ -z "$curr_branch" ]; then
-		echo "You are not currently on a branch, so I cannot use any"
-		echo "'branch.<branchname>.merge' in your configuration file."
-		echo "Please specify which remote branch you want to use on the command"
-		echo "line and try again (e.g. 'git pull <repository> <refspec>')."
-		echo "See git-pull(1) for details."
+		gettext "You are not currently on a branch, so I cannot use any
+'branch.<branchname>.merge' in your configuration file.
+Please specify which remote branch you want to use on the command
+line and try again (e.g. 'git pull <repository> <refspec>').
+See git-pull(1) for details.";
+		echo
 	elif [ -z "$upstream" ]; then
 		echo "You asked me to pull without telling me which branch you"
 		echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in"
diff --git a/git-send-email.perl b/git-send-email.perl
index 111c981..4977fdf 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -26,6 +26,7 @@ use Term::ANSIColor;
 use File::Temp qw/ tempdir tempfile /;
 use Error qw(:try);
 use Git;
+use Git::I18N;
 
 Getopt::Long::Configure qw/ pass_through /;
 
@@ -674,7 +675,7 @@ if (!defined $sender) {
 	$sender = $repoauthor || $repocommitter || '';
 	$sender = ask("Who should the emails appear to be from? [$sender] ",
 	              default => $sender);
-	print "Emails will be sent from: ", $sender, "\n";
+	printf __("Emails will be sent from: %s\n"), $sender;
 	$prompting++;
 }
 
diff --git a/po/is.po b/po/is.po
index 95739f1..8e4f8c5 100644
--- a/po/is.po
+++ b/po/is.po
@@ -13,6 +13,20 @@ msgstr ""
 msgid "See git help COMMAND for more information on a specific command."
 msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun."
 
+#: wt-status.c:63 wt-status.c:79 wt-status.c:98 wt-status.c:110
+#: wt-status.c:622
+msgid "On branch "
+msgstr "Á greininni "
+
+#: wt-status.c:629
+msgid "Not currently on any branch."
+msgstr "Ekki á neinni grein."
+
+#: wt-status.c:663
+#, c-format
+msgid "# No changes\n"
+msgstr "# Engar breytingar\n"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.c:9
 msgid "TEST: A C test string"
@@ -24,6 +38,10 @@ msgstr "TILRAUN: C tilraunastrengur"
 msgid "TEST: A C test string %s"
 msgstr "TILRAUN: C tilraunastrengur %s"
 
+#: git-pull.sh:124
+msgid "Fetching tags only, you probably meant:"
+msgstr "Næ aðeins í tögg, þú áttir líkast til við:"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.sh:8
 msgid "TEST: A Shell test string"
@@ -35,6 +53,11 @@ msgstr "TILRAUN: Skeljartilraunastrengur"
 msgid "TEST: A Shell test $variable"
 msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
 
+#: git-send-email.perl:678
+#, perl-format
+msgid "Emails will be sent from: %s\n"
+msgstr "Póstarnir verða sendir frá: %s\n"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.perl:8
 msgid "TEST: A Perl test string"
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
index b54e062..6b702e5 100755
--- a/t/t0200-gettext.sh
+++ b/t/t0200-gettext.sh
@@ -123,4 +123,32 @@ test_expect_success 'xgettext: Perl extraction with %s' '
     test_cmp expect actual
 '
 
+# Actually execute some C and Shell code that uses Gettext
+test_expect_success 'C: git-status reads our message catalog ' '
+    test_commit "some-file" &&
+    git checkout -b test/gettext &&
+    LANGUAGE=C LC_ALL=C git status | grep test/gettext > expect &&
+    echo "# On branch test/gettext" > actual &&
+    test_cmp expect actual &&
+
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 git status | grep test/gettext > expect &&
+    echo "# Á greininni test/gettext" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'Shell: git-pull reads our message catalog' '
+    # Repository for testing
+    mkdir parent &&
+    (cd parent && git init &&
+     echo one >file && git add file &&
+     git commit -m one) &&
+
+    # Actual test
+    (cd parent &&
+    (LANGUAGE=C LC_ALL=C git pull --tags "../" >out 2>err);
+    grep "Fetching tags only" err &&
+    (LANGUAGE=is LC_ALL=is_IS.UTF-8 git pull --tags ../ >out 2>err || :) &&
+    grep "Næ aðeins í" err)
+'
+
 test_done
diff --git a/wt-status.c b/wt-status.c
index 14e0acc..484a866 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -9,6 +9,7 @@
 #include "quote.h"
 #include "run-command.h"
 #include "remote.h"
+#include "gettext.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -49,16 +50,16 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Unmerged paths:");
+	color_fprintf_ln(s->fp, c, _("# Unmerged paths:"));
 	if (!advice_status_hints)
 		return;
 	if (s->in_merge)
 		;
 	else if (!s->is_initial)
-		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+		color_fprintf_ln(s->fp, c, _("#   (use \"git reset %s <file>...\" to unstage)"), s->reference);
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-	color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git rm --cached <file>...\" to unstage)"));
+	color_fprintf_ln(s->fp, c, _("#   (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -66,15 +67,15 @@ static void wt_status_print_cached_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+	color_fprintf_ln(s->fp, c, _("# Changes to be committed:"));
 	if (!advice_status_hints)
 		return;
 	if (s->in_merge)
 		; /* NEEDSWORK: use "git reset --unresolve"??? */
 	else if (!s->is_initial)
-		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+		color_fprintf_ln(s->fp, c, _("#   (use \"git reset %s <file>...\" to unstage)"), s->reference);
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git rm --cached <file>...\" to unstage)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -84,16 +85,16 @@ static void wt_status_print_dirty_header(struct wt_status *s,
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Changed but not updated:");
+	color_fprintf_ln(s->fp, c, _("# Changed but not updated:"));
 	if (!advice_status_hints)
 		return;
 	if (!has_deleted)
-		color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git add <file>...\" to update what will be committed)"));
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
-	color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git add/rm <file>...\" to update what will be committed)"));
+	color_fprintf_ln(s->fp, c, _("#   (use \"git checkout -- <file>...\" to discard changes in working directory)"));
 	if (has_dirty_submodules)
-		color_fprintf_ln(s->fp, c, "#   (commit or discard the untracked or modified content in submodules)");
+		color_fprintf_ln(s->fp, c, _("#   (commit or discard the untracked or modified content in submodules)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -102,10 +103,10 @@ static void wt_status_print_other_header(struct wt_status *s,
 					 const char *how)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
-	color_fprintf_ln(s->fp, c, "# %s files:", what);
+	color_fprintf_ln(s->fp, c, _("# %s files:"), what);
 	if (!advice_status_hints)
 		return;
-	color_fprintf_ln(s->fp, c, "#   (use \"git %s <file>...\" to include in what will be committed)", how);
+	color_fprintf_ln(s->fp, c, _("#   (use \"git %s <file>...\" to include in what will be committed)"), how);
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -122,20 +123,20 @@ static void wt_status_print_unmerged_data(struct wt_status *s,
 	const char *c = color(WT_STATUS_UNMERGED, s);
 	struct wt_status_change_data *d = it->util;
 	struct strbuf onebuf = STRBUF_INIT;
-	const char *one, *how = "bug";
+	const char *one, *how = _("bug");
 
 	one = quote_path(it->string, -1, &onebuf, s->prefix);
 	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
 	switch (d->stagemask) {
-	case 1: how = "both deleted:"; break;
-	case 2: how = "added by us:"; break;
-	case 3: how = "deleted by them:"; break;
-	case 4: how = "added by them:"; break;
-	case 5: how = "deleted by us:"; break;
-	case 6: how = "both added:"; break;
-	case 7: how = "both modified:"; break;
+	case 1: how = _("both deleted:"); break;
+	case 2: how = _("added by us:"); break;
+	case 3: how = _("deleted by them:"); break;
+	case 4: how = _("added by them:"); break;
+	case 5: how = _("deleted by us:"); break;
+	case 6: how = _("both added:"); break;
+	case 7: how = _("both modified:"); break;
 	}
-	color_fprintf(s->fp, c, "%-20s%s\n", how, one);
+	color_fprintf(s->fp, c, _("%-20s%s\n"), how, one);
 	strbuf_release(&onebuf);
 }
 
@@ -163,11 +164,11 @@ static void wt_status_print_change_data(struct wt_status *s,
 		if (d->new_submodule_commits || d->dirty_submodule) {
 			strbuf_addstr(&extra, " (");
 			if (d->new_submodule_commits)
-				strbuf_addf(&extra, "new commits, ");
+				strbuf_addf(&extra, _("new commits, "));
 			if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
-				strbuf_addf(&extra, "modified content, ");
+				strbuf_addf(&extra, _("modified content, "));
 			if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
-				strbuf_addf(&extra, "untracked content, ");
+				strbuf_addf(&extra, _("untracked content, "));
 			strbuf_setlen(&extra, extra.len - 2);
 			strbuf_addch(&extra, ')');
 		}
@@ -178,34 +179,34 @@ static void wt_status_print_change_data(struct wt_status *s,
 	one = quote_path(one_name, -1, &onebuf, s->prefix);
 	two = quote_path(two_name, -1, &twobuf, s->prefix);
 
-	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), _("#\t"));
 	switch (status) {
 	case DIFF_STATUS_ADDED:
-		color_fprintf(s->fp, c, "new file:   %s", one);
+		color_fprintf(s->fp, c, _("new file:   %s"), one);
 		break;
 	case DIFF_STATUS_COPIED:
-		color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
+		color_fprintf(s->fp, c, _("copied:     %s -> %s"), one, two);
 		break;
 	case DIFF_STATUS_DELETED:
-		color_fprintf(s->fp, c, "deleted:    %s", one);
+		color_fprintf(s->fp, c, _("deleted:    %s"), one);
 		break;
 	case DIFF_STATUS_MODIFIED:
-		color_fprintf(s->fp, c, "modified:   %s", one);
+		color_fprintf(s->fp, c, _("modified:   %s"), one);
 		break;
 	case DIFF_STATUS_RENAMED:
-		color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
+		color_fprintf(s->fp, c, _("renamed:    %s -> %s"), one, two);
 		break;
 	case DIFF_STATUS_TYPE_CHANGED:
-		color_fprintf(s->fp, c, "typechange: %s", one);
+		color_fprintf(s->fp, c, _("typechange: %s"), one);
 		break;
 	case DIFF_STATUS_UNKNOWN:
-		color_fprintf(s->fp, c, "unknown:    %s", one);
+		color_fprintf(s->fp, c, _("unknown:    %s"), one);
 		break;
 	case DIFF_STATUS_UNMERGED:
-		color_fprintf(s->fp, c, "unmerged:   %s", one);
+		color_fprintf(s->fp, c, _("unmerged:   %s"), one);
 		break;
 	default:
-		die("bug: unhandled diff status %c", status);
+		die(_("bug: unhandled diff status %c"), status);
 	}
 	if (extra.len) {
 		color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
@@ -618,14 +619,14 @@ void wt_status_print(struct wt_status *s)
 	const char *branch_color = color(WT_STATUS_HEADER, s);
 
 	if (s->branch) {
-		const char *on_what = "On branch ";
+		const char *on_what = _("On branch ");
 		const char *branch_name = s->branch;
 		if (!prefixcmp(branch_name, "refs/heads/"))
 			branch_name += 11;
 		else if (!strcmp(branch_name, "HEAD")) {
 			branch_name = "";
 			branch_color = color(WT_STATUS_NOBRANCH, s);
-			on_what = "Not currently on any branch.";
+			on_what = _("Not currently on any branch.");
 		}
 		color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
 		color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
@@ -635,7 +636,7 @@ void wt_status_print(struct wt_status *s)
 
 	if (s->is_initial) {
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
-		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _("# Initial commit"));
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 	}
 
@@ -647,38 +648,38 @@ void wt_status_print(struct wt_status *s)
 		wt_status_print_submodule_summary(s, 1);  /* unstaged */
 	}
 	if (s->show_untracked_files) {
-		wt_status_print_other(s, &s->untracked, "Untracked", "add");
+		wt_status_print_other(s, &s->untracked, _("Untracked"), _("add"));
 		if (s->show_ignored_files)
-			wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
+			wt_status_print_other(s, &s->ignored, _("Ignored"), _("add -f"));
 	} else if (s->commitable)
-		fprintf(s->fp, "# Untracked files not listed%s\n",
+		fprintf(s->fp, _("# Untracked files not listed%s\n"),
 			advice_status_hints
-			? " (use -u option to show untracked files)" : "");
+			? _(" (use -u option to show untracked files)") : "");
 
 	if (s->verbose)
 		wt_status_print_verbose(s);
 	if (!s->commitable) {
 		if (s->amend)
-			fprintf(s->fp, "# No changes\n");
+			fprintf(s->fp, _("# No changes\n"));
 		else if (s->nowarn)
 			; /* nothing */
 		else if (s->workdir_dirty)
-			printf("no changes added to commit%s\n",
+			printf(_("no changes added to commit%s\n"),
 				advice_status_hints
-				? " (use \"git add\" and/or \"git commit -a\")" : "");
+				? _(" (use \"git add\" and/or \"git commit -a\")") : "");
 		else if (s->untracked.nr)
-			printf("nothing added to commit but untracked files present%s\n",
+			printf(_("nothing added to commit but untracked files present%s\n"),
 				advice_status_hints
-				? " (use \"git add\" to track)" : "");
+				? _(" (use \"git add\" to track)") : "");
 		else if (s->is_initial)
 			printf("nothing to commit%s\n", advice_status_hints
-				? " (create/copy files and use \"git add\" to track)" : "");
+				? _(" (create/copy files and use \"git add\" to track)") : "");
 		else if (!s->show_untracked_files)
-			printf("nothing to commit%s\n", advice_status_hints
-				? " (use -u to show untracked files)" : "");
+			printf(_("nothing to commit%s\n"), advice_status_hints
+				? _(" (use -u to show untracked files)") : "");
 		else
-			printf("nothing to commit%s\n", advice_status_hints
-				? " (working directory clean)" : "");
+			printf(_("nothing to commit%s\n"), advice_status_hints
+				? _(" (working directory clean)") : "");
 	}
 }
 
-- 
1.7.1.243.gda92d6.dirty

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/2] Add infrastructure for translating Git with gettext
  2010-06-06 17:47 ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
@ 2010-06-07 10:02   ` Jakub Narebski
  2010-06-07 13:19   ` Jeff Epler
  2010-06-14 20:26   ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
  2 siblings, 0 replies; 16+ messages in thread
From: Jakub Narebski @ 2010-06-07 10:02 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Jeff Epler, Jonathan Nieder

On Sun, 6 Jun 2010, Ævar Arnfjörð Bjarmason wrote:

> diff --git a/INSTALL b/INSTALL
> index 61086ab..f30d5bd 100644
> --- a/INSTALL
> +++ b/INSTALL
> @@ -93,6 +93,14 @@ Issues of note:
>           history graphically, and in git-gui.  If you don't want gitk or
>           git-gui, you can use NO_TCLTK.
>  
> +       - The GNU "libintl" library is used by default for localizing
> +         Git. It needs a gettext.h on the system for C code, gettext.sh
> +         for shell scripts, and libintl-perl for Perl programs.

I think this addresses my concern about mentioning libintl-perl.

-- 
Jakub Narebski
Poland

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/2] Add infrastructure for translating Git with gettext
  2010-06-06 17:47 ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
  2010-06-07 10:02   ` Jakub Narebski
@ 2010-06-07 13:19   ` Jeff Epler
  2010-06-07 21:23     ` [PATCH v2 " Ævar Arnfjörð Bjarmason
  2010-06-14 20:26   ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
  2 siblings, 1 reply; 16+ messages in thread
From: Jeff Epler @ 2010-06-07 13:19 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Jakub Narebski, Jonathan Nieder

I'm excited to see the work you've done on this.

On Sun, Jun 06, 2010 at 05:47:32PM +0000, Ævar Arnfjörð Bjarmason wrote:
> the message catalog wont 't be installed on the system during make

trivial typo

Jeff

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v2 1/2] Add infrastructure for translating Git with gettext
  2010-06-07 13:19   ` Jeff Epler
@ 2010-06-07 21:23     ` Ævar Arnfjörð Bjarmason
  2010-06-14  5:00       ` Junio C Hamano
  0 siblings, 1 reply; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-07 21:23 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	Ævar Arnfjörð Bjarmason

All of the interface messages in Git core are currently hardcoded in
English. Change that by optionally enabling translation of the core C,
Shell and Perl programs via GNU gettext. If you set the appropriate
LC_* variables Git will speak your language, provided that someone has
submitted a translation.

If gettext isn't available, or if Git is compiled with
NO_GETTEXT=YesPlease, then Git fall back on its previous behavior of
only speaking English. When using ./configure the autoconf script will
auto-detect if the gettext libraries are installed and act
appropriately.

With NO_GETTEXT=YesPlease gettext support will be #defined away for C
programs. For Shell and Perl programs we rely on the git message
catalog not being avalalable. That's a reasonable assumption since
then the message catalog won't be installed on the system during make
install.

The gettext wrappers that are provided in the patch are only the bare
minimum required to begin translation work. In particular I haven't
added wrappers for the gettext functions that enable plural support,
or those that provide message context (msgctxt).

Those can be added later. The intent is to start with a small subset
and see what we need later, not to start with something that's
unnecessarily large right away.

Implementation and usage notes:

 * General:

   Gettext .mo files will be installed and looked for in the standard
   $(prefix)/share/locale path. GIT_TEXTDOMAINDIR can also be set to
   override that, but that's only intended to be used to test Git
   itself.

 * Perl:

   Perl code that wants to be localized should use the new Git::I18n
   module. It imports a __ function into the caller's package by
   default.

   Instead of using the high level Locale::TextDomain interface I've
   opted to use the low-level (equivalent to the C interface)
   Locale::Messages module, which Locale::TextDomain itself uses.

   Locale::TextDomain does a lot of redundant work we don't need, and
   some of it would potentially introduce bugs. It tries to set the
   $TEXTDOMAIN based on package of the caller, and has its own
   hardcoded paths where it'll search for messages.

   I found it easier just to completely avoid it rather than try to
   circumvent its behavior. In any case, this is an issue wholly
   internal Git::I18N. Its guts can be changed later if that's deemed
   necessary.

   See <AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LWnVd@mail.gmail.com>
   for a further elaboration on this topic.

 * Shell:

   Shell code that's to be localized should use the new git-sh-i18n
   library. It's just a wrapper for the system's gettext.sh.

   If gettext.sh isn't available we'll fall back on a dumb printf(1)
   fall-through wrapper.

   I originally tried to detect if the system supported `echo -n' but
   I found this to be a waste of time. My benchmarks on Linux, Solaris
   and FreeBSD reveal that printf(1) is fast enough, especially since
   we aren't calling gettext() from within any tight loops.

This patch is based on work by Jeff Epler <jepler@unpythonic.net> who
did the initial Makefile / C work, and a lot of comments from the Git
mailing list, including Jonathan Nieder, Jakub Narebski, Johannes
Sixt, Peter Krefting and others.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---

On Mon, Jun 7, 2010 at 13:19, Jeff Epler <jepler@unpythonic.net> wrote:
> On Sun, Jun 06, 2010 at 05:47:32PM +0000, Ęvar Arnfjörš Bjarmason wrote:
>> the message catalog wont 't be installed on the system during make
>
> trivial typo

Thanks for reviewing it. Fixed the commit message typo in this
version.

 .gitignore                   |    2 +
 INSTALL                      |    8 +++
 Makefile                     |   69 ++++++++++++++++++++++-
 config.mak.in                |    2 +
 configure.ac                 |   12 ++++
 daemon.c                     |    3 +
 fast-import.c                |    3 +
 gettext.c                    |   21 +++++++
 gettext.h                    |   18 ++++++
 git-sh-i18n.sh               |   47 ++++++++++++++++
 git.c                        |    3 +
 http-backend.c               |    3 +
 http-fetch.c                 |    3 +
 http-push.c                  |    3 +
 imap-send.c                  |    3 +
 perl/Git/I18N.pm             |   91 ++++++++++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 ++++-
 po/.gitignore                |    1 +
 po/is.po                     |   47 ++++++++++++++++
 shell.c                      |    3 +
 show-index.c                 |    3 +
 t/t0200-gettext.sh           |  126 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   13 ++++
 t/t0200/test.perl            |   14 +++++
 t/t0200/test.sh              |   14 +++++
 t/t0201-gettext-fallbacks.sh |   42 ++++++++++++++
 t/t0202-gettext-perl.sh      |   20 +++++++
 t/t0202/test.pl              |  104 ++++++++++++++++++++++++++++++++++
 t/test-lib.sh                |    2 +
 upload-pack.c                |    3 +
 31 files changed, 696 insertions(+), 4 deletions(-)
 create mode 100644 gettext.c
 create mode 100644 gettext.h
 create mode 100644 git-sh-i18n.sh
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/.gitignore
 create mode 100644 po/is.po
 create mode 100755 t/t0200-gettext.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0201-gettext-fallbacks.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl

diff --git a/.gitignore b/.gitignore
index 14e2b6b..6c2b193 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,6 +125,7 @@
 /git-rm
 /git-send-email
 /git-send-pack
+/git-sh-i18n
 /git-sh-setup
 /git-shell
 /git-shortlog
@@ -204,3 +205,4 @@
 *.pdb
 /Debug/
 /Release/
+/share/
diff --git a/INSTALL b/INSTALL
index 61086ab..f30d5bd 100644
--- a/INSTALL
+++ b/INSTALL
@@ -93,6 +93,14 @@ Issues of note:
 	  history graphically, and in git-gui.  If you don't want gitk or
 	  git-gui, you can use NO_TCLTK.
 
+	- The GNU "libintl" library is used by default for localizing
+	  Git. It needs a gettext.h on the system for C code, gettext.sh
+	  for shell scripts, and libintl-perl for Perl programs.
+
+	  Set NO_GETTEXT to disable localization support and make Git only
+	  use English. Under autoconf the configure script will do this
+	  automatically if it can't find libintl on the system.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
diff --git a/Makefile b/Makefile
index d5d6565..0ddf6e8 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,15 @@ all::
 # Define NO_EXPAT if you do not have expat installed.  git-http-push is
 # not built, and you cannot push using http:// and https:// transports.
 #
+# Define NO_GETTEXT if you don't want to build with Git with gettext
+# support. Building it requires GNU libintl, and additionally
+# libintl-perl at runtime.
+#
+# Define NEEDS_LIBINTL if you haven't set NO_GETTEXT and your system
+# needs to be explicitly linked to -lintl. It's defined automatically
+# on platforms where we don't expect glibc (Linux, Hurd,
+# GNU/kFreeBSD), which includes libintl.
+#
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
@@ -272,6 +281,7 @@ mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
 sharedir = $(prefix)/share
+localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ifeq ($(prefix),/usr)
@@ -285,7 +295,7 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir
+export prefix bindir sharedir sysconfdir localedir
 
 CC = gcc
 AR = ar
@@ -297,6 +307,8 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
+XGETTEXT = xgettext
+MSGFMT = msgfmt
 
 export TCL_PATH TCLTK_PATH
 
@@ -358,6 +370,7 @@ SCRIPT_SH += git-web--browse.sh
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
 SCRIPT_LIB += git-sh-setup
+SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
@@ -523,6 +536,7 @@ LIB_H += userdiff.h
 LIB_H += utf8.h
 LIB_H += xdiff-interface.h
 LIB_H += xdiff/xdiff.h
+LIB_H += gettext.h
 
 LIB_OBJS += abspath.o
 LIB_OBJS += advice.o
@@ -564,6 +578,9 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+ifndef NO_GETTEXT
+LIB_OBJS += gettext.o
+endif
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -735,6 +752,14 @@ EXTLIBS =
 # Platform specific tweaks
 #
 
+# Platform specific defaults. Where we'd only like some feature on the
+# minority of systems, e.g. if linking to a library isn't needed
+# because its features are included in the GNU C library.
+ifndef NO_GETTEXT
+	# Systems that use GNU gettext and glibc are the exception
+	NEEDS_LIBINTL = YesPlease
+endif
+
 # We choose to avoid "if .. else if .. else .. endif endif"
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
@@ -743,11 +768,13 @@ ifeq ($(uname_S),Linux)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),UnixWare)
 	CC = cc
@@ -917,6 +944,7 @@ ifeq ($(uname_S),GNU)
 	NO_STRLCPY=YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),IRIX)
 	NO_SETENV = YesPlease
@@ -1386,6 +1414,14 @@ ifdef USE_NED_ALLOCATOR
        COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
 endif
 
+ifdef NO_GETTEXT
+	COMPAT_CFLAGS += -DNO_GETTEXT
+endif
+
+ifdef NEEDS_LIBINTL
+	EXTLIBS += -lintl
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1415,6 +1451,7 @@ ifndef V
 	QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
 	QUIET_GEN      = @echo '   ' GEN $@;
 	QUIET_LNCP     = @echo '   ' LN/CP $@;
+	QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
 	QUIET_SUBDIR0  = +@subdir=
 	QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
 			 $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1442,7 +1479,9 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
 prefix_SQ = $(subst ','\'',$(prefix))
+sharedir_SQ = $(subst ','\'',$(sharedir))
 
+LOCALEDIR_SQ = $(subst ','\'',$(localedir))
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
@@ -1491,7 +1530,7 @@ ifndef NO_TCLTK
 	$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
 ifndef NO_PERL
-	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
 endif
 ifndef NO_PYTHON
 	$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1536,6 +1575,7 @@ $(RM) $@ $@+ && \
 sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's|@@LOCALEDIR@@|$(LOCALEDIR_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
@@ -1868,6 +1908,21 @@ cscope:
 	$(RM) cscope*
 	$(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 
+pot:
+	$(XGETTEXT) --add-comments --keyword=_ --keyword=N_ --output=po/git.pot --language=C $(C_OBJ:o=c) t/t0200/test.c
+	$(XGETTEXT) --add-comments --join-existing --output=po/git.pot --language=Shell $(SCRIPT_SH) t/t0200/test.sh
+	$(XGETTEXT) --add-comments --join-existing --keyword=__ --output=po/git.pot --language=Perl $(SCRIPT_PERL) t/t0200/test.perl
+
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES))
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+share/locale/%/LC_MESSAGES/git.mo: po/%.po
+	@mkdir -p $(dir $@)
+	$(QUIET_MSGFMT)$(MSGFMT) -o $@ $<
+
 ### Detect prefix changes
 TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
@@ -1889,6 +1944,7 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
 	@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
 	@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+	@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1980,6 +2036,11 @@ install: all
 	$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
+ifndef NO_GETTEXT
+	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sharedir_SQ)/locale'
+	(cd share && tar cf - locale) | \
+		(cd '$(DESTDIR_SQ)$(sharedir_SQ)' && umask 022 && tar xof -)
+endif
 	$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 ifndef NO_PERL
 	$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
@@ -2127,6 +2188,10 @@ ifndef NO_TCLTK
 	$(MAKE) -C git-gui clean
 endif
 	$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
+ifndef NO_GETTEXT
+	$(RM) po/git.pot
+	$(RM) -r share/
+endif
 
 .PHONY: all install clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
diff --git a/config.mak.in b/config.mak.in
index 0d4b64d..c49072c 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -32,9 +32,11 @@ NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
 NO_LIBGEN_H=@NO_LIBGEN_H@
 HAVE_PATHS_H=@HAVE_PATHS_H@
+NO_GETTEXT=@NO_GETTEXT@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
 NEEDS_RESOLV=@NEEDS_RESOLV@
+NEEDS_LIBINTL=@NEEDS_LIBINTL@
 NEEDS_LIBGEN=@NEEDS_LIBGEN@
 NO_SYS_SELECT_H=@NO_SYS_SELECT_H@
 NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@
diff --git a/configure.ac b/configure.ac
index 71038fc..74879b4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -559,6 +559,12 @@ AC_CHECK_LIB([c], [basename],
 AC_SUBST(NEEDS_LIBGEN)
 test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
+AC_CHECK_LIB([c], [gettext],
+[NEEDS_LIBINTL=],
+[NEEDS_LIBINTL=YesPlease])
+AC_SUBST(NEEDS_LIBINTL)
+test -n "$NEEDS_LIBINTL" && LIBS="$LIBS -lintl"
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -730,6 +736,12 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# Define NO_GETTEXT if you don't have libintl.h
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
diff --git a/daemon.c b/daemon.c
index a90ab10..7f4691c 100644
--- a/daemon.c
+++ b/daemon.c
@@ -3,6 +3,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 #include <syslog.h>
 
@@ -974,6 +975,8 @@ int main(int argc, char **argv)
 	gid_t gid = 0;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	for (i = 1; i < argc; i++) {
diff --git a/fast-import.c b/fast-import.c
index 129a786..6947f7a 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -156,6 +156,7 @@ Format of STDIN stream:
 #include "csum-file.h"
 #include "quote.h"
 #include "exec_cmd.h"
+#include "gettext.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -2904,6 +2905,8 @@ int main(int argc, const char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(fast_import_usage);
 
diff --git a/gettext.c b/gettext.c
new file mode 100644
index 0000000..407fbc0
--- /dev/null
+++ b/gettext.c
@@ -0,0 +1,21 @@
+#include "exec_cmd.h"
+#include <libintl.h>
+#include <stdlib.h>
+
+extern void git_setup_gettext(void) {
+	char *podir;
+	char *envdir = getenv("GIT_TEXTDOMAINDIR");
+
+	if (envdir) {
+		(void)bindtextdomain("git", envdir);
+	} else {
+		podir = (char *)system_path("share/locale");
+		if (!podir) return;
+		(void)bindtextdomain("git", podir);
+		free(podir);
+	}
+
+	(void)setlocale(LC_MESSAGES, "");
+	(void)setlocale(LC_CTYPE, "");
+	(void)textdomain("git");
+}
diff --git a/gettext.h b/gettext.h
new file mode 100644
index 0000000..e02939a
--- /dev/null
+++ b/gettext.h
@@ -0,0 +1,18 @@
+#ifndef GETTEXT_H
+#define GETTEXT_H
+
+#ifdef NO_GETTEXT
+static inline void git_setup_gettext(void) {}
+#else
+extern void git_setup_gettext(void);
+#endif
+
+#define N_(s) (s)
+#ifdef NO_GETTEXT
+#define _(s) (s)
+#else
+#include <libintl.h>
+#define _(s) gettext(s)
+#endif
+
+#endif
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
new file mode 100644
index 0000000..d4963e9
--- /dev/null
+++ b/git-sh-i18n.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+# This is Git's interface to gettext.sh. Use it right after
+# git-sh-setup as:
+#
+#   . git-sh-setup
+#   . git-sh-i18n
+#
+#   # For constant interface messages:
+#   gettext "A message for the user"; echo
+#
+#   # To interpolate variables:
+#   details="oh noes"
+#   eval_gettext "An error occured: \$details"; echo
+#
+# See "info '(gettext)sh'" for the full manual.
+
+# Try to use libintl's gettext.sh, or fall back to English if we
+# can't.
+. gettext.sh
+
+if test $? -eq 0 && test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+then
+	TEXTDOMAIN=git
+	export TEXTDOMAIN
+	if [ -z "$GIT_TEXTDOMAINDIR" ]
+	then
+		TEXTDOMAINDIR="@@LOCALEDIR@@"
+	else
+		TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+	fi
+	export TEXTDOMAINDIR
+else
+	# Since gettext.sh isn't available we'll have to define our own
+	# dummy pass-through functions.
+
+	gettext () {
+		printf "%s" "$1"
+	}
+
+	eval_gettext () {
+		gettext_eval="printf '%s' \"$1\""
+		printf "%s" "`eval \"$gettext_eval\"`"
+	}
+fi
diff --git a/git.c b/git.c
index 99f0363..d749eab 100644
--- a/git.c
+++ b/git.c
@@ -3,6 +3,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "run-command.h"
+#include "gettext.h"
 
 const char git_usage_string[] =
 	"git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n"
@@ -490,6 +491,8 @@ int main(int argc, const char **argv)
 	if (!cmd)
 		cmd = "git-help";
 
+	git_setup_gettext();
+
 	/*
 	 * "git-xxxx" is the same as "git xxxx", but we obviously:
 	 *
diff --git a/http-backend.c b/http-backend.c
index d1e83d0..b6d9bd5 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -6,6 +6,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "string-list.h"
+#include "gettext.h"
 
 static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
@@ -605,6 +606,8 @@ int main(int argc, char **argv)
 	char *cmd_arg = NULL;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	set_die_routine(die_webcgi);
 
diff --git a/http-fetch.c b/http-fetch.c
index 762c750..b889c36 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -2,6 +2,7 @@
 #include "exec_cmd.h"
 #include "http.h"
 #include "walker.h"
+#include "gettext.h"
 
 static const char http_fetch_usage[] = "git http-fetch "
 "[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url";
@@ -24,6 +25,8 @@ int main(int argc, const char **argv)
 	int get_verbosely = 0;
 	int get_recover = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	while (arg < argc && argv[arg][0] == '-') {
diff --git a/http-push.c b/http-push.c
index 415b1ab..ba0338c 100644
--- a/http-push.c
+++ b/http-push.c
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "list-objects.h"
 #include "sigchain.h"
+#include "gettext.h"
 
 #include <expat.h>
 
@@ -1791,6 +1792,8 @@ int main(int argc, char **argv)
 	struct remote *remote;
 	char *rewritten_url = NULL;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	repo = xcalloc(sizeof(*repo), 1);
diff --git a/imap-send.c b/imap-send.c
index 9d0097c..4f5f269 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -25,6 +25,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "run-command.h"
+#include "gettext.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #else
@@ -1535,6 +1536,8 @@ int main(int argc, char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(imap_send_usage);
 
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644
index 0000000..5918d68
--- /dev/null
+++ b/perl/Git/I18N.pm
@@ -0,0 +1,91 @@
+package Git::I18N;
+use 5.006002;
+use strict;
+use warnings;
+use Exporter;
+use base 'Exporter';
+
+our $VERSION = '0.01';
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+	our $TEXTDOMAIN = 'git';
+	our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+	require POSIX;
+	POSIX->import(qw(setlocale));
+	# Non-core prerequisite module
+	require Locale::Messages;
+	Locale::Messages->import(qw(:locale_h :libintl_h));
+
+	setlocale(LC_MESSAGES(), '');
+	setlocale(LC_CTYPE(), '');
+	textdomain($TEXTDOMAIN);
+	bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+	return;
+}
+
+BEGIN
+{
+	# Used by our test script to see if it should test fallbacks or
+	# not.
+	our $__HAS_LIBRARY = 1;
+
+	local $@;
+	eval { __bootstrap_locale_messages() };
+	if ($@) {
+		# Tell test.pl that we couldn't load the gettext library.
+		$Git::I18N::__HAS_LIBRARY = 0;
+
+		# Just a fall-through no-op
+		*__ = sub ($) { $_[0] };
+	} else {
+		*__ = \&Locale::Messages::gettext;
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+	use Git::I18N;
+
+	print __("Welcome to Git!\n");
+
+	printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
diff --git a/perl/Makefile b/perl/Makefile
index 4ab21d6..4e624ff 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -5,6 +5,7 @@ makfile:=perl.mak
 
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
 
 ifndef V
 	QUIET = @
@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	echo '	echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-	$(PERL_PATH) $< PREFIX='$(prefix_SQ)'
+	$(PERL_PATH) $< PREFIX='$(prefix_SQ)' --localedir='$(localedir_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index 0b9deca..456d45b 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
 use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
 
 sub MY::postamble {
 	return <<'MAKE_FRAG';
@@ -16,7 +24,10 @@ endif
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+my %pm = (
+	'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+	'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+);
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
@@ -33,6 +44,7 @@ WriteMakefile(
 	NAME            => 'Git',
 	VERSION_FROM    => 'Git.pm',
 	PM		=> \%pm,
+	PM_FILTER	=> qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
 	MAKEFILE	=> 'perl.mak',
 	INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..221000e
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1 @@
+/*.pot
diff --git a/po/is.po b/po/is.po
new file mode 100644
index 0000000..95739f1
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,47 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: t/t0200/test.c:4
+msgid "See git help COMMAND for more information on a specific command."
+msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:9
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:12
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
diff --git a/shell.c b/shell.c
index e4864e0..ba27c6b 100644
--- a/shell.c
+++ b/shell.c
@@ -2,6 +2,7 @@
 #include "quote.h"
 #include "exec_cmd.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 static int do_generic_cmd(const char *me, char *arg)
 {
@@ -51,6 +52,8 @@ int main(int argc, char **argv)
 	struct commands *cmd;
 	int devnull_fd;
 
+	git_setup_gettext();
+
 	/*
 	 * Always open file descriptors 0/1/2 to avoid clobbering files
 	 * in die().  It also avoids not messing up when the pipes are
diff --git a/show-index.c b/show-index.c
index 4c0ac13..c2f5448 100644
--- a/show-index.c
+++ b/show-index.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pack.h"
+#include "gettext.h"
 
 static const char show_index_usage[] =
 "git show-index < <packed archive index>";
@@ -11,6 +12,8 @@ int main(int argc, char **argv)
 	unsigned int version;
 	static unsigned int top_index[256];
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(show_index_usage);
 	if (fread(top_index, 2 * 4, 1, stdin) != 1)
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
new file mode 100755
index 0000000..b54e062
--- /dev/null
+++ b/t/t0200-gettext.sh
@@ -0,0 +1,126 @@
+#!/bin/sh
+
+test_description='Gettext support for Git'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_PO_PATH="$GIT_EXEC_PATH/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+    test $TEXTDOMAIN = "git"
+'
+
+if test_have_prereq GETTEXT; then
+	test_expect_success 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+    test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+	test_expect_success 'sanity: Icelandic locale was compiled' '
+    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+else
+	test_expect_success "sanity: \$TEXTDOMAINDIR doesn't exists with NO_GETTEXT=YesPlease" '
+    test_expect_failure test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+fi
+
+# Basic xgettext() extraction tests on po/*.po. Doesn't need gettext support
+test_expect_success 'xgettext: Perl _() strings are not extracted' '
+    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext: Comment extraction with --add-comments' '
+    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
+    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Comment extraction with --add-comments stops at statements' '
+    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+# We can go no further without actual gettext support
+if ! test_have_prereq GETTEXT; then
+	say "Skipping the rest of the gettext tests, Git was compiled with NO_GETTEXT=YesPlease"
+	test_done
+fi
+
+test_expect_success 'sanity: No gettext("") data for fantasy locale' '
+    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
+    test_expect_failure test -s real-locale
+'
+
+test_expect_success 'sanity: Some gettext("") data for real locale' '
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
+    test -s fantasy-locale
+'
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success 'sanity: gettext("") metadata is OK' '
+    # Return value may be non-zero
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > zero-expect &&
+    grep "Project-Id-Version: Git" zero-expect &&
+    grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+    grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+    grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success 'sanity: gettext(unknown) is passed through' '
+    printf "This is not a translation string"  > expect &&
+    gettext "This is not a translation string" > actual &&
+    eval_gettext "This is not a translation string" > actual &&
+    test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success 'xgettext: C extraction of _() and N_() strings' '
+    printf "TILRAUN: C tilraunastrengur" > expect &&
+    printf "\n" >> expect &&
+    printf "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun." >> expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string" > actual &&
+    printf "\n" >> actual &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "See git help COMMAND for more information on a specific command." >> actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: C extraction with %s' '
+    printf "TILRAUN: C tilraunastrengur %%s" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string %s" > actual &&
+    test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success 'xgettext: Shell extraction' '
+    printf "TILRAUN: Skeljartilraunastrengur" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Shell test string" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Shell extraction with $variable' '
+    printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" > x-expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 variable="a var i able" eval_gettext "TEST: A Shell test \$variable" > x-actual &&
+    test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success 'xgettext: Perl extraction' '
+    printf "TILRAUN: Perl tilraunastrengur" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test string" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Perl extraction with %s' '
+    printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test variable %s" > actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644
index 0000000..93373b3
--- /dev/null
+++ b/t/t0200/test.c
@@ -0,0 +1,13 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+	N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	puts(_("TEST: A C test string"));
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: A C test string %s"), "variable");
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644
index 0000000..36fba34
--- /dev/null
+++ b/t/t0200/test.perl
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644
index 0000000..022d607
--- /dev/null
+++ b/t/t0200/test.sh
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
new file mode 100755
index 0000000..b4bc1df
--- /dev/null
+++ b/t/t0201-gettext-fallbacks.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='Gettext Shell fallbacks'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+
+export GIT_TEXTDOMAINDIR GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+	test_expect_failure test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
+    printf "test" > expect &&
+    gettext "test" > actual &&
+    test_cmp expect actual &&
+    printf "test more words" > expect &&
+    gettext "test more words" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' '
+    printf "test" > expect &&
+    eval_gettext "test" > actual &&
+    test_cmp expect actual &&
+    printf "test more words" > expect &&
+    eval_gettext "test more words" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' '
+    printf "test YesPlease" > expect &&
+    eval_gettext "test \$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" > actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755
index 0000000..9b075b1
--- /dev/null
+++ b/t/t0202-gettext-perl.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+test_description='Perl gettext interface (Git::I18N)'
+. ./test-lib.sh
+
+if ! test_have_prereq PERL; then
+	say 'skipping perl interface tests, perl not available'
+	test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+	say "Perl Test::More unavailable, skipping test"
+	test_done
+}
+
+test_external_without_stderr \
+    'Perl Git::I18N API' \
+    "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644
index 0000000..4e9a0dc
--- /dev/null
+++ b/t/t0202/test.pl
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+use 5.006002;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use warnings;
+use strict;
+use Test::More tests => 9;
+use Git::I18N;
+use POSIX qw(:locale_h);
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N version $Git::I18N::VERSION with " .
+	 ($has_gettext_library
+	  ? "Locale::Messages version $Locale::Messages::VERSION"
+	  : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+ok($Git::I18N::VERSION, 'sanity: Git::I18N defines a $VERSION');
+{
+	my $exports = @Git::I18N::EXPORT;
+	ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+	# Add prototypes here when modifying the public interface to add
+	# more gettext wrapper functions.
+	my %prototypes = (qw(
+		__	$
+    ));
+	while (my ($sub, $proto) = each %prototypes) {
+		is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+	}
+}
+
+# Test basic passthrough in the C locale
+{
+	local $ENV{LANGUAGE} = 'C';
+	local $ENV{LC_ALL}   = 'C';
+	local $ENV{LANG} = 'C';
+
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+    unless ($ENV{TEST_GIT_I18N_EXHAUSTIVE}) {
+        # Can't reliably test __() with a non-C locales because the
+        # required locales may not be installed on the system.
+        #
+        # We test for these anyway as part of the shell
+        # tests. Skipping these here will eliminate failures on odd
+        # platforms with incomplete locale data.
+
+        skip "Set TEST_GIT_I18N_EXHAUSTIVE=1 to enable exhaustive Git::I18N locale tests", 2;
+    }
+
+	my $test = sub {
+		my ($got, $expect, $msg, $locale) = @_;
+		# Maybe this system doesn't have the locale we're trying to
+		# test.
+		my $locale_ok = setlocale(LC_ALL, $locale);
+		is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+	};
+
+	my $env_C = sub {
+		$ENV{LANGUAGE} = 'C';
+		$ENV{LC_ALL}   = 'C';
+	};
+
+	my $env_is = sub {
+		$ENV{LANGUAGE} = 'is';
+		$ENV{LC_ALL}   = 'is_IS.UTF-8';
+	};
+
+	# Translation's the same as the original
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	if ($has_gettext_library) {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "With", 'C');
+		}
+
+		{
+			my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+			local %ENV; $env_is->();
+			$test->($got, $expect, "With", 'is_IS.UTF-8');
+		}
+	} else {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "Without", 'C');
+		}
+
+		{
+			local %ENV; $env_is->();
+			$test->($got, $expect, "Without", 'is');
+		}
+	}
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 454880a..ae63316 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -37,6 +37,7 @@ ORIGINAL_TERM=$TERM
 # For repeatability, reset the environment to known value.
 LANG=C
 LC_ALL=C
+LANGUAGE=C
 PAGER=cat
 TZ=UTC
 TERM=dumb
@@ -845,6 +846,7 @@ esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # test whether the filesystem supports symbolic links
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
diff --git a/upload-pack.c b/upload-pack.c
index dc464d7..ece9a4b 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -10,6 +10,7 @@
 #include "revision.h"
 #include "list-objects.h"
 #include "run-command.h"
+#include "gettext.h"
 
 static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -686,6 +687,8 @@ int main(int argc, char **argv)
 	int i;
 	int strict = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	read_replace_refs = 0;
 
-- 
1.7.1.243.gda92d6.dirty

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 1/2] Add infrastructure for translating Git with gettext
  2010-06-07 21:23     ` [PATCH v2 " Ævar Arnfjörð Bjarmason
@ 2010-06-14  5:00       ` Junio C Hamano
  2010-06-15  2:11         ` [PATCH v3 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
                           ` (5 more replies)
  0 siblings, 6 replies; 16+ messages in thread
From: Junio C Hamano @ 2010-06-14  5:00 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Jeff Epler, Jakub Narebski, Jonathan Nieder

[-- Attachment #1: Type: text/plain, Size: 1285 bytes --]

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:

> diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
> new file mode 100755
> index 0000000..b54e062
> --- /dev/null
> +++ b/t/t0200-gettext.sh
> @@ -0,0 +1,126 @@
> ...
> +test_expect_success 'sanity: No gettext("") data for fantasy locale' '
> +    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
> +    test_expect_failure test -s real-locale
> +'
> +
> +test_expect_success 'sanity: Some gettext("") data for real locale' '
> +    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
> +    test -s fantasy-locale
> +'

I am getting an empty output in the fantasy-locale file here.

Besides, the tests above look somewhat wrong.  "test_expect_failure" is a
top-level construct and the 11th test should read like this instead:

    test_expect_success 'sanity: No gettext("") data for fantasy locale' '
        LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" >fantasy-locale &&
        ! test -s fantasy-locale
    '

The test label reads "fantasy" but the test files were named "real"; they
should be consistent.  Also notice that I fixed the redirection style,
too, but that is a minor point.

The full error log up to the first failure on my system is shown below...


[-- Attachment #2: error log \"sh -x t0200-gettext.sh -i -v\" --]
[-- Type: text/plain, Size: 22749 bytes --]

+ test_description='Gettext support for Git'
+ . ./test-lib.sh
++ case "$GIT_TEST_TEE_STARTED, $* " in
++ ORIGINAL_TERM=screen
++ LANG=C
++ LC_ALL=C
++ LANGUAGE=C
++ PAGER=cat
++ TZ=UTC
++ TERM=dumb
++ export LANG LC_ALL PAGER TERM TZ
++ EDITOR=:
++ unset VISUAL
++ unset GIT_EDITOR
++ unset AUTHOR_DATE
++ unset AUTHOR_EMAIL
++ unset AUTHOR_NAME
++ unset COMMIT_AUTHOR_EMAIL
++ unset COMMIT_AUTHOR_NAME
++ unset EMAIL
++ unset GIT_ALTERNATE_OBJECT_DIRECTORIES
++ unset GIT_AUTHOR_DATE
++ GIT_AUTHOR_EMAIL=author@example.com
++ GIT_AUTHOR_NAME='A U Thor'
++ unset GIT_COMMITTER_DATE
++ GIT_COMMITTER_EMAIL=committer@example.com
++ GIT_COMMITTER_NAME='C O Mitter'
++ unset GIT_DIFF_OPTS
++ unset GIT_DIR
++ unset GIT_WORK_TREE
++ unset GIT_EXTERNAL_DIFF
++ unset GIT_INDEX_FILE
++ unset GIT_OBJECT_DIRECTORY
++ unset GIT_CEILING_DIRECTORIES
++ unset SHA1_FILE_DIRECTORIES
++ unset SHA1_FILE_DIRECTORY
++ unset GIT_NOTES_REF
++ unset GIT_NOTES_DISPLAY_REF
++ unset GIT_NOTES_REWRITE_REF
++ unset GIT_NOTES_REWRITE_MODE
++ GIT_MERGE_VERBOSITY=5
++ export GIT_MERGE_VERBOSITY
++ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
++ export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
++ export EDITOR
++ GIT_TEST_CMP='diff -u'
++ unset CDPATH
++ unset GREP_OPTIONS
++ case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in
+++ echo
+++ tr '[A-Z]' '[a-z]'
++ _x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
++ _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
++ '[' xscreen '!=' xdumb ']'
++ TERM=screen
++ export TERM
++ '[' -t 1 ']'
++ test 2 -ne 0
++ case "$1" in
++ immediate=t
++ shift
++ test 1 -ne 0
++ case "$1" in
++ verbose=t
++ shift
++ test 0 -ne 0
++ test -n ''
++ test 'Gettext support for Git' '!=' ''
++ test '' = t
++ exec
++ test t = t
++ exec
++ test_failure=0
++ test_count=0
++ test_fixed=0
++ test_broken=0
++ test_success=0
++ GIT_EXIT_OK=
++ trap die EXIT
++ satisfied=' '
+++ pwd
++ TEST_DIRECTORY=/srv/project/git/git.build/t
++ test -n ''
++ test -n ''
++ git_bin_dir=/srv/project/git/git.build/t/../bin-wrappers
++ test -x /srv/project/git/git.build/t/../bin-wrappers/git
++ PATH=/srv/project/git/git.build/t/../bin-wrappers:/home/junio/g/Debian-5.0.4-x86_64/git-active/bin:/home/junio/bin/Debian-5.0.4-x86_64:/home/junio/bin/common:/usr/local/bin:/sbin:/usr/sbin:/usr/bin:/bin:/usr/games
++ GIT_EXEC_PATH=/srv/project/git/git.build/t/..
++ test -n ''
+++ pwd
++ GIT_TEMPLATE_DIR=/srv/project/git/git.build/t/../templates/blt
++ unset GIT_CONFIG
++ GIT_CONFIG_NOSYSTEM=1
++ GIT_CONFIG_NOGLOBAL=1
++ export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
++ . ../GIT-BUILD-OPTIONS
+++ SHELL_PATH=/bin/sh
+++ PERL_PATH=/usr/bin/perl
+++ TAR=tar
+++ NO_CURL=
+++ NO_PERL=
+++ NO_PYTHON=
+++ NO_GETTEXT=
+++ pwd
+++ pwd
++ GITPERLLIB=/srv/project/git/git.build/t/../perl/blib/lib:/srv/project/git/git.build/t/../perl/blib/arch/auto/Git
++ export GITPERLLIB
++ test -d ../templates/blt
++ test -z ''
++ test -z ''
+++ pwd
++ GITPYTHONLIB=/srv/project/git/git.build/t/../git_remote_helpers/build/lib
++ export GITPYTHONLIB
++ test -d ../git_remote_helpers/build
++ test -x ../test-chmtime
+++ basename t0200-gettext.sh .sh
++ test='trash directory.t0200-gettext'
++ test -n ''
++ case "$test" in
++ TRASH_DIRECTORY='/srv/project/git/git.build/t/trash directory.t0200-gettext'
++ test '!' -z ''
++ remove_trash='/srv/project/git/git.build/t/trash directory.t0200-gettext'
++ rm -fr 'trash directory.t0200-gettext'
++ test_create_repo 'trash directory.t0200-gettext'
++ test 1 = 1
+++ pwd
++ owd=/srv/project/git/git.build/t
++ repo='trash directory.t0200-gettext'
++ mkdir -p 'trash directory.t0200-gettext'
++ cd 'trash directory.t0200-gettext'
++ /srv/project/git/git.build/t/../git-init --template=/srv/project/git/git.build/t/../templates/blt/
Initialized empty Git repository in /srv/project/git/git.build/t/trash directory.t0200-gettext/.git/
++ mv .git/hooks .git/hooks-disabled
++ cd /srv/project/git/git.build/t
++ cd -P 'trash directory.t0200-gettext'
++ this_test=t0200-gettext.sh
++ this_test=t0200
++ case $(uname -s) in
+++ uname -s
++ test_set_prereq POSIXPERM
++ satisfied=' POSIXPERM '
++ test_set_prereq BSLASHPSPEC
++ satisfied=' POSIXPERM BSLASHPSPEC '
++ test_set_prereq EXECKEEPSPID
++ satisfied=' POSIXPERM BSLASHPSPEC EXECKEEPSPID '
++ test -z ''
++ test_set_prereq PERL
++ satisfied=' POSIXPERM BSLASHPSPEC EXECKEEPSPID PERL '
++ test -z ''
++ test_set_prereq PYTHON
++ satisfied=' POSIXPERM BSLASHPSPEC EXECKEEPSPID PERL PYTHON '
++ test -z ''
++ test_set_prereq GETTEXT
++ satisfied=' POSIXPERM BSLASHPSPEC EXECKEEPSPID PERL PYTHON GETTEXT '
++ ln -s x y
++ test -h y
++ test_set_prereq SYMLINKS
++ satisfied=' POSIXPERM BSLASHPSPEC EXECKEEPSPID PERL PYTHON GETTEXT SYMLINKS '
++ rm -f y
+ GIT_TEXTDOMAINDIR=/srv/project/git/git.build/t/../share/locale
+ GIT_PO_PATH=/srv/project/git/git.build/t/../po
+ export GIT_TEXTDOMAINDIR GIT_PO_PATH
+ . /srv/project/git/git.build/t/../git-sh-i18n
++ . gettext.sh
+++ test 'X\t' = 'X\t'
+++ echo=echo
+++ test -z ''
+++ case "$0" in
++ test 0 -eq 0
++ test -z ''
++ TEXTDOMAIN=git
++ export TEXTDOMAIN
++ '[' -z /srv/project/git/git.build/t/../share/locale ']'
++ TEXTDOMAINDIR=/srv/project/git/git.build/t/../share/locale
++ export TEXTDOMAINDIR
+ test_expect_success 'sanity: $TEXTDOMAIN is git' '
    test $TEXTDOMAIN = "git"
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'sanity: $TEXTDOMAIN is git' '
    test $TEXTDOMAIN = "git"
'
+ test_count=1
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    test $TEXTDOMAIN = "git"
'
+ say_color info 'expecting success: 
    test $TEXTDOMAIN = "git"
'
+ test -z info
+ shift
+ echo '* expecting success: 
    test $TEXTDOMAIN = "git"
'
* expecting success: 
    test $TEXTDOMAIN = "git"

+ test_run_ '
    test $TEXTDOMAIN = "git"
'
+ test_cleanup=:
+ eval '
    test $TEXTDOMAIN = "git"
'
++ test git = git
+ eval_ret=0
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 0 = 0 ']'
+ test_ok_ 'sanity: $TEXTDOMAIN is git'
+ test_success=1
+ say_color '' '  ok 1: sanity: $TEXTDOMAIN is git'
+ test -z ''
+ test -n ''
+ shift
+ echo '*   ok 1: sanity: $TEXTDOMAIN is git'
*   ok 1: sanity: $TEXTDOMAIN is git
+ echo ''

+ test_have_prereq GETTEXT
+ case $satisfied in
+ : yes, have it
+ test_expect_success 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
'
+ test_count=2
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
'
+ say_color info 'expecting success: 
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
'
+ test -z info
+ shift
+ echo '* expecting success: 
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
'
* expecting success: 
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"

+ test_run_ '
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
'
+ test_cleanup=:
+ eval '
    test -d "$TEXTDOMAINDIR" &&
    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
'
++ test -d /srv/project/git/git.build/t/../share/locale
++ test /srv/project/git/git.build/t/../share/locale = /srv/project/git/git.build/t/../share/locale
+ eval_ret=0
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 0 = 0 ']'
+ test_ok_ 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease'
+ test_success=2
+ say_color '' '  ok 2: sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease'
+ test -z ''
+ test -n ''
+ shift
+ echo '*   ok 2: sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease'
*   ok 2: sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease
+ echo ''

+ test_expect_success 'sanity: Icelandic locale was compiled' '
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'sanity: Icelandic locale was compiled' '
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
'
+ test_count=3
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
'
+ say_color info 'expecting success: 
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
'
+ test -z info
+ shift
+ echo '* expecting success: 
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
'
* expecting success: 
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"

+ test_run_ '
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
'
+ test_cleanup=:
+ eval '
    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
'
++ test -f /srv/project/git/git.build/t/../share/locale/is/LC_MESSAGES/git.mo
+ eval_ret=0
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 0 = 0 ']'
+ test_ok_ 'sanity: Icelandic locale was compiled'
+ test_success=3
+ say_color '' '  ok 3: sanity: Icelandic locale was compiled'
+ test -z ''
+ test -n ''
+ shift
+ echo '*   ok 3: sanity: Icelandic locale was compiled'
*   ok 3: sanity: Icelandic locale was compiled
+ echo ''

+ test_expect_success 'xgettext: Perl _() strings are not extracted' '
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'xgettext: Perl _() strings are not extracted' '
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'
+ test_count=4
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'
+ say_color info 'expecting success: 
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'
+ test -z info
+ shift
+ echo '* expecting success: 
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'
* expecting success: 
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po

+ test_run_ '
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'
+ test_cleanup=:
+ eval '
    test_expect_failure grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'
++ test_expect_failure grep 'A Perl string xgettext will not get' /srv/project/git/git.build/t/../po/is.po
++ test 3 = 3
++ prereq=grep
++ shift
++ test 2 = 2
++ test_skip 'A Perl string xgettext will not get' /srv/project/git/git.build/t/../po/is.po
++ test_count=5
++ to_skip=
++ test -z ''
++ test -n grep
++ test_have_prereq grep
++ case $satisfied in
++ : nope
++ to_skip=t
++ case "$to_skip" in
++ say_color skip 'skipping test: A Perl string xgettext will not get' /srv/project/git/git.build/t/../po/is.po
++ test -z skip
++ shift
++ echo '* skipping test: A Perl string xgettext will not get /srv/project/git/git.build/t/../po/is.po'
* skipping test: A Perl string xgettext will not get /srv/project/git/git.build/t/../po/is.po
++ say_color skip 'skip 5: A Perl string xgettext will not get'
++ test -z skip
++ shift
++ echo '* skip 5: A Perl string xgettext will not get'
* skip 5: A Perl string xgettext will not get
++ : true
++ echo ''

+ eval_ret=0
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 0 = 0 ']'
+ test_ok_ 'xgettext: Perl _() strings are not extracted'
+ test_success=4
+ say_color '' '  ok 5: xgettext: Perl _() strings are not extracted'
+ test -z ''
+ test -n ''
+ shift
+ echo '*   ok 5: xgettext: Perl _() strings are not extracted'
*   ok 5: xgettext: Perl _() strings are not extracted
+ echo ''

+ test_expect_success 'xgettext: Comment extraction with --add-comments' '
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'xgettext: Comment extraction with --add-comments' '
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual
'
+ test_count=6
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual
'
+ say_color info 'expecting success: 
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual
'
+ test -z info
+ shift
+ echo '* expecting success: 
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual
'
* expecting success: 
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual

+ test_run_ '
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual
'
+ test_cleanup=:
+ eval '
    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l > expect &&
    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l > actual &&
    test_cmp expect actual
'
++ wc -l
++ grep 'TRANSLATORS: This is a test' /srv/project/git/git.build/t/t0200/test.c /srv/project/git/git.build/t/t0200/test.perl /srv/project/git/git.build/t/t0200/test.sh
++ grep 'TRANSLATORS: This is a test' /srv/project/git/git.build/t/../po/is.po
++ wc -l
++ test_cmp expect actual
++ diff -u expect actual
+ eval_ret=0
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 0 = 0 ']'
+ test_ok_ 'xgettext: Comment extraction with --add-comments'
+ test_success=5
+ say_color '' '  ok 6: xgettext: Comment extraction with --add-comments'
+ test -z ''
+ test -n ''
+ shift
+ echo '*   ok 6: xgettext: Comment extraction with --add-comments'
*   ok 6: xgettext: Comment extraction with --add-comments
+ echo ''

+ test_expect_success 'xgettext: Comment extraction with --add-comments stops at statements' '
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'xgettext: Comment extraction with --add-comments stops at statements' '
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
'
+ test_count=7
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
'
+ say_color info 'expecting success: 
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
'
+ test -z info
+ shift
+ echo '* expecting success: 
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
'
* expecting success: 
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po

+ test_run_ '
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
'
+ test_cleanup=:
+ eval '
    test_expect_failure grep "This is a phony" "$GIT_PO_PATH"/is.po &&
    test_expect_failure grep "the above comment" "$GIT_PO_PATH"/is.po
'
++ test_expect_failure grep 'This is a phony' /srv/project/git/git.build/t/../po/is.po
++ test 3 = 3
++ prereq=grep
++ shift
++ test 2 = 2
++ test_skip 'This is a phony' /srv/project/git/git.build/t/../po/is.po
++ test_count=8
++ to_skip=
++ test -z ''
++ test -n grep
++ test_have_prereq grep
++ case $satisfied in
++ : nope
++ to_skip=t
++ case "$to_skip" in
++ say_color skip 'skipping test: This is a phony' /srv/project/git/git.build/t/../po/is.po
++ test -z skip
++ shift
++ echo '* skipping test: This is a phony /srv/project/git/git.build/t/../po/is.po'
* skipping test: This is a phony /srv/project/git/git.build/t/../po/is.po
++ say_color skip 'skip 8: This is a phony'
++ test -z skip
++ shift
++ echo '* skip 8: This is a phony'
* skip 8: This is a phony
++ : true
++ echo ''

++ test_expect_failure grep 'the above comment' /srv/project/git/git.build/t/../po/is.po
++ test 3 = 3
++ prereq=grep
++ shift
++ test 2 = 2
++ test_skip 'the above comment' /srv/project/git/git.build/t/../po/is.po
++ test_count=9
++ to_skip=
++ test -z ''
++ test -n grep
++ test_have_prereq grep
++ case $satisfied in
++ : nope
++ to_skip=t
++ case "$to_skip" in
++ say_color skip 'skipping test: the above comment' /srv/project/git/git.build/t/../po/is.po
++ test -z skip
++ shift
++ echo '* skipping test: the above comment /srv/project/git/git.build/t/../po/is.po'
* skipping test: the above comment /srv/project/git/git.build/t/../po/is.po
++ say_color skip 'skip 9: the above comment'
++ test -z skip
++ shift
++ echo '* skip 9: the above comment'
* skip 9: the above comment
++ : true
++ echo ''

+ eval_ret=0
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 0 = 0 ']'
+ test_ok_ 'xgettext: Comment extraction with --add-comments stops at statements'
+ test_success=6
+ say_color '' '  ok 9: xgettext: Comment extraction with --add-comments stops at statements'
+ test -z ''
+ test -n ''
+ shift
+ echo '*   ok 9: xgettext: Comment extraction with --add-comments stops at statements'
*   ok 9: xgettext: Comment extraction with --add-comments stops at statements
+ echo ''

+ test_have_prereq GETTEXT
+ case $satisfied in
+ : yes, have it
+ test_expect_success 'sanity: No gettext("") data for fantasy locale' '
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'sanity: No gettext("") data for fantasy locale' '
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale
'
+ test_count=10
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale
'
+ say_color info 'expecting success: 
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale
'
+ test -z info
+ shift
+ echo '* expecting success: 
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale
'
* expecting success: 
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale

+ test_run_ '
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale
'
+ test_cleanup=:
+ eval '
    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" > real-locale &&
    test_expect_failure test -s real-locale
'
++ LANGUAGE=is
++ LC_ALL=tlh_TLH.UTF-8
++ gettext ''
++ test_expect_failure test -s real-locale
++ test 3 = 3
++ prereq=test
++ shift
++ test 2 = 2
++ test_skip -s real-locale
++ test_count=11
++ to_skip=
++ test -z ''
++ test -n test
++ test_have_prereq test
++ case $satisfied in
++ : nope
++ to_skip=t
++ case "$to_skip" in
++ say_color skip 'skipping test: -s' real-locale
++ test -z skip
++ shift
++ echo '* skipping test: -s real-locale'
* skipping test: -s real-locale
++ say_color skip 'skip 11: -s'
++ test -z skip
++ shift
++ echo '* skip 11: -s'
* skip 11: -s
++ : true
++ echo ''

+ eval_ret=0
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 0 = 0 ']'
+ test_ok_ 'sanity: No gettext("") data for fantasy locale'
+ test_success=7
+ say_color '' '  ok 11: sanity: No gettext("") data for fantasy locale'
+ test -z ''
+ test -n ''
+ shift
+ echo '*   ok 11: sanity: No gettext("") data for fantasy locale'
*   ok 11: sanity: No gettext("") data for fantasy locale
+ echo ''

+ test_expect_success 'sanity: Some gettext("") data for real locale' '
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
+ test 2 = 3
+ prereq=
+ test 2 = 2
+ test_skip 'sanity: Some gettext("") data for real locale' '
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
+ test_count=12
+ to_skip=
+ test -z ''
+ test -n ''
+ case "$to_skip" in
+ false
+ say 'expecting success: 
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
+ say_color info 'expecting success: 
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
+ test -z info
+ shift
+ echo '* expecting success: 
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
* expecting success: 
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale

+ test_run_ '
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
+ test_cleanup=:
+ eval '
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
++ LANGUAGE=is
++ LC_ALL=is_IS.UTF-8
++ gettext ''
++ test -s fantasy-locale
+ eval_ret=1
+ eval :
++ :
+ return 0
+ '[' 0 = 0 -a 1 = 0 ']'
+ test_failure_ 'sanity: Some gettext("") data for real locale' '
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
+ test_failure=1
+ say_color error 'FAIL 12: sanity: Some gettext("") data for real locale'
+ test -z error
+ shift
+ echo '* FAIL 12: sanity: Some gettext("") data for real locale'
* FAIL 12: sanity: Some gettext("") data for real locale
+ shift
+ echo '
    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
    test -s fantasy-locale
'
+ sed -e 's/^/	/'
	
	    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" > fantasy-locale &&
	    test -s fantasy-locale
	
+ test t = ''
+ GIT_EXIT_OK=t
+ exit 1
+ die
+ code=1
+ test -n t
+ exit 1

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/2] Add infrastructure for translating Git with gettext
  2010-06-06 17:47 ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
  2010-06-07 10:02   ` Jakub Narebski
  2010-06-07 13:19   ` Jeff Epler
@ 2010-06-14 20:26   ` Ævar Arnfjörð Bjarmason
  2010-06-14 21:56     ` Thomas Rast
  2 siblings, 1 reply; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-14 20:26 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	Ævar Arnfjörð Bjarmason

On Sun, Jun 6, 2010 at 17:47, Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:

> +# Try to use libintl's gettext.sh, or fall back to English if we
> +# can't.
> +. gettext.sh
> +
> +if test $? -eq 0 && test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
> +then

It turns out that this doesn't actually work, and I can't find a
workaround. In Bash and Solaris's /bin/sh this executes until "dies
here". The problem is that I can't use the subshell trick, since the
gettext.sh inclusion has to be done in the current shell (I checked,
tests will fail).

    #!/bin/sh
    (. does-not-exist.sh)
    echo "A subshell made it! ret = $?"
    . does-not-exist.sh
  # dies here
    echo "A real shell made it! ret = $?"

Is there some clever shellscript trick that I'm missing, or will I
have to resort to modifying the file at `make' time for this to work
everywhere?

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/2] Add infrastructure for translating Git with gettext
  2010-06-14 20:26   ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
@ 2010-06-14 21:56     ` Thomas Rast
  2010-06-14 22:51       ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 16+ messages in thread
From: Thomas Rast @ 2010-06-14 21:56 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder

Ævar Arnfjörð Bjarmason wrote:
> It turns out that this doesn't actually work, and I can't find a
> workaround. In Bash and Solaris's /bin/sh this executes until "dies
> here". The problem is that I can't use the subshell trick, since the
> gettext.sh inclusion has to be done in the current shell (I checked,
> tests will fail).
> 
>     #!/bin/sh
>     (. does-not-exist.sh)
>     echo "A subshell made it! ret = $?"
>     . does-not-exist.sh
>   # dies here
>     echo "A real shell made it! ret = $?"
> 
> Is there some clever shellscript trick that I'm missing, or will I
> have to resort to modifying the file at `make' time for this to work
> everywhere?

Works for me in bash (4.0.35), but fails the way you say in dash (if
only I could figure out the option that tells me the version!).

This works however:

  type does-not-exist.sh 2>/dev/null && . does-not-exist.sh

I suspect it only works if the script is executable, as otherwise type
would not find it (but . would).  But at least on my system, it is.

-- 
Thomas Rast
trast@{inf,student}.ethz.ch

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 1/2] Add infrastructure for translating Git with gettext
  2010-06-14 21:56     ` Thomas Rast
@ 2010-06-14 22:51       ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-14 22:51 UTC (permalink / raw)
  To: Thomas Rast
  Cc: git, Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder

On Mon, Jun 14, 2010 at 21:56, Thomas Rast <trast@student.ethz.ch> wrote:
> Ævar Arnfjörð Bjarmason wrote:
>> It turns out that this doesn't actually work, and I can't find a
>> workaround. In Bash and Solaris's /bin/sh this executes until "dies
>> here". The problem is that I can't use the subshell trick, since the
>> gettext.sh inclusion has to be done in the current shell (I checked,
>> tests will fail).
>>
>>     #!/bin/sh
>>     (. does-not-exist.sh)
>>     echo "A subshell made it! ret = $?"
>>     . does-not-exist.sh
>>   # dies here
>>     echo "A real shell made it! ret = $?"
>>
>> Is there some clever shellscript trick that I'm missing, or will I
>> have to resort to modifying the file at `make' time for this to work
>> everywhere?
>
> Works for me in bash (4.0.35), but fails the way you say in dash (if
> only I could figure out the option that tells me the version!).
>
> This works however:
>
>  type does-not-exist.sh 2>/dev/null && . does-not-exist.sh

Thanks. That works, I've tested the following script:

    #!/bin/sh
    type meh.sh >/dev/null 2>&1 && . meh.sh && echo "Included meh.sh"
|| echo "Didn't include meh.sh"
    echo "I've made it!"
    type gettext.sh >/dev/null 2>&1 && . gettext.sh && echo "Included
gettext.sh" || echo "Didn't include gettext.sh"
    echo "I've made it!"

On the following:

    FreeBSD: /bin/sh, bash
    Solaris: /bin/sh, /usr/bin/ksh, bash,
    Debian: dash, bash

They all make it to the last "I've made it!".

> I suspect it only works if the script is executable, as otherwise type
> would not find it (but . would).  But at least on my system, it is.

With gettext.sh the assumption that it's +x seems valid, the
installation script for GNU gettext always gives it an executable bit.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v3 0/2] Gettext support for Git
  2010-06-14  5:00       ` Junio C Hamano
@ 2010-06-15  2:11         ` Ævar Arnfjörð Bjarmason
  2010-06-15  2:11         ` [PATCH v3 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
                           ` (4 subsequent siblings)
  5 siblings, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-15  2:11 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	John Wiegley, Graham Anderson, Thomas Rast,
	Ævar Arnfjörð Bjarmason

This patch series implements gettext support for git, making it
possible to localize it. This should really be v2, but PATCH 1/2 had a
trivial typo fix as v2, so I'm bumping the version of the whole series
to v3 to avoid dual v3/v2 confusion.

Changes since v1:

  * Fixed all the things that Junio pointed out:

    - Tests that are too fragile to run everywhere (depend on
      locale/OS/implementation combinations) are now skipped unless
      TEST_GIT_GETTEXT_EXHAUSTIVE=1 is set.

    - fantasy-locale / real-locale filenames now make sense with
      respect to their tests.

    - Use `! test' instead of `test_expect_failure test`

    - Use the `>foo' redirection style instead of `> foo'.

  * Don't use `. gettext.sh' but `type gettext.sh && . gettext.sh'.

    The former failed on dash and Solaris's ksh.

  * Now compatible with the SunOS gettext implmentation, this needed:

    - #include <locale.h> in gettext.c, which is a good idea
       anyway. GNU libintl.h included it indirectly.

    - Define a Shell fallback that uses gettext(1). SunOS doesn't have
      gettext.sh, but it has a gettext command that shellscipts should
      use. It also needed a custom eval_gettext() wrapper.

    - Updating of INSTALL and Makefile docs to mention this.

  * The Perl tests can now be run with TEST_GIT_GETTEXT_EXHAUSTIVE=1
    through the t0202-gettext-perl.sh wrapper. The original patch
    didn't set GIT_TEXTDOMAINDIR, and I always ran the test.pl script
    manually.

  * Updated commit messages to reflect the changes above, and fixed a
    typo or two.

The changes since v1 in diff -w --stat format:

 INSTALL                      |   10 ++++--
 Makefile                     |    4 +-
 gettext.c                    |    1 +
 git-sh-i18n.sh               |   36 ++++++++++++++++++----
 t/t0200-gettext.sh           |   68 ++++++++++++++++++++++++-----------------
 t/t0201-gettext-fallbacks.sh |   10 +++++-
 t/t0202-gettext-perl.sh      |    3 ++
 t/t0202/test.pl              |    4 +-
 8 files changed, 94 insertions(+), 42 deletions(-)

Ævar Arnfjörð Bjarmason (2):
  Add infrastructure for translating Git with gettext
  Add initial C, Shell and Perl gettext translations

 .gitignore                   |    2 +
 INSTALL                      |    8 ++
 Makefile                     |   69 +++++++++++++++++-
 config.mak.in                |    2 +
 configure.ac                 |   12 +++
 daemon.c                     |    3 +
 fast-import.c                |    3 +
 gettext.c                    |   22 ++++++
 gettext.h                    |   18 +++++
 git-pull.sh                  |   16 ++--
 git-send-email.perl          |    3 +-
 git-sh-i18n.sh               |   71 ++++++++++++++++++
 git.c                        |    3 +
 http-backend.c               |    3 +
 http-fetch.c                 |    3 +
 http-push.c                  |    3 +
 imap-send.c                  |    3 +
 perl/Git/I18N.pm             |   91 +++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 +++-
 po/.gitignore                |    1 +
 po/is.po                     |   70 ++++++++++++++++++
 shell.c                      |    3 +
 show-index.c                 |    3 +
 t/t0200-gettext.sh           |  166 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   13 +++
 t/t0200/test.perl            |   14 ++++
 t/t0200/test.sh              |   14 ++++
 t/t0201-gettext-fallbacks.sh |   50 +++++++++++++
 t/t0202-gettext-perl.sh      |   23 ++++++
 t/t0202/test.pl              |  104 ++++++++++++++++++++++++++
 t/test-lib.sh                |    2 +
 upload-pack.c                |    3 +
 wt-status.c                  |  107 ++++++++++++++-------------
 34 files changed, 860 insertions(+), 65 deletions(-)
 create mode 100644 gettext.c
 create mode 100644 gettext.h
 create mode 100644 git-sh-i18n.sh
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/.gitignore
 create mode 100644 po/is.po
 create mode 100755 t/t0200-gettext.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0201-gettext-fallbacks.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v3 1/2] Add infrastructure for translating Git with gettext
  2010-06-14  5:00       ` Junio C Hamano
  2010-06-15  2:11         ` [PATCH v3 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
@ 2010-06-15  2:11         ` Ævar Arnfjörð Bjarmason
  2010-06-15  2:11         ` [PATCH v3 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
                           ` (3 subsequent siblings)
  5 siblings, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-15  2:11 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	John Wiegley, Graham Anderson, Thomas Rast,
	Ævar Arnfjörð Bjarmason

All of the interface messages in Git core are currently hardcoded in
English. Change that by optionally enabling translation of the core C,
Shell and Perl programs via GNU or SunOS gettext. If you set the
appropriate LC_* variables Git will speak your language, provided that
someone has submitted a translation.

If gettext isn't available, or if Git is compiled with
NO_GETTEXT=YesPlease, then Git fall back on its previous behavior of
only speaking English. When using ./configure the autoconf script will
auto-detect if the gettext libraries are installed and act
appropriately.

With NO_GETTEXT=YesPlease gettext support will be #defined away for C
programs. For Shell and Perl programs we rely on the git message
catalog not being available. That's a reasonable assumption since then
the message catalog won't be installed on the system during make
install.

The gettext wrappers that are provided in the patch are only the bare
minimum required to begin translation work. In particular I haven't
added wrappers for the gettext functions that enable plural support,
or those that provide message context (msgctxt).

Those can be added later. The intent is to start with a small subset
and see what we need later, not to start with something that's
unnecessarily large right away.

Implementation and usage notes:

 * General:

   Gettext .mo files will be installed and looked for in the standard
   $(prefix)/share/locale path. GIT_TEXTDOMAINDIR can also be set to
   override that, but that's only intended to be used to test Git
   itself.

 * Perl:

   Perl code that wants to be localized should use the new Git::I18n
   module. It imports a __ function into the caller's package by
   default.

   Instead of using the high level Locale::TextDomain interface I've
   opted to use the low-level (equivalent to the C interface)
   Locale::Messages module, which Locale::TextDomain itself uses.

   Locale::TextDomain does a lot of redundant work we don't need, and
   some of it would potentially introduce bugs. It tries to set the
   $TEXTDOMAIN based on package of the caller, and has its own
   hardcoded paths where it'll search for messages.

   I found it easier just to completely avoid it rather than try to
   circumvent its behavior. In any case, this is an issue wholly
   internal Git::I18N. Its guts can be changed later if that's deemed
   necessary.

   See <AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LWnVd@mail.gmail.com>
   for a further elaboration on this topic.

 * Shell:

   Shell code that's to be localized should use the new git-sh-i18n
   library. It's just a wrapper for the system's gettext.sh.

   If gettext.sh isn't available we'll fall back gettext(1) if it's
   available. The latter is available without the former on Solaris,
   which has its own non-GNU gettext implementation. We also need to
   emulate eval_gettext() there.

   If neither are present we'll use a dumb printf(1) fall-through
   wrapper.

   I originally tried to detect if the system supported `echo -n' but
   I found this to be a waste of time. My benchmarks on Linux, Solaris
   and FreeBSD reveal that printf(1) is fast enough, especially since
   we aren't calling gettext() from within any tight loops, and
   unlikely to ever do so.

This series has been tested by me on Ubuntu 10.04, Debian testing,
FreeBSD 8.1 and SunOS 5.10, and by others on Mac OS X 10.6.3 (with
Xcode 3.2.2) and openSUSE Factory (11.3, milestone 7).

SunOS has its own non-GNU gettext implementation which this patch
supports, although that may change in the future if it turns out that
we need some GNU libintl features that SunOS doesn't provide.

This patch is based on work by Jeff Epler <jepler@unpythonic.net> who
did the initial Makefile / C work, and a lot of comments from the Git
mailing list, including Jonathan Nieder, Jakub Narebski, Johannes
Sixt, Peter Krefting, Junio C Hamano, Thomas Rast and others.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Tested-By: John Wiegley <johnw@newartisans.com>
Tested-by: Graham Anderson <graham.anderson@gmail.com>
---
 .gitignore                   |    2 +
 INSTALL                      |   12 ++++
 Makefile                     |   69 ++++++++++++++++++++-
 config.mak.in                |    2 +
 configure.ac                 |   12 ++++
 daemon.c                     |    3 +
 fast-import.c                |    3 +
 gettext.c                    |   22 +++++++
 gettext.h                    |   18 ++++++
 git-sh-i18n.sh               |   71 ++++++++++++++++++++++
 git.c                        |    3 +
 http-backend.c               |    3 +
 http-fetch.c                 |    3 +
 http-push.c                  |    3 +
 imap-send.c                  |    3 +
 perl/Git/I18N.pm             |   91 ++++++++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 ++++-
 po/.gitignore                |    1 +
 po/is.po                     |   47 ++++++++++++++
 shell.c                      |    3 +
 show-index.c                 |    3 +
 t/t0200-gettext.sh           |  137 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   13 ++++
 t/t0200/test.perl            |   14 ++++
 t/t0200/test.sh              |   14 ++++
 t/t0201-gettext-fallbacks.sh |   50 +++++++++++++++
 t/t0202-gettext-perl.sh      |   23 +++++++
 t/t0202/test.pl              |  104 ++++++++++++++++++++++++++++++++
 t/test-lib.sh                |    2 +
 upload-pack.c                |    3 +
 31 files changed, 747 insertions(+), 4 deletions(-)
 create mode 100644 gettext.c
 create mode 100644 gettext.h
 create mode 100644 git-sh-i18n.sh
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/.gitignore
 create mode 100644 po/is.po
 create mode 100755 t/t0200-gettext.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0201-gettext-fallbacks.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl

diff --git a/.gitignore b/.gitignore
index 14e2b6b..6c2b193 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,6 +125,7 @@
 /git-rm
 /git-send-email
 /git-send-pack
+/git-sh-i18n
 /git-sh-setup
 /git-shell
 /git-shortlog
@@ -204,3 +205,4 @@
 *.pdb
 /Debug/
 /Release/
+/share/
diff --git a/INSTALL b/INSTALL
index 61086ab..9c9a5fa 100644
--- a/INSTALL
+++ b/INSTALL
@@ -93,6 +93,18 @@ Issues of note:
 	  history graphically, and in git-gui.  If you don't want gitk or
 	  git-gui, you can use NO_TCLTK.
 
+	- A gettext library is used by default for localizing Git. The
+	  primary target is GNU libintl, but the Solaris gettext
+	  implementation also works.
+
+	  We need a gettext.h on the system for C code, gettext.sh (or
+	  Solaris gettext(1)) for shell scripts, and libintl-perl for Perl
+	  programs.
+
+	  Set NO_GETTEXT to disable localization support and make Git only
+	  use English. Under autoconf the configure script will do this
+	  automatically if it can't find libintl on the system.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
diff --git a/Makefile b/Makefile
index 5fa893c..b5297b6 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,15 @@ all::
 # Define NO_EXPAT if you do not have expat installed.  git-http-push is
 # not built, and you cannot push using http:// and https:// transports.
 #
+# Define NO_GETTEXT if you don't want to build with Git with gettext
+# support. Building it requires GNU libintl or another gettext
+# implementation, and additionally libintl-perl at runtime.
+#
+# Define NEEDS_LIBINTL if you haven't set NO_GETTEXT and your system
+# needs to be explicitly linked to -lintl. It's defined automatically
+# on platforms where we don't expect glibc (Linux, Hurd,
+# GNU/kFreeBSD), which includes libintl.
+#
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
@@ -272,6 +281,7 @@ mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
 sharedir = $(prefix)/share
+localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ifeq ($(prefix),/usr)
@@ -285,7 +295,7 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir
+export prefix bindir sharedir sysconfdir localedir
 
 CC = gcc
 AR = ar
@@ -297,6 +307,8 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
+XGETTEXT = xgettext
+MSGFMT = msgfmt
 
 export TCL_PATH TCLTK_PATH
 
@@ -358,6 +370,7 @@ SCRIPT_SH += git-web--browse.sh
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
 SCRIPT_LIB += git-sh-setup
+SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
@@ -523,6 +536,7 @@ LIB_H += userdiff.h
 LIB_H += utf8.h
 LIB_H += xdiff-interface.h
 LIB_H += xdiff/xdiff.h
+LIB_H += gettext.h
 
 LIB_OBJS += abspath.o
 LIB_OBJS += advice.o
@@ -564,6 +578,9 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+ifndef NO_GETTEXT
+LIB_OBJS += gettext.o
+endif
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -735,6 +752,14 @@ EXTLIBS =
 # Platform specific tweaks
 #
 
+# Platform specific defaults. Where we'd only like some feature on the
+# minority of systems, e.g. if linking to a library isn't needed
+# because its features are included in the GNU C library.
+ifndef NO_GETTEXT
+	# Systems that use GNU gettext and glibc are the exception
+	NEEDS_LIBINTL = YesPlease
+endif
+
 # We choose to avoid "if .. else if .. else .. endif endif"
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
@@ -743,11 +768,13 @@ ifeq ($(uname_S),Linux)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),UnixWare)
 	CC = cc
@@ -918,6 +945,7 @@ ifeq ($(uname_S),GNU)
 	NO_STRLCPY=YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),IRIX)
 	NO_SETENV = YesPlease
@@ -1387,6 +1415,14 @@ ifdef USE_NED_ALLOCATOR
        COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
 endif
 
+ifdef NO_GETTEXT
+	COMPAT_CFLAGS += -DNO_GETTEXT
+endif
+
+ifdef NEEDS_LIBINTL
+	EXTLIBS += -lintl
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1416,6 +1452,7 @@ ifndef V
 	QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
 	QUIET_GEN      = @echo '   ' GEN $@;
 	QUIET_LNCP     = @echo '   ' LN/CP $@;
+	QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
 	QUIET_SUBDIR0  = +@subdir=
 	QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
 			 $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1443,7 +1480,9 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
 prefix_SQ = $(subst ','\'',$(prefix))
+sharedir_SQ = $(subst ','\'',$(sharedir))
 
+LOCALEDIR_SQ = $(subst ','\'',$(localedir))
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
@@ -1492,7 +1531,7 @@ ifndef NO_TCLTK
 	$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
 ifndef NO_PERL
-	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
 endif
 ifndef NO_PYTHON
 	$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1537,6 +1576,7 @@ $(RM) $@ $@+ && \
 sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's|@@LOCALEDIR@@|$(LOCALEDIR_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
@@ -1872,6 +1912,21 @@ cscope:
 	$(RM) cscope*
 	$(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 
+pot:
+	$(XGETTEXT) --add-comments --keyword=_ --keyword=N_ --output=po/git.pot --language=C $(C_OBJ:o=c) t/t0200/test.c
+	$(XGETTEXT) --add-comments --join-existing --output=po/git.pot --language=Shell $(SCRIPT_SH) t/t0200/test.sh
+	$(XGETTEXT) --add-comments --join-existing --keyword=__ --output=po/git.pot --language=Perl $(SCRIPT_PERL) t/t0200/test.perl
+
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES))
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+share/locale/%/LC_MESSAGES/git.mo: po/%.po
+	@mkdir -p $(dir $@)
+	$(QUIET_MSGFMT)$(MSGFMT) -o $@ $<
+
 ### Detect prefix changes
 TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
@@ -1893,6 +1948,7 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
 	@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
 	@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+	@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1984,6 +2040,11 @@ install: all
 	$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
+ifndef NO_GETTEXT
+	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sharedir_SQ)/locale'
+	(cd share && tar cf - locale) | \
+		(cd '$(DESTDIR_SQ)$(sharedir_SQ)' && umask 022 && tar xof -)
+endif
 	$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 ifndef NO_PERL
 	$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
@@ -2131,6 +2192,10 @@ ifndef NO_TCLTK
 	$(MAKE) -C git-gui clean
 endif
 	$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
+ifndef NO_GETTEXT
+	$(RM) po/git.pot
+	$(RM) -r share/
+endif
 
 .PHONY: all install clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
diff --git a/config.mak.in b/config.mak.in
index 0d4b64d..c49072c 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -32,9 +32,11 @@ NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
 NO_LIBGEN_H=@NO_LIBGEN_H@
 HAVE_PATHS_H=@HAVE_PATHS_H@
+NO_GETTEXT=@NO_GETTEXT@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
 NEEDS_RESOLV=@NEEDS_RESOLV@
+NEEDS_LIBINTL=@NEEDS_LIBINTL@
 NEEDS_LIBGEN=@NEEDS_LIBGEN@
 NO_SYS_SELECT_H=@NO_SYS_SELECT_H@
 NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@
diff --git a/configure.ac b/configure.ac
index 71038fc..74879b4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -559,6 +559,12 @@ AC_CHECK_LIB([c], [basename],
 AC_SUBST(NEEDS_LIBGEN)
 test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
+AC_CHECK_LIB([c], [gettext],
+[NEEDS_LIBINTL=],
+[NEEDS_LIBINTL=YesPlease])
+AC_SUBST(NEEDS_LIBINTL)
+test -n "$NEEDS_LIBINTL" && LIBS="$LIBS -lintl"
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -730,6 +736,12 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# Define NO_GETTEXT if you don't have libintl.h
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
diff --git a/daemon.c b/daemon.c
index a90ab10..7f4691c 100644
--- a/daemon.c
+++ b/daemon.c
@@ -3,6 +3,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 #include <syslog.h>
 
@@ -974,6 +975,8 @@ int main(int argc, char **argv)
 	gid_t gid = 0;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	for (i = 1; i < argc; i++) {
diff --git a/fast-import.c b/fast-import.c
index 129a786..6947f7a 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -156,6 +156,7 @@ Format of STDIN stream:
 #include "csum-file.h"
 #include "quote.h"
 #include "exec_cmd.h"
+#include "gettext.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -2904,6 +2905,8 @@ int main(int argc, const char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(fast_import_usage);
 
diff --git a/gettext.c b/gettext.c
new file mode 100644
index 0000000..7ae5cae
--- /dev/null
+++ b/gettext.c
@@ -0,0 +1,22 @@
+#include "exec_cmd.h"
+#include <locale.h>
+#include <libintl.h>
+#include <stdlib.h>
+
+extern void git_setup_gettext(void) {
+	char *podir;
+	char *envdir = getenv("GIT_TEXTDOMAINDIR");
+
+	if (envdir) {
+		(void)bindtextdomain("git", envdir);
+	} else {
+		podir = (char *)system_path("share/locale");
+		if (!podir) return;
+		(void)bindtextdomain("git", podir);
+		free(podir);
+	}
+
+	(void)setlocale(LC_MESSAGES, "");
+	(void)setlocale(LC_CTYPE, "");
+	(void)textdomain("git");
+}
diff --git a/gettext.h b/gettext.h
new file mode 100644
index 0000000..e02939a
--- /dev/null
+++ b/gettext.h
@@ -0,0 +1,18 @@
+#ifndef GETTEXT_H
+#define GETTEXT_H
+
+#ifdef NO_GETTEXT
+static inline void git_setup_gettext(void) {}
+#else
+extern void git_setup_gettext(void);
+#endif
+
+#define N_(s) (s)
+#ifdef NO_GETTEXT
+#define _(s) (s)
+#else
+#include <libintl.h>
+#define _(s) gettext(s)
+#endif
+
+#endif
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
new file mode 100644
index 0000000..698a000
--- /dev/null
+++ b/git-sh-i18n.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+# This is Git's interface to gettext.sh. Use it right after
+# git-sh-setup as:
+#
+#   . git-sh-setup
+#   . git-sh-i18n
+#
+#   # For constant interface messages:
+#   gettext "A message for the user"; echo
+#
+#   # To interpolate variables:
+#   details="oh noes"
+#   eval_gettext "An error occured: \$details"; echo
+#
+# See "info '(gettext)sh'" for the full manual.
+
+# Export the TEXTDOMAIN* data that we need for Git
+TEXTDOMAIN=git
+export TEXTDOMAIN
+if [ -z "$GIT_TEXTDOMAINDIR" ]
+then
+	TEXTDOMAINDIR="@@LOCALEDIR@@"
+else
+	TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+fi
+export TEXTDOMAINDIR
+
+if test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && type gettext.sh >/dev/null 2>&1
+then
+	# This is GNU libintl's gettext.sh, we don't need to do anything
+	# else than setting up the environment and loading gettext.sh
+	GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
+	export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+	# Try to use libintl's gettext.sh, or fall back to English if we
+	# can't.
+	. gettext.sh
+elif test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && test "$(gettext -h 2>&1)" = "-h"
+then
+	# We don't have gettext.sh, but there's a gettext binary in our
+	# path. This is probably Solaris or something like it which has a
+	# gettext implementation that isn't GNU libintl.
+	GIT_INTERNAL_GETTEXT_SH_SCHEME=solaris
+	export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+	# Solaris has a gettext(1) but no eval_gettext(1)
+	eval_gettext () {
+		gettext_out=$(gettext "$1")
+		gettext_eval="printf '%s' \"$gettext_out\""
+		printf "%s" "`eval \"$gettext_eval\"`"
+	}
+else
+	# Since gettext.sh isn't available we'll have to define our own
+	# dummy pass-through functions.
+
+	# Tell our tests that we don't have the real gettext.sh
+	GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
+	export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+	gettext () {
+		printf "%s" "$1"
+	}
+
+	eval_gettext () {
+		gettext_eval="printf '%s' \"$1\""
+		printf "%s" "`eval \"$gettext_eval\"`"
+	}
+fi
diff --git a/git.c b/git.c
index 99f0363..d749eab 100644
--- a/git.c
+++ b/git.c
@@ -3,6 +3,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "run-command.h"
+#include "gettext.h"
 
 const char git_usage_string[] =
 	"git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n"
@@ -490,6 +491,8 @@ int main(int argc, const char **argv)
 	if (!cmd)
 		cmd = "git-help";
 
+	git_setup_gettext();
+
 	/*
 	 * "git-xxxx" is the same as "git xxxx", but we obviously:
 	 *
diff --git a/http-backend.c b/http-backend.c
index d1e83d0..b6d9bd5 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -6,6 +6,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "string-list.h"
+#include "gettext.h"
 
 static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
@@ -605,6 +606,8 @@ int main(int argc, char **argv)
 	char *cmd_arg = NULL;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	set_die_routine(die_webcgi);
 
diff --git a/http-fetch.c b/http-fetch.c
index 762c750..b889c36 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -2,6 +2,7 @@
 #include "exec_cmd.h"
 #include "http.h"
 #include "walker.h"
+#include "gettext.h"
 
 static const char http_fetch_usage[] = "git http-fetch "
 "[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url";
@@ -24,6 +25,8 @@ int main(int argc, const char **argv)
 	int get_verbosely = 0;
 	int get_recover = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	while (arg < argc && argv[arg][0] == '-') {
diff --git a/http-push.c b/http-push.c
index 415b1ab..ba0338c 100644
--- a/http-push.c
+++ b/http-push.c
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "list-objects.h"
 #include "sigchain.h"
+#include "gettext.h"
 
 #include <expat.h>
 
@@ -1791,6 +1792,8 @@ int main(int argc, char **argv)
 	struct remote *remote;
 	char *rewritten_url = NULL;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	repo = xcalloc(sizeof(*repo), 1);
diff --git a/imap-send.c b/imap-send.c
index 9d0097c..4f5f269 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -25,6 +25,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "run-command.h"
+#include "gettext.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #else
@@ -1535,6 +1536,8 @@ int main(int argc, char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(imap_send_usage);
 
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644
index 0000000..5918d68
--- /dev/null
+++ b/perl/Git/I18N.pm
@@ -0,0 +1,91 @@
+package Git::I18N;
+use 5.006002;
+use strict;
+use warnings;
+use Exporter;
+use base 'Exporter';
+
+our $VERSION = '0.01';
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+	our $TEXTDOMAIN = 'git';
+	our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+	require POSIX;
+	POSIX->import(qw(setlocale));
+	# Non-core prerequisite module
+	require Locale::Messages;
+	Locale::Messages->import(qw(:locale_h :libintl_h));
+
+	setlocale(LC_MESSAGES(), '');
+	setlocale(LC_CTYPE(), '');
+	textdomain($TEXTDOMAIN);
+	bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+	return;
+}
+
+BEGIN
+{
+	# Used by our test script to see if it should test fallbacks or
+	# not.
+	our $__HAS_LIBRARY = 1;
+
+	local $@;
+	eval { __bootstrap_locale_messages() };
+	if ($@) {
+		# Tell test.pl that we couldn't load the gettext library.
+		$Git::I18N::__HAS_LIBRARY = 0;
+
+		# Just a fall-through no-op
+		*__ = sub ($) { $_[0] };
+	} else {
+		*__ = \&Locale::Messages::gettext;
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+	use Git::I18N;
+
+	print __("Welcome to Git!\n");
+
+	printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
diff --git a/perl/Makefile b/perl/Makefile
index 4ab21d6..4e624ff 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -5,6 +5,7 @@ makfile:=perl.mak
 
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
 
 ifndef V
 	QUIET = @
@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	echo '	echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-	$(PERL_PATH) $< PREFIX='$(prefix_SQ)'
+	$(PERL_PATH) $< PREFIX='$(prefix_SQ)' --localedir='$(localedir_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index 0b9deca..456d45b 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
 use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
 
 sub MY::postamble {
 	return <<'MAKE_FRAG';
@@ -16,7 +24,10 @@ endif
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+my %pm = (
+	'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+	'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+);
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
@@ -33,6 +44,7 @@ WriteMakefile(
 	NAME            => 'Git',
 	VERSION_FROM    => 'Git.pm',
 	PM		=> \%pm,
+	PM_FILTER	=> qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
 	MAKEFILE	=> 'perl.mak',
 	INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..221000e
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1 @@
+/*.pot
diff --git a/po/is.po b/po/is.po
new file mode 100644
index 0000000..95739f1
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,47 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: t/t0200/test.c:4
+msgid "See git help COMMAND for more information on a specific command."
+msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:9
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:12
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
diff --git a/shell.c b/shell.c
index e4864e0..ba27c6b 100644
--- a/shell.c
+++ b/shell.c
@@ -2,6 +2,7 @@
 #include "quote.h"
 #include "exec_cmd.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 static int do_generic_cmd(const char *me, char *arg)
 {
@@ -51,6 +52,8 @@ int main(int argc, char **argv)
 	struct commands *cmd;
 	int devnull_fd;
 
+	git_setup_gettext();
+
 	/*
 	 * Always open file descriptors 0/1/2 to avoid clobbering files
 	 * in die().  It also avoids not messing up when the pipes are
diff --git a/show-index.c b/show-index.c
index 4c0ac13..c2f5448 100644
--- a/show-index.c
+++ b/show-index.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pack.h"
+#include "gettext.h"
 
 static const char show_index_usage[] =
 "git show-index < <packed archive index>";
@@ -11,6 +12,8 @@ int main(int argc, char **argv)
 	unsigned int version;
 	static unsigned int top_index[256];
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(show_index_usage);
 	if (fread(top_index, 2 * 4, 1, stdin) != 1)
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
new file mode 100755
index 0000000..cef1209
--- /dev/null
+++ b/t/t0200-gettext.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+
+test_description='Gettext support for Git'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_PO_PATH="$GIT_EXEC_PATH/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+    test $TEXTDOMAIN = "git"
+'
+
+# Basic xgettext() extraction tests on po/*.po. Doesn't need gettext support
+test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '
+    ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
+    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect &&
+    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '
+    ! grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+    ! grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+if test_have_prereq GETTEXT; then
+	test_expect_success 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+    test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+	test_expect_success 'sanity: Icelandic locale was compiled' '
+    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+else
+	test_expect_success "sanity: \$TEXTDOMAINDIR doesn't exists with NO_GETTEXT=YesPlease" '
+    ! test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+fi
+
+# We can go no further without actual gettext support
+if ! test_have_prereq GETTEXT || test $GIT_INTERNAL_GETTEXT_SH_SCHEME = "fallthrough"; then
+	say "Skipping the rest of the gettext tests, Git was compiled with NO_GETTEXT=YesPlease"
+	test_done
+fi
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success 'sanity: gettext("") metadata is OK' '
+    # Return value may be non-zero
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" >zero-expect &&
+    grep "Project-Id-Version: Git" zero-expect &&
+    grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+    grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+    grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success 'sanity: gettext(unknown) is passed through' '
+    printf "This is not a translation string"  >expect &&
+    gettext "This is not a translation string" >actual &&
+    eval_gettext "This is not a translation string" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success 'xgettext: C extraction of _() and N_() strings' '
+    printf "TILRAUN: C tilraunastrengur" >expect &&
+    printf "\n" >>expect &&
+    printf "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun." >>expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string" >actual &&
+    printf "\n" >>actual &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "See git help COMMAND for more information on a specific command." >>actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: C extraction with %s' '
+    printf "TILRAUN: C tilraunastrengur %%s" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string %s" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success 'xgettext: Shell extraction' '
+    printf "TILRAUN: Skeljartilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Shell test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Shell extraction with $variable' '
+    printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 variable="a var i able" eval_gettext "TEST: A Shell test \$variable" >x-actual &&
+    test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success 'xgettext: Perl extraction' '
+    printf "TILRAUN: Perl tilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Perl extraction with %s' '
+    printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test variable %s" >actual &&
+    test_cmp expect actual
+'
+
+if test -n "$TEST_GIT_GETTEXT_EXHAUSTIVE"
+then
+	# These tests are locale sensitive. They're very likely to fail in
+	# the wild.
+	test_expect_success 'sanity: No gettext("") data for fantasy locale' '
+	    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" >fantasy-locale &&
+	    ! test -s fantasy-locale
+	'
+
+	test_expect_success 'sanity: Some gettext("") data for real locale' '
+	    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" >real-locale &&
+	    test -s real-locale
+	'
+	test_done
+else
+	say 'Skipping unportable gettext("") tests, set TEST_GIT_GETTEXT_EXHAUSTIVE=1 to enable'
+	test_done
+fi
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644
index 0000000..93373b3
--- /dev/null
+++ b/t/t0200/test.c
@@ -0,0 +1,13 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+	N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	puts(_("TEST: A C test string"));
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: A C test string %s"), "variable");
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644
index 0000000..36fba34
--- /dev/null
+++ b/t/t0200/test.perl
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644
index 0000000..022d607
--- /dev/null
+++ b/t/t0200/test.sh
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
new file mode 100755
index 0000000..ea1ca9d
--- /dev/null
+++ b/t/t0201-gettext-fallbacks.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='Gettext Shell fallbacks'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+
+export GIT_TEXTDOMAINDIR GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+    test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' '
+    test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "fallthrough"
+'
+
+test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
+    printf "test" >expect &&
+    gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "test more words" >expect &&
+    gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' '
+    printf "test" >expect &&
+    eval_gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "test more words" >expect &&
+    eval_gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' '
+    printf "test YesPlease" >expect &&
+    eval_gettext "test \$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" >actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755
index 0000000..3a37b23
--- /dev/null
+++ b/t/t0202-gettext-perl.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='Perl gettext interface (Git::I18N)'
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+export GIT_TEXTDOMAINDIR
+
+if ! test_have_prereq PERL; then
+	say 'skipping perl interface tests, perl not available'
+	test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+	say "Perl Test::More unavailable, skipping test"
+	test_done
+}
+
+test_external_without_stderr \
+    'Perl Git::I18N API' \
+    "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644
index 0000000..a952fb2
--- /dev/null
+++ b/t/t0202/test.pl
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+use 5.006002;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use warnings;
+use strict;
+use Test::More tests => 9;
+use Git::I18N;
+use POSIX qw(:locale_h);
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N version $Git::I18N::VERSION with " .
+	 ($has_gettext_library
+	  ? "Locale::Messages version $Locale::Messages::VERSION"
+	  : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+ok($Git::I18N::VERSION, 'sanity: Git::I18N defines a $VERSION');
+{
+	my $exports = @Git::I18N::EXPORT;
+	ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+	# Add prototypes here when modifying the public interface to add
+	# more gettext wrapper functions.
+	my %prototypes = (qw(
+		__	$
+    ));
+	while (my ($sub, $proto) = each %prototypes) {
+		is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+	}
+}
+
+# Test basic passthrough in the C locale
+{
+	local $ENV{LANGUAGE} = 'C';
+	local $ENV{LC_ALL}   = 'C';
+	local $ENV{LANG} = 'C';
+
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+    unless ($ENV{TEST_GIT_GETTEXT_EXHAUSTIVE}) {
+        # Can't reliably test __() with a non-C locales because the
+        # required locales may not be installed on the system.
+        #
+        # We test for these anyway as part of the shell
+        # tests. Skipping these here will eliminate failures on odd
+        # platforms with incomplete locale data.
+
+        skip "Set TEST_GIT_GETTEXT_EXHAUSTIVE=1 to enable exhaustive Git::I18N locale tests", 2;
+    }
+
+	my $test = sub {
+		my ($got, $expect, $msg, $locale) = @_;
+		# Maybe this system doesn't have the locale we're trying to
+		# test.
+		my $locale_ok = setlocale(LC_ALL, $locale);
+		is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+	};
+
+	my $env_C = sub {
+		$ENV{LANGUAGE} = 'C';
+		$ENV{LC_ALL}   = 'C';
+	};
+
+	my $env_is = sub {
+		$ENV{LANGUAGE} = 'is';
+		$ENV{LC_ALL}   = 'is_IS.UTF-8';
+	};
+
+	# Translation's the same as the original
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	if ($has_gettext_library) {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "With", 'C');
+		}
+
+		{
+			my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+			local %ENV; $env_is->();
+			$test->($got, $expect, "With", 'is_IS.UTF-8');
+		}
+	} else {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "Without", 'C');
+		}
+
+		{
+			local %ENV; $env_is->();
+			$test->($got, $expect, "Without", 'is');
+		}
+	}
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 454880a..ae63316 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -37,6 +37,7 @@ ORIGINAL_TERM=$TERM
 # For repeatability, reset the environment to known value.
 LANG=C
 LC_ALL=C
+LANGUAGE=C
 PAGER=cat
 TZ=UTC
 TERM=dumb
@@ -845,6 +846,7 @@ esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # test whether the filesystem supports symbolic links
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
diff --git a/upload-pack.c b/upload-pack.c
index dc464d7..ece9a4b 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -10,6 +10,7 @@
 #include "revision.h"
 #include "list-objects.h"
 #include "run-command.h"
+#include "gettext.h"
 
 static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -686,6 +687,8 @@ int main(int argc, char **argv)
 	int i;
 	int strict = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	read_replace_refs = 0;
 
-- 
1.7.1.251.g92a7

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v3 2/2] Add initial C, Shell and Perl gettext translations
  2010-06-14  5:00       ` Junio C Hamano
  2010-06-15  2:11         ` [PATCH v3 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
  2010-06-15  2:11         ` [PATCH v3 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
@ 2010-06-15  2:11         ` Ævar Arnfjörð Bjarmason
  2010-06-15 19:33         ` [PATCH v4 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
                           ` (2 subsequent siblings)
  5 siblings, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-15  2:11 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	John Wiegley, Graham Anderson, Thomas Rast,
	Ævar Arnfjörð Bjarmason

Change the git status, git pull, and git send-email commands to have
at least one translatable string. Each command uses a different core
language, so this makes a good example of how C, Shell and Perl
programs can be translated using gettext.

Since this introduces translation into the real Git tools more tests
can be added to check if they translations actually work for real core
tools.

The new tests are only run under TEST_GIT_I18N_EXHAUSTIVE=1, since
they might fail if the host OS doesn't provide the relevant locale
files.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-pull.sh         |   16 ++++---
 git-send-email.perl |    3 +-
 po/is.po            |   23 +++++++++++
 t/t0200-gettext.sh  |   29 ++++++++++++++
 wt-status.c         |  107 ++++++++++++++++++++++++++-------------------------
 5 files changed, 117 insertions(+), 61 deletions(-)

diff --git a/git-pull.sh b/git-pull.sh
index a09a44e..388406f 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -9,6 +9,7 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 . git-sh-setup
+. git-sh-i18n
 set_reflog_action "pull $*"
 require_work_tree
 cd_to_toplevel
@@ -125,8 +126,8 @@ error_on_no_merge_candidates () {
 	do
 		case "$opt" in
 		-t|--t|--ta|--tag|--tags)
-			echo "Fetching tags only, you probably meant:"
-			echo "  git fetch --tags"
+			gettext "Fetching tags only, you probably meant:"; echo
+			gettext "  git fetch --tags"; echo
 			exit 1
 		esac
 	done
@@ -158,11 +159,12 @@ error_on_no_merge_candidates () {
 		echo "a branch. Because this is not the default configured remote"
 		echo "for your current branch, you must specify a branch on the command line."
 	elif [ -z "$curr_branch" ]; then
-		echo "You are not currently on a branch, so I cannot use any"
-		echo "'branch.<branchname>.merge' in your configuration file."
-		echo "Please specify which remote branch you want to use on the command"
-		echo "line and try again (e.g. 'git pull <repository> <refspec>')."
-		echo "See git-pull(1) for details."
+		gettext "You are not currently on a branch, so I cannot use any
+'branch.<branchname>.merge' in your configuration file.
+Please specify which remote branch you want to use on the command
+line and try again (e.g. 'git pull <repository> <refspec>').
+See git-pull(1) for details.";
+		echo
 	elif [ -z "$upstream" ]; then
 		echo "You asked me to pull without telling me which branch you"
 		echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in"
diff --git a/git-send-email.perl b/git-send-email.perl
index 111c981..4977fdf 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -26,6 +26,7 @@ use Term::ANSIColor;
 use File::Temp qw/ tempdir tempfile /;
 use Error qw(:try);
 use Git;
+use Git::I18N;
 
 Getopt::Long::Configure qw/ pass_through /;
 
@@ -674,7 +675,7 @@ if (!defined $sender) {
 	$sender = $repoauthor || $repocommitter || '';
 	$sender = ask("Who should the emails appear to be from? [$sender] ",
 	              default => $sender);
-	print "Emails will be sent from: ", $sender, "\n";
+	printf __("Emails will be sent from: %s\n"), $sender;
 	$prompting++;
 }
 
diff --git a/po/is.po b/po/is.po
index 95739f1..8e4f8c5 100644
--- a/po/is.po
+++ b/po/is.po
@@ -13,6 +13,20 @@ msgstr ""
 msgid "See git help COMMAND for more information on a specific command."
 msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun."
 
+#: wt-status.c:63 wt-status.c:79 wt-status.c:98 wt-status.c:110
+#: wt-status.c:622
+msgid "On branch "
+msgstr "Á greininni "
+
+#: wt-status.c:629
+msgid "Not currently on any branch."
+msgstr "Ekki á neinni grein."
+
+#: wt-status.c:663
+#, c-format
+msgid "# No changes\n"
+msgstr "# Engar breytingar\n"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.c:9
 msgid "TEST: A C test string"
@@ -24,6 +38,10 @@ msgstr "TILRAUN: C tilraunastrengur"
 msgid "TEST: A C test string %s"
 msgstr "TILRAUN: C tilraunastrengur %s"
 
+#: git-pull.sh:124
+msgid "Fetching tags only, you probably meant:"
+msgstr "Næ aðeins í tögg, þú áttir líkast til við:"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.sh:8
 msgid "TEST: A Shell test string"
@@ -35,6 +53,11 @@ msgstr "TILRAUN: Skeljartilraunastrengur"
 msgid "TEST: A Shell test $variable"
 msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
 
+#: git-send-email.perl:678
+#, perl-format
+msgid "Emails will be sent from: %s\n"
+msgstr "Póstarnir verða sendir frá: %s\n"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.perl:8
 msgid "TEST: A Perl test string"
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
index cef1209..b29f693 100755
--- a/t/t0200-gettext.sh
+++ b/t/t0200-gettext.sh
@@ -130,6 +130,35 @@ then
 	    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" >real-locale &&
 	    test -s real-locale
 	'
+
+	# Actually execute some C and Shell code that uses Gettext
+	test_expect_success 'C: git-status reads our message catalog ' '
+	    test_commit "some-file" &&
+	    git checkout -b test/gettext &&
+	    LANGUAGE=C LC_ALL=C git status | grep test/gettext > expect &&
+	    echo "# On branch test/gettext" > actual &&
+	    test_cmp expect actual &&
+
+	    LANGUAGE=is LC_ALL=is_IS.UTF-8 git status | grep test/gettext > expect &&
+	    echo "# Á greininni test/gettext" > actual &&
+	    test_cmp expect actual
+	'
+
+	test_expect_success 'Shell: git-pull reads our message catalog' '
+	    # Repository for testing
+	    mkdir parent &&
+	    (cd parent && git init &&
+	     echo one >file && git add file &&
+	     git commit -m one) &&
+
+	    # Actual test
+	    (cd parent &&
+	    (LANGUAGE=C LC_ALL=C git pull --tags "../" >out 2>err);
+	    grep "Fetching tags only" err &&
+	    (LANGUAGE=is LC_ALL=is_IS.UTF-8 git pull --tags ../ >out 2>err || :) &&
+	    grep "Næ aðeins í" err)
+	'
+
 	test_done
 else
 	say 'Skipping unportable gettext("") tests, set TEST_GIT_GETTEXT_EXHAUSTIVE=1 to enable'
diff --git a/wt-status.c b/wt-status.c
index 14e0acc..484a866 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -9,6 +9,7 @@
 #include "quote.h"
 #include "run-command.h"
 #include "remote.h"
+#include "gettext.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -49,16 +50,16 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Unmerged paths:");
+	color_fprintf_ln(s->fp, c, _("# Unmerged paths:"));
 	if (!advice_status_hints)
 		return;
 	if (s->in_merge)
 		;
 	else if (!s->is_initial)
-		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+		color_fprintf_ln(s->fp, c, _("#   (use \"git reset %s <file>...\" to unstage)"), s->reference);
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-	color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git rm --cached <file>...\" to unstage)"));
+	color_fprintf_ln(s->fp, c, _("#   (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -66,15 +67,15 @@ static void wt_status_print_cached_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+	color_fprintf_ln(s->fp, c, _("# Changes to be committed:"));
 	if (!advice_status_hints)
 		return;
 	if (s->in_merge)
 		; /* NEEDSWORK: use "git reset --unresolve"??? */
 	else if (!s->is_initial)
-		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+		color_fprintf_ln(s->fp, c, _("#   (use \"git reset %s <file>...\" to unstage)"), s->reference);
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git rm --cached <file>...\" to unstage)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -84,16 +85,16 @@ static void wt_status_print_dirty_header(struct wt_status *s,
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Changed but not updated:");
+	color_fprintf_ln(s->fp, c, _("# Changed but not updated:"));
 	if (!advice_status_hints)
 		return;
 	if (!has_deleted)
-		color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git add <file>...\" to update what will be committed)"));
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
-	color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git add/rm <file>...\" to update what will be committed)"));
+	color_fprintf_ln(s->fp, c, _("#   (use \"git checkout -- <file>...\" to discard changes in working directory)"));
 	if (has_dirty_submodules)
-		color_fprintf_ln(s->fp, c, "#   (commit or discard the untracked or modified content in submodules)");
+		color_fprintf_ln(s->fp, c, _("#   (commit or discard the untracked or modified content in submodules)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -102,10 +103,10 @@ static void wt_status_print_other_header(struct wt_status *s,
 					 const char *how)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
-	color_fprintf_ln(s->fp, c, "# %s files:", what);
+	color_fprintf_ln(s->fp, c, _("# %s files:"), what);
 	if (!advice_status_hints)
 		return;
-	color_fprintf_ln(s->fp, c, "#   (use \"git %s <file>...\" to include in what will be committed)", how);
+	color_fprintf_ln(s->fp, c, _("#   (use \"git %s <file>...\" to include in what will be committed)"), how);
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -122,20 +123,20 @@ static void wt_status_print_unmerged_data(struct wt_status *s,
 	const char *c = color(WT_STATUS_UNMERGED, s);
 	struct wt_status_change_data *d = it->util;
 	struct strbuf onebuf = STRBUF_INIT;
-	const char *one, *how = "bug";
+	const char *one, *how = _("bug");
 
 	one = quote_path(it->string, -1, &onebuf, s->prefix);
 	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
 	switch (d->stagemask) {
-	case 1: how = "both deleted:"; break;
-	case 2: how = "added by us:"; break;
-	case 3: how = "deleted by them:"; break;
-	case 4: how = "added by them:"; break;
-	case 5: how = "deleted by us:"; break;
-	case 6: how = "both added:"; break;
-	case 7: how = "both modified:"; break;
+	case 1: how = _("both deleted:"); break;
+	case 2: how = _("added by us:"); break;
+	case 3: how = _("deleted by them:"); break;
+	case 4: how = _("added by them:"); break;
+	case 5: how = _("deleted by us:"); break;
+	case 6: how = _("both added:"); break;
+	case 7: how = _("both modified:"); break;
 	}
-	color_fprintf(s->fp, c, "%-20s%s\n", how, one);
+	color_fprintf(s->fp, c, _("%-20s%s\n"), how, one);
 	strbuf_release(&onebuf);
 }
 
@@ -163,11 +164,11 @@ static void wt_status_print_change_data(struct wt_status *s,
 		if (d->new_submodule_commits || d->dirty_submodule) {
 			strbuf_addstr(&extra, " (");
 			if (d->new_submodule_commits)
-				strbuf_addf(&extra, "new commits, ");
+				strbuf_addf(&extra, _("new commits, "));
 			if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
-				strbuf_addf(&extra, "modified content, ");
+				strbuf_addf(&extra, _("modified content, "));
 			if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
-				strbuf_addf(&extra, "untracked content, ");
+				strbuf_addf(&extra, _("untracked content, "));
 			strbuf_setlen(&extra, extra.len - 2);
 			strbuf_addch(&extra, ')');
 		}
@@ -178,34 +179,34 @@ static void wt_status_print_change_data(struct wt_status *s,
 	one = quote_path(one_name, -1, &onebuf, s->prefix);
 	two = quote_path(two_name, -1, &twobuf, s->prefix);
 
-	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), _("#\t"));
 	switch (status) {
 	case DIFF_STATUS_ADDED:
-		color_fprintf(s->fp, c, "new file:   %s", one);
+		color_fprintf(s->fp, c, _("new file:   %s"), one);
 		break;
 	case DIFF_STATUS_COPIED:
-		color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
+		color_fprintf(s->fp, c, _("copied:     %s -> %s"), one, two);
 		break;
 	case DIFF_STATUS_DELETED:
-		color_fprintf(s->fp, c, "deleted:    %s", one);
+		color_fprintf(s->fp, c, _("deleted:    %s"), one);
 		break;
 	case DIFF_STATUS_MODIFIED:
-		color_fprintf(s->fp, c, "modified:   %s", one);
+		color_fprintf(s->fp, c, _("modified:   %s"), one);
 		break;
 	case DIFF_STATUS_RENAMED:
-		color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
+		color_fprintf(s->fp, c, _("renamed:    %s -> %s"), one, two);
 		break;
 	case DIFF_STATUS_TYPE_CHANGED:
-		color_fprintf(s->fp, c, "typechange: %s", one);
+		color_fprintf(s->fp, c, _("typechange: %s"), one);
 		break;
 	case DIFF_STATUS_UNKNOWN:
-		color_fprintf(s->fp, c, "unknown:    %s", one);
+		color_fprintf(s->fp, c, _("unknown:    %s"), one);
 		break;
 	case DIFF_STATUS_UNMERGED:
-		color_fprintf(s->fp, c, "unmerged:   %s", one);
+		color_fprintf(s->fp, c, _("unmerged:   %s"), one);
 		break;
 	default:
-		die("bug: unhandled diff status %c", status);
+		die(_("bug: unhandled diff status %c"), status);
 	}
 	if (extra.len) {
 		color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
@@ -618,14 +619,14 @@ void wt_status_print(struct wt_status *s)
 	const char *branch_color = color(WT_STATUS_HEADER, s);
 
 	if (s->branch) {
-		const char *on_what = "On branch ";
+		const char *on_what = _("On branch ");
 		const char *branch_name = s->branch;
 		if (!prefixcmp(branch_name, "refs/heads/"))
 			branch_name += 11;
 		else if (!strcmp(branch_name, "HEAD")) {
 			branch_name = "";
 			branch_color = color(WT_STATUS_NOBRANCH, s);
-			on_what = "Not currently on any branch.";
+			on_what = _("Not currently on any branch.");
 		}
 		color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
 		color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
@@ -635,7 +636,7 @@ void wt_status_print(struct wt_status *s)
 
 	if (s->is_initial) {
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
-		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _("# Initial commit"));
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 	}
 
@@ -647,38 +648,38 @@ void wt_status_print(struct wt_status *s)
 		wt_status_print_submodule_summary(s, 1);  /* unstaged */
 	}
 	if (s->show_untracked_files) {
-		wt_status_print_other(s, &s->untracked, "Untracked", "add");
+		wt_status_print_other(s, &s->untracked, _("Untracked"), _("add"));
 		if (s->show_ignored_files)
-			wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
+			wt_status_print_other(s, &s->ignored, _("Ignored"), _("add -f"));
 	} else if (s->commitable)
-		fprintf(s->fp, "# Untracked files not listed%s\n",
+		fprintf(s->fp, _("# Untracked files not listed%s\n"),
 			advice_status_hints
-			? " (use -u option to show untracked files)" : "");
+			? _(" (use -u option to show untracked files)") : "");
 
 	if (s->verbose)
 		wt_status_print_verbose(s);
 	if (!s->commitable) {
 		if (s->amend)
-			fprintf(s->fp, "# No changes\n");
+			fprintf(s->fp, _("# No changes\n"));
 		else if (s->nowarn)
 			; /* nothing */
 		else if (s->workdir_dirty)
-			printf("no changes added to commit%s\n",
+			printf(_("no changes added to commit%s\n"),
 				advice_status_hints
-				? " (use \"git add\" and/or \"git commit -a\")" : "");
+				? _(" (use \"git add\" and/or \"git commit -a\")") : "");
 		else if (s->untracked.nr)
-			printf("nothing added to commit but untracked files present%s\n",
+			printf(_("nothing added to commit but untracked files present%s\n"),
 				advice_status_hints
-				? " (use \"git add\" to track)" : "");
+				? _(" (use \"git add\" to track)") : "");
 		else if (s->is_initial)
 			printf("nothing to commit%s\n", advice_status_hints
-				? " (create/copy files and use \"git add\" to track)" : "");
+				? _(" (create/copy files and use \"git add\" to track)") : "");
 		else if (!s->show_untracked_files)
-			printf("nothing to commit%s\n", advice_status_hints
-				? " (use -u to show untracked files)" : "");
+			printf(_("nothing to commit%s\n"), advice_status_hints
+				? _(" (use -u to show untracked files)") : "");
 		else
-			printf("nothing to commit%s\n", advice_status_hints
-				? " (working directory clean)" : "");
+			printf(_("nothing to commit%s\n"), advice_status_hints
+				? _(" (working directory clean)") : "");
 	}
 }
 
-- 
1.7.1.251.g92a7

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v4 0/2] Gettext support for Git
  2010-06-14  5:00       ` Junio C Hamano
                           ` (2 preceding siblings ...)
  2010-06-15  2:11         ` [PATCH v3 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
@ 2010-06-15 19:33         ` Ævar Arnfjörð Bjarmason
  2010-06-15 19:33         ` [PATCH v4 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
  2010-06-15 19:33         ` [PATCH v4 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
  5 siblings, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-15 19:33 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	John Wiegley, Graham Anderson, Thomas Rast,
	Ævar Arnfjörð Bjarmason

This patch series implements gettext support for git, making it
possible to localize it.

Changes since v3:

  * Skip tests that depend on is_IS locale unless
    TEST_GIT_GETTEXT_EXHAUSTIVE=1 is set.

    All the systems I'd tested on previously (FreeBSD, Solaris, Mac OS
    X, openSUSE, Debian, Ubuntu) had an Icelandic locale
    installed. Skip tests that depend on being able to set the locale.

    I've now added a bare-bones Debian system to my test setup. It
    failed on the same tests that Junio failed on.

Here's the git diff -w --stat since v3:

    t/t0200-gettext.sh |   16 ++++++++--------
    1 files changed, 8 insertions(+), 8 deletions(-)

Ævar Arnfjörð Bjarmason (2):
  Add infrastructure for translating Git with gettext
  Add initial C, Shell and Perl gettext translations

 .gitignore                   |    2 +
 INSTALL                      |   12 +++
 Makefile                     |   69 +++++++++++++++++-
 config.mak.in                |    2 +
 configure.ac                 |   12 +++
 daemon.c                     |    3 +
 fast-import.c                |    3 +
 gettext.c                    |   22 ++++++
 gettext.h                    |   18 +++++
 git-pull.sh                  |   16 ++--
 git-send-email.perl          |    3 +-
 git-sh-i18n.sh               |   71 ++++++++++++++++++
 git.c                        |    3 +
 http-backend.c               |    3 +
 http-fetch.c                 |    3 +
 http-push.c                  |    3 +
 imap-send.c                  |    3 +
 perl/Git/I18N.pm             |   91 +++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 +++-
 po/.gitignore                |    1 +
 po/is.po                     |   70 ++++++++++++++++++
 shell.c                      |    3 +
 show-index.c                 |    3 +
 t/t0200-gettext.sh           |  166 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   13 +++
 t/t0200/test.perl            |   14 ++++
 t/t0200/test.sh              |   14 ++++
 t/t0201-gettext-fallbacks.sh |   50 +++++++++++++
 t/t0202-gettext-perl.sh      |   23 ++++++
 t/t0202/test.pl              |  104 ++++++++++++++++++++++++++
 t/test-lib.sh                |    2 +
 upload-pack.c                |    3 +
 wt-status.c                  |  107 ++++++++++++++-------------
 34 files changed, 864 insertions(+), 65 deletions(-)
 create mode 100644 gettext.c
 create mode 100644 gettext.h
 create mode 100644 git-sh-i18n.sh
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/.gitignore
 create mode 100644 po/is.po
 create mode 100755 t/t0200-gettext.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0201-gettext-fallbacks.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH v4 1/2] Add infrastructure for translating Git with gettext
  2010-06-14  5:00       ` Junio C Hamano
                           ` (3 preceding siblings ...)
  2010-06-15 19:33         ` [PATCH v4 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
@ 2010-06-15 19:33         ` Ævar Arnfjörð Bjarmason
  2010-06-15 19:33         ` [PATCH v4 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
  5 siblings, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-15 19:33 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	John Wiegley, Graham Anderson, Thomas Rast,
	Ævar Arnfjörð Bjarmason

All of the interface messages in Git core are currently hardcoded in
English. Change that by optionally enabling translation of the core C,
Shell and Perl programs via GNU or SunOS gettext. If you set the
appropriate LC_* variables Git will speak your language, provided that
someone has submitted a translation.

If gettext isn't available, or if Git is compiled with
NO_GETTEXT=YesPlease, then Git fall back on its previous behavior of
only speaking English. When using ./configure the autoconf script will
auto-detect if the gettext libraries are installed and act
appropriately.

With NO_GETTEXT=YesPlease gettext support will be #defined away for C
programs. For Shell and Perl programs we rely on the git message
catalog not being available. That's a reasonable assumption since then
the message catalog won't be installed on the system during make
install.

The gettext wrappers that are provided in the patch are only the bare
minimum required to begin translation work. In particular I haven't
added wrappers for the gettext functions that enable plural support,
or those that provide message context (msgctxt).

Those can be added later. The intent is to start with a small subset
and see what we need later, not to start with something that's
unnecessarily large right away.

Implementation and usage notes:

 * General:

   Gettext .mo files will be installed and looked for in the standard
   $(prefix)/share/locale path. GIT_TEXTDOMAINDIR can also be set to
   override that, but that's only intended to be used to test Git
   itself.

 * Perl:

   Perl code that wants to be localized should use the new Git::I18n
   module. It imports a __ function into the caller's package by
   default.

   Instead of using the high level Locale::TextDomain interface I've
   opted to use the low-level (equivalent to the C interface)
   Locale::Messages module, which Locale::TextDomain itself uses.

   Locale::TextDomain does a lot of redundant work we don't need, and
   some of it would potentially introduce bugs. It tries to set the
   $TEXTDOMAIN based on package of the caller, and has its own
   hardcoded paths where it'll search for messages.

   I found it easier just to completely avoid it rather than try to
   circumvent its behavior. In any case, this is an issue wholly
   internal Git::I18N. Its guts can be changed later if that's deemed
   necessary.

   See <AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LWnVd@mail.gmail.com>
   for a further elaboration on this topic.

 * Shell:

   Shell code that's to be localized should use the new git-sh-i18n
   library. It's just a wrapper for the system's gettext.sh.

   If gettext.sh isn't available we'll fall back gettext(1) if it's
   available. The latter is available without the former on Solaris,
   which has its own non-GNU gettext implementation. We also need to
   emulate eval_gettext() there.

   If neither are present we'll use a dumb printf(1) fall-through
   wrapper.

   I originally tried to detect if the system supported `echo -n' but
   I found this to be a waste of time. My benchmarks on Linux, Solaris
   and FreeBSD reveal that printf(1) is fast enough, especially since
   we aren't calling gettext() from within any tight loops, and
   unlikely to ever do so.

This series has been tested by me on Ubuntu 10.04, Debian testing,
FreeBSD 8.1 and SunOS 5.10, and by others on Mac OS X 10.6.3 (with
Xcode 3.2.2) and openSUSE Factory (11.3, milestone 7).

SunOS has its own non-GNU gettext implementation which this patch
supports, although that may change in the future if it turns out that
we need some GNU libintl features that SunOS doesn't provide.

This patch is based on work by Jeff Epler <jepler@unpythonic.net> who
did the initial Makefile / C work, and a lot of comments from the Git
mailing list, including Jonathan Nieder, Jakub Narebski, Johannes
Sixt, Peter Krefting, Junio C Hamano, Thomas Rast and others.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Tested-By: John Wiegley <johnw@newartisans.com>
Tested-by: Graham Anderson <graham.anderson@gmail.com>
---
 .gitignore                   |    2 +
 INSTALL                      |   12 ++++
 Makefile                     |   69 ++++++++++++++++++++-
 config.mak.in                |    2 +
 configure.ac                 |   12 ++++
 daemon.c                     |    3 +
 fast-import.c                |    3 +
 gettext.c                    |   22 +++++++
 gettext.h                    |   18 ++++++
 git-sh-i18n.sh               |   71 +++++++++++++++++++++
 git.c                        |    3 +
 http-backend.c               |    3 +
 http-fetch.c                 |    3 +
 http-push.c                  |    3 +
 imap-send.c                  |    3 +
 perl/Git/I18N.pm             |   91 +++++++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 ++++-
 po/.gitignore                |    1 +
 po/is.po                     |   47 ++++++++++++++
 shell.c                      |    3 +
 show-index.c                 |    3 +
 t/t0200-gettext.sh           |  138 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   13 ++++
 t/t0200/test.perl            |   14 ++++
 t/t0200/test.sh              |   14 ++++
 t/t0201-gettext-fallbacks.sh |   50 +++++++++++++++
 t/t0202-gettext-perl.sh      |   23 +++++++
 t/t0202/test.pl              |  104 +++++++++++++++++++++++++++++++
 t/test-lib.sh                |    2 +
 upload-pack.c                |    3 +
 31 files changed, 748 insertions(+), 4 deletions(-)
 create mode 100644 gettext.c
 create mode 100644 gettext.h
 create mode 100644 git-sh-i18n.sh
 create mode 100644 perl/Git/I18N.pm
 create mode 100644 po/.gitignore
 create mode 100644 po/is.po
 create mode 100755 t/t0200-gettext.sh
 create mode 100644 t/t0200/test.c
 create mode 100644 t/t0200/test.perl
 create mode 100644 t/t0200/test.sh
 create mode 100755 t/t0201-gettext-fallbacks.sh
 create mode 100755 t/t0202-gettext-perl.sh
 create mode 100644 t/t0202/test.pl

diff --git a/.gitignore b/.gitignore
index 14e2b6b..6c2b193 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,6 +125,7 @@
 /git-rm
 /git-send-email
 /git-send-pack
+/git-sh-i18n
 /git-sh-setup
 /git-shell
 /git-shortlog
@@ -204,3 +205,4 @@
 *.pdb
 /Debug/
 /Release/
+/share/
diff --git a/INSTALL b/INSTALL
index 61086ab..9c9a5fa 100644
--- a/INSTALL
+++ b/INSTALL
@@ -93,6 +93,18 @@ Issues of note:
 	  history graphically, and in git-gui.  If you don't want gitk or
 	  git-gui, you can use NO_TCLTK.
 
+	- A gettext library is used by default for localizing Git. The
+	  primary target is GNU libintl, but the Solaris gettext
+	  implementation also works.
+
+	  We need a gettext.h on the system for C code, gettext.sh (or
+	  Solaris gettext(1)) for shell scripts, and libintl-perl for Perl
+	  programs.
+
+	  Set NO_GETTEXT to disable localization support and make Git only
+	  use English. Under autoconf the configure script will do this
+	  automatically if it can't find libintl on the system.
+
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
diff --git a/Makefile b/Makefile
index 5fa893c..b5297b6 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,15 @@ all::
 # Define NO_EXPAT if you do not have expat installed.  git-http-push is
 # not built, and you cannot push using http:// and https:// transports.
 #
+# Define NO_GETTEXT if you don't want to build with Git with gettext
+# support. Building it requires GNU libintl or another gettext
+# implementation, and additionally libintl-perl at runtime.
+#
+# Define NEEDS_LIBINTL if you haven't set NO_GETTEXT and your system
+# needs to be explicitly linked to -lintl. It's defined automatically
+# on platforms where we don't expect glibc (Linux, Hurd,
+# GNU/kFreeBSD), which includes libintl.
+#
 # Define EXPATDIR=/foo/bar if your expat header and library files are in
 # /foo/bar/include and /foo/bar/lib directories.
 #
@@ -272,6 +281,7 @@ mandir = share/man
 infodir = share/info
 gitexecdir = libexec/git-core
 sharedir = $(prefix)/share
+localedir = $(sharedir)/locale
 template_dir = share/git-core/templates
 htmldir = share/doc/git-doc
 ifeq ($(prefix),/usr)
@@ -285,7 +295,7 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
-export prefix bindir sharedir sysconfdir
+export prefix bindir sharedir sysconfdir localedir
 
 CC = gcc
 AR = ar
@@ -297,6 +307,8 @@ RPMBUILD = rpmbuild
 TCL_PATH = tclsh
 TCLTK_PATH = wish
 PTHREAD_LIBS = -lpthread
+XGETTEXT = xgettext
+MSGFMT = msgfmt
 
 export TCL_PATH TCLTK_PATH
 
@@ -358,6 +370,7 @@ SCRIPT_SH += git-web--browse.sh
 SCRIPT_LIB += git-mergetool--lib
 SCRIPT_LIB += git-parse-remote
 SCRIPT_LIB += git-sh-setup
+SCRIPT_LIB += git-sh-i18n
 
 SCRIPT_PERL += git-add--interactive.perl
 SCRIPT_PERL += git-difftool.perl
@@ -523,6 +536,7 @@ LIB_H += userdiff.h
 LIB_H += utf8.h
 LIB_H += xdiff-interface.h
 LIB_H += xdiff/xdiff.h
+LIB_H += gettext.h
 
 LIB_OBJS += abspath.o
 LIB_OBJS += advice.o
@@ -564,6 +578,9 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+ifndef NO_GETTEXT
+LIB_OBJS += gettext.o
+endif
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -735,6 +752,14 @@ EXTLIBS =
 # Platform specific tweaks
 #
 
+# Platform specific defaults. Where we'd only like some feature on the
+# minority of systems, e.g. if linking to a library isn't needed
+# because its features are included in the GNU C library.
+ifndef NO_GETTEXT
+	# Systems that use GNU gettext and glibc are the exception
+	NEEDS_LIBINTL = YesPlease
+endif
+
 # We choose to avoid "if .. else if .. else .. endif endif"
 # because maintaining the nesting to match is a pain.  If
 # we had "elif" things would have been much nicer...
@@ -743,11 +768,13 @@ ifeq ($(uname_S),Linux)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),GNU/kFreeBSD)
 	NO_STRLCPY = YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),UnixWare)
 	CC = cc
@@ -918,6 +945,7 @@ ifeq ($(uname_S),GNU)
 	NO_STRLCPY=YesPlease
 	NO_MKSTEMPS = YesPlease
 	HAVE_PATHS_H = YesPlease
+	NEEDS_LIBINTL =
 endif
 ifeq ($(uname_S),IRIX)
 	NO_SETENV = YesPlease
@@ -1387,6 +1415,14 @@ ifdef USE_NED_ALLOCATOR
        COMPAT_OBJS += compat/nedmalloc/nedmalloc.o
 endif
 
+ifdef NO_GETTEXT
+	COMPAT_CFLAGS += -DNO_GETTEXT
+endif
+
+ifdef NEEDS_LIBINTL
+	EXTLIBS += -lintl
+endif
+
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
@@ -1416,6 +1452,7 @@ ifndef V
 	QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
 	QUIET_GEN      = @echo '   ' GEN $@;
 	QUIET_LNCP     = @echo '   ' LN/CP $@;
+	QUIET_MSGFMT   = @echo '   ' MSGFMT $@;
 	QUIET_SUBDIR0  = +@subdir=
 	QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
 			 $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -1443,7 +1480,9 @@ gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 htmldir_SQ = $(subst ','\'',$(htmldir))
 prefix_SQ = $(subst ','\'',$(prefix))
+sharedir_SQ = $(subst ','\'',$(sharedir))
 
+LOCALEDIR_SQ = $(subst ','\'',$(localedir))
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
@@ -1492,7 +1531,7 @@ ifndef NO_TCLTK
 	$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
 ifndef NO_PERL
-	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+	$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
 endif
 ifndef NO_PYTHON
 	$(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
@@ -1537,6 +1576,7 @@ $(RM) $@ $@+ && \
 sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
     -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
     -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+    -e 's|@@LOCALEDIR@@|$(LOCALEDIR_SQ)|g' \
     -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
     -e $(BROKEN_PATH_FIX) \
     $@.sh >$@+
@@ -1872,6 +1912,21 @@ cscope:
 	$(RM) cscope*
 	$(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 
+pot:
+	$(XGETTEXT) --add-comments --keyword=_ --keyword=N_ --output=po/git.pot --language=C $(C_OBJ:o=c) t/t0200/test.c
+	$(XGETTEXT) --add-comments --join-existing --output=po/git.pot --language=Shell $(SCRIPT_SH) t/t0200/test.sh
+	$(XGETTEXT) --add-comments --join-existing --keyword=__ --output=po/git.pot --language=Perl $(SCRIPT_PERL) t/t0200/test.perl
+
+POFILES := $(wildcard po/*.po)
+MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))
+MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES))
+ifndef NO_GETTEXT
+all:: $(MOFILES)
+endif
+share/locale/%/LC_MESSAGES/git.mo: po/%.po
+	@mkdir -p $(dir $@)
+	$(QUIET_MSGFMT)$(MSGFMT) -o $@ $<
+
 ### Detect prefix changes
 TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
@@ -1893,6 +1948,7 @@ GIT-BUILD-OPTIONS: FORCE
 	@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
 	@echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
 	@echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
+	@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1984,6 +2040,11 @@ install: all
 	$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
 	$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
+ifndef NO_GETTEXT
+	$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sharedir_SQ)/locale'
+	(cd share && tar cf - locale) | \
+		(cd '$(DESTDIR_SQ)$(sharedir_SQ)' && umask 022 && tar xof -)
+endif
 	$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
 ifndef NO_PERL
 	$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
@@ -2131,6 +2192,10 @@ ifndef NO_TCLTK
 	$(MAKE) -C git-gui clean
 endif
 	$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS
+ifndef NO_GETTEXT
+	$(RM) po/git.pot
+	$(RM) -r share/
+endif
 
 .PHONY: all install clean strip
 .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
diff --git a/config.mak.in b/config.mak.in
index 0d4b64d..c49072c 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -32,9 +32,11 @@ NO_CURL=@NO_CURL@
 NO_EXPAT=@NO_EXPAT@
 NO_LIBGEN_H=@NO_LIBGEN_H@
 HAVE_PATHS_H=@HAVE_PATHS_H@
+NO_GETTEXT=@NO_GETTEXT@
 NEEDS_LIBICONV=@NEEDS_LIBICONV@
 NEEDS_SOCKET=@NEEDS_SOCKET@
 NEEDS_RESOLV=@NEEDS_RESOLV@
+NEEDS_LIBINTL=@NEEDS_LIBINTL@
 NEEDS_LIBGEN=@NEEDS_LIBGEN@
 NO_SYS_SELECT_H=@NO_SYS_SELECT_H@
 NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@
diff --git a/configure.ac b/configure.ac
index 71038fc..74879b4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -559,6 +559,12 @@ AC_CHECK_LIB([c], [basename],
 AC_SUBST(NEEDS_LIBGEN)
 test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
 
+AC_CHECK_LIB([c], [gettext],
+[NEEDS_LIBINTL=],
+[NEEDS_LIBINTL=YesPlease])
+AC_SUBST(NEEDS_LIBINTL)
+test -n "$NEEDS_LIBINTL" && LIBS="$LIBS -lintl"
+
 ## Checks for header files.
 AC_MSG_NOTICE([CHECKS for header files])
 #
@@ -730,6 +736,12 @@ AC_CHECK_HEADER([paths.h],
 [HAVE_PATHS_H=])
 AC_SUBST(HAVE_PATHS_H)
 #
+# Define NO_GETTEXT if you don't have libintl.h
+AC_CHECK_HEADER([libintl.h],
+[NO_GETTEXT=],
+[NO_GETTEXT=YesPlease])
+AC_SUBST(NO_GETTEXT)
+#
 # Define NO_STRCASESTR if you don't have strcasestr.
 GIT_CHECK_FUNC(strcasestr,
 [NO_STRCASESTR=],
diff --git a/daemon.c b/daemon.c
index a90ab10..7f4691c 100644
--- a/daemon.c
+++ b/daemon.c
@@ -3,6 +3,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 #include <syslog.h>
 
@@ -974,6 +975,8 @@ int main(int argc, char **argv)
 	gid_t gid = 0;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	for (i = 1; i < argc; i++) {
diff --git a/fast-import.c b/fast-import.c
index 129a786..6947f7a 100644
--- a/fast-import.c
+++ b/fast-import.c
@@ -156,6 +156,7 @@ Format of STDIN stream:
 #include "csum-file.h"
 #include "quote.h"
 #include "exec_cmd.h"
+#include "gettext.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -2904,6 +2905,8 @@ int main(int argc, const char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc == 2 && !strcmp(argv[1], "-h"))
 		usage(fast_import_usage);
 
diff --git a/gettext.c b/gettext.c
new file mode 100644
index 0000000..7ae5cae
--- /dev/null
+++ b/gettext.c
@@ -0,0 +1,22 @@
+#include "exec_cmd.h"
+#include <locale.h>
+#include <libintl.h>
+#include <stdlib.h>
+
+extern void git_setup_gettext(void) {
+	char *podir;
+	char *envdir = getenv("GIT_TEXTDOMAINDIR");
+
+	if (envdir) {
+		(void)bindtextdomain("git", envdir);
+	} else {
+		podir = (char *)system_path("share/locale");
+		if (!podir) return;
+		(void)bindtextdomain("git", podir);
+		free(podir);
+	}
+
+	(void)setlocale(LC_MESSAGES, "");
+	(void)setlocale(LC_CTYPE, "");
+	(void)textdomain("git");
+}
diff --git a/gettext.h b/gettext.h
new file mode 100644
index 0000000..e02939a
--- /dev/null
+++ b/gettext.h
@@ -0,0 +1,18 @@
+#ifndef GETTEXT_H
+#define GETTEXT_H
+
+#ifdef NO_GETTEXT
+static inline void git_setup_gettext(void) {}
+#else
+extern void git_setup_gettext(void);
+#endif
+
+#define N_(s) (s)
+#ifdef NO_GETTEXT
+#define _(s) (s)
+#else
+#include <libintl.h>
+#define _(s) gettext(s)
+#endif
+
+#endif
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
new file mode 100644
index 0000000..698a000
--- /dev/null
+++ b/git-sh-i18n.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
+#
+# This is Git's interface to gettext.sh. Use it right after
+# git-sh-setup as:
+#
+#   . git-sh-setup
+#   . git-sh-i18n
+#
+#   # For constant interface messages:
+#   gettext "A message for the user"; echo
+#
+#   # To interpolate variables:
+#   details="oh noes"
+#   eval_gettext "An error occured: \$details"; echo
+#
+# See "info '(gettext)sh'" for the full manual.
+
+# Export the TEXTDOMAIN* data that we need for Git
+TEXTDOMAIN=git
+export TEXTDOMAIN
+if [ -z "$GIT_TEXTDOMAINDIR" ]
+then
+	TEXTDOMAINDIR="@@LOCALEDIR@@"
+else
+	TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
+fi
+export TEXTDOMAINDIR
+
+if test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && type gettext.sh >/dev/null 2>&1
+then
+	# This is GNU libintl's gettext.sh, we don't need to do anything
+	# else than setting up the environment and loading gettext.sh
+	GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu
+	export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+	# Try to use libintl's gettext.sh, or fall back to English if we
+	# can't.
+	. gettext.sh
+elif test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && test "$(gettext -h 2>&1)" = "-h"
+then
+	# We don't have gettext.sh, but there's a gettext binary in our
+	# path. This is probably Solaris or something like it which has a
+	# gettext implementation that isn't GNU libintl.
+	GIT_INTERNAL_GETTEXT_SH_SCHEME=solaris
+	export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+	# Solaris has a gettext(1) but no eval_gettext(1)
+	eval_gettext () {
+		gettext_out=$(gettext "$1")
+		gettext_eval="printf '%s' \"$gettext_out\""
+		printf "%s" "`eval \"$gettext_eval\"`"
+	}
+else
+	# Since gettext.sh isn't available we'll have to define our own
+	# dummy pass-through functions.
+
+	# Tell our tests that we don't have the real gettext.sh
+	GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough
+	export GIT_INTERNAL_GETTEXT_SH_SCHEME
+
+	gettext () {
+		printf "%s" "$1"
+	}
+
+	eval_gettext () {
+		gettext_eval="printf '%s' \"$1\""
+		printf "%s" "`eval \"$gettext_eval\"`"
+	}
+fi
diff --git a/git.c b/git.c
index 99f0363..d749eab 100644
--- a/git.c
+++ b/git.c
@@ -3,6 +3,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "run-command.h"
+#include "gettext.h"
 
 const char git_usage_string[] =
 	"git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n"
@@ -490,6 +491,8 @@ int main(int argc, const char **argv)
 	if (!cmd)
 		cmd = "git-help";
 
+	git_setup_gettext();
+
 	/*
 	 * "git-xxxx" is the same as "git xxxx", but we obviously:
 	 *
diff --git a/http-backend.c b/http-backend.c
index d1e83d0..b6d9bd5 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -6,6 +6,7 @@
 #include "exec_cmd.h"
 #include "run-command.h"
 #include "string-list.h"
+#include "gettext.h"
 
 static const char content_type[] = "Content-Type";
 static const char content_length[] = "Content-Length";
@@ -605,6 +606,8 @@ int main(int argc, char **argv)
 	char *cmd_arg = NULL;
 	int i;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	set_die_routine(die_webcgi);
 
diff --git a/http-fetch.c b/http-fetch.c
index 762c750..b889c36 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -2,6 +2,7 @@
 #include "exec_cmd.h"
 #include "http.h"
 #include "walker.h"
+#include "gettext.h"
 
 static const char http_fetch_usage[] = "git http-fetch "
 "[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url";
@@ -24,6 +25,8 @@ int main(int argc, const char **argv)
 	int get_verbosely = 0;
 	int get_recover = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	while (arg < argc && argv[arg][0] == '-') {
diff --git a/http-push.c b/http-push.c
index 415b1ab..ba0338c 100644
--- a/http-push.c
+++ b/http-push.c
@@ -10,6 +10,7 @@
 #include "remote.h"
 #include "list-objects.h"
 #include "sigchain.h"
+#include "gettext.h"
 
 #include <expat.h>
 
@@ -1791,6 +1792,8 @@ int main(int argc, char **argv)
 	struct remote *remote;
 	char *rewritten_url = NULL;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 
 	repo = xcalloc(sizeof(*repo), 1);
diff --git a/imap-send.c b/imap-send.c
index 9d0097c..4f5f269 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -25,6 +25,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "run-command.h"
+#include "gettext.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #else
@@ -1535,6 +1536,8 @@ int main(int argc, char **argv)
 
 	git_extract_argv0_path(argv[0]);
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(imap_send_usage);
 
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
new file mode 100644
index 0000000..5918d68
--- /dev/null
+++ b/perl/Git/I18N.pm
@@ -0,0 +1,91 @@
+package Git::I18N;
+use 5.006002;
+use strict;
+use warnings;
+use Exporter;
+use base 'Exporter';
+
+our $VERSION = '0.01';
+
+our @EXPORT = qw(__);
+our @EXPORT_OK = @EXPORT;
+
+sub __bootstrap_locale_messages {
+	our $TEXTDOMAIN = 'git';
+	our $TEXTDOMAINDIR = $ENV{GIT_TEXTDOMAINDIR} || '++LOCALEDIR++';
+
+	require POSIX;
+	POSIX->import(qw(setlocale));
+	# Non-core prerequisite module
+	require Locale::Messages;
+	Locale::Messages->import(qw(:locale_h :libintl_h));
+
+	setlocale(LC_MESSAGES(), '');
+	setlocale(LC_CTYPE(), '');
+	textdomain($TEXTDOMAIN);
+	bindtextdomain($TEXTDOMAIN => $TEXTDOMAINDIR);
+
+	return;
+}
+
+BEGIN
+{
+	# Used by our test script to see if it should test fallbacks or
+	# not.
+	our $__HAS_LIBRARY = 1;
+
+	local $@;
+	eval { __bootstrap_locale_messages() };
+	if ($@) {
+		# Tell test.pl that we couldn't load the gettext library.
+		$Git::I18N::__HAS_LIBRARY = 0;
+
+		# Just a fall-through no-op
+		*__ = sub ($) { $_[0] };
+	} else {
+		*__ = \&Locale::Messages::gettext;
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Git::I18N - Perl interface to Git's Gettext localizations
+
+=head1 SYNOPSIS
+
+	use Git::I18N;
+
+	print __("Welcome to Git!\n");
+
+	printf __("The following error occured: %s\n"), $error;
+
+=head1 DESCRIPTION
+
+Git's internal Perl interface to gettext via L<Locale::Messages>. If
+L<Locale::Messages> can't be loaded (it's not a core module) we
+provide stub passthrough fallbacks.
+
+This is a distilled interface to gettext, see C<info '(gettext)Perl'>
+for the full interface. This module implements only a small part of
+it.
+
+=head1 FUNCTIONS
+
+=head2 __($)
+
+L<Locale::Messages>'s gettext function if all goes well, otherwise our
+passthrough fallback function.
+
+=head1 AUTHOR
+
+E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avarab@gmail.com>
+
+=cut
diff --git a/perl/Makefile b/perl/Makefile
index 4ab21d6..4e624ff 100644
--- a/perl/Makefile
+++ b/perl/Makefile
@@ -5,6 +5,7 @@ makfile:=perl.mak
 
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
 prefix_SQ = $(subst ','\'',$(prefix))
+localedir_SQ = $(subst ','\'',$(localedir))
 
 ifndef V
 	QUIET = @
@@ -38,7 +39,7 @@ $(makfile): ../GIT-CFLAGS Makefile
 	echo '	echo $(instdir_SQ)' >> $@
 else
 $(makfile): Makefile.PL ../GIT-CFLAGS
-	$(PERL_PATH) $< PREFIX='$(prefix_SQ)'
+	$(PERL_PATH) $< PREFIX='$(prefix_SQ)' --localedir='$(localedir_SQ)'
 endif
 
 # this is just added comfort for calling make directly in perl dir
diff --git a/perl/Makefile.PL b/perl/Makefile.PL
index 0b9deca..456d45b 100644
--- a/perl/Makefile.PL
+++ b/perl/Makefile.PL
@@ -1,4 +1,12 @@
+use strict;
+use warnings;
 use ExtUtils::MakeMaker;
+use Getopt::Long;
+
+# Sanity: die at first unknown option
+Getopt::Long::Configure qw/ pass_through /;
+
+GetOptions("localedir=s" => \my $localedir);
 
 sub MY::postamble {
 	return <<'MAKE_FRAG';
@@ -16,7 +24,10 @@ endif
 MAKE_FRAG
 }
 
-my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+my %pm = (
+	'Git.pm' => '$(INST_LIBDIR)/Git.pm',
+	'Git/I18N.pm' => '$(INST_LIBDIR)/Git/I18N.pm',
+);
 
 # We come with our own bundled Error.pm. It's not in the set of default
 # Perl modules so install it if it's not available on the system yet.
@@ -33,6 +44,7 @@ WriteMakefile(
 	NAME            => 'Git',
 	VERSION_FROM    => 'Git.pm',
 	PM		=> \%pm,
+	PM_FILTER	=> qq[\$(PERL) -pe "s<\\Q++LOCALEDIR++\\E><$localedir>"],
 	MAKEFILE	=> 'perl.mak',
 	INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3'
 );
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..221000e
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1 @@
+/*.pot
diff --git a/po/is.po b/po/is.po
new file mode 100644
index 0000000..95739f1
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,47 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: t/t0200/test.c:4
+msgid "See git help COMMAND for more information on a specific command."
+msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun."
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:9
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.c:12
+#, c-format
+msgid "TEST: A C test string %s"
+msgstr "TILRAUN: C tilraunastrengur %s"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:8
+msgid "TEST: A Shell test string"
+msgstr "TILRAUN: Skeljartilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.sh:11
+#, sh-format
+msgid "TEST: A Shell test $variable"
+msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:8
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200/test.perl:11
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
diff --git a/shell.c b/shell.c
index e4864e0..ba27c6b 100644
--- a/shell.c
+++ b/shell.c
@@ -2,6 +2,7 @@
 #include "quote.h"
 #include "exec_cmd.h"
 #include "strbuf.h"
+#include "gettext.h"
 
 static int do_generic_cmd(const char *me, char *arg)
 {
@@ -51,6 +52,8 @@ int main(int argc, char **argv)
 	struct commands *cmd;
 	int devnull_fd;
 
+	git_setup_gettext();
+
 	/*
 	 * Always open file descriptors 0/1/2 to avoid clobbering files
 	 * in die().  It also avoids not messing up when the pipes are
diff --git a/show-index.c b/show-index.c
index 4c0ac13..c2f5448 100644
--- a/show-index.c
+++ b/show-index.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "pack.h"
+#include "gettext.h"
 
 static const char show_index_usage[] =
 "git show-index < <packed archive index>";
@@ -11,6 +12,8 @@ int main(int argc, char **argv)
 	unsigned int version;
 	static unsigned int top_index[256];
 
+	git_setup_gettext();
+
 	if (argc != 1)
 		usage(show_index_usage);
 	if (fread(top_index, 2 * 4, 1, stdin) != 1)
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
new file mode 100755
index 0000000..98fbc4d
--- /dev/null
+++ b/t/t0200-gettext.sh
@@ -0,0 +1,138 @@
+#!/bin/sh
+
+test_description='Gettext support for Git'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_PO_PATH="$GIT_EXEC_PATH/po"
+export GIT_TEXTDOMAINDIR GIT_PO_PATH
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $TEXTDOMAIN is git' '
+    test $TEXTDOMAIN = "git"
+'
+
+# Basic xgettext() extraction tests on po/*.po. Doesn't need gettext support
+test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '
+    ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
+    grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect &&
+    grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po  | wc -l >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '
+    ! grep "This is a phony" "$GIT_PO_PATH"/is.po &&
+    ! grep "the above comment" "$GIT_PO_PATH"/is.po
+'
+
+if test_have_prereq GETTEXT; then
+	test_expect_success 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
+    test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+
+	test_expect_success 'sanity: Icelandic locale was compiled' '
+    test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo"
+'
+else
+	test_expect_success "sanity: \$TEXTDOMAINDIR doesn't exists with NO_GETTEXT=YesPlease" '
+    ! test -d "$TEXTDOMAINDIR" &&
+    test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR"
+'
+fi
+
+# We can go no further without actual gettext support
+if ! test_have_prereq GETTEXT || test $GIT_INTERNAL_GETTEXT_SH_SCHEME = "fallthrough"; then
+	say "Skipping the rest of the gettext tests, Git was compiled with NO_GETTEXT=YesPlease"
+	test_done
+fi
+
+# The remaining tests are locale sensitive. They'll fail if the
+# example is_IS locale isn't installed on e.g a fresh Debian system
+if test -z "$TEST_GIT_GETTEXT_EXHAUSTIVE"; then
+	say 'Skipping unportable gettext() tests which depend on is_IS locale, set TEST_GIT_GETTEXT_EXHAUSTIVE=1 to enable'
+
+	test_done
+fi 
+
+# TODO: When we have more locales, generalize this to test them
+# all. Maybe we'll need a dir->locale map for that.
+test_expect_success 'sanity: gettext("") metadata is OK' '
+    # Return value may be non-zero
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" >zero-expect &&
+    grep "Project-Id-Version: Git" zero-expect &&
+    grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
+    grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
+    grep "Content-Transfer-Encoding: 8bit" zero-expect
+'
+
+test_expect_success 'sanity: gettext(unknown) is passed through' '
+    printf "This is not a translation string"  >expect &&
+    gettext "This is not a translation string" >actual &&
+    eval_gettext "This is not a translation string" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from C
+test_expect_success 'xgettext: C extraction of _() and N_() strings' '
+    printf "TILRAUN: C tilraunastrengur" >expect &&
+    printf "\n" >>expect &&
+    printf "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun." >>expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string" >actual &&
+    printf "\n" >>actual &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "See git help COMMAND for more information on a specific command." >>actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: C extraction with %s' '
+    printf "TILRAUN: C tilraunastrengur %%s" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string %s" >actual &&
+    test_cmp expect actual
+'
+
+# xgettext from Shell
+test_expect_success 'xgettext: Shell extraction' '
+    printf "TILRAUN: Skeljartilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Shell test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Shell extraction with $variable' '
+    printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 variable="a var i able" eval_gettext "TEST: A Shell test \$variable" >x-actual &&
+    test_cmp x-expect x-actual
+'
+
+# xgettext from Perl
+test_expect_success 'xgettext: Perl extraction' '
+    printf "TILRAUN: Perl tilraunastrengur" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test string" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'xgettext: Perl extraction with %s' '
+    printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A Perl test variable %s" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'sanity: No gettext("") data for fantasy locale' '
+    LANGUAGE=is LC_ALL=tlh_TLH.UTF-8 gettext "" >fantasy-locale &&
+    ! test -s fantasy-locale
+'
+
+test_expect_success 'sanity: Some gettext("") data for real locale' '
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "" >real-locale &&
+    test -s real-locale
+'
+
+test_done
diff --git a/t/t0200/test.c b/t/t0200/test.c
new file mode 100644
index 0000000..93373b3
--- /dev/null
+++ b/t/t0200/test.c
@@ -0,0 +1,13 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+const char help[] =
+	N_("See 'git help COMMAND' for more information on a specific command.");
+
+int main(void)
+{
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	puts(_("TEST: A C test string"));
+
+	/* TRANSLATORS: This is a test. You don't need to translate it. */
+	printf(_("TEST: A C test string %s"), "variable");
+}
diff --git a/t/t0200/test.perl b/t/t0200/test.perl
new file mode 100644
index 0000000..36fba34
--- /dev/null
+++ b/t/t0200/test.perl
@@ -0,0 +1,14 @@
+# This is a phony Perl program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+1;
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+print __("TEST: A Perl test string");
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+printf __("TEST: A Perl test variable %s"), "moo";
+
+# TRANSLATORS: If you see this, Git has a bug
+print _"TEST: A Perl string xgettext will not get";
diff --git a/t/t0200/test.sh b/t/t0200/test.sh
new file mode 100644
index 0000000..022d607
--- /dev/null
+++ b/t/t0200/test.sh
@@ -0,0 +1,14 @@
+# This is a phony Shell program that's only here to test xgettext
+# message extraction
+
+# so the above comment won't be folded into the next one by xgettext
+echo
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+gettext "TEST: A Shell test string"
+
+# TRANSLATORS: This is a test. You don't need to translate it.
+eval_gettext "TEST: A Shell test \$variable"
+
+# TRANSLATORS: If you see this, Git has a bug
+_("TEST: A Shell string xgettext won't get")
diff --git a/t/t0201-gettext-fallbacks.sh b/t/t0201-gettext-fallbacks.sh
new file mode 100755
index 0000000..ea1ca9d
--- /dev/null
+++ b/t/t0201-gettext-fallbacks.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='Gettext Shell fallbacks'
+
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease
+
+export GIT_TEXTDOMAINDIR GIT_INTERNAL_GETTEXT_TEST_FALLBACKS
+
+. "$GIT_EXEC_PATH"/git-sh-i18n
+
+test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
+    test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' '
+    test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS"
+'
+
+test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' '
+    test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "fallthrough"
+'
+
+test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
+    printf "test" >expect &&
+    gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "test more words" >expect &&
+    gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' '
+    printf "test" >expect &&
+    eval_gettext "test" >actual &&
+    test_cmp expect actual &&
+    printf "test more words" >expect &&
+    eval_gettext "test more words" >actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' '
+    printf "test YesPlease" >expect &&
+    eval_gettext "test \$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" >actual &&
+    test_cmp expect actual
+'
+
+test_done
diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh
new file mode 100755
index 0000000..3a37b23
--- /dev/null
+++ b/t/t0202-gettext-perl.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='Perl gettext interface (Git::I18N)'
+. ./test-lib.sh
+
+GIT_TEXTDOMAINDIR="$GIT_EXEC_PATH/share/locale"
+export GIT_TEXTDOMAINDIR
+
+if ! test_have_prereq PERL; then
+	say 'skipping perl interface tests, perl not available'
+	test_done
+fi
+
+"$PERL_PATH" -MTest::More -e 0 2>/dev/null || {
+	say "Perl Test::More unavailable, skipping test"
+	test_done
+}
+
+test_external_without_stderr \
+    'Perl Git::I18N API' \
+    "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl
+
+test_done
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
new file mode 100644
index 0000000..a952fb2
--- /dev/null
+++ b/t/t0202/test.pl
@@ -0,0 +1,104 @@
+#!/usr/bin/perl
+use 5.006002;
+use lib (split(/:/, $ENV{GITPERLLIB}));
+use warnings;
+use strict;
+use Test::More tests => 9;
+use Git::I18N;
+use POSIX qw(:locale_h);
+
+my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
+
+ok(1, "Testing Git::I18N version $Git::I18N::VERSION with " .
+	 ($has_gettext_library
+	  ? "Locale::Messages version $Locale::Messages::VERSION"
+	  : "NO Perl gettext library"));
+ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
+
+ok($Git::I18N::VERSION, 'sanity: Git::I18N defines a $VERSION');
+{
+	my $exports = @Git::I18N::EXPORT;
+	ok($exports, "sanity: Git::I18N has $exports export(s)");
+}
+is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N exports everything by default");
+
+# prototypes
+{
+	# Add prototypes here when modifying the public interface to add
+	# more gettext wrapper functions.
+	my %prototypes = (qw(
+		__	$
+    ));
+	while (my ($sub, $proto) = each %prototypes) {
+		is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
+	}
+}
+
+# Test basic passthrough in the C locale
+{
+	local $ENV{LANGUAGE} = 'C';
+	local $ENV{LC_ALL}   = 'C';
+	local $ENV{LANG} = 'C';
+
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	is(__($got), $expect, "Passing a string through __() in the C locale works");
+}
+
+# Test a basic message on different locales
+SKIP: {
+    unless ($ENV{TEST_GIT_GETTEXT_EXHAUSTIVE}) {
+        # Can't reliably test __() with a non-C locales because the
+        # required locales may not be installed on the system.
+        #
+        # We test for these anyway as part of the shell
+        # tests. Skipping these here will eliminate failures on odd
+        # platforms with incomplete locale data.
+
+        skip "Set TEST_GIT_GETTEXT_EXHAUSTIVE=1 to enable exhaustive Git::I18N locale tests", 2;
+    }
+
+	my $test = sub {
+		my ($got, $expect, $msg, $locale) = @_;
+		# Maybe this system doesn't have the locale we're trying to
+		# test.
+		my $locale_ok = setlocale(LC_ALL, $locale);
+		is(__($got), $expect, "$msg a gettext library + <$locale> locale <$got> turns into <$expect>");
+	};
+
+	my $env_C = sub {
+		$ENV{LANGUAGE} = 'C';
+		$ENV{LC_ALL}   = 'C';
+	};
+
+	my $env_is = sub {
+		$ENV{LANGUAGE} = 'is';
+		$ENV{LC_ALL}   = 'is_IS.UTF-8';
+	};
+
+	# Translation's the same as the original
+	my ($got, $expect) = (('TEST: A Perl test string') x 2);
+
+	if ($has_gettext_library) {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "With", 'C');
+		}
+
+		{
+			my ($got, $expect) = ($got, 'TILRAUN: Perl tilraunastrengur');
+			local %ENV; $env_is->();
+			$test->($got, $expect, "With", 'is_IS.UTF-8');
+		}
+	} else {
+		{
+			local %ENV; $env_C->();
+			$test->($got, $expect, "Without", 'C');
+		}
+
+		{
+			local %ENV; $env_is->();
+			$test->($got, $expect, "Without", 'is');
+		}
+	}
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 454880a..ae63316 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -37,6 +37,7 @@ ORIGINAL_TERM=$TERM
 # For repeatability, reset the environment to known value.
 LANG=C
 LC_ALL=C
+LANGUAGE=C
 PAGER=cat
 TZ=UTC
 TERM=dumb
@@ -845,6 +846,7 @@ esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
 test -z "$NO_PYTHON" && test_set_prereq PYTHON
+test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
 
 # test whether the filesystem supports symbolic links
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
diff --git a/upload-pack.c b/upload-pack.c
index dc464d7..ece9a4b 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -10,6 +10,7 @@
 #include "revision.h"
 #include "list-objects.h"
 #include "run-command.h"
+#include "gettext.h"
 
 static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>";
 
@@ -686,6 +687,8 @@ int main(int argc, char **argv)
 	int i;
 	int strict = 0;
 
+	git_setup_gettext();
+
 	git_extract_argv0_path(argv[0]);
 	read_replace_refs = 0;
 
-- 
1.7.1.251.g92a7

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v4 2/2] Add initial C, Shell and Perl gettext translations
  2010-06-14  5:00       ` Junio C Hamano
                           ` (4 preceding siblings ...)
  2010-06-15 19:33         ` [PATCH v4 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
@ 2010-06-15 19:33         ` Ævar Arnfjörð Bjarmason
  5 siblings, 0 replies; 16+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-15 19:33 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Jeff Epler, Jakub Narebski, Jonathan Nieder,
	John Wiegley, Graham Anderson, Thomas Rast,
	Ævar Arnfjörð Bjarmason

Change the git status, git pull, and git send-email commands to have
at least one translatable string. Each command uses a different core
language, so this makes a good example of how C, Shell and Perl
programs can be translated using gettext.

Since this introduces translation into the real Git tools more tests
can be added to check if they translations actually work for real core
tools.

The new tests are only run under TEST_GIT_I18N_EXHAUSTIVE=1, since
they might fail if the host OS doesn't provide the relevant locale
files.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 git-pull.sh         |   16 ++++---
 git-send-email.perl |    3 +-
 po/is.po            |   23 +++++++++++
 t/t0200-gettext.sh  |   28 +++++++++++++
 wt-status.c         |  107 ++++++++++++++++++++++++++-------------------------
 5 files changed, 116 insertions(+), 61 deletions(-)

diff --git a/git-pull.sh b/git-pull.sh
index a09a44e..388406f 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -9,6 +9,7 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
 . git-sh-setup
+. git-sh-i18n
 set_reflog_action "pull $*"
 require_work_tree
 cd_to_toplevel
@@ -125,8 +126,8 @@ error_on_no_merge_candidates () {
 	do
 		case "$opt" in
 		-t|--t|--ta|--tag|--tags)
-			echo "Fetching tags only, you probably meant:"
-			echo "  git fetch --tags"
+			gettext "Fetching tags only, you probably meant:"; echo
+			gettext "  git fetch --tags"; echo
 			exit 1
 		esac
 	done
@@ -158,11 +159,12 @@ error_on_no_merge_candidates () {
 		echo "a branch. Because this is not the default configured remote"
 		echo "for your current branch, you must specify a branch on the command line."
 	elif [ -z "$curr_branch" ]; then
-		echo "You are not currently on a branch, so I cannot use any"
-		echo "'branch.<branchname>.merge' in your configuration file."
-		echo "Please specify which remote branch you want to use on the command"
-		echo "line and try again (e.g. 'git pull <repository> <refspec>')."
-		echo "See git-pull(1) for details."
+		gettext "You are not currently on a branch, so I cannot use any
+'branch.<branchname>.merge' in your configuration file.
+Please specify which remote branch you want to use on the command
+line and try again (e.g. 'git pull <repository> <refspec>').
+See git-pull(1) for details.";
+		echo
 	elif [ -z "$upstream" ]; then
 		echo "You asked me to pull without telling me which branch you"
 		echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in"
diff --git a/git-send-email.perl b/git-send-email.perl
index 111c981..4977fdf 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -26,6 +26,7 @@ use Term::ANSIColor;
 use File::Temp qw/ tempdir tempfile /;
 use Error qw(:try);
 use Git;
+use Git::I18N;
 
 Getopt::Long::Configure qw/ pass_through /;
 
@@ -674,7 +675,7 @@ if (!defined $sender) {
 	$sender = $repoauthor || $repocommitter || '';
 	$sender = ask("Who should the emails appear to be from? [$sender] ",
 	              default => $sender);
-	print "Emails will be sent from: ", $sender, "\n";
+	printf __("Emails will be sent from: %s\n"), $sender;
 	$prompting++;
 }
 
diff --git a/po/is.po b/po/is.po
index 95739f1..8e4f8c5 100644
--- a/po/is.po
+++ b/po/is.po
@@ -13,6 +13,20 @@ msgstr ""
 msgid "See git help COMMAND for more information on a specific command."
 msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun."
 
+#: wt-status.c:63 wt-status.c:79 wt-status.c:98 wt-status.c:110
+#: wt-status.c:622
+msgid "On branch "
+msgstr "Á greininni "
+
+#: wt-status.c:629
+msgid "Not currently on any branch."
+msgstr "Ekki á neinni grein."
+
+#: wt-status.c:663
+#, c-format
+msgid "# No changes\n"
+msgstr "# Engar breytingar\n"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.c:9
 msgid "TEST: A C test string"
@@ -24,6 +38,10 @@ msgstr "TILRAUN: C tilraunastrengur"
 msgid "TEST: A C test string %s"
 msgstr "TILRAUN: C tilraunastrengur %s"
 
+#: git-pull.sh:124
+msgid "Fetching tags only, you probably meant:"
+msgstr "Næ aðeins í tögg, þú áttir líkast til við:"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.sh:8
 msgid "TEST: A Shell test string"
@@ -35,6 +53,11 @@ msgstr "TILRAUN: Skeljartilraunastrengur"
 msgid "TEST: A Shell test $variable"
 msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable"
 
+#: git-send-email.perl:678
+#, perl-format
+msgid "Emails will be sent from: %s\n"
+msgstr "Póstarnir verða sendir frá: %s\n"
+
 #. TRANSLATORS: This is a test. You don't need to translate it.
 #: t/t0200/test.perl:8
 msgid "TEST: A Perl test string"
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
index 98fbc4d..1a682ce 100755
--- a/t/t0200-gettext.sh
+++ b/t/t0200-gettext.sh
@@ -135,4 +135,32 @@ test_expect_success 'sanity: Some gettext("") data for real locale' '
     test -s real-locale
 '
 
+# Actually execute some C and Shell code that uses Gettext
+test_expect_success 'C: git-status reads our message catalog ' '
+    test_commit "some-file" &&
+    git checkout -b test/gettext &&
+    LANGUAGE=C LC_ALL=C git status | grep test/gettext > expect &&
+    echo "# On branch test/gettext" > actual &&
+    test_cmp expect actual &&
+
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 git status | grep test/gettext > expect &&
+    echo "# Á greininni test/gettext" > actual &&
+    test_cmp expect actual
+'
+
+test_expect_success 'Shell: git-pull reads our message catalog' '
+    # Repository for testing
+    mkdir parent &&
+    (cd parent && git init &&
+     echo one >file && git add file &&
+     git commit -m one) &&
+
+    # Actual test
+    (cd parent &&
+    (LANGUAGE=C LC_ALL=C git pull --tags "../" >out 2>err);
+    grep "Fetching tags only" err &&
+    (LANGUAGE=is LC_ALL=is_IS.UTF-8 git pull --tags ../ >out 2>err || :) &&
+    grep "Næ aðeins í" err)
+'
+
 test_done
diff --git a/wt-status.c b/wt-status.c
index 14e0acc..484a866 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -9,6 +9,7 @@
 #include "quote.h"
 #include "run-command.h"
 #include "remote.h"
+#include "gettext.h"
 
 static char default_wt_status_colors[][COLOR_MAXLEN] = {
 	GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
@@ -49,16 +50,16 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Unmerged paths:");
+	color_fprintf_ln(s->fp, c, _("# Unmerged paths:"));
 	if (!advice_status_hints)
 		return;
 	if (s->in_merge)
 		;
 	else if (!s->is_initial)
-		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+		color_fprintf_ln(s->fp, c, _("#   (use \"git reset %s <file>...\" to unstage)"), s->reference);
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-	color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git rm --cached <file>...\" to unstage)"));
+	color_fprintf_ln(s->fp, c, _("#   (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -66,15 +67,15 @@ static void wt_status_print_cached_header(struct wt_status *s)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Changes to be committed:");
+	color_fprintf_ln(s->fp, c, _("# Changes to be committed:"));
 	if (!advice_status_hints)
 		return;
 	if (s->in_merge)
 		; /* NEEDSWORK: use "git reset --unresolve"??? */
 	else if (!s->is_initial)
-		color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
+		color_fprintf_ln(s->fp, c, _("#   (use \"git reset %s <file>...\" to unstage)"), s->reference);
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git rm --cached <file>...\" to unstage)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -84,16 +85,16 @@ static void wt_status_print_dirty_header(struct wt_status *s,
 {
 	const char *c = color(WT_STATUS_HEADER, s);
 
-	color_fprintf_ln(s->fp, c, "# Changed but not updated:");
+	color_fprintf_ln(s->fp, c, _("# Changed but not updated:"));
 	if (!advice_status_hints)
 		return;
 	if (!has_deleted)
-		color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to update what will be committed)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git add <file>...\" to update what will be committed)"));
 	else
-		color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" to update what will be committed)");
-	color_fprintf_ln(s->fp, c, "#   (use \"git checkout -- <file>...\" to discard changes in working directory)");
+		color_fprintf_ln(s->fp, c, _("#   (use \"git add/rm <file>...\" to update what will be committed)"));
+	color_fprintf_ln(s->fp, c, _("#   (use \"git checkout -- <file>...\" to discard changes in working directory)"));
 	if (has_dirty_submodules)
-		color_fprintf_ln(s->fp, c, "#   (commit or discard the untracked or modified content in submodules)");
+		color_fprintf_ln(s->fp, c, _("#   (commit or discard the untracked or modified content in submodules)"));
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -102,10 +103,10 @@ static void wt_status_print_other_header(struct wt_status *s,
 					 const char *how)
 {
 	const char *c = color(WT_STATUS_HEADER, s);
-	color_fprintf_ln(s->fp, c, "# %s files:", what);
+	color_fprintf_ln(s->fp, c, _("# %s files:"), what);
 	if (!advice_status_hints)
 		return;
-	color_fprintf_ln(s->fp, c, "#   (use \"git %s <file>...\" to include in what will be committed)", how);
+	color_fprintf_ln(s->fp, c, _("#   (use \"git %s <file>...\" to include in what will be committed)"), how);
 	color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -122,20 +123,20 @@ static void wt_status_print_unmerged_data(struct wt_status *s,
 	const char *c = color(WT_STATUS_UNMERGED, s);
 	struct wt_status_change_data *d = it->util;
 	struct strbuf onebuf = STRBUF_INIT;
-	const char *one, *how = "bug";
+	const char *one, *how = _("bug");
 
 	one = quote_path(it->string, -1, &onebuf, s->prefix);
 	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
 	switch (d->stagemask) {
-	case 1: how = "both deleted:"; break;
-	case 2: how = "added by us:"; break;
-	case 3: how = "deleted by them:"; break;
-	case 4: how = "added by them:"; break;
-	case 5: how = "deleted by us:"; break;
-	case 6: how = "both added:"; break;
-	case 7: how = "both modified:"; break;
+	case 1: how = _("both deleted:"); break;
+	case 2: how = _("added by us:"); break;
+	case 3: how = _("deleted by them:"); break;
+	case 4: how = _("added by them:"); break;
+	case 5: how = _("deleted by us:"); break;
+	case 6: how = _("both added:"); break;
+	case 7: how = _("both modified:"); break;
 	}
-	color_fprintf(s->fp, c, "%-20s%s\n", how, one);
+	color_fprintf(s->fp, c, _("%-20s%s\n"), how, one);
 	strbuf_release(&onebuf);
 }
 
@@ -163,11 +164,11 @@ static void wt_status_print_change_data(struct wt_status *s,
 		if (d->new_submodule_commits || d->dirty_submodule) {
 			strbuf_addstr(&extra, " (");
 			if (d->new_submodule_commits)
-				strbuf_addf(&extra, "new commits, ");
+				strbuf_addf(&extra, _("new commits, "));
 			if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
-				strbuf_addf(&extra, "modified content, ");
+				strbuf_addf(&extra, _("modified content, "));
 			if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
-				strbuf_addf(&extra, "untracked content, ");
+				strbuf_addf(&extra, _("untracked content, "));
 			strbuf_setlen(&extra, extra.len - 2);
 			strbuf_addch(&extra, ')');
 		}
@@ -178,34 +179,34 @@ static void wt_status_print_change_data(struct wt_status *s,
 	one = quote_path(one_name, -1, &onebuf, s->prefix);
 	two = quote_path(two_name, -1, &twobuf, s->prefix);
 
-	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t");
+	color_fprintf(s->fp, color(WT_STATUS_HEADER, s), _("#\t"));
 	switch (status) {
 	case DIFF_STATUS_ADDED:
-		color_fprintf(s->fp, c, "new file:   %s", one);
+		color_fprintf(s->fp, c, _("new file:   %s"), one);
 		break;
 	case DIFF_STATUS_COPIED:
-		color_fprintf(s->fp, c, "copied:     %s -> %s", one, two);
+		color_fprintf(s->fp, c, _("copied:     %s -> %s"), one, two);
 		break;
 	case DIFF_STATUS_DELETED:
-		color_fprintf(s->fp, c, "deleted:    %s", one);
+		color_fprintf(s->fp, c, _("deleted:    %s"), one);
 		break;
 	case DIFF_STATUS_MODIFIED:
-		color_fprintf(s->fp, c, "modified:   %s", one);
+		color_fprintf(s->fp, c, _("modified:   %s"), one);
 		break;
 	case DIFF_STATUS_RENAMED:
-		color_fprintf(s->fp, c, "renamed:    %s -> %s", one, two);
+		color_fprintf(s->fp, c, _("renamed:    %s -> %s"), one, two);
 		break;
 	case DIFF_STATUS_TYPE_CHANGED:
-		color_fprintf(s->fp, c, "typechange: %s", one);
+		color_fprintf(s->fp, c, _("typechange: %s"), one);
 		break;
 	case DIFF_STATUS_UNKNOWN:
-		color_fprintf(s->fp, c, "unknown:    %s", one);
+		color_fprintf(s->fp, c, _("unknown:    %s"), one);
 		break;
 	case DIFF_STATUS_UNMERGED:
-		color_fprintf(s->fp, c, "unmerged:   %s", one);
+		color_fprintf(s->fp, c, _("unmerged:   %s"), one);
 		break;
 	default:
-		die("bug: unhandled diff status %c", status);
+		die(_("bug: unhandled diff status %c"), status);
 	}
 	if (extra.len) {
 		color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "%s", extra.buf);
@@ -618,14 +619,14 @@ void wt_status_print(struct wt_status *s)
 	const char *branch_color = color(WT_STATUS_HEADER, s);
 
 	if (s->branch) {
-		const char *on_what = "On branch ";
+		const char *on_what = _("On branch ");
 		const char *branch_name = s->branch;
 		if (!prefixcmp(branch_name, "refs/heads/"))
 			branch_name += 11;
 		else if (!strcmp(branch_name, "HEAD")) {
 			branch_name = "";
 			branch_color = color(WT_STATUS_NOBRANCH, s);
-			on_what = "Not currently on any branch.";
+			on_what = _("Not currently on any branch.");
 		}
 		color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# ");
 		color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
@@ -635,7 +636,7 @@ void wt_status_print(struct wt_status *s)
 
 	if (s->is_initial) {
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
-		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
+		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _("# Initial commit"));
 		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
 	}
 
@@ -647,38 +648,38 @@ void wt_status_print(struct wt_status *s)
 		wt_status_print_submodule_summary(s, 1);  /* unstaged */
 	}
 	if (s->show_untracked_files) {
-		wt_status_print_other(s, &s->untracked, "Untracked", "add");
+		wt_status_print_other(s, &s->untracked, _("Untracked"), _("add"));
 		if (s->show_ignored_files)
-			wt_status_print_other(s, &s->ignored, "Ignored", "add -f");
+			wt_status_print_other(s, &s->ignored, _("Ignored"), _("add -f"));
 	} else if (s->commitable)
-		fprintf(s->fp, "# Untracked files not listed%s\n",
+		fprintf(s->fp, _("# Untracked files not listed%s\n"),
 			advice_status_hints
-			? " (use -u option to show untracked files)" : "");
+			? _(" (use -u option to show untracked files)") : "");
 
 	if (s->verbose)
 		wt_status_print_verbose(s);
 	if (!s->commitable) {
 		if (s->amend)
-			fprintf(s->fp, "# No changes\n");
+			fprintf(s->fp, _("# No changes\n"));
 		else if (s->nowarn)
 			; /* nothing */
 		else if (s->workdir_dirty)
-			printf("no changes added to commit%s\n",
+			printf(_("no changes added to commit%s\n"),
 				advice_status_hints
-				? " (use \"git add\" and/or \"git commit -a\")" : "");
+				? _(" (use \"git add\" and/or \"git commit -a\")") : "");
 		else if (s->untracked.nr)
-			printf("nothing added to commit but untracked files present%s\n",
+			printf(_("nothing added to commit but untracked files present%s\n"),
 				advice_status_hints
-				? " (use \"git add\" to track)" : "");
+				? _(" (use \"git add\" to track)") : "");
 		else if (s->is_initial)
 			printf("nothing to commit%s\n", advice_status_hints
-				? " (create/copy files and use \"git add\" to track)" : "");
+				? _(" (create/copy files and use \"git add\" to track)") : "");
 		else if (!s->show_untracked_files)
-			printf("nothing to commit%s\n", advice_status_hints
-				? " (use -u to show untracked files)" : "");
+			printf(_("nothing to commit%s\n"), advice_status_hints
+				? _(" (use -u to show untracked files)") : "");
 		else
-			printf("nothing to commit%s\n", advice_status_hints
-				? " (working directory clean)" : "");
+			printf(_("nothing to commit%s\n"), advice_status_hints
+				? _(" (working directory clean)") : "");
 	}
 }
 
-- 
1.7.1.251.g92a7

^ permalink raw reply related	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2010-06-15 19:35 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-06-06 17:47 [PATCH 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
2010-06-06 17:47 ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
2010-06-07 10:02   ` Jakub Narebski
2010-06-07 13:19   ` Jeff Epler
2010-06-07 21:23     ` [PATCH v2 " Ævar Arnfjörð Bjarmason
2010-06-14  5:00       ` Junio C Hamano
2010-06-15  2:11         ` [PATCH v3 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
2010-06-15  2:11         ` [PATCH v3 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
2010-06-15  2:11         ` [PATCH v3 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
2010-06-15 19:33         ` [PATCH v4 0/2] Gettext support for Git Ævar Arnfjörð Bjarmason
2010-06-15 19:33         ` [PATCH v4 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
2010-06-15 19:33         ` [PATCH v4 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
2010-06-14 20:26   ` [PATCH 1/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
2010-06-14 21:56     ` Thomas Rast
2010-06-14 22:51       ` Ævar Arnfjörð Bjarmason
2010-06-06 17:47 ` [PATCH 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.