Skip to content

Commit 5e9637c

Browse files
avargitster
authored andcommitted
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 <[email protected]> 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 <[email protected]> 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 <[email protected]> Signed-off-by: Ramsay Jones <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent bc1bbe0 commit 5e9637c

37 files changed

+1291
-39
lines changed

Documentation/CodingGuidelines

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ For shell scripts specifically (not exhaustive):
8181
are ERE elements not BRE (note that \? and \+ are not even part
8282
of BRE -- making them accessible from BRE is a GNU extension).
8383

84+
- Use Git's gettext wrappers in git-sh-i18n to make the user
85+
interface translatable. See "Marking strings for translation" in
86+
po/README.
87+
8488
For C programs:
8589

8690
- We use tabs to indent, and interpret tabs as taking up to
@@ -144,6 +148,9 @@ For C programs:
144148
- When we pass <string, length> pair to functions, we should try to
145149
pass them in that order.
146150

151+
- Use Git's gettext wrappers to make the user interface
152+
translatable. See "Marking strings for translation" in po/README.
153+
147154
Writing Documentation:
148155

149156
Every user-visible change should be reflected in the documentation.

INSTALL

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ Issues of note:
106106
history graphically, and in git-gui. If you don't want gitk or
107107
git-gui, you can use NO_TCLTK.
108108

109+
- A gettext library is used by default for localizing Git. The
110+
primary target is GNU libintl, but the Solaris gettext
111+
implementation also works.
112+
113+
We need a gettext.h on the system for C code, gettext.sh (or
114+
Solaris gettext(1)) for shell scripts, and libintl-perl for Perl
115+
programs.
116+
117+
Set NO_GETTEXT to disable localization support and make Git only
118+
use English. Under autoconf the configure script will do this
119+
automatically if it can't find libintl on the system.
120+
109121
- Some platform specific issues are dealt with Makefile rules,
110122
but depending on your specific installation, you may not
111123
have all the libraries/tools needed, or you may have

Makefile

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,22 @@ all::
4343
# Define EXPATDIR=/foo/bar if your expat header and library files are in
4444
# /foo/bar/include and /foo/bar/lib directories.
4545
#
46+
# Define NO_GETTEXT if you don't want Git output to be translated.
47+
# A translated Git requires GNU libintl or another gettext implementation,
48+
# plus libintl-perl at runtime.
49+
#
50+
# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't
51+
# trust the langinfo.h's nl_langinfo(CODESET) function to return the
52+
# current character set. GNU and Solaris have a nl_langinfo(CODESET),
53+
# FreeBSD can use either, but MinGW and some others need to use
54+
# libcharset.h's locale_charset() instead.
55+
#
56+
# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
57+
# need -lintl when linking.
58+
#
59+
# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt
60+
# doesn't support GNU extensions like --check and --statistics
61+
#
4662
# Define HAVE_PATHS_H if you have paths.h and want to use the default PATH
4763
# it specifies.
4864
#
@@ -301,6 +317,7 @@ gitexecdir = libexec/git-core
301317
mergetoolsdir = $(gitexecdir)/mergetools
302318
sharedir = $(prefix)/share
303319
gitwebdir = $(sharedir)/gitweb
320+
localedir = $(sharedir)/locale
304321
template_dir = share/git-core/templates
305322
htmldir = share/doc/git-doc
306323
ETC_GITCONFIG = $(sysconfdir)/gitconfig
@@ -309,7 +326,7 @@ lib = lib
309326
# DESTDIR=
310327
pathsep = :
311328

312-
export prefix bindir sharedir sysconfdir gitwebdir
329+
export prefix bindir sharedir sysconfdir gitwebdir localedir
313330

314331
CC = gcc
315332
AR = ar
@@ -322,6 +339,7 @@ RPMBUILD = rpmbuild
322339
TCL_PATH = tclsh
323340
TCLTK_PATH = wish
324341
XGETTEXT = xgettext
342+
MSGFMT = msgfmt
325343
PTHREAD_LIBS = -lpthread
326344
PTHREAD_CFLAGS =
327345
GCOV = gcov
@@ -621,6 +639,7 @@ LIB_OBJS += entry.o
621639
LIB_OBJS += environment.o
622640
LIB_OBJS += exec_cmd.o
623641
LIB_OBJS += fsck.o
642+
LIB_OBJS += gettext.o
624643
LIB_OBJS += graph.o
625644
LIB_OBJS += grep.o
626645
LIB_OBJS += hash.o
@@ -817,12 +836,14 @@ ifeq ($(uname_S),Linux)
817836
NO_STRLCPY = YesPlease
818837
NO_MKSTEMPS = YesPlease
819838
HAVE_PATHS_H = YesPlease
839+
LIBC_CONTAINS_LIBINTL = YesPlease
820840
endif
821841
ifeq ($(uname_S),GNU/kFreeBSD)
822842
NO_STRLCPY = YesPlease
823843
NO_MKSTEMPS = YesPlease
824844
HAVE_PATHS_H = YesPlease
825845
DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
846+
LIBC_CONTAINS_LIBINTL = YesPlease
826847
endif
827848
ifeq ($(uname_S),UnixWare)
828849
CC = cc
@@ -889,6 +910,7 @@ ifeq ($(uname_S),SunOS)
889910
NO_MKSTEMPS = YesPlease
890911
NO_REGEX = YesPlease
891912
NO_FNMATCH_CASEFOLD = YesPlease
913+
NO_MSGFMT_EXTENDED_OPTIONS = YesPlease
892914
ifeq ($(uname_R),5.6)
893915
SOCKLEN_T = int
894916
NO_HSTRERROR = YesPlease
@@ -1012,6 +1034,7 @@ ifeq ($(uname_S),GNU)
10121034
NO_STRLCPY=YesPlease
10131035
NO_MKSTEMPS = YesPlease
10141036
HAVE_PATHS_H = YesPlease
1037+
LIBC_CONTAINS_LIBINTL = YesPlease
10151038
endif
10161039
ifeq ($(uname_S),IRIX)
10171040
NO_SETENV = YesPlease
@@ -1228,6 +1251,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
12281251
EXTLIBS += /mingw/lib/libz.a
12291252
NO_R_TO_GCC_LINKER = YesPlease
12301253
INTERNAL_QSORT = YesPlease
1254+
HAVE_LIBCHARSET_H = YesPlease
12311255
else
12321256
NO_CURL = YesPlease
12331257
endif
@@ -1405,6 +1429,11 @@ endif
14051429
ifdef NEEDS_LIBGEN
14061430
EXTLIBS += -lgen
14071431
endif
1432+
ifndef NO_GETTEXT
1433+
ifndef LIBC_CONTAINS_LIBINTL
1434+
EXTLIBS += -lintl
1435+
endif
1436+
endif
14081437
ifdef NEEDS_SOCKET
14091438
EXTLIBS += -lsocket
14101439
endif
@@ -1447,9 +1476,11 @@ ifdef NO_SYMLINK_HEAD
14471476
BASIC_CFLAGS += -DNO_SYMLINK_HEAD
14481477
endif
14491478
ifdef GETTEXT_POISON
1450-
LIB_OBJS += gettext.o
14511479
BASIC_CFLAGS += -DGETTEXT_POISON
14521480
endif
1481+
ifdef NO_GETTEXT
1482+
BASIC_CFLAGS += -DNO_GETTEXT
1483+
endif
14531484
ifdef NO_STRCASESTR
14541485
COMPAT_CFLAGS += -DNO_STRCASESTR
14551486
COMPAT_OBJS += compat/strcasestr.o
@@ -1612,6 +1643,10 @@ ifdef HAVE_PATHS_H
16121643
BASIC_CFLAGS += -DHAVE_PATHS_H
16131644
endif
16141645

1646+
ifdef HAVE_LIBCHARSET_H
1647+
BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
1648+
endif
1649+
16151650
ifdef DIR_HAS_BSD_GROUP_SEMANTICS
16161651
COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
16171652
endif
@@ -1632,6 +1667,10 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
16321667
export GIT_TEST_CMP_USE_COPIED_CONTEXT
16331668
endif
16341669

1670+
ifndef NO_MSGFMT_EXTENDED_OPTIONS
1671+
MSGFMT += --check --statistics
1672+
endif
1673+
16351674
ifeq ($(TCLTK_PATH),)
16361675
NO_TCLTK=NoThanks
16371676
endif
@@ -1662,6 +1701,7 @@ ifndef V
16621701
QUIET_GEN = @echo ' ' GEN $@;
16631702
QUIET_LNCP = @echo ' ' LN/CP $@;
16641703
QUIET_XGETTEXT = @echo ' ' XGETTEXT $@;
1704+
QUIET_MSGFMT = @echo ' ' MSGFMT $@;
16651705
QUIET_GCOV = @echo ' ' GCOV $@;
16661706
QUIET_SP = @echo ' ' SP $<;
16671707
QUIET_SUBDIR0 = +@subdir=
@@ -1688,6 +1728,7 @@ bindir_SQ = $(subst ','\'',$(bindir))
16881728
bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
16891729
mandir_SQ = $(subst ','\'',$(mandir))
16901730
infodir_SQ = $(subst ','\'',$(infodir))
1731+
localedir_SQ = $(subst ','\'',$(localedir))
16911732
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
16921733
template_dir_SQ = $(subst ','\'',$(template_dir))
16931734
htmldir_SQ = $(subst ','\'',$(htmldir))
@@ -1743,7 +1784,7 @@ ifndef NO_TCLTK
17431784
$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
17441785
endif
17451786
ifndef NO_PERL
1746-
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
1787+
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all
17471788
endif
17481789
ifndef NO_PYTHON
17491790
$(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)|' \
17931834
-e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \
17941835
-e 's|@@DIFF@@|$(DIFF_SQ)|' \
17951836
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
1837+
-e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \
17961838
-e 's/@@NO_CURL@@/$(NO_CURL)/g' \
17971839
-e $(BROKEN_PATH_FIX) \
17981840
@@ -2045,6 +2087,9 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \
20452087
attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \
20462088
-DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
20472089

2090+
gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \
2091+
-DGIT_LOCALE_PATH='"$(localedir_SQ)"'
2092+
20482093
http.sp http.s http.o: EXTRA_CPPFLAGS = \
20492094
-DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
20502095

@@ -2118,17 +2163,37 @@ XGETTEXT_FLAGS = \
21182163
XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
21192164
--keyword=_ --keyword=N_ --keyword="Q_:1,2"
21202165
XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell
2166+
XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
21212167
LOCALIZED_C := $(C_OBJ:o=c)
21222168
LOCALIZED_SH := $(SCRIPT_SH)
2169+
LOCALIZED_PERL := $(SCRIPT_PERL)
2170+
2171+
ifdef XGETTEXT_INCLUDE_TESTS
2172+
LOCALIZED_C += t/t0200/test.c
2173+
LOCALIZED_SH += t/t0200/test.sh
2174+
LOCALIZED_PERL += t/t0200/test.perl
2175+
endif
21232176

21242177
po/git.pot: $(LOCALIZED_C)
21252178
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C)
21262179
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \
21272180
$(LOCALIZED_SH)
2181+
$(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \
2182+
$(LOCALIZED_PERL)
21282183
mv $@+ $@
21292184

21302185
pot: po/git.pot
21312186

2187+
POFILES := $(wildcard po/*.po)
2188+
MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES))
2189+
2190+
ifndef NO_GETTEXT
2191+
all:: $(MOFILES)
2192+
endif
2193+
2194+
po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
2195+
$(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
2196+
21322197
FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
21332198
$(FIND) . \( -name .git -type d -prune \) \
21342199
-o \( -name '*.[hcS]' -type f -print \) )
@@ -2147,7 +2212,8 @@ cscope:
21472212

21482213
### Detect prefix changes
21492214
TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
2150-
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
2215+
$(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\
2216+
$(localedir_SQ)
21512217

21522218
GIT-CFLAGS: FORCE
21532219
@FLAGS='$(TRACK_CFLAGS)'; \
@@ -2184,6 +2250,7 @@ endif
21842250
ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
21852251
@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
21862252
endif
2253+
@echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
21872254
@echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
21882255

21892256
### Detect Tck/Tk interpreter path changes
@@ -2299,6 +2366,11 @@ install: all
22992366
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
23002367
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
23012368
$(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
2369+
ifndef NO_GETTEXT
2370+
$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)'
2371+
(cd po/build/locale && $(TAR) cf - .) | \
2372+
(cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -)
2373+
endif
23022374
ifndef NO_PERL
23032375
$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
23042376
$(MAKE) -C gitweb install
@@ -2435,6 +2507,7 @@ clean:
24352507
$(RM) $(TEST_PROGRAMS)
24362508
$(RM) -r bin-wrappers
24372509
$(RM) -r $(dep_dirs)
2510+
$(RM) -r po/build/
24382511
$(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope*
24392512
$(RM) -r autom4te.cache
24402513
$(RM) config.log config.mak.autogen config.mak.append config.status config.cache

config.mak.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ NO_CURL=@NO_CURL@
3535
NO_EXPAT=@NO_EXPAT@
3636
NO_LIBGEN_H=@NO_LIBGEN_H@
3737
HAVE_PATHS_H=@HAVE_PATHS_H@
38+
HAVE_LIBCHARSET_H=@HAVE_LIBCHARSET_H@
39+
NO_GETTEXT=@NO_GETTEXT@
40+
LIBC_CONTAINS_LIBINTL=@LIBC_CONTAINS_LIBINTL@
3841
NEEDS_LIBICONV=@NEEDS_LIBICONV@
3942
NEEDS_SOCKET=@NEEDS_SOCKET@
4043
NEEDS_RESOLV=@NEEDS_RESOLV@

configure.ac

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,12 @@ AC_CHECK_LIB([c], [basename],
636636
AC_SUBST(NEEDS_LIBGEN)
637637
test -n "$NEEDS_LIBGEN" && LIBS="$LIBS -lgen"
638638

639+
AC_CHECK_LIB([c], [gettext],
640+
[LIBC_CONTAINS_LIBINTL=YesPlease],
641+
[LIBC_CONTAINS_LIBINTL=])
642+
AC_SUBST(LIBC_CONTAINS_LIBINTL)
643+
test -n "$LIBC_CONTAINS_LIBINTL" || LIBS="$LIBS -lintl"
644+
639645
## Checks for header files.
640646
AC_MSG_NOTICE([CHECKS for header files])
641647
#
@@ -818,6 +824,19 @@ AC_CHECK_HEADER([paths.h],
818824
[HAVE_PATHS_H=])
819825
AC_SUBST(HAVE_PATHS_H)
820826
#
827+
# Define NO_GETTEXT if you don't want Git output to be translated.
828+
# A translated Git requires GNU libintl or another gettext implementation
829+
AC_CHECK_HEADER([libintl.h],
830+
[NO_GETTEXT=],
831+
[NO_GETTEXT=YesPlease])
832+
AC_SUBST(NO_GETTEXT)
833+
#
834+
# Define HAVE_LIBCHARSET_H if have libcharset.h
835+
AC_CHECK_HEADER([libcharset.h],
836+
[HAVE_LIBCHARSET_H=YesPlease],
837+
[HAVE_LIBCHARSET_H=])
838+
AC_SUBST(HAVE_LIBCHARSET_H)
839+
#
821840
# Define NO_STRCASESTR if you don't have strcasestr.
822841
GIT_CHECK_FUNC(strcasestr,
823842
[NO_STRCASESTR=],

daemon.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,8 @@ int main(int argc, char **argv)
10991099
struct credentials *cred = NULL;
11001100
int i;
11011101

1102+
git_setup_gettext();
1103+
11021104
git_extract_argv0_path(argv[0]);
11031105

11041106
for (i = 1; i < argc; i++) {

fast-import.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3292,6 +3292,8 @@ int main(int argc, const char **argv)
32923292

32933293
git_extract_argv0_path(argv[0]);
32943294

3295+
git_setup_gettext();
3296+
32953297
if (argc == 2 && !strcmp(argv[1], "-h"))
32963298
usage(fast_import_usage);
32973299

0 commit comments

Comments
 (0)