git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH/RFC v7 0/2] Add infrastructure for translating Git with gettext
@ 2010-06-05  2:13 Ævar Arnfjörð Bjarmason
  2010-06-05  2:13 ` [PATCH/RFC v7 1/2] " Ævar Arnfjörð Bjarmason
  2010-06-05  2:13 ` [PATCH/RFC v7 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
  0 siblings, 2 replies; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05  2:13 UTC (permalink / raw)
  To: git; +Cc: Ævar Arnfjörð Bjarmason

This is version 7, and hopefully the final version, of the patch
series to make Git optionally translatable with GNU gettext.

Changes since version 6:

   * Document the NO_GETTEXT and NEEDS_LIBINTL parameters in the
     Makefile.

   * non-GNU need -lintl during linking to use gettext. Supply it
     everywhere where we don't expect glibc, i.e. Linux, Hurd and
     GNU/kFreeBSD. I've described how I did the Makefile change in
     "[PATCH RFC] gettext: Support building on non-GNU with -lintl".

   * Skip some tests when Git is built with NO_GETTEXT=YesPlease, and
     add a new one to test that it really wasn't compiled as the user
     requested.

Here's the diffstat since version 6:

     Makefile           |   27 +++++++++++++++++++++++++--
     t/t0200-gettext.sh |   48 +++++++++++++++++++++++++++++++-----------------
     t/test-lib.sh      |    1 +
     3 files changed, 57 insertions(+), 19 deletions(-)

That's it. I've tested this on FreeBSD, Debian testing and
Ubuntu. Both with and without building gettext support, with and
without libintl-perl, and with and without the Icelandic locale on the
system (which is used for some tests).

Here's the script I used to test it. I'd appreciate test reports from
more platforms:

    curl http://github.com/avar/test-git-gettext/raw/master/test-git-gettext.sh | sh

If it all works I'll submit a non-RFC.
    
Æ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                     |   67 ++++++++++++++++++-
 config.mak.in                |    1 +
 configure.ac                 |    6 ++
 gettext.c                    |   25 +++++++
 gettext.h                    |   13 ++++
 git-pull.sh                  |   16 +++--
 git-send-email.perl          |    3 +-
 git-sh-i18n.sh               |   47 +++++++++++++
 git.c                        |    3 +
 perl/Git/I18N.pm             |   91 +++++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 ++++-
 po/.gitignore                |    1 +
 po/is.po                     |   69 +++++++++++++++++++
 t/t0200-gettext.sh           |  150 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   10 +++
 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 +
 wt-status.c                  |  107 +++++++++++++++---------------
 25 files changed, 767 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] 19+ messages in thread

* [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05  2:13 [PATCH/RFC v7 0/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
@ 2010-06-05  2:13 ` Ævar Arnfjörð Bjarmason
  2010-06-05  2:57   ` Jonathan Nieder
                     ` (2 more replies)
  2010-06-05  2:13 ` [PATCH/RFC v7 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason
  1 sibling, 3 replies; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05  2:13 UTC (permalink / raw)
  To: git; +Cc: Æ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.

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 the
*.po files 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 this is 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.

 * Shell:

   Shell code that wants 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)-powered 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.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 .gitignore                   |    2 +
 INSTALL                      |    8 ++
 Makefile                     |   67 ++++++++++++++++++-
 config.mak.in                |    1 +
 configure.ac                 |    6 ++
 gettext.c                    |   25 +++++++
 gettext.h                    |   13 ++++
 git-sh-i18n.sh               |   47 +++++++++++++
 git.c                        |    3 +
 perl/Git/I18N.pm             |   91 +++++++++++++++++++++++++
 perl/Makefile                |    3 +-
 perl/Makefile.PL             |   14 ++++-
 po/.gitignore                |    1 +
 po/is.po                     |   46 +++++++++++++
 t/t0200-gettext.sh           |  150 ++++++++++++++++++++++++++++++++++++++++++
 t/t0200/test.c               |   11 +++
 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 +
 22 files changed, 680 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..3040000 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,7 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+LIB_OBJS += gettext.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
@@ -735,6 +750,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 +766,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 +942,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 +1412,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 +1449,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 +1477,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 +1528,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 +1573,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 +1906,21 @@ cscope:
 	$(RM) cscope*
 	$(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 
+pot:
+	$(XGETTEXT) --add-comments --keyword=_ --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 +1942,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 +2034,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 +2186,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..a15f3c1 100644
--- a/config.mak.in
+++ b/config.mak.in
@@ -32,6 +32,7 @@ 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@
diff --git a/configure.ac b/configure.ac
index 71038fc..7bebfd8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -730,6 +730,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/gettext.c b/gettext.c
new file mode 100644
index 0000000..22cdcc1
--- /dev/null
+++ b/gettext.c
@@ -0,0 +1,25 @@
+#ifdef NO_GETTEXT
+void git_setup_gettext(void) {}
+#else
+#include "exec_cmd.h"
+#include <libintl.h>
+#include <stdlib.h>
+
+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");
+}
+#endif
diff --git a/gettext.h b/gettext.h
new file mode 100644
index 0000000..a99da6a
--- /dev/null
+++ b/gettext.h
@@ -0,0 +1,13 @@
+#ifndef GETTEXT_H
+#define GETTEXT_H
+
+void git_setup_gettext(void);
+
+#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/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..1b35738
--- /dev/null
+++ b/po/is.po
@@ -0,0 +1,46 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Git\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-06-02 17:35+0000\n"
+"PO-Revision-Date: 2010-06-02 16:01+0000\n"
+"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
+"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: English\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200-gettext/test.c:6
+msgid "TEST: A C test string"
+msgstr "TILRAUN: C tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200-gettext/test.c:9
+#, 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-gettext/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-gettext/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-gettext/test.perl:5
+msgid "TEST: A Perl test string"
+msgstr "TILRAUN: Perl tilraunastrengur"
+
+#. TRANSLATORS: This is a test. You don't need to translate it.
+#: t/t0200-gettext/test.perl:8
+#, perl-format
+msgid "TEST: A Perl test variable %s"
+msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s"
diff --git a/t/t0200-gettext.sh b/t/t0200-gettext.sh
new file mode 100755
index 0000000..3554566
--- /dev/null
+++ b/t/t0200-gettext.sh
@@ -0,0 +1,150 @@
+#!/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' '
+    printf "TILRAUN: C tilraunastrengur" > expect &&
+    LANGUAGE=is LC_ALL=is_IS.UTF-8 gettext "TEST: A C test string" > 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
+'
+
+# 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/t/t0200/test.c b/t/t0200/test.c
new file mode 100644
index 0000000..9fa4c23
--- /dev/null
+++ b/t/t0200/test.c
@@ -0,0 +1,11 @@
+/* This is a phony C program that's only here to test xgettext message extraction */
+
+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
-- 
1.7.1.243.gda92d6

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

* [PATCH/RFC v7 2/2] Add initial C, Shell and Perl gettext translations
  2010-06-05  2:13 [PATCH/RFC v7 0/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
  2010-06-05  2:13 ` [PATCH/RFC v7 1/2] " Ævar Arnfjörð Bjarmason
@ 2010-06-05  2:13 ` Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05  2:13 UTC (permalink / raw)
  To: git; +Cc: Æ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            |   27 ++++++++++++-
 t/t0200/test.c      |    1 -
 wt-status.c         |  107 ++++++++++++++++++++++++++-------------------------
 5 files changed, 90 insertions(+), 64 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 1b35738..0c77b90 100644
--- a/po/is.po
+++ b/po/is.po
@@ -2,8 +2,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Git\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-06-02 17:35+0000\n"
-"PO-Revision-Date: 2010-06-02 16:01+0000\n"
+"POT-Creation-Date: 2010-06-02 19:43+0000\n"
+"PO-Revision-Date: 2010-06-02 19:35+0000\n"
 "Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
 "Language-Team: Git Mailing List <git@vger.kernel.org>\n"
 "MIME-Version: 1.0\n"
@@ -12,6 +12,20 @@ msgstr ""
 "X-Poedit-Language: English\n"
 "X-Poedit-SourceCharset: utf-8\n"
 
+#: 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-gettext/test.c:6
 msgid "TEST: A C test string"
@@ -23,6 +37,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-gettext/test.sh:8
 msgid "TEST: A Shell test string"
@@ -34,6 +52,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-gettext/test.perl:5
 msgid "TEST: A Perl test string"
diff --git a/t/t0200/test.c b/t/t0200/test.c
index 9fa4c23..171bcc6 100644
--- a/t/t0200/test.c
+++ b/t/t0200/test.c
@@ -8,4 +8,3 @@ int main(void)
 	/* TRANSLATORS: This is a test. You don't need to translate it. */
 	printf(_("TEST: A C test string %s"), "variable");
 }
-	
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

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05  2:13 ` [PATCH/RFC v7 1/2] " Ævar Arnfjörð Bjarmason
@ 2010-06-05  2:57   ` Jonathan Nieder
  2010-06-05  3:28     ` Ævar Arnfjörð Bjarmason
  2010-06-05  3:01   ` Jonathan Nieder
  2010-06-05 13:57   ` [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext Jakub Narebski
  2 siblings, 1 reply; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-05  2:57 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Ævar Arnfjörð Bjarmason wrote:

> 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();
> +

This handles builtins.  ‘grep PROGRAM_OBJS Makefile’ reveals nine
other source files with their own main() functions.

Jonathan

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05  2:13 ` [PATCH/RFC v7 1/2] " Ævar Arnfjörð Bjarmason
  2010-06-05  2:57   ` Jonathan Nieder
@ 2010-06-05  3:01   ` Jonathan Nieder
  2010-06-05  3:30     ` Ævar Arnfjörð Bjarmason
  2010-06-05 13:57   ` [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext Jakub Narebski
  2 siblings, 1 reply; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-05  3:01 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Ævar Arnfjörð Bjarmason wrote:

> diff --git a/gettext.c b/gettext.c
> new file mode 100644
> index 0000000..22cdcc1
> --- /dev/null
> +++ b/gettext.c
> @@ -0,0 +1,25 @@
> +#ifdef NO_GETTEXT
> +void git_setup_gettext(void) {}
> +#else
> +#include "exec_cmd.h"
> +#include <libintl.h>
> +#include <stdlib.h>
> +
> +void git_setup_gettext(void) {
[...]

Could this conditional be taken care of by gettext.h and the Makefile
and avoid complicating the main source file?  i.e., something like

 #ifdef NO_GETTEXT
 static inline void git_setup_gettext(void) {}
 #endif

and

 ifndef NO_GETTEXT
 LIB_OBJS += gettext.o
 endif

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with  gettext
  2010-06-05  2:57   ` Jonathan Nieder
@ 2010-06-05  3:28     ` Ævar Arnfjörð Bjarmason
  2010-06-05  3:36       ` Jonathan Nieder
  0 siblings, 1 reply; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05  3:28 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: git

On Sat, Jun 5, 2010 at 02:57, Jonathan Nieder <jrnieder@gmail.com> wrote:
> Ævar Arnfjörð Bjarmason wrote:
>>  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();
>> +
>
> This handles builtins.  ‘grep PROGRAM_OBJS Makefile’ reveals nine
> other source files with their own main() functions.

Thanks. I spotted those, but I thought they could be skipped for now.

They're mostly guts, but I guess imap-send should be translated.

I can add git_setup_gettext() to them as well if that's wanted. Or I
could just do so during some future commit when they actually get
translated.

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with  gettext
  2010-06-05  3:01   ` Jonathan Nieder
@ 2010-06-05  3:30     ` Ævar Arnfjörð Bjarmason
  2010-06-05  3:38       ` Jonathan Nieder
  0 siblings, 1 reply; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05  3:30 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: git

On Sat, Jun 5, 2010 at 03:01, Jonathan Nieder <jrnieder@gmail.com> wrote:
>> diff --git a/gettext.c b/gettext.c
>> new file mode 100644
>> index 0000000..22cdcc1
>> --- /dev/null
>> +++ b/gettext.c
>> @@ -0,0 +1,25 @@
>> +#ifdef NO_GETTEXT
>> +void git_setup_gettext(void) {}
>> +#else
>> +#include "exec_cmd.h"
>> +#include <libintl.h>
>> +#include <stdlib.h>
>> +
>> +void git_setup_gettext(void) {
> [...]
>
> Could this conditional be taken care of by gettext.h and the Makefile
> and avoid complicating the main source file?  i.e., something like
>
>  #ifdef NO_GETTEXT
>  static inline void git_setup_gettext(void) {}
>  #endif
>
> and
>
>  ifndef NO_GETTEXT
>  LIB_OBJS += gettext.o
>  endif

Sure, but that would be putting code in a header file, which is
usually taboo. It looks like there's some prior art on that though.
Like strbuf.h.

I don't care either way, what do you think?

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05  3:28     ` Ævar Arnfjörð Bjarmason
@ 2010-06-05  3:36       ` Jonathan Nieder
  2010-06-05 15:19         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-05  3:36 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Ævar Arnfjörð Bjarmason wrote:
> On Sat, Jun 5, 2010 at 02:57, Jonathan Nieder <jrnieder@gmail.com> wrote:

>> This handles builtins.  ‘grep PROGRAM_OBJS Makefile’ reveals nine
>> other source files with their own main() functions.
[...]
> They're mostly guts, but I guess imap-send should be translated.

Ok, I just wanted to make sure it was intentional.

Is the porcelain/plumbing distinction meant to be honored here?
It is possible the right thing to do would be to have run_builtin()
call the function that calls setlocale(), if and only if the command
has a USE_LOCALE flag set.

The downside: this would make the handle_internal_command table even
wider than it already is.

Jonathan

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05  3:30     ` Ævar Arnfjörð Bjarmason
@ 2010-06-05  3:38       ` Jonathan Nieder
  2010-06-05 14:10         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-05  3:38 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Ævar Arnfjörð Bjarmason wrote:
> On Sat, Jun 5, 2010 at 03:01, Jonathan Nieder <jrnieder@gmail.com> wrote:

>>  #ifdef NO_GETTEXT
>>  static inline void git_setup_gettext(void) {}
>>  #endif
>>
>> and
>>
>>  ifndef NO_GETTEXT
>>  LIB_OBJS += gettext.o
>>  endif
>
> Sure, but that would be putting code in a header file, which is
> usually taboo. It looks like there's some prior art on that though.
> Like strbuf.h.
> 
> I don't care either way, what do you think?

This is what ‘inline’ is for.  I think using it for stubs like this
is perfectly acceptable and improves readability.

Jonathan

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05  2:13 ` [PATCH/RFC v7 1/2] " Ævar Arnfjörð Bjarmason
  2010-06-05  2:57   ` Jonathan Nieder
  2010-06-05  3:01   ` Jonathan Nieder
@ 2010-06-05 13:57   ` Jakub Narebski
  2010-06-05 16:19     ` Ævar Arnfjörð Bjarmason
  2 siblings, 1 reply; 19+ messages in thread
From: Jakub Narebski @ 2010-06-05 13:57 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

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

> 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.
[...]

> Implementation and usage notes:
[...]
>  * 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.

Actually both of those can be set using Locale::TextDomain->import()
call.  See e.g. this answer on StackOverflow:
  http://stackoverflow.com/questions/2965626/examples-of-localization-in-perl-using-gettext-and-localetextdomain-with-fallb/2967872#2967872

>    [...] In any case, this is an issue wholly internal
>    Git::I18N. Its guts can be changed later if that's deemed
>    necessary.

Right.
 
> --- 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.
> +

Shouldn't you also add here that you need also "libintl-perl" to have
those commands that are written in Perl localized?

> diff --git a/Makefile b/Makefile
> index d5d6565..3040000 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.
[...]

> diff --git a/config.mak.in b/config.mak.in
> index 0d4b64d..a15f3c1 100644
> --- a/config.mak.in
> +++ b/config.mak.in
> @@ -32,6 +32,7 @@ NO_CURL=@NO_CURL@
>  NO_EXPAT=@NO_EXPAT@
>  NO_LIBGEN_H=@NO_LIBGEN_H@
>  HAVE_PATHS_H=@HAVE_PATHS_H@
> +NO_GETTEXT=@NO_GETTEXT@

No

  +NEEDS_LIBINTL=@NEEDS_LIBINTL@

(see also below)?

> diff --git a/configure.ac b/configure.ac
> index 71038fc..7bebfd8 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -730,6 +730,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)
> +#

No check for NEEDS_LIBINTL?  No check that gettext is properly set up
with AM_GNU_GETTEXT (protected with m4_ifdef)?

  +# 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.
  +AC_CHECK_LIB([c], [gettext],
  +[NEEDS_LIBINTL=],
  +[NEEDS_LIBINTL=YesPlease])
  +AC_SUBST(NEEDS_LIBINTL)
  +test -n "$NEEDS_LIBINTL" && LIBS="$LIBS -lintl"

Or something like that (following examples for NEEDS_SOCKET and
NEEDS_RESOLV in configure.ac).

> 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

This ensures that testsuite is run without translation.  It is
required because tests often include checking output of git commands
against expected output.

But perhaps, in later commit, we should mark those test that check
porcelain output format with NO_GETTEXT or LANG_C, and add --gettext
or --intl or --localized to run (parts of) testsuite with localized
strings, checking if localization didn't broke some scripted command
(somewhere command parses output of other git command).

What do you think?

-- 
Jakub Narebski
Poland
ShadeHawk on #git

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with  gettext
  2010-06-05  3:38       ` Jonathan Nieder
@ 2010-06-05 14:10         ` Ævar Arnfjörð Bjarmason
  2010-06-05 18:59           ` Jonathan Nieder
  0 siblings, 1 reply; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05 14:10 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: git

On Sat, Jun 5, 2010 at 03:38, Jonathan Nieder <jrnieder@gmail.com> wrote:
> Ævar Arnfjörð Bjarmason wrote:
>> On Sat, Jun 5, 2010 at 03:01, Jonathan Nieder <jrnieder@gmail.com> wrote:
>
>>>  #ifdef NO_GETTEXT
>>>  static inline void git_setup_gettext(void) {}
>>>  #endif
>>>
>>> and
>>>
>>>  ifndef NO_GETTEXT
>>>  LIB_OBJS += gettext.o
>>>  endif
>>
>> Sure, but that would be putting code in a header file, which is
>> usually taboo. It looks like there's some prior art on that though.
>> Like strbuf.h.
>>
>> I don't care either way, what do you think?
>
> This is what ‘inline’ is for.  I think using it for stubs like this
> is perfectly acceptable and improves readability.

Great. I've fixed this in my local copy. Now I don't compile gettext.c
when NO_GETTEXT is set, and it'll be defined as static inline in the
header file.

It'll become part of the next patch. Here's the diff:

diff --git a/Makefile b/Makefile
index 3040000..e21f7f0 100644
--- a/Makefile
+++ b/Makefile
@@ -578,7 +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
diff --git a/gettext.c b/gettext.c
index 22cdcc1..4825799 100644
--- a/gettext.c
+++ b/gettext.c
@@ -1,11 +1,8 @@
-#ifdef NO_GETTEXT
-void git_setup_gettext(void) {}
-#else
 #include "exec_cmd.h"
 #include <libintl.h>
 #include <stdlib.h>

-void git_setup_gettext(void) {
+inline void git_setup_gettext(void) {
 	char *podir;
 	char *envdir = getenv("GIT_TEXTDOMAINDIR");

@@ -22,4 +19,3 @@ void git_setup_gettext(void) {
 	(void)setlocale(LC_CTYPE, "");
 	(void)textdomain("git");
 }
-#endif
diff --git a/gettext.h b/gettext.h
index a99da6a..8d44808 100644
--- a/gettext.h
+++ b/gettext.h
@@ -1,7 +1,11 @@
 #ifndef GETTEXT_H
 #define GETTEXT_H

-void git_setup_gettext(void);
+#ifdef NO_GETTEXT
+static inline void git_setup_gettext(void) {}
+#else
+inline void git_setup_gettext(void);
+#endif

 #ifdef NO_GETTEXT
 #define _(s) (s)

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with  gettext
  2010-06-05  3:36       ` Jonathan Nieder
@ 2010-06-05 15:19         ` Ævar Arnfjörð Bjarmason
  2010-06-05 19:27           ` Jonathan Nieder
  0 siblings, 1 reply; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05 15:19 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: git

On Sat, Jun 5, 2010 at 03:36, Jonathan Nieder <jrnieder@gmail.com> wrote:
> Ævar Arnfjörð Bjarmason wrote:
>> On Sat, Jun 5, 2010 at 02:57, Jonathan Nieder <jrnieder@gmail.com> wrote:
>
>>> This handles builtins.  ‘grep PROGRAM_OBJS Makefile’ reveals nine
>>> other source files with their own main() functions.
> [...]
>> They're mostly guts, but I guess imap-send should be translated.
>
> Ok, I just wanted to make sure it was intentional.

The main motivation is just to get the majority of messages in
translatable state.

> Is the porcelain/plumbing distinction meant to be honored here?
> It is possible the right thing to do would be to have run_builtin()
> call the function that calls setlocale(), if and only if the command
> has a USE_LOCALE flag set.

I looked at some of the PROGRAM_OBJS that were omitted and all of them
have some parts where they'll die with a message to STDERR. Those
should probably be translatable.

So in the interest of completion I've patched my copy so that all the
PROGRAM_OBJS include the gettext setup function. I can include it in
my next series if this is deemed a good idea.

> The downside: this would make the handle_internal_command table even
> wider than it already is.

IMO trying to plan in advance what we should and shouldn't translate
is a bit premature.

I think it's better to just add glue so that everything can be
translated, then we can try decide later if some arbitrary subset
shouldn't have translations (even for human readable error messages)
and act appropriately.

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++) {
	Modified fast-import.c
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);

	Modified http-backend.c
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);

	Modified http-fetch.c
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] == '-') {
	Modified http-push.c
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);
	Modified imap-send.c
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);

	Modified shell.c
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
	Modified show-index.c
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)
	Modified upload-pack.c
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;

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with  gettext
  2010-06-05 13:57   ` [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext Jakub Narebski
@ 2010-06-05 16:19     ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05 16:19 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git

On Sat, Jun 5, 2010 at 13:57, Jakub Narebski <jnareb@gmail.com> wrote:
> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
>
>> 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.
> [...]
>
>> Implementation and usage notes:
> [...]
>>  * 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.
>
> Actually both of those can be set using Locale::TextDomain->import()
> call.  See e.g. this answer on StackOverflow:
>  http://stackoverflow.com/questions/2965626/examples-of-localization-in-perl-using-gettext-and-localetextdomain-with-fallb/2967872#2967872
>
>>    [...] In any case, this is an issue wholly internal
>>    Git::I18N. Its guts can be changed later if that's deemed
>>    necessary.
>
> Right.

Locale::TextDomain's interface simply does more than
Locale::Messages. And since I'm trying to present exactly the same
interface to C, Shell and Perl any code that uses it has to spend
effort on disabling its features.

E.g.

  * No I don't want a tied hash (yes, I know I don't have to export it
    in turn):

        BEGIN {
            # Tie the hash to gettext().
            tie %__, '__TiedTextDomain', \&__tied_gettext;
            $__ = \%__;

  * I *only* want our TEXTDOMAINDIR, not this. And there's no way to
    turn it off:

    	# Add default search directories, but only if they exist.
    	for my $dir (qw (/usr/share/locale /usr/local/share/locale)) {
            if (-d $dir) {
                @default_dirs = ($dir);

  * I also don't want to search for locale files in Perl's @INC, or
    $textdomaindir/LocaleData:

        unless (exists $bound_dirs{$textdomain}) {
    		@search_dirs = map $_ . '/LocaleData', @INC, @default_dirs
    			unless @search_dirs;
    		$bound_dirs{$textdomain} = [@search_dirs];

  * Here's what the normal functions look like. Since caller() will be
    the top-level caller and not Git::I18N (unless I define a wrapper
    function, which Git::I18N doesn't do) I'd have to take measures to
    set the textdomain for the importer's package as well as my own:

        # Normal gettext.
        sub __ ($)
        {
            my $msgid = shift;
            my $package = caller;

     And that sub does this:

        __find_domain $textdomain if
    		defined $textdomain && defined $bound_dirs{$textdomain};

  * Which again bypasses what I just want to do with bindtextdomain():

        sub __find_domain ($)
        {
        	my $domain = shift;
        	
        	my $try_dirs = $bound_dirs{$domain};
        	
        	if (defined $try_dirs) {
        		my $found_dir = '';
        		
        		TRYDIR: foreach my $dir (map { abs_path $_ } grep { -d $_ }
@$try_dirs) {
        			# Is there a message catalog?  We have to search recursively
        			# for it.  Since globbing is reported to be buggy under
        			# MS-DOS, we roll our own version.
        			local *DIR;
        			if (opendir DIR, $dir) {
        				my @files = map { "$dir/$_/LC_MESSAGES/$domain.mo" }
        					grep { ! /^\.\.?$/ } readdir DIR;

        				foreach my $file (@files) {
        					if (-f $file || -l $file) {
        						# If we find a non-readable file on our way,
        						# we access has been disabled on purpose.
        						# Therefore no -r check here.
        						$found_dir = $dir;
        						last TRYDIR;
        					}
        				}
        			}
        		}
        		
        		# If there was no success, this will fall back to the default search
        		# directories.
        		bindtextdomain $domain => $found_dir;

  * That'll only happen on the first invocation though, later it'll
    just skip that and do:

        dgettext $textdomain => $msgid;

None of this is a showstopper. It's just more code to test, override
and stuff that can go wrong.

It doesn't make any sense to use it when all I want is simply
bindtextdomain(git => $dir) && gettext($msgid).

>> --- 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.
>> +
>
> Shouldn't you also add here that you need also "libintl-perl" to have
> those commands that are written in Perl localized?

I pondered it, it's it's documented in the INSTALL file, but all the
comments in the Makefile are pertinent only to compiling the C code.

Since libintl-perl isn't a compilation requirement, or anything you
need to worry about when running make I didn't add it in the Makefile
comments.

>> diff --git a/Makefile b/Makefile
>> index d5d6565..3040000 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.
> [...]
>
>> diff --git a/config.mak.in b/config.mak.in
>> index 0d4b64d..a15f3c1 100644
>> --- a/config.mak.in
>> +++ b/config.mak.in
>> @@ -32,6 +32,7 @@ NO_CURL=@NO_CURL@
>>  NO_EXPAT=@NO_EXPAT@
>>  NO_LIBGEN_H=@NO_LIBGEN_H@
>>  HAVE_PATHS_H=@HAVE_PATHS_H@
>> +NO_GETTEXT=@NO_GETTEXT@
>
> No
>
>  +NEEDS_LIBINTL=@NEEDS_LIBINTL@
>
> (see also below)?
>
>> diff --git a/configure.ac b/configure.ac
>> index 71038fc..7bebfd8 100644
>> --- a/configure.ac
>> +++ b/configure.ac
>> @@ -730,6 +730,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)
>> +#
>
> No check for NEEDS_LIBINTL? [..]
>
>  +# 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.
>  +AC_CHECK_LIB([c], [gettext],
>  +[NEEDS_LIBINTL=],
>  +[NEEDS_LIBINTL=YesPlease])
>  +AC_SUBST(NEEDS_LIBINTL)
>  +test -n "$NEEDS_LIBINTL" && LIBS="$LIBS -lintl"
>
> Or something like that (following examples for NEEDS_SOCKET and
> NEEDS_RESOLV in configure.ac).

(I added a NEEDS_RESOLV check in another patch that's exactly like
this, thanks).

> No check that gettext is properly set up with AM_GNU_GETTEXT
> (protected with m4_ifdef)?

Since it was noted before and Ben Walton and David M. Syzdek were CC'd
I thought I'd wait for what they have to say about it:

    http://www.spinics.net/lists/git/msg132431.html

I'm not familiar enough with autoconf to know which'd be better, and I
don't see how to use the AM_GNU_GETTEXT() macro to set and substitute
the NO_GETTEXT variable like the AC_CHECK_HEADER check does now.

Help from someone familiar with autoconf's m4 library would be
appreciated.

>> 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
>
> This ensures that testsuite is run without translation.  It is
> required because tests often include checking output of git commands
> against expected output.

Right, but I found that it didn't work as intended unless LANGUAGE=
was also set, so I added it.

> But perhaps, in later commit, we should mark those test that check
> porcelain output format with NO_GETTEXT or LANG_C, and add --gettext
> or --intl or --localized to run (parts of) testsuite with localized
> strings, checking if localization didn't broke some scripted command
> (somewhere command parses output of other git command).
>
> What do you think?

Yeah, optionally overriding the test suite to not set the LC variables
to test whether something breaks with localized output would be useful
later. It could be done just as:

    if test -z "$GIT_TEST_SET_C_LOCALE"
    then
         LC_ALL=C
         ...
    fi

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05 14:10         ` Ævar Arnfjörð Bjarmason
@ 2010-06-05 18:59           ` Jonathan Nieder
  2010-06-05 19:33             ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-05 18:59 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Hi Ævar,

Thanks for trying it out.

Ævar Arnfjörð Bjarmason wrote:

> --- a/gettext.c
> +++ b/gettext.c
> @@ -1,11 +1,8 @@
> -#ifdef NO_GETTEXT
> -void git_setup_gettext(void) {}
> -#else
>  #include "exec_cmd.h"
>  #include <libintl.h>
>  #include <stdlib.h>
> 
> -void git_setup_gettext(void) {
> +inline void git_setup_gettext(void) {

This should not be inline when NO_GETTEXT is unset, since other
translation units don’t get a chance to see the definition.

> diff --git a/gettext.h b/gettext.h
> index a99da6a..8d44808 100644
> --- a/gettext.h
> +++ b/gettext.h
> @@ -1,7 +1,11 @@
>  #ifndef GETTEXT_H
>  #define GETTEXT_H
> 
> -void git_setup_gettext(void);
> +#ifdef NO_GETTEXT
> +static inline void git_setup_gettext(void) {}
> +#else
> +inline void git_setup_gettext(void);
> +#endif

With s/^inline \(.*;\)/extern &/, to make it:

 -void git_setup_gettext(void);
 +#ifdef NO_GETTEXT
 +static inline void git_setup_gettext(void) {}
 +#else
 +extern void git_setup_gettext(void);
 +#endif

this part is exactly how I was imagining it (i.e., like might_fault()
in linux-2.6/include/linux/kernel.h and many other examples).

Thanks again.
Jonathan

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05 15:19         ` Ævar Arnfjörð Bjarmason
@ 2010-06-05 19:27           ` Jonathan Nieder
  2010-06-05 19:47             ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-05 19:27 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Ævar Arnfjörð Bjarmason wrote:

> IMO trying to plan in advance what we should and shouldn't translate
> is a bit premature.
> 
> I think it's better to just add glue so that everything can be
> translated, then we can try decide later if some arbitrary subset
> shouldn't have translations (even for human readable error messages)
> and act appropriately.

Certainly there is no need to commit to decisions that will have no
observable effect.  However, setlocale()

 - takes some small amount of time (probably negligible);

 - changes the semantics of regular expression matching
   (this affects ‘git grep’, ‘git diff’'s search for a function
   header, and http-backend’s service-command regexps);

 - changes the human language used for strerror().

We could avoid these issues by turning translations off by default and
calling it experimental, which might be the right thing to do anyway.

Alternatively we can make some choice and stick to it (e.g., all git
commands use setlocale(), or just the translated ones, or whatever).
I just want to ensure it is a conscious choice.

Jonathan

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with  gettext
  2010-06-05 18:59           ` Jonathan Nieder
@ 2010-06-05 19:33             ` Ævar Arnfjörð Bjarmason
  2010-06-05 20:16               ` Inline functions (Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext) Jonathan Nieder
  0 siblings, 1 reply; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05 19:33 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: git

On Sat, Jun 5, 2010 at 18:59, Jonathan Nieder <jrnieder@gmail.com> wrote:
> Hi Ævar,
>
> Thanks for trying it out.

And thanks for your comments helping to make the series better.

> Ævar Arnfjörð Bjarmason wrote:
>
>> --- a/gettext.c
>> +++ b/gettext.c
>> @@ -1,11 +1,8 @@
>> -#ifdef NO_GETTEXT
>> -void git_setup_gettext(void) {}
>> -#else
>>  #include "exec_cmd.h"
>>  #include <libintl.h>
>>  #include <stdlib.h>
>>
>> -void git_setup_gettext(void) {
>> +inline void git_setup_gettext(void) {
>
> This should not be inline when NO_GETTEXT is unset, since other
> translation units don’t get a chance to see the definition.

I'm not familiar with what you mean, since I'm not that familiar with
the semantics of inline in C/GCC.

As an aside, I'd appreciate a document pointer, what are the
implications here exactly?

>> diff --git a/gettext.h b/gettext.h
>> index a99da6a..8d44808 100644
>> --- a/gettext.h
>> +++ b/gettext.h
>> @@ -1,7 +1,11 @@
>>  #ifndef GETTEXT_H
>>  #define GETTEXT_H
>>
>> -void git_setup_gettext(void);
>> +#ifdef NO_GETTEXT
>> +static inline void git_setup_gettext(void) {}
>> +#else
>> +inline void git_setup_gettext(void);
>> +#endif
>
> With s/^inline \(.*;\)/extern &/, to make it:
>
>  -void git_setup_gettext(void);
>  +#ifdef NO_GETTEXT
>  +static inline void git_setup_gettext(void) {}
>  +#else
>  +extern void git_setup_gettext(void);
>  +#endif
>
> this part is exactly how I was imagining it (i.e., like might_fault()
> in linux-2.6/include/linux/kernel.h and many other examples).

Done:

    diff --git a/gettext.c b/gettext.c
    index 4825799..407fbc0 100644
    --- a/gettext.c
    +++ b/gettext.c
    @@ -2,7 +2,7 @@
     #include <libintl.h>
     #include <stdlib.h>

    -inline void git_setup_gettext(void) {
    +extern void git_setup_gettext(void) {
     	char *podir;
     	char *envdir = getenv("GIT_TEXTDOMAINDIR");

    diff --git a/gettext.h b/gettext.h
    index 8d44808..7c36c1e 100644
    --- a/gettext.h
    +++ b/gettext.h
    @@ -4,7 +4,7 @@
     #ifdef NO_GETTEXT
     static inline void git_setup_gettext(void) {}
     #else
    -inline void git_setup_gettext(void);
    +extern void git_setup_gettext(void);
     #endif

     #ifdef NO_GETTEXT

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with  gettext
  2010-06-05 19:27           ` Jonathan Nieder
@ 2010-06-05 19:47             ` Ævar Arnfjörð Bjarmason
  2010-06-12 17:26               ` Jonathan Nieder
  0 siblings, 1 reply; 19+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2010-06-05 19:47 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: git

On Sat, Jun 5, 2010 at 19:27, Jonathan Nieder <jrnieder@gmail.com> wrote:
> Ævar Arnfjörð Bjarmason wrote:
>
>> IMO trying to plan in advance what we should and shouldn't translate
>> is a bit premature.
>>
>> I think it's better to just add glue so that everything can be
>> translated, then we can try decide later if some arbitrary subset
>> shouldn't have translations (even for human readable error messages)
>> and act appropriately.
>
> Certainly there is no need to commit to decisions that will have no
> observable effect.  However, setlocale()
>
>  - takes some small amount of time (probably negligible);
>
>  - changes the semantics of regular expression matching
>   (this affects ‘git grep’, ‘git diff’'s search for a function
>   header, and http-backend’s service-command regexps);
>
>  - changes the human language used for strerror().
>
> We could avoid these issues by turning translations off by default and
> calling it experimental, which might be the right thing to do anyway.
>
> Alternatively we can make some choice and stick to it (e.g., all git
> commands use setlocale(), or just the translated ones, or whatever).
> I just want to ensure it is a conscious choice.

What I'm trying to do is to make Git follow the semantics that
localized programs that use gettext traditionally do.

Everything that's translated on my desktop does pretty much what this
patch series does, i.e. calls setlocale() very early in main() so that
everything's translated and locale aware. Including strerror()
invocations and locale-aware functions like grep.

Whether it's the default or not doesn't really matter to me. Normal
users are going to get Git via their OS, and those turn on l10n
support as a matter of policy if it's available.

But since the semantics of Git will be the same unless users
explicitly request translations (by setting the LC_* variables in
their OS) I didn't see a reason not to enable it by default.

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

* Inline functions (Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext)
  2010-06-05 19:33             ` Ævar Arnfjörð Bjarmason
@ 2010-06-05 20:16               ` Jonathan Nieder
  0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-05 20:16 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Ævar Arnfjörð Bjarmason wrote:

> As an aside, I'd appreciate a document pointer, what are the
> implications here exactly?

Oh, good point!  I clean forgot to provide that.

"static inline" functions are just like macros, except each argument
is evaluated exactly once and the arguments are typechecked.

In the older dialect, what I was suggesting would be written as
follows:

 #ifdef NO_GETTEXT
 #define git_setup_gettext() do { } while(0)
 #else
 extern void git_setup_gettext(void);
 #endif

The feature has been part of GCC and various compilers for a long time
now.  It was finally officially adopted when the C99 standard came
out.  It "degrades gracefully" in that the Makefile will define inline
to nothing on those very old compilers known not to support it, making
these into static functions (which is kind of ugly but with most
optimizers it tends to produce the same result).

GCC documentation:

  http://gcc.gnu.org/onlinedocs/gcc/Inline.html

C99 documentation (see the "most current draft" of the revised C99
and navigate with PDF bookmarks to Language → Declarations →
Function specifiers → Semantics):

  http://www.open-std.org/jtc1/sc22/wg14/www/projects#9899

More important than all that is how the construct gets used and why.
For this, see SECTION 2 item 2 ("Ifdefs are ugly") of
linux-2.6/Documentation/SubmittingPatches[1]: and take a look at some
of the surrounding code (maybe include/linux/kernel.h would be
analogous) for examples.

Hope that helps,
Jonathan

[1] http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=Documentation/SubmittingPatches

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

* Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext
  2010-06-05 19:47             ` Ævar Arnfjörð Bjarmason
@ 2010-06-12 17:26               ` Jonathan Nieder
  0 siblings, 0 replies; 19+ messages in thread
From: Jonathan Nieder @ 2010-06-12 17:26 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Hi Ævar,

Sorry for the long delay in replying.

Ævar Arnfjörð Bjarmason wrote:

> I didn't see a reason not to enable it by default.

I only meant that if it is very useful but the kinks not ironed out,
releasing with the feature disabled by default is a way to cope.
An even better way is to get experience with the feature in next or
pu until it is ready to be shipped enabled by default, so you can
forget I said anything. :)

> But since the semantics of Git will be the same unless users
> explicitly request translations (by setting the LC_* variables in
> their OS) 

Sometimes I am on machines with LANG set to fr_FR.UTF-8.  If plumbing
is going to be giving _all_ its other messages in English, I would
rather it give the system call errors in English, too.  But this is
not a strong preference, and it is possible we would want to let all
messages to stderr be translated, making this moot.

Thanks for the explanations.
Jonathan

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

end of thread, other threads:[~2010-06-12 17:26 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-06-05  2:13 [PATCH/RFC v7 0/2] Add infrastructure for translating Git with gettext Ævar Arnfjörð Bjarmason
2010-06-05  2:13 ` [PATCH/RFC v7 1/2] " Ævar Arnfjörð Bjarmason
2010-06-05  2:57   ` Jonathan Nieder
2010-06-05  3:28     ` Ævar Arnfjörð Bjarmason
2010-06-05  3:36       ` Jonathan Nieder
2010-06-05 15:19         ` Ævar Arnfjörð Bjarmason
2010-06-05 19:27           ` Jonathan Nieder
2010-06-05 19:47             ` Ævar Arnfjörð Bjarmason
2010-06-12 17:26               ` Jonathan Nieder
2010-06-05  3:01   ` Jonathan Nieder
2010-06-05  3:30     ` Ævar Arnfjörð Bjarmason
2010-06-05  3:38       ` Jonathan Nieder
2010-06-05 14:10         ` Ævar Arnfjörð Bjarmason
2010-06-05 18:59           ` Jonathan Nieder
2010-06-05 19:33             ` Ævar Arnfjörð Bjarmason
2010-06-05 20:16               ` Inline functions (Re: [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext) Jonathan Nieder
2010-06-05 13:57   ` [PATCH/RFC v7 1/2] Add infrastructure for translating Git with gettext Jakub Narebski
2010-06-05 16:19     ` Ævar Arnfjörð Bjarmason
2010-06-05  2:13 ` [PATCH/RFC v7 2/2] Add initial C, Shell and Perl gettext translations Ævar Arnfjörð Bjarmason

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).