{"thread":{"id":"23830","subject":"[PATCH] WIP: begin to translate git with gettext","startedAt":"2010-05-17T16:05:03Z","lastAt":"2010-05-21T18:02:28Z","messageCount":9,"participants":["Jeff Epler","Robert Buck","Ævar Arnfjörð Bjarmason","Michael J Gruber","Dévai Tamás"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"141827","messageId":"20100517160503.GA12717@unpythonic.net","threadId":"23830","inReplyTo":null,"subject":"[PATCH] WIP: begin to translate git with gettext","fromName":"Jeff Epler","fromEmail":"jepler@unpythonic.net","sentAt":"2010-05-17T16:05:03Z","receivedAt":"2010-05-17T16:05:03Z","isPatch":true,"sender":{"key":"jepler@unpythonic.net","avatar":"https://avatars.githubusercontent.com/u/1517291?v=4"},"body":"Signed-off-by: Jeff Epler <jepler@unpythonic.net>\n---\n[resent with Cc to list and thread participants]\n\nWhile I'm certain that there are a lot of things to object to in this\npatch, it shows 90% of what is needed to use gettext to translate\nthe portions of git written in c, without involving undesired gnu\ninfrastructure such as automake.\n\nMakefile adds necessary rules for generating git.pot and for building\nand installing compiled message catalogs (.mo) from text message\ncatalogs (.po).  It also adds a gettext support header and source file.\n\nMinimal changes are made to git to use the requested LC_CTYPE and\nLC_MESSAGES, and some messages for 'git status' are marked for\ntranslation.\n\nWhen I provided a gibberish translation of a message:\n#: wt-status.c:87\nmsgid \"# Changed but not updated:\"\nmsgstr \"# Changes not blah blah blah\"\n\nrunning 'git status' used the translation:\n$ git status\n# On branch master\n# Your branch is ahead of 'origin/master' by 1 commit.\n#\n# Changes not blah blah blah\n...\n\nI ran with 'make install' and prefix set in config.mak.  It didn't seem\nto work when running from the source directory, and it may or may not\nwork with runtime prefix.\n\n\n Makefile    |   26 +++++++++++++\n gettext.c   |   17 +++++++++\n gettext.h   |   15 ++++++++\n git.c       |    3 ++\n wt-status.c |  117 ++++++++++++++++++++++++++++++-----------------------------\n 5 files changed, 120 insertions(+), 58 deletions(-)\n create mode 100644 gettext.c\n create mode 100644 gettext.h\n\ndiff --git a/Makefile b/Makefile\nindex 4f7224a..c02ca18 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -294,6 +294,8 @@ RPMBUILD = rpmbuild\n TCL_PATH = tclsh\n TCLTK_PATH = wish\n PTHREAD_LIBS = -lpthread\n+XGETTEXT = xgettext\n+MSGFMT = msgfmt\n \n export TCL_PATH TCLTK_PATH\n \n@@ -518,6 +520,7 @@ LIB_H += userdiff.h\n LIB_H += utf8.h\n LIB_H += xdiff-interface.h\n LIB_H += xdiff/xdiff.h\n+LIB_H += gettext.h\n \n LIB_OBJS += abspath.o\n LIB_OBJS += advice.o\n@@ -559,6 +562,7 @@ LIB_OBJS += entry.o\n LIB_OBJS += environment.o\n LIB_OBJS += exec_cmd.o\n LIB_OBJS += fsck.o\n+LIB_OBJS += gettext.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hash.o\n@@ -1371,6 +1375,12 @@ ifdef USE_NED_ALLOCATOR\n        COMPAT_OBJS += compat/nedmalloc/nedmalloc.o\n endif\n \n+ifdef NO_GETTEXT\n+\tCOMPAT_CFLAGS += -DNO_GETTEXT\n+else\n+\tLIBINTL = -lintl\n+endif\n+\n ifeq ($(TCLTK_PATH),)\n NO_TCLTK=NoThanks\n endif\n@@ -1400,6 +1410,7 @@ ifndef V\n \tQUIET_BUILT_IN = @echo '   ' BUILTIN $@;\n \tQUIET_GEN      = @echo '   ' GEN $@;\n \tQUIET_LNCP     = @echo '   ' LN/CP $@;\n+\tQUIET_MSGFMT   = @echo '   ' MSGFMT $@;\n \tQUIET_SUBDIR0  = +@subdir=\n \tQUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \\\n \t\t\t $(MAKE) $(PRINT_DIR) -C $$subdir\n@@ -1427,6 +1438,7 @@ gitexecdir_SQ = $(subst ','\\'',$(gitexecdir))\n template_dir_SQ = $(subst ','\\'',$(template_dir))\n htmldir_SQ = $(subst ','\\'',$(htmldir))\n prefix_SQ = $(subst ','\\'',$(prefix))\n+sharedir_SQ = $(subst ','\\'',$(sharedir))\n \n SHELL_PATH_SQ = $(subst ','\\'',$(SHELL_PATH))\n PERL_PATH_SQ = $(subst ','\\'',$(PERL_PATH))\n@@ -1858,6 +1870,17 @@ cscope:\n \t$(RM) cscope*\n \t$(FIND) . -name '*.[hcS]' -print | xargs cscope -b\n \n+pot:\n+\t$(XGETTEXT) -k_ -o po/git.pot $(C_OBJ:o=c)\n+\n+POFILES := $(wildcard po/*.po)\n+MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))\n+MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES))\n+all:: $(MOFILES)\n+share/locale/%/LC_MESSAGES/git.mo: po/%.po\n+\t@mkdir -p $(dir $@)\n+\t$(QUIET_MSGFMT)$(MSGFMT) -o $@ $<\n+\n ### Detect prefix changes\n TRACK_CFLAGS = $(subst ','\\'',$(ALL_CFLAGS)):\\\n              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)\n@@ -1970,6 +1993,9 @@ install: all\n \t$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'\n \t$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'\n \t$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'\n+\t$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sharedir_SQ)/locale'\n+\t(cd share && tar cf - locale) | \\\n+\t\t(cd '$(DESTDIR_SQ)$(sharedir_SQ)' && umask 022 && tar xof -)\n \t$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install\n ifndef NO_PERL\n \t$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install\ndiff --git a/gettext.c b/gettext.c\nnew file mode 100644\nindex 0000000..aadce19\n--- /dev/null\n+++ b/gettext.c\n@@ -0,0 +1,17 @@\n+#ifdef NO_GETTEXT\n+void git_setup_gettext() {}\n+#else\n+#include \"exec_cmd.h\"\n+#include <libintl.h>\n+#include <stdlib.h>\n+\n+void git_setup_gettext() {\n+    const char *podir = system_path(\"share/locale\");\n+    if(!podir) return;\n+    char *ret = bindtextdomain(\"git\", podir);\n+    free((void*)podir);\n+    ret = setlocale(LC_MESSAGES, \"\");\n+    ret = setlocale(LC_CTYPE, \"\");\n+    ret = textdomain(\"git\");\n+}\n+#endif\ndiff --git a/gettext.h b/gettext.h\nnew file mode 100644\nindex 0000000..8b221b4\n--- /dev/null\n+++ b/gettext.h\n@@ -0,0 +1,15 @@\n+#ifndef GETTEXT_H\n+#define GETTEXT_H\n+\n+void git_setup_gettext();\n+\n+#ifdef NO_GETTEXT\n+#define _(s) (s)\n+#define N_(s) (s)\n+#else\n+#include <libintl.h>\n+#define _(s) gettext(s)\n+#define N_(s) (s)\n+#endif\n+\n+#endif\ndiff --git a/git.c b/git.c\nindex 6bae305..5e7aedd 100644\n--- a/git.c\n+++ b/git.c\n@@ -3,6 +3,7 @@\n #include \"cache.h\"\n #include \"quote.h\"\n #include \"run-command.h\"\n+#include \"gettext.h\"\n \n const char git_usage_string[] =\n \t\"git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\\n\"\n@@ -481,6 +482,8 @@ int main(int argc, const char **argv)\n \tif (!cmd)\n \t\tcmd = \"git-help\";\n \n+\tgit_setup_gettext();\n+\n \t/*\n \t * \"git-xxxx\" is the same as \"git xxxx\", but we obviously:\n \t *\ndiff --git a/wt-status.c b/wt-status.c\nindex 8ca59a2..a31cbc6 100644\n--- a/wt-status.c\n+++ b/wt-status.c\n@@ -9,6 +9,7 @@\n #include \"quote.h\"\n #include \"run-command.h\"\n #include \"remote.h\"\n+#include \"gettext.h\"\n \n static char default_wt_status_colors[][COLOR_MAXLEN] = {\n \tGIT_COLOR_NORMAL, /* WT_STATUS_HEADER */\n@@ -48,33 +49,33 @@ static void wt_status_print_unmerged_header(struct wt_status *s)\n {\n \tconst char *c = color(WT_STATUS_HEADER, s);\n \n-\tcolor_fprintf_ln(s->fp, c, \"# Unmerged paths:\");\n+\tcolor_fprintf_ln(s->fp, c, _(\"# Unmerged paths:\"));\n \tif (!advice_status_hints)\n \t\treturn;\n \tif (s->in_merge)\n \t\t;\n \telse if (!s->is_initial)\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git reset %s <file>...\\\" to unstage)\", s->reference);\n+\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git reset %s <file>...\\\" to unstage)\"), s->reference);\n \telse\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git rm --cached <file>...\\\" to unstage)\");\n-\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" as appropriate to mark resolution)\");\n-\tcolor_fprintf_ln(s->fp, c, \"#\");\n+\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git rm --cached <file>...\\\" to unstage)\"));\n+\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add/rm <file>...\\\" as appropriate to mark resolution)\"));\n+\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n }\n \n static void wt_status_print_cached_header(struct wt_status *s)\n {\n \tconst char *c = color(WT_STATUS_HEADER, s);\n \n-\tcolor_fprintf_ln(s->fp, c, \"# Changes to be committed:\");\n+\tcolor_fprintf_ln(s->fp, c, _(\"# Changes to be committed:\"));\n \tif (!advice_status_hints)\n \t\treturn;\n \tif (s->in_merge)\n \t\t; /* NEEDSWORK: use \"git reset --unresolve\"??? */\n \telse if (!s->is_initial)\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git reset %s <file>...\\\" to unstage)\", s->reference);\n+\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git reset %s <file>...\\\" to unstage)\"), s->reference);\n \telse\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git rm --cached <file>...\\\" to unstage)\");\n-\tcolor_fprintf_ln(s->fp, c, \"#\");\n+\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git rm --cached <file>...\\\" to unstage)\"));\n+\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n }\n \n static void wt_status_print_dirty_header(struct wt_status *s,\n@@ -83,32 +84,32 @@ static void wt_status_print_dirty_header(struct wt_status *s,\n {\n \tconst char *c = color(WT_STATUS_HEADER, s);\n \n-\tcolor_fprintf_ln(s->fp, c, \"# Changed but not updated:\");\n+\tcolor_fprintf_ln(s->fp, c, _(\"# Changed but not updated:\"));\n \tif (!advice_status_hints)\n \t\treturn;\n \tif (!has_deleted)\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add <file>...\\\" to update what will be committed)\");\n+\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add <file>...\\\" to update what will be committed)\"));\n \telse\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\");\n-\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\");\n+\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\"));\n+\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\"));\n \tif (has_dirty_submodules)\n-\t\tcolor_fprintf_ln(s->fp, c, \"#   (commit or discard the untracked or modified content in submodules)\");\n-\tcolor_fprintf_ln(s->fp, c, \"#\");\n+\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (commit or discard the untracked or modified content in submodules)\"));\n+\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n }\n \n static void wt_status_print_untracked_header(struct wt_status *s)\n {\n \tconst char *c = color(WT_STATUS_HEADER, s);\n-\tcolor_fprintf_ln(s->fp, c, \"# Untracked files:\");\n+\tcolor_fprintf_ln(s->fp, c, _(\"# Untracked files:\"));\n \tif (!advice_status_hints)\n \t\treturn;\n-\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add <file>...\\\" to include in what will be committed)\");\n-\tcolor_fprintf_ln(s->fp, c, \"#\");\n+\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add <file>...\\\" to include in what will be committed)\"));\n+\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n }\n \n static void wt_status_print_trailer(struct wt_status *s)\n {\n-\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"#\");\n+\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"#\"));\n }\n \n #define quote_path quote_path_relative\n@@ -119,20 +120,20 @@ static void wt_status_print_unmerged_data(struct wt_status *s,\n \tconst char *c = color(WT_STATUS_UNMERGED, s);\n \tstruct wt_status_change_data *d = it->util;\n \tstruct strbuf onebuf = STRBUF_INIT;\n-\tconst char *one, *how = \"bug\";\n+\tconst char *one, *how = _(\"bug\");\n \n \tone = quote_path(it->string, -1, &onebuf, s->prefix);\n-\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"#\\t\");\n+\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"#\\t\"));\n \tswitch (d->stagemask) {\n-\tcase 1: how = \"both deleted:\"; break;\n-\tcase 2: how = \"added by us:\"; break;\n-\tcase 3: how = \"deleted by them:\"; break;\n-\tcase 4: how = \"added by them:\"; break;\n-\tcase 5: how = \"deleted by us:\"; break;\n-\tcase 6: how = \"both added:\"; break;\n-\tcase 7: how = \"both modified:\"; break;\n+\tcase 1: how = _(\"both deleted:\"); break;\n+\tcase 2: how = _(\"added by us:\"); break;\n+\tcase 3: how = _(\"deleted by them:\"); break;\n+\tcase 4: how = _(\"added by them:\"); break;\n+\tcase 5: how = _(\"deleted by us:\"); break;\n+\tcase 6: how = _(\"both added:\"); break;\n+\tcase 7: how = _(\"both modified:\"); break;\n \t}\n-\tcolor_fprintf(s->fp, c, \"%-20s%s\\n\", how, one);\n+\tcolor_fprintf(s->fp, c, _(\"%-20s%s\\n\"), how, one);\n \tstrbuf_release(&onebuf);\n }\n \n@@ -158,13 +159,13 @@ static void wt_status_print_change_data(struct wt_status *s,\n \t\tbreak;\n \tcase WT_STATUS_CHANGED:\n \t\tif (d->new_submodule_commits || d->dirty_submodule) {\n-\t\t\tstrbuf_addstr(&extra, \" (\");\n+\t\t\tstrbuf_addstr(&extra, _(\" (\"));\n \t\t\tif (d->new_submodule_commits)\n-\t\t\t\tstrbuf_addf(&extra, \"new commits, \");\n+\t\t\t\tstrbuf_addf(&extra, _(\"new commits, \"));\n \t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)\n-\t\t\t\tstrbuf_addf(&extra, \"modified content, \");\n+\t\t\t\tstrbuf_addf(&extra, _(\"modified content, \"));\n \t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)\n-\t\t\t\tstrbuf_addf(&extra, \"untracked content, \");\n+\t\t\t\tstrbuf_addf(&extra, _(\"untracked content, \"));\n \t\t\tstrbuf_setlen(&extra, extra.len - 2);\n \t\t\tstrbuf_addch(&extra, ')');\n \t\t}\n@@ -175,40 +176,40 @@ static void wt_status_print_change_data(struct wt_status *s,\n \tone = quote_path(one_name, -1, &onebuf, s->prefix);\n \ttwo = quote_path(two_name, -1, &twobuf, s->prefix);\n \n-\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"#\\t\");\n+\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"#\\t\"));\n \tswitch (status) {\n \tcase DIFF_STATUS_ADDED:\n-\t\tcolor_fprintf(s->fp, c, \"new file:   %s\", one);\n+\t\tcolor_fprintf(s->fp, c, _(\"new file:   %s\"), one);\n \t\tbreak;\n \tcase DIFF_STATUS_COPIED:\n-\t\tcolor_fprintf(s->fp, c, \"copied:     %s -> %s\", one, two);\n+\t\tcolor_fprintf(s->fp, c, _(\"copied:     %s -> %s\"), one, two);\n \t\tbreak;\n \tcase DIFF_STATUS_DELETED:\n-\t\tcolor_fprintf(s->fp, c, \"deleted:    %s\", one);\n+\t\tcolor_fprintf(s->fp, c, _(\"deleted:    %s\"), one);\n \t\tbreak;\n \tcase DIFF_STATUS_MODIFIED:\n-\t\tcolor_fprintf(s->fp, c, \"modified:   %s\", one);\n+\t\tcolor_fprintf(s->fp, c, _(\"modified:   %s\"), one);\n \t\tbreak;\n \tcase DIFF_STATUS_RENAMED:\n-\t\tcolor_fprintf(s->fp, c, \"renamed:    %s -> %s\", one, two);\n+\t\tcolor_fprintf(s->fp, c, _(\"renamed:    %s -> %s\"), one, two);\n \t\tbreak;\n \tcase DIFF_STATUS_TYPE_CHANGED:\n-\t\tcolor_fprintf(s->fp, c, \"typechange: %s\", one);\n+\t\tcolor_fprintf(s->fp, c, _(\"typechange: %s\"), one);\n \t\tbreak;\n \tcase DIFF_STATUS_UNKNOWN:\n-\t\tcolor_fprintf(s->fp, c, \"unknown:    %s\", one);\n+\t\tcolor_fprintf(s->fp, c, _(\"unknown:    %s\"), one);\n \t\tbreak;\n \tcase DIFF_STATUS_UNMERGED:\n-\t\tcolor_fprintf(s->fp, c, \"unmerged:   %s\", one);\n+\t\tcolor_fprintf(s->fp, c, _(\"unmerged:   %s\"), one);\n \t\tbreak;\n \tdefault:\n-\t\tdie(\"bug: unhandled diff status %c\", status);\n+\t\tdie(_(\"bug: unhandled diff status %c\"), status);\n \t}\n \tif (extra.len) {\n-\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"%s\", extra.buf);\n+\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"%s\"), extra.buf);\n \t\tstrbuf_release(&extra);\n \t}\n-\tfprintf(s->fp, \"\\n\");\n+\tfprintf(s->fp, _(\"\\n\"));\n \tstrbuf_release(&onebuf);\n \tstrbuf_release(&twobuf);\n }\n@@ -535,7 +536,7 @@ static void wt_status_print_untracked(struct wt_status *s)\n \tfor (i = 0; i < s->untracked.nr; i++) {\n \t\tstruct string_list_item *it;\n \t\tit = &(s->untracked.items[i]);\n-\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"#\\t\");\n+\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"#\\t\"));\n \t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), \"%s\",\n \t\t\t\t quote_path(it->string, strlen(it->string),\n \t\t\t\t\t    &buf, s->prefix));\n@@ -594,14 +595,14 @@ void wt_status_print(struct wt_status *s)\n \tconst char *branch_color = color(WT_STATUS_HEADER, s);\n \n \tif (s->branch) {\n-\t\tconst char *on_what = \"On branch \";\n+\t\tconst char *on_what = _(\"On branch \");\n \t\tconst char *branch_name = s->branch;\n \t\tif (!prefixcmp(branch_name, \"refs/heads/\"))\n \t\t\tbranch_name += 11;\n \t\telse if (!strcmp(branch_name, \"HEAD\")) {\n \t\t\tbranch_name = \"\";\n \t\t\tbranch_color = color(WT_STATUS_NOBRANCH, s);\n-\t\t\ton_what = \"Not currently on any branch.\";\n+\t\t\ton_what = _(\"Not currently on any branch.\");\n \t\t}\n \t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"# \");\n \t\tcolor_fprintf_ln(s->fp, branch_color, \"%s%s\", on_what, branch_name);\n@@ -610,9 +611,9 @@ void wt_status_print(struct wt_status *s)\n \t}\n \n \tif (s->is_initial) {\n-\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"#\");\n-\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"# Initial commit\");\n-\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"#\");\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"#\"));\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"# Initial commit\"));\n+\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"#\"));\n \t}\n \n \twt_status_print_updated(s);\n@@ -625,25 +626,25 @@ void wt_status_print(struct wt_status *s)\n \tif (s->show_untracked_files)\n \t\twt_status_print_untracked(s);\n \telse if (s->commitable)\n-\t\t fprintf(s->fp, \"# Untracked files not listed (use -u option to show untracked files)\\n\");\n+\t\t fprintf(s->fp, _(\"# Untracked files not listed (use -u option to show untracked files)\\n\"));\n \n \tif (s->verbose)\n \t\twt_status_print_verbose(s);\n \tif (!s->commitable) {\n \t\tif (s->amend)\n-\t\t\tfprintf(s->fp, \"# No changes\\n\");\n+\t\t\tfprintf(s->fp, _(\"# No changes\\n\"));\n \t\telse if (s->nowarn)\n \t\t\t; /* nothing */\n \t\telse if (s->workdir_dirty)\n-\t\t\tprintf(\"no changes added to commit (use \\\"git add\\\" and/or \\\"git commit -a\\\")\\n\");\n+\t\t\tprintf(_(\"no changes added to commit (use \\\"git add\\\" and/or \\\"git commit -a\\\")\\n\"));\n \t\telse if (s->untracked.nr)\n-\t\t\tprintf(\"nothing added to commit but untracked files present (use \\\"git add\\\" to track)\\n\");\n+\t\t\tprintf(_(\"nothing added to commit but untracked files present (use \\\"git add\\\" to track)\\n\"));\n \t\telse if (s->is_initial)\n-\t\t\tprintf(\"nothing to commit (create/copy files and use \\\"git add\\\" to track)\\n\");\n+\t\t\tprintf(_(\"nothing to commit (create/copy files and use \\\"git add\\\" to track)\\n\"));\n \t\telse if (!s->show_untracked_files)\n-\t\t\tprintf(\"nothing to commit (use -u to show untracked files)\\n\");\n+\t\t\tprintf(_(\"nothing to commit (use -u to show untracked files)\\n\"));\n \t\telse\n-\t\t\tprintf(\"nothing to commit (working directory clean)\\n\");\n+\t\t\tprintf(_(\"nothing to commit (working directory clean)\\n\"));\n \t}\n }\n \n-- \n1.7.0.4\n"},{"id":"141846","messageId":"AANLkTikkfD_mfBz3mEpu_lP5heB-22QtI6FZP2eZ_C1g@mail.gmail.com","threadId":"23830","inReplyTo":"20100517160503.GA12717@unpythonic.net","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Robert Buck","fromEmail":"buck.robert.j@gmail.com","sentAt":"2010-05-17T23:29:58Z","receivedAt":"2010-05-17T23:29:58Z","isPatch":true,"sender":{"key":"buck.robert.j@gmail.com","avatar":"https://gravatar.com/avatar/1686742e8ac2595378aac67f26fd638ddaf494194f9af0cb6082c4a7eca1a366?d=mp&s=160"},"body":"Is gettext portable? Or is it only POSIX? If it's not portable, have\nyou considered using ICU instead as it is the best of class solution\nfor I18N/L10N?\n\nThat's my 0.02.\n\n-Bob\n"},{"id":"141851","messageId":"AANLkTikUGV8HnkJcz1LOmbpttAmyhf-IynRDZkyNwG6N@mail.gmail.com","threadId":"23830","inReplyTo":"AANLkTikkfD_mfBz3mEpu_lP5heB-22QtI6FZP2eZ_C1g@mail.gmail.com","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-05-18T04:23:28Z","receivedAt":"2010-05-18T04:23:28Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Mon, May 17, 2010 at 23:29, Robert Buck <buck.robert.j@gmail.com> wrote:\n> Is gettext portable? Or is it only POSIX? If it's not portable, have\n> you considered using ICU instead as it is the best of class solution\n> for I18N/L10N?\n\nYes it's portable. libintl or libraries that provide compatible\ninterfaces are available on every platform Git is.\n\nICU is not a gettext replacement, it's a Unicode processing library, I\ndon't think it has any gettext-like features. In any case it would be\na huge dependency for the service it would provide.\n"},{"id":"141854","messageId":"4BF24467.7000204@drmicha.warpmail.net","threadId":"23830","inReplyTo":"20100517160503.GA12717@unpythonic.net","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2010-05-18T07:40:23Z","receivedAt":"2010-05-18T07:40:23Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Jeff Epler venit, vidit, dixit 17.05.2010 18:05:\n> Signed-off-by: Jeff Epler <jepler@unpythonic.net>\n> ---\n> [resent with Cc to list and thread participants]\n> \n> While I'm certain that there are a lot of things to object to in this\n> patch, it shows 90% of what is needed to use gettext to translate\n> the portions of git written in c, without involving undesired gnu\n> infrastructure such as automake.\n> \n> Makefile adds necessary rules for generating git.pot and for building\n> and installing compiled message catalogs (.mo) from text message\n> catalogs (.po).  It also adds a gettext support header and source file.\n> \n> Minimal changes are made to git to use the requested LC_CTYPE and\n> LC_MESSAGES, and some messages for 'git status' are marked for\n> translation.\n> \n> When I provided a gibberish translation of a message:\n> #: wt-status.c:87\n> msgid \"# Changed but not updated:\"\n> msgstr \"# Changes not blah blah blah\"\n> \n> running 'git status' used the translation:\n> $ git status\n> # On branch master\n> # Your branch is ahead of 'origin/master' by 1 commit.\n> #\n> # Changes not blah blah blah\n> ...\n> \n> I ran with 'make install' and prefix set in config.mak.  It didn't seem\n> to work when running from the source directory, and it may or may not\n> work with runtime prefix.\n> \n> \n>  Makefile    |   26 +++++++++++++\n>  gettext.c   |   17 +++++++++\n>  gettext.h   |   15 ++++++++\n>  git.c       |    3 ++\n>  wt-status.c |  117 ++++++++++++++++++++++++++++++-----------------------------\n>  5 files changed, 120 insertions(+), 58 deletions(-)\n>  create mode 100644 gettext.c\n>  create mode 100644 gettext.h\n> \n> diff --git a/Makefile b/Makefile\n> index 4f7224a..c02ca18 100644\n> --- a/Makefile\n> +++ b/Makefile\n> @@ -294,6 +294,8 @@ RPMBUILD = rpmbuild\n>  TCL_PATH = tclsh\n>  TCLTK_PATH = wish\n>  PTHREAD_LIBS = -lpthread\n> +XGETTEXT = xgettext\n> +MSGFMT = msgfmt\n>  \n>  export TCL_PATH TCLTK_PATH\n>  \n> @@ -518,6 +520,7 @@ LIB_H += userdiff.h\n>  LIB_H += utf8.h\n>  LIB_H += xdiff-interface.h\n>  LIB_H += xdiff/xdiff.h\n> +LIB_H += gettext.h\n>  \n>  LIB_OBJS += abspath.o\n>  LIB_OBJS += advice.o\n> @@ -559,6 +562,7 @@ LIB_OBJS += entry.o\n>  LIB_OBJS += environment.o\n>  LIB_OBJS += exec_cmd.o\n>  LIB_OBJS += fsck.o\n> +LIB_OBJS += gettext.o\n>  LIB_OBJS += graph.o\n>  LIB_OBJS += grep.o\n>  LIB_OBJS += hash.o\n> @@ -1371,6 +1375,12 @@ ifdef USE_NED_ALLOCATOR\n>         COMPAT_OBJS += compat/nedmalloc/nedmalloc.o\n>  endif\n>  \n> +ifdef NO_GETTEXT\n> +\tCOMPAT_CFLAGS += -DNO_GETTEXT\n> +else\n> +\tLIBINTL = -lintl\n> +endif\n> +\n>  ifeq ($(TCLTK_PATH),)\n>  NO_TCLTK=NoThanks\n>  endif\n> @@ -1400,6 +1410,7 @@ ifndef V\n>  \tQUIET_BUILT_IN = @echo '   ' BUILTIN $@;\n>  \tQUIET_GEN      = @echo '   ' GEN $@;\n>  \tQUIET_LNCP     = @echo '   ' LN/CP $@;\n> +\tQUIET_MSGFMT   = @echo '   ' MSGFMT $@;\n>  \tQUIET_SUBDIR0  = +@subdir=\n>  \tQUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \\\n>  \t\t\t $(MAKE) $(PRINT_DIR) -C $$subdir\n> @@ -1427,6 +1438,7 @@ gitexecdir_SQ = $(subst ','\\'',$(gitexecdir))\n>  template_dir_SQ = $(subst ','\\'',$(template_dir))\n>  htmldir_SQ = $(subst ','\\'',$(htmldir))\n>  prefix_SQ = $(subst ','\\'',$(prefix))\n> +sharedir_SQ = $(subst ','\\'',$(sharedir))\n>  \n>  SHELL_PATH_SQ = $(subst ','\\'',$(SHELL_PATH))\n>  PERL_PATH_SQ = $(subst ','\\'',$(PERL_PATH))\n> @@ -1858,6 +1870,17 @@ cscope:\n>  \t$(RM) cscope*\n>  \t$(FIND) . -name '*.[hcS]' -print | xargs cscope -b\n>  \n> +pot:\n> +\t$(XGETTEXT) -k_ -o po/git.pot $(C_OBJ:o=c)\n> +\n> +POFILES := $(wildcard po/*.po)\n> +MOFILES := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/git.mo,$(POFILES))\n> +MODIRS := $(patsubst po/%.po,share/locale/%/LC_MESSAGES/,$(POFILES))\n> +all:: $(MOFILES)\n> +share/locale/%/LC_MESSAGES/git.mo: po/%.po\n> +\t@mkdir -p $(dir $@)\n> +\t$(QUIET_MSGFMT)$(MSGFMT) -o $@ $<\n> +\n>  ### Detect prefix changes\n>  TRACK_CFLAGS = $(subst ','\\'',$(ALL_CFLAGS)):\\\n>               $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)\n> @@ -1970,6 +1993,9 @@ install: all\n>  \t$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'\n>  \t$(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'\n>  \t$(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'\n> +\t$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sharedir_SQ)/locale'\n> +\t(cd share && tar cf - locale) | \\\n> +\t\t(cd '$(DESTDIR_SQ)$(sharedir_SQ)' && umask 022 && tar xof -)\n>  \t$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install\n>  ifndef NO_PERL\n>  \t$(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install\n> diff --git a/gettext.c b/gettext.c\n> new file mode 100644\n> index 0000000..aadce19\n> --- /dev/null\n> +++ b/gettext.c\n> @@ -0,0 +1,17 @@\n> +#ifdef NO_GETTEXT\n> +void git_setup_gettext() {}\n> +#else\n> +#include \"exec_cmd.h\"\n> +#include <libintl.h>\n> +#include <stdlib.h>\n> +\n> +void git_setup_gettext() {\n> +    const char *podir = system_path(\"share/locale\");\n> +    if(!podir) return;\n> +    char *ret = bindtextdomain(\"git\", podir);\n> +    free((void*)podir);\n> +    ret = setlocale(LC_MESSAGES, \"\");\n> +    ret = setlocale(LC_CTYPE, \"\");\n> +    ret = textdomain(\"git\");\n> +}\n> +#endif\n> diff --git a/gettext.h b/gettext.h\n> new file mode 100644\n> index 0000000..8b221b4\n> --- /dev/null\n> +++ b/gettext.h\n> @@ -0,0 +1,15 @@\n> +#ifndef GETTEXT_H\n> +#define GETTEXT_H\n> +\n> +void git_setup_gettext();\n> +\n> +#ifdef NO_GETTEXT\n> +#define _(s) (s)\n> +#define N_(s) (s)\n> +#else\n> +#include <libintl.h>\n> +#define _(s) gettext(s)\n> +#define N_(s) (s)\n> +#endif\n> +\n> +#endif\n> diff --git a/git.c b/git.c\n> index 6bae305..5e7aedd 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -3,6 +3,7 @@\n>  #include \"cache.h\"\n>  #include \"quote.h\"\n>  #include \"run-command.h\"\n> +#include \"gettext.h\"\n>  \n>  const char git_usage_string[] =\n>  \t\"git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\\n\"\n> @@ -481,6 +482,8 @@ int main(int argc, const char **argv)\n>  \tif (!cmd)\n>  \t\tcmd = \"git-help\";\n>  \n> +\tgit_setup_gettext();\n> +\n>  \t/*\n>  \t * \"git-xxxx\" is the same as \"git xxxx\", but we obviously:\n>  \t *\n> diff --git a/wt-status.c b/wt-status.c\n> index 8ca59a2..a31cbc6 100644\n> --- a/wt-status.c\n> +++ b/wt-status.c\n> @@ -9,6 +9,7 @@\n>  #include \"quote.h\"\n>  #include \"run-command.h\"\n>  #include \"remote.h\"\n> +#include \"gettext.h\"\n>  \n>  static char default_wt_status_colors[][COLOR_MAXLEN] = {\n>  \tGIT_COLOR_NORMAL, /* WT_STATUS_HEADER */\n> @@ -48,33 +49,33 @@ static void wt_status_print_unmerged_header(struct wt_status *s)\n>  {\n>  \tconst char *c = color(WT_STATUS_HEADER, s);\n>  \n> -\tcolor_fprintf_ln(s->fp, c, \"# Unmerged paths:\");\n> +\tcolor_fprintf_ln(s->fp, c, _(\"# Unmerged paths:\"));\n>  \tif (!advice_status_hints)\n>  \t\treturn;\n>  \tif (s->in_merge)\n>  \t\t;\n>  \telse if (!s->is_initial)\n> -\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git reset %s <file>...\\\" to unstage)\", s->reference);\n> +\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git reset %s <file>...\\\" to unstage)\"), s->reference);\n>  \telse\n> -\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git rm --cached <file>...\\\" to unstage)\");\n> -\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" as appropriate to mark resolution)\");\n> -\tcolor_fprintf_ln(s->fp, c, \"#\");\n> +\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git rm --cached <file>...\\\" to unstage)\"));\n> +\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add/rm <file>...\\\" as appropriate to mark resolution)\"));\n> +\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n>  }\n>  \n>  static void wt_status_print_cached_header(struct wt_status *s)\n>  {\n>  \tconst char *c = color(WT_STATUS_HEADER, s);\n>  \n> -\tcolor_fprintf_ln(s->fp, c, \"# Changes to be committed:\");\n> +\tcolor_fprintf_ln(s->fp, c, _(\"# Changes to be committed:\"));\n>  \tif (!advice_status_hints)\n>  \t\treturn;\n>  \tif (s->in_merge)\n>  \t\t; /* NEEDSWORK: use \"git reset --unresolve\"??? */\n>  \telse if (!s->is_initial)\n> -\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git reset %s <file>...\\\" to unstage)\", s->reference);\n> +\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git reset %s <file>...\\\" to unstage)\"), s->reference);\n>  \telse\n> -\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git rm --cached <file>...\\\" to unstage)\");\n> -\tcolor_fprintf_ln(s->fp, c, \"#\");\n> +\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git rm --cached <file>...\\\" to unstage)\"));\n> +\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n>  }\n>  \n>  static void wt_status_print_dirty_header(struct wt_status *s,\n> @@ -83,32 +84,32 @@ static void wt_status_print_dirty_header(struct wt_status *s,\n>  {\n>  \tconst char *c = color(WT_STATUS_HEADER, s);\n>  \n> -\tcolor_fprintf_ln(s->fp, c, \"# Changed but not updated:\");\n> +\tcolor_fprintf_ln(s->fp, c, _(\"# Changed but not updated:\"));\n>  \tif (!advice_status_hints)\n>  \t\treturn;\n>  \tif (!has_deleted)\n> -\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add <file>...\\\" to update what will be committed)\");\n> +\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add <file>...\\\" to update what will be committed)\"));\n>  \telse\n> -\t\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\");\n> -\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\");\n> +\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add/rm <file>...\\\" to update what will be committed)\"));\n> +\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git checkout -- <file>...\\\" to discard changes in working directory)\"));\n>  \tif (has_dirty_submodules)\n> -\t\tcolor_fprintf_ln(s->fp, c, \"#   (commit or discard the untracked or modified content in submodules)\");\n> -\tcolor_fprintf_ln(s->fp, c, \"#\");\n> +\t\tcolor_fprintf_ln(s->fp, c, _(\"#   (commit or discard the untracked or modified content in submodules)\"));\n> +\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n>  }\n>  \n>  static void wt_status_print_untracked_header(struct wt_status *s)\n>  {\n>  \tconst char *c = color(WT_STATUS_HEADER, s);\n> -\tcolor_fprintf_ln(s->fp, c, \"# Untracked files:\");\n> +\tcolor_fprintf_ln(s->fp, c, _(\"# Untracked files:\"));\n>  \tif (!advice_status_hints)\n>  \t\treturn;\n> -\tcolor_fprintf_ln(s->fp, c, \"#   (use \\\"git add <file>...\\\" to include in what will be committed)\");\n> -\tcolor_fprintf_ln(s->fp, c, \"#\");\n> +\tcolor_fprintf_ln(s->fp, c, _(\"#   (use \\\"git add <file>...\\\" to include in what will be committed)\"));\n> +\tcolor_fprintf_ln(s->fp, c, _(\"#\"));\n>  }\n>  \n>  static void wt_status_print_trailer(struct wt_status *s)\n>  {\n> -\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"#\");\n> +\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"#\"));\n>  }\n>  \n>  #define quote_path quote_path_relative\n> @@ -119,20 +120,20 @@ static void wt_status_print_unmerged_data(struct wt_status *s,\n>  \tconst char *c = color(WT_STATUS_UNMERGED, s);\n>  \tstruct wt_status_change_data *d = it->util;\n>  \tstruct strbuf onebuf = STRBUF_INIT;\n> -\tconst char *one, *how = \"bug\";\n> +\tconst char *one, *how = _(\"bug\");\n>  \n>  \tone = quote_path(it->string, -1, &onebuf, s->prefix);\n> -\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"#\\t\");\n> +\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"#\\t\"));\n>  \tswitch (d->stagemask) {\n> -\tcase 1: how = \"both deleted:\"; break;\n> -\tcase 2: how = \"added by us:\"; break;\n> -\tcase 3: how = \"deleted by them:\"; break;\n> -\tcase 4: how = \"added by them:\"; break;\n> -\tcase 5: how = \"deleted by us:\"; break;\n> -\tcase 6: how = \"both added:\"; break;\n> -\tcase 7: how = \"both modified:\"; break;\n> +\tcase 1: how = _(\"both deleted:\"); break;\n> +\tcase 2: how = _(\"added by us:\"); break;\n> +\tcase 3: how = _(\"deleted by them:\"); break;\n> +\tcase 4: how = _(\"added by them:\"); break;\n> +\tcase 5: how = _(\"deleted by us:\"); break;\n> +\tcase 6: how = _(\"both added:\"); break;\n> +\tcase 7: how = _(\"both modified:\"); break;\n>  \t}\n> -\tcolor_fprintf(s->fp, c, \"%-20s%s\\n\", how, one);\n> +\tcolor_fprintf(s->fp, c, _(\"%-20s%s\\n\"), how, one);\n>  \tstrbuf_release(&onebuf);\n>  }\n>  \n> @@ -158,13 +159,13 @@ static void wt_status_print_change_data(struct wt_status *s,\n>  \t\tbreak;\n>  \tcase WT_STATUS_CHANGED:\n>  \t\tif (d->new_submodule_commits || d->dirty_submodule) {\n> -\t\t\tstrbuf_addstr(&extra, \" (\");\n> +\t\t\tstrbuf_addstr(&extra, _(\" (\"));\n>  \t\t\tif (d->new_submodule_commits)\n> -\t\t\t\tstrbuf_addf(&extra, \"new commits, \");\n> +\t\t\t\tstrbuf_addf(&extra, _(\"new commits, \"));\n>  \t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)\n> -\t\t\t\tstrbuf_addf(&extra, \"modified content, \");\n> +\t\t\t\tstrbuf_addf(&extra, _(\"modified content, \"));\n>  \t\t\tif (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)\n> -\t\t\t\tstrbuf_addf(&extra, \"untracked content, \");\n> +\t\t\t\tstrbuf_addf(&extra, _(\"untracked content, \"));\n>  \t\t\tstrbuf_setlen(&extra, extra.len - 2);\n>  \t\t\tstrbuf_addch(&extra, ')');\n>  \t\t}\n> @@ -175,40 +176,40 @@ static void wt_status_print_change_data(struct wt_status *s,\n>  \tone = quote_path(one_name, -1, &onebuf, s->prefix);\n>  \ttwo = quote_path(two_name, -1, &twobuf, s->prefix);\n>  \n> -\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"#\\t\");\n> +\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"#\\t\"));\n>  \tswitch (status) {\n>  \tcase DIFF_STATUS_ADDED:\n> -\t\tcolor_fprintf(s->fp, c, \"new file:   %s\", one);\n> +\t\tcolor_fprintf(s->fp, c, _(\"new file:   %s\"), one);\n>  \t\tbreak;\n>  \tcase DIFF_STATUS_COPIED:\n> -\t\tcolor_fprintf(s->fp, c, \"copied:     %s -> %s\", one, two);\n> +\t\tcolor_fprintf(s->fp, c, _(\"copied:     %s -> %s\"), one, two);\n>  \t\tbreak;\n>  \tcase DIFF_STATUS_DELETED:\n> -\t\tcolor_fprintf(s->fp, c, \"deleted:    %s\", one);\n> +\t\tcolor_fprintf(s->fp, c, _(\"deleted:    %s\"), one);\n>  \t\tbreak;\n>  \tcase DIFF_STATUS_MODIFIED:\n> -\t\tcolor_fprintf(s->fp, c, \"modified:   %s\", one);\n> +\t\tcolor_fprintf(s->fp, c, _(\"modified:   %s\"), one);\n>  \t\tbreak;\n>  \tcase DIFF_STATUS_RENAMED:\n> -\t\tcolor_fprintf(s->fp, c, \"renamed:    %s -> %s\", one, two);\n> +\t\tcolor_fprintf(s->fp, c, _(\"renamed:    %s -> %s\"), one, two);\n>  \t\tbreak;\n>  \tcase DIFF_STATUS_TYPE_CHANGED:\n> -\t\tcolor_fprintf(s->fp, c, \"typechange: %s\", one);\n> +\t\tcolor_fprintf(s->fp, c, _(\"typechange: %s\"), one);\n>  \t\tbreak;\n>  \tcase DIFF_STATUS_UNKNOWN:\n> -\t\tcolor_fprintf(s->fp, c, \"unknown:    %s\", one);\n> +\t\tcolor_fprintf(s->fp, c, _(\"unknown:    %s\"), one);\n>  \t\tbreak;\n>  \tcase DIFF_STATUS_UNMERGED:\n> -\t\tcolor_fprintf(s->fp, c, \"unmerged:   %s\", one);\n> +\t\tcolor_fprintf(s->fp, c, _(\"unmerged:   %s\"), one);\n\nI have no experience whatsover with gettext, but it looks quite\ndangerous to me to have printf format specifiers as part of the\nlocalized text. It means that our programs can crash depending on the\nLANG setting at run time if localisers mess up. We'll never catch this\nunless we run all tests in all languages!\n\nAlso, the basic structure of the output should probably be independent\nof the language, preferring consistent structure across languages over\nlinguistically consistent structure  within a language.\n\nThat means we'll have to do a lot of strcat's (the _() things are not\ncompile time constants, are they?) rather than those mechanical\nreplacements above. Are you prepared to do that?\n\n>  \t\tbreak;\n>  \tdefault:\n> -\t\tdie(\"bug: unhandled diff status %c\", status);\n> +\t\tdie(_(\"bug: unhandled diff status %c\"), status);\n>  \t}\n>  \tif (extra.len) {\n> -\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"%s\", extra.buf);\n> +\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"%s\"), extra.buf);\n\nSeriously?\n\n>  \t\tstrbuf_release(&extra);\n>  \t}\n> -\tfprintf(s->fp, \"\\n\");\n> +\tfprintf(s->fp, _(\"\\n\"));\n>  \tstrbuf_release(&onebuf);\n>  \tstrbuf_release(&twobuf);\n>  }\n> @@ -535,7 +536,7 @@ static void wt_status_print_untracked(struct wt_status *s)\n>  \tfor (i = 0; i < s->untracked.nr; i++) {\n>  \t\tstruct string_list_item *it;\n>  \t\tit = &(s->untracked.items[i]);\n> -\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"#\\t\");\n> +\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"#\\t\"));\n\n?\n\n>  \t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), \"%s\",\n>  \t\t\t\t quote_path(it->string, strlen(it->string),\n>  \t\t\t\t\t    &buf, s->prefix));\n> @@ -594,14 +595,14 @@ void wt_status_print(struct wt_status *s)\n>  \tconst char *branch_color = color(WT_STATUS_HEADER, s);\n>  \n>  \tif (s->branch) {\n> -\t\tconst char *on_what = \"On branch \";\n> +\t\tconst char *on_what = _(\"On branch \");\n>  \t\tconst char *branch_name = s->branch;\n>  \t\tif (!prefixcmp(branch_name, \"refs/heads/\"))\n>  \t\t\tbranch_name += 11;\n>  \t\telse if (!strcmp(branch_name, \"HEAD\")) {\n>  \t\t\tbranch_name = \"\";\n>  \t\t\tbranch_color = color(WT_STATUS_NOBRANCH, s);\n> -\t\t\ton_what = \"Not currently on any branch.\";\n> +\t\t\ton_what = _(\"Not currently on any branch.\");\n>  \t\t}\n>  \t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"# \");\n>  \t\tcolor_fprintf_ln(s->fp, branch_color, \"%s%s\", on_what, branch_name);\n> @@ -610,9 +611,9 @@ void wt_status_print(struct wt_status *s)\n>  \t}\n>  \n>  \tif (s->is_initial) {\n> -\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"#\");\n> -\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"# Initial commit\");\n> -\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), \"#\");\n> +\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"#\"));\n> +\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"# Initial commit\"));\n> +\t\tcolor_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), _(\"#\"));\n>  \t}\n>  \n>  \twt_status_print_updated(s);\n> @@ -625,25 +626,25 @@ void wt_status_print(struct wt_status *s)\n>  \tif (s->show_untracked_files)\n>  \t\twt_status_print_untracked(s);\n>  \telse if (s->commitable)\n> -\t\t fprintf(s->fp, \"# Untracked files not listed (use -u option to show untracked files)\\n\");\n> +\t\t fprintf(s->fp, _(\"# Untracked files not listed (use -u option to show untracked files)\\n\"));\n>  \n>  \tif (s->verbose)\n>  \t\twt_status_print_verbose(s);\n>  \tif (!s->commitable) {\n>  \t\tif (s->amend)\n> -\t\t\tfprintf(s->fp, \"# No changes\\n\");\n> +\t\t\tfprintf(s->fp, _(\"# No changes\\n\"));\n>  \t\telse if (s->nowarn)\n>  \t\t\t; /* nothing */\n>  \t\telse if (s->workdir_dirty)\n> -\t\t\tprintf(\"no changes added to commit (use \\\"git add\\\" and/or \\\"git commit -a\\\")\\n\");\n> +\t\t\tprintf(_(\"no changes added to commit (use \\\"git add\\\" and/or \\\"git commit -a\\\")\\n\"));\n>  \t\telse if (s->untracked.nr)\n> -\t\t\tprintf(\"nothing added to commit but untracked files present (use \\\"git add\\\" to track)\\n\");\n> +\t\t\tprintf(_(\"nothing added to commit but untracked files present (use \\\"git add\\\" to track)\\n\"));\n>  \t\telse if (s->is_initial)\n> -\t\t\tprintf(\"nothing to commit (create/copy files and use \\\"git add\\\" to track)\\n\");\n> +\t\t\tprintf(_(\"nothing to commit (create/copy files and use \\\"git add\\\" to track)\\n\"));\n>  \t\telse if (!s->show_untracked_files)\n> -\t\t\tprintf(\"nothing to commit (use -u to show untracked files)\\n\");\n> +\t\t\tprintf(_(\"nothing to commit (use -u to show untracked files)\\n\"));\n>  \t\telse\n> -\t\t\tprintf(\"nothing to commit (working directory clean)\\n\");\n> +\t\t\tprintf(_(\"nothing to commit (working directory clean)\\n\"));\n>  \t}\n>  }\n>  \n"},{"id":"141856","messageId":"AANLkTikNzz5T4oJR1MQjdWjs-4zmWfXrPaBOHNfAi-1S@mail.gmail.com","threadId":"23830","inReplyTo":"4BF24467.7000204@drmicha.warpmail.net","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-05-18T08:11:51Z","receivedAt":"2010-05-18T08:11:51Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Tue, May 18, 2010 at 07:40, Michael J Gruber\n<git@drmicha.warpmail.net> wrote:\n> I have no experience whatsover with gettext, but it looks quite\n> dangerous to me to have printf format specifiers as part of the\n> localized text. It means that our programs can crash depending on the\n> LANG setting at run time if localisers mess up. We'll never catch this\n> unless we run all tests in all languages!\n\nI don't have much experience with gettext either (except through\nLaunchpad), maybe it has some internal facilities to avoid errors in\nthese cases.\n\nYou can test if the translated messages contain the same format\nspecifiers as the originals, and in any case much larger projects than\nGit manage dozens of translations with gettext while avoiding\ndisaster.\n\n> Also, the basic structure of the output should probably be independent\n> of the language, preferring consistent structure across languages over\n> linguistically consistent structure  within a language.\n>\n> That means we'll have to do a lot of strcat's (the _() things are not\n> compile time constants, are they?) rather than those mechanical\n> replacements above. Are you prepared to do that?\n\nGenerally you don't to strcat's since you don't want to enforce word\norder, doing so will make the messages sound like Yoda in some of the\ntarget languages.\n\nThat does mean re-arranging some code if it's to be done properly.\n\n>>               break;\n>>       default:\n>> -             die(\"bug: unhandled diff status %c\", status);\n>> +             die(_(\"bug: unhandled diff status %c\"), status);\n>>       }\n>>       if (extra.len) {\n>> -             color_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"%s\", extra.buf);\n>> +             color_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"%s\"), extra.buf);\n>\n> Seriously?\n\nNo, as Jeff said it's just a proof of concept. That patch as-is\ndoesn't reflect good translation practices, it just bootstraps\ngettext.\n\nWhich is very useful by the way, thanks Jeff.\n"},{"id":"141868","messageId":"20100518164002.GC20842@unpythonic.net","threadId":"23830","inReplyTo":"4BF24467.7000204@drmicha.warpmail.net","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Jeff Epler","fromEmail":"jepler@unpythonic.net","sentAt":"2010-05-18T16:40:02Z","receivedAt":"2010-05-18T16:40:02Z","isPatch":true,"sender":{"key":"jepler@unpythonic.net","avatar":"https://avatars.githubusercontent.com/u/1517291?v=4"},"body":"On Tue, May 18, 2010 at 09:40:23AM +0200, Michael J Gruber wrote:\n> > -\t\tcolor_fprintf(s->fp, c, \"unmerged:   %s\", one);\n> > +\t\tcolor_fprintf(s->fp, c, _(\"unmerged:   %s\"), one);\n> \n> I have no experience whatsover with gettext, but it looks quite\n> dangerous to me to have printf format specifiers as part of the\n> localized text. It means that our programs can crash depending on the\n> LANG setting at run time if localisers mess up. We'll never catch this\n> unless we run all tests in all languages!\n\nThis is exactly how gettext works.  Yes, you can get crashes if the\ntranslated string does not have the right arguments--and I would not be\nat all surprised to hear of at least one privilege escalation bug\ndue to a bad message catalog, since printf format errors can be used in\nsuch interesting ways.\n\nAnyway, for printf-style formats, 'msgfmt' can be directed to check for\nthis situation:\n    $ cat bad.po\n    msgid \"\"\n    msgstr \"Content-Type: text/plain; charset=UTF-8\\n\"\n\n    #,c-format\n    msgid \"foo %s %d\"\n    msgstr \"föö %d %d\"\n\n    $ msgfmt --check-format bad.po\n    bad.po:6: format specifications in 'msgid' and 'msgstr' for argument 1 are not the same\n    msgfmt: found 1 fatal error\n \n> Also, the basic structure of the output should probably be independent\n> of the language, preferring consistent structure across languages over\n> linguistically consistent structure  within a language.\n\nNo, the ability of gettext+printf to use the right structure of the\nuser's language is a strength.  For instance, consider the translation\ninto Yoda's locale of the following sentence:\n\n    printf(\"The %s is %s.\\n\", \"Future\", \"Clouded\");\n\nThe proper localized message is\n\n    Clouded the Future is.\n\nAnything else will range from confusing to unintelligible to the\nnative speaker.  You get that with gettext by writing\n\n    printf(_(\"The %s is %s.\\n\"), _(\"Future\"), _(\"Clouded\"));\n\ntogether with the message catalog entry\n    msgid \"The %s is %s.\\n\"\n    msgfmt \"%2$s the %1$s is.\\n\"\n\n> >  \tif (extra.len) {\n> > -\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), \"%s\", extra.buf);\n> > +\t\tcolor_fprintf(s->fp, color(WT_STATUS_HEADER, s), _(\"%s\"), extra.buf);\n> \n> Seriously?\n\nNo, that one's a mistake.  I did not take care when choosing which\nstrings to mark, because I was mostly interested in showing a\nproof-of-concept for using gettext to translate core parts of git.\n\nThe amount of work to mark all the source files and then to keep the\nmarks up to date should not be underestimated--and that's just the work\nto enable translators to localize the software.  It is important to\ngauge the interest in the git community in actually doing this work.\n\nAs my own primary language is English, I have only a theoretical\ninterest in this feature.  However, the existence of translations for\ngitk and git-gui indicates to me that the community probably does desire\nthis.\n\nJeff\n"},{"id":"141870","messageId":"AANLkTikbl3jDXzdBhd0w6jSXTD-q5kai6Sy6CKLVmRdD@mail.gmail.com","threadId":"23830","inReplyTo":"20100518164002.GC20842@unpythonic.net","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-05-18T17:02:08Z","receivedAt":"2010-05-18T17:02:08Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Tue, May 18, 2010 at 16:40, Jeff Epler <jepler@unpythonic.net> wrote:\n> The amount of work to mark all the source files and then to keep the\n> marks up to date should not be underestimated--and that's just the work\n> to enable translators to localize the software.  It is important to\n> gauge the interest in the git community in actually doing this work.\n\nIt's also something you shouldn't overestimate. I've been involved in\ninternationalizing several projects that were previously English-only.\n\nThe work of making things translatable can be done incrementally. You\nalso don't have to get everything right the first time, the current\nproof of concept translation of `git status` for instance suffers from\nnumerous problems, but it's still better than nothing.\n\nIt can be used as-is and then incrementally improved by arranging the\nstrings more intelligently in the future.\n"},{"id":"141972","messageId":"AANLkTinksuRQ-XJrusR9PXeP0HowaK-6ZJh8uwxMFUg2@mail.gmail.com","threadId":"23830","inReplyTo":"AANLkTikbl3jDXzdBhd0w6jSXTD-q5kai6Sy6CKLVmRdD@mail.gmail.com","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2010-05-20T16:02:46Z","receivedAt":"2010-05-20T16:02:46Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Tue, May 18, 2010 at 17:02, Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n> On Tue, May 18, 2010 at 16:40, Jeff Epler <jepler@unpythonic.net> wrote:\n>> The amount of work to mark all the source files and then to keep the\n>> marks up to date should not be underestimated--and that's just the work\n>> to enable translators to localize the software.  It is important to\n>> gauge the interest in the git community in actually doing this work.\n>\n> It's also something you shouldn't overestimate. I've been involved in\n> internationalizing several projects that were previously English-only.\n>\n> The work of making things translatable can be done incrementally. You\n> also don't have to get everything right the first time, the current\n> proof of concept translation of `git status` for instance suffers from\n> numerous problems, but it's still better than nothing.\n>\n> It can be used as-is and then incrementally improved by arranging the\n> strings more intelligently in the future.\n\nI did some work on this in my branch:\nhttp://github.com/avar/git/compare/master...topic/git-gettext\n\nFixed up the Makefile rules a bit, added appropriate gitignores, and\nadded a work in progress po/is.po.\n\nStill have to go through the gettext manual to figure out how to\nintegrate this with our shellscripts.\n"},{"id":"142065","messageId":"1274464949.1166.3.camel@localhost.localdomain","threadId":"23830","inReplyTo":"AANLkTinksuRQ-XJrusR9PXeP0HowaK-6ZJh8uwxMFUg2@mail.gmail.com","subject":"Re: [PATCH] WIP: begin to translate git with gettext","fromName":"Dévai Tamás","fromEmail":"devait@vnet.hu","sentAt":"2010-05-21T18:02:28Z","receivedAt":"2010-05-21T18:02:28Z","isPatch":true,"sender":{"key":"devait@vnet.hu","avatar":null},"body":"2010. 05. 20, csütörtök keltezéssel 16.02-kor Ævar Arnfjörð Bjarmason\nezt írta:\n\n> Still have to go through the gettext manual to figure out how to\n> integrate this with our shellscripts.\n\ninfo '(gettext)sh'\n\nor the online help[1][2] might be of some help.\n\n[1]: http://www.gnu.org/software/gettext/manual/gettext.html#sh_002dformat\n[2]: http://www.gnu.org/software/gettext/manual/gettext.html#sh\n"}]}