{"thread":{"id":"29880","subject":"[PATCH 0/5] i18n-ize relative dates, git-help and parseopt","startedAt":"2012-03-08T09:16:33Z","lastAt":"2012-03-16T11:47:06Z","messageCount":15,"participants":["Nguyễn Thái Ngọc Duy","Jonathan Nieder","Nguyen Thai Ngoc Duy","Ævar Arnfjörð Bjarmason","Johannes Sixt"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"186394","messageId":"1331198198-22409-1-git-send-email-pclouds@gmail.com","threadId":"29880","inReplyTo":null,"subject":"[PATCH 0/5] i18n-ize relative dates, git-help and parseopt","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-08T09:16:33Z","receivedAt":"2012-03-08T09:16:33Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Except \"git log --date=relative\", the focus of this series is \"git\nhelp\" and \"git blah -h\" (only \"git help -h\" for the latter because\nother commands need a lot of N_() additions). I think I got everything\nfrom \"git help [options]\" fully translatable.\n\nNguyễn Thái Ngọc Duy (5):\n  i18n: keep the last \\n even when text is poisoned\n  i18n: mark relative dates for translation\n  i18n: parseopt: lookup help and argument translations when showing\n    usage\n  i18n: help: mark parseopt strings for translation\n  i18n: help: mark strings for translation\n\n Makefile            |    2 +-\n builtin/help.c      |   58 +++++++++++++++++++++++++-------------------------\n date.c              |   46 +++++++++++++++++++++++++---------------\n generate-cmdlist.sh |    2 +-\n gettext.c           |   10 ++++++++\n gettext.h           |    6 +++-\n git.c               |    2 +-\n help.c              |   34 ++++++++++++++++++-----------\n parse-options.c     |   15 ++++++-------\n 9 files changed, 103 insertions(+), 72 deletions(-)\n\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"186395","messageId":"1331198198-22409-2-git-send-email-pclouds@gmail.com","threadId":"29880","inReplyTo":"1331198198-22409-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 1/5] i18n: keep the last \\n even when text is poisoned","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-08T09:16:34Z","receivedAt":"2012-03-08T09:16:34Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This helps maintain the layout somewhat when translatable text is\npoisoned.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n gettext.c |   10 ++++++++++\n gettext.h |    6 ++++--\n 2 files changed, 14 insertions(+), 2 deletions(-)\n\ndiff --git a/gettext.c b/gettext.c\nindex f75bca7..60bc2ad 100644\n--- a/gettext.c\n+++ b/gettext.c\n@@ -24,6 +24,16 @@ int use_gettext_poison(void)\n \t\tpoison_requested = getenv(\"GIT_GETTEXT_POISON\") ? 1 : 0;\n \treturn poison_requested;\n }\n+\n+const char *poison_text(const char *msgid)\n+{\n+\tint len = strlen(msgid);\n+\tif (len && msgid[len-1] == '\\n')\n+\t\treturn \"# GETTEXT POISON #\\n\";\n+\telse\n+\t\treturn \"# GETTEXT POISON #\";\n+}\n+\n #endif\n \n #ifndef NO_GETTEXT\ndiff --git a/gettext.h b/gettext.h\nindex 57ba8bb..2313c84 100644\n--- a/gettext.h\n+++ b/gettext.h\n@@ -38,20 +38,22 @@ static inline void git_setup_gettext(void)\n \n #ifdef GETTEXT_POISON\n extern int use_gettext_poison(void);\n+extern const char *poison_text(const char *msgid);\n #else\n #define use_gettext_poison() 0\n+#define poison_text(x) NULL\n #endif\n \n static inline FORMAT_PRESERVING(1) const char *_(const char *msgid)\n {\n-\treturn use_gettext_poison() ? \"# GETTEXT POISON #\" : gettext(msgid);\n+\treturn use_gettext_poison() ? poison_text(msgid) : gettext(msgid);\n }\n \n static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2)\n const char *Q_(const char *msgid, const char *plu, unsigned long n)\n {\n \tif (use_gettext_poison())\n-\t\treturn \"# GETTEXT POISON #\";\n+\t\treturn poison_text(msgid);\n \treturn ngettext(msgid, plu, n);\n }\n \n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"186396","messageId":"1331198198-22409-3-git-send-email-pclouds@gmail.com","threadId":"29880","inReplyTo":"1331198198-22409-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 2/5] i18n: mark relative dates for translation","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-08T09:16:35Z","receivedAt":"2012-03-08T09:16:35Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"English dates get correct plural/singular form as a side effect.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n date.c |   46 +++++++++++++++++++++++++++++-----------------\n 1 files changed, 29 insertions(+), 17 deletions(-)\n\ndiff --git a/date.c b/date.c\nindex a5055ca..063f344 100644\n--- a/date.c\n+++ b/date.c\n@@ -93,38 +93,46 @@ const char *show_date_relative(unsigned long time, int tz,\n {\n \tunsigned long diff;\n \tif (now->tv_sec < time)\n-\t\treturn \"in the future\";\n+\t\treturn _(\"in the future\");\n \tdiff = now->tv_sec - time;\n \tif (diff < 90) {\n-\t\tsnprintf(timebuf, timebuf_size, \"%lu seconds ago\", diff);\n+\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t Q_(\"%lu second ago\", \"%lu seconds ago\", diff), diff);\n \t\treturn timebuf;\n \t}\n \t/* Turn it into minutes */\n \tdiff = (diff + 30) / 60;\n \tif (diff < 90) {\n-\t\tsnprintf(timebuf, timebuf_size, \"%lu minutes ago\", diff);\n+\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t Q_(\"%lu minute ago\", \"%lu minutes ago\", diff), diff);\n \t\treturn timebuf;\n \t}\n \t/* Turn it into hours */\n \tdiff = (diff + 30) / 60;\n \tif (diff < 36) {\n-\t\tsnprintf(timebuf, timebuf_size, \"%lu hours ago\", diff);\n+\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t Q_(\"%lu hour ago\", \"%lu hours ago\", diff), diff);\n \t\treturn timebuf;\n \t}\n \t/* We deal with number of days from here on */\n \tdiff = (diff + 12) / 24;\n \tif (diff < 14) {\n-\t\tsnprintf(timebuf, timebuf_size, \"%lu days ago\", diff);\n+\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t Q_(\"%lu day ago\", \"%lu days ago\", diff), diff);\n \t\treturn timebuf;\n \t}\n \t/* Say weeks for the past 10 weeks or so */\n \tif (diff < 70) {\n-\t\tsnprintf(timebuf, timebuf_size, \"%lu weeks ago\", (diff + 3) / 7);\n+\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t Q_(\"%lu week ago\", \"%lu weeks ago\", (diff + 3) / 7),\n+\t\t\t (diff + 3) / 7);\n \t\treturn timebuf;\n \t}\n \t/* Say months for the past 12 months or so */\n \tif (diff < 365) {\n-\t\tsnprintf(timebuf, timebuf_size, \"%lu months ago\", (diff + 15) / 30);\n+\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t Q_(\"%lu month ago\", \"%lu months ago\", (diff + 15) / 30),\n+\t\t\t (diff + 15) / 30);\n \t\treturn timebuf;\n \t}\n \t/* Give years and months for 5 years or so */\n@@ -132,19 +140,23 @@ const char *show_date_relative(unsigned long time, int tz,\n \t\tunsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);\n \t\tunsigned long years = totalmonths / 12;\n \t\tunsigned long months = totalmonths % 12;\n-\t\tint n;\n-\t\tn = snprintf(timebuf, timebuf_size, \"%lu year%s\",\n-\t\t\t\tyears, (years > 1 ? \"s\" : \"\"));\n-\t\tif (months)\n-\t\t\tsnprintf(timebuf + n, timebuf_size - n,\n-\t\t\t\t\t\", %lu month%s ago\",\n-\t\t\t\t\tmonths, (months > 1 ? \"s\" : \"\"));\n-\t\telse\n-\t\t\tsnprintf(timebuf + n, timebuf_size - n, \" ago\");\n+\t\tif (months) {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&sb, Q_(\"%lu year\", \"%lu years\", years), years);\n+\t\t\t/* TRANSLATORS: \"%s\" is \"<n> years\" */\n+\t\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t\t Q_(\"%s, %lu month ago\", \"%s, %lu months ago\", months),\n+\t\t\t\t sb.buf, months);\n+\t\t\tstrbuf_release(&sb);\n+\t\t} else\n+\t\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\t\t Q_(\"%lu year ago\", \"%lu years ago\", years), years);\n \t\treturn timebuf;\n \t}\n \t/* Otherwise, just years. Centuries is probably overkill. */\n-\tsnprintf(timebuf, timebuf_size, \"%lu years ago\", (diff + 183) / 365);\n+\tsnprintf(timebuf, timebuf_size,\n+\t\t Q_(\"%lu year ago\", \"%lu years ago\", (diff + 183) / 365),\n+\t\t (diff + 183) / 365);\n \treturn timebuf;\n }\n \n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"186397","messageId":"1331198198-22409-4-git-send-email-pclouds@gmail.com","threadId":"29880","inReplyTo":"1331198198-22409-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 3/5] i18n: parseopt: lookup help and argument translations when showing usage","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-08T09:16:36Z","receivedAt":"2012-03-08T09:16:36Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\"struct option\" and the help usage array needs N_() marking on help\nand argument text for this to work.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n parse-options.c |   15 +++++++--------\n 1 files changed, 7 insertions(+), 8 deletions(-)\n\ndiff --git a/parse-options.c b/parse-options.c\nindex 850cfa7..6247c20 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -490,7 +490,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)\n \t\t\ts = literal ? \"[%s]\" : \"[<%s>]\";\n \telse\n \t\ts = literal ? \" %s\" : \" <%s>\";\n-\treturn fprintf(outfile, s, opts->argh ? opts->argh : \"...\");\n+\treturn fprintf(outfile, s, opts->argh ? gettext(opts->argh) : \"...\");\n }\n \n #define USAGE_OPTS_WIDTH 24\n@@ -508,13 +508,12 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,\n \tif (!err && ctx && ctx->flags & PARSE_OPT_SHELL_EVAL)\n \t\tfprintf(outfile, \"cat <<\\\\EOF\\n\");\n \n-\tfprintf(outfile, \"usage: %s\\n\", *usagestr++);\n+\tfprintf(outfile, _(\"usage: %s\\n\"), gettext(*usagestr++));\n \twhile (*usagestr && **usagestr)\n-\t\tfprintf(outfile, \"   or: %s\\n\", *usagestr++);\n+\t\tfprintf(outfile, _(\"   or: %s\\n\"), gettext(*usagestr++));\n \twhile (*usagestr) {\n-\t\tfprintf(outfile, \"%s%s\\n\",\n-\t\t\t\t**usagestr ? \"    \" : \"\",\n-\t\t\t\t*usagestr);\n+\t\tfprintf(outfile, \"%s%s\\n\", **usagestr ? \"    \" : \"\",\n+\t\t\tgettext(*usagestr));\n \t\tusagestr++;\n \t}\n \n@@ -528,7 +527,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,\n \t\tif (opts->type == OPTION_GROUP) {\n \t\t\tfputc('\\n', outfile);\n \t\t\tif (*opts->help)\n-\t\t\t\tfprintf(outfile, \"%s\\n\", opts->help);\n+\t\t\t\tfprintf(outfile, \"%s\\n\", gettext(opts->help));\n \t\t\tcontinue;\n \t\t}\n \t\tif (!full && (opts->flags & PARSE_OPT_HIDDEN))\n@@ -558,7 +557,7 @@ static int usage_with_options_internal(struct parse_opt_ctx_t *ctx,\n \t\t\tfputc('\\n', outfile);\n \t\t\tpad = USAGE_OPTS_WIDTH;\n \t\t}\n-\t\tfprintf(outfile, \"%*s%s\\n\", pad + USAGE_GAP, \"\", opts->help);\n+\t\tfprintf(outfile, \"%*s%s\\n\", pad + USAGE_GAP, \"\", gettext(opts->help));\n \t}\n \tfputc('\\n', outfile);\n \n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"186398","messageId":"1331198198-22409-5-git-send-email-pclouds@gmail.com","threadId":"29880","inReplyTo":"1331198198-22409-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 4/5] i18n: help: mark parseopt strings for translation","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-08T09:16:37Z","receivedAt":"2012-03-08T09:16:37Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n A demonstration of the previous patch.\n\n builtin/help.c |   10 +++++-----\n 1 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/help.c b/builtin/help.c\nindex 61ff798..22756bd 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -32,17 +32,17 @@ enum help_format {\n static int show_all = 0;\n static enum help_format help_format = HELP_FORMAT_NONE;\n static struct option builtin_help_options[] = {\n-\tOPT_BOOLEAN('a', \"all\", &show_all, \"print all available commands\"),\n-\tOPT_SET_INT('m', \"man\", &help_format, \"show man page\", HELP_FORMAT_MAN),\n-\tOPT_SET_INT('w', \"web\", &help_format, \"show manual in web browser\",\n+\tOPT_BOOLEAN('a', \"all\", &show_all, N_(\"print all available commands\")),\n+\tOPT_SET_INT('m', \"man\", &help_format, N_(\"show man page\"), HELP_FORMAT_MAN),\n+\tOPT_SET_INT('w', \"web\", &help_format, N_(\"show manual in web browser\"),\n \t\t\tHELP_FORMAT_WEB),\n-\tOPT_SET_INT('i', \"info\", &help_format, \"show info page\",\n+\tOPT_SET_INT('i', \"info\", &help_format, N_(\"show info page\"),\n \t\t\tHELP_FORMAT_INFO),\n \tOPT_END(),\n };\n \n static const char * const builtin_help_usage[] = {\n-\t\"git help [--all] [--man|--web|--info] [command]\",\n+\tN_(\"git help [--all] [--man|--web|--info] [command]\"),\n \tNULL\n };\n \n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"186399","messageId":"1331198198-22409-6-git-send-email-pclouds@gmail.com","threadId":"29880","inReplyTo":"1331198198-22409-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 5/5] i18n: help: mark strings for translation","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-08T09:16:38Z","receivedAt":"2012-03-08T09:16:38Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"This patch also marks most common commands' synopsis for translation\nso that \"git help\" gives a friendly listing.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Makefile            |    2 +-\n builtin/help.c      |   48 ++++++++++++++++++++++++------------------------\n generate-cmdlist.sh |    2 +-\n git.c               |    2 +-\n help.c              |   34 +++++++++++++++++++++-------------\n 5 files changed, 48 insertions(+), 40 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex be1957a..34cf971 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2282,7 +2282,7 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \\\n \t--keyword=_ --keyword=N_ --keyword=\"Q_:1,2\"\n XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell\n XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl\n-LOCALIZED_C := $(C_OBJ:o=c)\n+LOCALIZED_C := $(C_OBJ:o=c) common-cmds.h\n LOCALIZED_SH := $(SCRIPT_SH)\n LOCALIZED_PERL := $(SCRIPT_PERL)\n \ndiff --git a/builtin/help.c b/builtin/help.c\nindex 22756bd..3d0f8de 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -54,7 +54,7 @@ static enum help_format parse_help_format(const char *format)\n \t\treturn HELP_FORMAT_INFO;\n \tif (!strcmp(format, \"web\") || !strcmp(format, \"html\"))\n \t\treturn HELP_FORMAT_WEB;\n-\tdie(\"unrecognized help format '%s'\", format);\n+\tdie(_(\"unrecognized help format '%s'\"), format);\n }\n \n static const char *get_man_viewer_info(const char *name)\n@@ -82,7 +82,7 @@ static int check_emacsclient_version(void)\n \tec_process.err = -1;\n \tec_process.stdout_to_stderr = 1;\n \tif (start_command(&ec_process))\n-\t\treturn error(\"Failed to start emacsclient.\");\n+\t\treturn error(_(\"Failed to start emacsclient.\"));\n \n \tstrbuf_read(&buffer, ec_process.err, 20);\n \tclose(ec_process.err);\n@@ -95,7 +95,7 @@ static int check_emacsclient_version(void)\n \n \tif (prefixcmp(buffer.buf, \"emacsclient\")) {\n \t\tstrbuf_release(&buffer);\n-\t\treturn error(\"Failed to parse emacsclient version.\");\n+\t\treturn error(_(\"Failed to parse emacsclient version.\"));\n \t}\n \n \tstrbuf_remove(&buffer, 0, strlen(\"emacsclient\"));\n@@ -103,7 +103,7 @@ static int check_emacsclient_version(void)\n \n \tif (version < 22) {\n \t\tstrbuf_release(&buffer);\n-\t\treturn error(\"emacsclient version '%d' too old (< 22).\",\n+\t\treturn error(_(\"emacsclient version '%d' too old (< 22).\"),\n \t\t\tversion);\n \t}\n \n@@ -121,7 +121,7 @@ static void exec_woman_emacs(const char *path, const char *page)\n \t\t\tpath = \"emacsclient\";\n \t\tstrbuf_addf(&man_page, \"(woman \\\"%s\\\")\", page);\n \t\texeclp(path, \"emacsclient\", \"-e\", man_page.buf, (char *)NULL);\n-\t\twarning(\"failed to exec '%s': %s\", path, strerror(errno));\n+\t\twarning(_(\"failed to exec '%s': %s\"), path, strerror(errno));\n \t}\n }\n \n@@ -149,7 +149,7 @@ static void exec_man_konqueror(const char *path, const char *page)\n \t\t\tpath = \"kfmclient\";\n \t\tstrbuf_addf(&man_page, \"man:%s(1)\", page);\n \t\texeclp(path, filename, \"newTab\", man_page.buf, (char *)NULL);\n-\t\twarning(\"failed to exec '%s': %s\", path, strerror(errno));\n+\t\twarning(_(\"failed to exec '%s': %s\"), path, strerror(errno));\n \t}\n }\n \n@@ -158,7 +158,7 @@ static void exec_man_man(const char *path, const char *page)\n \tif (!path)\n \t\tpath = \"man\";\n \texeclp(path, \"man\", page, (char *)NULL);\n-\twarning(\"failed to exec '%s': %s\", path, strerror(errno));\n+\twarning(_(\"failed to exec '%s': %s\"), path, strerror(errno));\n }\n \n static void exec_man_cmd(const char *cmd, const char *page)\n@@ -166,7 +166,7 @@ static void exec_man_cmd(const char *cmd, const char *page)\n \tstruct strbuf shell_cmd = STRBUF_INIT;\n \tstrbuf_addf(&shell_cmd, \"%s %s\", cmd, page);\n \texecl(\"/bin/sh\", \"sh\", \"-c\", shell_cmd.buf, (char *)NULL);\n-\twarning(\"failed to exec '%s': %s\", cmd, strerror(errno));\n+\twarning(_(\"failed to exec '%s': %s\"), cmd, strerror(errno));\n }\n \n static void add_man_viewer(const char *name)\n@@ -206,8 +206,8 @@ static int add_man_viewer_path(const char *name,\n \tif (supported_man_viewer(name, len))\n \t\tdo_add_man_viewer_info(name, len, value);\n \telse\n-\t\twarning(\"'%s': path for unsupported man viewer.\\n\"\n-\t\t\t\"Please consider using 'man.<tool>.cmd' instead.\",\n+\t\twarning(_(\"'%s': path for unsupported man viewer.\\n\"\n+\t\t\t  \"Please consider using 'man.<tool>.cmd' instead.\"),\n \t\t\tname);\n \n \treturn 0;\n@@ -218,8 +218,8 @@ static int add_man_viewer_cmd(const char *name,\n \t\t\t      const char *value)\n {\n \tif (supported_man_viewer(name, len))\n-\t\twarning(\"'%s': cmd for supported man viewer.\\n\"\n-\t\t\t\"Please consider using 'man.<tool>.path' instead.\",\n+\t\twarning(_(\"'%s': cmd for supported man viewer.\\n\"\n+\t\t\t  \"Please consider using 'man.<tool>.path' instead.\"),\n \t\t\tname);\n \telse\n \t\tdo_add_man_viewer_info(name, len, value);\n@@ -280,11 +280,11 @@ void list_common_cmds_help(void)\n \t\t\tlongest = strlen(common_cmds[i].name);\n \t}\n \n-\tputs(\"The most commonly used git commands are:\");\n+\tputs(_(\"The most commonly used git commands are:\"));\n \tfor (i = 0; i < ARRAY_SIZE(common_cmds); i++) {\n \t\tprintf(\"   %s   \", common_cmds[i].name);\n \t\tmput_char(' ', longest - strlen(common_cmds[i].name));\n-\t\tputs(common_cmds[i].help);\n+\t\tputs(gettext(common_cmds[i].help));\n \t}\n }\n \n@@ -348,7 +348,7 @@ static void exec_viewer(const char *name, const char *page)\n \telse if (info)\n \t\texec_man_cmd(info, page);\n \telse\n-\t\twarning(\"'%s': unknown man viewer.\", name);\n+\t\twarning(_(\"'%s': unknown man viewer.\"), name);\n }\n \n static void show_man_page(const char *git_cmd)\n@@ -365,7 +365,7 @@ static void show_man_page(const char *git_cmd)\n \tif (fallback)\n \t\texec_viewer(fallback, page);\n \texec_viewer(\"man\", page);\n-\tdie(\"no man viewer handled the request\");\n+\tdie(_(\"no man viewer handled the request\"));\n }\n \n static void show_info_page(const char *git_cmd)\n@@ -373,7 +373,7 @@ static void show_info_page(const char *git_cmd)\n \tconst char *page = cmd_to_page(git_cmd);\n \tsetenv(\"INFOPATH\", system_path(GIT_INFO_PATH), 1);\n \texeclp(\"info\", \"info\", \"gitman\", page, (char *)NULL);\n-\tdie(\"no info viewer handled the request\");\n+\tdie(_(\"no info viewer handled the request\"));\n }\n \n static void get_html_page_path(struct strbuf *page_path, const char *page)\n@@ -384,7 +384,7 @@ static void get_html_page_path(struct strbuf *page_path, const char *page)\n \t/* Check that we have a git documentation directory. */\n \tif (stat(mkpath(\"%s/git.html\", html_path), &st)\n \t    || !S_ISREG(st.st_mode))\n-\t\tdie(\"'%s': not a documentation directory.\", html_path);\n+\t\tdie(_(\"'%s': not a documentation directory.\"), html_path);\n \n \tstrbuf_init(page_path, 0);\n \tstrbuf_addf(page_path, \"%s/%s.html\", html_path, page);\n@@ -424,16 +424,16 @@ int cmd_help(int argc, const char **argv, const char *prefix)\n \tparsed_help_format = help_format;\n \n \tif (show_all) {\n-\t\tprintf(\"usage: %s\\n\\n\", git_usage_string);\n-\t\tlist_commands(\"git commands\", &main_cmds, &other_cmds);\n-\t\tprintf(\"%s\\n\", git_more_info_string);\n+\t\tprintf(_(\"usage: %s\\n\\n\"), gettext(git_usage_string));\n+\t\tlist_commands(_(\"git commands\"), &main_cmds, &other_cmds);\n+\t\tprintf(\"%s\\n\", gettext(git_more_info_string));\n \t\treturn 0;\n \t}\n \n \tif (!argv[0]) {\n-\t\tprintf(\"usage: %s\\n\\n\", git_usage_string);\n+\t\tprintf(_(\"usage: %s\\n\\n\"), gettext(git_usage_string));\n \t\tlist_common_cmds_help();\n-\t\tprintf(\"\\n%s\\n\", git_more_info_string);\n+\t\tprintf(\"\\n%s\\n\", gettext(git_more_info_string));\n \t\treturn 0;\n \t}\n \n@@ -445,7 +445,7 @@ int cmd_help(int argc, const char **argv, const char *prefix)\n \n \talias = alias_lookup(argv[0]);\n \tif (alias && !is_git_command(argv[0])) {\n-\t\tprintf(\"`git %s' is aliased to `%s'\\n\", argv[0], alias);\n+\t\tprintf(_(\"`git %s' is aliased to `%s'\\n\"), argv[0], alias);\n \t\treturn 0;\n \t}\n \ndiff --git a/generate-cmdlist.sh b/generate-cmdlist.sh\nindex 1093ef4..9a4c9b9 100755\n--- a/generate-cmdlist.sh\n+++ b/generate-cmdlist.sh\n@@ -16,7 +16,7 @@ do\n      /^NAME/,/git-'\"$cmd\"'/H\n      ${\n \t    x\n-\t    s/.*git-'\"$cmd\"' - \\(.*\\)/  {\"'\"$cmd\"'\", \"\\1\"},/\n+\t    s/.*git-'\"$cmd\"' - \\(.*\\)/  {\"'\"$cmd\"'\", N_(\"\\1\")},/\n \t    p\n      }' \"Documentation/git-$cmd.txt\"\n done\ndiff --git a/git.c b/git.c\nindex 3805616..4486deb 100644\n--- a/git.c\n+++ b/git.c\n@@ -13,7 +13,7 @@ const char git_usage_string[] =\n \t\"           <command> [<args>]\";\n \n const char git_more_info_string[] =\n-\t\"See 'git help <command>' for more information on a specific command.\";\n+\tN_(\"See 'git help <command>' for more information on a specific command.\");\n \n static struct startup_info git_startup_info;\n static int use_pager = -1;\ndiff --git a/help.c b/help.c\nindex 14eefc9..69927d8 100644\n--- a/help.c\n+++ b/help.c\n@@ -217,8 +217,9 @@ void list_commands(const char *title, struct cmdnames *main_cmds,\n \n \tif (main_cmds->cnt) {\n \t\tconst char *exec_path = git_exec_path();\n-\t\tprintf(\"available %s in '%s'\\n\", title, exec_path);\n-\t\tprintf(\"----------------\");\n+\t\tprintf(_(\"available %s in '%s'\\n\"), title, exec_path);\n+\t\t/* TRANSLATIONS: this must align with \"available %s in '%s'\\n\"*/\n+\t\tprintf(_(\"----------------\"));\n \t\tmput_char('-', strlen(title) + strlen(exec_path));\n \t\tputchar('\\n');\n \t\tpretty_print_string_list(main_cmds, longest);\n@@ -226,8 +227,12 @@ void list_commands(const char *title, struct cmdnames *main_cmds,\n \t}\n \n \tif (other_cmds->cnt) {\n-\t\tprintf(\"%s available from elsewhere on your $PATH\\n\", title);\n-\t\tprintf(\"---------------------------------------\");\n+\t\tprintf(_(\"%s available from elsewhere on your $PATH\\n\"), title);\n+\t\t/* TRANSLATIONS:\n+\t\t * this must align with\n+\t\t * \"%s available from elsewhere on your $PATH\\n\"\n+\t\t */\n+\t\tprintf(_(\"---------------------------------------\"));\n \t\tmput_char('-', strlen(title));\n \t\tputchar('\\n');\n \t\tpretty_print_string_list(other_cmds, longest);\n@@ -341,7 +346,7 @@ const char *help_unknown_cmd(const char *cmd)\n \t      sizeof(*main_cmds.names), levenshtein_compare);\n \n \tif (!main_cmds.cnt)\n-\t\tdie (\"Uh oh. Your system reports no Git commands at all.\");\n+\t\tdie(_(\"Uh oh. Your system reports no Git commands at all.\"));\n \n \t/* skip and count prefix matches */\n \tfor (n = 0; n < main_cmds.cnt && !main_cmds.names[n]->len; n++)\n@@ -362,23 +367,26 @@ const char *help_unknown_cmd(const char *cmd)\n \t\tconst char *assumed = main_cmds.names[0]->name;\n \t\tmain_cmds.names[0] = NULL;\n \t\tclean_cmdnames(&main_cmds);\n-\t\tfprintf(stderr, \"WARNING: You called a Git command named '%s', \"\n-\t\t\t\"which does not exist.\\n\"\n-\t\t\t\"Continuing under the assumption that you meant '%s'\\n\",\n+\t\tfprintf(stderr,\n+\t\t\t_(\"WARNING: You called a Git command named '%s', \"\n+\t\t\t  \"which does not exist.\\n\"\n+\t\t\t  \"Continuing under the assumption that you meant '%s'\\n\"),\n \t\t\tcmd, assumed);\n \t\tif (autocorrect > 0) {\n-\t\t\tfprintf(stderr, \"in %0.1f seconds automatically...\\n\",\n+\t\t\tfprintf(stderr, _(\"in %0.1f seconds automatically...\\n\"),\n \t\t\t\t(float)autocorrect/10.0);\n \t\t\tpoll(NULL, 0, autocorrect * 100);\n \t\t}\n \t\treturn assumed;\n \t}\n \n-\tfprintf(stderr, \"git: '%s' is not a git command. See 'git --help'.\\n\", cmd);\n+\tfprintf(stderr, _(\"git: '%s' is not a git command. See 'git --help'.\\n\"), cmd);\n \n \tif (SIMILAR_ENOUGH(best_similarity)) {\n-\t\tfprintf(stderr, \"\\nDid you mean %s?\\n\",\n-\t\t\tn < 2 ? \"this\": \"one of these\");\n+\t\tfprintf(stderr,\n+\t\t\tQ_(\"\\nDid you mean this?\\n\",\n+\t\t\t   \"\\nDid you mean one of these?\\n\",\n+\t\t\t   n));\n \n \t\tfor (i = 0; i < n; i++)\n \t\t\tfprintf(stderr, \"\\t%s\\n\", main_cmds.names[i]->name);\n@@ -389,6 +397,6 @@ const char *help_unknown_cmd(const char *cmd)\n \n int cmd_version(int argc, const char **argv, const char *prefix)\n {\n-\tprintf(\"git version %s\\n\", git_version_string);\n+\tprintf(_(\"git version %s\\n\"), git_version_string);\n \treturn 0;\n }\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"186502","messageId":"20120308220131.GA10122@burratino","threadId":"29880","inReplyTo":"1331198198-22409-2-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 1/5] i18n: keep the last \\n even when text is poisoned","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-03-08T22:01:32Z","receivedAt":"2012-03-08T22:01:32Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Nguyễn Thái Ngọc Duy wrote:\n\n> --- a/gettext.c\n> +++ b/gettext.c\n> @@ -24,6 +24,16 @@ int use_gettext_poison(void)\n>  \t\tpoison_requested = getenv(\"GIT_GETTEXT_POISON\") ? 1 : 0;\n>  \treturn poison_requested;\n>  }\n> +\n> +const char *poison_text(const char *msgid)\n> +{\n> +\tint len = strlen(msgid);\n> +\tif (len && msgid[len-1] == '\\n')\n> +\t\treturn \"# GETTEXT POISON #\\n\";\n> +\telse\n> +\t\treturn \"# GETTEXT POISON #\";\n\nI realize this was not the motivation behind the above patch, but if\nthe translation of some message has to end with a newline for git to\nfunction correctly, would we consider that a bug?\n\nI am of two minds on that:\n\n - on one hand, translators tend to be trustworthy, reasonable folks\n - on the other hand, anything we can do to make the translation\n   process less fussy seems like time well spent\n\nThe latter wins out for me, so I would prefer not to have this patch\nso the test suite can detect important newlines that should be not be\npart of the translatable string.\n\nJust my two cents,\nJonathan\n"},{"id":"186504","messageId":"20120308220724.GA10270@burratino","threadId":"29880","inReplyTo":"1331198198-22409-4-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 3/5] i18n: parseopt: lookup help and argument translations when showing usage","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-03-08T22:07:24Z","receivedAt":"2012-03-08T22:07:24Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Nguyễn Thái Ngọc Duy wrote:\n\n> --- a/parse-options.c\n> +++ b/parse-options.c\n> @@ -490,7 +490,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)\n>  \t\t\ts = literal ? \"[%s]\" : \"[<%s>]\";\n>  \telse\n>  \t\ts = literal ? \" %s\" : \" <%s>\";\n> -\treturn fprintf(outfile, s, opts->argh ? opts->argh : \"...\");\n> +\treturn fprintf(outfile, s, opts->argh ? gettext(opts->argh) : \"...\");\n\nIt is ok to use _() with an argument that is not a string literal, and\nin fact it is needed if the argument is to be poisoned.\n\nMaybe it would be worth resending the patch to make the gettext()\nidentifier not available in NO_GETTEXT builds, so this kind of thing\nis easier to catch.\n\nThanks and hope that helps,\nJonathan\n"},{"id":"186514","messageId":"CACsJy8AcUy9FZiiehGc7mEL4i+XP6u0pmH1rGoR-WZnhYT1UMQ@mail.gmail.com","threadId":"29880","inReplyTo":"20120308220131.GA10122@burratino","subject":"Re: [PATCH 1/5] i18n: keep the last \\n even when text is poisoned","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-09T01:11:39Z","receivedAt":"2012-03-09T01:11:39Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"2012/3/9 Jonathan Nieder <jrnieder@gmail.com>:\n> I realize this was not the motivation behind the above patch, but if\n> the translation of some message has to end with a newline for git to\n> function correctly, would we consider that a bug?\n>\n> I am of two minds on that:\n>\n>  - on one hand, translators tend to be trustworthy, reasonable folks\n\nWe trust msgfmt and it does check \\n in msgstr if there is in msgid.\n\n>  - on the other hand, anything we can do to make the translation\n>   process less fussy seems like time well spent\n\nThat's at the cost of more code to split \\n out of the strings, and\nthere are 113 of them (some not published yet).\n\n> The latter wins out for me, so I would prefer not to have this patch\n> so the test suite can detect important newlines that should be not be\n> part of the translatable string.\n\nIt's the former for me, but that's probably because I'm also a\ntranslator :) Anyway for tricky cases where msgfmt fails to detect,\nthere's the role of l10n coordinators, who should review translation\nand catch these errors.\n\n> Just my two cents,\n> Jonathan\n-- \nDuy\n"},{"id":"186516","messageId":"CACsJy8BZi6NG+pixq45dbTMMEXkvy8soma95Zx+UWemxnX2jqw@mail.gmail.com","threadId":"29880","inReplyTo":"CACsJy8AcUy9FZiiehGc7mEL4i+XP6u0pmH1rGoR-WZnhYT1UMQ@mail.gmail.com","subject":"Re: [PATCH 1/5] i18n: keep the last \\n even when text is poisoned","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-09T02:33:23Z","receivedAt":"2012-03-09T02:33:23Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Mar 9, 2012 at 8:11 AM, Nguyen Thai Ngoc Duy <pclouds@gmail.com> wrote:\n> It's the former for me, but that's probably because I'm also a\n> translator :) Anyway for tricky cases where msgfmt fails to detect,\n> there's the role of l10n coordinators, who should review translation\n> and catch these errors.\n\nOn second thought, most of call sites are [fs]printf or strbuf_addf,\nand we have to update the call sites anyway to mark for translation.\nIf we introduce [fs]printf_ln, changes should be minimum. So now I'm\non your side too.\n-- \nDuy\n"},{"id":"186554","messageId":"CACBZZX4L+PiFUfre=_Qn+dgZAAanu6ebwjJFZUStu4_Ne7YwFg@mail.gmail.com","threadId":"29880","inReplyTo":"20120308220131.GA10122@burratino","subject":"Re: [PATCH 1/5] i18n: keep the last \\n even when text is poisoned","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2012-03-09T11:24:36Z","receivedAt":"2012-03-09T11:24:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"2012/3/8 Jonathan Nieder <jrnieder@gmail.com>:\n> The latter wins out for me, so I would prefer not to have this patch\n> so the test suite can detect important newlines that should be not be\n> part of the translatable string.\n\nFWIW that's why I kept it this way. I can't recall the specific cases\nbut I smoked out some issues with the poison feature that I wouldn't\nhave smoked out if it had a \\n at the end.\n\nArguably we should make it even less user friendly and have it\nrandomly output 10-50 bytes of binary garbage to catch more errors.\n"},{"id":"187052","messageId":"20120315185102.GA4636@burratino","threadId":"29880","inReplyTo":"1331198198-22409-3-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH 2/5] i18n: mark relative dates for translation","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-03-15T18:51:02Z","receivedAt":"2012-03-15T18:51:02Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Hi,\n\nNguyễn Thái Ngọc Duy wrote:\n\n> English dates get correct plural/singular form as a side effect.\n[...]\n> +++ b/date.c\n> @@ -93,38 +93,46 @@ const char *show_date_relative(unsigned long time, int tz,\n[...]\n>  \tif (diff < 90) {\n> -\t\tsnprintf(timebuf, timebuf_size, \"%lu seconds ago\", diff);\n> +\t\tsnprintf(timebuf, timebuf_size,\n> +\t\t\t Q_(\"%lu second ago\", \"%lu seconds ago\", diff), diff);\n>  \t\treturn timebuf;\n\nThis leaves me vaguely nervous --- sure, no language is going to use\nan expression for \"<n> years\" that is more than 200 bytes long, but if\none does, it would get truncated.\n\nWould something like the following (untested) on top make sense?\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nMy other worry is that\n\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\tstrbuf_addf(&sb, Q_(\"%lu year\", \"%lu years\", years), years);\n> +\t/* TRANSLATORS: \"%s\" is \"<n> years\" */\n> +\tsnprintf(timebuf, timebuf_size,\n> +\t         Q_(\"%s, %lu month ago\", \"%s, %lu months ago\", months),\n> +\t         sb.buf, months);\n> +\tstrbuf_release(&sb);\n\nseems excessively complicated.  How do translations normally deal with\ncases like this of strings with multiple numbers in them?\n\n cache.h     |    5 ++--\n date.c      |   73 +++++++++++++++++++++++++++++++----------------------------\n test-date.c |    7 +++---\n 3 files changed, 45 insertions(+), 40 deletions(-)\n\ndiff --git i/cache.h w/cache.h\nindex e5e1aa4e..ca704bdd 100644\n--- i/cache.h\n+++ w/cache.h\n@@ -906,10 +906,9 @@ enum date_mode {\n };\n \n const char *show_date(unsigned long time, int timezone, enum date_mode mode);\n-const char *show_date_relative(unsigned long time, int tz,\n+void show_date_relative(unsigned long time, int tz,\n \t\t\t       const struct timeval *now,\n-\t\t\t       char *timebuf,\n-\t\t\t       size_t timebuf_size);\n+\t\t\t       struct strbuf *timebuf);\n int parse_date(const char *date, char *buf, int bufsize);\n int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);\n void datestamp(char *buf, int bufsize);\ndiff --git i/date.c w/date.c\nindex 063f3449..dfe906dd 100644\n--- i/date.c\n+++ w/date.c\n@@ -86,54 +86,55 @@ static int local_tzoffset(unsigned long time)\n \treturn offset * eastwest;\n }\n \n-const char *show_date_relative(unsigned long time, int tz,\n+void show_date_relative(unsigned long time, int tz,\n \t\t\t       const struct timeval *now,\n-\t\t\t       char *timebuf,\n-\t\t\t       size_t timebuf_size)\n+\t\t\t       struct strbuf *timebuf)\n {\n \tunsigned long diff;\n-\tif (now->tv_sec < time)\n-\t\treturn _(\"in the future\");\n+\tif (now->tv_sec < time) {\n+\t\tstrbuf_addstr(timebuf, _(\"in the future\"));\n+\t\treturn;\n+\t}\n \tdiff = now->tv_sec - time;\n \tif (diff < 90) {\n-\t\tsnprintf(timebuf, timebuf_size,\n+\t\tstrbuf_addf(timebuf,\n \t\t\t Q_(\"%lu second ago\", \"%lu seconds ago\", diff), diff);\n-\t\treturn timebuf;\n+\t\treturn;\n \t}\n \t/* Turn it into minutes */\n \tdiff = (diff + 30) / 60;\n \tif (diff < 90) {\n-\t\tsnprintf(timebuf, timebuf_size,\n+\t\tstrbuf_addf(timebuf,\n \t\t\t Q_(\"%lu minute ago\", \"%lu minutes ago\", diff), diff);\n-\t\treturn timebuf;\n+\t\treturn;\n \t}\n \t/* Turn it into hours */\n \tdiff = (diff + 30) / 60;\n \tif (diff < 36) {\n-\t\tsnprintf(timebuf, timebuf_size,\n+\t\tstrbuf_addf(timebuf,\n \t\t\t Q_(\"%lu hour ago\", \"%lu hours ago\", diff), diff);\n-\t\treturn timebuf;\n+\t\treturn;\n \t}\n \t/* We deal with number of days from here on */\n \tdiff = (diff + 12) / 24;\n \tif (diff < 14) {\n-\t\tsnprintf(timebuf, timebuf_size,\n+\t\tstrbuf_addf(timebuf,\n \t\t\t Q_(\"%lu day ago\", \"%lu days ago\", diff), diff);\n-\t\treturn timebuf;\n+\t\treturn;\n \t}\n \t/* Say weeks for the past 10 weeks or so */\n \tif (diff < 70) {\n-\t\tsnprintf(timebuf, timebuf_size,\n+\t\tstrbuf_addf(timebuf,\n \t\t\t Q_(\"%lu week ago\", \"%lu weeks ago\", (diff + 3) / 7),\n \t\t\t (diff + 3) / 7);\n-\t\treturn timebuf;\n+\t\treturn;\n \t}\n \t/* Say months for the past 12 months or so */\n \tif (diff < 365) {\n-\t\tsnprintf(timebuf, timebuf_size,\n+\t\tstrbuf_addf(timebuf,\n \t\t\t Q_(\"%lu month ago\", \"%lu months ago\", (diff + 15) / 30),\n \t\t\t (diff + 15) / 30);\n-\t\treturn timebuf;\n+\t\treturn;\n \t}\n \t/* Give years and months for 5 years or so */\n \tif (diff < 1825) {\n@@ -141,40 +142,42 @@ const char *show_date_relative(unsigned long time, int tz,\n \t\tunsigned long years = totalmonths / 12;\n \t\tunsigned long months = totalmonths % 12;\n \t\tif (months) {\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n \t\t\tstrbuf_addf(&sb, Q_(\"%lu year\", \"%lu years\", years), years);\n \t\t\t/* TRANSLATORS: \"%s\" is \"<n> years\" */\n-\t\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\tstrbuf_addf(timebuf,\n \t\t\t\t Q_(\"%s, %lu month ago\", \"%s, %lu months ago\", months),\n \t\t\t\t sb.buf, months);\n \t\t\tstrbuf_release(&sb);\n \t\t} else\n-\t\t\tsnprintf(timebuf, timebuf_size,\n+\t\t\tstrbuf_addf(timebuf,\n \t\t\t\t Q_(\"%lu year ago\", \"%lu years ago\", years), years);\n-\t\treturn timebuf;\n+\t\treturn;\n \t}\n \t/* Otherwise, just years. Centuries is probably overkill. */\n-\tsnprintf(timebuf, timebuf_size,\n+\tstrbuf_addf(timebuf,\n \t\t Q_(\"%lu year ago\", \"%lu years ago\", (diff + 183) / 365),\n \t\t (diff + 183) / 365);\n-\treturn timebuf;\n }\n \n const char *show_date(unsigned long time, int tz, enum date_mode mode)\n {\n \tstruct tm *tm;\n-\tstatic char timebuf[200];\n+\tstatic struct strbuf timebuf = STRBUF_INIT;\n \n \tif (mode == DATE_RAW) {\n-\t\tsnprintf(timebuf, sizeof(timebuf), \"%lu %+05d\", time, tz);\n-\t\treturn timebuf;\n+\t\tstrbuf_reset(&timebuf);\n+\t\tstrbuf_addf(&timebuf, \"%lu %+05d\", time, tz);\n+\t\treturn timebuf.buf;\n \t}\n \n \tif (mode == DATE_RELATIVE) {\n \t\tstruct timeval now;\n+\n+\t\tstrbuf_reset(&timebuf);\n \t\tgettimeofday(&now, NULL);\n-\t\treturn show_date_relative(time, tz, &now,\n-\t\t\t\t\t  timebuf, sizeof(timebuf));\n+\t\tshow_date_relative(time, tz, &now, &timebuf);\n+\t\treturn timebuf.buf;\n \t}\n \n \tif (mode == DATE_LOCAL)\n@@ -183,23 +186,25 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)\n \ttm = time_to_tm(time, tz);\n \tif (!tm)\n \t\treturn NULL;\n+\n+\tstrbuf_reset(&timebuf);\n \tif (mode == DATE_SHORT)\n-\t\tsprintf(timebuf, \"%04d-%02d-%02d\", tm->tm_year + 1900,\n+\t\tstrbuf_addf(&timebuf, \"%04d-%02d-%02d\", tm->tm_year + 1900,\n \t\t\t\ttm->tm_mon + 1, tm->tm_mday);\n \telse if (mode == DATE_ISO8601)\n-\t\tsprintf(timebuf, \"%04d-%02d-%02d %02d:%02d:%02d %+05d\",\n+\t\tstrbuf_addf(&timebuf, \"%04d-%02d-%02d %02d:%02d:%02d %+05d\",\n \t\t\t\ttm->tm_year + 1900,\n \t\t\t\ttm->tm_mon + 1,\n \t\t\t\ttm->tm_mday,\n \t\t\t\ttm->tm_hour, tm->tm_min, tm->tm_sec,\n \t\t\t\ttz);\n \telse if (mode == DATE_RFC2822)\n-\t\tsprintf(timebuf, \"%.3s, %d %.3s %d %02d:%02d:%02d %+05d\",\n+\t\tstrbuf_addf(&timebuf, \"%.3s, %d %.3s %d %02d:%02d:%02d %+05d\",\n \t\t\tweekday_names[tm->tm_wday], tm->tm_mday,\n \t\t\tmonth_names[tm->tm_mon], tm->tm_year + 1900,\n \t\t\ttm->tm_hour, tm->tm_min, tm->tm_sec, tz);\n \telse\n-\t\tsprintf(timebuf, \"%.3s %.3s %d %02d:%02d:%02d %d%c%+05d\",\n+\t\tstrbuf_addf(&timebuf, \"%.3s %.3s %d %02d:%02d:%02d %d%c%+05d\",\n \t\t\t\tweekday_names[tm->tm_wday],\n \t\t\t\tmonth_names[tm->tm_mon],\n \t\t\t\ttm->tm_mday,\n@@ -207,7 +212,7 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)\n \t\t\t\ttm->tm_year + 1900,\n \t\t\t\t(mode == DATE_LOCAL) ? 0 : ' ',\n \t\t\t\ttz);\n-\treturn timebuf;\n+\treturn timebuf.buf;\n }\n \n /*\ndiff --git i/test-date.c w/test-date.c\nindex 6bcd5b03..10afaabb 100644\n--- i/test-date.c\n+++ w/test-date.c\n@@ -7,13 +7,14 @@ static const char *usage_msg = \"\\n\"\n \n static void show_dates(char **argv, struct timeval *now)\n {\n-\tchar buf[128];\n+\tstruct strbuf buf = STRBUF_INIT;\n \n \tfor (; *argv; argv++) {\n \t\ttime_t t = atoi(*argv);\n-\t\tshow_date_relative(t, 0, now, buf, sizeof(buf));\n-\t\tprintf(\"%s -> %s\\n\", *argv, buf);\n+\t\tshow_date_relative(t, 0, now, &buf);\n+\t\tprintf(\"%s -> %s\\n\", *argv, buf.buf);\n \t}\n+\tstrbuf_release(&buf);\n }\n \n static void parse_dates(char **argv, struct timeval *now)\n"},{"id":"187053","messageId":"4F623FF8.1040507@kdbg.org","threadId":"29880","inReplyTo":"20120315185102.GA4636@burratino","subject":"Re: [PATCH 2/5] i18n: mark relative dates for translation","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2012-03-15T19:16:08Z","receivedAt":"2012-03-15T19:16:08Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 15.03.2012 19:51, schrieb Jonathan Nieder:\n> @@ -141,40 +142,42 @@ const char *show_date_relative(unsigned long time, int tz,\n>  \t\tunsigned long years = totalmonths / 12;\n>  \t\tunsigned long months = totalmonths % 12;\n>  \t\tif (months) {\n>  \t\t\tstruct strbuf sb = STRBUF_INIT;\n>  \t\t\tstrbuf_addf(&sb, Q_(\"%lu year\", \"%lu years\", years), years);\n>  \t\t\t/* TRANSLATORS: \"%s\" is \"<n> years\" */\n> -\t\t\tsnprintf(timebuf, timebuf_size,\n> +\t\t\tstrbuf_addf(timebuf,\n\nOT: You have 6 lines of context here, unlike all other hunks, which have\nonly 3 lines. Something weird or simple to explain?\n\n-- Hannes\n"},{"id":"187054","messageId":"20120315191846.GA23731@burratino","threadId":"29880","inReplyTo":"4F623FF8.1040507@kdbg.org","subject":"Re: [PATCH 2/5] i18n: mark relative dates for translation","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2012-03-15T19:18:46Z","receivedAt":"2012-03-15T19:18:46Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Johannes Sixt wrote:\n> Am 15.03.2012 19:51, schrieb Jonathan Nieder:\n\n>> @@ -141,40 +142,42 @@ const char *show_date_relative(unsigned long time, int tz,\n>>  \t\tunsigned long years = totalmonths / 12;\n>>  \t\tunsigned long months = totalmonths % 12;\n>>  \t\tif (months) {\n>>  \t\t\tstruct strbuf sb = STRBUF_INIT;\n>>  \t\t\tstrbuf_addf(&sb, Q_(\"%lu year\", \"%lu years\", years), years);\n>>  \t\t\t/* TRANSLATORS: \"%s\" is \"<n> years\" */\n>> -\t\t\tsnprintf(timebuf, timebuf_size,\n>> +\t\t\tstrbuf_addf(timebuf,\n>\n> OT: You have 6 lines of context here, unlike all other hunks, which have\n> only 3 lines. Something weird or simple to explain?\n\nJust manual patch mangling. ;-)\n"},{"id":"187083","messageId":"CACsJy8D0KN_M3txmq3J==3Q5=u2XAa0hR=Wm+=T3qYN+LAuWkQ@mail.gmail.com","threadId":"29880","inReplyTo":"20120315185102.GA4636@burratino","subject":"Re: [PATCH 2/5] i18n: mark relative dates for translation","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-03-16T11:47:06Z","receivedAt":"2012-03-16T11:47:06Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Mar 16, 2012 at 1:51 AM, Jonathan Nieder <jrnieder@gmail.com> wrote:\n> Hi,\n>\n> Nguyễn Thái Ngọc Duy wrote:\n>\n>> English dates get correct plural/singular form as a side effect.\n> [...]\n>> +++ b/date.c\n>> @@ -93,38 +93,46 @@ const char *show_date_relative(unsigned long time, int tz,\n> [...]\n>>       if (diff < 90) {\n>> -             snprintf(timebuf, timebuf_size, \"%lu seconds ago\", diff);\n>> +             snprintf(timebuf, timebuf_size,\n>> +                      Q_(\"%lu second ago\", \"%lu seconds ago\", diff), diff);\n>>               return timebuf;\n>\n> This leaves me vaguely nervous --- sure, no language is going to use\n> an expression for \"<n> years\" that is more than 200 bytes long, but if\n> one does, it would get truncated.\n>\n> Would something like the following (untested) on top make sense?\n\nIt does. I will reuse your patch next time.\n\n> My other worry is that\n>\n>> +     struct strbuf sb = STRBUF_INIT;\n>> +     strbuf_addf(&sb, Q_(\"%lu year\", \"%lu years\", years), years);\n>> +     /* TRANSLATORS: \"%s\" is \"<n> years\" */\n>> +     snprintf(timebuf, timebuf_size,\n>> +              Q_(\"%s, %lu month ago\", \"%s, %lu months ago\", months),\n>> +              sb.buf, months);\n>> +     strbuf_release(&sb);\n>\n> seems excessively complicated.  How do translations normally deal with\n> cases like this of strings with multiple numbers in them?\n\nI don't recall any similar cases. A search for 'ago\"' in all gnome\ntranslations I have only shows \"<one number> ago\", or \"%d blah ago,\n<absolute time>\". The closet is probably strftime, where translators\nare free to reorder date and time items, something like this\n\n/* Translators: the first %s is the number of months, the second the\nnumber of years */\nsprintf(.., \"%s, %s ago\", month_string, year_string);\n\nOr we can just round it up and show only one number.\n-- \nDuy\n"}]}