i18n: add infrastructure for translating Git with gettext

Change the skeleton implementation of i18n in Git to one that can show
localized strings to users for our C, Shell and Perl programs using
either GNU libintl or the Solaris gettext implementation.

This new internationalization support is enabled by default. If
gettext isn't available, or if Git is compiled with
NO_GETTEXT=YesPlease, Git falls back on its current behavior of
showing interface messages in English. When using the autoconf script
we'll auto-detect if the gettext libraries are installed and act
appropriately.

This change is somewhat large because as well as adding a C, Shell and
Perl i18n interface we're adding a lot of tests for them, and for
those tests to work we need a skeleton PO file to actually test
translations. A minimal Icelandic translation is included for this
purpose. Icelandic includes multi-byte characters which makes it easy
to test various edge cases, and it's a language I happen to
understand.

The rest of the commit message goes into detail about various
sub-parts of this commit.

= Installation

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

= Perl

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

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

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

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

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

= Shell

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

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

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

= About libcharset.h and langinfo.h

We use libcharset to query the character set of the current locale if
it's available. I.e. we'll use it instead of nl_langinfo if
HAVE_LIBCHARSET_H is set.

The GNU gettext manual recommends using langinfo.h's
nl_langinfo(CODESET) to acquire the current character set, but on
systems that have libcharset.h's locale_charset() using the latter is
either saner, or the only option on those systems.

GNU and Solaris have a nl_langinfo(CODESET), FreeBSD can use either,
but MinGW and some others need to use libcharset.h's locale_charset()
instead.

=Credits

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

[jc: squashed a small Makefile fix from Ramsay]

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Ramsay Jones <ramsay@ramsay1.demon.co.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Ævar Arnfjörð Bjarmason 2011-11-18 00:14:42 +01:00 committed by Junio C Hamano
parent bc1bbe0c19
commit 5e9637c629
37 changed files with 1291 additions and 39 deletions

View File

@ -81,6 +81,10 @@ For shell scripts specifically (not exhaustive):
are ERE elements not BRE (note that \? and \+ are not even part
of BRE -- making them accessible from BRE is a GNU extension).
- Use Git's gettext wrappers in git-sh-i18n to make the user
interface translatable. See "Marking strings for translation" in
po/README.
For C programs:
- We use tabs to indent, and interpret tabs as taking up to
@ -144,6 +148,9 @@ For C programs:
- When we pass <string, length> pair to functions, we should try to
pass them in that order.
- Use Git's gettext wrappers to make the user interface
translatable. See "Marking strings for translation" in po/README.
Writing Documentation:
Every user-visible change should be reflected in the documentation.

12
INSTALL
View File

@ -106,6 +106,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

View File

@ -43,6 +43,22 @@ all::
# Define EXPATDIR=/foo/bar if your expat header and library files are in
# /foo/bar/include and /foo/bar/lib directories.
#
# Define NO_GETTEXT if you don't want Git output to be translated.
# A translated Git requires GNU libintl or another gettext implementation,
# plus libintl-perl at runtime.
#
# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
# trust the langinfo.h's nl_langinfo(CODESET) function to return the
# current character set. GNU and Solaris have a nl_langinfo(CODESET),
# FreeBSD can use either, but MinGW and some others need to use
# libcharset.h's locale_charset() instead.
#
# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
# need -lintl when linking.
#
# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
# doesn't support GNU extensions like --check and --statistics
#
# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
# it specifies.
#
@ -301,6 +317,7 @@ gitexecdir = libexec/git-core
mergetoolsdir = $(gitexecdir)/mergetools
sharedir = $(prefix)/share
gitwebdir = $(sharedir)/gitweb
localedir = $(sharedir)/locale
template_dir = share/git-core/templates
htmldir = share/doc/git-doc
ETC_GITCONFIG = $(sysconfdir)/gitconfig
@ -309,7 +326,7 @@ lib = lib
# DESTDIR=
pathsep = :
export prefix bindir sharedir sysconfdir gitwebdir
export prefix bindir sharedir sysconfdir gitwebdir localedir
CC = gcc
AR = ar
@ -322,6 +339,7 @@ RPMBUILD = rpmbuild
TCL_PATH = tclsh
TCLTK_PATH = wish
XGETTEXT = xgettext
MSGFMT = msgfmt
PTHREAD_LIBS = -lpthread
PTHREAD_CFLAGS =
GCOV = gcov
@ -621,6 +639,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
@ -817,12 +836,14 @@ ifeq ($(uname_S),Linux)
NO_STRLCPY = YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
LIBC_CONTAINS_LIBINTL = YesPlease
endif
ifeq ($(uname_S),GNU/kFreeBSD)
NO_STRLCPY = YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
LIBC_CONTAINS_LIBINTL = YesPlease
endif
ifeq ($(uname_S),UnixWare)
CC = cc
@ -889,6 +910,7 @@ ifeq ($(uname_S),SunOS)
NO_MKSTEMPS = YesPlease
NO_REGEX = YesPlease
NO_FNMATCH_CASEFOLD = YesPlease
NO_MSGFMT_EXTENDED_OPTIONS = YesPlease
ifeq ($(uname_R),5.6)
SOCKLEN_T = int
NO_HSTRERROR = YesPlease
@ -1012,6 +1034,7 @@ ifeq ($(uname_S),GNU)
NO_STRLCPY=YesPlease
NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease
LIBC_CONTAINS_LIBINTL = YesPlease
endif
ifeq ($(uname_S),IRIX)
NO_SETENV = YesPlease
@ -1228,6 +1251,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
EXTLIBS += /mingw/lib/libz.a
NO_R_TO_GCC_LINKER = YesPlease
INTERNAL_QSORT = YesPlease
HAVE_LIBCHARSET_H = YesPlease
else
NO_CURL = YesPlease
endif
@ -1405,6 +1429,11 @@ endif
ifdef NEEDS_LIBGEN
EXTLIBS += -lgen
endif
ifndef NO_GETTEXT
ifndef LIBC_CONTAINS_LIBINTL
EXTLIBS += -lintl
endif
endif
ifdef NEEDS_SOCKET
EXTLIBS += -lsocket
endif
@ -1447,9 +1476,11 @@ ifdef NO_SYMLINK_HEAD
BASIC_CFLAGS += -DNO_SYMLINK_HEAD
endif
ifdef GETTEXT_POISON
LIB_OBJS += gettext.o
BASIC_CFLAGS += -DGETTEXT_POISON
endif
ifdef NO_GETTEXT
BASIC_CFLAGS += -DNO_GETTEXT
endif
ifdef NO_STRCASESTR
COMPAT_CFLAGS += -DNO_STRCASESTR
COMPAT_OBJS += compat/strcasestr.o
@ -1612,6 +1643,10 @@ ifdef HAVE_PATHS_H
BASIC_CFLAGS += -DHAVE_PATHS_H
endif
ifdef HAVE_LIBCHARSET_H
BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
endif
ifdef DIR_HAS_BSD_GROUP_SEMANTICS
COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
endif
@ -1632,6 +1667,10 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
export GIT_TEST_CMP_USE_COPIED_CONTEXT
endif
ifndef NO_MSGFMT_EXTENDED_OPTIONS
MSGFMT += --check --statistics
endif
ifeq ($(TCLTK_PATH),)
NO_TCLTK=NoThanks
endif
@ -1662,6 +1701,7 @@ ifndef V
QUIET_GEN = @echo ' ' GEN $@;
QUIET_LNCP = @echo ' ' LN/CP $@;
QUIET_XGETTEXT = @echo ' ' XGETTEXT $@;
QUIET_MSGFMT = @echo ' ' MSGFMT $@;
QUIET_GCOV = @echo ' ' GCOV $@;
QUIET_SP = @echo ' ' SP $<;
QUIET_SUBDIR0 = +@subdir=
@ -1688,6 +1728,7 @@ bindir_SQ = $(subst ','\'',$(bindir))
bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
mandir_SQ = $(subst ','\'',$(mandir))
infodir_SQ = $(subst ','\'',$(infodir))
localedir_SQ = $(subst ','\'',$(localedir))
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
template_dir_SQ = $(subst ','\'',$(template_dir))
htmldir_SQ = $(subst ','\'',$(htmldir))
@ -1743,7 +1784,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
@ -1793,6 +1834,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 >$@+
@ -2045,6 +2087,9 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \
attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
-DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
-DGIT_LOCALE_PATH='"$(localedir_SQ)"'
http.sp http.s http.o: EXTRA_CPPFLAGS = \
-DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
@ -2118,17 +2163,37 @@ XGETTEXT_FLAGS = \
XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
--keyword=_ --keyword=N_ --keyword="Q_:1,2"
XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
LOCALIZED_C := $(C_OBJ:o=c)
LOCALIZED_SH := $(SCRIPT_SH)
LOCALIZED_PERL := $(SCRIPT_PERL)
ifdef XGETTEXT_INCLUDE_TESTS
LOCALIZED_C += t/t0200/test.c
LOCALIZED_SH += t/t0200/test.sh
LOCALIZED_PERL += t/t0200/test.perl
endif
po/git.pot: $(LOCALIZED_C)
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
$(LOCALIZED_SH)
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \
$(LOCALIZED_PERL)
mv $@+ $@
pot: po/git.pot
POFILES := $(wildcard po/*.po)
MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES))
ifndef NO_GETTEXT
all:: $(MOFILES)
endif
po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
$(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
$(FIND) . \( -name .git -type d -prune \) \
-o \( -name '*.[hcS]' -type f -print \) )
@ -2147,7 +2212,8 @@ cscope:
### Detect prefix changes
TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
$(localedir_SQ)
GIT-CFLAGS: FORCE
@FLAGS='$(TRACK_CFLAGS)'; \
@ -2184,6 +2250,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)))'\' >>$@
@echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
### Detect Tck/Tk interpreter path changes
@ -2299,6 +2366,11 @@ install: all
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
$(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
ifndef NO_GETTEXT
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)'
(cd po/build/locale && $(TAR) cf - .) | \
(cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -)
endif
ifndef NO_PERL
$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
$(MAKE) -C gitweb install
@ -2435,6 +2507,7 @@ clean:
$(RM) $(TEST_PROGRAMS)
$(RM) -r bin-wrappers
$(RM) -r $(dep_dirs)
$(RM) -r po/build/
$(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
$(RM) -r autom4te.cache
$(RM) config.log config.mak.autogen config.mak.append config.status config.cache

View File

@ -35,6 +35,9 @@ NO_CURL=@NO_CURL@
NO_EXPAT=@NO_EXPAT@
NO_LIBGEN_H=@NO_LIBGEN_H@
HAVE_PATHS_H=@HAVE_PATHS_H@
HAVE_LIBCHARSET_H=@HAVE_LIBCHARSET_H@
NO_GETTEXT=@NO_GETTEXT@
LIBC_CONTAINS_LIBINTL=@LIBC_CONTAINS_LIBINTL@
NEEDS_LIBICONV=@NEEDS_LIBICONV@
NEEDS_SOCKET=@NEEDS_SOCKET@
NEEDS_RESOLV=@NEEDS_RESOLV@

View File

@ -636,6 +636,12 @@ AC_CHECK_LIB([c], [basename],
AC_SUBST(NEEDS_LIBGEN)
test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
AC_CHECK_LIB([c], [gettext],
[LIBC_CONTAINS_LIBINTL=YesPlease],
[LIBC_CONTAINS_LIBINTL=])
AC_SUBST(LIBC_CONTAINS_LIBINTL)
test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
## Checks for header files.
AC_MSG_NOTICE([CHECKS for header files])
#
@ -818,6 +824,19 @@ AC_CHECK_HEADER([paths.h],
[HAVE_PATHS_H=])
AC_SUBST(HAVE_PATHS_H)
#
# Define NO_GETTEXT if you don't want Git output to be translated.
# A translated Git requires GNU libintl or another gettext implementation
AC_CHECK_HEADER([libintl.h],
[NO_GETTEXT=],
[NO_GETTEXT=YesPlease])
AC_SUBST(NO_GETTEXT)
#
# Define HAVE_LIBCHARSET_H if have libcharset.h
AC_CHECK_HEADER([libcharset.h],
[HAVE_LIBCHARSET_H=YesPlease],
[HAVE_LIBCHARSET_H=])
AC_SUBST(HAVE_LIBCHARSET_H)
#
# Define NO_STRCASESTR if you don't have strcasestr.
GIT_CHECK_FUNC(strcasestr,
[NO_STRCASESTR=],

View File

@ -1099,6 +1099,8 @@ int main(int argc, char **argv)
struct credentials *cred = NULL;
int i;
git_setup_gettext();
git_extract_argv0_path(argv[0]);
for (i = 1; i < argc; i++) {

View File

@ -3292,6 +3292,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);

117
gettext.c
View File

@ -5,6 +5,18 @@
#include "git-compat-util.h"
#include "gettext.h"
#ifndef NO_GETTEXT
# include <locale.h>
# include <libintl.h>
# ifdef HAVE_LIBCHARSET_H
# include <libcharset.h>
# else
# include <langinfo.h>
# define locale_charset() nl_langinfo(CODESET)
# endif
#endif
#ifdef GETTEXT_POISON
int use_gettext_poison(void)
{
static int poison_requested = -1;
@ -12,3 +24,108 @@ int use_gettext_poison(void)
poison_requested = getenv("GIT_GETTEXT_POISON") ? 1 : 0;
return poison_requested;
}
#endif
#ifndef NO_GETTEXT
static void init_gettext_charset(const char *domain)
{
const char *charset;
/*
This trick arranges for messages to be emitted in the user's
requested encoding, but avoids setting LC_CTYPE from the
environment for the whole program.
This primarily done to avoid a bug in vsnprintf in the GNU C
Library [1]. which triggered a "your vsnprintf is broken" error
on Git's own repository when inspecting v0.99.6~1 under a UTF-8
locale.
That commit contains a ISO-8859-1 encoded author name, which
the locale aware vsnprintf(3) won't interpolate in the format
argument, due to mismatch between the data encoding and the
locale.
Even if it wasn't for that bug we wouldn't want to use LC_CTYPE at
this point, because it'd require auditing all the code that uses C
functions whose semantics are modified by LC_CTYPE.
But only setting LC_MESSAGES as we do creates a problem, since
we declare the encoding of our PO files[2] the gettext
implementation will try to recode it to the user's locale, but
without LC_CTYPE it'll emit something like this on 'git init'
under the Icelandic locale:
Bj? til t?ma Git lind ? /hlagh/.git/
Gettext knows about the encoding of our PO file, but we haven't
told it about the user's encoding, so all the non-US-ASCII
characters get encoded to question marks.
But we're in luck! We can set LC_CTYPE from the environment
only while we call nl_langinfo and
bind_textdomain_codeset. That suffices to tell gettext what
encoding it should emit in, so it'll now say:
Bjó til tóma Git lind í /hlagh/.git/
And the equivalent ISO-8859-1 string will be emitted under a
ISO-8859-1 locale.
With this change way we get the advantages of setting LC_CTYPE
(talk to the user in his language/encoding), without the major
drawbacks (changed semantics for C functions we rely on).
However foreign functions using other message catalogs that
aren't using our neat trick will still have a problem, e.g. if
we have to call perror(3):
#include <stdio.h>
#include <locale.h>
#include <errno.h>
int main(void)
{
setlocale(LC_MESSAGES, "");
setlocale(LC_CTYPE, "C");
errno = ENODEV;
perror("test");
return 0;
}
Running that will give you a message with question marks:
$ LANGUAGE= LANG=de_DE.utf8 ./test
test: Kein passendes Ger?t gefunden
In the long term we should probably see about getting that
vsnprintf bug in glibc fixed, and audit our code so it won't
fall apart under a non-C locale.
Then we could simply set LC_CTYPE from the environment, which would
make things like the external perror(3) messages work.
See t/t0203-gettext-setlocale-sanity.sh's "gettext.c" tests for
regression tests.
1. http://sourceware.org/bugzilla/show_bug.cgi?id=6530
2. E.g. "Content-Type: text/plain; charset=UTF-8\n" in po/is.po
*/
setlocale(LC_CTYPE, "");
charset = locale_charset();
bind_textdomain_codeset(domain, charset);
setlocale(LC_CTYPE, "C");
}
void git_setup_gettext(void)
{
const char *podir = getenv("GIT_TEXTDOMAINDIR");
if (!podir)
podir = GIT_LOCALE_PATH;
bindtextdomain("git", podir);
setlocale(LC_MESSAGES, "");
init_gettext_charset("git");
textdomain("git");
}
#endif

View File

@ -13,8 +13,29 @@
#error "namespace conflict: '_' or 'Q_' is pre-defined?"
#endif
#ifndef NO_GETTEXT
# include <libintl.h>
#else
# ifdef gettext
# undef gettext
# endif
# define gettext(s) (s)
# ifdef ngettext
# undef ngettext
# endif
# define ngettext(s, p, n) ((n == 1) ? (s) : (p))
#endif
#define FORMAT_PRESERVING(n) __attribute__((format_arg(n)))
#ifndef NO_GETTEXT
extern void git_setup_gettext(void);
#else
static inline void git_setup_gettext(void)
{
}
#endif
#ifdef GETTEXT_POISON
extern int use_gettext_poison(void);
#else
@ -23,7 +44,7 @@ extern int use_gettext_poison(void);
static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)
{
return use_gettext_poison() ? "# GETTEXT POISON #" : msgid;
return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid);
}
static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)
@ -31,7 +52,7 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n)
{
if (use_gettext_poison())
return "# GETTEXT POISON #";
return n == 1 ? msgid : plu;
return ngettext(msgid, plu, n);
}
/* Mark msgid for translation but do not translate it. */

View File

@ -2,47 +2,91 @@
#
# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
#
# This is a skeleton no-op implementation of gettext for Git. It'll be
# replaced by something that uses gettext.sh in a future patch series.
# This is Git's interface to gettext.sh. See po/README for usage
# instructions.
# Export the TEXTDOMAIN* data that we need for Git
TEXTDOMAIN=git
export TEXTDOMAIN
if test -z "$GIT_TEXTDOMAINDIR"
then
TEXTDOMAINDIR="@@LOCALEDIR@@"
else
TEXTDOMAINDIR="$GIT_TEXTDOMAINDIR"
fi
export TEXTDOMAINDIR
if test -z "$GIT_GETTEXT_POISON"
then
gettext () {
printf "%s" "$1"
}
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
gettextln() {
printf "%s\n" "$1"
}
# Try to use libintl's gettext.sh, or fall back to English if we
# can't.
. gettext.sh
eval_gettext () {
printf "%s" "$1" | (
export PATH $(git sh-i18n--envsubst --variables "$1");
git sh-i18n--envsubst "$1"
)
}
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
eval_gettextln () {
printf "%s\n" "$1" | (
export PATH $(git sh-i18n--envsubst --variables "$1");
git sh-i18n--envsubst "$1"
)
}
# Solaris has a gettext(1) but no eval_gettext(1)
eval_gettext () {
gettext "$1" | (
export PATH $(git sh-i18n--envsubst --variables "$1");
git sh-i18n--envsubst "$1"
)
}
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 () {
printf "%s" "$1" | (
export PATH $(git sh-i18n--envsubst --variables "$1");
git sh-i18n--envsubst "$1"
)
}
fi
else
# Emit garbage under GETTEXT_POISON=YesPlease. Unlike the C tests
# this relies on an environment variable
GIT_INTERNAL_GETTEXT_SH_SCHEME=poison
export GIT_INTERNAL_GETTEXT_SH_SCHEME
gettext () {
printf "%s" "# GETTEXT POISON #"
}
gettextln () {
printf "%s\n" "# GETTEXT POISON #"
}
eval_gettext () {
printf "%s" "# GETTEXT POISON #"
}
eval_gettextln () {
printf "%s\n" "# GETTEXT POISON #"
}
fi
# Git-specific wrapper functions
gettextln () {
gettext "$1"
echo
}
eval_gettextln () {
eval_gettext "$1"
echo
}

2
git.c
View File

@ -537,6 +537,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:
*

View File

@ -545,6 +545,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);

View File

@ -22,6 +22,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] == '-') {

View File

@ -1748,6 +1748,8 @@ int main(int argc, char **argv)
int new_refs;
struct ref *ref, *local_refs;
git_setup_gettext();
git_extract_argv0_path(argv[0]);
repo = xcalloc(sizeof(*repo), 1);

View File

@ -1539,6 +1539,8 @@ int main(int argc, char **argv)
git_extract_argv0_path(argv[0]);
git_setup_gettext();
if (argc != 1)
usage(imap_send_usage);

89
perl/Git/I18N.pm Normal file
View File

@ -0,0 +1,89 @@
package Git::I18N;
use 5.008;
use strict;
use warnings;
use Exporter 'import';
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();
*__ = \&Locale::Messages::gettext;
1;
} or do {
# Tell test.pl that we couldn't load the gettext library.
$Git::I18N::__HAS_LIBRARY = 0;
# Just a fall-through no-op
*__ = sub ($) { $_[0] };
};
}
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

View File

@ -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)' INSTALL_BASE=''
$(PERL_PATH) $< PREFIX='$(prefix_SQ)' INSTALL_BASE='' --localedir='$(localedir_SQ)'
endif
# this is just added comfort for calling make directly in perl dir

View File

@ -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'
);

1
po/.gitignore vendored
View File

@ -1 +1,2 @@
/git.pot
/build

229
po/README Normal file
View File

@ -0,0 +1,229 @@
Core GIT Translations
=====================
This directory holds the translations for the core of Git. This
document describes how to add to and maintain these translations, and
how to mark source strings for translation.
Generating a .pot file
----------------------
The po/git.pot file contains a message catalog extracted from Git's
sources. You need to generate it to add new translations with
msginit(1), or update existing ones with msgmerge(1).
Since the file can be automatically generated it's not checked into
git.git. To generate it do, at the top-level:
make pot
Initializing a .po file
-----------------------
To add a new translation first generate git.pot (see above) and then
in the po/ directory do:
msginit --locale=XX
Where XX is your locale, e.g. "is", "de" or "pt_BR".
Then edit the automatically generated copyright info in your new XX.po
to be correct, e.g. for Icelandic:
@@ -1,6 +1,6 @@
-# Icelandic translations for PACKAGE package.
-# Copyright (C) 2010 THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
+# Icelandic translations for Git.
+# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+# This file is distributed under the same license as the Git package.
# Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
And change references to PACKAGE VERSION in the PO Header Entry to
just "Git":
perl -pi -e 's/(?<="Project-Id-Version: )PACKAGE VERSION/Git/' XX.po
Updating a .po file
-------------------
If there's an existing *.po file for your language but you need to
update the translation you first need to generate git.pot (see above)
and then in the po/ directory do:
msgmerge --add-location --backup=off -U XX.po git.pot
Where XX.po is the file you want to update.
Testing your changes
--------------------
Before you submit your changes go back to the top-level and do:
make
On systems with GNU gettext (i.e. not Solaris) this will compile your
changed PO file with `msgfmt --check`, the --check option flags many
common errors, e.g. missing printf format strings, or translated
messages that deviate from the originals in whether they begin/end
with a newline or not.
Marking strings for translation
-------------------------------
Before strings can be translated they first have to be marked for
translation.
Git uses an internationalization interface that wraps the system's
gettext library, so most of the advice in your gettext documentation
(on GNU systems `info gettext` in a terminal) applies.
General advice:
- Don't mark everything for translation, only strings which will be
read by humans (the porcelain interface) should be translated.
The output from Git's plumbing utilities will primarily be read by
programs and would break scripts under non-C locales if it was
translated. Plumbing strings should not be translated, since
they're part of Git's API.
- Adjust the strings so that they're easy to translate. Most of the
advice in `info '(gettext)Preparing Strings'` applies here.
- If something is unclear or ambiguous you can use a "TRANSLATORS"
comment to tell the translators what to make of it. These will be
extracted by xgettext(1) and put in the po/*.po files, e.g. from
git-am.sh:
# TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
# in your translation. The program will only accept English
# input at this point.
gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
Or in C, from builtin/revert.c:
/* TRANSLATORS: %s will be "revert" or "cherry-pick" */
die(_("%s: Unable to write new index file"), action_name(opts));
We provide wrappers for C, Shell and Perl programs. Here's how they're
used:
C:
- Include builtin.h at the top, it'll pull in in gettext.h, which
defines the gettext interface. Consult with the list if you need to
use gettext.h directly.
- The C interface is a subset of the normal GNU gettext
interface. We currently export these functions:
- _()
Mark and translate a string. E.g.:
printf(_("HEAD is now at %s"), hex);
- Q_()
Mark and translate a plural string. E.g.:
printf(Q_("%d commit", "%d commits", number_of_commits));
This is just a wrapper for the ngettext() function.
- N_()
A no-op pass-through macro for marking strings inside static
initializations, e.g.:
static const char *reset_type_names[] = {
N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL
};
And then, later:
die(_("%s reset is not allowed in a bare repository"),
_(reset_type_names[reset_type]));
Here _() couldn't have statically determined what the translation
string will be, but since it was already marked for translation
with N_() the look-up in the message catalog will succeed.
Shell:
- The Git gettext shell interface is just a wrapper for
gettext.sh. Import it right after git-sh-setup like this:
. git-sh-setup
. git-sh-i18n
And then use the gettext or eval_gettext functions:
# For constant interface messages:
gettext "A message for the user"; echo
# To interpolate variables:
details="oh noes"
eval_gettext "An error occured: \$details"; echo
In addition we have wrappers for messages that end with a trailing
newline. I.e. you could write the above as:
# For constant interface messages:
gettextln "A message for the user"
# To interpolate variables:
details="oh noes"
eval_gettextln "An error occured: \$details"
More documentation about the interface is available in the GNU info
page: `info '(gettext)sh'`. Looking at git-am.sh (the first shell
command to be translated) for examples is also useful:
git log --reverse -p --grep=i18n git-am.sh
Perl:
- The Git::I18N module provides a limited subset of the
Locale::Messages functionality, e.g.:
use Git::I18N;
print __("Welcome to Git!\n");
printf __("The following error occured: %s\n"), $error;
Run `perldoc perl/Git/I18N.pm` for more info.
Testing marked strings
----------------------
Even if you've correctly marked porcelain strings for translation
something in the test suite might still depend on the US English
version of the strings, e.g. to grep some error message or other
output.
To smoke out issues like these Git can be compiled with gettext poison
support, at the top-level:
make GETTEXT_POISON=YesPlease
That'll give you a git which emits gibberish on every call to
gettext. It's obviously not meant to be installed, but you should run
the test suite with it:
cd t && prove -j 9 ./t[0-9]*.sh
If tests break with it you should inspect them manually and see if
what you're translating is sane, i.e. that you're not translating
plumbing output.
If not you should replace calls to grep with test_i18ngrep, or
test_cmp calls with test_i18ncmp. If that's not enough you can skip
the whole test by making it depend on the C_LOCALE_OUTPUT
prerequisite. See existing test files with this prerequisite for
examples.

93
po/is.po Normal file
View File

@ -0,0 +1,93 @@
# Icelandic translations for Git.
# Copyright (C) 2010 Ævar Arnfjörð Bjarmason <avarab@gmail.com>
# This file is distributed under the same license as the Git package.
# Ævar Arnfjörð Bjarmason <avarab@gmail.com>, 2010.
#
msgid ""
msgstr ""
"Project-Id-Version: Git\n"
"Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n"
"POT-Creation-Date: 2010-09-20 14:44+0000\n"
"PO-Revision-Date: 2010-06-05 19:06 +0000\n"
"Last-Translator: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n"
"Language-Team: Git Mailing List <git@vger.kernel.org>\n"
"Language: is\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#. TRANSLATORS: This is a test. You don't need to translate it.
#: t/t0200/test.c:5
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:10
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:13
#, 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.c:16
#, c-format
msgid "TEST: Hello World!"
msgstr "TILRAUN: Halló Heimur!"
#. TRANSLATORS: This is a test. You don't need to translate it.
#: t/t0200/test.c:19
#, c-format
msgid "TEST: Old English Runes"
msgstr "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ"
#. TRANSLATORS: This is a test. You don't need to translate it.
#: t/t0200/test.c:22
#, c-format
msgid "TEST: single and “double” quotes"
msgstr "TILRAUN: einfaldar og „tvöfaldar“ gæsalappir"
#. 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"
#. TRANSLATORS: The first '%s' is either "Reinitialized
#. existing" or "Initialized empty", the second " shared" or
#. "", and the last '%s%s' is the verbatim directory name.
#: builtin/init-db.c:355
#, c-format
msgid "%s%s Git repository in %s%s\n"
msgstr "%s%s Git lind í %s%s\n"
#: builtin/init-db.c:356
msgid "Reinitialized existing"
msgstr "Endurgerði"
#: builtin/init-db.c:356
msgid "Initialized empty"
msgstr "Bjó til tóma"
#: builtin/init-db.c:357
msgid " shared"
msgstr " sameiginlega"

View File

@ -137,6 +137,8 @@ int main(int argc, char **argv)
int devnull_fd;
int count;
git_setup_gettext();
git_extract_argv0_path(argv[0]);
/*

View File

@ -11,6 +11,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)

55
t/lib-gettext.sh Normal file
View File

@ -0,0 +1,55 @@
#!/bin/sh
#
# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
#
. ./test-lib.sh
GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/po/build/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 && ! test_have_prereq GETTEXT_POISON
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
}')
# is_IS.ISO8859-1 on Solaris and FreeBSD, is_IS.iso88591 on Debian
is_IS_iso_locale=$(locale -a | sed -n '/^is_IS\.[iI][sS][oO]8859-*1$/{
p
q
}')
# Export them as an environment variable so the t0202/test.pl Perl
# test can use it too
export is_IS_locale is_IS_iso_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 an is_IS UTF-8 locale"
else
say "# lib-gettext: No is_IS UTF-8 locale available"
fi
if test -n "$is_IS_iso_locale" &&
test $GIT_INTERNAL_GETTEXT_SH_SCHEME != "fallthrough"
then
# Some of the tests need the reference Icelandic locale
test_set_prereq GETTEXT_ISO_LOCALE
say "# lib-gettext: Found '$is_IS_iso_locale' as an is_IS ISO-8859-1 locale"
else
say "# lib-gettext: No is_IS ISO-8859-1 locale available"
fi
fi

108
t/t0200-gettext-basic.sh Executable file
View File

@ -0,0 +1,108 @@
#!/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"
'
# 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 <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 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

23
t/t0200/test.c Normal file
View File

@ -0,0 +1,23 @@
/* This is a phony C program that's only here to test xgettext message extraction */
const char help[] =
/* TRANSLATORS: This is a test. You don't need to translate it. */
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");
/* TRANSLATORS: This is a test. You don't need to translate it. */
printf(_("TEST: Hello World!"));
/* TRANSLATORS: This is a test. You don't need to translate it. */
printf(_("TEST: Old English Runes"));
/* TRANSLATORS: This is a test. You don't need to translate it. */
printf(_("TEST: single and “double” quotes"));
}

14
t/t0200/test.perl Normal file
View File

@ -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";

14
t/t0200/test.sh Normal file
View File

@ -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")

View File

@ -5,8 +5,24 @@
test_description='Gettext Shell fallbacks'
. ./test-lib.sh
. "$GIT_BUILD_DIR"/git-sh-i18n
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 C_LOCALE_OUTPUT 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is fallthrough' '
echo fallthrough >expect &&
echo $GIT_INTERNAL_GETTEXT_SH_SCHEME >actual &&
test_cmp expect actual
'
test_expect_success 'gettext: our gettext() fallback has pass-through semantics' '
printf "test" >expect &&

27
t/t0202-gettext-perl.sh Executable file
View File

@ -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

110
t/t0202/test.pl Normal file
View File

@ -0,0 +1,110 @@
#!/usr/bin/perl
use 5.008;
use lib (split(/:/, $ENV{GITPERLLIB}));
use strict;
use warnings;
use POSIX qw(:locale_h);
use Test::More tests => 8;
use Git::I18N;
my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
ok(1, "Testing Git::I18N with " .
($has_gettext_library
? (defined $Locale::Messages::VERSION
? "Locale::Messages version $Locale::Messages::VERSION"
# Versions of Locale::Messages before 1.17 didn't have a
# $VERSION variable.
: "Locale::Messages version <1.17")
: "NO Perl gettext library"));
ok(1, "Git::I18N is located at $INC{'Git/I18N.pm'}");
{
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');
}
}
}

View File

@ -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

View File

@ -0,0 +1,78 @@
#!/bin/sh
#
# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
#
test_description="Gettext reencoding of our *.po/*.mo files works"
. ./lib-gettext.sh
test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Icelandic' '
printf "TILRAUN: Halló Heimur!" >expect &&
LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Hello World!" >actual &&
test_cmp expect actual
'
test_expect_success GETTEXT_LOCALE 'gettext: Emitting UTF-8 from our UTF-8 *.mo files / Runes' '
printf "TILRAUN: ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" >expect &&
LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: Old English Runes" >actual &&
test_cmp expect actual
'
test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Icelandic' '
printf "TILRAUN: Halló Heimur!" | iconv -f UTF-8 -t ISO8859-1 >expect &&
LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Hello World!" >actual &&
test_cmp expect actual
'
test_expect_success GETTEXT_ISO_LOCALE 'gettext: Emitting ISO-8859-1 from our UTF-8 *.mo files / Runes' '
LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: Old English Runes" >runes &&
if grep "^TEST: Old English Runes$" runes
then
say "Your system can not handle this complexity and returns the string as-is"
else
# Both Solaris and GNU libintl will return this stream of
# question marks, so it is s probably portable enough
printf "TILRAUN: ?? ???? ??? ?? ???? ?? ??? ????? ??????????? ??? ?? ????" >runes-expect &&
test_cmp runes-expect runes
fi
'
test_expect_success GETTEXT_LOCALE 'gettext: Fetching a UTF-8 msgid -> UTF-8' '
printf "TILRAUN: einfaldar og „tvöfaldar“ gæsalappir" >expect &&
LANGUAGE=is LC_ALL="$is_IS_locale" gettext "TEST: single and “double” quotes" >actual &&
test_cmp expect actual
'
# How these quotes get transliterated depends on the gettext implementation:
#
# Debian: ,einfaldar' og ,,tvöfaldar" [GNU libintl]
# FreeBSD: `einfaldar` og "tvöfaldar" [GNU libintl]
# Solaris: ?einfaldar? og ?tvöfaldar? [Solaris libintl]
#
# Just make sure the contents are transliterated, and don't use grep -q
# so that these differences are emitted under --verbose for curious
# eyes.
test_expect_success GETTEXT_ISO_LOCALE 'gettext: Fetching a UTF-8 msgid -> ISO-8859-1' '
LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: single and “double” quotes" >actual &&
grep "einfaldar" actual &&
grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual
'
test_expect_success GETTEXT_LOCALE 'gettext.c: git init UTF-8 -> UTF-8' '
printf "Bjó til tóma Git lind" >expect &&
LANGUAGE=is LC_ALL="$is_IS_locale" git init repo >actual &&
test_when_finished "rm -rf repo" &&
grep "^$(cat expect) " actual
'
test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' '
printf "Bjó til tóma Git lind" >expect &&
LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual &&
test_when_finished "rm -rf repo" &&
grep "^$(cat expect | iconv -f UTF-8 -t ISO8859-1) " actual
'
test_done

36
t/t0205-gettext-poison.sh Executable file
View File

@ -0,0 +1,36 @@
#!/bin/sh
#
# Copyright (c) 2010 Ævar Arnfjörð Bjarmason
#
test_description='Gettext Shell poison'
. ./lib-gettext.sh
test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
'
test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' '
test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison"
'
test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison semantics' '
printf "# GETTEXT POISON #" >expect &&
gettext "test" >actual &&
test_cmp expect actual &&
printf "# GETTEXT POISON #" >expect &&
gettext "test more words" >actual &&
test_cmp expect actual
'
test_expect_success GETTEXT_POISON 'eval_gettext: our eval_gettext() fallback has poison semantics' '
printf "# GETTEXT POISON #" >expect &&
eval_gettext "test" >actual &&
test_cmp expect actual &&
printf "# GETTEXT POISON #" >expect &&
eval_gettext "test more words" >actual &&
test_cmp expect actual
'
test_done

View File

@ -44,6 +44,7 @@ export LANG LC_ALL PAGER TERM TZ
EDITOR=:
unset VISUAL
unset EMAIL
unset LANGUAGE
unset $(perl -e '
my @env = keys %ENV;
my $ok = join("|", qw(
@ -1113,12 +1114,14 @@ esac
test -z "$NO_PERL" && test_set_prereq PERL
test -z "$NO_PYTHON" && test_set_prereq PYTHON
test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE
test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
# Can we rely on git's output in the C locale?
if test -n "$GETTEXT_POISON"
then
GIT_GETTEXT_POISON=YesPlease
export GIT_GETTEXT_POISON
test_set_prereq GETTEXT_POISON
else
test_set_prereq C_LOCALE_OUTPUT
fi

View File

@ -784,6 +784,8 @@ int main(int argc, char **argv)
int i;
int strict = 0;
git_setup_gettext();
packet_trace_identity("upload-pack");
git_extract_argv0_path(argv[0]);
read_replace_refs = 0;

View File

@ -15,7 +15,8 @@ else
export GIT_TEMPLATE_DIR
fi
GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'
GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale'
PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH"
export GIT_EXEC_PATH GITPERLLIB PATH
export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR
exec "${GIT_EXEC_PATH}/@@PROG@@" "$@"