threads / patch / 42506

v3, 39 partsi18n: transport: mark strings for translation

Subject: [PATCH v3 04/39] i18n: transport: mark strings for translation

## tl;dr

50 messages between Jun 1, 2016 and Jun 3, 2016. Diffs are folded; open one to read it.

replies: 49people: 2as markdown or json

Vasco Almeida· Jun 1, 2016, 16:40 UTC · lore

[PATCH v3 00/39] i18n and test updates

Marks several messages for translation and updates tests to pass under GETTEXT_POISON. Some tests were updated to fit previous i18n marks, others were updated to fit marks made by these patches. Patches that only touch test file refer to marks done in commits previous to these ones.

This re-roll fixes issues comment on the previous discussion and adds patches to mark more strings for translation.

Previous discussion: http://thread.gmane.org/gmane.comp.version-control.git/295354

Interdiff v2 v3 included bellow.
Vasco Almeida (39):
  i18n: builtin/remote.c: fix mark for translation
  i18n: advice: mark string about detached head for translation
  i18n: advice: internationalize message for conflicts
  i18n: transport: mark strings for translation
  i18n: sequencer: mark entire sentences for translation
  i18n: sequencer: mark string for translation
  i18n: merge-octopus: mark messages for translation
  merge-octupus: use die shell function from git-sh-setup.sh
  i18n: rebase: fix marked string to use eval_gettext variant
  i18n: rebase: mark placeholder for translation
  i18n: bisect: simplify error message for i18n
  t6030: update to use test_i18ncmp
  i18n: git-sh-setup.sh: mark strings for translation
  i18n: rebase-interactive: mark strings for translation
  i18n: rebase-interactive: mark here-doc strings for translation
  i18n: rebase-interactive: mark comments of squash for translation
  i18n: setup: mark strings for translation
  tests: use test_i18n* functions to suppress false positives
  tests: unpack-trees: update to use test_i18n* functions
  t9003: become resilient to GETTEXT_POISON
  t4153: fix negated test_i18ngrep call
  t5523: use test_i18ngrep for negation
  i18n: bisect: mark strings for translation
  i18n: bisect: enable l10n of bisect terms in messages
  i18n: transport-helper.c: change N_() call to _()
  i18n: notes: mark strings for translation
  i18n: notes: mark options for translation
  i18n: config: unfold error messages marked for translation
  i18n: merge: mark messages for translation
  i18n: merge: change command option help to lowercase
  i18n: sequencer: add period to error message
  i18n: standardise messages
  i18n: remote: mark URL fallback text for translation
  i18n: remote: allow translations to reorder message
  i18n: init-db: join message pieces
  i18n: submodule: join strings marked for translation
  i18n: submodule: escape shell variables inside eval_gettext
  i18n: unmark die messages for translation
  i18n: branch: mark comment when editing branch description for
    translation
 Makefile                              |   5 +-
 advice.c                              |  23 ++-
 bisect.c                              |  83 ++++++----
 builtin/apply.c                       |   6 +-
 builtin/branch.c                      |   6 +-
 builtin/checkout.c                    |   6 +-
 builtin/init-db.c                     |  17 +-
 builtin/merge.c                       |  12 +-
 builtin/notes.c                       |  24 +--
 builtin/pull.c                        |   2 +-
 builtin/remote.c                      |  19 ++-
 builtin/repack.c                      |   2 +-
 cache.h                               |   9 +-
 config.c                              | 102 ++++++++++--
 git-bisect.sh                         |   7 +-
 git-merge-octopus.sh                  |  24 ++-
 git-rebase--interactive.sh            | 290 ++++++++++++++++++++--------------
 git-rebase.sh                         |   4 +-
 git-sh-i18n.sh                        |  18 +++
 git-sh-setup.sh                       |  62 ++++++--
 git-submodule.sh                      |  22 +--
 sequencer.c                           |  15 +-
 setup.c                               |  16 +-
 submodule-config.c                    |   2 +-
 t/lib-rebase.sh                       |   1 +
 t/t0008-ignores.sh                    |   4 +-
 t/t1011-read-tree-sparse-checkout.sh  |   2 +-
 t/t1300-repo-config.sh                |   8 +-
 t/t1307-config-blob.sh                |   5 +-
 t/t1308-config-set.sh                 |   4 +-
 t/t1400-update-ref.sh                 |   2 +-
 t/t1506-rev-parse-diagnosis.sh        |   2 +-
 t/t2010-checkout-ambiguous.sh         |   2 +-
 t/t2018-checkout-branch.sh            |   2 +-
 t/t3200-branch.sh                     |   6 +-
 t/t3201-branch-contains.sh            |   2 +-
 t/t3310-notes-merge-manual-resolve.sh |   8 +-
 t/t3320-notes-merge-worktrees.sh      |   4 +-
 t/t3400-rebase.sh                     |   4 +-
 t/t3404-rebase-interactive.sh         |  18 +--
 t/t4153-am-resume-override-opts.sh    |   2 +-
 t/t4208-log-magic-pathspec.sh         |   4 +-
 t/t5505-remote.sh                     |   2 +-
 t/t5510-fetch.sh                      |   2 +-
 t/t5520-pull.sh                       |   2 +-
 t/t5523-push-upstream.sh              |  12 +-
 t/t5536-fetch-conflicts.sh            |   4 +-
 t/t6030-bisect-porcelain.sh           |  26 +--
 t/t6301-for-each-ref-errors.sh        |  10 +-
 t/t7063-status-untracked-cache.sh     |   2 +-
 t/t7102-reset.sh                      |   4 +-
 t/t7201-co.sh                         |   2 +-
 t/t7400-submodule-basic.sh            |   2 +-
 t/t7403-submodule-sync.sh             |   4 +-
 t/t7406-submodule-update.sh           |  10 +-
 t/t7508-status.sh                     |   4 +-
 t/t7607-merge-overwrite.sh            |   2 +-
 t/t9003-help-autocorrect.sh           |   4 +-
 transport-helper.c                    |   2 +-
 transport.c                           |  20 +--
 wt-status.c                           |   4 +-
 61 files changed, 596 insertions(+), 378 deletions(-)
---- 8< ----
Show changes to 26 files +254 −142

advice.c, bisect.c, builtin/apply.c, builtin/branch.c, builtin/checkout.c, builtin/init-db.c, builtin/merge.c, builtin/notes.c, builtin/remote.c, builtin/repack.c, cache.h, config.c, git-bisect.sh, git-rebase--interactive.sh, git-sh-i18n.sh, git-sh-setup.sh, git-submodule.sh, sequencer.c, submodule-config.c, t/t1307-config-blob.sh, t/t3310-notes-merge-manual-resolve.sh, t/t3320-notes-merge-worktrees.sh, t/t6030-bisect-porcelain.sh, t/t7201-co.sh, transport-helper.c, wt-status.c

diff --git a/advice.c b/advice.c
index 4612165..b84ae49 100644
--- a/advice.c
+++ b/advice.c
@@ -119,8 +119,8 @@ void NORETURN die_conclude_merge(void)
 
 void detach_advice(const char *new_name)
 {
-	const char fmt[] =
-	N_("Note: checking out '%s'.\n\n"
+	const char *fmt =
+	_("Note: checking out '%s'.\n\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
 	"state without impacting any branches by performing another checkout.\n\n"
diff --git a/bisect.c b/bisect.c
index 6d93edb..293d7ec 100644
--- a/bisect.c
+++ b/bisect.c
@@ -23,6 +23,15 @@ static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 static const char *term_bad;
 static const char *term_good;
 
+enum term { BAD, GOOD, OLD, NEW };
+static const char *term_names[] = {
+/* TRANSLATORS: in bisect.c source code file, the following terms are
+   used to describe a "bad commit", "good commit", "new revision", etc.
+   Please, if you can, check the source when you are not sure if a %s
+   would be replaced by one of the following terms. */
+	N_("bad"), N_("good"), N_("old"), N_("new"),  NULL
+};
+
 /* Remember to update object flag allocation in object.h */
 #define COUNTED		(1u<<16)
 
@@ -438,12 +447,12 @@ static void read_bisect_paths(struct argv_array *array)
 	FILE *fp = fopen(filename, "r");
 
 	if (!fp)
-		die_errno("Could not open file '%s'", filename);
+		die_errno(_("Could not open file '%s'"), filename);
 
 	while (strbuf_getline_lf(&str, fp) != EOF) {
 		strbuf_trim(&str);
 		if (sq_dequote_to_argv_array(str.buf, array))
-			die("Badly quoted content in file '%s': %s",
+			die(_("Badly quoted content in file '%s': %s"),
 			    filename, str.buf);
 	}
 
@@ -649,7 +658,7 @@ static void exit_if_skipped_commits(struct commit_list *tried,
 	print_commit_list(tried, "%s\n", "%s\n");
 	if (bad)
 		printf("%s\n", oid_to_hex(bad));
-	printf("We cannot bisect more!\n");
+	printf(_("We cannot bisect more!\n"));
 	exit(2);
 }
 
@@ -702,7 +711,7 @@ static struct commit *get_commit_reference(const unsigned char *sha1)
 {
 	struct commit *r = lookup_commit_reference(sha1);
 	if (!r)
-		die("Not a valid commit name %s", sha1_to_hex(sha1));
+		die(_("Not a valid commit name %s"), sha1_to_hex(sha1));
 	return r;
 }
 
@@ -725,29 +734,29 @@ static void handle_bad_merge_base(void)
 	if (is_expected_rev(current_bad_oid)) {
 		char *bad_hex = oid_to_hex(current_bad_oid);
 		char *good_hex = join_sha1_array_hex(&good_revs, ' ');
-		if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
-			fprintf(stderr, "The merge base %s is bad.\n"
+		if (!strcmp(term_bad, term_names[BAD]) && !strcmp(term_good, term_names[GOOD])) {
+			fprintf(stderr, _("The merge base %s is bad.\n"
 				"This means the bug has been fixed "
-				"between %s and [%s].\n",
+				"between %s and [%s].\n"),
 				bad_hex, bad_hex, good_hex);
-		} else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
-			fprintf(stderr, "The merge base %s is new.\n"
+		} else if (!strcmp(term_bad, term_names[NEW]) && !strcmp(term_good, term_names[OLD])) {
+			fprintf(stderr, _("The merge base %s is new.\n"
 				"The property has changed "
-				"between %s and [%s].\n",
+				"between %s and [%s].\n"),
 				bad_hex, bad_hex, good_hex);
 		} else {
-			fprintf(stderr, "The merge base %s is %s.\n"
+			fprintf(stderr, _("The merge base %s is %s.\n"
 				"This means the first '%s' commit is "
-				"between %s and [%s].\n",
-				bad_hex, term_bad, term_good, bad_hex, good_hex);
+				"between %s and [%s].\n"),
+				bad_hex, _(term_bad), _(term_good), bad_hex, good_hex);
 		}
 		exit(3);
 	}
 
-	fprintf(stderr, "Some %s revs are not ancestor of the %s rev.\n"
+	fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n"
 		"git bisect cannot work properly in this case.\n"
-		"Maybe you mistook %s and %s revs?\n",
-		term_good, term_bad, term_good, term_bad);
+		"Maybe you mistook %s and %s revs?\n"),
+		_(term_good), _(term_bad), _(term_good), _(term_bad));
 	exit(1);
 }
 
@@ -757,12 +766,12 @@ static void handle_skipped_merge_base(const unsigned char *mb)
 	char *bad_hex = sha1_to_hex(current_bad_oid->hash);
 	char *good_hex = join_sha1_array_hex(&good_revs, ' ');
 
-	warning("the merge base between %s and [%s] "
+	warning(_("the merge base between %s and [%s] "
 		"must be skipped.\n"
 		"So we cannot be sure the first %s commit is "
 		"between %s and %s.\n"
-		"We continue anyway.",
-		bad_hex, good_hex, term_bad, mb_hex, bad_hex);
+		"We continue anyway."),
+		bad_hex, good_hex, _(term_bad), mb_hex, bad_hex);
 	free(good_hex);
 }
 
@@ -792,7 +801,7 @@ static void check_merge_bases(int no_checkout)
 		} else if (0 <= sha1_array_lookup(&skipped_revs, mb)) {
 			handle_skipped_merge_base(mb);
 		} else {
-			printf("Bisecting: a merge base must be tested\n");
+			printf(_("Bisecting: a merge base must be tested\n"));
 			exit(bisect_checkout(mb, no_checkout));
 		}
 	}
@@ -843,7 +852,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
 	int fd;
 
 	if (!current_bad_oid)
-		die("a %s revision is needed", term_bad);
+		die(_("a %s revision is needed"), _(term_bad));
 
 	/* Check if file BISECT_ANCESTORS_OK exists. */
 	if (!stat(filename, &st) && S_ISREG(st.st_mode))
@@ -860,7 +869,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
 	/* Create file BISECT_ANCESTORS_OK. */
 	fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 	if (fd < 0)
-		warning_errno("could not create file '%s'",
+		warning_errno(_("could not create file '%s'"),
 			      filename);
 	else
 		close(fd);
@@ -906,11 +915,11 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
 
 	if (!fp) {
 		if (errno == ENOENT) {
-			*read_bad = "bad";
-			*read_good = "good";
+			*read_bad = term_names[BAD];
+			*read_good = term_names[GOOD];
 			return;
 		} else {
-			die_errno("could not read file '%s'", filename);
+			die_errno(_("could not read file '%s'"), filename);
 		}
 	} else {
 		strbuf_getline_lf(&str, fp);
@@ -936,10 +945,11 @@ int bisect_next_all(const char *prefix, int no_checkout)
 	struct commit_list *tried;
 	int reaches = 0, all = 0, nr, steps;
 	const unsigned char *bisect_rev;
+	char steps_msg[32];
 
 	read_bisect_terms(&term_bad, &term_good);
 	if (read_bisect_refs())
-		die("reading bisect refs failed");
+		die(_("reading bisect refs failed"));
 
 	check_good_are_ancestors_of_bad(prefix, no_checkout);
 
@@ -959,16 +969,16 @@ int bisect_next_all(const char *prefix, int no_checkout)
 		 */
 		exit_if_skipped_commits(tried, NULL);
 
-		printf("%s was both %s and %s\n",
+		printf(_("%s was both %s and %s\n"),
 		       oid_to_hex(current_bad_oid),
-		       term_good,
-		       term_bad);
+		       _(term_good),
+		       _(term_bad));
 		exit(1);
 	}
 
 	if (!all) {
-		fprintf(stderr, "No testable commit found.\n"
-			"Maybe you started with bad path parameters?\n");
+		fprintf(stderr, _("No testable commit found.\n"
+			"Maybe you started with bad path parameters?\n"));
 		exit(4);
 	}
 
@@ -985,9 +995,14 @@ int bisect_next_all(const char *prefix, int no_checkout)
 
 	nr = all - reaches - 1;
 	steps = estimate_bisect_steps(all);
-	printf("Bisecting: %d revision%s left to test after this "
-	       "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
-	       steps, (steps == 1 ? "" : "s"));
+	xsnprintf(steps_msg, sizeof(steps_msg),
+		  Q_("(roughly %d step)", "(roughly %d steps)", steps),
+		  steps);
+	/* TRANSLATORS: the last %s will be replaced with
+	   "(roughly %d steps)" translation */
+	printf(Q_("Bisecting: %d revision left to test after this %s\n",
+		  "Bisecting: %d revisions left to test after this %s\n",
+		  nr), nr, steps_msg);
 
 	return bisect_checkout(bisect_rev, no_checkout);
 }
diff --git a/builtin/apply.c b/builtin/apply.c
index 8e4da2e..205c9f8f3 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3226,7 +3226,7 @@ static int load_patch_target(struct strbuf *buf,
 {
 	if (cached || check_index) {
 		if (read_file_or_gitlink(ce, buf))
-			return error(_("read of %s failed"), name);
+			return error(_("failed to read %s"), name);
 	} else if (name) {
 		if (S_ISGITLINK(expected_mode)) {
 			if (ce)
@@ -3237,7 +3237,7 @@ static int load_patch_target(struct strbuf *buf,
 			return error(_("reading from '%s' beyond a symbolic link"), name);
 		} else {
 			if (read_old_data(st, name, buf))
-				return error(_("read of %s failed"), name);
+				return error(_("failed to read %s"), name);
 		}
 	}
 	return 0;
@@ -3282,7 +3282,7 @@ static int load_preimage(struct image *image,
 			free_fragment_list(patch->fragments);
 			patch->fragments = NULL;
 		} else if (status) {
-			return error(_("read of %s failed"), patch->old_name);
+			return error(_("failed to read %s"), patch->old_name);
 		}
 	}
 
diff --git a/builtin/branch.c b/builtin/branch.c
index 2ecde53..12203fd 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -614,9 +614,9 @@ static int edit_branch_description(const char *branch_name)
 	if (!buf.len || buf.buf[buf.len-1] != '\n')
 		strbuf_addch(&buf, '\n');
 	strbuf_commented_addf(&buf,
-		    "Please edit the description for the branch\n"
-		    "  %s\n"
-		    "Lines starting with '%c' will be stripped.\n",
+		    _("Please edit the description for the branch\n"
+		      "  %s\n"
+		      "Lines starting with '%c' will be stripped.\n"),
 		    branch_name, comment_line_char);
 	if (write_file_gently(git_path(edit_description), "%s", buf.buf)) {
 		strbuf_release(&buf);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3398c61..05c7b71 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -276,7 +276,7 @@ static int checkout_paths(const struct checkout_opts *opts,
 
 	hold_locked_index(lock_file, 1);
 	if (read_cache_preload(&opts->pathspec) < 0)
-		return error(_("corrupt index file"));
+		return error(_("index file corrupt"));
 
 	if (opts->source_tree)
 		read_tree_some(opts->source_tree, &opts->pathspec);
@@ -470,7 +470,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 
 	hold_locked_index(lock_file, 1);
 	if (read_cache_preload(NULL) < 0)
-		return error(_("corrupt index file"));
+		return error(_("index file corrupt"));
 
 	resolve_undo_clear();
 	if (opts->force) {
@@ -1138,7 +1138,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
 			   N_("create/reset and checkout a branch")),
 		OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
-		OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
+		OPT_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")),
 		OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/builtin/init-db.c b/builtin/init-db.c
index b2d8d40..3a45f0b 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -397,13 +397,16 @@ int init_db(const char *template_dir, unsigned int flags)
 	if (!(flags & INIT_DB_QUIET)) {
 		int len = strlen(git_dir);
 
-		/* TRANSLATORS: The first '%s' is either "Reinitialized
-		   existing" or "Initialized empty", the second " shared" or
-		   "", and the last '%s%s' is the verbatim directory name. */
-		printf(_("%s%s Git repository in %s%s\n"),
-		       reinit ? _("Reinitialized existing") : _("Initialized empty"),
-		       get_shared_repository() ? _(" shared") : "",
-		       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		if (reinit)
+			printf(get_shared_repository()
+			       ? _("Reinitialized existing shared Git repository in %s%s\n")
+			       : _("Reinitialized existing Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		else
+			printf(get_shared_repository()
+			       ? _("Initialized empty shared Git repository in %s%s\n")
+			       : _("Initialized empty Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
 	}
 
 	return 0;
diff --git a/builtin/merge.c b/builtin/merge.c
index b555a1b..d82f6c1 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -211,7 +211,7 @@ static struct option builtin_merge_options[] = {
 		PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },
 	OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
 	OPT_BOOL(0, "verify-signatures", &verify_signatures,
-		N_("Verify that the named commit has a valid GPG signature")),
+		N_("verify that the named commit has a valid GPG signature")),
 	OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"),
 		N_("merge strategy to use"), option_parse_strategy),
 	OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"),
@@ -1014,7 +1014,7 @@ static int default_edit_option(void)
 	if (e) {
 		int v = git_config_maybe_bool(name, e);
 		if (v < 0)
-			die("Bad value '%s' in environment '%s'", e, name);
+			die(_("Bad value '%s' in environment '%s'"), e, name);
 		return v;
 	}
 
@@ -1115,7 +1115,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
 		if (!commit) {
 			if (ptr)
 				*ptr = '\0';
-			die("not something we can merge in %s: %s",
+			die(_("not something we can merge in %s: %s"),
 			    filename, merge_names->buf + pos);
 		}
 		remotes = &commit_list_insert(commit, remotes)->next;
@@ -1149,7 +1149,7 @@ static struct commit_list *collect_parents(struct commit *head_commit,
 			struct commit *commit = get_merge_parent(argv[i]);
 			if (!commit)
 				help_unknown_ref(argv[i], "merge",
-						 "not something we can merge");
+						 _("not something we can merge"));
 			remotes = &commit_list_insert(commit, remotes)->next;
 		}
 		remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
@@ -1421,7 +1421,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		 * If head can reach all the merge then we are up to date.
 		 * but first the most common case of merging one remote.
 		 */
-		finish_up_to_date("Already up-to-date.");
+		finish_up_to_date(_("Already up-to-date."));
 		goto done;
 	} else if (fast_forward != FF_NO && !remoteheads->next &&
 			!common->next &&
@@ -1506,7 +1506,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			}
 		}
 		if (up_to_date) {
-			finish_up_to_date("Already up-to-date. Yeeah!");
+			finish_up_to_date(_("Already up-to-date. Yeeah!"));
 			goto done;
 		}
 	}
diff --git a/builtin/notes.c b/builtin/notes.c
index c65b59a..0572051 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -749,7 +749,7 @@ static int git_config_get_notes_strategy(const char *key,
 	if (git_config_get_string(key, &value))
 		return 1;
 	if (parse_notes_merge_strategy(value, strategy))
-		git_die_config(key, "unknown notes merge strategy %s", value);
+		git_die_config(key, _("unknown notes merge strategy %s"), value);
 
 	free(value);
 	return 0;
@@ -788,15 +788,15 @@ static int merge(int argc, const char **argv, const char *prefix)
 	if (strategy || do_commit + do_abort == 0)
 		do_merge = 1;
 	if (do_merge + do_commit + do_abort != 1) {
-		error("cannot mix --commit, --abort or -s/--strategy");
+		error(_("cannot mix --commit, --abort or -s/--strategy"));
 		usage_with_options(git_notes_merge_usage, options);
 	}
 
 	if (do_merge && argc != 1) {
-		error("Must specify a notes ref to merge");
+		error(_("Must specify a notes ref to merge"));
 		usage_with_options(git_notes_merge_usage, options);
 	} else if (!do_merge && argc) {
-		error("too many parameters");
+		error(_("too many parameters"));
 		usage_with_options(git_notes_merge_usage, options);
 	}
 
@@ -817,7 +817,7 @@ static int merge(int argc, const char **argv, const char *prefix)
 
 	if (strategy) {
 		if (parse_notes_merge_strategy(strategy, &o.strategy)) {
-			error("Unknown -s/--strategy: %s", strategy);
+			error(_("Unknown -s/--strategy: %s"), strategy);
 			usage_with_options(git_notes_merge_usage, options);
 		}
 	} else {
@@ -857,11 +857,11 @@ static int merge(int argc, const char **argv, const char *prefix)
 			die(_("A notes merge into %s is already in-progress at %s"),
 			    default_notes_ref(), wt->path);
 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
-			die("Failed to store link to current notes ref (%s)",
+			die(_("Failed to store link to current notes ref (%s)"),
 			    default_notes_ref());
-		printf("Automatic notes merge failed. Fix conflicts in %s and "
-		       "commit the result with 'git notes merge --commit', or "
-		       "abort the merge with 'git notes merge --abort'.\n",
+		printf(_("Automatic notes merge failed. Fix conflicts in %s and "
+			 "commit the result with 'git notes merge --commit', or "
+			 "abort the merge with 'git notes merge --abort'.\n"),
 		       git_path(NOTES_MERGE_WORKTREE));
 	}
 
@@ -934,8 +934,8 @@ static int prune(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	int show_only = 0, verbose = 0;
 	struct option options[] = {
-		OPT__DRY_RUN(&show_only, "do not remove, show only"),
-		OPT__VERBOSE(&verbose, "report pruned notes"),
+		OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
+		OPT__VERBOSE(&verbose, N_("report pruned notes")),
 		OPT_END()
 	};
 
@@ -964,7 +964,7 @@ static int get_ref(int argc, const char **argv, const char *prefix)
 			     git_notes_get_ref_usage, 0);
 
 	if (argc) {
-		error("too many parameters");
+		error(_("too many parameters"));
 		usage_with_options(git_notes_get_ref_usage, options);
 	}
 
diff --git a/builtin/remote.c b/builtin/remote.c
index ae74da6..7f0f78b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -952,7 +952,7 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 	struct show_info *show_info = cb_data;
 	struct branch_info *branch_info = item->util;
 	struct string_list *merge = &branch_info->merge;
-	const char *also;
+	int width = show_info->width + 4;
 	int i;
 
 	if (branch_info->rebase && branch_info->merge.nr > 1) {
@@ -969,13 +969,12 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 		return 0;
 	} else if (show_info->any_rebase) {
 		printf_ln(_(" merges with remote %s"), merge->items[0].string);
-		also = _("    and with remote");
+		width++;
 	} else {
 		printf_ln(_("merges with remote %s"), merge->items[0].string);
-		also = _("   and with remote");
 	}
 	for (i = 1; i < merge->nr; i++)
-		printf("    %-*s %s %s\n", show_info->width, "", also,
+		printf(_("%-*s    and with remote %s\n"), width, "",
 		       merge->items[i].string);
 
 	return 0;
@@ -1158,11 +1157,11 @@ static int show(int argc, const char **argv)
 			   the one in "  Fetch URL: %s" translation */
 			printf_ln(_("  Push  URL: %s"), url[i]);
 		if (!i)
-			printf_ln(_("  Push  URL: %s"), "(no URL)");
+			printf_ln(_("  Push  URL: %s"), _("(no URL)"));
 		if (no_query)
-			printf_ln(_("  HEAD branch: %s"), "(not queried)");
+			printf_ln(_("  HEAD branch: %s"), _("(not queried)"));
 		else if (!states.heads.nr)
-			printf_ln(_("  HEAD branch: %s"), "(unknown)");
+			printf_ln(_("  HEAD branch: %s"), _("(unknown)"));
 		else if (states.heads.nr == 1)
 			printf_ln(_("  HEAD branch: %s"), states.heads.items[0].string);
 		else {
diff --git a/builtin/repack.c b/builtin/repack.c
index 858db38..0108819 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -378,7 +378,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 					  item->string,
 					  exts[ext].name);
 			if (remove_path(fname))
-				warning(_("removing '%s' failed"), fname);
+				warning(_("failed to remove '%s'"), fname);
 			free(fname);
 		}
 	}
diff --git a/cache.h b/cache.h
index 6049f86..4bded9a 100644
--- a/cache.h
+++ b/cache.h
@@ -1559,10 +1559,17 @@ struct git_config_source {
 	const char *blob;
 };
 
+enum git_config_origin_type {
+	CFG_BLOB,
+	CFG_FILE,
+	CFG_STDIN,
+	CFG_SUBMODULE_BLOB
+};
+
 typedef int (*config_fn_t)(const char *, const char *, void *);
 extern int git_default_config(const char *, const char *, void *);
 extern int git_config_from_file(config_fn_t fn, const char *, void *);
-extern int git_config_from_mem(config_fn_t fn, const char *origin_type,
+extern int git_config_from_mem(config_fn_t fn, const enum git_config_origin_type,
 					const char *name, const char *buf, size_t len, void *data);
 extern void git_config_push_parameter(const char *text);
 extern int git_config_from_parameters(config_fn_t fn, void *data);
diff --git a/config.c b/config.c
index f51c56b..b08a418 100644
--- a/config.c
+++ b/config.c
@@ -24,7 +24,7 @@ struct config_source {
 			size_t pos;
 		} buf;
 	} u;
-	const char *origin_type;
+	enum git_config_origin_type origin_type;
 	const char *name;
 	const char *path;
 	int die_on_error;
@@ -417,6 +417,7 @@ static int git_parse_source(config_fn_t fn, void *data)
 	int comment = 0;
 	int baselen = 0;
 	struct strbuf *var = &cf->var;
+	char error_msg[128];
 
 	/* U+FEFF Byte Order Mark in UTF8 */
 	const char *bomptr = utf8_bom;
@@ -471,10 +472,38 @@ static int git_parse_source(config_fn_t fn, void *data)
 		if (get_value(fn, data, var) < 0)
 			break;
 	}
+
+	switch (cf->origin_type) {
+	case CFG_BLOB:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in blob %s"),
+			  cf->linenr, cf->name);
+		break;
+	case CFG_FILE:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in file %s"),
+			  cf->linenr, cf->name);
+		break;
+	case CFG_STDIN:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in standard input"),
+			  cf->linenr);
+		break;
+	case CFG_SUBMODULE_BLOB:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in submodule-blob %s"),
+			  cf->linenr, cf->name);
+		break;
+	default:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in %s"),
+			  cf->linenr, cf->name);
+	}
+
 	if (cf->die_on_error)
-		die(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
+		die(error_msg);
 	else
-		return error(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
+		return error(error_msg);
 }
 
 static int parse_unit_factor(const char *end, uintmax_t *val)
@@ -583,16 +612,42 @@ int git_parse_ulong(const char *value, unsigned long *ret)
 NORETURN
 static void die_bad_number(const char *name, const char *value)
 {
-	const char *reason = errno == ERANGE ?
-			     "out of range" :
-			     "invalid unit";
 	if (!value)
 		value = "";
 
-	if (cf && cf->origin_type && cf->name)
-		die(_("bad numeric config value '%s' for '%s' in %s %s: %s"),
-		    value, name, cf->origin_type, cf->name, reason);
-	die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason);
+	if (!(cf && cf->name))
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s': out of range")
+		    : _("bad numeric config value '%s' for '%s': invalid unit"),
+		    value, name);
+
+	switch (cf->origin_type) {
+	case CFG_BLOB:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in blob %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in blob %s: invalid unit"),
+		    value, name, cf->name);
+	case CFG_FILE:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in file %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in file %s: invalid unit"),
+		    value, name, cf->name);
+	case CFG_STDIN:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in standard input: out of range")
+		    : _("bad numeric config value '%s' for '%s' in standard input: invalid unit"),
+		    value, name);
+	case CFG_SUBMODULE_BLOB:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in submodule-blob %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in submodule-blob %s: invalid unit"),
+		    value, name, cf->name);
+	default:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in %s: invalid unit"),
+		    value, name, cf->name);
+	}
 }
 
 int git_config_int(const char *name, const char *value)
@@ -1069,7 +1124,8 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
 }
 
 static int do_config_from_file(config_fn_t fn,
-		const char *origin_type, const char *name, const char *path, FILE *f,
+		const enum git_config_origin_type origin_type,
+		const char *name, const char *path, FILE *f,
 		void *data)
 {
 	struct config_source top;
@@ -1088,7 +1144,7 @@ static int do_config_from_file(config_fn_t fn,
 
 static int git_config_from_stdin(config_fn_t fn, void *data)
 {
-	return do_config_from_file(fn, "standard input", "", NULL, stdin, data);
+	return do_config_from_file(fn, CFG_STDIN, "", NULL, stdin, data);
 }
 
 int git_config_from_file(config_fn_t fn, const char *filename, void *data)
@@ -1099,14 +1155,14 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 	f = fopen(filename, "r");
 	if (f) {
 		flockfile(f);
-		ret = do_config_from_file(fn, "file", filename, filename, f, data);
+		ret = do_config_from_file(fn, CFG_FILE, filename, filename, f, data);
 		funlockfile(f);
 		fclose(f);
 	}
 	return ret;
 }
 
-int git_config_from_mem(config_fn_t fn, const char *origin_type,
+int git_config_from_mem(config_fn_t fn, const enum git_config_origin_type origin_type,
 			const char *name, const char *buf, size_t len, void *data)
 {
 	struct config_source top;
@@ -1143,7 +1199,7 @@ static int git_config_from_blob_sha1(config_fn_t fn,
 		return error("reference '%s' does not point to a blob", name);
 	}
 
-	ret = git_config_from_mem(fn, "blob", name, buf, size, data);
+	ret = git_config_from_mem(fn, CFG_BLOB, name, buf, size, data);
 	free(buf);
 
 	return ret;
@@ -2442,7 +2498,21 @@ int parse_config_key(const char *var,
 
 const char *current_config_origin_type(void)
 {
-	return cf && cf->origin_type ? cf->origin_type : "command line";
+	if (!cf)
+		return "command line";
+
+	switch (cf->origin_type) {
+	case CFG_BLOB:
+		return "blob";
+	case CFG_FILE:
+		return "file";
+	case CFG_STDIN:
+		return "standard input";
+	case CFG_SUBMODULE_BLOB:
+		return "submodule-blob";
+	default:
+		return "command line";
+	}
 }
 
 const char *current_config_name(void)
diff --git a/git-bisect.sh b/git-bisect.sh
index 737bf05..c9acab6 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -275,7 +275,7 @@ bisect_state() {
 	check_and_set_terms $state
 	case "$#,$state" in
 	0,*)
-		die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
+		die "Please call 'bisect_state' with at least one argument." ;;
 	1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip)
 		bisected_head=$(bisect_head)
 		rev=$(git rev-parse --verify "$bisected_head") ||
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index caf4aa1..a545d92 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -222,11 +222,12 @@ has_action () {
 }
 
 is_empty_commit() {
-	sha1=$1
-	tree=$(git rev-parse -q --verify "$sha1"^{tree} 2>/dev/null ||
-		die "$(eval_gettext "\$sha1: not a commit that can be picked")")
-	ptree=$(git rev-parse -q --verify "$sha1"^^{tree} 2>/dev/null ||
-		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+	tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || {
+		sha1=$1
+		die "$(eval_gettext "\$sha1: not a commit that can be picked")"
+	}
+	ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) ||
+		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
 	test "$tree" = "$ptree"
 }
 
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
index c14c303..1ef1889 100644
--- a/git-sh-i18n.sh
+++ b/git-sh-i18n.sh
@@ -53,6 +53,13 @@ gettext_without_eval_gettext)
 			git sh-i18n--envsubst "$1"
 		)
 	}
+
+	eval_ngettext () {
+		ngettext "$1" "$2" "$3" | (
+			export PATH $(git sh-i18n--envsubst --variables "$2");
+			git sh-i18n--envsubst "$2"
+		)
+	}
 	;;
 poison)
 	# Emit garbage so that tests that incorrectly rely on translatable
@@ -80,6 +87,13 @@ poison)
 			git sh-i18n--envsubst "$1"
 		)
 	}
+
+	eval_ngettext () {
+		(test "$3" = 1 && printf "%s" "$1" || printf "%s" "$2") | (
+			export PATH $(git sh-i18n--envsubst --variables "$2");
+			git sh-i18n--envsubst "$2"
+		)
+	}
 	;;
 esac
 
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index 55efa23..61a3d95 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -192,32 +192,35 @@ cd_to_toplevel () {
 }
 
 require_work_tree_exists () {
-	program_name=$0
 	if test "z$(git rev-parse --is-bare-repository)" != zfalse
 	then
+		program_name=$0
 		die "$(gettext "fatal: \$program_name cannot be used without a working tree.")"
 	fi
 }
 
 require_work_tree () {
-	program_name=$0
-	test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ||
+	test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || {
+		program_name=$0
 		die "$(gettext "fatal: \$program_name cannot be used without a working tree.")"
+	}
 }
 
 require_clean_work_tree () {
-	action=$1
-	hint=$2
 	git rev-parse --verify HEAD >/dev/null || exit 1
 	git update-index -q --ignore-submodules --refresh
 	err=0
 
 	if ! git diff-files --quiet --ignore-submodules
 	then
+		action=$1
 		case "$action" in
 		rebase)
 			gettextln "Cannot rebase: You have unstaged changes." >&2
 			;;
+		"rewrite branches")
+			gettextln "Cannot rewrite branches: You have unstaged changes." >&2
+			;;
 		"pull with rebase")
 			gettextln "Cannot pull with rebase: You have unstaged changes." >&2
 			;;
@@ -232,6 +235,7 @@ require_clean_work_tree () {
 	then
 		if test $err = 0
 		then
+			action=$1
 			case "$action" in
 			rebase)
 				gettextln "Cannot rebase: Your index contains uncommitted changes." >&2
@@ -251,7 +255,7 @@ require_clean_work_tree () {
 
 	if test $err = 1
 	then
-		test -n "$hint" && echo "$2" >&2
+		test -n "$2" && echo "$2" >&2
 		exit 1
 	fi
 }
diff --git a/git-submodule.sh b/git-submodule.sh
index 5a4dec0..75a7ebe 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -275,14 +275,15 @@ Use -f if you really want to add it." >&2
 		then
 			if test -z "$force"
 			then
-				echo >&2 "$(eval_gettext "A git directory for '\$sm_name' is found locally with remote(s):")"
+				eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):"
 				GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^,"  ", -e s,' (fetch)',, >&2
-				echo >&2 "$(eval_gettext "If you want to reuse this local git directory instead of cloning again from")"
-				echo >&2 "  $realrepo"
-				echo >&2 "$(eval_gettext "use the '--force' option. If the local git directory is not the correct repo")"
-				die "$(eval_gettext "or you are unsure what this means choose another name with the '--name' option.")"
+				die "$(eval_gettextln "\
+If you want to reuse this local git directory instead of cloning again from
+  \$realrepo
+use the '--force' option. If the local git directory is not the correct repo
+or you are unsure what this means choose another name with the '--name' option.")"
 			else
-				echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
+				eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
 			fi
 		fi
 		git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit
@@ -467,8 +468,9 @@ cmd_deinit()
 			# Protect submodules containing a .git directory
 			if test -d "$sm_path/.git"
 			then
-				echo >&2 "$(eval_gettext "Submodule work tree '\$displaypath' contains a .git directory")"
-				die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
+				die "$(eval_gettext "\
+Submodule work tree '\$displaypath' contains a .git directory
+(use 'rm -rf' if you really want to remove it including all of its history)")"
 			fi
 
 			if test -z "$force"
@@ -646,7 +648,7 @@ cmd_update()
 			remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote)
 			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
 				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")"
+			die "$(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
 		fi
 
 		if test "$subsha1" != "$sha1" || test -n "$force"
@@ -670,7 +672,7 @@ cmd_update()
 				# not be reachable from any of the refs
 				is_tip_reachable "$sm_path" "$sha1" ||
 				fetch_in_submodule "$sm_path" "$sha1" ||
-				die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain $sha1. Direct fetching of that commit failed.")"
+				die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
 			fi
 
 			must_die_on_failure=
diff --git a/sequencer.c b/sequencer.c
index 57b3671..e916387 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -190,7 +190,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
 		die_errno(_("Could not write to %s"), filename);
 	strbuf_release(msgbuf);
 	if (commit_lock_file(&msg_file) < 0)
-		die(_("Error wrapping up %s"), filename);
+		die(_("Error wrapping up %s."), filename);
 }
 
 static struct tree *empty_tree(void)
diff --git a/submodule-config.c b/submodule-config.c
index debab29..f53f6bd 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -439,7 +439,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,
 	parameter.commit_sha1 = commit_sha1;
 	parameter.gitmodules_sha1 = sha1;
 	parameter.overwrite = 0;
-	git_config_from_mem(parse_config, "submodule-blob", rev.buf,
+	git_config_from_mem(parse_config, CFG_SUBMODULE_BLOB, rev.buf,
 			config, config_size, &parameter);
 	free(config);
 
diff --git a/t/t1307-config-blob.sh b/t/t1307-config-blob.sh
index 4079fef..eed31ff 100755
--- a/t/t1307-config-blob.sh
+++ b/t/t1307-config-blob.sh
@@ -61,9 +61,6 @@ test_expect_success 'parse errors in blobs are properly attributed' '
 	git commit -m broken &&
 
 	test_must_fail git config --blob=HEAD:config some.value 2>err &&
-
-	# just grep for our token as the exact error message is likely to
-	# change or be internationalized
 	test_i18ngrep "HEAD:config" err
 '
 
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index d557212..6967436 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -178,7 +178,7 @@ test_expect_success 'merge z into m (== y) with default ("manual") resolver => C
 	git config core.notesRef refs/notes/m &&
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
@@ -381,7 +381,7 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
 	git config core.notesRef refs/notes/m &&
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
@@ -415,7 +415,7 @@ git rev-parse refs/notes/z > pre_merge_z
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
@@ -496,7 +496,7 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
 	git update-ref refs/notes/m refs/notes/y &&
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh
index 522157b..6e05115 100755
--- a/t/t3320-notes-merge-worktrees.sh
+++ b/t/t3320-notes-merge-worktrees.sh
@@ -62,7 +62,7 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' '
 		cd worktree2 &&
 		git config core.notesRef refs/notes/x &&
 		test_must_fail git notes merge z 2>&1 >out &&
-		grep "Automatic notes merge failed" out &&
+		test_i18ngrep "Automatic notes merge failed" out &&
 		grep -v "A notes merge into refs/notes/x is already in-progress in" out
 	) &&
 	echo "ref: refs/notes/x" >expect &&
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 7012011..86d1380 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -362,7 +362,7 @@ test_expect_success 'bisect starting with a detached HEAD' '
 test_expect_success 'bisect errors out if bad and good are mistaken' '
 	git bisect reset &&
 	test_must_fail git bisect start $HASH2 $HASH4 2> rev_list_error &&
-	grep "mistook good and bad" rev_list_error &&
+	test_i18ngrep "mistook good and bad" rev_list_error &&
 	git bisect reset
 '
 
@@ -404,7 +404,7 @@ test_expect_success 'side branch creation' '
 
 test_expect_success 'good merge base when good and bad are siblings' '
 	git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt &&
 	git bisect good > my_bisect_log.txt &&
 	test_must_fail grep "merge base must be tested" my_bisect_log.txt &&
@@ -413,7 +413,7 @@ test_expect_success 'good merge base when good and bad are siblings' '
 '
 test_expect_success 'skipped merge base when good and bad are siblings' '
 	git bisect start "$SIDE_HASH7" "$HASH7" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt &&
 	git bisect skip > my_bisect_log.txt 2>&1 &&
 	grep "warning" my_bisect_log.txt &&
@@ -423,11 +423,11 @@ test_expect_success 'skipped merge base when good and bad are siblings' '
 
 test_expect_success 'bad merge base when good and bad are siblings' '
 	git bisect start "$HASH7" HEAD > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt &&
 	test_must_fail git bisect bad > my_bisect_log.txt 2>&1 &&
-	grep "merge base $HASH4 is bad" my_bisect_log.txt &&
-	grep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt &&
+	test_i18ngrep "merge base $HASH4 is bad" my_bisect_log.txt &&
+	test_i18ngrep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt &&
 	git bisect reset
 '
 
@@ -460,9 +460,9 @@ test_expect_success 'many merge bases creation' '
 
 test_expect_success 'good merge bases when good and bad are siblings' '
 	git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	git bisect good > my_bisect_log2.txt &&
-	grep "merge base must be tested" my_bisect_log2.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log2.txt &&
 	{
 		{
 			grep "$SIDE_HASH5" my_bisect_log.txt &&
@@ -477,14 +477,14 @@ test_expect_success 'good merge bases when good and bad are siblings' '
 
 test_expect_success 'optimized merge base checks' '
 	git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep "$HASH4" my_bisect_log.txt &&
 	git bisect good > my_bisect_log2.txt &&
 	test -f ".git/BISECT_ANCESTORS_OK" &&
 	test "$HASH6" = $(git rev-parse --verify HEAD) &&
 	git bisect bad > my_bisect_log3.txt &&
 	git bisect good "$A_HASH" > my_bisect_log4.txt &&
-	grep "merge base must be tested" my_bisect_log4.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log4.txt &&
 	test_must_fail test -f ".git/BISECT_ANCESTORS_OK"
 '
 
@@ -562,7 +562,7 @@ test_expect_success 'skipping away from skipped commit' '
 
 test_expect_success 'erroring out when using bad path parameters' '
 	test_must_fail git bisect start $PARA_HASH7 $HASH1 -- foobar 2> error.txt &&
-	grep "bad path parameters" error.txt
+	test_i18ngrep "bad path parameters" error.txt
 '
 
 test_expect_success 'test bisection on bare repo - --no-checkout specified' '
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 8859236..d4b217b 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -257,7 +257,7 @@ test_expect_success 'checkout to detach HEAD' '
 	git checkout -f renamer && git clean -f &&
 	git checkout renamer^ 2>messages &&
 	test_i18ngrep "HEAD is now at 7329388" messages &&
-	test_line_count -gt 1 messages &&
+	(test_line_count -gt 1 messages || test -n "$GETTEXT_POISON") &&
 	H=$(git rev-parse --verify HEAD) &&
 	M=$(git show-ref -s --verify refs/heads/master) &&
 	test "z$H" = "z$M" &&
diff --git a/transport-helper.c b/transport-helper.c
index bd666b2..4208743 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -1038,7 +1038,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
 				(*tail)->status |= REF_STATUS_UPTODATE;
 				if (read_ref((*tail)->name,
 					     (*tail)->old_oid.hash) < 0)
-					die(N_("Could not read ref %s"),
+					die(_("Could not read ref %s"),
 					    (*tail)->name);
 			}
 		}
diff --git a/wt-status.c b/wt-status.c
index 4f27bd6..5a91279 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die(_("bug: unhandled unmerged status %x"), stagemask);
+		die("bug: unhandled unmerged status %x", stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die(_("bug: unhandled diff status %c"), status);
+		die("bug: unhandled diff status %c", status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
---- 8< ----
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 01/39] i18n: builtin/remote.c: fix mark for translation

The second string inside _() was not being extracted for translation by xgettext, meaning that, although the string was passed to gettext, there was no translation available.

Mark each individual string instead of marking the result of ternary if.
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/remote.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/remote.c +3 −3
diff --git a/builtin/remote.c b/builtin/remote.c
index d33766b..ae74da6 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -963,9 +963,9 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 
 	printf("    %-*s ", show_info->width, item->string);
 	if (branch_info->rebase) {
-		printf_ln(_(branch_info->rebase == INTERACTIVE_REBASE ?
-			"rebases interactively onto remote %s" :
-			"rebases onto remote %s"), merge->items[0].string);
+		printf_ln(branch_info->rebase == INTERACTIVE_REBASE
+			  ? _("rebases interactively onto remote %s")
+			  : _("rebases onto remote %s"), merge->items[0].string);
 		return 0;
 	} else if (show_info->any_rebase) {
 		printf_ln(_(" merges with remote %s"), merge->items[0].string);
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 02/39] i18n: advice: mark string about detached head for translation

Mark string with advice seen by the user when in detached head.

Update test t7201-co.sh to pass under GETTEXT_POISON build. Pretend success if the number of lines of "git checkout renamer^" output is not greater than 1 and test are running under GETTEXT_POISON.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 advice.c      | 6 +++---
 t/t7201-co.sh | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)
Show changes to 2 files +4 −4

advice.c, t/t7201-co.sh

diff --git a/advice.c b/advice.c
index 4dc5cf1..cb445a7 100644
--- a/advice.c
+++ b/advice.c
@@ -106,14 +106,14 @@ void NORETURN die_conclude_merge(void)
 
 void detach_advice(const char *new_name)
 {
-	const char fmt[] =
-	"Note: checking out '%s'.\n\n"
+	const char *fmt =
+	_("Note: checking out '%s'.\n\n"
 	"You are in 'detached HEAD' state. You can look around, make experimental\n"
 	"changes and commit them, and you can discard any commits you make in this\n"
 	"state without impacting any branches by performing another checkout.\n\n"
 	"If you want to create a new branch to retain commits you create, you may\n"
 	"do so (now or later) by using -b with the checkout command again. Example:\n\n"
-	"  git checkout -b <new-branch-name>\n\n";
+	"  git checkout -b <new-branch-name>\n\n");
 
 	fprintf(stderr, fmt, new_name);
 }
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 8859236..d4b217b 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -257,7 +257,7 @@ test_expect_success 'checkout to detach HEAD' '
 	git checkout -f renamer && git clean -f &&
 	git checkout renamer^ 2>messages &&
 	test_i18ngrep "HEAD is now at 7329388" messages &&
-	test_line_count -gt 1 messages &&
+	(test_line_count -gt 1 messages || test -n "$GETTEXT_POISON") &&
 	H=$(git rev-parse --verify HEAD) &&
 	M=$(git show-ref -s --verify refs/heads/master) &&
 	test "z$H" = "z$M" &&
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 03/39] i18n: advice: internationalize message for conflicts

Mark message for translation telling the user she has conflicts to resolve. Expose each particular use case, in order to enable translating entire sentences which would facilitate translating into other languages.

Change "Pull" to lowercase to match other instances. Update test t5520-pull.sh, that relied on the old error message, to use the new one.

Although we loose in source code conciseness, we would gain better translations because translators can 1) translate the entire sentence, including those terms concerning Git (committing, merging, etc) 2) have leeway to adapt to their languages.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 advice.c        | 17 +++++++++++++++--
 builtin/pull.c  |  2 +-
 t/t5520-pull.sh |  2 +-
 3 files changed, 17 insertions(+), 4 deletions(-)
Show changes to 3 files +17 −4

advice.c, builtin/pull.c, t/t5520-pull.sh

diff --git a/advice.c b/advice.c
index cb445a7..b84ae49 100644
--- a/advice.c
+++ b/advice.c
@@ -79,7 +79,20 @@ int git_default_advice_config(const char *var, const char *value)
 
 int error_resolve_conflict(const char *me)
 {
-	error("%s is not possible because you have unmerged files.", me);
+	if (!strcmp(me, "cherry-pick"))
+		error(_("Cherry-picking is not possible because you have unmerged files."));
+	else if (!strcmp(me, "commit"))
+		error(_("Committing is not possible because you have unmerged files."));
+	else if (!strcmp(me, "merge"))
+		error(_("Merging is not possible because you have unmerged files."));
+	else if (!strcmp(me, "pull"))
+		error(_("Pulling is not possible because you have unmerged files."));
+	else if (!strcmp(me, "revert"))
+		error(_("Reverting is not possible because you have unmerged files."));
+	else
+		error(_("It is not possible to %s because you have unmerged files."),
+			me);
+
 	if (advice_resolve_conflict)
 		/*
 		 * Message used both when 'git commit' fails and when
@@ -93,7 +106,7 @@ int error_resolve_conflict(const char *me)
 void NORETURN die_resolve_conflict(const char *me)
 {
 	error_resolve_conflict(me);
-	die("Exiting because of an unresolved conflict.");
+	die(_("Exiting because of an unresolved conflict."));
 }
 
 void NORETURN die_conclude_merge(void)
diff --git a/builtin/pull.c b/builtin/pull.c
index 1d7333c..a980dcf 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -852,7 +852,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
 	git_config(git_pull_config, NULL);
 
 	if (read_cache_unmerged())
-		die_resolve_conflict("Pull");
+		die_resolve_conflict("pull");
 
 	if (file_exists(git_path("MERGE_HEAD")))
 		die_conclude_merge();
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index 739c089..45e44ca 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -211,7 +211,7 @@ test_expect_success 'fail if the index has unresolved entries' '
 	test -n "$(git ls-files -u)" &&
 	cp file expected &&
 	test_must_fail git pull . second 2>err &&
-	test_i18ngrep "Pull is not possible because you have unmerged files" err &&
+	test_i18ngrep "Pulling is not possible because you have unmerged files." err &&
 	test_cmp expected file &&
 	git add file &&
 	test -z "$(git ls-files -u)" &&
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore
Mark one printf string and one error string for translation.
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 transport.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
Show changes to transport.c +10 −10
diff --git a/transport.c b/transport.c
index 095e61f..59b911e 100644
--- a/transport.c
+++ b/transport.c
@@ -59,7 +59,7 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
 				localname + 11, transport->remote->name,
 				remotename);
 		else
-			printf("Would set upstream of '%s' to '%s' of '%s'\n",
+			printf(_("Would set upstream of '%s' to '%s' of '%s'\n"),
 				localname + 11, remotename + 11,
 				transport->remote->name);
 	}
@@ -148,7 +148,7 @@ static int set_git_option(struct git_transport_options *opts,
 			char *end;
 			opts->depth = strtol(value, &end, 0);
 			if (*end)
-				die("transport: invalid depth option '%s'", value);
+				die(_("transport: invalid depth option '%s'"), value);
 		}
 		return 0;
 	}
@@ -767,19 +767,19 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)
 {
 	int i;
 
-	fprintf(stderr, "The following submodule paths contain changes that can\n"
-			"not be found on any remote:\n");
+	fprintf(stderr, _("The following submodule paths contain changes that can\n"
+			"not be found on any remote:\n"));
 	for (i = 0; i < needs_pushing->nr; i++)
 		printf("  %s\n", needs_pushing->items[i].string);
-	fprintf(stderr, "\nPlease try\n\n"
-			"	git push --recurse-submodules=on-demand\n\n"
-			"or cd to the path and use\n\n"
-			"	git push\n\n"
-			"to push them to a remote.\n\n");
+	fprintf(stderr, _("\nPlease try\n\n"
+			  "	git push --recurse-submodules=on-demand\n\n"
+			  "or cd to the path and use\n\n"
+			  "	git push\n\n"
+			  "to push them to a remote.\n\n"));
 
 	string_list_clear(needs_pushing, 0);
 
-	die("Aborting.");
+	die(_("Aborting."));
 }
 
 static int run_pre_push_hook(struct transport *transport,
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 05/39] i18n: sequencer: mark entire sentences for translation

Mark entire sentences of error message rather than assembling one using placeholders (e.g. "Cannot %s during a %s").

That would facilitate translation work because it is easier to translate a entire sentence than translating pieces. We would have better translations at the expense of source code verbosity.

Moreover, translators can now 1) translate the terms "revert" and "cherry-pick" if they please 2) have more leeway to adapt their translations.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 sequencer.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
Show changes to sequencer.c +8 −3
diff --git a/sequencer.c b/sequencer.c
index 4687ad4..88a7c78 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -697,9 +697,14 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *
 	 * opts; we don't support arbitrary instructions
 	 */
 	if (action != opts->action) {
-		const char *action_str;
-		action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick";
-		error(_("Cannot %s during a %s"), action_str, action_name(opts));
+		if (action == REPLAY_REVERT)
+		      error((opts->action == REPLAY_REVERT)
+			    ? _("Cannot revert during a another revert.")
+			    : _("Cannot revert during a cherry-pick."));
+		else
+		      error((opts->action == REPLAY_REVERT)
+			    ? _("Cannot cherry-pick during a revert.")
+			    : _("Cannot cherry-pick during another cherry-pick."));
 		return NULL;
 	}
 
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 06/39] i18n: sequencer: mark string for translation

Mark informative string "<action_name>: fast-forward" for translation.
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 sequencer.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to sequencer.c +1 −1
diff --git a/sequencer.c b/sequencer.c
index 88a7c78..57b3671 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -225,7 +225,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 	if (checkout_fast_forward(from, to, 1))
 		exit(128); /* the callee should have complained already */
 
-	strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
+	strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts));
 
 	transaction = ref_transaction_begin(&err);
 	if (!transaction ||
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 07/39] i18n: merge-octopus: mark messages for translation

Mark messages in git-merge-octopus.sh for translation.
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-merge-octopus.sh | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
Show changes to git-merge-octopus.sh +10 −8
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
index dc2fd1b..89e967a 100755
--- a/git-merge-octopus.sh
+++ b/git-merge-octopus.sh
@@ -5,6 +5,8 @@
 # Resolve two or more trees.
 #
 
+. git-sh-i18n
+
 LF='
 '
 
@@ -46,7 +48,7 @@ esac
 
 if ! git diff-index --quiet --cached HEAD --
 then
-    echo "Error: Your local changes to the following files would be overwritten by merge"
+    gettextln "Error: Your local changes to the following files would be overwritten by merge"
     git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'
     exit 2
 fi
@@ -61,8 +63,8 @@ do
 		# We allow only last one to have a hand-resolvable
 		# conflicts.  Last round failed and we still had
 		# a head to merge.
-		echo "Automated merge did not work."
-		echo "Should not be doing an Octopus."
+		gettextln "Automated merge did not work."
+		gettextln "Should not be doing an Octopus."
 		exit 2
 	esac
 
@@ -73,11 +75,11 @@ do
 		eval pretty_name=\${GITHEAD_$SHA1_UP:-$pretty_name}
 	fi
 	common=$(git merge-base --all $SHA1 $MRC) ||
-		die "Unable to find common commit with $pretty_name"
+		die "$(eval_gettext "Unable to find common commit with \$pretty_name")"
 
 	case "$LF$common$LF" in
 	*"$LF$SHA1$LF"*)
-		echo "Already up-to-date with $pretty_name"
+		eval_gettextln "Already up-to-date with \$pretty_name"
 		continue
 		;;
 	esac
@@ -89,7 +91,7 @@ do
 		# tree as the intermediate result of the merge.
 		# We still need to count this as part of the parent set.
 
-		echo "Fast-forwarding to: $pretty_name"
+		eval_gettextln "Fast-forwarding to: \$pretty_name"
 		git read-tree -u -m $head $SHA1 || exit
 		MRC=$SHA1 MRT=$(git write-tree)
 		continue
@@ -97,12 +99,12 @@ do
 
 	NON_FF_MERGE=1
 
-	echo "Trying simple merge with $pretty_name"
+	eval_gettextln "Trying simple merge with \$pretty_name"
 	git read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2
 	next=$(git write-tree 2>/dev/null)
 	if test $? -ne 0
 	then
-		echo "Simple merge did not work, trying automatic merge."
+		gettextln "Simple merge did not work, trying automatic merge."
 		git-merge-index -o git-merge-one-file -a ||
 		OCTOPUS_FAILURE=1
 		next=$(git write-tree 2>/dev/null)
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 08/39] merge-octupus: use die shell function from git-sh-setup.sh

Source git-sh-setup in order to use die shell function from git-sh-setup.sh library instead of using the one defined in git-merge-octopus.sh. Remove the former die function.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-merge-octopus.sh | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)
Show changes to git-merge-octopus.sh +1 −5
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
index 89e967a..d79fc84 100755
--- a/git-merge-octopus.sh
+++ b/git-merge-octopus.sh
@@ -5,16 +5,12 @@
 # Resolve two or more trees.
 #
 
+. git-sh-setup
 . git-sh-i18n
 
 LF='
 '
 
-die () {
-    echo >&2 "$*"
-    exit 1
-}
-
 # The first parameters up to -- are merge bases; the rest are heads.
 bases= head= remotes= sep_seen=
 for arg
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:40 UTC · re: Vasco Almeida · lore

[PATCH v3 09/39] i18n: rebase: fix marked string to use eval_gettext variant

The string message marked for translation should use eval_gettext variant instead of the gettext one, since we want to dollar-substitute $head_name in the result.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-rebase.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to git-rebase.sh +1 −1
diff --git a/git-rebase.sh b/git-rebase.sh
index 44ede36..1fadc04 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -154,7 +154,7 @@ move_to_original_branch () {
 		git symbolic-ref \
 			-m "rebase finished: returning to $head_name" \
 			HEAD $head_name ||
-		die "$(gettext "Could not move back to $head_name")"
+		die "$(eval_gettext "Could not move back to \$head_name")"
 		;;
 	esac
 }
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 10/39] i18n: rebase: mark placeholder for translation

Mark placeholder "<branch>" in git-rebase.sh for translation. The string containing the named placeholder is passed to shell function error_on_missing_default_upstream in git-parse-remote.sh which uses it to display a command hint for the user.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-rebase.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to git-rebase.sh +1 −1
diff --git a/git-rebase.sh b/git-rebase.sh
index 1fadc04..9ba21ab 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -449,7 +449,7 @@ then
 		then
 			. git-parse-remote
 			error_on_missing_default_upstream "rebase" "rebase" \
-				"against" "git rebase <branch>"
+				"against" "git rebase $(gettext '<branch>')"
 		fi
 
 		test "$fork_point" = auto && fork_point=t
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 11/39] i18n: bisect: simplify error message for i18n

The message was not being extracted by xgettext, although it was marked for translation, seemingly because it contained a command substitution. Moreover, eval_gettext should be used instead of gettext for strings with substitution.

See step 4. of section 15.5.2.1 Preparing Shell Scripts for Internationalization from gettext manual [1]: "Simplify translatable strings so that they don't contain command substitution ("`...`" or "$(...)") [...]"

[1] http://www.gnu.org/software/gettext/manual/html_node/Preparing-Shell-Scripts.html
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-bisect.sh | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Show changes to git-bisect.sh +3 −2
diff --git a/git-bisect.sh b/git-bisect.sh
index 5d1cb00..737bf05 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -277,8 +277,9 @@ bisect_state() {
 	0,*)
 		die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
 	1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip)
-		rev=$(git rev-parse --verify $(bisect_head)) ||
-			die "$(gettext "Bad rev input: $(bisect_head)")"
+		bisected_head=$(bisect_head)
+		rev=$(git rev-parse --verify "$bisected_head") ||
+			die "$(eval_gettext "Bad rev input: \$bisected_head")"
 		bisect_write "$state" "$rev"
 		check_expected_revs "$rev" ;;
 	2,"$TERM_BAD"|*,"$TERM_GOOD"|*,skip)
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 12/39] t6030: update to use test_i18ncmp

Since the git bisect output tested here is subject to translation, the helper function test_i18ncmp should be used over test_cmp.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 t/t6030-bisect-porcelain.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to t/t6030-bisect-porcelain.sh +2 −2
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index e74662b..7012011 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -803,7 +803,7 @@ test_expect_success 'bisect terms needs 0 or 1 argument' '
 	test_must_fail git bisect terms 1 2 &&
 	test_must_fail git bisect terms 2>actual &&
 	echo "no terms defined" >expected &&
-	test_cmp expected actual
+	test_i18ncmp expected actual
 '
 
 test_expect_success 'bisect terms shows good/bad after start' '
@@ -875,7 +875,7 @@ test_expect_success 'bisect start --term-* does store terms' '
 	Your current terms are two for the old state
 	and one for the new state.
 	EOF
-	test_cmp expected actual &&
+	test_i18ncmp expected actual &&
 	git bisect terms --term-bad >actual &&
 	echo one >expected &&
 	test_cmp expected actual &&
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 13/39] i18n: git-sh-setup.sh: mark strings for translation

Positional arguments, such as $0, $1, etc, need to be stored on shell variables for use in translatable strings, according to gettext manual [1].

Add git-sh-setup.sh to LOCALIZED_SH variable in Makefile to enable extraction of string marked for translation by xgettext.

Although git-sh-setup.sh is a shell library to be sourced by other shell scripts, it is necessary to source git-sh-i18n at this point, because some scripts don't do it themselves. Not sourcing git-sh-i18n would lead to failure due to, for instance, gettextln not being found.

Source "$(git --exec-path)"/git-sh-i18n instead of simply git-sh-i18n, because latter case would fail test t2300-cd-to-toplevel.sh.

[1] http://www.gnu.org/software/gettext/manual/html_node/Preparing-Shell-Scripts.html
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 Makefile        |  4 +++-
 git-sh-setup.sh | 62 +++++++++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 50 insertions(+), 16 deletions(-)
Show changes to 2 files +50 −16

Makefile, git-sh-setup.sh

diff --git a/Makefile b/Makefile
index 0d59718..4e0c4eb 100644
--- a/Makefile
+++ b/Makefile
@@ -2062,7 +2062,9 @@ XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \
 	--keyword=gettextln --keyword=eval_gettextln
 XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
 LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
-LOCALIZED_SH = $(SCRIPT_SH) git-parse-remote.sh
+LOCALIZED_SH = $(SCRIPT_SH)
+LOCALIZED_SH += git-parse-remote.sh
+LOCALIZED_SH += git-sh-setup.sh
 LOCALIZED_PERL = $(SCRIPT_PERL)
 
 ifdef XGETTEXT_INCLUDE_TESTS
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index c48139a..61a3d95 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -2,6 +2,10 @@
 # to set up some variables pointing at the normal git directories and
 # a few helper shell functions.
 
+# Some shell scripts source git-sh-setup (i.e. this scriplet) but
+# don't source git-sh-i18n which is needed here for gettext support.
+. "$(git --exec-path)"/git-sh-i18n
+
 # Having this variable in your environment would break scripts because
 # you would cause "cd" to be taken to unexpected places.  If you
 # like CDPATH, define it for your interactive shell sessions without
@@ -83,14 +87,14 @@ if test -n "$OPTIONS_SPEC"; then
 else
 	dashless=$(basename -- "$0" | sed -e 's/-/ /')
 	usage() {
-		die "usage: $dashless $USAGE"
+		die "$(gettext usage:) $dashless $USAGE"
 	}
 
 	if [ -z "$LONG_USAGE" ]
 	then
-		LONG_USAGE="usage: $dashless $USAGE"
+		LONG_USAGE="$(gettext usage:) $dashless $USAGE"
 	else
-		LONG_USAGE="usage: $dashless $USAGE
+		LONG_USAGE="$(gettext usage:) $dashless $USAGE
 
 $LONG_USAGE"
 	fi
@@ -182,7 +186,7 @@ is_bare_repository () {
 cd_to_toplevel () {
 	cdup=$(git rev-parse --show-toplevel) &&
 	cd "$cdup" || {
-		echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+		gettextln "Cannot chdir to \$cdup, the toplevel of the working tree" >&2
 		exit 1
 	}
 }
@@ -190,13 +194,16 @@ cd_to_toplevel () {
 require_work_tree_exists () {
 	if test "z$(git rev-parse --is-bare-repository)" != zfalse
 	then
-		die "fatal: $0 cannot be used without a working tree."
+		program_name=$0
+		die "$(gettext "fatal: \$program_name cannot be used without a working tree.")"
 	fi
 }
 
 require_work_tree () {
-	test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true ||
-	die "fatal: $0 cannot be used without a working tree."
+	test "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = true || {
+		program_name=$0
+		die "$(gettext "fatal: \$program_name cannot be used without a working tree.")"
+	}
 }
 
 require_clean_work_tree () {
@@ -206,24 +213,49 @@ require_clean_work_tree () {
 
 	if ! git diff-files --quiet --ignore-submodules
 	then
-		echo >&2 "Cannot $1: You have unstaged changes."
+		action=$1
+		case "$action" in
+		rebase)
+			gettextln "Cannot rebase: You have unstaged changes." >&2
+			;;
+		"rewrite branches")
+			gettextln "Cannot rewrite branches: You have unstaged changes." >&2
+			;;
+		"pull with rebase")
+			gettextln "Cannot pull with rebase: You have unstaged changes." >&2
+			;;
+		*)
+			eval_gettextln "Cannot \$action: You have unstaged changes." >&2
+			;;
+		esac
 		err=1
 	fi
 
 	if ! git diff-index --cached --quiet --ignore-submodules HEAD --
 	then
-		if [ $err = 0 ]
+		if test $err = 0
 		then
-		    echo >&2 "Cannot $1: Your index contains uncommitted changes."
+			action=$1
+			case "$action" in
+			rebase)
+				gettextln "Cannot rebase: Your index contains uncommitted changes." >&2
+				;;
+			"pull with rebase")
+				gettextln "Cannot pull with rebase: Your index contains uncommitted changes." >&2
+				;;
+			*)
+				eval_gettextln "Cannot \$action: Your index contains uncommitted changes." >&2
+				;;
+			esac
 		else
-		    echo >&2 "Additionally, your index contains uncommitted changes."
+		    gettextln "Additionally, your index contains uncommitted changes." >&2
 		fi
 		err=1
 	fi
 
-	if [ $err = 1 ]
+	if test $err = 1
 	then
-		test -n "$2" && echo >&2 "$2"
+		test -n "$2" && echo "$2" >&2
 		exit 1
 	fi
 }
@@ -336,12 +368,12 @@ git_dir_init () {
 	then
 		test -z "$(git rev-parse --show-cdup)" || {
 			exit=$?
-			echo >&2 "You need to run this command from the toplevel of the working tree."
+			gettextln "You need to run this command from the toplevel of the working tree." >&2
 			exit $exit
 		}
 	fi
 	test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || {
-		echo >&2 "Unable to determine absolute path of git directory"
+		gettextln "Unable to determine absolute path of git directory" >&2
 		exit 1
 	}
 	: ${GIT_OBJECT_DIRECTORY="$(git rev-parse --git-path objects)"}
-- 
2.7.3
Junio C Hamano· Jun 1, 2016, 18:20 UTC · re: Vasco Almeida · lore

Re: [PATCH v3 13/39] i18n: git-sh-setup.sh: mark strings for translation

Vasco Almeida <vascomalmeida@sapo.pt> writes:
Show 11 quoted lines
> Positional arguments, such as $0, $1, etc, need to be stored on shell
> variables for use in translatable strings, according to gettext manual
> [1].
>
> Add git-sh-setup.sh to LOCALIZED_SH variable in Makefile to enable
> extraction of string marked for translation by xgettext.
>
> Although git-sh-setup.sh is a shell library to be sourced by other shell
> scripts, it is necessary to source git-sh-i18n at this point, because
> some scripts don't do it themselves. Not sourcing git-sh-i18n would lead
> to failure due to, for instance, gettextln not being found.

That is correct, but doesn't that hint that the current users of git-sh-i18n no longer have to (and they probably do not want to) dot-source it, as they are already dot-sourcing git-sh-setup?

Otherwise they will waste cycles reading the same thing twice (assuming that git-sh-i18n is safely read twice, that is).

> Source "$(git --exec-path)"/git-sh-i18n instead of simply git-sh-i18n,

That is somewhat strange. "git-rebase.sh" dot-sources both git-sh-setup and git-sh-i18n with just ". $name". What is so special about sh-setup dot-sourcing sh-i18n your patch adds?

> because latter case would fail test t2300-cd-to-toplevel.sh.
What is the real cause of this?

". git-sh-setup" is supposed to work because we prepend the $GIT_EXEC_PATH in "git" itself and git-sh-setup is installed there. ". git-sh-i18n" in "git-rebase.sh" works the same way. If it is because is t2300 doing something strange, then we should fix that, not adding $(git --exec-path) on the dot-source line to paper things over.

Junio C Hamano· Jun 1, 2016, 20:30 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 13/39] i18n: git-sh-setup.sh: mark strings for translation

Junio C Hamano <gitster@pobox.com> writes:
Show 6 quoted lines
> That is correct, but doesn't that hint that the current users of
> git-sh-i18n no longer have to (and they probably do not want to)
> dot-source it, as they are already dot-sourcing git-sh-setup?
>
> Otherwise they will waste cycles reading the same thing twice
> (assuming that git-sh-i18n is safely read twice, that is).
This part still stands.

If we assume that it is a good idea to i18n shell functions that are supplied by git-sh-setup, it might be a better direction to retire git-sh-i18n and include its text directly in git-sh-setup, so that any scripted Porcelain has to open just one file to dot-source it, instead of two.

Show 16 quoted lines
>> Source "$(git --exec-path)"/git-sh-i18n instead of simply git-sh-i18n,
>
> That is somewhat strange.  "git-rebase.sh" dot-sources both
> git-sh-setup and git-sh-i18n with just ". $name".  What is so
> special about sh-setup dot-sourcing sh-i18n your patch adds?
>
>> because latter case would fail test t2300-cd-to-toplevel.sh.
>
> What is the real cause of this?
>
> ". git-sh-setup" is supposed to work because we prepend the
> $GIT_EXEC_PATH in "git" itself and git-sh-setup is installed there.
> ". git-sh-i18n" in "git-rebase.sh" works the same way.  If it is
> because is t2300 doing something strange, then we should fix that,
> not adding $(git --exec-path) on the dot-source line to paper things
> over.

This because what t2300 does is strange; I wouldn't call it "wrong" per se, but it is borderline wrong.

Would it allow you to lose the $(git --exec-path) prefix in the new dot-source to have this patch before applying your patch?

-- >8 --
Subject: t2300: run git-sh-setup in an environment that better mimics the real life

When we run scripted Porcelains, "git" potty has set up the $PATH by prepending $GIT_EXEC_PATH, the path given by "git --exec-path=$there $cmd", etc. already. Because of this, scripted Porcelains can dot-source shell script library like git-sh-setup with simple dot without specifying any path.

t2300 however dot-sources git-sh-setup without adjusting $PATH like the real "git" potty does. This has not been a problem so far, but once git-sh-setup wants to rely on the $PATH adjustment, just like any scripted Porcelains already do, it would become one. It cannot for example dot-source another shell library without specifying the full path to it by prefixing $(git --exec-path).

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t2300-cd-to-toplevel.sh | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Show changes to t/t2300-cd-to-toplevel.sh +2 −1
diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh
index 9965bc5..cccd7d9 100755
--- a/t/t2300-cd-to-toplevel.sh
+++ b/t/t2300-cd-to-toplevel.sh
@@ -8,7 +8,8 @@ test_cd_to_toplevel () {
 	test_expect_success $3 "$2" '
 		(
 			cd '"'$1'"' &&
-			. "$(git --exec-path)"/git-sh-setup &&
+			PATH="$(git --exec-path):$PATH" &&
+			. git-sh-setup &&
 			cd_to_toplevel &&
 			[ "$(pwd -P)" = "$TOPLEVEL" ]
 		)
Vasco Almeida· Jun 2, 2016, 14:14 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 13/39] i18n: git-sh-setup.sh: mark strings for translation

Às 20:30 de 01-06-2016, Junio C Hamano escreveu:
Show 40 quoted lines
> Junio C Hamano <gitster@pobox.com> writes:
> Would it allow you to lose the $(git --exec-path) prefix in the new
> dot-source to have this patch before applying your patch?
> 
> -- >8 --
> Subject: t2300: run git-sh-setup in an environment that better mimics the real life
> 
> When we run scripted Porcelains, "git" potty has set up the $PATH by
> prepending $GIT_EXEC_PATH, the path given by "git --exec-path=$there
> $cmd", etc. already.  Because of this, scripted Porcelains can
> dot-source shell script library like git-sh-setup with simple dot
> without specifying any path.
> 
> t2300 however dot-sources git-sh-setup without adjusting $PATH like
> the real "git" potty does.  This has not been a problem so far, but
> once git-sh-setup wants to rely on the $PATH adjustment, just like
> any scripted Porcelains already do, it would become one.  It cannot
> for example dot-source another shell library without specifying the
> full path to it by prefixing $(git --exec-path).
> 
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  t/t2300-cd-to-toplevel.sh | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh
> index 9965bc5..cccd7d9 100755
> --- a/t/t2300-cd-to-toplevel.sh
> +++ b/t/t2300-cd-to-toplevel.sh
> @@ -8,7 +8,8 @@ test_cd_to_toplevel () {
>  	test_expect_success $3 "$2" '
>  		(
>  			cd '"'$1'"' &&
> -			. "$(git --exec-path)"/git-sh-setup &&
> +			PATH="$(git --exec-path):$PATH" &&
> +			. git-sh-setup &&
>  			cd_to_toplevel &&
>  			[ "$(pwd -P)" = "$TOPLEVEL" ]
>  		)
> 

Yes, this patch allows to have . git-sh-i18n instead of . "$(git --exec-path)"/git-sh-i18n in git-sh-setup.sh file.

Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 14/39] i18n: rebase-interactive: mark strings for translation

Mark strings in git-rebase--interactive.sh for translation. There is no need to source git-sh-i18n since git-rebase.sh already does so.

Add git-rebase--interactive.sh to LOCALIZED_SH in Makefile in order to enable extracting strings marked for translation by xgettext.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 Makefile                   |   1 +
 git-rebase--interactive.sh | 191 ++++++++++++++++++++++++---------------------
 2 files changed, 102 insertions(+), 90 deletions(-)
Show changes to 2 files +102 −90

Makefile, git-rebase--interactive.sh

diff --git a/Makefile b/Makefile
index 4e0c4eb..d8117bb 100644
--- a/Makefile
+++ b/Makefile
@@ -2064,6 +2064,7 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
 LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
 LOCALIZED_SH = $(SCRIPT_SH)
 LOCALIZED_SH += git-parse-remote.sh
+LOCALIZED_SH += git-rebase--interactive.sh
 LOCALIZED_SH += git-sh-setup.sh
 LOCALIZED_PERL = $(SCRIPT_PERL)
 
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 9d2bfb7..078ca5b 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -128,7 +128,7 @@ mark_action_done () {
 	if test "$last_count" != "$new_count"
 	then
 		last_count=$new_count
-		printf "Rebasing (%d/%d)\r" $new_count $total
+		eval_gettext "Rebasing (\$new_count/\$total)"; printf "\r"
 		test -z "$verbose" || echo
 	fi
 }
@@ -201,13 +201,14 @@ exit_with_patch () {
 	make_patch $1
 	git rev-parse --verify HEAD > "$amend"
 	gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
-	warn "You can amend the commit now, with"
-	warn
-	warn "	git commit --amend $gpg_sign_opt_quoted"
-	warn
-	warn "Once you are satisfied with your changes, run"
-	warn
-	warn "	git rebase --continue"
+	warn "$(eval_gettext "\
+You can amend the commit now, with
+
+	git commit --amend \$gpg_sign_opt_quoted
+
+Once you are satisfied with your changes, run
+
+	git rebase --continue")"
 	warn
 	exit $2
 }
@@ -222,10 +223,12 @@ has_action () {
 }
 
 is_empty_commit() {
-	tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
-		die "$1: not a commit that can be picked")
-	ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
-		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+	tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) || {
+		sha1=$1
+		die "$(eval_gettext "\$sha1: not a commit that can be picked")"
+	}
+	ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) ||
+		ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
 	test "$tree" = "$ptree"
 }
 
@@ -261,7 +264,7 @@ pick_one () {
 
 	case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
 	case "$force_rebase" in '') ;; ?*) ff= ;; esac
-	output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+	output git rev-parse --verify $sha1 || die "$(eval_gettext "Invalid commit name: \$sha1")"
 
 	if is_empty_commit "$sha1"
 	then
@@ -303,7 +306,7 @@ pick_one_preserving_merges () {
 				git rev-parse HEAD > "$rewritten"/$current_commit
 			done <"$state_dir"/current-commit
 			rm "$state_dir"/current-commit ||
-			die "Cannot write current commit's replacement sha1"
+				die "$(gettext "Cannot write current commit's replacement sha1")"
 		fi
 	fi
 
@@ -355,9 +358,9 @@ pick_one_preserving_merges () {
 	done
 	case $fast_forward in
 	t)
-		output warn "Fast-forward to $sha1"
+		output warn "$(eval_gettext "Fast-forward to \$sha1")"
 		output git reset --hard $sha1 ||
-			die "Cannot fast-forward to $sha1"
+			die "$(eval_gettext "Cannot fast-forward to \$sha1")"
 		;;
 	f)
 		first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
@@ -366,12 +369,12 @@ pick_one_preserving_merges () {
 		then
 			# detach HEAD to current parent
 			output git checkout $first_parent 2> /dev/null ||
-				die "Cannot move HEAD to $first_parent"
+				die "$(eval_gettext "Cannot move HEAD to \$first_parent")"
 		fi
 
 		case "$new_parents" in
 		' '*' '*)
-			test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
+			test "a$1" = a-n && die "$(eval_gettext "Refusing to squash a merge: \$sha1")"
 
 			# redo merge
 			author_script_content=$(get_author_ident_from_commit $sha1)
@@ -385,7 +388,7 @@ pick_one_preserving_merges () {
 				$merge_args $strategy_args -m "$msg_content" $new_parents'
 			then
 				printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG
-				die_with_patch $sha1 "Error redoing merge $sha1"
+				die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")"
 			fi
 			echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list"
 			;;
@@ -393,7 +396,7 @@ pick_one_preserving_merges () {
 			output eval git cherry-pick \
 				${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \
 				"$strategy_args" "$@" ||
-				die_with_patch $sha1 "Could not pick $sha1"
+				die_with_patch $sha1 "$(eval_gettext "Could not pick \$sha1")"
 			;;
 		esac
 		;;
@@ -460,12 +463,14 @@ peek_next_command () {
 # messages, effectively causing the combined commit to be used as the
 # new basis for any further squash/fixups.  Args: sha1 rest
 die_failed_squash() {
+	sha1=$1
+	rest=$2
 	mv "$squash_msg" "$msg" || exit
 	rm -f "$fixup_msg"
 	cp "$msg" "$GIT_DIR"/MERGE_MSG || exit
 	warn
-	warn "Could not apply $1... $2"
-	die_with_patch $1 ""
+	warn "$(eval_gettext "Could not apply \$sha1... \$rest")"
+	die_with_patch $sha1 ""
 }
 
 flush_rewritten_pending() {
@@ -489,6 +494,8 @@ record_in_rewritten() {
 }
 
 do_pick () {
+	sha1=$1
+	rest=$2
 	if test "$(git rev-parse HEAD)" = "$squash_onto"
 	then
 		# Set the correct commit message and author info on the
@@ -500,15 +507,15 @@ do_pick () {
 		# resolve before manually running git commit --amend then git
 		# rebase --continue.
 		git commit --allow-empty --allow-empty-message --amend \
-			   --no-post-rewrite -n -q -C $1 &&
-			pick_one -n $1 &&
+			   --no-post-rewrite -n -q -C $sha1 &&
+			pick_one -n $sha1 &&
 			git commit --allow-empty --allow-empty-message \
-				   --amend --no-post-rewrite -n -q -C $1 \
+				   --amend --no-post-rewrite -n -q -C $sha1 \
 				   ${gpg_sign_opt:+"$gpg_sign_opt"} ||
-			die_with_patch $1 "Could not apply $1... $2"
+				   die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
 	else
-		pick_one $1 ||
-			die_with_patch $1 "Could not apply $1... $2"
+		pick_one $sha1 ||
+			die_with_patch $sha1 "$(eval_gettext "Could not apply \$sha1... \$rest")"
 	fi
 }
 
@@ -536,10 +543,11 @@ do_next () {
 		mark_action_done
 		do_pick $sha1 "$rest"
 		git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || {
-			warn "Could not amend commit after successfully picking $sha1... $rest"
-			warn "This is most likely due to an empty commit message, or the pre-commit hook"
-			warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
-			warn "you are able to reword the commit."
+			warn "$(eval_gettext "\
+Could not amend commit after successfully picking \$sha1... \$rest
+This is most likely due to an empty commit message, or the pre-commit hook
+failed. If the pre-commit hook failed, you may need to resolve the issue before
+you are able to reword the commit.")"
 			exit_with_patch $sha1 1
 		}
 		record_in_rewritten $sha1
@@ -550,7 +558,7 @@ do_next () {
 		mark_action_done
 		do_pick $sha1 "$rest"
 		sha1_abbrev=$(git rev-parse --short $sha1)
-		warn "Stopped at $sha1_abbrev... $rest"
+		warn "$(eval_gettext "Stopped at \$sha1_abbrev... \$rest")"
 		exit_with_patch $sha1 0
 		;;
 	squash|s|fixup|f)
@@ -565,7 +573,7 @@ do_next () {
 		comment_for_reflog $squash_style
 
 		test -f "$done" && has_action "$done" ||
-			die "Cannot '$squash_style' without a previous commit"
+			die "$(eval_gettext "Cannot '\$squash_style' without a previous commit")"
 
 		mark_action_done
 		update_squash_messages $squash_style $sha1
@@ -607,7 +615,7 @@ do_next () {
 	x|"exec")
 		read -r command rest < "$todo"
 		mark_action_done
-		printf 'Executing: %s\n' "$rest"
+		eval_gettextln "Executing: \$rest"
 		"${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution
 		status=$?
 		# Run in subshell because require_clean_work_tree can die.
@@ -615,13 +623,14 @@ do_next () {
 		(require_clean_work_tree "rebase" 2>/dev/null) || dirty=t
 		if test "$status" -ne 0
 		then
-			warn "Execution failed: $rest"
+			warn "$(eval_gettext "Execution failed: \$rest")"
 			test "$dirty" = f ||
-			warn "and made changes to the index and/or the working tree"
+				warn "$(gettext "and made changes to the index and/or the working tree")"
 
-			warn "You can fix the problem, and then run"
-			warn
-			warn "	git rebase --continue"
+			warn "$(gettext "\
+You can fix the problem, and then run
+
+	git rebase --continue")"
 			warn
 			if test $status -eq 127		# command not found
 			then
@@ -630,18 +639,20 @@ do_next () {
 			exit "$status"
 		elif test "$dirty" = t
 		then
-			warn "Execution succeeded: $rest"
-			warn "but left changes to the index and/or the working tree"
-			warn "Commit or stash your changes, and then run"
-			warn
-			warn "	git rebase --continue"
+			# TRANSLATORS: after these lines is a command to be issued by the user
+			warn "$(eval_gettext "\
+Execution succeeded: \$rest
+but left changes to the index and/or the working tree
+Commit or stash your changes, and then run
+
+	git rebase --continue")"
 			warn
 			exit 1
 		fi
 		;;
 	*)
-		warn "Unknown command: $command $sha1 $rest"
-		fixtodo="Please fix this using 'git rebase --edit-todo'."
+		warn "$(eval_gettext "Unknown command: \$command \$sha1 \$rest")"
+		fixtodo="$(gettext "Please fix this using 'git rebase --edit-todo'.")"
 		if git rev-parse --verify -q "$sha1" >/dev/null
 		then
 			die_with_patch $sha1 "$fixtodo"
@@ -676,7 +687,7 @@ do_next () {
 		"$hook" rebase < "$rewritten_list"
 		true # we don't care if this hook failed
 	fi &&
-	warn "Successfully rebased and updated $head_name."
+		warn "$(eval_gettext "Successfully rebased and updated \$head_name.")"
 
 	return 1 # not failure; just to break the do_rest loop
 }
@@ -723,7 +734,7 @@ skip_unnecessary_picks () {
 		record_in_rewritten "$onto"
 		;;
 	esac ||
-	die "Could not skip unnecessary pick commands"
+		die "$(gettext "Could not skip unnecessary pick commands")"
 }
 
 transform_todo_ids () {
@@ -881,9 +892,9 @@ check_commit_sha () {
 	if test $badsha -ne 0
 	then
 		line="$(sed -n -e "${2}p" "$3")"
-		warn "Warning: the SHA-1 is missing or isn't" \
-			"a commit in the following line:"
-		warn " - $line"
+		warn "$(eval_gettext "\
+Warning: the SHA-1 is missing or isn't a commit in the following line:
+ - \$line")"
 		warn
 	fi
 
@@ -914,9 +925,9 @@ check_bad_cmd_and_sha () {
 			;;
 		*)
 			line="$(sed -n -e "${lineno}p" "$1")"
-			warn "Warning: the command isn't recognized" \
-				"in the following line:"
-			warn " - $line"
+			warn "$(eval_gettext "\
+Warning: the command isn't recognized in the following line:
+ - \$line")"
 			warn
 			retval=1
 			;;
@@ -953,7 +964,7 @@ warn_lines () {
 # Switch to the branch in $into and notify it in the reflog
 checkout_onto () {
 	GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
-	output git checkout $onto || die_abort "could not detach HEAD"
+	output git checkout $onto || die_abort "$(gettext "could not detach HEAD")"
 	git update-ref ORIG_HEAD $orig_head
 }
 
@@ -991,28 +1002,26 @@ check_todo_list () {
 		then
 			test "$check_level" = error && raise_error=t
 
-			warn "Warning: some commits may have been dropped" \
-				"accidentally."
-			warn "Dropped commits (newer to older):"
+			warn "$(gettext "\
+Warning: some commits may have been dropped accidentally.
+Dropped commits (newer to older):")"
 
 			# Make the list user-friendly and display
 			opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin"
 			git rev-list $opt <"$todo".miss | warn_lines
 
-			warn "To avoid this message, use \"drop\" to" \
-				"explicitly remove a commit."
-			warn
-			warn "Use 'git config rebase.missingCommitsCheck' to change" \
-				"the level of warnings."
-			warn "The possible behaviours are: ignore, warn, error."
+			warn "$(gettext "\
+To avoid this message, use \"drop\" to explicitly remove a commit.
+
+Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
+The possible behaviours are: ignore, warn, error.")"
 			warn
 		fi
 		;;
 	ignore)
 		;;
 	*)
-		warn "Unrecognized setting $check_level for option" \
-			"rebase.missingCommitsCheck. Ignoring."
+		warn "$(eval_gettext "Unrecognized setting \$check_level for option rebase.missingCommitsCheck. Ignoring.")"
 		;;
 	esac
 
@@ -1029,8 +1038,8 @@ check_todo_list () {
 		# placed before the commit of the next action
 		checkout_onto
 
-		warn "You can fix this with 'git rebase --edit-todo'."
-		die "Or you can abort the rebase with 'git rebase --abort'."
+		warn "$(gettext "You can fix this with 'git rebase --edit-todo'.")"
+		die "$(gettext "Or you can abort the rebase with 'git rebase --abort'.")"
 	fi
 }
 
@@ -1054,41 +1063,43 @@ continue)
 
 		test ! -f "$GIT_DIR"/CHERRY_PICK_HEAD ||
 		rm "$GIT_DIR"/CHERRY_PICK_HEAD ||
-		die "Could not remove CHERRY_PICK_HEAD"
+		die "$(gettext "Could not remove CHERRY_PICK_HEAD")"
 	else
 		if ! test -f "$author_script"
 		then
 			gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")}
-			die "You have staged changes in your working tree. If these changes are meant to be
+			die "$(eval_gettext "\
+You have staged changes in your working tree.
+If these changes are meant to be
 squashed into the previous commit, run:
 
-  git commit --amend $gpg_sign_opt_quoted
+  git commit --amend \$gpg_sign_opt_quoted
 
 If they are meant to go into a new commit, run:
 
-  git commit $gpg_sign_opt_quoted
+  git commit \$gpg_sign_opt_quoted
 
 In both case, once you're done, continue with:
 
   git rebase --continue
-"
+")"
 		fi
 		. "$author_script" ||
-			die "Error trying to find the author identity to amend commit"
+			die "$(gettext "Error trying to find the author identity to amend commit")"
 		if test -f "$amend"
 		then
 			current_head=$(git rev-parse --verify HEAD)
 			test "$current_head" = $(cat "$amend") ||
-			die "\
-You have uncommitted changes in your working tree. Please, commit them
-first and then run 'git rebase --continue' again."
+			die "$(gettext "\
+You have uncommitted changes in your working tree. Please commit them
+first and then run 'git rebase --continue' again.")"
 			do_with_author git commit --amend --no-verify -F "$msg" -e \
 				${gpg_sign_opt:+"$gpg_sign_opt"} ||
-				die "Could not commit staged changes."
+				die "$(gettext "Could not commit staged changes.")"
 		else
 			do_with_author git commit --no-verify -F "$msg" -e \
 				${gpg_sign_opt:+"$gpg_sign_opt"} ||
-				die "Could not commit staged changes."
+				die "$(gettext "Could not commit staged changes.")"
 		fi
 	fi
 
@@ -1121,7 +1132,7 @@ To continue rebase after editing, run:
 EOF
 
 	git_sequence_editor "$todo" ||
-		die "Could not execute editor"
+		die "$(gettext "Could not execute editor")"
 	expand_todo_ids
 
 	exit
@@ -1129,7 +1140,7 @@ EOF
 esac
 
 git var GIT_COMMITTER_IDENT >/dev/null ||
-	die "You need to set your committer info first"
+	die "$(gettext "You need to set your committer info first")"
 
 comment_for_reflog start
 
@@ -1137,15 +1148,15 @@ if test ! -z "$switch_to"
 then
 	GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
 	output git checkout "$switch_to" -- ||
-	die "Could not checkout $switch_to"
+		die "$(eval_gettext "Could not checkout \$switch_to")"
 
 	comment_for_reflog start
 fi
 
-orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?"
-mkdir -p "$state_dir" || die "Could not create temporary $state_dir"
+orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
+mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
 
-: > "$state_dir"/interactive || die "Could not mark as interactive"
+: > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
 write_basic_state
 if test t = "$preserve_merges"
 then
@@ -1155,12 +1166,12 @@ then
 		for c in $(git merge-base --all $orig_head $upstream)
 		do
 			echo $onto > "$rewritten"/$c ||
-				die "Could not init rewritten commits"
+				die "$(gettext "Could not init rewritten commits")"
 		done
 	else
 		mkdir "$rewritten" &&
 		echo $onto > "$rewritten"/root ||
-			die "Could not init rewritten commits"
+			die "$(gettext "Could not init rewritten commits")"
 	fi
 	# No cherry-pick because our first pass is to determine
 	# parents to rewrite and skipping dropped commits would
@@ -1270,7 +1281,7 @@ EOF
 
 if test -z "$keep_empty"
 then
-	printf '%s\n' "$comment_char Note that empty commits are commented out" >>"$todo"
+	printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
 fi
 
 
@@ -1280,7 +1291,7 @@ has_action "$todo" ||
 cp "$todo" "$todo".backup
 collapse_todo_ids
 git_sequence_editor "$todo" ||
-	die_abort "Could not execute editor"
+	die_abort "$(gettext "Could not execute editor")"
 
 has_action "$todo" ||
 	return 2
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 15/39] i18n: rebase-interactive: mark here-doc strings for translation

Use pipe to send gettext output to git stripspace instead of the original method of using shell here-document, because command substitution '$(...)' would not take place inside the here-documents. The exception is the case of the last here-document redirecting to cat, in which commands substitution works and, thus, is preserved in this commit.

t3404: adapt test to the strings newly marked for translation Test t3404-rebase-interactive.sh would fail under GETTEXT_POISON unless using test_i18ngrep.

Add eval_ngettext fallback functions to be called when running, for instance, under GETTEXT_POISON. Otherwise, tests would fail under GETTEXT_POISON, or other build that doesn't support the GNU gettext, because that function could not be found.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-rebase--interactive.sh    | 30 +++++++++++++++---------------
 git-sh-i18n.sh                | 18 ++++++++++++++++++
 t/t3404-rebase-interactive.sh | 14 +++++++-------
 3 files changed, 40 insertions(+), 22 deletions(-)
Show changes to 3 files +40 −22

git-rebase--interactive.sh, git-sh-i18n.sh, t/t3404-rebase-interactive.sh

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 078ca5b..e132f07 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -144,29 +144,28 @@ reschedule_last_action () {
 }
 
 append_todo_help () {
-	git stripspace --comment-lines >>"$todo" <<\EOF
-
+	gettext "
 Commands:
  p, pick = use commit
  r, reword = use commit, but edit the commit message
  e, edit = use commit, but stop for amending
  s, squash = use commit, but meld into previous commit
- f, fixup = like "squash", but discard this commit's log message
+ f, fixup = like \"squash\", but discard this commit's log message
  x, exec = run command (the rest of the line) using shell
  d, drop = remove commit
 
 These lines can be re-ordered; they are executed from top to bottom.
+" | git stripspace --comment-lines >>"$todo"
 
-EOF
 	if test $(get_missing_commit_check_level) = error
 	then
-		git stripspace --comment-lines >>"$todo" <<\EOF
+		gettext "
 Do not remove any line. Use 'drop' explicitly to remove a commit.
-EOF
+" | git stripspace --comment-lines >>"$todo"
 	else
-		git stripspace --comment-lines >>"$todo" <<\EOF
+		gettext "
 If you remove a line here THAT COMMIT WILL BE LOST.
-EOF
+" | git stripspace --comment-lines >>"$todo"
 	fi
 }
 
@@ -1123,13 +1122,12 @@ edit-todo)
 	mv -f "$todo".new "$todo"
 	collapse_todo_ids
 	append_todo_help
-	git stripspace --comment-lines >>"$todo" <<\EOF
-
+	gettext "
 You are editing the todo file of an ongoing interactive rebase.
 To continue rebase after editing, run:
     git rebase --continue
 
-EOF
+" | git stripspace --comment-lines >>"$todo"
 
 	git_sequence_editor "$todo" ||
 		die "$(gettext "Could not execute editor")"
@@ -1270,14 +1268,16 @@ todocount=${todocount##* }
 
 cat >>"$todo" <<EOF
 
-$comment_char Rebase $shortrevisions onto $shortonto ($todocount command(s))
+$comment_char $(eval_ngettext \
+	"Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
+	"Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
+	"$todocount")
 EOF
 append_todo_help
-git stripspace --comment-lines >>"$todo" <<\EOF
-
+gettext "
 However, if you remove everything, the rebase will be aborted.
 
-EOF
+" | git stripspace --comment-lines >>"$todo"
 
 if test -z "$keep_empty"
 then
diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh
index e6c3116..1ef1889 100644
--- a/git-sh-i18n.sh
+++ b/git-sh-i18n.sh
@@ -53,6 +53,13 @@ gettext_without_eval_gettext)
 			git sh-i18n--envsubst "$1"
 		)
 	}
+
+	eval_ngettext () {
+		ngettext "$1" "$2" "$3" | (
+			export PATH $(git sh-i18n--envsubst --variables "$2");
+			git sh-i18n--envsubst "$2"
+		)
+	}
 	;;
 poison)
 	# Emit garbage so that tests that incorrectly rely on translatable
@@ -64,6 +71,10 @@ poison)
 	eval_gettext () {
 		printf "%s" "# GETTEXT POISON #"
 	}
+
+	eval_ngettext () {
+		printf "%s" "# GETTEXT POISON #"
+	}
 	;;
 *)
 	gettext () {
@@ -76,6 +87,13 @@ poison)
 			git sh-i18n--envsubst "$1"
 		)
 	}
+
+	eval_ngettext () {
+		(test "$3" = 1 && printf "%s" "$1" || printf "%s" "$2") | (
+			export PATH $(git sh-i18n--envsubst --variables "$2");
+			git sh-i18n--envsubst "$2"
+		)
+	}
 	;;
 esac
 
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 66348f1..f4ccd10 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -540,7 +540,7 @@ test_expect_success 'clean error after failed "exec"' '
 	echo "edited again" > file7 &&
 	git add file7 &&
 	test_must_fail git rebase --continue 2>error &&
-	grep "You have staged changes in your working tree." error
+	test_i18ngrep "You have staged changes in your working tree." error
 '
 
 test_expect_success 'rebase a detached HEAD' '
@@ -1060,7 +1060,7 @@ test_expect_success 'todo count' '
 	EOF
 	test_set_editor "$(pwd)/dump-raw.sh" &&
 	git rebase -i HEAD~4 >actual &&
-	grep "^# Rebase ..* onto ..* ([0-9]" actual
+	test_i18ngrep "^# Rebase ..* onto ..* ([0-9]" actual
 '
 
 test_expect_success 'rebase -i commits that overwrite untracked files (pick)' '
@@ -1160,7 +1160,7 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
 	FAKE_LINES="1 2 3 4" \
 		git rebase -i --root 2>actual &&
 	test D = $(git cat-file commit HEAD | sed -ne \$p) &&
-	test_cmp expect actual
+	test_i18ncmp expect actual
 '
 
 cat >expect <<EOF
@@ -1181,7 +1181,7 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' '
 	set_fake_editor &&
 	FAKE_LINES="1 2 3 4" \
 		git rebase -i --root 2>actual &&
-	test_cmp expect actual &&
+	test_i18ncmp expect actual &&
 	test D = $(git cat-file commit HEAD | sed -ne \$p)
 '
 
@@ -1205,7 +1205,7 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
 	set_fake_editor &&
 	test_must_fail env FAKE_LINES="1 2 4" \
 		git rebase -i --root 2>actual &&
-	test_cmp expect actual &&
+	test_i18ncmp expect actual &&
 	cp .git/rebase-merge/git-rebase-todo.backup \
 		.git/rebase-merge/git-rebase-todo &&
 	FAKE_LINES="1 2 drop 3 4 drop 5" \
@@ -1228,7 +1228,7 @@ test_expect_success 'static check of bad command' '
 	set_fake_editor &&
 	test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
 		git rebase -i --root 2>actual &&
-	test_cmp expect actual &&
+	test_i18ncmp expect actual &&
 	FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
 	git rebase --continue &&
 	test E = $(git cat-file commit HEAD | sed -ne \$p) &&
@@ -1263,7 +1263,7 @@ test_expect_success 'static check of bad SHA-1' '
 	set_fake_editor &&
 	test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
 		git rebase -i --root 2>actual &&
-	test_cmp expect actual &&
+	test_i18ncmp expect actual &&
 	FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
 	git rebase --continue &&
 	test E = $(git cat-file commit HEAD | sed -ne \$p)
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 16/39] i18n: rebase-interactive: mark comments of squash for translation

Mark comment messages of squash/fixup file ($squash_msg) for translation.

Helper functions this_nth_commit_message and skip_nth_commit_message replace the previous method of making the comment messages (such as "This is the 2nd commit message:") aided by nth_string helper function. This step was taken as a workaround to enabled translation of entire sentences. However, doesn't change any text seen in English by the user, except for string "The first commit's message is:" which was changed to match the style of other instances.

The test t3404-rebase-interactive.sh resorts to set_fake_editor which didn't account for GETTEXT_POISON. Fix it by assuming success when we find dummy gettext poison output where was supposed to find the first comment line "This is a combination of $count commits.".

For that same message, use plural aware eval_ngettext instead of eval_gettext, since other languages have more complex plural forms.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-rebase--interactive.sh | 69 +++++++++++++++++++++++++++++++++++++---------
 t/lib-rebase.sh            |  1 +
 2 files changed, 57 insertions(+), 13 deletions(-)
Show changes to 2 files +57 −13

git-rebase--interactive.sh, t/lib-rebase.sh

diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index e132f07..a545d92 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -402,12 +402,52 @@ pick_one_preserving_merges () {
 	esac
 }
 
-nth_string () {
-	case "$1" in
-	*1[0-9]|*[04-9]) echo "$1"th;;
-	*1) echo "$1"st;;
-	*2) echo "$1"nd;;
-	*3) echo "$1"rd;;
+this_nth_commit_message () {
+	n=$1
+	case "$n" in
+	1) gettext "This is the 1st commit message:";;
+	2) gettext "This is the 2nd commit message:";;
+	3) gettext "This is the 3rd commit message:";;
+	4) gettext "This is the 4th commit message:";;
+	5) gettext "This is the 5th commit message:";;
+	6) gettext "This is the 6th commit message:";;
+	7) gettext "This is the 7th commit message:";;
+	8) gettext "This is the 8th commit message:";;
+	9) gettext "This is the 9th commit message:";;
+	10) gettext "This is the 10th commit message:";;
+	# TRANSLATORS: if the language you are translating into
+	# doesn't allow you to compose a sentence in this fashion,
+	# consider translating as if this and the following few strings
+	# were "This is the commit message ${n}:"
+	*1[0-9]|*[04-9]) eval_gettext "This is the \${n}th commit message:";;
+	*1) eval_gettext "This is the \${n}st commit message:";;
+	*2) eval_gettext "This is the \${n}nd commit message:";;
+	*3) eval_gettext "This is the \${n}rd commit message:";;
+	*) eval_gettext "This is the commit message \${n}:";;
+	esac
+}
+skip_nth_commit_message () {
+	n=$1
+	case "$n" in
+	1) gettext "The 1st commit message will be skipped:";;
+	2) gettext "The 2nd commit message will be skipped:";;
+	3) gettext "The 3rd commit message will be skipped:";;
+	4) gettext "The 4th commit message will be skipped:";;
+	5) gettext "The 5th commit message will be skipped:";;
+	6) gettext "The 6th commit message will be skipped:";;
+	7) gettext "The 7th commit message will be skipped:";;
+	8) gettext "The 8th commit message will be skipped:";;
+	9) gettext "The 9th commit message will be skipped:";;
+	10) gettext "The 10th commit message will be skipped:";;
+	# TRANSLATORS: if the language you are translating into
+	# doesn't allow you to compose a sentence in this fashion,
+	# consider translating as if this and the following few strings
+	# were "The commit message ${n} will be skipped:"
+	*1[0-9]|*[04-9]) eval_gettext "The \${n}th commit message will be skipped:";;
+	*1) eval_gettext "The \${n}st commit message will be skipped:";;
+	*2) eval_gettext "The \${n}nd commit message will be skipped:";;
+	*3) eval_gettext "The \${n}rd commit message will be skipped:";;
+	*) eval_gettext "The commit message \${n} will be skipped:";;
 	esac
 }
 
@@ -415,20 +455,23 @@ update_squash_messages () {
 	if test -f "$squash_msg"; then
 		mv "$squash_msg" "$squash_msg".bak || exit
 		count=$(($(sed -n \
-			-e "1s/^. This is a combination of \(.*\) commits\./\1/p" \
+			-e "1s/^$comment_char.*\([0-9][0-9]*\).*/\1/p" \
 			-e "q" < "$squash_msg".bak)+1))
 		{
-			printf '%s\n' "$comment_char This is a combination of $count commits."
+			printf '%s\n' "$comment_char $(eval_ngettext \
+				"This is a combination of \$count commit." \
+				"This is a combination of \$count commits." \
+				$count)"
 			sed -e 1d -e '2,/^./{
 				/^$/d
 			}' <"$squash_msg".bak
 		} >"$squash_msg"
 	else
-		commit_message HEAD > "$fixup_msg" || die "Cannot write $fixup_msg"
+		commit_message HEAD > "$fixup_msg" || die "$(gettext "Cannot write \$fixup_msg")"
 		count=2
 		{
-			printf '%s\n' "$comment_char This is a combination of 2 commits."
-			printf '%s\n' "$comment_char The first commit's message is:"
+			printf '%s\n' "$comment_char $(gettext "This is a combination of 2 commits.")"
+			printf '%s\n' "$comment_char $(gettext "This is the 1st commit message:")"
 			echo
 			cat "$fixup_msg"
 		} >"$squash_msg"
@@ -437,13 +480,13 @@ update_squash_messages () {
 	squash)
 		rm -f "$fixup_msg"
 		echo
-		printf '%s\n' "$comment_char This is the $(nth_string $count) commit message:"
+		printf '%s\n' "$comment_char $(this_nth_commit_message $count)"
 		echo
 		commit_message $2
 		;;
 	fixup)
 		echo
-		printf '%s\n' "$comment_char The $(nth_string $count) commit message will be skipped:"
+		printf '%s\n' "$comment_char $(skip_nth_commit_message $count)"
 		echo
 		# Change the space after the comment character to TAB:
 		commit_message $2 | git stripspace --comment-lines | sed -e 's/ /	/'
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index 9a96e15..25a77ee 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -29,6 +29,7 @@ set_fake_editor () {
 	*/COMMIT_EDITMSG)
 		test -z "$EXPECT_HEADER_COUNT" ||
 			test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" ||
+			test "# # GETTEXT POISON #" = "$(sed -n '1p' < "$1")" ||
 			exit
 		test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
 		test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 17/39] i18n: setup: mark strings for translation

Update tests that compare the strings newly marked for translation to succeed when running under GETTEXT_POISON.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 setup.c                        | 16 ++++++++--------
 t/t1506-rev-parse-diagnosis.sh |  2 +-
 t/t4208-log-magic-pathspec.sh  |  4 ++--
 3 files changed, 11 insertions(+), 11 deletions(-)
Show changes to 3 files +11 −11

setup.c, t/t1506-rev-parse-diagnosis.sh, t/t4208-log-magic-pathspec.sh

diff --git a/setup.c b/setup.c
index c86bf5c..6d0e0c9 100644
--- a/setup.c
+++ b/setup.c
@@ -157,8 +157,8 @@ static void NORETURN die_verify_filename(const char *prefix,
 					 int diagnose_misspelt_rev)
 {
 	if (!diagnose_misspelt_rev)
-		die("%s: no such path in the working tree.\n"
-		    "Use 'git <command> -- <path>...' to specify paths that do not exist locally.",
+		die(_("%s: no such path in the working tree.\n"
+		      "Use 'git <command> -- <path>...' to specify paths that do not exist locally."),
 		    arg);
 	/*
 	 * Saying "'(icase)foo' does not exist in the index" when the
@@ -170,9 +170,9 @@ static void NORETURN die_verify_filename(const char *prefix,
 		maybe_die_on_misspelt_object_name(arg, prefix);
 
 	/* ... or fall back the most general message. */
-	die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
-	    "Use '--' to separate paths from revisions, like this:\n"
-	    "'git <command> [<revision>...] -- [<file>...]'", arg);
+	die(_("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+	      "Use '--' to separate paths from revisions, like this:\n"
+	      "'git <command> [<revision>...] -- [<file>...]'"), arg);
 
 }
 
@@ -220,9 +220,9 @@ void verify_non_filename(const char *prefix, const char *arg)
 		return; /* flag */
 	if (!check_filename(prefix, arg))
 		return;
-	die("ambiguous argument '%s': both revision and filename\n"
-	    "Use '--' to separate paths from revisions, like this:\n"
-	    "'git <command> [<revision>...] -- [<file>...]'", arg);
+	die(_("ambiguous argument '%s': both revision and filename\n"
+	      "Use '--' to separate paths from revisions, like this:\n"
+	      "'git <command> [<revision>...] -- [<file>...]'"), arg);
 }
 
 int get_common_dir(struct strbuf *sb, const char *gitdir)
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index 86c2ff2..79a0251 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -106,7 +106,7 @@ test_expect_success 'incorrect revision id' '
 	test_must_fail git rev-parse foobar:file.txt 2>error &&
 	grep "Invalid object name '"'"'foobar'"'"'." error &&
 	test_must_fail git rev-parse foobar 2> error &&
-	grep "unknown revision or path not in the working tree." error
+	test_i18ngrep "unknown revision or path not in the working tree." error
 '
 
 test_expect_success 'incorrect file in sha1:path' '
diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh
index d8f23f4..001343e 100755
--- a/t/t4208-log-magic-pathspec.sh
+++ b/t/t4208-log-magic-pathspec.sh
@@ -18,7 +18,7 @@ test_expect_success '"git log :/" should not be ambiguous' '
 test_expect_success '"git log :/a" should be ambiguous (applied both rev and worktree)' '
 	: >a &&
 	test_must_fail git log :/a 2>error &&
-	grep ambiguous error
+	test_i18ngrep ambiguous error
 '
 
 test_expect_success '"git log :/a -- " should not be ambiguous' '
@@ -31,7 +31,7 @@ test_expect_success '"git log -- :/a" should not be ambiguous' '
 
 test_expect_success '"git log :" should be ambiguous' '
 	test_must_fail git log : 2>error &&
-	grep ambiguous error
+	test_i18ngrep ambiguous error
 '
 
 test_expect_success 'git log -- :' '
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 18/39] tests: use test_i18n* functions to suppress false positives

The test functions test_i18ncmp and test_i18ngrep pretend success if run under GETTEXT_POISON. By using those functions to test output which is correctly marked as translatable, enables one to detect if the strings newly marked for translation are from plumbing output. If they are indeed from plumbing, the test would fail, and the string should be unmarked, since it is not seen by users.

Thus, it is productive to not have false positives when running the test under GETTEXT_POISON. This commit replaces normal test functions by their i18n aware variants in use-cases know to be correctly marked for translation, suppressing false positives.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 t/t0008-ignores.sh                |  4 ++--
 t/t1300-repo-config.sh            |  8 ++++----
 t/t1307-config-blob.sh            |  5 +----
 t/t1308-config-set.sh             |  4 ++--
 t/t1400-update-ref.sh             |  2 +-
 t/t2010-checkout-ambiguous.sh     |  2 +-
 t/t2018-checkout-branch.sh        |  2 +-
 t/t3200-branch.sh                 |  6 +++---
 t/t3201-branch-contains.sh        |  2 +-
 t/t3320-notes-merge-worktrees.sh  |  2 +-
 t/t5505-remote.sh                 |  2 +-
 t/t5510-fetch.sh                  |  2 +-
 t/t5523-push-upstream.sh          |  4 ++--
 t/t5536-fetch-conflicts.sh        |  4 ++--
 t/t6301-for-each-ref-errors.sh    | 10 +++++-----
 t/t7063-status-untracked-cache.sh |  2 +-
 t/t7102-reset.sh                  |  4 ++--
 t/t7400-submodule-basic.sh        |  2 +-
 t/t7403-submodule-sync.sh         |  4 ++--
 t/t7406-submodule-update.sh       | 10 +++++-----
 t/t7508-status.sh                 |  4 ++--
 21 files changed, 41 insertions(+), 44 deletions(-)
Show changes to 21 files +41 −44

t/t0008-ignores.sh, t/t1300-repo-config.sh, t/t1307-config-blob.sh, t/t1308-config-set.sh, t/t1400-update-ref.sh, t/t2010-checkout-ambiguous.sh, t/t2018-checkout-branch.sh, t/t3200-branch.sh, t/t3201-branch-contains.sh, t/t3320-notes-merge-worktrees.sh, t/t5505-remote.sh, t/t5510-fetch.sh, t/t5523-push-upstream.sh, t/t5536-fetch-conflicts.sh, t/t6301-for-each-ref-errors.sh, t/t7063-status-untracked-cache.sh, t/t7102-reset.sh, t/t7400-submodule-basic.sh, t/t7403-submodule-sync.sh, t/t7406-submodule-update.sh, t/t7508-status.sh

diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
index b425f3a..d27f438 100755
--- a/t/t0008-ignores.sh
+++ b/t/t0008-ignores.sh
@@ -34,7 +34,7 @@ expect_from_stdin () {
 test_stderr () {
 	expected="$1"
 	expect_in stderr "$1" &&
-	test_cmp "$HOME/expected-stderr" "$HOME/stderr"
+	test_i18ncmp "$HOME/expected-stderr" "$HOME/stderr"
 }
 
 broken_c_unquote () {
@@ -47,7 +47,7 @@ broken_c_unquote_verbose () {
 
 stderr_contains () {
 	regexp="$1"
-	if grep "$regexp" "$HOME/stderr"
+	if test_i18ngrep "$regexp" "$HOME/stderr"
 	then
 		return 0
 	else
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index d934a24..923bfc5 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -886,7 +886,7 @@ test_expect_success !MINGW 'get --path copes with unset $HOME' '
 		git config --get --path path.normal >>result &&
 		git config --get --path path.trailingtilde >>result
 	) &&
-	grep "[Ff]ailed to expand.*~/" msg &&
+	test_i18ngrep "[Ff]ailed to expand.*~/" msg &&
 	test_cmp expect result
 '
 
@@ -1126,7 +1126,7 @@ test_expect_success 'barf on syntax error' '
 	key garbage
 	EOF
 	test_must_fail git config --get section.key >actual 2>error &&
-	grep " line 3 " error
+	test_i18ngrep " line 3 " error
 '
 
 test_expect_success 'barf on incomplete section header' '
@@ -1136,7 +1136,7 @@ test_expect_success 'barf on incomplete section header' '
 	key = value
 	EOF
 	test_must_fail git config --get section.key >actual 2>error &&
-	grep " line 2 " error
+	test_i18ngrep " line 2 " error
 '
 
 test_expect_success 'barf on incomplete string' '
@@ -1146,7 +1146,7 @@ test_expect_success 'barf on incomplete string' '
 	key = "value string
 	EOF
 	test_must_fail git config --get section.key >actual 2>error &&
-	grep " line 3 " error
+	test_i18ngrep " line 3 " error
 '
 
 test_expect_success 'urlmatch' '
diff --git a/t/t1307-config-blob.sh b/t/t1307-config-blob.sh
index 3c6791e..eed31ff 100755
--- a/t/t1307-config-blob.sh
+++ b/t/t1307-config-blob.sh
@@ -61,10 +61,7 @@ test_expect_success 'parse errors in blobs are properly attributed' '
 	git commit -m broken &&
 
 	test_must_fail git config --blob=HEAD:config some.value 2>err &&
-
-	# just grep for our token as the exact error message is likely to
-	# change or be internationalized
-	grep "HEAD:config" err
+	test_i18ngrep "HEAD:config" err
 '
 
 test_expect_success 'can parse blob ending with CR' '
diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh
index 005d66d..cd62063 100755
--- a/t/t1308-config-set.sh
+++ b/t/t1308-config-set.sh
@@ -197,14 +197,14 @@ test_expect_success 'proper error on error in default config files' '
 	echo "[" >>.git/config &&
 	echo "fatal: bad config line 34 in file .git/config" >expect &&
 	test_expect_code 128 test-config get_value foo.bar 2>actual &&
-	test_cmp expect actual
+	test_i18ncmp expect actual
 '
 
 test_expect_success 'proper error on error in custom config files' '
 	echo "[" >>syntax-error &&
 	echo "fatal: bad config line 1 in file syntax-error" >expect &&
 	test_expect_code 128 test-config configset_get_value foo.bar syntax-error 2>actual &&
-	test_cmp expect actual
+	test_i18ncmp expect actual
 '
 
 test_expect_success 'check line errors for malformed values' '
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index af1b20d..75fa654 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -361,7 +361,7 @@ test_expect_success 'stdin test setup' '
 
 test_expect_success '-z fails without --stdin' '
 	test_must_fail git update-ref -z $m $m $m 2>err &&
-	grep "usage: git update-ref" err
+	test_i18ngrep "usage: git update-ref" err
 '
 
 test_expect_success 'stdin works with no input' '
diff --git a/t/t2010-checkout-ambiguous.sh b/t/t2010-checkout-ambiguous.sh
index 87bdf9c..e76e84a 100755
--- a/t/t2010-checkout-ambiguous.sh
+++ b/t/t2010-checkout-ambiguous.sh
@@ -49,7 +49,7 @@ test_expect_success 'disambiguate checking out from a tree-ish' '
 
 test_expect_success 'accurate error message with more than one ref' '
 	test_must_fail git checkout HEAD master -- 2>actual &&
-	grep 2 actual &&
+	test_i18ngrep 2 actual &&
 	test_i18ngrep "one reference expected, 2 given" actual
 '
 
diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh
index 2741262..2131fb2 100755
--- a/t/t2018-checkout-branch.sh
+++ b/t/t2018-checkout-branch.sh
@@ -124,7 +124,7 @@ test_expect_success 'checkout -b to @{-1} fails with the right branch name' '
 	git checkout branch2 &&
 	echo  >expect "fatal: A branch named '\''branch1'\'' already exists." &&
 	test_must_fail git checkout -b @{-1} 2>actual &&
-	test_cmp expect actual
+	test_i18ncmp expect actual
 '
 
 test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index f3e3b6c..ac9c764 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -550,7 +550,7 @@ If you wanted to make '"'master'"' track '"'origin/master'"', do this:
     git branch -d origin/master
     git branch --set-upstream-to origin/master
 EOF
-	test_cmp expected actual
+	test_i18ncmp expected actual
 '
 
 test_expect_success '--set-upstream with two args only shows the deprecation message' '
@@ -559,7 +559,7 @@ test_expect_success '--set-upstream with two args only shows the deprecation mes
 	cat >expected <<EOF &&
 The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
 EOF
-	test_cmp expected actual
+	test_i18ncmp expected actual
 '
 
 test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' '
@@ -568,7 +568,7 @@ test_expect_success '--set-upstream with one arg only shows the deprecation mess
 	cat >expected <<EOF &&
 The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
 EOF
-	test_cmp expected actual
+	test_i18ncmp expected actual
 '
 
 test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh
index 912a663..7f3ec47 100755
--- a/t/t3201-branch-contains.sh
+++ b/t/t3201-branch-contains.sh
@@ -156,7 +156,7 @@ test_expect_success 'branch --merged with --verbose' '
 	* topic  2c939f4 [ahead 1] foo
 	  zzz    c77a0a9 second on master
 	EOF
-	test_cmp expect actual
+	test_i18ncmp expect actual
 '
 
 test_done
diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh
index 1f71d58..522157b 100755
--- a/t/t3320-notes-merge-worktrees.sh
+++ b/t/t3320-notes-merge-worktrees.sh
@@ -52,7 +52,7 @@ test_expect_success 'merge z into y while mid-merge in another workdir fails' '
 		cd worktree &&
 		git config core.notesRef refs/notes/y &&
 		test_must_fail git notes merge z 2>err &&
-		grep "A notes merge into refs/notes/y is already in-progress at" err
+		test_i18ngrep "A notes merge into refs/notes/y is already in-progress at" err
 	) &&
 	test_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF
 '
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index dd2e6ce..8198d8e 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -1182,7 +1182,7 @@ test_expect_success 'extra args: setup' '
 test_extra_arg () {
 	test_expect_success "extra args: $*" "
 		test_must_fail git remote $* bogus_extra_arg 2>actual &&
-		grep '^usage:' actual
+		test_i18ngrep '^usage:' actual
 	"
 }
 
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 454d896..88076da 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -644,7 +644,7 @@ test_expect_success 'fetch --prune prints the remotes url' '
 		git fetch --prune origin 2>&1 | head -n1 >../actual
 	) &&
 	echo "From ${D}/." >expect &&
-	test_cmp expect actual
+	test_i18ncmp expect actual
 '
 
 test_expect_success 'branchname D/F conflict resolved by --prune' '
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
index 3683df1..4a7b98b 100755
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
@@ -75,7 +75,7 @@ test_expect_success TTY 'progress messages go to tty' '
 	ensure_fresh_upstream &&
 
 	test_terminal git push -u upstream master >out 2>err &&
-	grep "Writing objects" err
+	test_i18ngrep "Writing objects" err
 '
 
 test_expect_success 'progress messages do not go to non-tty' '
@@ -91,7 +91,7 @@ test_expect_success 'progress messages go to non-tty (forced)' '
 
 	# force progress messages to stderr, even though it is non-tty
 	git push -u --progress upstream master >out 2>err &&
-	grep "Writing objects" err
+	test_i18ngrep "Writing objects" err
 '
 
 test_expect_success TTY 'push -q suppresses progress' '
diff --git a/t/t5536-fetch-conflicts.sh b/t/t5536-fetch-conflicts.sh
index 6c5d3a4..2e42cf3 100755
--- a/t/t5536-fetch-conflicts.sh
+++ b/t/t5536-fetch-conflicts.sh
@@ -22,8 +22,8 @@ verify_stderr () {
 	cat >expected &&
 	# We're not interested in the error
 	# "fatal: The remote end hung up unexpectedly":
-	grep -E '^(fatal|warning):' <error | grep -v 'hung up' >actual | sort &&
-	test_cmp expected actual
+	test_i18ngrep -E '^(fatal|warning):' <error | grep -v 'hung up' >actual | sort &&
+	test_i18ncmp expected actual
 }
 
 test_expect_success 'setup' '
diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh
index cdb67a0..c734ce2 100755
--- a/t/t6301-for-each-ref-errors.sh
+++ b/t/t6301-for-each-ref-errors.sh
@@ -20,8 +20,8 @@ test_expect_success 'Broken refs are reported correctly' '
 	test_when_finished "rm -f .git/$r" &&
 	echo "warning: ignoring broken ref $r" >broken-err &&
 	git for-each-ref >out 2>err &&
-	test_cmp full-list out &&
-	test_cmp broken-err err
+	test_i18ncmp full-list out &&
+	test_i18ncmp broken-err err
 '
 
 test_expect_success 'NULL_SHA1 refs are reported correctly' '
@@ -31,10 +31,10 @@ test_expect_success 'NULL_SHA1 refs are reported correctly' '
 	echo "warning: ignoring broken ref $r" >zeros-err &&
 	git for-each-ref >out 2>err &&
 	test_cmp full-list out &&
-	test_cmp zeros-err err &&
+	test_i18ncmp zeros-err err &&
 	git for-each-ref --format="%(objectname) %(refname)" >brief-out 2>brief-err &&
 	test_cmp brief-list brief-out &&
-	test_cmp zeros-err brief-err
+	test_i18ncmp zeros-err brief-err
 '
 
 test_expect_success 'Missing objects are reported correctly' '
@@ -43,7 +43,7 @@ test_expect_success 'Missing objects are reported correctly' '
 	test_when_finished "rm -f .git/$r" &&
 	echo "fatal: missing object $MISSING for $r" >missing-err &&
 	test_must_fail git for-each-ref 2>err &&
-	test_cmp missing-err err &&
+	test_i18ncmp missing-err err &&
 	(
 		cat brief-list &&
 		echo "$MISSING $r"
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index a971884..38b3890 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -643,7 +643,7 @@ test_expect_success 'test ident field is working' '
 	cp -R done dthree dtwo four three ../other_worktree &&
 	GIT_WORK_TREE=../other_worktree git status 2>../err &&
 	echo "warning: Untracked cache is disabled on this system or location." >../expect &&
-	test_cmp ../expect ../err
+	test_i18ncmp ../expect ../err
 '
 
 test_done
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
index 98bcfe2..86f23be 100755
--- a/t/t7102-reset.sh
+++ b/t/t7102-reset.sh
@@ -66,14 +66,14 @@ test_expect_success 'reset --hard message' '
 	hex=$(git log -1 --format="%h") &&
 	git reset --hard > .actual &&
 	echo HEAD is now at $hex $(commit_msg) > .expected &&
-	test_cmp .expected .actual
+	test_i18ncmp .expected .actual
 '
 
 test_expect_success 'reset --hard message (ISO8859-1 logoutputencoding)' '
 	hex=$(git log -1 --format="%h") &&
 	git -c "i18n.logOutputEncoding=$test_encoding" reset --hard > .actual &&
 	echo HEAD is now at $hex $(commit_msg $test_encoding) > .expected &&
-	test_cmp .expected .actual
+	test_i18ncmp .expected .actual
 '
 
 >.diff_expect
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 3570f7b..b77cce8 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -942,7 +942,7 @@ test_expect_success 'submodule deinit from subdirectory' '
 		cd sub &&
 		git submodule deinit ../init >../output
 	) &&
-	grep "\\.\\./init" output &&
+	test_i18ngrep "\\.\\./init" output &&
 	test -z "$(git config --get-regexp "submodule\.example\.")" &&
 	test -n "$(git config --get-regexp "submodule\.example2\.")" &&
 	test -f example2/.git &&
diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh
index 79bc135..b8690b1 100755
--- a/t/t7403-submodule-sync.sh
+++ b/t/t7403-submodule-sync.sh
@@ -157,7 +157,7 @@ test_expect_success '"git submodule sync" should update submodule URLs - subdire
 		cd sub &&
 		git submodule sync >../../output
 	) &&
-	grep "\\.\\./submodule" output &&
+	test_i18ngrep "\\.\\./submodule" output &&
 	test -d "$(
 		cd super-clone/submodule &&
 		git config remote.origin.url
@@ -188,7 +188,7 @@ test_expect_success '"git submodule sync --recursive" should update all submodul
 		cd sub &&
 		git submodule sync --recursive >../../output
 	) &&
-	grep "\\.\\./submodule/sub-submodule" output &&
+	test_i18ngrep "\\.\\./submodule/sub-submodule" output &&
 	test -d "$(
 		cd super-clone/submodule &&
 		git config remote.origin.url
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 5f27879..88e9750 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -136,8 +136,8 @@ test_expect_success 'submodule update --init --recursive from subdirectory' '
 	 cd tmp &&
 	 git submodule update --init --recursive ../super >../../actual 2>../../actual2
 	) &&
-	test_cmp expect actual &&
-	test_cmp expect2 actual2
+	test_i18ncmp expect actual &&
+	test_i18ncmp expect2 actual2
 '
 
 apos="'";
@@ -370,7 +370,7 @@ test_expect_success 'submodule update - command in .git/config catches failure'
 	(cd super &&
 	 test_must_fail git submodule update submodule 2>../actual
 	) &&
-	test_cmp actual expect
+	test_i18ncmp actual expect
 '
 
 cat << EOF >expect
@@ -388,7 +388,7 @@ test_expect_success 'submodule update - command in .git/config catches failure -
 	 mkdir tmp && cd tmp &&
 	 test_must_fail git submodule update ../submodule 2>../../actual
 	) &&
-	test_cmp actual expect
+	test_i18ncmp actual expect
 '
 
 cat << EOF >expect
@@ -408,7 +408,7 @@ test_expect_success 'recursive submodule update - command in .git/config catches
 	 mkdir -p tmp && cd tmp &&
 	 test_must_fail git submodule update --recursive ../super 2>../../actual
 	) &&
-	test_cmp actual expect
+	test_i18ncmp actual expect
 '
 
 test_expect_success 'submodule init does not copy command into .git/config' '
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index c3ed7cb..b3bdd16 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1377,7 +1377,7 @@ EOF
 	git config --add -f .gitmodules submodule.subname.ignore all &&
 	git config --add -f .gitmodules submodule.subname.path sm &&
 	git status > output &&
-	test_cmp expect output &&
+	test_i18ncmp expect output &&
 	git config -f .gitmodules  --remove-section submodule.subname
 '
 
@@ -1387,7 +1387,7 @@ test_expect_success '.git/config ignore=all suppresses unstaged submodule summar
 	git config --add submodule.subname.ignore all &&
 	git config --add submodule.subname.path sm &&
 	git status > output &&
-	test_cmp expect output &&
+	test_i18ncmp expect output &&
 	git config --remove-section submodule.subname &&
 	git config -f .gitmodules  --remove-section submodule.subname
 '
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 19/39] tests: unpack-trees: update to use test_i18n* functions

Use functions test_i18ncmp and test_i18ngrep to successfully pass tests running under GETTEXT_POISON.

The output strings compared to in these test were marked for translation in ed47fdf ("i18n: unpack-trees: mark strings for translation", 2016-04-09) and later improved in 2e3926b ("i18n: unpack-trees: avoid substituting only a verb in sentences", 2016-05-12).

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 t/t1011-read-tree-sparse-checkout.sh | 2 +-
 t/t3400-rebase.sh                    | 4 ++--
 t/t3404-rebase-interactive.sh        | 4 ++--
 t/t7607-merge-overwrite.sh           | 2 +-
 4 files changed, 6 insertions(+), 6 deletions(-)
Show changes to 4 files +6 −6

t/t1011-read-tree-sparse-checkout.sh, t/t3400-rebase.sh, t/t3404-rebase-interactive.sh, t/t7607-merge-overwrite.sh

diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index 0c74bee..3583105 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -247,7 +247,7 @@ error: The following untracked working tree files would be overwritten by checko
 Please move or remove them before you can switch branches.
 Aborting
 EOF
-	test_cmp expected actual
+	test_i18ncmp expected actual
 '
 
 test_expect_success 'checkout without --ignore-skip-worktree-bits' '
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 47b5682..f5fd15e 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -136,8 +136,8 @@ test_expect_success 'setup: recover' '
 test_expect_success 'Show verbose error when HEAD could not be detached' '
 	>B &&
 	test_must_fail git rebase topic 2>output.err >output.out &&
-	grep "The following untracked working tree files would be overwritten by checkout:" output.err &&
-	grep B output.err
+	test_i18ngrep "The following untracked working tree files would be overwritten by checkout:" output.err &&
+	test_i18ngrep B output.err
 '
 rm -f B
 
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index f4ccd10..8ac1868 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -219,9 +219,9 @@ test_expect_success 'abort with error when new base cannot be checked out' '
 	git commit -m "remove file in base" &&
 	set_fake_editor &&
 	test_must_fail git rebase -i master > output 2>&1 &&
-	grep "The following untracked working tree files would be overwritten by checkout:" \
+	test_i18ngrep "The following untracked working tree files would be overwritten by checkout:" \
 		output &&
-	grep "file1" output &&
+	test_i18ngrep "file1" output &&
 	test_path_is_missing .git/rebase-merge &&
 	git reset --hard HEAD^
 '
diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh
index 758a623..e8ec54c 100755
--- a/t/t7607-merge-overwrite.sh
+++ b/t/t7607-merge-overwrite.sh
@@ -125,7 +125,7 @@ test_expect_success 'will not overwrite untracked file in leading path' '
 	cp important sub &&
 	cp important sub2 &&
 	test_must_fail git merge sub 2>out &&
-	test_cmp out expect &&
+	test_i18ncmp out expect &&
 	test_path_is_missing .git/MERGE_HEAD &&
 	test_cmp important sub &&
 	test_cmp important sub2 &&
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 20/39] t9003: become resilient to GETTEXT_POISON

The test t9003-help-autocorrect.sh fails when run under GETTEXT_POISON, because it's expecting to filter out the original output. Accommodate gettext poison case by also filtering out the default simulated output.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 t/t9003-help-autocorrect.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to t/t9003-help-autocorrect.sh +2 −2
diff --git a/t/t9003-help-autocorrect.sh b/t/t9003-help-autocorrect.sh
index dfe95c9..b1c7919 100755
--- a/t/t9003-help-autocorrect.sh
+++ b/t/t9003-help-autocorrect.sh
@@ -31,10 +31,10 @@ test_expect_success 'autocorrect showing candidates' '
 	git config help.autocorrect 0 &&
 
 	test_must_fail git lfg 2>actual &&
-	sed -e "1,/^Did you mean this/d" actual | grep lgf &&
+	grep "^	lgf" actual &&
 
 	test_must_fail git distimdist 2>actual &&
-	sed -e "1,/^Did you mean this/d" actual | grep distimdistim
+	grep "^	distimdistim" actual
 '
 
 test_expect_success 'autocorrect running commands' '
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 21/39] t4153: fix negated test_i18ngrep call

The function test_i18ngrep fakes success when run under GETTEXT_POISON. Hence, running in the following manner will always fail under gettext poison:

	! test_i18ngrep expected actual
Use correct syntax: test_i18ngrep ! expected actual

For other instance of this issue see 41ca19b ("tests: fix negated test_i18ngrep calls", 2014-08-13).

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 t/t4153-am-resume-override-opts.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to t/t4153-am-resume-override-opts.sh +1 −1
diff --git a/t/t4153-am-resume-override-opts.sh b/t/t4153-am-resume-override-opts.sh
index 7c013d8..8ea22d1 100755
--- a/t/t4153-am-resume-override-opts.sh
+++ b/t/t4153-am-resume-override-opts.sh
@@ -53,7 +53,7 @@ test_expect_success '--no-quiet overrides --quiet' '
 	# Applying side1 will be quiet.
 	test_must_fail git am --quiet side[123].eml >out &&
 	test_path_is_dir .git/rebase-apply &&
-	! test_i18ngrep "^Applying: " out &&
+	test_i18ngrep ! "^Applying: " out &&
 	echo side1 >file &&
 	git add file &&
 
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 22/39] t5523: use test_i18ngrep for negation

Replace the first form with the second one:
	! grep expected actual
	test_i18ngrep ! expected actual

The latter syntax is supported by test_i18ngrep defined in t/test-lib.sh.

Although the test already passes whether GETTEXT_POSION is enabled, use the i18n grep variant for the sake of consistency and also to make obvious that those strings are subject to i18n.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 t/t5523-push-upstream.sh | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to t/t5523-push-upstream.sh +4 −4
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
index 4a7b98b..d6981ba 100755
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
@@ -83,7 +83,7 @@ test_expect_success 'progress messages do not go to non-tty' '
 
 	# skip progress messages, since stderr is non-tty
 	git push -u upstream master >out 2>err &&
-	! grep "Writing objects" err
+	test_i18ngrep ! "Writing objects" err
 '
 
 test_expect_success 'progress messages go to non-tty (forced)' '
@@ -98,15 +98,15 @@ test_expect_success TTY 'push -q suppresses progress' '
 	ensure_fresh_upstream &&
 
 	test_terminal git push -u -q upstream master >out 2>err &&
-	! grep "Writing objects" err
+	test_i18ngrep ! "Writing objects" err
 '
 
 test_expect_success TTY 'push --no-progress suppresses progress' '
 	ensure_fresh_upstream &&
 
 	test_terminal git push -u --no-progress upstream master >out 2>err &&
-	! grep "Unpacking objects" err &&
-	! grep "Writing objects" err
+	test_i18ngrep ! "Unpacking objects" err &&
+	test_i18ngrep ! "Writing objects" err
 '
 
 test_expect_success TTY 'quiet push' '
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 23/39] i18n: bisect: mark strings for translation

In the last message, involving Q_(), try to mark the message in such way that is suited for RTL (Right to Left) languages.

Update test t6030-bisect-porcelain.sh to reflect the changes.
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 bisect.c                    | 56 +++++++++++++++++++++++++--------------------
 t/t6030-bisect-porcelain.sh | 22 +++++++++---------
 2 files changed, 42 insertions(+), 36 deletions(-)
Show changes to 2 files +42 −36

bisect.c, t/t6030-bisect-porcelain.sh

diff --git a/bisect.c b/bisect.c
index 6d93edb..a8713a8 100644
--- a/bisect.c
+++ b/bisect.c
@@ -438,12 +438,12 @@ static void read_bisect_paths(struct argv_array *array)
 	FILE *fp = fopen(filename, "r");
 
 	if (!fp)
-		die_errno("Could not open file '%s'", filename);
+		die_errno(_("Could not open file '%s'"), filename);
 
 	while (strbuf_getline_lf(&str, fp) != EOF) {
 		strbuf_trim(&str);
 		if (sq_dequote_to_argv_array(str.buf, array))
-			die("Badly quoted content in file '%s': %s",
+			die(_("Badly quoted content in file '%s': %s"),
 			    filename, str.buf);
 	}
 
@@ -649,7 +649,7 @@ static void exit_if_skipped_commits(struct commit_list *tried,
 	print_commit_list(tried, "%s\n", "%s\n");
 	if (bad)
 		printf("%s\n", oid_to_hex(bad));
-	printf("We cannot bisect more!\n");
+	printf(_("We cannot bisect more!\n"));
 	exit(2);
 }
 
@@ -702,7 +702,7 @@ static struct commit *get_commit_reference(const unsigned char *sha1)
 {
 	struct commit *r = lookup_commit_reference(sha1);
 	if (!r)
-		die("Not a valid commit name %s", sha1_to_hex(sha1));
+		die(_("Not a valid commit name %s"), sha1_to_hex(sha1));
 	return r;
 }
 
@@ -726,27 +726,27 @@ static void handle_bad_merge_base(void)
 		char *bad_hex = oid_to_hex(current_bad_oid);
 		char *good_hex = join_sha1_array_hex(&good_revs, ' ');
 		if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
-			fprintf(stderr, "The merge base %s is bad.\n"
+			fprintf(stderr, _("The merge base %s is bad.\n"
 				"This means the bug has been fixed "
-				"between %s and [%s].\n",
+				"between %s and [%s].\n"),
 				bad_hex, bad_hex, good_hex);
 		} else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
-			fprintf(stderr, "The merge base %s is new.\n"
+			fprintf(stderr, _("The merge base %s is new.\n"
 				"The property has changed "
-				"between %s and [%s].\n",
+				"between %s and [%s].\n"),
 				bad_hex, bad_hex, good_hex);
 		} else {
-			fprintf(stderr, "The merge base %s is %s.\n"
+			fprintf(stderr, _("The merge base %s is %s.\n"
 				"This means the first '%s' commit is "
-				"between %s and [%s].\n",
+				"between %s and [%s].\n"),
 				bad_hex, term_bad, term_good, bad_hex, good_hex);
 		}
 		exit(3);
 	}
 
-	fprintf(stderr, "Some %s revs are not ancestor of the %s rev.\n"
+	fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n"
 		"git bisect cannot work properly in this case.\n"
-		"Maybe you mistook %s and %s revs?\n",
+		"Maybe you mistook %s and %s revs?\n"),
 		term_good, term_bad, term_good, term_bad);
 	exit(1);
 }
@@ -757,11 +757,11 @@ static void handle_skipped_merge_base(const unsigned char *mb)
 	char *bad_hex = sha1_to_hex(current_bad_oid->hash);
 	char *good_hex = join_sha1_array_hex(&good_revs, ' ');
 
-	warning("the merge base between %s and [%s] "
+	warning(_("the merge base between %s and [%s] "
 		"must be skipped.\n"
 		"So we cannot be sure the first %s commit is "
 		"between %s and %s.\n"
-		"We continue anyway.",
+		"We continue anyway."),
 		bad_hex, good_hex, term_bad, mb_hex, bad_hex);
 	free(good_hex);
 }
@@ -792,7 +792,7 @@ static void check_merge_bases(int no_checkout)
 		} else if (0 <= sha1_array_lookup(&skipped_revs, mb)) {
 			handle_skipped_merge_base(mb);
 		} else {
-			printf("Bisecting: a merge base must be tested\n");
+			printf(_("Bisecting: a merge base must be tested\n"));
 			exit(bisect_checkout(mb, no_checkout));
 		}
 	}
@@ -843,7 +843,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
 	int fd;
 
 	if (!current_bad_oid)
-		die("a %s revision is needed", term_bad);
+		die(_("a %s revision is needed"), term_bad);
 
 	/* Check if file BISECT_ANCESTORS_OK exists. */
 	if (!stat(filename, &st) && S_ISREG(st.st_mode))
@@ -860,7 +860,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
 	/* Create file BISECT_ANCESTORS_OK. */
 	fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
 	if (fd < 0)
-		warning_errno("could not create file '%s'",
+		warning_errno(_("could not create file '%s'"),
 			      filename);
 	else
 		close(fd);
@@ -910,7 +910,7 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
 			*read_good = "good";
 			return;
 		} else {
-			die_errno("could not read file '%s'", filename);
+			die_errno(_("could not read file '%s'"), filename);
 		}
 	} else {
 		strbuf_getline_lf(&str, fp);
@@ -936,10 +936,11 @@ int bisect_next_all(const char *prefix, int no_checkout)
 	struct commit_list *tried;
 	int reaches = 0, all = 0, nr, steps;
 	const unsigned char *bisect_rev;
+	char steps_msg[32];
 
 	read_bisect_terms(&term_bad, &term_good);
 	if (read_bisect_refs())
-		die("reading bisect refs failed");
+		die(_("reading bisect refs failed"));
 
 	check_good_are_ancestors_of_bad(prefix, no_checkout);
 
@@ -959,7 +960,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
 		 */
 		exit_if_skipped_commits(tried, NULL);
 
-		printf("%s was both %s and %s\n",
+		printf(_("%s was both %s and %s\n"),
 		       oid_to_hex(current_bad_oid),
 		       term_good,
 		       term_bad);
@@ -967,8 +968,8 @@ int bisect_next_all(const char *prefix, int no_checkout)
 	}
 
 	if (!all) {
-		fprintf(stderr, "No testable commit found.\n"
-			"Maybe you started with bad path parameters?\n");
+		fprintf(stderr, _("No testable commit found.\n"
+			"Maybe you started with bad path parameters?\n"));
 		exit(4);
 	}
 
@@ -985,9 +986,14 @@ int bisect_next_all(const char *prefix, int no_checkout)
 
 	nr = all - reaches - 1;
 	steps = estimate_bisect_steps(all);
-	printf("Bisecting: %d revision%s left to test after this "
-	       "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
-	       steps, (steps == 1 ? "" : "s"));
+	xsnprintf(steps_msg, sizeof(steps_msg),
+		  Q_("(roughly %d step)", "(roughly %d steps)", steps),
+		  steps);
+	/* TRANSLATORS: the last %s will be replaced with
+	   "(roughly %d steps)" translation */
+	printf(Q_("Bisecting: %d revision left to test after this %s\n",
+		  "Bisecting: %d revisions left to test after this %s\n",
+		  nr), nr, steps_msg);
 
 	return bisect_checkout(bisect_rev, no_checkout);
 }
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 7012011..86d1380 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -362,7 +362,7 @@ test_expect_success 'bisect starting with a detached HEAD' '
 test_expect_success 'bisect errors out if bad and good are mistaken' '
 	git bisect reset &&
 	test_must_fail git bisect start $HASH2 $HASH4 2> rev_list_error &&
-	grep "mistook good and bad" rev_list_error &&
+	test_i18ngrep "mistook good and bad" rev_list_error &&
 	git bisect reset
 '
 
@@ -404,7 +404,7 @@ test_expect_success 'side branch creation' '
 
 test_expect_success 'good merge base when good and bad are siblings' '
 	git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt &&
 	git bisect good > my_bisect_log.txt &&
 	test_must_fail grep "merge base must be tested" my_bisect_log.txt &&
@@ -413,7 +413,7 @@ test_expect_success 'good merge base when good and bad are siblings' '
 '
 test_expect_success 'skipped merge base when good and bad are siblings' '
 	git bisect start "$SIDE_HASH7" "$HASH7" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt &&
 	git bisect skip > my_bisect_log.txt 2>&1 &&
 	grep "warning" my_bisect_log.txt &&
@@ -423,11 +423,11 @@ test_expect_success 'skipped merge base when good and bad are siblings' '
 
 test_expect_success 'bad merge base when good and bad are siblings' '
 	git bisect start "$HASH7" HEAD > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt &&
 	test_must_fail git bisect bad > my_bisect_log.txt 2>&1 &&
-	grep "merge base $HASH4 is bad" my_bisect_log.txt &&
-	grep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt &&
+	test_i18ngrep "merge base $HASH4 is bad" my_bisect_log.txt &&
+	test_i18ngrep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt &&
 	git bisect reset
 '
 
@@ -460,9 +460,9 @@ test_expect_success 'many merge bases creation' '
 
 test_expect_success 'good merge bases when good and bad are siblings' '
 	git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	git bisect good > my_bisect_log2.txt &&
-	grep "merge base must be tested" my_bisect_log2.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log2.txt &&
 	{
 		{
 			grep "$SIDE_HASH5" my_bisect_log.txt &&
@@ -477,14 +477,14 @@ test_expect_success 'good merge bases when good and bad are siblings' '
 
 test_expect_success 'optimized merge base checks' '
 	git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
-	grep "merge base must be tested" my_bisect_log.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log.txt &&
 	grep "$HASH4" my_bisect_log.txt &&
 	git bisect good > my_bisect_log2.txt &&
 	test -f ".git/BISECT_ANCESTORS_OK" &&
 	test "$HASH6" = $(git rev-parse --verify HEAD) &&
 	git bisect bad > my_bisect_log3.txt &&
 	git bisect good "$A_HASH" > my_bisect_log4.txt &&
-	grep "merge base must be tested" my_bisect_log4.txt &&
+	test_i18ngrep "merge base must be tested" my_bisect_log4.txt &&
 	test_must_fail test -f ".git/BISECT_ANCESTORS_OK"
 '
 
@@ -562,7 +562,7 @@ test_expect_success 'skipping away from skipped commit' '
 
 test_expect_success 'erroring out when using bad path parameters' '
 	test_must_fail git bisect start $PARA_HASH7 $HASH1 -- foobar 2> error.txt &&
-	grep "bad path parameters" error.txt
+	test_i18ngrep "bad path parameters" error.txt
 '
 
 test_expect_success 'test bisection on bare repo - --no-checkout specified' '
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 24/39] i18n: bisect: enable l10n of bisect terms in messages

Enable localization (l10n) of bisect terms in messages for the user.

Use an array to explicitly list and mark bisect terms for translation using the no-op N_(). Additionally, this catches at compile time errors such as wrongly writing the bisect terms, e.g., writing "god" instead of "good", since now we would use term_names[GOOD] syntax.

Triggering the retrieval of the term translation is done at runtime with _(term_bad) and _(term_good).

There are 2 messages that can't be marked for translation at this point, because they're grep'ed by bisect_run function in git-bisect.sh.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 bisect.c | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)
Show changes to bisect.c +19 −10
diff --git a/bisect.c b/bisect.c
index a8713a8..293d7ec 100644
--- a/bisect.c
+++ b/bisect.c
@@ -23,6 +23,15 @@ static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 static const char *term_bad;
 static const char *term_good;
 
+enum term { BAD, GOOD, OLD, NEW };
+static const char *term_names[] = {
+/* TRANSLATORS: in bisect.c source code file, the following terms are
+   used to describe a "bad commit", "good commit", "new revision", etc.
+   Please, if you can, check the source when you are not sure if a %s
+   would be replaced by one of the following terms. */
+	N_("bad"), N_("good"), N_("old"), N_("new"),  NULL
+};
+
 /* Remember to update object flag allocation in object.h */
 #define COUNTED		(1u<<16)
 
@@ -725,12 +734,12 @@ static void handle_bad_merge_base(void)
 	if (is_expected_rev(current_bad_oid)) {
 		char *bad_hex = oid_to_hex(current_bad_oid);
 		char *good_hex = join_sha1_array_hex(&good_revs, ' ');
-		if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
+		if (!strcmp(term_bad, term_names[BAD]) && !strcmp(term_good, term_names[GOOD])) {
 			fprintf(stderr, _("The merge base %s is bad.\n"
 				"This means the bug has been fixed "
 				"between %s and [%s].\n"),
 				bad_hex, bad_hex, good_hex);
-		} else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
+		} else if (!strcmp(term_bad, term_names[NEW]) && !strcmp(term_good, term_names[OLD])) {
 			fprintf(stderr, _("The merge base %s is new.\n"
 				"The property has changed "
 				"between %s and [%s].\n"),
@@ -739,7 +748,7 @@ static void handle_bad_merge_base(void)
 			fprintf(stderr, _("The merge base %s is %s.\n"
 				"This means the first '%s' commit is "
 				"between %s and [%s].\n"),
-				bad_hex, term_bad, term_good, bad_hex, good_hex);
+				bad_hex, _(term_bad), _(term_good), bad_hex, good_hex);
 		}
 		exit(3);
 	}
@@ -747,7 +756,7 @@ static void handle_bad_merge_base(void)
 	fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n"
 		"git bisect cannot work properly in this case.\n"
 		"Maybe you mistook %s and %s revs?\n"),
-		term_good, term_bad, term_good, term_bad);
+		_(term_good), _(term_bad), _(term_good), _(term_bad));
 	exit(1);
 }
 
@@ -762,7 +771,7 @@ static void handle_skipped_merge_base(const unsigned char *mb)
 		"So we cannot be sure the first %s commit is "
 		"between %s and %s.\n"
 		"We continue anyway."),
-		bad_hex, good_hex, term_bad, mb_hex, bad_hex);
+		bad_hex, good_hex, _(term_bad), mb_hex, bad_hex);
 	free(good_hex);
 }
 
@@ -843,7 +852,7 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout)
 	int fd;
 
 	if (!current_bad_oid)
-		die(_("a %s revision is needed"), term_bad);
+		die(_("a %s revision is needed"), _(term_bad));
 
 	/* Check if file BISECT_ANCESTORS_OK exists. */
 	if (!stat(filename, &st) && S_ISREG(st.st_mode))
@@ -906,8 +915,8 @@ void read_bisect_terms(const char **read_bad, const char **read_good)
 
 	if (!fp) {
 		if (errno == ENOENT) {
-			*read_bad = "bad";
-			*read_good = "good";
+			*read_bad = term_names[BAD];
+			*read_good = term_names[GOOD];
 			return;
 		} else {
 			die_errno(_("could not read file '%s'"), filename);
@@ -962,8 +971,8 @@ int bisect_next_all(const char *prefix, int no_checkout)
 
 		printf(_("%s was both %s and %s\n"),
 		       oid_to_hex(current_bad_oid),
-		       term_good,
-		       term_bad);
+		       _(term_good),
+		       _(term_bad));
 		exit(1);
 	}
 
-- 
2.7.3
Junio C Hamano· Jun 1, 2016, 17:38 UTC · re: Vasco Almeida · lore

Re: [PATCH v3 24/39] i18n: bisect: enable l10n of bisect terms in messages

Vasco Almeida <vascomalmeida@sapo.pt> writes:
Show 33 quoted lines
> +enum term { BAD, GOOD, OLD, NEW };
> +static const char *term_names[] = {
> +/* TRANSLATORS: in bisect.c source code file, the following terms are
> +   used to describe a "bad commit", "good commit", "new revision", etc.
> +   Please, if you can, check the source when you are not sure if a %s
> +   would be replaced by one of the following terms. */
> +	N_("bad"), N_("good"), N_("old"), N_("new"),  NULL
> +};
> +
>  /* Remember to update object flag allocation in object.h */
>  #define COUNTED		(1u<<16)
>  
> @@ -725,12 +734,12 @@ static void handle_bad_merge_base(void)
>  	if (is_expected_rev(current_bad_oid)) {
>  		char *bad_hex = oid_to_hex(current_bad_oid);
>  		char *good_hex = join_sha1_array_hex(&good_revs, ' ');
> -		if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
> +		if (!strcmp(term_bad, term_names[BAD]) && !strcmp(term_good, term_names[GOOD])) {
>  			fprintf(stderr, _("The merge base %s is bad.\n"
>  				"This means the bug has been fixed "
>  				"between %s and [%s].\n"),
>  				bad_hex, bad_hex, good_hex);
> -		} else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
> +		} else if (!strcmp(term_bad, term_names[NEW]) && !strcmp(term_good, term_names[OLD])) {
>  			fprintf(stderr, _("The merge base %s is new.\n"
>  				"The property has changed "
>  				"between %s and [%s].\n"),
> @@ -739,7 +748,7 @@ static void handle_bad_merge_base(void)
>  			fprintf(stderr, _("The merge base %s is %s.\n"
>  				"This means the first '%s' commit is "
>  				"between %s and [%s].\n"),
> -				bad_hex, term_bad, term_good, bad_hex, good_hex);
> +				bad_hex, _(term_bad), _(term_good), bad_hex, good_hex);

These "bad" and "good" that are compared with term_bad and term_good are the literal tokens the end user gives from the "git bisect" command line. I do not think you would want to catch them with

    $ git bisect novo <rev>
    $ git bisect velho <rev>
unless the user has done
    $ git bisect --term-old=velho --term-new=novo
previously.

And that "custom bisect terms" case is covered by the last "else" clause in this if/elseif cascade (outside the context we can see in your message).

The only thing you need to do around here is to mark the string as translatable. I do not think we need "enum term", or term_names[].

Show 6 quoted lines
> @@ -747,7 +756,7 @@ static void handle_bad_merge_base(void)
>  	fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n"
>  		"git bisect cannot work properly in this case.\n"
>  		"Maybe you mistook %s and %s revs?\n"),
> -		term_good, term_bad, term_good, term_bad);
> +		_(term_good), _(term_bad), _(term_good), _(term_bad));

Likewise for all _(term_good), _(term_bad) and use of term_names[] we see in this patch.

Vasco Almeida· Jun 2, 2016, 09:04 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 24/39] i18n: bisect: enable l10n of bisect terms in messages

Às 17:38 de 01-06-2016, Junio C Hamano escreveu:
Show 48 quoted lines
> Vasco Almeida <vascomalmeida@sapo.pt> writes:
> 
>> +enum term { BAD, GOOD, OLD, NEW };
>> +static const char *term_names[] = {
>> +/* TRANSLATORS: in bisect.c source code file, the following terms are
>> +   used to describe a "bad commit", "good commit", "new revision", etc.
>> +   Please, if you can, check the source when you are not sure if a %s
>> +   would be replaced by one of the following terms. */
>> +	N_("bad"), N_("good"), N_("old"), N_("new"),  NULL
>> +};
>> +
>>  /* Remember to update object flag allocation in object.h */
>>  #define COUNTED		(1u<<16)
>>  
>> @@ -725,12 +734,12 @@ static void handle_bad_merge_base(void)
>>  	if (is_expected_rev(current_bad_oid)) {
>>  		char *bad_hex = oid_to_hex(current_bad_oid);
>>  		char *good_hex = join_sha1_array_hex(&good_revs, ' ');
>> -		if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
>> +		if (!strcmp(term_bad, term_names[BAD]) && !strcmp(term_good, term_names[GOOD])) {
>>  			fprintf(stderr, _("The merge base %s is bad.\n"
>>  				"This means the bug has been fixed "
>>  				"between %s and [%s].\n"),
>>  				bad_hex, bad_hex, good_hex);
>> -		} else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
>> +		} else if (!strcmp(term_bad, term_names[NEW]) && !strcmp(term_good, term_names[OLD])) {
>>  			fprintf(stderr, _("The merge base %s is new.\n"
>>  				"The property has changed "
>>  				"between %s and [%s].\n"),
>> @@ -739,7 +748,7 @@ static void handle_bad_merge_base(void)
>>  			fprintf(stderr, _("The merge base %s is %s.\n"
>>  				"This means the first '%s' commit is "
>>  				"between %s and [%s].\n"),
>> -				bad_hex, term_bad, term_good, bad_hex, good_hex);
>> +				bad_hex, _(term_bad), _(term_good), bad_hex, good_hex);
> 
> These "bad" and "good" that are compared with term_bad and term_good
> are the literal tokens the end user gives from the "git bisect"
> command line.  I do not think you would want to catch them with
> 
>     $ git bisect novo <rev>
>     $ git bisect velho <rev>
> 
> unless the user has done
> 
>     $ git bisect --term-old=velho --term-new=novo
> 
> previously.

I may be misunderstanding you, but we do not "catch" those terms with this patch, although I'm not sure what you mean by "catch them". I think you forget that no-operation N_("good"), does not affect in any way the string "good", it only enables xgettext to extract it to .pot file, does not trigger translation. Overall, I don't understand what are you trying to tell me here.

Show 7 quoted lines
> 
> And that "custom bisect terms" case is covered by the last "else"
> clause in this if/elseif cascade (outside the context we can see in
> your message).
> 
> The only thing you need to do around here is to mark the string as
> translatable.  I do not think we need "enum term", or term_names[].

This patch tries to make bisect output those term translated within the also translated message. To enable this, it is handy to have term_names[] in order to mark each term, although I could have mark them anywhere they appeared in the source. It was only for that I chose to have term_names[].

Show 11 quoted lines
> 
>> @@ -747,7 +756,7 @@ static void handle_bad_merge_base(void)
>>  	fprintf(stderr, _("Some %s revs are not ancestor of the %s rev.\n"
>>  		"git bisect cannot work properly in this case.\n"
>>  		"Maybe you mistook %s and %s revs?\n"),
>> -		term_good, term_bad, term_good, term_bad);
>> +		_(term_good), _(term_bad), _(term_good), _(term_bad));
> 
> Likewise for all _(term_good), _(term_bad) and use of term_names[]
> we see in this patch.
> 

Indeed this was more of a PATCH/RFC to see what people would think of it. If nobody contest and there is no value in it, I'll happily drop this patch in the next re-roll.

My motivation for this patch was that a user could feel embarrassment reading a message in her language with those terms untranslated. Although I do believe that no translating them is also a possibility.

Junio C Hamano· Jun 2, 2016, 17:33 UTC · re: Vasco Almeida · lore

Re: [PATCH v3 24/39] i18n: bisect: enable l10n of bisect terms in messages

Vasco Almeida <vascomalmeida@sapo.pt> writes:
Show 11 quoted lines
> Às 17:38 de 01-06-2016, Junio C Hamano escreveu:
>> Vasco Almeida <vascomalmeida@sapo.pt> writes:
>> 
>>> +enum term { BAD, GOOD, OLD, NEW };
>>> +static const char *term_names[] = {
>>> +/* TRANSLATORS: in bisect.c source code file, the following terms are
>>> +   used to describe a "bad commit", "good commit", "new revision", etc.
>>> +   Please, if you can, check the source when you are not sure if a %s
>>> +   would be replaced by one of the following terms. */
>>> +	N_("bad"), N_("good"), N_("old"), N_("new"),  NULL
>>> +};
As you pointed out (below), I _did_ misread these N_() ...
Show 15 quoted lines
>>>  /* Remember to update object flag allocation in object.h */
>>>  #define COUNTED		(1u<<16)
>>>  
>>> @@ -725,12 +734,12 @@ static void handle_bad_merge_base(void)
>>>  	if (is_expected_rev(current_bad_oid)) {
>>>  		char *bad_hex = oid_to_hex(current_bad_oid);
>>>  		char *good_hex = join_sha1_array_hex(&good_revs, ' ');
>>> -		if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) {
>>> +		if (!strcmp(term_bad, term_names[BAD]) && !strcmp(term_good, term_names[GOOD])) {
>>>  			fprintf(stderr, _("The merge base %s is bad.\n"
>>>  				"This means the bug has been fixed "
>>>  				"between %s and [%s].\n"),
>>>  				bad_hex, bad_hex, good_hex);
>>> -		} else if (!strcmp(term_bad, "new") && !strcmp(term_good, "old")) {
>>> +		} else if (!strcmp(term_bad, term_names[NEW]) && !strcmp(term_good, term_names[OLD])) {

... which means the above two changes are "since we have these four word constants in an array up there, let's use them", i.e. there is no change in behaviour when running the program in any locale, but it is a good programming practice (as long as the term_names[] array that holds the four word constants and indexes into it with enum term is justifiable, that is).

Show 6 quoted lines
>>> @@ -739,7 +748,7 @@ static void handle_bad_merge_base(void)
>>>  			fprintf(stderr, _("The merge base %s is %s.\n"
>>>  				"This means the first '%s' commit is "
>>>  				"between %s and [%s].\n"),
>>> -				bad_hex, term_bad, term_good, bad_hex, good_hex);
>>> +				bad_hex, _(term_bad), _(term_good), bad_hex, good_hex);
But this translation still does not make much sense to me.

If the user did not change term_bad and term_good from the default ones (i.e. the four words above), the flow of control would not come here; one of the above two !strcmp() lines you changed would trigger.

On the other hand, if the user did change term_bad and term_good by using --term-old=velho --term-new=novo earlier (which is stored in BISECT_TERMS and read into these two variables), these are strings that Git encounters at runtime; you cannot expect _(term_bad) aka _("novo") to be translated by *.po files for the locale.

And even if it were somehow translatable, you do not want to.

Imagine a case where there is a Portuguese word the user used in place of 'novo' in the example above, and there is an English word with the same spelling with a totally different meaning. And further imagine that English word is used elsewhere in Git and has translation to Portuguese in your *.po file. _(term_bad) would become a translation of that English word into Portuguese, which would be a word totally unrelated to the word the user used originally when she did --term-old=<that word in portuguese>.

So I do not think _(term_bad) and _(term_good) is a good idea in the last hunk quoted above (it is not just "not a good idea", but actually would be harmful), which would make term_names[] and "enum term" unnecessary.

There is a case where one of the default four words could seep through to the last else clause. Is that what you are trying to address?

That is, when term_bad is left as "bad" but term_good is customized to "super", neither "term_bad is 'bad' and term_good is 'good'" nor "term_bad is 'new' and term_good is 'old" is true, and we would say

	Merge base is BAD.  This means the first SUPER commit is ...

With your change to mark _(term_bad) and _(term_good), the message would translate term_bad to whatever word it is in your language, and term_good that comes from the end user will be left as SUPER as the user typed. I am not sure if it is a good thing to translate only BAD while leaving SUPER as-is in such a case.

Vasco Almeida· Jun 3, 2016, 12:32 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 24/39] i18n: bisect: enable l10n of bisect terms in messages

Às 17:33 de 02-06-2016, Junio C Hamano escreveu:
Show 18 quoted lines
> Vasco Almeida <vascomalmeida@sapo.pt> writes:
> 
>> Às 17:38 de 01-06-2016, Junio C Hamano escreveu:
>>> Vasco Almeida <vascomalmeida@sapo.pt> writes:
>>>
>>>> @@ -739,7 +748,7 @@ static void handle_bad_merge_base(void)
>>>>  			fprintf(stderr, _("The merge base %s is %s.\n"
>>>>  				"This means the first '%s' commit is "
>>>>  				"between %s and [%s].\n"),
>>>> -				bad_hex, term_bad, term_good, bad_hex, good_hex);
>>>> +				bad_hex, _(term_bad), _(term_good), bad_hex, good_hex);
> 
> But this translation still does not make much sense to me.
> 
> If the user did not change term_bad and term_good from the default
> ones (i.e. the four words above), the flow of control would not come
> here; one of the above two !strcmp() lines you changed would trigger.
> 

Hence, marking _(term_bad) and _(term_good) in this instance is both futile and wrong. Because, at this point of flow, these term were likely defined by the user, therefore must not be translated.

Show 17 quoted lines
> On the other hand, if the user did change term_bad and term_good by
> using --term-old=velho --term-new=novo earlier (which is stored in
> BISECT_TERMS and read into these two variables), these are strings
> that Git encounters at runtime; you cannot expect _(term_bad) aka
> _("novo") to be translated by *.po files for the locale.
> 
> And even if it were somehow translatable, you do not want to.
> 
> Imagine a case where there is a Portuguese word the user used in
> place of 'novo' in the example above, and there is an English word
> with the same spelling with a totally different meaning.  And
> further imagine that English word is used elsewhere in Git and has
> translation to Portuguese in your *.po file.  _(term_bad) would
> become a translation of that English word into Portuguese, which
> would be a word totally unrelated to the word the user used
> originally when she did --term-old=<that word in portuguese>.
> 

I thought about this after sending my last email about this topic. It is something that can happen indeed.

Show 5 quoted lines
> So I do not think _(term_bad) and _(term_good) is a good idea in the
> last hunk quoted above (it is not just "not a good idea", but
> actually would be harmful), which would make term_names[] and "enum
> term" unnecessary.
> 

I agree with you. I'm going to drop this patch in the next re-roll. Leaving the previous patch, [PATCH v3 23/39] i18n: bisect: mark strings for translation, unchanged which just masks the strings for translation but not anything else.

> There is a case where one of the default four words could seep
> through to the last else clause.  Is that what you are trying to
> address?
> 

No. I was trying to replace the English terms by their translation in those messages. But we have arrive to conclusion it is a bad idea to pursue.

Show 11 quoted lines
> That is, when term_bad is left as "bad" but term_good is customized
> to "super", neither "term_bad is 'bad' and term_good is 'good'" nor
> "term_bad is 'new' and term_good is 'old" is true, and we would say
> 
> 	Merge base is BAD.  This means the first SUPER commit is ...
> 
> With your change to mark _(term_bad) and _(term_good), the message
> would translate term_bad to whatever word it is in your language,
> and term_good that comes from the end user will be left as SUPER as
> the user typed.  I am not sure if it is a good thing to translate
> only BAD while leaving SUPER as-is in such a case.

I think this may be confusing for some users. I am positive it is a bad thing.

If I had thought more about this, I hadn't proposed this patch. I'm sorry for wasting your time with such nonsense. I'm going to drop it in the next re-roll.

Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 25/39] i18n: transport-helper.c: change N_() call to _()

The N_() no-op call currently marks the string to be extracted by xgettext but doesn't trigger the retrieval of the translation at run time, whereas _() does both. Meaning that, in spite of having translations available, they were never retrieved to make use of them.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 transport-helper.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to transport-helper.c +1 −1
diff --git a/transport-helper.c b/transport-helper.c
index bd666b2..4208743 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -1038,7 +1038,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
 				(*tail)->status |= REF_STATUS_UPTODATE;
 				if (read_ref((*tail)->name,
 					     (*tail)->old_oid.hash) < 0)
-					die(N_("Could not read ref %s"),
+					die(_("Could not read ref %s"),
 					    (*tail)->name);
 			}
 		}
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 26/39] i18n: notes: mark strings for translation

Mark strings of messages for the user as translatable.

Update tests t3310-notes-merge-manual-resolve.sh and t3320-notes-merge-worktrees.sh to reflect new translatable messages.

Tests that grep for .git/NOTES_MERGE_WORKTREE reflect the translatable string "Automatic notes merge failed. Fix conflicts in %s and [...]".

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/notes.c                       | 20 ++++++++++----------
 t/t3310-notes-merge-manual-resolve.sh |  8 ++++----
 t/t3320-notes-merge-worktrees.sh      |  2 +-
 3 files changed, 15 insertions(+), 15 deletions(-)
Show changes to 3 files +15 −15

builtin/notes.c, t/t3310-notes-merge-manual-resolve.sh, t/t3320-notes-merge-worktrees.sh

diff --git a/builtin/notes.c b/builtin/notes.c
index c65b59a..d11e6eb 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -749,7 +749,7 @@ static int git_config_get_notes_strategy(const char *key,
 	if (git_config_get_string(key, &value))
 		return 1;
 	if (parse_notes_merge_strategy(value, strategy))
-		git_die_config(key, "unknown notes merge strategy %s", value);
+		git_die_config(key, _("unknown notes merge strategy %s"), value);
 
 	free(value);
 	return 0;
@@ -788,15 +788,15 @@ static int merge(int argc, const char **argv, const char *prefix)
 	if (strategy || do_commit + do_abort == 0)
 		do_merge = 1;
 	if (do_merge + do_commit + do_abort != 1) {
-		error("cannot mix --commit, --abort or -s/--strategy");
+		error(_("cannot mix --commit, --abort or -s/--strategy"));
 		usage_with_options(git_notes_merge_usage, options);
 	}
 
 	if (do_merge && argc != 1) {
-		error("Must specify a notes ref to merge");
+		error(_("Must specify a notes ref to merge"));
 		usage_with_options(git_notes_merge_usage, options);
 	} else if (!do_merge && argc) {
-		error("too many parameters");
+		error(_("too many parameters"));
 		usage_with_options(git_notes_merge_usage, options);
 	}
 
@@ -817,7 +817,7 @@ static int merge(int argc, const char **argv, const char *prefix)
 
 	if (strategy) {
 		if (parse_notes_merge_strategy(strategy, &o.strategy)) {
-			error("Unknown -s/--strategy: %s", strategy);
+			error(_("Unknown -s/--strategy: %s"), strategy);
 			usage_with_options(git_notes_merge_usage, options);
 		}
 	} else {
@@ -857,11 +857,11 @@ static int merge(int argc, const char **argv, const char *prefix)
 			die(_("A notes merge into %s is already in-progress at %s"),
 			    default_notes_ref(), wt->path);
 		if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
-			die("Failed to store link to current notes ref (%s)",
+			die(_("Failed to store link to current notes ref (%s)"),
 			    default_notes_ref());
-		printf("Automatic notes merge failed. Fix conflicts in %s and "
-		       "commit the result with 'git notes merge --commit', or "
-		       "abort the merge with 'git notes merge --abort'.\n",
+		printf(_("Automatic notes merge failed. Fix conflicts in %s and "
+			 "commit the result with 'git notes merge --commit', or "
+			 "abort the merge with 'git notes merge --abort'.\n"),
 		       git_path(NOTES_MERGE_WORKTREE));
 	}
 
@@ -964,7 +964,7 @@ static int get_ref(int argc, const char **argv, const char *prefix)
 			     git_notes_get_ref_usage, 0);
 
 	if (argc) {
-		error("too many parameters");
+		error(_("too many parameters"));
 		usage_with_options(git_notes_get_ref_usage, options);
 	}
 
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index d557212..6967436 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -178,7 +178,7 @@ test_expect_success 'merge z into m (== y) with default ("manual") resolver => C
 	git config core.notesRef refs/notes/m &&
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
@@ -381,7 +381,7 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
 	git config core.notesRef refs/notes/m &&
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
@@ -415,7 +415,7 @@ git rev-parse refs/notes/z > pre_merge_z
 test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' '
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
@@ -496,7 +496,7 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
 	git update-ref refs/notes/m refs/notes/y &&
 	test_must_fail git notes merge z >output &&
 	# Output should point to where to resolve conflicts
-	grep -q "\\.git/NOTES_MERGE_WORKTREE" output &&
+	test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output &&
 	# Inspect merge conflicts
 	ls .git/NOTES_MERGE_WORKTREE >output_conflicts &&
 	test_cmp expect_conflicts output_conflicts &&
diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh
index 522157b..6e05115 100755
--- a/t/t3320-notes-merge-worktrees.sh
+++ b/t/t3320-notes-merge-worktrees.sh
@@ -62,7 +62,7 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' '
 		cd worktree2 &&
 		git config core.notesRef refs/notes/x &&
 		test_must_fail git notes merge z 2>&1 >out &&
-		grep "Automatic notes merge failed" out &&
+		test_i18ngrep "Automatic notes merge failed" out &&
 		grep -v "A notes merge into refs/notes/x is already in-progress in" out
 	) &&
 	echo "ref: refs/notes/x" >expect &&
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 27/39] i18n: notes: mark options for translation

Mark options description of git prune for translation.
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/notes.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to builtin/notes.c +2 −2
diff --git a/builtin/notes.c b/builtin/notes.c
index d11e6eb..0572051 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -934,8 +934,8 @@ static int prune(int argc, const char **argv, const char *prefix)
 	struct notes_tree *t;
 	int show_only = 0, verbose = 0;
 	struct option options[] = {
-		OPT__DRY_RUN(&show_only, "do not remove, show only"),
-		OPT__VERBOSE(&verbose, "report pruned notes"),
+		OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
+		OPT__VERBOSE(&verbose, N_("report pruned notes")),
 		OPT_END()
 	};
 
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 28/39] i18n: config: unfold error messages marked for translation

Introduced in 473166b ("config: add 'origin_type' to config_source struct", 2016-02-19), Git can inform the user about the origin of a config error, but the implementation does not allow translators to translate the keywords 'file', 'blob, 'standard input', and 'submodule-blob'. Moreover, for the second message, a reason for the error is appended to the message, not allowing translators to translate that reason either.

Unfold the message into several templates for each known origin_type. That would result in better translation at the expense of code verbosity.

Add enum git_config_oringin_type to ease management of the various configuration origin types (blob, file, etc).

For the first instance, use xsnprintf function to prepare the message string, instead of doing something like it's done for the second one, because intelligibility and code conciseness are improved for that instance.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 cache.h            |   9 ++++-
 config.c           | 102 ++++++++++++++++++++++++++++++++++++++++++++---------
 submodule-config.c |   2 +-
 3 files changed, 95 insertions(+), 18 deletions(-)
Show changes to 3 files +95 −18

cache.h, config.c, submodule-config.c

diff --git a/cache.h b/cache.h
index 6049f86..4bded9a 100644
--- a/cache.h
+++ b/cache.h
@@ -1559,10 +1559,17 @@ struct git_config_source {
 	const char *blob;
 };
 
+enum git_config_origin_type {
+	CFG_BLOB,
+	CFG_FILE,
+	CFG_STDIN,
+	CFG_SUBMODULE_BLOB
+};
+
 typedef int (*config_fn_t)(const char *, const char *, void *);
 extern int git_default_config(const char *, const char *, void *);
 extern int git_config_from_file(config_fn_t fn, const char *, void *);
-extern int git_config_from_mem(config_fn_t fn, const char *origin_type,
+extern int git_config_from_mem(config_fn_t fn, const enum git_config_origin_type,
 					const char *name, const char *buf, size_t len, void *data);
 extern void git_config_push_parameter(const char *text);
 extern int git_config_from_parameters(config_fn_t fn, void *data);
diff --git a/config.c b/config.c
index f51c56b..b08a418 100644
--- a/config.c
+++ b/config.c
@@ -24,7 +24,7 @@ struct config_source {
 			size_t pos;
 		} buf;
 	} u;
-	const char *origin_type;
+	enum git_config_origin_type origin_type;
 	const char *name;
 	const char *path;
 	int die_on_error;
@@ -417,6 +417,7 @@ static int git_parse_source(config_fn_t fn, void *data)
 	int comment = 0;
 	int baselen = 0;
 	struct strbuf *var = &cf->var;
+	char error_msg[128];
 
 	/* U+FEFF Byte Order Mark in UTF8 */
 	const char *bomptr = utf8_bom;
@@ -471,10 +472,38 @@ static int git_parse_source(config_fn_t fn, void *data)
 		if (get_value(fn, data, var) < 0)
 			break;
 	}
+
+	switch (cf->origin_type) {
+	case CFG_BLOB:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in blob %s"),
+			  cf->linenr, cf->name);
+		break;
+	case CFG_FILE:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in file %s"),
+			  cf->linenr, cf->name);
+		break;
+	case CFG_STDIN:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in standard input"),
+			  cf->linenr);
+		break;
+	case CFG_SUBMODULE_BLOB:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in submodule-blob %s"),
+			  cf->linenr, cf->name);
+		break;
+	default:
+		xsnprintf(error_msg, sizeof(error_msg),
+			  _("bad config line %d in %s"),
+			  cf->linenr, cf->name);
+	}
+
 	if (cf->die_on_error)
-		die(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
+		die(error_msg);
 	else
-		return error(_("bad config line %d in %s %s"), cf->linenr, cf->origin_type, cf->name);
+		return error(error_msg);
 }
 
 static int parse_unit_factor(const char *end, uintmax_t *val)
@@ -583,16 +612,42 @@ int git_parse_ulong(const char *value, unsigned long *ret)
 NORETURN
 static void die_bad_number(const char *name, const char *value)
 {
-	const char *reason = errno == ERANGE ?
-			     "out of range" :
-			     "invalid unit";
 	if (!value)
 		value = "";
 
-	if (cf && cf->origin_type && cf->name)
-		die(_("bad numeric config value '%s' for '%s' in %s %s: %s"),
-		    value, name, cf->origin_type, cf->name, reason);
-	die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason);
+	if (!(cf && cf->name))
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s': out of range")
+		    : _("bad numeric config value '%s' for '%s': invalid unit"),
+		    value, name);
+
+	switch (cf->origin_type) {
+	case CFG_BLOB:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in blob %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in blob %s: invalid unit"),
+		    value, name, cf->name);
+	case CFG_FILE:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in file %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in file %s: invalid unit"),
+		    value, name, cf->name);
+	case CFG_STDIN:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in standard input: out of range")
+		    : _("bad numeric config value '%s' for '%s' in standard input: invalid unit"),
+		    value, name);
+	case CFG_SUBMODULE_BLOB:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in submodule-blob %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in submodule-blob %s: invalid unit"),
+		    value, name, cf->name);
+	default:
+		die(errno == ERANGE
+		    ? _("bad numeric config value '%s' for '%s' in %s: out of range")
+		    : _("bad numeric config value '%s' for '%s' in %s: invalid unit"),
+		    value, name, cf->name);
+	}
 }
 
 int git_config_int(const char *name, const char *value)
@@ -1069,7 +1124,8 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
 }
 
 static int do_config_from_file(config_fn_t fn,
-		const char *origin_type, const char *name, const char *path, FILE *f,
+		const enum git_config_origin_type origin_type,
+		const char *name, const char *path, FILE *f,
 		void *data)
 {
 	struct config_source top;
@@ -1088,7 +1144,7 @@ static int do_config_from_file(config_fn_t fn,
 
 static int git_config_from_stdin(config_fn_t fn, void *data)
 {
-	return do_config_from_file(fn, "standard input", "", NULL, stdin, data);
+	return do_config_from_file(fn, CFG_STDIN, "", NULL, stdin, data);
 }
 
 int git_config_from_file(config_fn_t fn, const char *filename, void *data)
@@ -1099,14 +1155,14 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 	f = fopen(filename, "r");
 	if (f) {
 		flockfile(f);
-		ret = do_config_from_file(fn, "file", filename, filename, f, data);
+		ret = do_config_from_file(fn, CFG_FILE, filename, filename, f, data);
 		funlockfile(f);
 		fclose(f);
 	}
 	return ret;
 }
 
-int git_config_from_mem(config_fn_t fn, const char *origin_type,
+int git_config_from_mem(config_fn_t fn, const enum git_config_origin_type origin_type,
 			const char *name, const char *buf, size_t len, void *data)
 {
 	struct config_source top;
@@ -1143,7 +1199,7 @@ static int git_config_from_blob_sha1(config_fn_t fn,
 		return error("reference '%s' does not point to a blob", name);
 	}
 
-	ret = git_config_from_mem(fn, "blob", name, buf, size, data);
+	ret = git_config_from_mem(fn, CFG_BLOB, name, buf, size, data);
 	free(buf);
 
 	return ret;
@@ -2442,7 +2498,21 @@ int parse_config_key(const char *var,
 
 const char *current_config_origin_type(void)
 {
-	return cf && cf->origin_type ? cf->origin_type : "command line";
+	if (!cf)
+		return "command line";
+
+	switch (cf->origin_type) {
+	case CFG_BLOB:
+		return "blob";
+	case CFG_FILE:
+		return "file";
+	case CFG_STDIN:
+		return "standard input";
+	case CFG_SUBMODULE_BLOB:
+		return "submodule-blob";
+	default:
+		return "command line";
+	}
 }
 
 const char *current_config_name(void)
diff --git a/submodule-config.c b/submodule-config.c
index debab29..f53f6bd 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -439,7 +439,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,
 	parameter.commit_sha1 = commit_sha1;
 	parameter.gitmodules_sha1 = sha1;
 	parameter.overwrite = 0;
-	git_config_from_mem(parse_config, "submodule-blob", rev.buf,
+	git_config_from_mem(parse_config, CFG_SUBMODULE_BLOB, rev.buf,
 			config, config_size, &parameter);
 	free(config);
 
-- 
2.7.3
Junio C Hamano· Jun 1, 2016, 17:43 UTC · re: Vasco Almeida · lore

Re: [PATCH v3 28/39] i18n: config: unfold error messages marked for translation

Vasco Almeida <vascomalmeida@sapo.pt> writes:
Show 7 quoted lines
> Introduced in 473166b ("config: add 'origin_type' to config_source
> struct", 2016-02-19), Git can inform the user about the origin of a
> config error, but the implementation does not allow translators to
> translate the keywords 'file', 'blob, 'standard input', and
> 'submodule-blob'. Moreover, for the second message, a reason for the
> error is appended to the message, not allowing translators to translate
> that reason either.
Good intentions.
Show 18 quoted lines
> @@ -417,6 +417,7 @@ static int git_parse_source(config_fn_t fn, void *data)
>  	int comment = 0;
>  	int baselen = 0;
>  	struct strbuf *var = &cf->var;
> +	char error_msg[128];
>  
>  	/* U+FEFF Byte Order Mark in UTF8 */
>  	const char *bomptr = utf8_bom;
> @@ -471,10 +472,38 @@ static int git_parse_source(config_fn_t fn, void *data)
>  		if (get_value(fn, data, var) < 0)
>  			break;
>  	}
> +
> +	switch (cf->origin_type) {
> +	case CFG_BLOB:
> +		xsnprintf(error_msg, sizeof(error_msg),
> +			  _("bad config line %d in blob %s"),
> +			  cf->linenr, cf->name);
Use xstrfmt() intead, perhaps?  That would be cleaner.
Vasco Almeida· Jun 2, 2016, 09:28 UTC · re: Junio C Hamano · lore

Re: [PATCH v3 28/39] i18n: config: unfold error messages marked for translation

Às 17:43 de 01-06-2016, Junio C Hamano escreveu:
Show 33 quoted lines
> Vasco Almeida <vascomalmeida@sapo.pt> writes:
> 
>> Introduced in 473166b ("config: add 'origin_type' to config_source
>> struct", 2016-02-19), Git can inform the user about the origin of a
>> config error, but the implementation does not allow translators to
>> translate the keywords 'file', 'blob, 'standard input', and
>> 'submodule-blob'. Moreover, for the second message, a reason for the
>> error is appended to the message, not allowing translators to translate
>> that reason either.
> 
> Good intentions.
> 
>> @@ -417,6 +417,7 @@ static int git_parse_source(config_fn_t fn, void *data)
>>  	int comment = 0;
>>  	int baselen = 0;
>>  	struct strbuf *var = &cf->var;
>> +	char error_msg[128];
>>  
>>  	/* U+FEFF Byte Order Mark in UTF8 */
>>  	const char *bomptr = utf8_bom;
>> @@ -471,10 +472,38 @@ static int git_parse_source(config_fn_t fn, void *data)
>>  		if (get_value(fn, data, var) < 0)
>>  			break;
>>  	}
>> +
>> +	switch (cf->origin_type) {
>> +	case CFG_BLOB:
>> +		xsnprintf(error_msg, sizeof(error_msg),
>> +			  _("bad config line %d in blob %s"),
>> +			  cf->linenr, cf->name);
> 
> Use xstrfmt() intead, perhaps?  That would be cleaner.
> 

Wouldn't that create a memory leak? Because, in this patch, error_msg is used in the following way

        if (cf->die_on_error)
                die(error_msg);
        else
                return error(error_msg);
Junio C Hamano· Jun 2, 2016, 17:36 UTC · re: Vasco Almeida · lore

Re: [PATCH v3 28/39] i18n: config: unfold error messages marked for translation

Vasco Almeida <vascomalmeida@sapo.pt> writes:
Show 35 quoted lines
> Às 17:43 de 01-06-2016, Junio C Hamano escreveu:
>> Vasco Almeida <vascomalmeida@sapo.pt> writes:
>> 
>>> Introduced in 473166b ("config: add 'origin_type' to config_source
>>> struct", 2016-02-19), Git can inform the user about the origin of a
>>> config error, but the implementation does not allow translators to
>>> translate the keywords 'file', 'blob, 'standard input', and
>>> 'submodule-blob'. Moreover, for the second message, a reason for the
>>> error is appended to the message, not allowing translators to translate
>>> that reason either.
>> 
>> Good intentions.
>> 
>>> @@ -417,6 +417,7 @@ static int git_parse_source(config_fn_t fn, void *data)
>>>  	int comment = 0;
>>>  	int baselen = 0;
>>>  	struct strbuf *var = &cf->var;
>>> +	char error_msg[128];
>>>  
>>>  	/* U+FEFF Byte Order Mark in UTF8 */
>>>  	const char *bomptr = utf8_bom;
>>> @@ -471,10 +472,38 @@ static int git_parse_source(config_fn_t fn, void *data)
>>>  		if (get_value(fn, data, var) < 0)
>>>  			break;
>>>  	}
>>> +
>>> +	switch (cf->origin_type) {
>>> +	case CFG_BLOB:
>>> +		xsnprintf(error_msg, sizeof(error_msg),
>>> +			  _("bad config line %d in blob %s"),
>>> +			  cf->linenr, cf->name);
>> 
>> Use xstrfmt() intead, perhaps?  That would be cleaner.
>> 
> Wouldn't that create a memory leak?

Yes, I didn't mean to suggest "use xstrfmt() instead of xsnprintf() without changing anything else". It was merely to suggest that you do not have to have 128-byte limit if you used xstrfmt(); having to free the result was too obvious that I left it unsaid, and as expected, you noticed the need to do so, which is good ;-)

Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 29/39] i18n: merge: mark messages for translation

Mark messages shown to the user for translation.
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/merge.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
Show changes to builtin/merge.c +5 −5
diff --git a/builtin/merge.c b/builtin/merge.c
index b555a1b..961def5 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1014,7 +1014,7 @@ static int default_edit_option(void)
 	if (e) {
 		int v = git_config_maybe_bool(name, e);
 		if (v < 0)
-			die("Bad value '%s' in environment '%s'", e, name);
+			die(_("Bad value '%s' in environment '%s'"), e, name);
 		return v;
 	}
 
@@ -1115,7 +1115,7 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge
 		if (!commit) {
 			if (ptr)
 				*ptr = '\0';
-			die("not something we can merge in %s: %s",
+			die(_("not something we can merge in %s: %s"),
 			    filename, merge_names->buf + pos);
 		}
 		remotes = &commit_list_insert(commit, remotes)->next;
@@ -1149,7 +1149,7 @@ static struct commit_list *collect_parents(struct commit *head_commit,
 			struct commit *commit = get_merge_parent(argv[i]);
 			if (!commit)
 				help_unknown_ref(argv[i], "merge",
-						 "not something we can merge");
+						 _("not something we can merge"));
 			remotes = &commit_list_insert(commit, remotes)->next;
 		}
 		remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads);
@@ -1421,7 +1421,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		 * If head can reach all the merge then we are up to date.
 		 * but first the most common case of merging one remote.
 		 */
-		finish_up_to_date("Already up-to-date.");
+		finish_up_to_date(_("Already up-to-date."));
 		goto done;
 	} else if (fast_forward != FF_NO && !remoteheads->next &&
 			!common->next &&
@@ -1506,7 +1506,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			}
 		}
 		if (up_to_date) {
-			finish_up_to_date("Already up-to-date. Yeeah!");
+			finish_up_to_date(_("Already up-to-date. Yeeah!"));
 			goto done;
 		}
 	}
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 30/39] i18n: merge: change command option help to lowercase

Change command option description to lowercase, matching pull counterpart option. Translators would have to translate such message only once.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/merge.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/merge.c +1 −1
diff --git a/builtin/merge.c b/builtin/merge.c
index 961def5..d82f6c1 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -211,7 +211,7 @@ static struct option builtin_merge_options[] = {
 		PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },
 	OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
 	OPT_BOOL(0, "verify-signatures", &verify_signatures,
-		N_("Verify that the named commit has a valid GPG signature")),
+		N_("verify that the named commit has a valid GPG signature")),
 	OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"),
 		N_("merge strategy to use"), option_parse_strategy),
 	OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"),
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 31/39] i18n: sequencer: add period to error message

Add a period to error message so it matches others instances in sequencer.c. Now translator would have to translate such message only once.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 sequencer.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to sequencer.c +1 −1
diff --git a/sequencer.c b/sequencer.c
index 57b3671..e916387 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -190,7 +190,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
 		die_errno(_("Could not write to %s"), filename);
 	strbuf_release(msgbuf);
 	if (commit_lock_file(&msg_file) < 0)
-		die(_("Error wrapping up %s"), filename);
+		die(_("Error wrapping up %s."), filename);
 }
 
 static struct tree *empty_tree(void)
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 32/39] i18n: standardise messages

Standardise messages in order to save translators some work.

Nuances fixed in this commit: "failed to read %s" "read of %s failed"

"detach the HEAD at named commit" "detach HEAD at named commit"

"removing '%s' failed" "failed to remove '%s'"

"index file corrupt" "corrupt index file"

"failed to read %s" "read of %s failed"

"detach the HEAD at named commit" "detach HEAD at named commit"

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/apply.c    | 6 +++---
 builtin/checkout.c | 6 +++---
 builtin/repack.c   | 2 +-
 3 files changed, 7 insertions(+), 7 deletions(-)
Show changes to 3 files +7 −7

builtin/apply.c, builtin/checkout.c, builtin/repack.c

diff --git a/builtin/apply.c b/builtin/apply.c
index 8e4da2e..205c9f8f3 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3226,7 +3226,7 @@ static int load_patch_target(struct strbuf *buf,
 {
 	if (cached || check_index) {
 		if (read_file_or_gitlink(ce, buf))
-			return error(_("read of %s failed"), name);
+			return error(_("failed to read %s"), name);
 	} else if (name) {
 		if (S_ISGITLINK(expected_mode)) {
 			if (ce)
@@ -3237,7 +3237,7 @@ static int load_patch_target(struct strbuf *buf,
 			return error(_("reading from '%s' beyond a symbolic link"), name);
 		} else {
 			if (read_old_data(st, name, buf))
-				return error(_("read of %s failed"), name);
+				return error(_("failed to read %s"), name);
 		}
 	}
 	return 0;
@@ -3282,7 +3282,7 @@ static int load_preimage(struct image *image,
 			free_fragment_list(patch->fragments);
 			patch->fragments = NULL;
 		} else if (status) {
-			return error(_("read of %s failed"), patch->old_name);
+			return error(_("failed to read %s"), patch->old_name);
 		}
 	}
 
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 3398c61..05c7b71 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -276,7 +276,7 @@ static int checkout_paths(const struct checkout_opts *opts,
 
 	hold_locked_index(lock_file, 1);
 	if (read_cache_preload(&opts->pathspec) < 0)
-		return error(_("corrupt index file"));
+		return error(_("index file corrupt"));
 
 	if (opts->source_tree)
 		read_tree_some(opts->source_tree, &opts->pathspec);
@@ -470,7 +470,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
 
 	hold_locked_index(lock_file, 1);
 	if (read_cache_preload(NULL) < 0)
-		return error(_("corrupt index file"));
+		return error(_("index file corrupt"));
 
 	resolve_undo_clear();
 	if (opts->force) {
@@ -1138,7 +1138,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 		OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
 			   N_("create/reset and checkout a branch")),
 		OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
-		OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
+		OPT_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")),
 		OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
 			BRANCH_TRACK_EXPLICIT),
 		OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/builtin/repack.c b/builtin/repack.c
index 858db38..0108819 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -378,7 +378,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 					  item->string,
 					  exts[ext].name);
 			if (remove_path(fname))
-				warning(_("removing '%s' failed"), fname);
+				warning(_("failed to remove '%s'"), fname);
 			free(fname);
 		}
 	}
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 16:41 UTC · re: Vasco Almeida · lore

[PATCH v3 33/39] i18n: remote: mark URL fallback text for translation

Marks fallback text for translation that may be displayed in git remote show output.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/remote.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/remote.c +3 −3
diff --git a/builtin/remote.c b/builtin/remote.c
index ae74da6..9f934cb 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -1158,11 +1158,11 @@ static int show(int argc, const char **argv)
 			   the one in "  Fetch URL: %s" translation */
 			printf_ln(_("  Push  URL: %s"), url[i]);
 		if (!i)
-			printf_ln(_("  Push  URL: %s"), "(no URL)");
+			printf_ln(_("  Push  URL: %s"), _("(no URL)"));
 		if (no_query)
-			printf_ln(_("  HEAD branch: %s"), "(not queried)");
+			printf_ln(_("  HEAD branch: %s"), _("(not queried)"));
 		else if (!states.heads.nr)
-			printf_ln(_("  HEAD branch: %s"), "(unknown)");
+			printf_ln(_("  HEAD branch: %s"), _("(unknown)"));
 		else if (states.heads.nr == 1)
 			printf_ln(_("  HEAD branch: %s"), states.heads.items[0].string);
 		else {
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 18:12 UTC · re: Vasco Almeida · lore

[PATCH v3 34/39] i18n: remote: allow translations to reorder message

Before this patch, translations couldn't place the branch name where it was better fit in the message "and with remote <branch_name>". Allow translations that, instead of forcing the branch name to display right of the message.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/remote.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)
Show changes to builtin/remote.c +3 −4
diff --git a/builtin/remote.c b/builtin/remote.c
index 9f934cb..7f0f78b 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -952,7 +952,7 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 	struct show_info *show_info = cb_data;
 	struct branch_info *branch_info = item->util;
 	struct string_list *merge = &branch_info->merge;
-	const char *also;
+	int width = show_info->width + 4;
 	int i;
 
 	if (branch_info->rebase && branch_info->merge.nr > 1) {
@@ -969,13 +969,12 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data)
 		return 0;
 	} else if (show_info->any_rebase) {
 		printf_ln(_(" merges with remote %s"), merge->items[0].string);
-		also = _("    and with remote");
+		width++;
 	} else {
 		printf_ln(_("merges with remote %s"), merge->items[0].string);
-		also = _("   and with remote");
 	}
 	for (i = 1; i < merge->nr; i++)
-		printf("    %-*s %s %s\n", show_info->width, "", also,
+		printf(_("%-*s    and with remote %s\n"), width, "",
 		       merge->items[i].string);
 
 	return 0;
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 18:12 UTC · re: Vasco Almeida · lore

[PATCH v3 35/39] i18n: init-db: join message pieces

Join message displayed during repository initialization in one entire sentence. That would improve translations since it's easier translate an entire sentence than translating each piece.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/init-db.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
Show changes to builtin/init-db.c +10 −7
diff --git a/builtin/init-db.c b/builtin/init-db.c
index b2d8d40..3a45f0b 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -397,13 +397,16 @@ int init_db(const char *template_dir, unsigned int flags)
 	if (!(flags & INIT_DB_QUIET)) {
 		int len = strlen(git_dir);
 
-		/* TRANSLATORS: The first '%s' is either "Reinitialized
-		   existing" or "Initialized empty", the second " shared" or
-		   "", and the last '%s%s' is the verbatim directory name. */
-		printf(_("%s%s Git repository in %s%s\n"),
-		       reinit ? _("Reinitialized existing") : _("Initialized empty"),
-		       get_shared_repository() ? _(" shared") : "",
-		       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		if (reinit)
+			printf(get_shared_repository()
+			       ? _("Reinitialized existing shared Git repository in %s%s\n")
+			       : _("Reinitialized existing Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+		else
+			printf(get_shared_repository()
+			       ? _("Initialized empty shared Git repository in %s%s\n")
+			       : _("Initialized empty Git repository in %s%s\n"),
+			       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
 	}
 
 	return 0;
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 18:13 UTC · re: Vasco Almeida · lore

[PATCH v3 36/39] i18n: submodule: join strings marked for translation

Join strings marked for translation since that would facilitate and improve translations result.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-submodule.sh | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
Show changes to git-submodule.sh +10 −8
diff --git a/git-submodule.sh b/git-submodule.sh
index 5a4dec0..91fca56 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -275,14 +275,15 @@ Use -f if you really want to add it." >&2
 		then
 			if test -z "$force"
 			then
-				echo >&2 "$(eval_gettext "A git directory for '\$sm_name' is found locally with remote(s):")"
+				eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):"
 				GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^,"  ", -e s,' (fetch)',, >&2
-				echo >&2 "$(eval_gettext "If you want to reuse this local git directory instead of cloning again from")"
-				echo >&2 "  $realrepo"
-				echo >&2 "$(eval_gettext "use the '--force' option. If the local git directory is not the correct repo")"
-				die "$(eval_gettext "or you are unsure what this means choose another name with the '--name' option.")"
+				die "$(eval_gettextln "\
+If you want to reuse this local git directory instead of cloning again from
+  \$realrepo
+use the '--force' option. If the local git directory is not the correct repo
+or you are unsure what this means choose another name with the '--name' option.")"
 			else
-				echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
+				eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
 			fi
 		fi
 		git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit
@@ -467,8 +468,9 @@ cmd_deinit()
 			# Protect submodules containing a .git directory
 			if test -d "$sm_path/.git"
 			then
-				echo >&2 "$(eval_gettext "Submodule work tree '\$displaypath' contains a .git directory")"
-				die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
+				die "$(eval_gettext "\
+Submodule work tree '\$displaypath' contains a .git directory
+(use 'rm -rf' if you really want to remove it including all of its history)")"
 			fi
 
 			if test -z "$force"
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 18:13 UTC · re: Vasco Almeida · lore

[PATCH v3 37/39] i18n: submodule: escape shell variables inside eval_gettext

According to the gettext manual [1], references to shell variables inside eval_gettext call must be escaped so that eval_gettext receives the translatable string before the variable values are substituted into it.

[1] http://www.gnu.org/software/gettext/manual/html_node/Preparing-Shell-Scripts.html
Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-submodule.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Show changes to git-submodule.sh +2 −2
diff --git a/git-submodule.sh b/git-submodule.sh
index 91fca56..75a7ebe 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -648,7 +648,7 @@ cmd_update()
 			remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote)
 			sha1=$(sanitize_submodule_env; cd "$sm_path" &&
 				git rev-parse --verify "${remote_name}/${branch}") ||
-			die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")"
+			die "$(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
 		fi
 
 		if test "$subsha1" != "$sha1" || test -n "$force"
@@ -672,7 +672,7 @@ cmd_update()
 				# not be reachable from any of the refs
 				is_tip_reachable "$sm_path" "$sha1" ||
 				fetch_in_submodule "$sm_path" "$sha1" ||
-				die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain $sha1. Direct fetching of that commit failed.")"
+				die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
 			fi
 
 			must_die_on_failure=
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 18:13 UTC · re: Vasco Almeida · lore

[PATCH v3 38/39] i18n: unmark die messages for translation

These messages are relevant for the programmer only, not for the end user. Thus, they can be unmarked for translation, saving translator some work.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 git-bisect.sh | 2 +-
 wt-status.c   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)
Show changes to 2 files +3 −3

git-bisect.sh, wt-status.c

diff --git a/git-bisect.sh b/git-bisect.sh
index 737bf05..c9acab6 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -275,7 +275,7 @@ bisect_state() {
 	check_and_set_terms $state
 	case "$#,$state" in
 	0,*)
-		die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;;
+		die "Please call 'bisect_state' with at least one argument." ;;
 	1,"$TERM_BAD"|1,"$TERM_GOOD"|1,skip)
 		bisected_head=$(bisect_head)
 		rev=$(git rev-parse --verify "$bisected_head") ||
diff --git a/wt-status.c b/wt-status.c
index 4f27bd6..5a91279 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -263,7 +263,7 @@ static const char *wt_status_unmerged_status_string(int stagemask)
 	case 7:
 		return _("both modified:");
 	default:
-		die(_("bug: unhandled unmerged status %x"), stagemask);
+		die("bug: unhandled unmerged status %x", stagemask);
 	}
 }
 
@@ -388,7 +388,7 @@ static void wt_status_print_change_data(struct wt_status *s,
 	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
 	what = wt_status_diff_status_string(status);
 	if (!what)
-		die(_("bug: unhandled diff status %c"), status);
+		die("bug: unhandled diff status %c", status);
 	len = label_width - utf8_strwidth(what);
 	assert(len >= 0);
 	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
-- 
2.7.3
Vasco Almeida· Jun 1, 2016, 18:13 UTC · re: Vasco Almeida · lore

[PATCH v3 39/39] i18n: branch: mark comment when editing branch description for translation

When one issues git branch --edit-description branch_name, a edit with that message commented out is opened. Mark that message for translation in to order to be localized.

Signed-off-by: Vasco Almeida <vascomalmeida@sapo.pt>
---
 builtin/branch.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/branch.c +3 −3
diff --git a/builtin/branch.c b/builtin/branch.c
index 2ecde53..12203fd 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -614,9 +614,9 @@ static int edit_branch_description(const char *branch_name)
 	if (!buf.len || buf.buf[buf.len-1] != '\n')
 		strbuf_addch(&buf, '\n');
 	strbuf_commented_addf(&buf,
-		    "Please edit the description for the branch\n"
-		    "  %s\n"
-		    "Lines starting with '%c' will be stripped.\n",
+		    _("Please edit the description for the branch\n"
+		      "  %s\n"
+		      "Lines starting with '%c' will be stripped.\n"),
 		    branch_name, comment_line_char);
 	if (write_file_gently(git_path(edit_description), "%s", buf.buf)) {
 		strbuf_release(&buf);
-- 
2.7.3

← back to recent threads