diff --git a/.gitignore b/.gitignore index 0a30a7e97c..2a9dde161c 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 @@ -210,3 +211,4 @@ *.pdb /Debug/ /Release/ +/share/ diff --git a/INSTALL b/INSTALL index 59200b730e..e4e7506ff4 100644 --- a/INSTALL +++ b/INSTALL @@ -93,6 +93,18 @@ Issues of note: history graphically, and in git-gui. If you don't want gitk or git-gui, you can use NO_TCLTK. + - A gettext library is used by default for localizing Git. The + primary target is GNU libintl, but the Solaris gettext + implementation also works. + + We need a gettext.h on the system for C code, gettext.sh (or + Solaris gettext(1)) for shell scripts, and libintl-perl for Perl + programs. + + Set NO_GETTEXT to disable localization support and make Git only + use English. Under autoconf the configure script will do this + automatically if it can't find libintl on the system. + - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not have all the libraries/tools needed, or you may have diff --git a/Makefile b/Makefile index 194c26fc5e..b031450a9c 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,15 @@ all:: # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. # +# Define NO_GETTEXT if you don't want to build with Git with gettext +# support. Building it requires GNU libintl or another gettext +# implementation, and additionally libintl-perl at runtime. +# +# Define NEEDS_LIBINTL if you haven't set NO_GETTEXT and your system +# needs to be explicitly linked to -lintl. It's defined automatically +# on platforms where we don't expect glibc (Linux, Hurd, +# GNU/kFreeBSD), which includes libintl. +# # Define EXPATDIR=/foo/bar if your expat header and library files are in # /foo/bar/include and /foo/bar/lib directories. # @@ -281,6 +290,7 @@ infodir = share/info gitexecdir = libexec/git-core sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb +localedir = $(sharedir)/locale template_dir = share/git-core/templates htmldir = share/doc/git-doc ifeq ($(prefix),/usr) @@ -294,7 +304,7 @@ lib = lib # DESTDIR= pathsep = : -export prefix bindir sharedir sysconfdir gitwebdir +export prefix bindir sharedir sysconfdir gitwebdir localedir CC = gcc AR = ar @@ -308,6 +318,8 @@ TCL_PATH = tclsh TCLTK_PATH = wish PTHREAD_LIBS = -lpthread PTHREAD_CFLAGS = +XGETTEXT = xgettext +MSGFMT = msgfmt GCOV = gcov export TCL_PATH TCLTK_PATH @@ -370,6 +382,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 @@ -535,6 +548,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 @@ -576,6 +590,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 @@ -748,6 +765,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... @@ -763,11 +788,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 @@ -959,6 +986,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 @@ -1457,6 +1485,14 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT export GIT_TEST_CMP_USE_COPIED_CONTEXT endif +ifdef NO_GETTEXT + COMPAT_CFLAGS += -DNO_GETTEXT +endif + +ifdef NEEDS_LIBINTL + EXTLIBS += -lintl +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks endif @@ -1486,6 +1522,7 @@ ifndef V QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; QUIET_LNCP = @echo ' ' LN/CP $@; + QUIET_MSGFMT = @echo ' ' MSGFMT $@; QUIET_GCOV = @echo ' ' GCOV $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ @@ -1515,7 +1552,9 @@ template_dir_SQ = $(subst ','\'',$(template_dir)) htmldir_SQ = $(subst ','\'',$(htmldir)) prefix_SQ = $(subst ','\'',$(prefix)) gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) +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)) @@ -1565,7 +1604,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 @@ -1611,6 +1650,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ -e 's|@@DIFF@@|$(DIFF_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 >$@+ @@ -1939,6 +1979,21 @@ cscope: $(RM) cscope* $(FIND) . -name '*.[hcS]' -print | xargs cscope -b +pot: + $(XGETTEXT) --add-comments --keyword=_ --keyword=N_ --output=po/git.pot --language=C $(C_OBJ:o=c) t/t0200/test.c + $(XGETTEXT) --add-comments --join-existing --output=po/git.pot --language=Shell $(SCRIPT_SH) t/t0200/test.sh + $(XGETTEXT) --add-comments --join-existing --keyword=__ --output=po/git.pot --language=Perl $(SCRIPT_PERL) t/t0200/test.perl + +POFILES := $(wildcard po/*.po) +MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES)) +MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES)) +ifndef NO_GETTEXT +all:: $(MOFILES) +endif +share/locale/%/LC_MESSAGES/git.mo: po/%.po + @mkdir -p $(dir $@) + $(QUIET_MSGFMT)$(MSGFMT) -o $@ $< + ### Detect prefix changes TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) @@ -1968,6 +2023,7 @@ endif ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@ endif + @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@ ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK @@ -2059,6 +2115,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 @@ -2216,6 +2277,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 b4e65c32b2..011b103d1b 100644 --- a/config.mak.in +++ b/config.mak.in @@ -34,9 +34,11 @@ NO_CURL=@NO_CURL@ NO_EXPAT=@NO_EXPAT@ NO_LIBGEN_H=@NO_LIBGEN_H@ HAVE_PATHS_H=@HAVE_PATHS_H@ +NO_GETTEXT=@NO_GETTEXT@ NEEDS_LIBICONV=@NEEDS_LIBICONV@ NEEDS_SOCKET=@NEEDS_SOCKET@ NEEDS_RESOLV=@NEEDS_RESOLV@ +NEEDS_LIBINTL=@NEEDS_LIBINTL@ NEEDS_LIBGEN=@NEEDS_LIBGEN@ NO_SYS_SELECT_H=@NO_SYS_SELECT_H@ NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@ diff --git a/configure.ac b/configure.ac index 5601e8bac9..202e82084e 100644 --- a/configure.ac +++ b/configure.ac @@ -600,6 +600,12 @@ AC_CHECK_LIB([c], [basename], AC_SUBST(NEEDS_LIBGEN) test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen" +AC_CHECK_LIB([c], [gettext], +[NEEDS_LIBINTL=], +[NEEDS_LIBINTL=YesPlease]) +AC_SUBST(NEEDS_LIBINTL) +test -n "$NEEDS_LIBINTL" && LIBS="$LIBS -lintl" + ## Checks for header files. AC_MSG_NOTICE([CHECKS for header files]) # @@ -777,6 +783,12 @@ AC_CHECK_HEADER([paths.h], [HAVE_PATHS_H=]) AC_SUBST(HAVE_PATHS_H) # +# Define NO_GETTEXT if you don't have libintl.h +AC_CHECK_HEADER([libintl.h], +[NO_GETTEXT=], +[NO_GETTEXT=YesPlease]) +AC_SUBST(NO_GETTEXT) +# # Define NO_STRCASESTR if you don't have strcasestr. GIT_CHECK_FUNC(strcasestr, [NO_STRCASESTR=], diff --git a/daemon.c b/daemon.c index e22a2b7fa5..784f8975d4 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 @@ -975,6 +976,8 @@ int main(int argc, char **argv) gid_t gid = 0; int i; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); for (i = 1; i < argc; i++) { diff --git a/fast-import.c b/fast-import.c index ddad289dae..68241e3cf2 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< +#include +#include + +extern void git_setup_gettext(void) { + char *podir; + char *envdir = getenv("GIT_TEXTDOMAINDIR"); + + if (envdir) { + (void)bindtextdomain("git", envdir); + } else { + podir = (char *)system_path("share/locale"); + if (!podir) return; + (void)bindtextdomain("git", podir); + free(podir); + } + + (void)setlocale(LC_MESSAGES, ""); + (void)textdomain("git"); +} diff --git a/gettext.h b/gettext.h new file mode 100644 index 0000000000..e02939ae36 --- /dev/null +++ b/gettext.h @@ -0,0 +1,18 @@ +#ifndef GETTEXT_H +#define GETTEXT_H + +#ifdef NO_GETTEXT +static inline void git_setup_gettext(void) {} +#else +extern void git_setup_gettext(void); +#endif + +#define N_(s) (s) +#ifdef NO_GETTEXT +#define _(s) (s) +#else +#include +#define _(s) gettext(s) +#endif + +#endif diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh new file mode 100644 index 0000000000..698a000fe5 --- /dev/null +++ b/git-sh-i18n.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# +# This is Git's interface to gettext.sh. Use it right after +# git-sh-setup as: +# +# . git-sh-setup +# . git-sh-i18n +# +# # For constant interface messages: +# gettext "A message for the user"; echo +# +# # To interpolate variables: +# details="oh noes" +# eval_gettext "An error occured: \$details"; echo +# +# See "info '(gettext)sh'" for the full manual. + +# Export the TEXTDOMAIN* data that we need for Git +TEXTDOMAIN=git +export TEXTDOMAIN +if [ -z "$GIT_TEXTDOMAINDIR" ] +then + TEXTDOMAINDIR="@@LOCALEDIR@@" +else + TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR" +fi +export TEXTDOMAINDIR + +if test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && type gettext.sh >/dev/null 2>&1 +then + # This is GNU libintl's gettext.sh, we don't need to do anything + # else than setting up the environment and loading gettext.sh + GIT_INTERNAL_GETTEXT_SH_SCHEME=gnu + export GIT_INTERNAL_GETTEXT_SH_SCHEME + + # Try to use libintl's gettext.sh, or fall back to English if we + # can't. + . gettext.sh +elif test -z "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" && test "$(gettext -h 2>&1)" = "-h" +then + # We don't have gettext.sh, but there's a gettext binary in our + # path. This is probably Solaris or something like it which has a + # gettext implementation that isn't GNU libintl. + GIT_INTERNAL_GETTEXT_SH_SCHEME=solaris + export GIT_INTERNAL_GETTEXT_SH_SCHEME + + # Solaris has a gettext(1) but no eval_gettext(1) + eval_gettext () { + gettext_out=$(gettext "$1") + gettext_eval="printf '%s' \"$gettext_out\"" + printf "%s" "`eval \"$gettext_eval\"`" + } +else + # Since gettext.sh isn't available we'll have to define our own + # dummy pass-through functions. + + # Tell our tests that we don't have the real gettext.sh + GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough + export GIT_INTERNAL_GETTEXT_SH_SCHEME + + gettext () { + printf "%s" "$1" + } + + eval_gettext () { + gettext_eval="printf '%s' \"$1\"" + printf "%s" "`eval \"$gettext_eval\"`" + } +fi diff --git a/git.c b/git.c index f37028b3b7..4e31281a76 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" @@ -493,6 +494,8 @@ int main(int argc, const char **argv) if (!cmd) cmd = "git-help"; + git_setup_gettext(); + /* * "git-xxxx" is the same as "git xxxx", but we obviously: * diff --git a/http-backend.c b/http-backend.c index 14c90c2e84..502103d50d 100644 --- a/http-backend.c +++ b/http-backend.c @@ -7,6 +7,7 @@ #include "run-command.h" #include "string-list.h" #include "url.h" +#include "gettext.h" static const char content_type[] = "Content-Type"; static const char content_length[] = "Content-Length"; @@ -550,6 +551,8 @@ int main(int argc, char **argv) char *cmd_arg = NULL; int i; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); set_die_routine(die_webcgi); diff --git a/http-fetch.c b/http-fetch.c index 762c750d7a..b889c36994 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -2,6 +2,7 @@ #include "exec_cmd.h" #include "http.h" #include "walker.h" +#include "gettext.h" static const char http_fetch_usage[] = "git http-fetch " "[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"; @@ -24,6 +25,8 @@ int main(int argc, const char **argv) int get_verbosely = 0; int get_recover = 0; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); while (arg < argc && argv[arg][0] == '-') { diff --git a/http-push.c b/http-push.c index c9bcd11697..fc2c5f7f65 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 @@ -1791,6 +1792,8 @@ int main(int argc, char **argv) struct remote *remote; char *rewritten_url = NULL; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); repo = xcalloc(sizeof(*repo), 1); diff --git a/imap-send.c b/imap-send.c index 71506a8dd3..c9dc7ecd55 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 @@ -1539,6 +1540,8 @@ int main(int argc, char **argv) git_extract_argv0_path(argv[0]); + git_setup_gettext(); + if (argc != 1) usage(imap_send_usage); diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm new file mode 100644 index 0000000000..5918d68847 --- /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. If +L can't be loaded (it's not a core module) we +provide stub passthrough fallbacks. + +This is a distilled interface to gettext, see C +for the full interface. This module implements only a small part of +it. + +=head1 FUNCTIONS + +=head2 __($) + +L's gettext function if all goes well, otherwise our +passthrough fallback function. + +=head1 AUTHOR + +Evar ArnfjErE Bjarmason + +=head1 COPYRIGHT + +Copyright 2010 Evar ArnfjErE Bjarmason + +=cut diff --git a/perl/Makefile b/perl/Makefile index 4ab21d61b8..4e624fff90 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 0b9deca2cc..456d45bf40 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 0000000000..221000e1b0 --- /dev/null +++ b/po/.gitignore @@ -0,0 +1 @@ +/*.pot diff --git a/po/is.po b/po/is.po new file mode 100644 index 0000000000..95739f1faa --- /dev/null +++ b/po/is.po @@ -0,0 +1,47 @@ +msgid "" +msgstr "" +"Project-Id-Version: Git\n" +"PO-Revision-Date: 2010-06-05 19:06 +0000\n" +"Language-Team: Git Mailing List \n" +"Report-Msgid-Bugs-To: Git Mailing List \n" +"Last-Translator: Ævar Arnfjörð Bjarmason \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: t/t0200/test.c:4 +msgid "See git help COMMAND for more information on a specific command." +msgstr "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun." + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:9 +msgid "TEST: A C test string" +msgstr "TILRAUN: C tilraunastrengur" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.c:12 +#, c-format +msgid "TEST: A C test string %s" +msgstr "TILRAUN: C tilraunastrengur %s" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.sh:8 +msgid "TEST: A Shell test string" +msgstr "TILRAUN: Skeljartilraunastrengur" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.sh:11 +#, sh-format +msgid "TEST: A Shell test $variable" +msgstr "TILRAUN: Skeljartilraunastrengur með breytunni $variable" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.perl:8 +msgid "TEST: A Perl test string" +msgstr "TILRAUN: Perl tilraunastrengur" + +#. TRANSLATORS: This is a test. You don't need to translate it. +#: t/t0200/test.perl:11 +#, perl-format +msgid "TEST: A Perl test variable %s" +msgstr "TILRAUN: Perl tilraunastrengur með breytunni %s" diff --git a/shell.c b/shell.c index e4864e04da..ba27c6be83 100644 --- a/shell.c +++ b/shell.c @@ -2,6 +2,7 @@ #include "quote.h" #include "exec_cmd.h" #include "strbuf.h" +#include "gettext.h" static int do_generic_cmd(const char *me, char *arg) { @@ -51,6 +52,8 @@ int main(int argc, char **argv) struct commands *cmd; int devnull_fd; + git_setup_gettext(); + /* * Always open file descriptors 0/1/2 to avoid clobbering files * in die(). It also avoids not messing up when the pipes are diff --git a/show-index.c b/show-index.c index 4c0ac138af..c2f5448536 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 < "; @@ -11,6 +12,8 @@ int main(int argc, char **argv) unsigned int version; static unsigned int top_index[256]; + git_setup_gettext(); + if (argc != 1) usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh new file mode 100644 index 0000000000..f0cdd3da93 --- /dev/null +++ b/t/lib-gettext.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +. ./test-lib.sh + +GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/share/locale" +GIT_PO_PATH="$GIT_BUILD_DIR/po" +export GIT_TEXTDOMAINDIR GIT_PO_PATH + +. "$GIT_BUILD_DIR"/git-sh-i18n + +if test_have_prereq GETTEXT +then + # is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian + is_IS_locale=$(locale -a | sed -n '/^is_IS\.[uU][tT][fF]-*8$/{ + p + q + }') + # Export it as an environmental variable so the t0202/test.pl Perl + # test can use it too + export is_IS_locale + + if test -n "$is_IS_locale" && + test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough" + then + # Some of the tests need the reference Icelandic locale + test_set_prereq GETTEXT_LOCALE + + # Exporting for t0202/test.pl + GETTEXT_LOCALE=1 + export GETTEXT_LOCALE + say "# lib-gettext: Found '$is_IS_locale' as a is_IS UTF-8 locale" + else + say "# lib-gettext: No is_IS UTF-8 locale available" + fi +else + # Only run some tests when we don't have gettext support + test_set_prereq NO_GETTEXT + say "# lib-gettext: No GETTEXT support available" +fi diff --git a/t/t0200-gettext-basic.sh b/t/t0200-gettext-basic.sh new file mode 100755 index 0000000000..522338d13f --- /dev/null +++ b/t/t0200-gettext-basic.sh @@ -0,0 +1,113 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description='Gettext support for Git' + +. ./lib-gettext.sh + +test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" ' + test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME" +' + +test_expect_success 'sanity: $TEXTDOMAIN is git' ' + test $TEXTDOMAIN = "git" +' + +test_expect_success 'xgettext sanity: Perl _() strings are not extracted' ' + ! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po +' + +test_expect_success 'xgettext sanity: Comment extraction with --add-comments' ' + grep "TRANSLATORS: This is a test" "$TEST_DIRECTORY"/t0200/* | wc -l >expect && + grep "TRANSLATORS: This is a test" "$GIT_PO_PATH"/is.po | wc -l >actual && + test_cmp expect actual +' + +test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' ' + ! grep "This is a phony" "$GIT_PO_PATH"/is.po && + ! grep "the above comment" "$GIT_PO_PATH"/is.po +' + +test_expect_success GETTEXT 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' ' + test -d "$TEXTDOMAINDIR" && + test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR" +' + +test_expect_success GETTEXT 'sanity: Icelandic locale was compiled' ' + test -f "$TEXTDOMAINDIR/is/LC_MESSAGES/git.mo" +' + +test_expect_success NO_GETTEXT "sanity: \$TEXTDOMAINDIR doesn't exists with NO_GETTEXT=YesPlease" ' + ! test -d "$TEXTDOMAINDIR" && + test "$TEXTDOMAINDIR" = "$GIT_TEXTDOMAINDIR" +' + +# 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 GETTEXT_LOCALE 'sanity: gettext("") metadata is OK' ' + # Return value may be non-zero + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >zero-expect && + grep "Project-Id-Version: Git" zero-expect && + grep "Git Mailing List " zero-expect && + grep "Content-Type: text/plain; charset=UTF-8" zero-expect && + grep "Content-Transfer-Encoding: 8bit" zero-expect +' + +test_expect_success GETTEXT_LOCALE '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 GETTEXT_LOCALE 'xgettext: C extraction of _() and N_() strings' ' + printf "TILRAUN: C tilraunastrengur" >expect && + printf "\n" >>expect && + printf "Sjá git help SKIPUN til að sjá hjálp fyrir tiltekna skipun." >>expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string" >actual && + printf "\n" >>actual && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "See git help COMMAND for more information on a specific command." >>actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'xgettext: C extraction with %s' ' + printf "TILRAUN: C tilraunastrengur %%s" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A C test string %s" >actual && + test_cmp expect actual +' + +# xgettext from Shell +test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction' ' + printf "TILRAUN: Skeljartilraunastrengur" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Shell test string" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'xgettext: Shell extraction with $variable' ' + printf "TILRAUN: Skeljartilraunastrengur með breytunni a var i able" >x-expect && + LANGUAGE=is LC_ALL="$is_IS_locale" 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 GETTEXT_LOCALE 'xgettext: Perl extraction' ' + printf "TILRAUN: Perl tilraunastrengur" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test string" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'xgettext: Perl extraction with %s' ' + printf "TILRAUN: Perl tilraunastrengur með breytunni %%s" >expect && + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: A Perl test variable %s" >actual && + test_cmp expect actual +' + +test_expect_success GETTEXT_LOCALE 'sanity: Some gettext("") data for real locale' ' + LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >real-locale && + test -s real-locale +' + +test_done diff --git a/t/t0200/test.c b/t/t0200/test.c new file mode 100644 index 0000000000..93373b38f7 --- /dev/null +++ b/t/t0200/test.c @@ -0,0 +1,13 @@ +/* This is a phony C program that's only here to test xgettext message extraction */ + +const char help[] = + N_("See 'git help COMMAND' for more information on a specific command."); + +int main(void) +{ + /* TRANSLATORS: This is a test. You don't need to translate it. */ + puts(_("TEST: A C test string")); + + /* TRANSLATORS: This is a test. You don't need to translate it. */ + printf(_("TEST: A C test string %s"), "variable"); +} diff --git a/t/t0200/test.perl b/t/t0200/test.perl new file mode 100644 index 0000000000..36fba341ba --- /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 0000000000..022d607f4c --- /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 0000000000..47ce4f6b6e --- /dev/null +++ b/t/t0201-gettext-fallbacks.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description='Gettext Shell fallbacks' + +GIT_INTERNAL_GETTEXT_TEST_FALLBACKS=YesPlease +export GIT_INTERNAL_GETTEXT_TEST_FALLBACKS + +. ./lib-gettext.sh + +test_expect_success "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" ' + test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME" +' + +test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_TEST_FALLBACKS is set' ' + test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" +' + +test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' ' + test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "fallthrough" +' + +test_expect_success 'gettext: our gettext() fallback has pass-through semantics' ' + printf "test" >expect && + gettext "test" >actual && + test_cmp expect actual && + printf "test more words" >expect && + gettext "test more words" >actual && + test_cmp expect actual +' + +test_expect_success 'eval_gettext: our eval_gettext() fallback has pass-through semantics' ' + printf "test" >expect && + eval_gettext "test" >actual && + test_cmp expect actual && + printf "test more words" >expect && + eval_gettext "test more words" >actual && + test_cmp expect actual +' + +test_expect_success 'eval_gettext: our eval_gettext() fallback can interpolate variables' ' + printf "test YesPlease" >expect && + eval_gettext "test \$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh new file mode 100755 index 0000000000..428ebb0080 --- /dev/null +++ b/t/t0202-gettext-perl.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description='Perl gettext interface (Git::I18N)' + +. ./lib-gettext.sh + +if ! test_have_prereq PERL; then + skip_all='skipping perl interface tests, perl not available' + test_done +fi + +"$PERL_PATH" -MTest::More -e 0 2>/dev/null || { + skip_all="Perl Test::More unavailable, skipping test" + test_done +} + +# The external test will outputs its own plan +test_external_has_tap=1 + +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 0000000000..c2055fa8de --- /dev/null +++ b/t/t0202/test.pl @@ -0,0 +1,107 @@ +#!/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{GETTEXT_LOCALE}) { + # 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 "GETTEXT_LOCALE must be set by lib-gettext.sh for exhaustive Git::I18N tests", 2; + } + + # The is_IS UTF-8 locale passed from lib-gettext.sh + my $is_IS_locale = $ENV{is_IS_locale}; + + 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_locale; + }; + + # 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_locale); + } + } else { + { + local %ENV; $env_C->(); + $test->($got, $expect, "Without", 'C'); + } + + { + local %ENV; $env_is->(); + $test->($got, $expect, "Without", 'is'); + } + } +} diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh new file mode 100755 index 0000000000..a212460081 --- /dev/null +++ b/t/t0203-gettext-setlocale-sanity.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright (c) 2010 Ævar Arnfjörð Bjarmason +# + +test_description="The Git C functions aren't broken by setlocale(3)" + +. ./lib-gettext.sh + +test_expect_success 'git show a ISO-8859-1 commit under C locale' ' + . "$TEST_DIRECTORY"/t3901-8859-1.txt && + test_commit "iso-c-commit" iso-under-c && + git show >out 2>err && + ! test -s err && + grep -q "iso-c-commit" out +' + +test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' ' + . "$TEST_DIRECTORY"/t3901-8859-1.txt && + test_commit "iso-utf8-commit" iso-under-utf8 && + LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err && + ! test -s err && + grep -q "iso-utf8-commit" out +' + +test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 46179988a3..ab4c96788e 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 @@ -793,6 +794,7 @@ else # normal case, use ../bin-wrappers only unless $with_dashes: PATH="$TEST_DIRECTORY/..:$PATH" fi fi +GIT_BUILD_DIR=$(pwd)/.. GIT_TEMPLATE_DIR=$(pwd)/../templates/blt unset GIT_CONFIG GIT_CONFIG_NOSYSTEM=1 @@ -908,6 +910,7 @@ esac test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PYTHON" && test_set_prereq PYTHON +test -z "$NO_GETTEXT" && test_set_prereq GETTEXT # test whether the filesystem supports symbolic links ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS diff --git a/upload-pack.c b/upload-pack.c index fc79ddef25..375c8a8188 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] "; @@ -682,6 +683,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;