[PATCH v2 00/11] Mark strings in perl script for translation

Subsystems: the rest

STALE3613d

Revision v2 of 2 in this series.

21 messages, 2 authors, 2016-10-03 · open the first message on its own page

[PATCH v2 00/11] Mark strings in perl script for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:32:48

Mark messages in some perl scripts for translation.

Since v1, adds brackets so parameter grouping of sprintf parameters is easier
to see.

Interdiff included below.

Vasco Almeida (11):
  i18n: add--interactive: mark strings for translation
  i18n: add--interactive: mark simple here documents for translation
  i18n: add--interactive: mark strings with interpolation for
    translation
  i18n: add--interactive: mark plural strings
  i18n: add--interactive: mark message for translation
  i18n: add--interactive: i18n of help_patch_cmd
  i18n: add--interactive: mark edit_hunk_manually message for
    translation
  i18n: send-email: mark strings for translation
  i18n: send-email: mark warnings and errors for translation
  i18n: send-email: mark string with interpolation for translation
  i18n: difftool: mark warnings for translation

 Makefile                  |   3 +-
 git-add--interactive.perl | 358 ++++++++++++++++++++++++++++++++++------------
 git-difftool.perl         |  18 +--
 git-send-email.perl       | 160 +++++++++++----------
 perl/Git/I18N.pm          |   4 +-
 t/t0202/test.pl           |  11 +-
 6 files changed, 376 insertions(+), 178 deletions(-)

---- >8 ----
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 1652a57..235142c 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -614,12 +614,12 @@ sub list_and_choose {
 			else {
 				$bottom = $top = find_unique($choice, @stuff);
 				if (!defined $bottom) {
-					error_msg sprintf __("Huh (%s)?\n"), $choice;
+					error_msg sprintf(__("Huh (%s)?\n"), $choice);
 					next TOPLOOP;
 				}
 			}
 			if ($opts->{SINGLETON} && $bottom != $top) {
-				error_msg sprintf __("Huh (%s)?\n"), $choice;
+				error_msg sprintf(__("Huh (%s)?\n"), $choice);
 				next TOPLOOP;
 			}
 			for ($i = $bottom-1; $i <= $top-1; $i++) {
@@ -669,17 +669,17 @@ sub say_n_paths {
 	my $did = shift @_;
 	my $cnt = scalar @_;
 	if ($did eq 'added') {
-		printf Q__("added one path\n", "added %d paths\n",
-			   $cnt), $cnt;
+		printf(Q__("added one path\n", "added %d paths\n",
+			   $cnt), $cnt);
 	} elsif ($did eq 'updated') {
-		printf Q__("updated one path\n", "updated %d paths\n",
-			   $cnt), $cnt;
+		printf(Q__("updated one path\n", "updated %d paths\n",
+			   $cnt), $cnt);
 	} elsif ($did eq 'reversed') {
-		printf Q__("reversed one path\n", "reversed %d paths\n",
-			   $cnt), $cnt;
+		printf(Q__("reversed one path\n", "reversed %d paths\n",
+			   $cnt), $cnt);
 	} else {
-		printf Q__("touched one path\n", "touched %d paths\n",
-			   $cnt), $cnt;
+		printf(Q__("touched one path\n", "touched %d paths\n",
+			   $cnt), $cnt);
 	}
 }
 
@@ -1056,12 +1056,12 @@ sub edit_hunk_manually {
 	my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
 	my $fh;
 	open $fh, '>', $hunkfile
-		or die sprintf __("failed to open hunk edit file for writing: %s"), $!;
+		or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
 	print $fh __("# Manual hunk edit mode -- see bottom for a quick guide\n");
 	print $fh @$oldtext;
 	my $is_reverse = $patch_mode_flavour{IS_REVERSE};
 	my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
-	print $fh (sprintf __(
+	print $fh sprintf(__(
 "# ---
 # To remove '%s' lines, make them ' ' lines (context).
 # To remove '%s' lines, delete them.
@@ -1121,7 +1121,7 @@ sub edit_hunk_manually {
 	}
 
 	open $fh, '<', $hunkfile
-		or die sprintf __("failed to open hunk edit file for reading: %s"), $!;
+		or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
 	my @newtext = grep { !/^#/ } <$fh>;
 	close $fh;
 	unlink $hunkfile;
@@ -1314,7 +1314,7 @@ sub apply_patch_for_checkout_commit {
 
 sub patch_update_cmd {
 	my @all_mods = list_modified($patch_mode_flavour{FILTER});
-	error_msg sprintf __("ignoring unmerged: %s\n"), $_->{VALUE}
+	error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
 		for grep { $_->{UNMERGED} } @all_mods;
 	@all_mods = grep { !$_->{UNMERGED} } @all_mods;
 
@@ -1458,100 +1458,79 @@ sub patch_update_file {
 		if ($patch_mode eq 'stage') {
 			if ($hunk[$ix]{TYPE} eq 'mode') {
 			  print colored $prompt_color,
-			    sprintf __("Stage mode change [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Stage mode change [y,n,q,a,d,/%s,?]? "), $other);
 			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
 			  print colored $prompt_color,
-			    sprintf __("Stage deletion [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Stage deletion [y,n,q,a,d,/%s,?]? "), $other);
 			} else {
 			  print colored $prompt_color,
-			    sprintf __("Stage this hunk [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Stage this hunk [y,n,q,a,d,/%s,?]? "), $other);
 			}
 		} elsif ($patch_mode eq 'stash') {
 			if ($hunk[$ix]{TYPE} eq 'mode') {
 			  print colored $prompt_color,
-			    sprintf __("Stash mode change [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Stash mode change [y,n,q,a,d,/%s,?]? "), $other);
 			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
 			  print colored $prompt_color,
-			    sprintf __("Stash deletion [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Stash deletion [y,n,q,a,d,/%s,?]? "), $other);
 			} else {
 			  print colored $prompt_color,
-			    sprintf __("Stash this hunk [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Stash this hunk [y,n,q,a,d,/%s,?]? "), $other);
 			}
 		} elsif ($patch_mode eq 'reset_head') {
 			if ($hunk[$ix]{TYPE} eq 'mode') {
 			  print colored $prompt_color,
-			    sprintf __("Unstage mode change [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Unstage mode change [y,n,q,a,d,/%s,?]? "), $other);
 			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
 			  print colored $prompt_color,
-			    sprintf __("Unstage deletion [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Unstage deletion [y,n,q,a,d,/%s,?]? "), $other);
 			} else {
 			  print colored $prompt_color,
-			    sprintf __("Unstage this hunk [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Unstage this hunk [y,n,q,a,d,/%s,?]? "), $other);
 			}
 		} elsif ($patch_mode eq 'reset_nothead') {
 			if ($hunk[$ix]{TYPE} eq 'mode') {
 			  print colored $prompt_color,
-			    sprintf __("Apply mode change to index [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Apply mode change to index [y,n,q,a,d,/%s,?]? "), $other);
 			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
 			  print colored $prompt_color,
-			    sprintf __("Apply deletion to index [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Apply deletion to index [y,n,q,a,d,/%s,?]? "), $other);
 			} else {
 			  print colored $prompt_color,
-			    sprintf __("Apply this hunk to index [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "), $other);
 			}
 		} elsif ($patch_mode eq 'checkout_index') {
 			if ($hunk[$ix]{TYPE} eq 'mode') {
 			  print colored $prompt_color,
-			    sprintf __("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "), $other);
 			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
 			  print colored $prompt_color,
-			    sprintf __("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "), $other);
 			} else {
 			  print colored $prompt_color,
-			    sprintf __("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "), $other);
 			}
 		} elsif ($patch_mode eq 'checkout_head') {
 			if ($hunk[$ix]{TYPE} eq 'mode') {
 			  print colored $prompt_color,
-			    sprintf __("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "), $other);
 			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
 			  print colored $prompt_color,
-			    sprintf __("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "), $other);
 			} else {
 			  print colored $prompt_color,
-			    sprintf __("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "), $other);
 			}
 		} elsif ($patch_mode eq 'checkout_nothead') {
 			if ($hunk[$ix]{TYPE} eq 'mode') {
 			  print colored $prompt_color,
-			    sprintf __("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "), $other);
 			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
 			  print colored $prompt_color,
-			    sprintf __("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "), $other);
 			} else {
 			  print colored $prompt_color,
-			    sprintf __("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "),
-				       $other;
+			    sprintf(__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "), $other);
 			}
 		}
 		my $line = prompt_single_character;
@@ -1589,13 +1568,13 @@ sub patch_update_file {
 					chomp $response;
 				}
 				if ($response !~ /^\s*\d+\s*$/) {
-					error_msg sprintf __("Invalid number: '%s'\n"),
-						      $response;
+					error_msg sprintf(__("Invalid number: '%s'\n"),
+							     $response);
 				} elsif (0 < $response && $response <= $num) {
 					$ix = $response - 1;
 				} else {
-					error_msg sprintf __("Sorry, only %s hunks available.\n"),
-						      $num;
+					error_msg sprintf(__("Sorry, only %s hunks available.\n"),
+							     $num);
 				}
 				next;
 			}
@@ -1633,7 +1612,7 @@ sub patch_update_file {
 				if ($@) {
 					my ($err,$exp) = ($@, $1);
 					$err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
-					error_msg sprintf __("Malformed search regexp %s: %s\n"), $exp, $err;
+					error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
 					next;
 				}
 				my $iy = $ix;
@@ -1690,10 +1669,10 @@ sub patch_update_file {
 			elsif ($other =~ /s/ && $line =~ /^s/) {
 				my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
 				if (1 < @split) {
-					print colored $header_color, sprintf
+					print colored $header_color, sprintf(
 						Q__("Split into %d hunk.\n",
 						    "Split into %d hunks.\n",
-						    scalar(@split)), scalar(@split);
+						    scalar(@split)), scalar(@split));
 				}
 				splice (@hunk, $ix, 1, @split);
 				$num = scalar @hunk;
@@ -1798,19 +1777,19 @@ sub process_args {
 				$patch_mode = $1;
 				$arg = shift @ARGV or die __("missing --");
 			} else {
-				die sprintf __("unknown --patch mode: %s"), $1;
+				die sprintf(__("unknown --patch mode: %s"), $1);
 			}
 		} else {
 			$patch_mode = 'stage';
 			$arg = shift @ARGV or die __("missing --");
 		}
-		die sprintf __("invalid argument %s, expecting --"),
-			       $arg unless $arg eq "--";
+		die sprintf(__("invalid argument %s, expecting --"),
+			       $arg) unless $arg eq "--";
 		%patch_mode_flavour = %{$patch_modes{$patch_mode}};
 		$cmd = 1;
 	}
 	elsif ($arg ne "--") {
-		die sprintf __("invalid argument %s, expecting --"), $arg;
+		die sprintf(__("invalid argument %s, expecting --"), $arg);
 	}
 }
 
diff --git a/git-difftool.perl b/git-difftool.perl
index fe7f003..de8d783 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -452,11 +452,11 @@ sub dir_diff
 		}
 
 		if (exists $wt_modified{$file} and exists $tmp_modified{$file}) {
-			warn sprintf __(
-"warning: Both files modified:
-'%s/%s' and '%s/%s'.
-warning: Working tree file has been left.
-warning:\n"), $workdir, $file, $b, $file;
+			warn sprintf(__(
+				"warning: Both files modified:\n" .
+				"'%s/%s' and '%s/%s'.\n" .
+				"warning: Working tree file has been left.\n" .
+				"warning:\n"), $workdir, $file, $b, $file);
 			$error = 1;
 		} elsif (exists $tmp_modified{$file}) {
 			my $mode = stat("$b/$file")->mode;
@@ -468,9 +468,9 @@ warning:\n"), $workdir, $file, $b, $file;
 		}
 	}
 	if ($error) {
-		warn sprintf __(
-"warning: Temporary files exist in '%s'.
-warning: You may want to cleanup or recover these.\n"), $tmpdir;
+		warn sprintf(__(
+			"warning: Temporary files exist in '%s'.\n" .
+			"warning: You may want to cleanup or recover these.\n"), $tmpdir);
 		exit(1);
 	} else {
 		exit_cleanup($tmpdir, $rc);
diff --git a/git-send-email.perl b/git-send-email.perl
index f445a5e..c29381b 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -434,7 +434,7 @@ $smtp_encryption = '' unless (defined $smtp_encryption);
 my(%suppress_cc);
 if (@suppress_cc) {
 	foreach my $entry (@suppress_cc) {
-		die sprintf __("Unknown --suppress-cc field: '%s'\n"), $entry
+		die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry)
 			unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/;
 		$suppress_cc{$entry} = 1;
 	}
@@ -585,12 +585,12 @@ sub is_format_patch_arg {
 		if (defined($format_patch)) {
 			return $format_patch;
 		}
-		die sprintf __(
+		die sprintf(__(
 "File '%s' exists but it could also be the range of commits
 to produce patches for.  Please disambiguate by...
 
     * Saying \"./%s\" if you mean a file; or
-    * Giving --format-patch option if you mean a range."), $f, $f;
+    * Giving --format-patch option if you mean a range."), $f, $f);
 	} catch Git::Error::Command with {
 		# Not a valid revision.  Treat it as a filename.
 		return 0;
@@ -606,7 +606,7 @@ while (defined(my $f = shift @ARGV)) {
 		@ARGV = ();
 	} elsif (-d $f and !is_format_patch_arg($f)) {
 		opendir my $dh, $f
-			or die sprintf __("Failed to opendir %s: %s"), $f, $!;
+			or die sprintf(__("Failed to opendir %s: %s"), $f, $!);
 
 		push @files, grep { -f $_ } map { catfile($f, $_) }
 				sort readdir $dh;
@@ -628,8 +628,8 @@ if ($validate) {
 	foreach my $f (@files) {
 		unless (-p $f) {
 			my $error = validate_patch($f);
-			$error and die sprintf __("fatal: %s: %s\nwarning: no patches were sent\n"),
-						  $f, $error;
+			$error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"),
+						  $f, $error);
 		}
 	}
 }
@@ -652,7 +652,7 @@ sub get_patch_subject {
 		return "GIT: $1\n";
 	}
 	close $fh;
-	die sprintf __("No subject line in %s ?"), $fn;
+	die sprintf(__("No subject line in %s ?"), $fn);
 }
 
 if ($compose) {
@@ -662,7 +662,7 @@ if ($compose) {
 		tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
 		tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
 	open my $c, ">", $compose_filename
-		or die sprintf __("Failed to open for writing %s: %s"), $compose_filename, $!;
+		or die sprintf(__("Failed to open for writing %s: %s"), $compose_filename, $!);
 
 
 	my $tpl_sender = $sender || $repoauthor || $repocommitter || '';
@@ -693,10 +693,10 @@ EOT
 	}
 
 	open my $c2, ">", $compose_filename . ".final"
-		or die sprintf __("Failed to open %s.final : %s"), $compose_filename, $!;
+		or die sprintf(__("Failed to open %s.final : %s"), $compose_filename, $!);
 
 	open $c, "<", $compose_filename
-		or die sprintf __("Failed to open %s : %s"), $compose_filename, $!;
+		or die sprintf(__("Failed to open %s : %s"), $compose_filename, $!);
 
 	my $need_8bit_cte = file_has_nonascii($compose_filename);
 	my $in_body = 0;
@@ -772,7 +772,7 @@ sub ask {
 		if ($confirm_only) {
 			# TRANSLATORS: Keep [y/N] as is.
 			my $yesno = $term->readline(
-				sprintf __("Are you sure you want to use <%s> [y/N]? "), $resp);
+				sprintf(__("Are you sure you want to use <%s> [y/N]? "), $resp));
 			if (defined $yesno && $yesno =~ /y/i) {
 				return $resp;
 			}
@@ -851,7 +851,7 @@ my %EXPANDED_ALIASES;
 sub expand_one_alias {
 	my $alias = shift;
 	if ($EXPANDED_ALIASES{$alias}) {
-		die sprintf __("fatal: alias '%s' expands to itself\n"), $alias;
+		die sprintf(__("fatal: alias '%s' expands to itself\n"), $alias);
 	}
 	local $EXPANDED_ALIASES{$alias} = 1;
 	return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
@@ -1226,7 +1226,7 @@ sub ssl_verify_params {
 		return (SSL_verify_mode => SSL_VERIFY_PEER(),
 			SSL_ca_file => $smtp_ssl_cert_path);
 	} else {
-		die sprintf __("CA path \"%s\" does not exist"), $smtp_ssl_cert_path;
+		die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path);
 	}
 }
 
@@ -1388,7 +1388,7 @@ Message-Id: $message_id
 					# supported commands
 					$smtp->hello($smtp_domain);
 				} else {
-					die sprintf __("Server does not support STARTTLS! %s"), $smtp->message;
+					die sprintf(__("Server does not support STARTTLS! %s"), $smtp->message);
 				}
 			}
 		}
@@ -1445,7 +1445,7 @@ $subject = $initial_subject;
 $message_num = 0;
 
 foreach my $t (@files) {
-	open my $fh, "<", $t or die sprintf __("can't open file %s"), $t;
+	open my $fh, "<", $t or die sprintf(__("can't open file %s"), $t);
 
 	my $author = undef;
 	my $sauthor = undef;
@@ -1678,7 +1678,7 @@ sub recipients_cmd {
 		       $prefix, $what, $address, $cmd) unless $quiet;
 		}
 	close $fh
-	    or die sprintf __("(%s) failed to close pipe to '%s'"), $prefix, $cmd;
+	    or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd);
 	return @addresses;
 }
 
@@ -1732,10 +1732,10 @@ sub unique_email_list {
 sub validate_patch {
 	my $fn = shift;
 	open(my $fh, '<', $fn)
-		or die sprintf __("unable to open %s: %s\n"), $fn, $!;
+		or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
 	while (my $line = <$fh>) {
 		if (length($line) > 998) {
-			return sprintf __("%s: patch contains a line longer than 998 characters"), $.;
+			return sprintf(__("%s: patch contains a line longer than 998 characters"), $.);
 		}
 	}
 	return;
@@ -1744,7 +1744,7 @@ sub validate_patch {
 sub file_has_nonascii {
 	my $fn = shift;
 	open(my $fh, '<', $fn)
-		or die sprintf __("unable to open %s: %s\n"), $fn, $!;
+		or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
 	while (my $line = <$fh>) {
 		return 1 if $line =~ /[^[:ascii:]]/;
 	}
@@ -1754,7 +1754,7 @@ sub file_has_nonascii {
 sub body_or_subject_has_nonascii {
 	my $fn = shift;
 	open(my $fh, '<', $fn)
-		or die sprintf __("unable to open %s: %s\n"), $fn, $!;
+		or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
 	while (my $line = <$fh>) {
 		last if $line =~ /^$/;
 		return 1 if $line =~ /^Subject.*[^[:ascii:]]/;
---- >8 ----
-- 
2.7.4

[PATCH v2 01/11] i18n: add--interactive: mark strings for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:32:52

Mark simple strings (without interpolation) for translation.

Brackets around first parameter of ternary operator is necessary because
otherwise xgettext fails to extract strings marked for translation from
the rest of the file.

Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 68 +++++++++++++++++++++++++----------------------
 1 file changed, 36 insertions(+), 32 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 822f857..fb8e5de 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -4,6 +4,7 @@ use 5.008;
 use strict;
 use warnings;
 use Git;
+use Git::I18N;
 
 binmode(STDOUT, ":raw");
 
@@ -252,7 +253,7 @@ sub list_untracked {
 }
 
 my $status_fmt = '%12s %12s %s';
-my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
+my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
 
 {
 	my $initial;
@@ -678,7 +679,7 @@ sub update_cmd {
 	my @mods = list_modified('file-only');
 	return if (!@mods);
 
-	my @update = list_and_choose({ PROMPT => 'Update',
+	my @update = list_and_choose({ PROMPT => __('Update'),
 				       HEADER => $status_head, },
 				     @mods);
 	if (@update) {
@@ -690,7 +691,7 @@ sub update_cmd {
 }
 
 sub revert_cmd {
-	my @update = list_and_choose({ PROMPT => 'Revert',
+	my @update = list_and_choose({ PROMPT => __('Revert'),
 				       HEADER => $status_head, },
 				     list_modified());
 	if (@update) {
@@ -724,13 +725,13 @@ sub revert_cmd {
 }
 
 sub add_untracked_cmd {
-	my @add = list_and_choose({ PROMPT => 'Add untracked' },
+	my @add = list_and_choose({ PROMPT => __('Add untracked') },
 				  list_untracked());
 	if (@add) {
 		system(qw(git update-index --add --), @add);
 		say_n_paths('added', @add);
 	} else {
-		print "No untracked files.\n";
+		print __("No untracked files.\n");
 	}
 	print "\n";
 }
@@ -1159,8 +1160,11 @@ sub edit_hunk_loop {
 		}
 		else {
 			prompt_yesno(
-				'Your edited hunk does not apply. Edit again '
-				. '(saying "no" discards!) [y/n]? '
+				# TRANSLATORS: do not translate [y/n]
+				# The program will only accept that input
+				# at this point.
+				__('Your edited hunk does not apply. Edit again '
+				   . '(saying "no" discards!) [y/n]? ')
 				) or return undef;
 		}
 	}
@@ -1206,11 +1210,11 @@ sub apply_patch_for_checkout_commit {
 		run_git_apply 'apply '.$reverse, @_;
 		return 1;
 	} elsif (!$applies_index) {
-		print colored $error_color, "The selected hunks do not apply to the index!\n";
-		if (prompt_yesno "Apply them to the worktree anyway? ") {
+		print colored $error_color, __("The selected hunks do not apply to the index!\n");
+		if (prompt_yesno __("Apply them to the worktree anyway? ")) {
 			return run_git_apply 'apply '.$reverse, @_;
 		} else {
-			print colored $error_color, "Nothing was applied.\n";
+			print colored $error_color, __("Nothing was applied.\n");
 			return 0;
 		}
 	} else {
@@ -1230,9 +1234,9 @@ sub patch_update_cmd {
 
 	if (!@mods) {
 		if (@all_mods) {
-			print STDERR "Only binary files changed.\n";
+			print STDERR __("Only binary files changed.\n");
 		} else {
-			print STDERR "No changes.\n";
+			print STDERR __("No changes.\n");
 		}
 		return 0;
 	}
@@ -1390,12 +1394,12 @@ sub patch_update_file {
 				my $response = $1;
 				my $no = $ix > 10 ? $ix - 10 : 0;
 				while ($response eq '') {
-					my $extra = "";
 					$no = display_hunks(\@hunk, $no);
 					if ($no < $num) {
-						$extra = " (<ret> to see more)";
+						print __("go to which hunk (<ret> to see more)? ");
+					} else {
+						print __("go to which hunk? ");
 					}
-					print "go to which hunk$extra? ";
 					$response = <STDIN>;
 					if (!defined $response) {
 						$response = '';
@@ -1432,7 +1436,7 @@ sub patch_update_file {
 			elsif ($line =~ m|^/(.*)|) {
 				my $regex = $1;
 				if ($1 eq "") {
-					print colored $prompt_color, "search for regex? ";
+					print colored $prompt_color, __("search for regex? ");
 					$regex = <STDIN>;
 					if (defined $regex) {
 						chomp $regex;
@@ -1455,7 +1459,7 @@ sub patch_update_file {
 					$iy++;
 					$iy = 0 if ($iy >= $num);
 					if ($ix == $iy) {
-						error_msg "No hunk matches the given pattern\n";
+						error_msg __("No hunk matches the given pattern\n");
 						last;
 					}
 				}
@@ -1467,7 +1471,7 @@ sub patch_update_file {
 					$ix--;
 				}
 				else {
-					error_msg "No previous hunk\n";
+					error_msg __("No previous hunk\n");
 				}
 				next;
 			}
@@ -1476,7 +1480,7 @@ sub patch_update_file {
 					$ix++;
 				}
 				else {
-					error_msg "No next hunk\n";
+					error_msg __("No next hunk\n");
 				}
 				next;
 			}
@@ -1489,13 +1493,13 @@ sub patch_update_file {
 					}
 				}
 				else {
-					error_msg "No previous hunk\n";
+					error_msg __("No previous hunk\n");
 				}
 				next;
 			}
 			elsif ($line =~ /^j/) {
 				if ($other !~ /j/) {
-					error_msg "No next hunk\n";
+					error_msg __("No next hunk\n");
 					next;
 				}
 			}
@@ -1553,18 +1557,18 @@ sub diff_cmd {
 	my @mods = list_modified('index-only');
 	@mods = grep { !($_->{BINARY}) } @mods;
 	return if (!@mods);
-	my (@them) = list_and_choose({ PROMPT => 'Review diff',
+	my (@them) = list_and_choose({ PROMPT => __('Review diff'),
 				     IMMEDIATE => 1,
 				     HEADER => $status_head, },
 				   @mods);
 	return if (!@them);
-	my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
+	my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
 	system(qw(git diff -p --cached), $reference, '--',
 		map { $_->{VALUE} } @them);
 }
 
 sub quit_cmd {
-	print "Bye.\n";
+	print __("Bye.\n");
 	exit(0);
 }
 
@@ -1587,32 +1591,32 @@ sub process_args {
 			if ($1 eq 'reset') {
 				$patch_mode = 'reset_head';
 				$patch_mode_revision = 'HEAD';
-				$arg = shift @ARGV or die "missing --";
+				$arg = shift @ARGV or die __("missing --");
 				if ($arg ne '--') {
 					$patch_mode_revision = $arg;
 					$patch_mode = ($arg eq 'HEAD' ?
 						       'reset_head' : 'reset_nothead');
-					$arg = shift @ARGV or die "missing --";
+					$arg = shift @ARGV or die __("missing --");
 				}
 			} elsif ($1 eq 'checkout') {
-				$arg = shift @ARGV or die "missing --";
+				$arg = shift @ARGV or die __("missing --");
 				if ($arg eq '--') {
 					$patch_mode = 'checkout_index';
 				} else {
 					$patch_mode_revision = $arg;
 					$patch_mode = ($arg eq 'HEAD' ?
 						       'checkout_head' : 'checkout_nothead');
-					$arg = shift @ARGV or die "missing --";
+					$arg = shift @ARGV or die __("missing --");
 				}
 			} elsif ($1 eq 'stage' or $1 eq 'stash') {
 				$patch_mode = $1;
-				$arg = shift @ARGV or die "missing --";
+				$arg = shift @ARGV or die __("missing --");
 			} else {
 				die "unknown --patch mode: $1";
 			}
 		} else {
 			$patch_mode = 'stage';
-			$arg = shift @ARGV or die "missing --";
+			$arg = shift @ARGV or die __("missing --");
 		}
 		die "invalid argument $arg, expecting --"
 		    unless $arg eq "--";
@@ -1634,10 +1638,10 @@ sub main_loop {
 		   [ 'help', \&help_cmd, ],
 	);
 	while (1) {
-		my ($it) = list_and_choose({ PROMPT => 'What now',
+		my ($it) = list_and_choose({ PROMPT => __('What now'),
 					     SINGLETON => 1,
 					     LIST_FLAT => 4,
-					     HEADER => '*** Commands ***',
+					     HEADER => __('*** Commands ***'),
 					     ON_EOF => \&quit_cmd,
 					     IMMEDIATE => 1 }, @cmd);
 		if ($it) {
-- 
2.7.4

[PATCH v2 02/11] i18n: add--interactive: mark simple here documents for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:32:57

Mark messages in here document without interpolation for translation.

Marking for translation by removing here documents this way, rather than
take advantage of "print __ << EOF" way, makes other instances of help
messages in clean.c match the first two in this file.  Otherwise,
reusing here document would add a trailer newline to the message, making
them not match 100%, hence creating two entries in pot template for
translation rather than a single entry.

Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index fb8e5de..e11a33d 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -636,25 +636,25 @@ sub list_and_choose {
 }
 
 sub singleton_prompt_help_cmd {
-	print colored $help_color, <<\EOF ;
-Prompt help:
+	print colored $help_color, __(
+"Prompt help:
 1          - select a numbered item
 foo        - select item based on unique prefix
-           - (empty) select nothing
-EOF
+           - (empty) select nothing"),
+"\n";
 }
 
 sub prompt_help_cmd {
-	print colored $help_color, <<\EOF ;
-Prompt help:
+	print colored $help_color, __(
+"Prompt help:
 1          - select a single item
 3-5        - select a range of items
 2-3,6-9    - select multiple ranges
 foo        - select item based on unique prefix
 -...       - unselect specified items
 *          - choose all items
-           - (empty) finish selecting
-EOF
+           - (empty) finish selecting"),
+"\n";
 }
 
 sub status_cmd {
@@ -1573,14 +1573,14 @@ sub quit_cmd {
 }
 
 sub help_cmd {
-	print colored $help_color, <<\EOF ;
-status        - show paths with changes
+	print colored $help_color, __(
+"status        - show paths with changes
 update        - add working tree state to the staged set of changes
 revert        - revert staged set of changes back to the HEAD version
 patch         - pick hunks and update selectively
 diff	      - view diff between HEAD and index
-add untracked - add contents of untracked files to the staged set of changes
-EOF
+add untracked - add contents of untracked files to the staged set of changes"),
+"\n";
 }
 
 sub process_args {
-- 
2.7.4

[PATCH v2 03/11] i18n: add--interactive: mark strings with interpolation for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:00

Use of sprintf following die or error_msg is necessary for placeholder
substitution take place.

Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index e11a33d..4e1e857 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -612,12 +612,12 @@ sub list_and_choose {
 			else {
 				$bottom = $top = find_unique($choice, @stuff);
 				if (!defined $bottom) {
-					error_msg "Huh ($choice)?\n";
+					error_msg sprintf(__("Huh (%s)?\n"), $choice);
 					next TOPLOOP;
 				}
 			}
 			if ($opts->{SINGLETON} && $bottom != $top) {
-				error_msg "Huh ($choice)?\n";
+				error_msg sprintf(__("Huh (%s)?\n"), $choice);
 				next TOPLOOP;
 			}
 			for ($i = $bottom-1; $i <= $top-1; $i++) {
@@ -1048,7 +1048,7 @@ sub edit_hunk_manually {
 	my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
 	my $fh;
 	open $fh, '>', $hunkfile
-		or die "failed to open hunk edit file for writing: " . $!;
+		or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
 	print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
 	print $fh @$oldtext;
 	my $participle = $patch_mode_flavour{PARTICIPLE};
@@ -1075,7 +1075,7 @@ EOF
 	}
 
 	open $fh, '<', $hunkfile
-		or die "failed to open hunk edit file for reading: " . $!;
+		or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
 	my @newtext = grep { !/^#/ } <$fh>;
 	close $fh;
 	unlink $hunkfile;
@@ -1225,7 +1225,7 @@ sub apply_patch_for_checkout_commit {
 
 sub patch_update_cmd {
 	my @all_mods = list_modified($patch_mode_flavour{FILTER});
-	error_msg "ignoring unmerged: $_->{VALUE}\n"
+	error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
 		for grep { $_->{UNMERGED} } @all_mods;
 	@all_mods = grep { !$_->{UNMERGED} } @all_mods;
 
@@ -1407,11 +1407,13 @@ sub patch_update_file {
 					chomp $response;
 				}
 				if ($response !~ /^\s*\d+\s*$/) {
-					error_msg "Invalid number: '$response'\n";
+					error_msg sprintf(__("Invalid number: '%s'\n"),
+							     $response);
 				} elsif (0 < $response && $response <= $num) {
 					$ix = $response - 1;
 				} else {
-					error_msg "Sorry, only $num hunks available.\n";
+					error_msg sprintf(__("Sorry, only %s hunks available.\n"),
+							     $num);
 				}
 				next;
 			}
@@ -1449,7 +1451,7 @@ sub patch_update_file {
 				if ($@) {
 					my ($err,$exp) = ($@, $1);
 					$err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
-					error_msg "Malformed search regexp $exp: $err\n";
+					error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
 					next;
 				}
 				my $iy = $ix;
@@ -1612,18 +1614,18 @@ sub process_args {
 				$patch_mode = $1;
 				$arg = shift @ARGV or die __("missing --");
 			} else {
-				die "unknown --patch mode: $1";
+				die sprintf(__("unknown --patch mode: %s"), $1);
 			}
 		} else {
 			$patch_mode = 'stage';
 			$arg = shift @ARGV or die __("missing --");
 		}
-		die "invalid argument $arg, expecting --"
-		    unless $arg eq "--";
+		die sprintf(__("invalid argument %s, expecting --"),
+			       $arg) unless $arg eq "--";
 		%patch_mode_flavour = %{$patch_modes{$patch_mode}};
 	}
 	elsif ($arg ne "--") {
-		die "invalid argument $arg, expecting --";
+		die sprintf(__("invalid argument %s, expecting --"), $arg);
 	}
 }
 
-- 
2.7.4

[PATCH v2 04/11] i18n: add--interactive: mark plural strings

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:04

Mark plural strings for translation.  Unfold each action case in one
entire sentence.

Pass new keyword for xgettext to extract.

Update test to include new subrotine Q__() for plural strings handling.

Signed-off-by: Vasco Almeida <redacted>
---
 Makefile                  |  3 ++-
 git-add--interactive.perl | 24 ++++++++++++++++--------
 perl/Git/I18N.pm          |  4 +++-
 t/t0202/test.pl           | 11 ++++++++++-
 4 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/Makefile b/Makefile
index de5a030..eedf1fa 100644
--- a/Makefile
+++ b/Makefile
@@ -2061,7 +2061,8 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
 	--keyword=_ --keyword=N_ --keyword="Q_:1,2"
 XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \
 	--keyword=gettextln --keyword=eval_gettextln
-XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
+XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
+	--keyword=__ --keyword="Q__:1,2"
 LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
 LOCALIZED_SH = $(SCRIPT_SH) git-parse-remote.sh
 LOCALIZED_PERL = $(SCRIPT_PERL)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 4e1e857..08badfa 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -666,12 +666,18 @@ sub status_cmd {
 sub say_n_paths {
 	my $did = shift @_;
 	my $cnt = scalar @_;
-	print "$did ";
-	if (1 < $cnt) {
-		print "$cnt paths\n";
-	}
-	else {
-		print "one path\n";
+	if ($did eq 'added') {
+		printf(Q__("added one path\n", "added %d paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'updated') {
+		printf(Q__("updated one path\n", "updated %d paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'reversed') {
+		printf(Q__("reversed one path\n", "reversed %d paths\n",
+			   $cnt), $cnt);
+	} else {
+		printf(Q__("touched one path\n", "touched %d paths\n",
+			   $cnt), $cnt);
 	}
 }
 
@@ -1508,8 +1514,10 @@ sub patch_update_file {
 			elsif ($other =~ /s/ && $line =~ /^s/) {
 				my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
 				if (1 < @split) {
-					print colored $header_color, "Split into ",
-					scalar(@split), " hunks.\n";
+					print colored $header_color, sprintf(
+						Q__("Split into %d hunk.\n",
+						    "Split into %d hunks.\n",
+						    scalar(@split)), scalar(@split));
 				}
 				splice (@hunk, $ix, 1, @split);
 				$num = scalar @hunk;
diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm
index f889fd6..5a75dd5 100644
--- a/perl/Git/I18N.pm
+++ b/perl/Git/I18N.pm
@@ -13,7 +13,7 @@ BEGIN {
 	}
 }
 
-our @EXPORT = qw(__);
+our @EXPORT = qw(__ Q__);
 our @EXPORT_OK = @EXPORT;
 
 sub __bootstrap_locale_messages {
@@ -44,6 +44,7 @@ BEGIN
 	eval {
 		__bootstrap_locale_messages();
 		*__ = \&Locale::Messages::gettext;
+		*Q__ = \&Locale::Messages::ngettext;
 		1;
 	} or do {
 		# Tell test.pl that we couldn't load the gettext library.
@@ -51,6 +52,7 @@ BEGIN
 
 		# Just a fall-through no-op
 		*__ = sub ($) { $_[0] };
+		*Q__ = sub ($$$) { $_[2] == 1 ? $_[0] : $_[1] };
 	};
 }
 
diff --git a/t/t0202/test.pl b/t/t0202/test.pl
index 2c10cb4..98aa9a3 100755
--- a/t/t0202/test.pl
+++ b/t/t0202/test.pl
@@ -4,7 +4,7 @@ use lib (split(/:/, $ENV{GITPERLLIB}));
 use strict;
 use warnings;
 use POSIX qw(:locale_h);
-use Test::More tests => 8;
+use Test::More tests => 11;
 use Git::I18N;
 
 my $has_gettext_library = $Git::I18N::__HAS_LIBRARY;
@@ -31,6 +31,7 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export
 	# more gettext wrapper functions.
 	my %prototypes = (qw(
 		__	$
+		Q__	$$$
 	));
 	while (my ($sub, $proto) = each %prototypes) {
 		is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype");
@@ -46,6 +47,14 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export
 	my ($got, $expect) = (('TEST: A Perl test string') x 2);
 
 	is(__($got), $expect, "Passing a string through __() in the C locale works");
+
+	my ($got_singular, $got_plural, $expect_singular, $expect_plural) =
+		(('TEST: 1 file', 'TEST: n files') x 2);
+
+	is(Q__($got_singular, $got_plural, 1), $expect_singular,
+		"Get singular string through Q__() in C locale");
+	is(Q__($got_singular, $got_plural, 2), $expect_plural,
+		"Get plural string through Q__() in C locale");
 }
 
 # Test a basic message on different locales
-- 
2.7.4

[PATCH v2 05/11] i18n: add--interactive: mark message for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:09

Mark message assembled in place for translation, unfolding each use case
for each entry in the %patch_modes hash table.

Previously, this script relied on whether $patch_mode was set to run the
command patch_update_cmd() or show status and loop the main loop. Now,
it uses $cmd to indicate we must run patch_update_cmd() and $patch_mode
is used to tell which flavor of the %patch_modes are we on.  This is
introduced in order to be able to mark and unfold the message prompt
knowing in which context we are.

The tracking of context was done previously by point %patch_mode_flavour
hash table to the correct entry of %patch_modes, focusing only on value
of %patch_modes. Now, we are also interested in the key ('staged',
'stash', 'checkout_head', ...).

Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 91 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 83 insertions(+), 8 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 08badfa..5b89b97 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -91,6 +91,7 @@ sub colored {
 }
 
 # command line options
+my $cmd;
 my $patch_mode;
 my $patch_mode_revision;
 
@@ -171,7 +172,8 @@ my %patch_modes = (
 	},
 );
 
-my %patch_mode_flavour = %{$patch_modes{stage}};
+$patch_mode = 'stage';
+my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
 
 sub run_cmd_pipe {
 	if ($^O eq 'MSWin32') {
@@ -1372,12 +1374,84 @@ sub patch_update_file {
 		for (@{$hunk[$ix]{DISPLAY}}) {
 			print;
 		}
-		print colored $prompt_color, $patch_mode_flavour{VERB},
-		  ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' :
-		   $hunk[$ix]{TYPE} eq 'deletion' ? ' deletion' :
-		   ' this hunk'),
-		  $patch_mode_flavour{TARGET},
-		  " [y,n,q,a,d,/$other,?]? ";
+		if ($patch_mode eq 'stage') {
+			if ($hunk[$ix]{TYPE} eq 'mode') {
+			  print colored $prompt_color,
+			    sprintf(__("Stage mode change [y,n,q,a,d,/%s,?]? "), $other);
+			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
+			  print colored $prompt_color,
+			    sprintf(__("Stage deletion [y,n,q,a,d,/%s,?]? "), $other);
+			} else {
+			  print colored $prompt_color,
+			    sprintf(__("Stage this hunk [y,n,q,a,d,/%s,?]? "), $other);
+			}
+		} elsif ($patch_mode eq 'stash') {
+			if ($hunk[$ix]{TYPE} eq 'mode') {
+			  print colored $prompt_color,
+			    sprintf(__("Stash mode change [y,n,q,a,d,/%s,?]? "), $other);
+			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
+			  print colored $prompt_color,
+			    sprintf(__("Stash deletion [y,n,q,a,d,/%s,?]? "), $other);
+			} else {
+			  print colored $prompt_color,
+			    sprintf(__("Stash this hunk [y,n,q,a,d,/%s,?]? "), $other);
+			}
+		} elsif ($patch_mode eq 'reset_head') {
+			if ($hunk[$ix]{TYPE} eq 'mode') {
+			  print colored $prompt_color,
+			    sprintf(__("Unstage mode change [y,n,q,a,d,/%s,?]? "), $other);
+			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
+			  print colored $prompt_color,
+			    sprintf(__("Unstage deletion [y,n,q,a,d,/%s,?]? "), $other);
+			} else {
+			  print colored $prompt_color,
+			    sprintf(__("Unstage this hunk [y,n,q,a,d,/%s,?]? "), $other);
+			}
+		} elsif ($patch_mode eq 'reset_nothead') {
+			if ($hunk[$ix]{TYPE} eq 'mode') {
+			  print colored $prompt_color,
+			    sprintf(__("Apply mode change to index [y,n,q,a,d,/%s,?]? "), $other);
+			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
+			  print colored $prompt_color,
+			    sprintf(__("Apply deletion to index [y,n,q,a,d,/%s,?]? "), $other);
+			} else {
+			  print colored $prompt_color,
+			    sprintf(__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "), $other);
+			}
+		} elsif ($patch_mode eq 'checkout_index') {
+			if ($hunk[$ix]{TYPE} eq 'mode') {
+			  print colored $prompt_color,
+			    sprintf(__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "), $other);
+			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
+			  print colored $prompt_color,
+			    sprintf(__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "), $other);
+			} else {
+			  print colored $prompt_color,
+			    sprintf(__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "), $other);
+			}
+		} elsif ($patch_mode eq 'checkout_head') {
+			if ($hunk[$ix]{TYPE} eq 'mode') {
+			  print colored $prompt_color,
+			    sprintf(__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "), $other);
+			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
+			  print colored $prompt_color,
+			    sprintf(__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "), $other);
+			} else {
+			  print colored $prompt_color,
+			    sprintf(__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "), $other);
+			}
+		} elsif ($patch_mode eq 'checkout_nothead') {
+			if ($hunk[$ix]{TYPE} eq 'mode') {
+			  print colored $prompt_color,
+			    sprintf(__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "), $other);
+			} elsif ($hunk[$ix]{TYPE} eq 'deletion') {
+			  print colored $prompt_color,
+			    sprintf(__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "), $other);
+			} else {
+			  print colored $prompt_color,
+			    sprintf(__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "), $other);
+			}
+		}
 		my $line = prompt_single_character;
 		last unless defined $line;
 		if ($line) {
@@ -1631,6 +1705,7 @@ sub process_args {
 		die sprintf(__("invalid argument %s, expecting --"),
 			       $arg) unless $arg eq "--";
 		%patch_mode_flavour = %{$patch_modes{$patch_mode}};
+		$cmd = 1;
 	}
 	elsif ($arg ne "--") {
 		die sprintf(__("invalid argument %s, expecting --"), $arg);
@@ -1667,7 +1742,7 @@ sub main_loop {
 
 process_args();
 refresh();
-if ($patch_mode) {
+if ($cmd) {
 	patch_update_cmd();
 }
 else {
-- 
2.7.4

[PATCH v2 06/11] i18n: add--interactive: i18n of help_patch_cmd

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:14

Mark help message of help_patch_cmd for translation.  The message must
be unfolded to be free of variables so we can have high quality
translations.

Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 65 +++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 54 insertions(+), 11 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 5b89b97..acbfa4e 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -1179,15 +1179,58 @@ sub edit_hunk_loop {
 }
 
 sub help_patch_cmd {
-	my $verb = lc $patch_mode_flavour{VERB};
-	my $target = $patch_mode_flavour{TARGET};
-	print colored $help_color, <<EOF ;
-y - $verb this hunk$target
-n - do not $verb this hunk$target
-q - quit; do not $verb this hunk or any of the remaining ones
-a - $verb this hunk and all later hunks in the file
-d - do not $verb this hunk or any of the later hunks in the file
-g - select a hunk to go to
+	if ($patch_mode eq 'stage') {
+		print colored $help_color, __(
+"y - stage this hunk
+n - do not stage this hunk
+q - quit; do not stage this hunk or any of the remaining ones
+a - stage this hunk and all later hunks in the file
+d - do not stage this hunk or any of the later hunks in the file");
+	} elsif ($patch_mode eq 'stash') {
+		print colored $help_color, __(
+"y - stash this hunk
+n - do not stash this hunk
+q - quit; do not stash this hunk or any of the remaining ones
+a - stash this hunk and all later hunks in the file
+d - do not stash this hunk or any of the later hunks in the file");
+	} elsif ($patch_mode eq 'reset_head') {
+		print colored $help_color, __(
+"y - unstage this hunk
+n - do not unstage this hunk
+q - quit; do not unstage this hunk or any of the remaining ones
+a - unstage this hunk and all later hunks in the file
+d - do not unstage this hunk or any of the later hunks in the file");
+	} elsif ($patch_mode eq 'reset_nothead') {
+		print colored $help_color, __(
+"y - apply this hunk to index
+n - do not apply this hunk to index
+q - quit; do not apply this hunk or any of the remaining ones
+a - apply this hunk and all later hunks in the file
+d - do not apply this hunk or any of the later hunks in the file");
+	} elsif ($patch_mode eq 'checkout_index') {
+		print colored $help_color, __(
+"y - discard this hunk from worktree
+n - do not discard this hunk from worktree
+q - quit; do not discard this hunk or any of the remaining ones
+a - discard this hunk and all later hunks in the file
+d - do not discard this hunk or any of the later hunks in the file");
+	} elsif ($patch_mode eq 'checkout_head') {
+		print colored $help_color, __(
+"y - discard this hunk from index and worktree
+n - do not discard this hunk from index and worktree
+q - quit; do not discard this hunk or any of the remaining ones
+a - discard this hunk and all later hunks in the file
+d - do not discard this hunk or any of the later hunks in the file");
+	} elsif ($patch_mode eq 'checkout_nothead') {
+		print colored $help_color, __(
+"y - apply this hunk to index and worktree
+n - do not apply this hunk to index and worktree
+q - quit; do not apply this hunk or any of the remaining ones
+a - apply this hunk and all later hunks in the file
+d - do not apply this hunk or any of the later hunks in the file");
+	}
+	print colored $help_color, "\n", __(
+"g - select a hunk to go to
 / - search for a hunk matching the given regex
 j - leave this hunk undecided, see next undecided hunk
 J - leave this hunk undecided, see next hunk
@@ -1195,8 +1238,8 @@ k - leave this hunk undecided, see previous undecided hunk
 K - leave this hunk undecided, see previous hunk
 s - split the current hunk into smaller hunks
 e - manually edit the current hunk
-? - print help
-EOF
+? - print help"),
+"\n";
 }
 
 sub apply_patch {
-- 
2.7.4

[PATCH v2 07/11] i18n: add--interactive: mark edit_hunk_manually message for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:18

Mark message of edit_hunk_manually displayed in the editing file when
user chooses 'e' option.  The message had to be unfolded to allow
translation of the $participle verb.

Some messages end up being exactly the same for some uses cases, but
left it for easier change in the future, e.g., wanting to change wording
of one particular use case.

Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 60 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 49 insertions(+), 11 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index acbfa4e..235142c 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -1057,22 +1057,60 @@ sub edit_hunk_manually {
 	my $fh;
 	open $fh, '>', $hunkfile
 		or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
-	print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
+	print $fh __("# Manual hunk edit mode -- see bottom for a quick guide\n");
 	print $fh @$oldtext;
-	my $participle = $patch_mode_flavour{PARTICIPLE};
 	my $is_reverse = $patch_mode_flavour{IS_REVERSE};
 	my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
-	print $fh <<EOF;
-# ---
-# To remove '$remove_minus' lines, make them ' ' lines (context).
-# To remove '$remove_plus' lines, delete them.
+	print $fh sprintf(__(
+"# ---
+# To remove '%s' lines, make them ' ' lines (context).
+# To remove '%s' lines, delete them.
 # Lines starting with # will be removed.
-#
-# If the patch applies cleanly, the edited hunk will immediately be
-# marked for $participle. If it does not apply cleanly, you will be given
+#"), $remove_minus, $remove_plus),
+"\n";
+	if ($patch_mode eq 'stage') {
+		print $fh __(
+"# If the patch applies cleanly, the edited hunk will immediately be
+# marked for staging. If it does not apply cleanly, you will be given
+# an opportunity to edit again. If all lines of the hunk are removed,
+# then the edit is aborted and the hunk is left unchanged.");
+	} elsif ($patch_mode eq 'stash') {
+		print $fh __(
+"# If the patch applies cleanly, the edited hunk will immediately be
+# marked for stashing. If it does not apply cleanly, you will be given
+# an opportunity to edit again. If all lines of the hunk are removed,
+# then the edit is aborted and the hunk is left unchanged.");
+	} elsif ($patch_mode eq 'reset_head') {
+		print $fh __(
+"# If the patch applies cleanly, the edited hunk will immediately be
+# marked for unstaging. If it does not apply cleanly, you will be given
+# an opportunity to edit again. If all lines of the hunk are removed,
+# then the edit is aborted and the hunk is left unchanged.");
+	} elsif ($patch_mode eq 'reset_nothead') {
+		print $fh __(
+"# If the patch applies cleanly, the edited hunk will immediately be
+# marked for applying. If it does not apply cleanly, you will be given
 # an opportunity to edit again. If all lines of the hunk are removed,
-# then the edit is aborted and the hunk is left unchanged.
-EOF
+# then the edit is aborted and the hunk is left unchanged.");
+	} elsif ($patch_mode eq 'checkout_index') {
+		print $fh __(
+"# If the patch applies cleanly, the edited hunk will immediately be
+# marked for discarding. If it does not apply cleanly, you will be given
+# an opportunity to edit again. If all lines of the hunk are removed,
+# then the edit is aborted and the hunk is left unchanged.");
+	} elsif ($patch_mode eq 'checkout_head') {
+		print $fh __(
+"# If the patch applies cleanly, the edited hunk will immediately be
+# marked for discarding. If it does not apply cleanly, you will be given
+# an opportunity to edit again. If all lines of the hunk are removed,
+# then the edit is aborted and the hunk is left unchanged.");
+	} elsif ($patch_mode eq 'checkout_nothead') {
+		print $fh __(
+"# If the patch applies cleanly, the edited hunk will immediately be
+# marked for applying. If it does not apply cleanly, you will be given
+# an opportunity to edit again. If all lines of the hunk are removed,
+# then the edit is aborted and the hunk is left unchanged.");
+	}
 	close $fh;
 
 	chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
-- 
2.7.4

[PATCH v2 08/11] i18n: send-email: mark strings for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:38

Mark strings often displayed to user for translation.

Signed-off-by: Vasco Almeida <redacted>
---
 git-send-email.perl | 53 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 31 insertions(+), 22 deletions(-)
diff --git a/git-send-email.perl b/git-send-email.perl
index 6958785..2521832 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -28,6 +28,7 @@ use File::Temp qw/ tempdir tempfile /;
 use File::Spec::Functions qw(catfile);
 use Error qw(:try);
 use Git;
+use Git::I18N;
 
 Getopt::Long::Configure qw/ pass_through /;
 
@@ -795,12 +796,12 @@ foreach my $f (@files) {
 }
 
 if (!defined $auto_8bit_encoding && scalar %broken_encoding) {
-	print "The following files are 8bit, but do not declare " .
-		"a Content-Transfer-Encoding.\n";
+	print __("The following files are 8bit, but do not declare " .
+		 "a Content-Transfer-Encoding.\n");
 	foreach my $f (sort keys %broken_encoding) {
 		print "    $f\n";
 	}
-	$auto_8bit_encoding = ask("Which 8bit encoding should I declare [UTF-8]? ",
+	$auto_8bit_encoding = ask(__("Which 8bit encoding should I declare [UTF-8]? "),
 				  valid_re => qr/.{4}/, confirm_only => 1,
 				  default => "UTF-8");
 }
@@ -827,7 +828,7 @@ if (defined $sender) {
 # But it's a no-op to run sanitize_address on an already sanitized address.
 $sender = sanitize_address($sender);
 
-my $to_whom = "To whom should the emails be sent (if anyone)?";
+my $to_whom = __("To whom should the emails be sent (if anyone)?");
 my $prompting = 0;
 if (!@initial_to && !defined $to_cmd) {
 	my $to = ask("$to_whom ",
@@ -857,7 +858,7 @@ sub expand_one_alias {
 
 if ($thread && !defined $initial_reply_to && $prompting) {
 	$initial_reply_to = ask(
-		"Message-ID to be used as In-Reply-To for the first email (if any)? ",
+		__("Message-ID to be used as In-Reply-To for the first email (if any)? "),
 		default => "",
 		valid_re => qr/\@.*\./, confirm_only => 1);
 }
@@ -916,7 +917,10 @@ sub validate_address {
 	my $address = shift;
 	while (!extract_valid_address($address)) {
 		print STDERR "error: unable to extract a valid address from: $address\n";
-		$_ = ask("What to do with this address? ([q]uit|[d]rop|[e]dit): ",
+		# TRANSLATORS: Make sure to include [q] [d] [e] in your
+		# translation. The program will only accept English input
+		# at this point.
+		$_ = ask(__("What to do with this address? ([q]uit|[d]rop|[e]dit): "),
 			valid_re => qr/^(?:quit|q|drop|d|edit|e)/i,
 			default => 'q');
 		if (/^d/i) {
@@ -1291,17 +1295,22 @@ Message-Id: $message_id
 		if ($needs_confirm eq "inform") {
 			$confirm_unconfigured = 0; # squelch this message for the rest of this run
 			$ask_default = "y"; # assume yes on EOF since user hasn't explicitly asked for confirmation
-			print "    The Cc list above has been expanded by additional\n";
-			print "    addresses found in the patch commit message. By default\n";
-			print "    send-email prompts before sending whenever this occurs.\n";
-			print "    This behavior is controlled by the sendemail.confirm\n";
-			print "    configuration setting.\n";
-			print "\n";
-			print "    For additional information, run 'git send-email --help'.\n";
-			print "    To retain the current behavior, but squelch this message,\n";
-			print "    run 'git config --global sendemail.confirm auto'.\n\n";
+			print __(
+"    The Cc list above has been expanded by additional
+    addresses found in the patch commit message. By default
+    send-email prompts before sending whenever this occurs.
+    This behavior is controlled by the sendemail.confirm
+    configuration setting.
+
+    For additional information, run 'git send-email --help'.
+    To retain the current behavior, but squelch this message,
+    run 'git config --global sendemail.confirm auto'."),
+"\n\n";
 		}
-		$_ = ask("Send this email? ([y]es|[n]o|[q]uit|[a]ll): ",
+		# TRANSLATORS: Make sure to include [y] [n] [q] [a] in your
+		# translation. The program will only accept English input
+		# at this point.
+		$_ = ask(__("Send this email? ([y]es|[n]o|[q]uit|[a]ll): "),
 		         valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
 		         default => $ask_default);
 		die "Send this email reply required" unless defined $_;
@@ -1403,7 +1412,7 @@ Message-Id: $message_id
 	if ($quiet) {
 		printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
 	} else {
-		print (($dry_run ? "Dry-" : "")."OK. Log says:\n");
+		print (($dry_run ? "Dry-" : ""). __("OK. Log says:\n"));
 		if (!file_name_is_absolute($smtp_server)) {
 			print "Server: $smtp_server\n";
 			print "MAIL FROM:<$raw_from>\n";
@@ -1478,13 +1487,13 @@ foreach my $t (@files) {
 				$sauthor = sanitize_address($author);
 				next if $suppress_cc{'author'};
 				next if $suppress_cc{'self'} and $sauthor eq $sender;
-				printf("(mbox) Adding cc: %s from line '%s'\n",
+				printf(__("(mbox) Adding cc: %s from line '%s'\n"),
 					$1, $_) unless $quiet;
 				push @cc, $1;
 			}
 			elsif (/^To:\s+(.*)$/i) {
 				foreach my $addr (parse_address_line($1)) {
-					printf("(mbox) Adding to: %s from line '%s'\n",
+					printf(__("(mbox) Adding to: %s from line '%s'\n"),
 						$addr, $_) unless $quiet;
 					push @to, $addr;
 				}
@@ -1498,7 +1507,7 @@ foreach my $t (@files) {
 					} else {
 						next if ($suppress_cc{'cc'});
 					}
-					printf("(mbox) Adding cc: %s from line '%s'\n",
+					printf(__("(mbox) Adding cc: %s from line '%s'\n"),
 						$addr, $_) unless $quiet;
 					push @cc, $addr;
 				}
@@ -1532,7 +1541,7 @@ foreach my $t (@files) {
 			# So let's support that, too.
 			$input_format = 'lots';
 			if (@cc == 0 && !$suppress_cc{'cc'}) {
-				printf("(non-mbox) Adding cc: %s from line '%s'\n",
+				printf(__("(non-mbox) Adding cc: %s from line '%s'\n"),
 					$_, $_) unless $quiet;
 				push @cc, $_;
 			} elsif (!defined $subject) {
@@ -1555,7 +1564,7 @@ foreach my $t (@files) {
 				next if $suppress_cc{'bodycc'} and $what =~ /Cc/i;
 			}
 			push @cc, $c;
-			printf("(body) Adding cc: %s from line '%s'\n",
+			printf(__("(body) Adding cc: %s from line '%s'\n"),
 				$c, $_) unless $quiet;
 		}
 	}
-- 
2.7.4

[PATCH v2 11/11] i18n: difftool: mark warnings for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:41

Signed-off-by: Vasco Almeida <redacted>
---
 git-difftool.perl | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/git-difftool.perl b/git-difftool.perl
index ebd13ba..de8d783 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -22,6 +22,7 @@ use File::Path qw(mkpath rmtree);
 use File::Temp qw(tempdir);
 use Getopt::Long qw(:config pass_through);
 use Git;
+use Git::I18N;
 
 sub usage
 {
@@ -144,7 +145,7 @@ sub setup_dir_diff
 	my $i = 0;
 	while ($i < $#rawdiff) {
 		if ($rawdiff[$i] =~ /^::/) {
-			warn << 'EOF';
+			warn __ <<'EOF';
 Combined diff formats ('-c' and '--cc') are not supported in
 directory diff mode ('-d' and '--dir-diff').
 EOF
@@ -451,11 +452,11 @@ sub dir_diff
 		}
 
 		if (exists $wt_modified{$file} and exists $tmp_modified{$file}) {
-			my $errmsg = "warning: Both files modified: ";
-			$errmsg .= "'$workdir/$file' and '$b/$file'.\n";
-			$errmsg .= "warning: Working tree file has been left.\n";
-			$errmsg .= "warning:\n";
-			warn $errmsg;
+			warn sprintf(__(
+				"warning: Both files modified:\n" .
+				"'%s/%s' and '%s/%s'.\n" .
+				"warning: Working tree file has been left.\n" .
+				"warning:\n"), $workdir, $file, $b, $file);
 			$error = 1;
 		} elsif (exists $tmp_modified{$file}) {
 			my $mode = stat("$b/$file")->mode;
@@ -467,8 +468,9 @@ sub dir_diff
 		}
 	}
 	if ($error) {
-		warn "warning: Temporary files exist in '$tmpdir'.\n";
-		warn "warning: You may want to cleanup or recover these.\n";
+		warn sprintf(__(
+			"warning: Temporary files exist in '%s'.\n" .
+			"warning: You may want to cleanup or recover these.\n"), $tmpdir);
 		exit(1);
 	} else {
 		exit_cleanup($tmpdir, $rc);
-- 
2.7.4

[PATCH v2 09/11] i18n: send-email: mark warnings and errors for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:42

Mark warnings, errors and other messages for translation.

Signed-off-by: Vasco Almeida <redacted>
---
 git-send-email.perl | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/git-send-email.perl b/git-send-email.perl
index 2521832..e7f712e 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -118,20 +118,20 @@ sub format_2822_time {
 	my $localmin = $localtm[1] + $localtm[2] * 60;
 	my $gmtmin = $gmttm[1] + $gmttm[2] * 60;
 	if ($localtm[0] != $gmttm[0]) {
-		die "local zone differs from GMT by a non-minute interval\n";
+		die __("local zone differs from GMT by a non-minute interval\n");
 	}
 	if ((($gmttm[6] + 1) % 7) == $localtm[6]) {
 		$localmin += 1440;
 	} elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) {
 		$localmin -= 1440;
 	} elsif ($gmttm[6] != $localtm[6]) {
-		die "local time offset greater than or equal to 24 hours\n";
+		die __("local time offset greater than or equal to 24 hours\n");
 	}
 	my $offset = $localmin - $gmtmin;
 	my $offhour = $offset / 60;
 	my $offmin = abs($offset % 60);
 	if (abs($offhour) >= 24) {
-		die ("local time offset greater than or equal to 24 hours\n");
+		die __("local time offset greater than or equal to 24 hours\n");
 	}
 
 	return sprintf("%s, %2d %s %d %02d:%02d:%02d %s%02d%02d",
@@ -199,13 +199,13 @@ sub do_edit {
 		map {
 			system('sh', '-c', $editor.' "$@"', $editor, $_);
 			if (($? & 127) || ($? >> 8)) {
-				die("the editor exited uncleanly, aborting everything");
+				die(__("the editor exited uncleanly, aborting everything"));
 			}
 		} @_;
 	} else {
 		system('sh', '-c', $editor.' "$@"', $editor, @_);
 		if (($? & 127) || ($? >> 8)) {
-			die("the editor exited uncleanly, aborting everything");
+			die(__("the editor exited uncleanly, aborting everything"));
 		}
 	}
 }
@@ -299,7 +299,7 @@ my $help;
 my $rc = GetOptions("h" => \$help,
                     "dump-aliases" => \$dump_aliases);
 usage() unless $rc;
-die "--dump-aliases incompatible with other options\n"
+die __("--dump-aliases incompatible with other options\n")
     if !$help and $dump_aliases and @ARGV;
 $rc = GetOptions(
 		    "sender|from=s" => \$sender,
@@ -362,7 +362,7 @@ unless ($rc) {
     usage();
 }
 
-die "Cannot run git format-patch from outside a repository\n"
+die __("Cannot run git format-patch from outside a repository\n")
 	if $format_patch and not $repo;
 
 # Now, let's fill any that aren't set in with defaults:
@@ -617,7 +617,7 @@ while (defined(my $f = shift @ARGV)) {
 }
 
 if (@rev_list_opts) {
-	die "Cannot run git format-patch from outside a repository\n"
+	die __("Cannot run git format-patch from outside a repository\n")
 		unless $repo;
 	push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);
 }
@@ -636,7 +636,7 @@ if (@files) {
 		print $_,"\n" for (@files);
 	}
 } else {
-	print STDERR "\nNo patch files specified!\n\n";
+	print STDERR __("\nNo patch files specified!\n\n");
 	usage();
 }
 
@@ -728,7 +728,7 @@ EOT
 			$sender = $1;
 			next;
 		} elsif (/^(?:To|Cc|Bcc):/i) {
-			print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
+			print __("To/Cc/Bcc fields are not interpreted yet, they have been ignored\n");
 			next;
 		}
 		print $c2 $_;
@@ -737,7 +737,7 @@ EOT
 	close $c2;
 
 	if ($summary_empty) {
-		print "Summary email is empty, skipping it\n";
+		print __("Summary email is empty, skipping it\n");
 		$compose = -1;
 	}
 } elsif ($annotate) {
@@ -1313,7 +1313,7 @@ Message-Id: $message_id
 		$_ = ask(__("Send this email? ([y]es|[n]o|[q]uit|[a]ll): "),
 		         valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
 		         default => $ask_default);
-		die "Send this email reply required" unless defined $_;
+		die __("Send this email reply required") unless defined $_;
 		if (/^n/i) {
 			return 0;
 		} elsif (/^q/i) {
@@ -1339,7 +1339,7 @@ Message-Id: $message_id
 	} else {
 
 		if (!defined $smtp_server) {
-			die "The required SMTP server is not properly defined."
+			die __("The required SMTP server is not properly defined.")
 		}
 
 		if ($smtp_encryption eq 'ssl') {
@@ -1410,7 +1410,7 @@ Message-Id: $message_id
 		$smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message;
 	}
 	if ($quiet) {
-		printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
+		printf (($dry_run ? "Dry-" : ""). __("Sent %s\n"), $subject);
 	} else {
 		print (($dry_run ? "Dry-" : ""). __("OK. Log says:\n"));
 		if (!file_name_is_absolute($smtp_server)) {
@@ -1424,10 +1424,10 @@ Message-Id: $message_id
 		}
 		print $header, "\n";
 		if ($smtp) {
-			print "Result: ", $smtp->code, ' ',
+			print __("Result: "), $smtp->code, ' ',
 				($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
 		} else {
-			print "Result: OK\n";
+			print __("Result: OK\n");
 		}
 	}
 
@@ -1700,7 +1700,7 @@ sub apply_transfer_encoding {
 	$message = MIME::Base64::decode($message)
 		if ($from eq 'base64');
 
-	die "cannot send message as 7bit"
+	die __("cannot send message as 7bit")
 		if ($to eq '7bit' and $message =~ /[^[:ascii:]]/);
 	return $message
 		if ($to eq '7bit' or $to eq '8bit');
@@ -1708,7 +1708,7 @@ sub apply_transfer_encoding {
 		if ($to eq 'quoted-printable');
 	return MIME::Base64::encode($message, "\n")
 		if ($to eq 'base64');
-	die "invalid transfer encoding";
+	die __("invalid transfer encoding");
 }
 
 sub unique_email_list {
-- 
2.7.4

[PATCH v2 10/11] i18n: send-email: mark string with interpolation for translation

From: Vasco Almeida <hidden>
Date: 2016-08-31 12:33:43

Mark warnings, errors and other messages that are interpolated for
translation.

We must call sprintf() before calling die() and in few other
circumstances in order to interpolation take place.

Signed-off-by: Vasco Almeida <redacted>
---
 git-send-email.perl | 71 ++++++++++++++++++++++++++++-------------------------
 1 file changed, 38 insertions(+), 33 deletions(-)
diff --git a/git-send-email.perl b/git-send-email.perl
index e7f712e..c29381b 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -279,10 +279,13 @@ sub signal_handler {
 	# tmp files from --compose
 	if (defined $compose_filename) {
 		if (-e $compose_filename) {
-			print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
+			printf __("'%s' contains an intermediate version ".
+				  "of the email you were composing.\n"),
+				  $compose_filename;
 		}
 		if (-e ($compose_filename . ".final")) {
-			print "'$compose_filename.final' contains the composed email.\n"
+			printf __("'%s.final' contains the composed email.\n"),
+				  $compose_filename;
 		}
 	}
 
@@ -431,7 +434,7 @@ $smtp_encryption = '' unless (defined $smtp_encryption);
 my(%suppress_cc);
 if (@suppress_cc) {
 	foreach my $entry (@suppress_cc) {
-		die "Unknown --suppress-cc field: '$entry'\n"
+		die sprintf(__("Unknown --suppress-cc field: '%s'\n"), $entry)
 			unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/;
 		$suppress_cc{$entry} = 1;
 	}
@@ -460,7 +463,7 @@ my $confirm_unconfigured = !defined $confirm;
 if ($confirm_unconfigured) {
 	$confirm = scalar %suppress_cc ? 'compose' : 'auto';
 };
-die "Unknown --confirm setting: '$confirm'\n"
+die sprintf(__("Unknown --confirm setting: '%s'\n"), $confirm)
 	unless $confirm =~ /^(?:auto|cc|compose|always|never)/;
 
 # Debugging, print out the suppressions.
@@ -492,16 +495,16 @@ my %aliases;
 sub parse_sendmail_alias {
 	local $_ = shift;
 	if (/"/) {
-		print STDERR "warning: sendmail alias with quotes is not supported: $_\n";
+		printf STDERR __("warning: sendmail alias with quotes is not supported: %s\n"), $_;
 	} elsif (/:include:/) {
-		print STDERR "warning: `:include:` not supported: $_\n";
+		printf STDERR __("warning: `:include:` not supported: %s\n"), $_;
 	} elsif (/[\/|]/) {
-		print STDERR "warning: `/file` or `|pipe` redirection not supported: $_\n";
+		printf STDERR __("warning: `/file` or `|pipe` redirection not supported: %s\n"), $_;
 	} elsif (/^(\S+?)\s*:\s*(.+)$/) {
 		my ($alias, $addr) = ($1, $2);
 		$aliases{$alias} = [ split_addrs($addr) ];
 	} else {
-		print STDERR "warning: sendmail line is not recognized: $_\n";
+		printf STDERR __("warning: sendmail line is not recognized: %s\n"), $_;
 	}
 }
 
@@ -582,13 +585,12 @@ sub is_format_patch_arg {
 		if (defined($format_patch)) {
 			return $format_patch;
 		}
-		die(<<EOF);
-File '$f' exists but it could also be the range of commits
+		die sprintf(__(
+"File '%s' exists but it could also be the range of commits
 to produce patches for.  Please disambiguate by...
 
-    * Saying "./$f" if you mean a file; or
-    * Giving --format-patch option if you mean a range.
-EOF
+    * Saying \"./%s\" if you mean a file; or
+    * Giving --format-patch option if you mean a range."), $f, $f);
 	} catch Git::Error::Command with {
 		# Not a valid revision.  Treat it as a filename.
 		return 0;
@@ -604,7 +606,7 @@ while (defined(my $f = shift @ARGV)) {
 		@ARGV = ();
 	} elsif (-d $f and !is_format_patch_arg($f)) {
 		opendir my $dh, $f
-			or die "Failed to opendir $f: $!";
+			or die sprintf(__("Failed to opendir %s: %s"), $f, $!);
 
 		push @files, grep { -f $_ } map { catfile($f, $_) }
 				sort readdir $dh;
@@ -626,7 +628,8 @@ if ($validate) {
 	foreach my $f (@files) {
 		unless (-p $f) {
 			my $error = validate_patch($f);
-			$error and die "fatal: $f: $error\nwarning: no patches were sent\n";
+			$error and die sprintf(__("fatal: %s: %s\nwarning: no patches were sent\n"),
+						  $f, $error);
 		}
 	}
 }
@@ -649,7 +652,7 @@ sub get_patch_subject {
 		return "GIT: $1\n";
 	}
 	close $fh;
-	die "No subject line in $fn ?";
+	die sprintf(__("No subject line in %s ?"), $fn);
 }
 
 if ($compose) {
@@ -659,7 +662,7 @@ if ($compose) {
 		tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
 		tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
 	open my $c, ">", $compose_filename
-		or die "Failed to open for writing $compose_filename: $!";
+		or die sprintf(__("Failed to open for writing %s: %s"), $compose_filename, $!);
 
 
 	my $tpl_sender = $sender || $repoauthor || $repocommitter || '';
@@ -690,10 +693,10 @@ EOT
 	}
 
 	open my $c2, ">", $compose_filename . ".final"
-		or die "Failed to open $compose_filename.final : " . $!;
+		or die sprintf(__("Failed to open %s.final : %s"), $compose_filename, $!);
 
 	open $c, "<", $compose_filename
-		or die "Failed to open $compose_filename : " . $!;
+		or die sprintf(__("Failed to open %s : %s"), $compose_filename, $!);
 
 	my $need_8bit_cte = file_has_nonascii($compose_filename);
 	my $in_body = 0;
@@ -767,7 +770,9 @@ sub ask {
 			return $resp;
 		}
 		if ($confirm_only) {
-			my $yesno = $term->readline("Are you sure you want to use <$resp> [y/N]? ");
+			# TRANSLATORS: Keep [y/N] as is.
+			my $yesno = $term->readline(
+				sprintf(__("Are you sure you want to use <%s> [y/N]? "), $resp));
 			if (defined $yesno && $yesno =~ /y/i) {
 				return $resp;
 			}
@@ -846,7 +851,7 @@ my %EXPANDED_ALIASES;
 sub expand_one_alias {
 	my $alias = shift;
 	if ($EXPANDED_ALIASES{$alias}) {
-		die "fatal: alias '$alias' expands to itself\n";
+		die sprintf(__("fatal: alias '%s' expands to itself\n"), $alias);
 	}
 	local $EXPANDED_ALIASES{$alias} = 1;
 	return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
@@ -908,7 +913,7 @@ sub extract_valid_address {
 sub extract_valid_address_or_die {
 	my $address = shift;
 	$address = extract_valid_address($address);
-	die "error: unable to extract a valid address from: $address\n"
+	die sprintf(__("error: unable to extract a valid address from: %s\n"), $address)
 		if !$address;
 	return $address;
 }
@@ -916,7 +921,7 @@ sub extract_valid_address_or_die {
 sub validate_address {
 	my $address = shift;
 	while (!extract_valid_address($address)) {
-		print STDERR "error: unable to extract a valid address from: $address\n";
+		printf STDERR __("error: unable to extract a valid address from: %s\n"), $address;
 		# TRANSLATORS: Make sure to include [q] [d] [e] in your
 		# translation. The program will only accept English input
 		# at this point.
@@ -1221,7 +1226,7 @@ sub ssl_verify_params {
 		return (SSL_verify_mode => SSL_VERIFY_PEER(),
 			SSL_ca_file => $smtp_ssl_cert_path);
 	} else {
-		die "CA path \"$smtp_ssl_cert_path\" does not exist";
+		die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path);
 	}
 }
 
@@ -1383,7 +1388,7 @@ Message-Id: $message_id
 					# supported commands
 					$smtp->hello($smtp_domain);
 				} else {
-					die "Server does not support STARTTLS! ".$smtp->message;
+					die sprintf(__("Server does not support STARTTLS! %s"), $smtp->message);
 				}
 			}
 		}
@@ -1440,7 +1445,7 @@ $subject = $initial_subject;
 $message_num = 0;
 
 foreach my $t (@files) {
-	open my $fh, "<", $t or die "can't open file $t";
+	open my $fh, "<", $t or die sprintf(__("can't open file %s"), $t);
 
 	my $author = undef;
 	my $sauthor = undef;
@@ -1669,11 +1674,11 @@ sub recipients_cmd {
 		$address = sanitize_address($address);
 		next if ($address eq $sender and $suppress_cc{'self'});
 		push @addresses, $address;
-		printf("($prefix) Adding %s: %s from: '%s'\n",
-		       $what, $address, $cmd) unless $quiet;
+		printf(__("(%s) Adding %s: %s from: '%s'\n"),
+		       $prefix, $what, $address, $cmd) unless $quiet;
 		}
 	close $fh
-	    or die "($prefix) failed to close pipe to '$cmd'";
+	    or die sprintf(__("(%s) failed to close pipe to '%s'"), $prefix, $cmd);
 	return @addresses;
 }
 
@@ -1727,10 +1732,10 @@ sub unique_email_list {
 sub validate_patch {
 	my $fn = shift;
 	open(my $fh, '<', $fn)
-		or die "unable to open $fn: $!\n";
+		or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
 	while (my $line = <$fh>) {
 		if (length($line) > 998) {
-			return "$.: patch contains a line longer than 998 characters";
+			return sprintf(__("%s: patch contains a line longer than 998 characters"), $.);
 		}
 	}
 	return;
@@ -1739,7 +1744,7 @@ sub validate_patch {
 sub file_has_nonascii {
 	my $fn = shift;
 	open(my $fh, '<', $fn)
-		or die "unable to open $fn: $!\n";
+		or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
 	while (my $line = <$fh>) {
 		return 1 if $line =~ /[^[:ascii:]]/;
 	}
@@ -1749,7 +1754,7 @@ sub file_has_nonascii {
 sub body_or_subject_has_nonascii {
 	my $fn = shift;
 	open(my $fh, '<', $fn)
-		or die "unable to open $fn: $!\n";
+		or die sprintf(__("unable to open %s: %s\n"), $fn, $!);
 	while (my $line = <$fh>) {
 		last if $line =~ /^$/;
 		return 1 if $line =~ /^Subject.*[^[:ascii:]]/;
-- 
2.7.4

Re: [PATCH v2 04/11] i18n: add--interactive: mark plural strings

From: Vasco Almeida <hidden>
Date: 2016-09-26 18:15:32

A Qua, 31-08-2016 às 12:31 +0000, Vasco Almeida escreveu:
quoted hunk
Mark plural strings for translation.  Unfold each action case in one
entire sentence.

Pass new keyword for xgettext to extract.

Update test to include new subrotine Q__() for plural strings handling.
quoted
Signed-off-by: Vasco Almeida <redacted>
---
 Makefile                  |  3 ++-
 git-add--interactive.perl | 24 ++++++++++++++++--------
 perl/Git/I18N.pm          |  4 +++-
 t/t0202/test.pl           | 11 ++++++++++-
 4 files changed, 31 insertions(+), 11 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 4e1e857..08badfa 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -666,12 +666,18 @@ sub status_cmd {
 sub say_n_paths {
quoted
 	my $did = shift @_;
 	my $cnt = scalar @_;
-	print "$did ";
-	if (1 < $cnt) {
-		print "$cnt paths\n";
-	}
-	else {
-		print "one path\n";
+	if ($did eq 'added') {
+		printf(Q__("added one path\n", "added %d paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'updated') {
+		printf(Q__("updated one path\n", "updated %d paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'reversed') {
+		printf(Q__("reversed one path\n", "reversed %d paths\n",
This should be 'reverted' not 'reversed'.
quoted
+			   $cnt), $cnt);
+	} else {
+		printf(Q__("touched one path\n", "touched %d paths\n",
+			   $cnt), $cnt);
 	}
 }
When $cnt is 1 I get the following warning:
Redundant argument in printf at /home/vasco/dev/local/libexec/git-core/git-add--interactive line 680.

The singular form does not have a %d to consume $cnt argument to
printf(). Either we find a way to suppress that warning or we change
the singular form to contain %d.
quoted hunk
@@ -1508,8 +1514,10 @@ sub patch_update_file {
...
quoted
-					print colored $header_color, "Split into ",
-					scalar(@split), " hunks.\n";
+					print colored $header_color, sprintf(
+						Q__("Split into %d hunk.\n",
+						    "Split into %d hunks.\n",
+						    scalar(@split)), scalar(@split));
Like we do with this.
quoted

Re: [PATCH v2 02/11] i18n: add--interactive: mark simple here documents for translation

From: Jakub Narębski <hidden>
Date: 2016-09-30 17:26:51

W dniu 31.08.2016 o 14:31, Vasco Almeida pisze:
Mark messages in here document without interpolation for translation.

Marking for translation by removing here documents this way, rather than
take advantage of "print __ << EOF" way, makes other instances of help
messages in clean.c match the first two in this file.  Otherwise,
reusing here document would add a trailer newline to the message, making
them not match 100%, hence creating two entries in pot template for
translation rather than a single entry.
This is good catch, but I think it goes backwards with the solution.

If the text to be translated is multi-line, and it must end with newline,
why is this final newline not included in the msgid?  This would involve
turning printf_ln into printf, and adding trailing newline in final
entry for builtin/clean.c:295, etc. - I think it is better solution than
uglyifing git-add--interactive.perl

Though it is not much of uglifying thanks to Perl support for embedded
newlines in double-quoted strings.
quoted hunk
Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index fb8e5de..e11a33d 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -636,25 +636,25 @@ sub list_and_choose {
 }
 
 sub singleton_prompt_help_cmd {
-	print colored $help_color, <<\EOF ;
-Prompt help:
+	print colored $help_color, __(
+"Prompt help:
 1          - select a numbered item
 foo        - select item based on unique prefix
-           - (empty) select nothing
-EOF
+           - (empty) select nothing"),
+"\n";
 }
[... enough for information ...]

Regards,
-- 
Jakub Narębski

Re: [PATCH v2 03/11] i18n: add--interactive: mark strings with interpolation for translation

From: Jakub Narębski <hidden>
Date: 2016-09-30 17:53:10

W dniu 31.08.2016 o 14:31, Vasco Almeida pisze:
Use of sprintf following die or error_msg is necessary for placeholder
substitution take place.
No, it is not.  Though I don't think that we have in out Git::I18N
the support for Perl i18n placeholder substitution.

From gettext manual:
https://www.gnu.org/software/gettext/manual/gettext.html#perl_002dformat

  15.3.16 Perl Format Strings

  There are two kinds format strings in Perl: those acceptable to the Perl
  built-in function printf, labelled as ‘perl-format’, and those acceptable
  to the libintl-perl function __x, labelled as ‘perl-brace-format’.

  Perl printf format strings are described in the sprintf section of
  ‘man perlfunc’.

  Perl brace format strings are described in the Locale::TextDomain(3pm)
  manual page of the CPAN package libintl-perl. In brief, Perl format uses
  placeholders put between braces (‘{’ and ‘}’). The placeholder must have
  the syntax of simple identifiers.
 
Git doesn't use Locale::TextDomain, from what I understand, to provide
fallback in no-gettext case.  Also, Locale::TextDomain is not in core.

The syntax, with the help of shorthand helper function, looks like this:
http://search.cpan.org/dist/libintl-perl/lib/Locale/TextDomain.pm#EXPORTED_FUNCTIONS
https://metacpan.org/pod/Locale::TextDomain#EXPORTED-FUNCTIONS

  __x MSGID, ID1 => VAL1, ID2 => VAL2, ...
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  One of the nicest features in Perl is its capability to interpolate
  variables into strings:

    print "This is the $color $thing.\n";

  This nice feature might con you into thinking that you could now write

    print __"This is the $color $thing.\n";

  [But this doesn't work...]

  [...] The Perl backend to GNU gettext has defined an alternative format
  [to using printf / sprintf] for interpolatable strings:

    "This is the {color} {thing}.\n";

  Instead of Perl variables you use place-holders (legal Perl variables
  are also legal place-holders) in curly braces, and then you call

    print __x ("This is the {color} {thing}.\n", 
               thing => $thang,
               color => $color);
quoted hunk
Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index e11a33d..4e1e857 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -612,12 +612,12 @@ sub list_and_choose {
 			else {
 				$bottom = $top = find_unique($choice, @stuff);
 				if (!defined $bottom) {
-					error_msg "Huh ($choice)?\n";
+					error_msg sprintf(__("Huh (%s)?\n"), $choice);
So this would be, self explained without need of comment
for translators:

  +					error_msg __x ("Huh ({choice})?\n"), choice => $choice);

 					next TOPLOOP;
 				}
Though this is probably more work that you wanted to do.
The __x might be defined like this (borrowing from Locale::TextDomain),
which needs to be put into perl/Git/I18N.pm

  sub __ ($);
  sub __expand ($%);

  # With interpolation.
  sub __x ($@)
  {
  	my ($msgid, %vars) = @_;

  	return __expand (__($msgid), %vars);
  }
  
  sub __expand ($%)
  {
  	my ($translation, %args) = @_;
    
  	my $re = join '|', map { quotemeta $_ } keys %args;
  	$translation =~ s/\{($re)\}/defined $args{$1} ? $args{$1} : "{$1}"/ge;

  	return $translation;
  }



Best regards,
-- 
Jakub Narębski

Re: [PATCH v2 04/11] i18n: add--interactive: mark plural strings

From: Jakub Narębski <hidden>
Date: 2016-10-01 16:50:47

W dniu 26.09.2016 o 20:15, Vasco Almeida pisze:
A Qua, 31-08-2016 às 12:31 +0000, Vasco Almeida escreveu:
quoted
Mark plural strings for translation.  Unfold each action case in one
entire sentence.

Pass new keyword for xgettext to extract.

Update test to include new subrotine Q__() for plural strings handling.
Why use Q__() as the name of the subroutine? [looks further]. Oh, I see
that you are following the example of C shortcut functions (_, Q_ and N_).

But this is Perl, not C.  The standard shortcut functions are those
defined in Locale::TextDomain, even if we can't use this module directly.
Those that deal with plural strings handling are __n and __nx / __xn.

The Perl equivalent of Q_ shorthand function in C, C++, etc. is __n.
There is also a function __nx for combining handling plural strings
together with variable interpolation.

  __n MSGID, MSGID_PLURAL, COUNT
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  That is the reason for the existance of the function ngettext(),
  that __n() is a short-cut for:

    print __n"One file has been deleted.\n", 
             "All files have been deleted.\n",
             $files_deleted;

  Alternatively:

    print __n ("One file has been deleted.\n",
               "All files have been deleted.\n",
               $files_deleted);


  __nx MSGID, MSGID_PLURAL, COUNT, VAR1 => VAL1, VAR2 => VAL2, ...
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  Bringing it all together:

    print __nx ("One file has been deleted.\n",
                "{count} files have been deleted.\n",
                $num_files,
                count => $num_files);

  The function __nx() [and its alias __xn()] picks the correct plural
  form (also for English!) and it is capable of interpolating variables
  into strings.

quoted
Signed-off-by: Vasco Almeida <redacted>
---
 Makefile                  |  3 ++-
 git-add--interactive.perl | 24 ++++++++++++++++--------
 perl/Git/I18N.pm          |  4 +++-
 t/t0202/test.pl           | 11 ++++++++++-
 4 files changed, 31 insertions(+), 11 deletions(-)
quoted
diff --git a/Makefile b/Makefile
index de5a030..eedf1fa 100644
--- a/Makefile
+++ b/Makefile
@@ -2061,7 +2061,8 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \
 	--keyword=_ --keyword=N_ --keyword="Q_:1,2"
 XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \
 	--keyword=gettextln --keyword=eval_gettextln
-XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
+XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
+	--keyword=__ --keyword="Q__:1,2"
So this would be

   +XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
   +	--keyword=__ --keyword=__x --keyword=__n:1,2 --keyword=__nx:1,2

(assuming that __x was used for interpolation)
 
quoted
 LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
 LOCALIZED_SH = $(SCRIPT_SH) git-parse-remote.sh
 LOCALIZED_PERL = $(SCRIPT_PERL)
[...]
quoted
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index 4e1e857..08badfa 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -666,12 +666,18 @@ sub status_cmd {
 sub say_n_paths {
 	my $did = shift @_;
 	my $cnt = scalar @_;
-	print "$did ";
-	if (1 < $cnt) {
-		print "$cnt paths\n";
-	}
-	else {
-		print "one path\n";
+	if ($did eq 'added') {
+		printf(Q__("added one path\n", "added %d paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'updated') {
+		printf(Q__("updated one path\n", "updated %d paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'reverted') {
+		printf(Q__("reverted one path\n", "reverted %d paths\n",
+			   $cnt), $cnt);
+	} else {
+		printf(Q__("touched one path\n", "touched %d paths\n",
+			   $cnt), $cnt);
 	}
 }
One one hand side, it is recommended to avoid lego-like construction
of sentences.

  Translatable strings should be entire sentences. It is often not
  possible to translate single verbs or adjectives in a substitutable
  way.

I think however that the action part ($did in original non-i18n code)
is whole part in any language, so something like the following would
be enough:

  	# this hash is as much for validation, as for translation
   	my %actions = map { $_ => 1 } (N__"added", N__"updated", N__"reverted");
   	if (exists $actions{$did}) {
   		print __nx("{did} one path\n", "{did} {count} paths\n", $cnt,
   			   did => __($did), count => $cnt);
  	} else {
   		print __nx("touched one path\n", "touched {count} paths\n", $cnt,
   			   count => $cnt);
   	}

Please correct me if I am wrong, and you know language where
"added %d paths", "updated %d paths", "reverted %d paths" etc. must have
different word order.
When $cnt is 1 I get the following warning:
Redundant argument in printf at .../libexec/git-core/git-add--interactive line 680.
I wonder what is the case of C code - is similar warning here, or is
gettext smarter in that case...
The singular form does not have a %d to consume $cnt argument to
printf(). Either we find a way to suppress that warning or we change
the singular form to contain %d.
Anyway, with __nx there should be no such problem.
quoted
@@ -1508,8 +1514,10 @@ sub patch_update_file {
...
-					print colored $header_color, "Split into ",
-					scalar(@split), " hunks.\n";
+					print colored $header_color, sprintf(
+						Q__("Split into %d hunk.\n",
+						    "Split into %d hunks.\n",
+						    scalar(@split)), scalar(@split));
Like we do with this.
Note that it is a bit of change in behavior: previously Git would
say "Split into 0 hunks.\n".


Though this is probably more work that you wanted to do.  The __n would
take place of Q__, while the __nx function might be defined like this
(borrowing from Locale::TextDomain), which needs to be put into Git::I18N:

# Plural with interpolation.
sub __nx ($$$@)
{
    my ($msgid, $msgid_plural, $count, %args) = @_;
    
    return __expand ((__n $msgid, $msgid_plural, $count),
		     %args);
}

Best regards,
-- 
Jakub Narębski

Re: [PATCH v2 07/11] i18n: add--interactive: mark edit_hunk_manually message for translation

From: Jakub Narębski <hidden>
Date: 2016-10-01 18:49:45

W dniu 31.08.2016 o 14:31, Vasco Almeida pisze:
Mark message of edit_hunk_manually displayed in the editing file when
user chooses 'e' option.  The message had to be unfolded to allow
translation of the $participle verb.
Actually you need to unfold the message if exact translation (the ordering
of words) depends on the $participle verb.

Moreover, because the translation of message of edit_hunk_manually can
depend *only* on $participle verb (it cannot depends on $remove_minus
and $remove_plus, as they are either ('-', '+') or ('+', '-')), with
"particular" (contexted) translation of $participle, if the translation
would be different for different $participle, one can simply move whole
translation to $participle.
quoted hunk
Some messages end up being exactly the same for some uses cases, but
left it for easier change in the future, e.g., wanting to change wording
of one particular use case.

Signed-off-by: Vasco Almeida <redacted>
---
 git-add--interactive.perl | 60 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 49 insertions(+), 11 deletions(-)
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index acbfa4e..235142c 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -1057,22 +1057,60 @@ sub edit_hunk_manually {
 	my $fh;
 	open $fh, '>', $hunkfile
 		or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
-	print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
+	print $fh __("# Manual hunk edit mode -- see bottom for a quick guide\n");
This would be left as is (though you can write __"..." instead of __("...")).
 	print $fh @$oldtext;
-	my $participle = $patch_mode_flavour{PARTICIPLE};
This would be replaced with

  -	my $participle = __($patch_mode_flavour{PARTICIPLE});

Additionally you would need to mark the text of $participle itself for
translation, with context; for example:

  -                PARTICIPLE => 'staging',
  +                PARTICIPLE => N__p('Participle for "stage" operation, for edit hunk manually message', 'staging'),

That would require adding "--keyword=N__p:1c,2" to Makefile, of course.
The entry in the *.pot / *.po translations file would be preceded with 
"msgctxt <context>" line.
 	my $is_reverse = $patch_mode_flavour{IS_REVERSE};
 	my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
-	print $fh <<EOF;
-# ---
-# To remove '$remove_minus' lines, make them ' ' lines (context).
-# To remove '$remove_plus' lines, delete them.
+	print $fh sprintf(__(
  +	print $fh __x(<<'EOF', remove_minus => $remove_minus, remove_plus => $remove_plus,
  +		      participle => $participle);

or

  +		      participle => __($participle));

assuming that $participle assignment doesn't use __ then.

Note that xgettext supports heredoc syntax for Perl language:

  Here-documents are recognized. If the delimiter is enclosed in
  single quotes, the string is not interpolated. If it is enclosed
  in double quotes or has no quotes at all, the string is interpolated.

  Delimiters that start with a digit are not supported!
+"# ---
+# To remove '%s' lines, make them ' ' lines (context).
+# To remove '%s' lines, delete them.
  +# To remove '{remove_minus}' lines, make them ' ' lines (context).
  +# To remove '{remove_plus}' lines, delete them.
 # Lines starting with # will be removed.
-#
-# If the patch applies cleanly, the edited hunk will immediately be
-# marked for $participle. If it does not apply cleanly, you will be given
+#"), $remove_minus, $remove_plus),
   # Lines starting with # will be removed.
  +#
  +# If the patch applies cleanly, the edited hunk will immediately be
  +# marked for {participle}. If it does not apply cleanly, you will be given

[...]

Here is definition of relevant subroutines, based on Locale::TextDomain.

sub N__p($$) {
    return @_;
}

-- 
Jakub Narębski

Re: [PATCH v2 09/11] i18n: send-email: mark warnings and errors for translation

From: Jakub Narębski <hidden>
Date: 2016-10-01 19:52:52

W dniu 31.08.2016 o 14:31, Vasco Almeida pisze:
Mark warnings, errors and other messages for translation.
Which discovered a few places with questionable code...

(though there is one place with bad handling of translation)
 
quoted hunk
Signed-off-by: Vasco Almeida <redacted>
---
 git-send-email.perl | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/git-send-email.perl b/git-send-email.perl
index 2521832..e7f712e 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -118,20 +118,20 @@ sub format_2822_time {
 	my $localmin = $localtm[1] + $localtm[2] * 60;
 	my $gmtmin = $gmttm[1] + $gmttm[2] * 60;
 	if ($localtm[0] != $gmttm[0]) {
-		die "local zone differs from GMT by a non-minute interval\n";
+		die __("local zone differs from GMT by a non-minute interval\n");
 	}
 	if ((($gmttm[6] + 1) % 7) == $localtm[6]) {
 		$localmin += 1440;
 	} elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) {
 		$localmin -= 1440;
 	} elsif ($gmttm[6] != $localtm[6]) {
-		die "local time offset greater than or equal to 24 hours\n";
+		die __("local time offset greater than or equal to 24 hours\n");
As one can see that this is the same message as below, so I wondered
why the same test is repeated... But it is even worse.  This test
is first, wrongly described: it checks that the day of week is the
same in local timezone and in UTC / GMT.  But second, it is WRONG!

For example if UTC time is just before midnight, then any positive
timezone has different day of week (next day); if UTC is just before
midnight, then any negative timezone has different day of week (previous
day).

  $ LC_ALL=C TZ=US/Hawaii date --date="2016-10-01 01:00 UTC"
  Fri Sep 30 15:00:00 HST 2016
  $ LC_ALL=C TZ=US/Hawaii date --date="2016-10-01 01:00 UTC" --utc
  Sat Oct  1 01:00:00 UTC 2016

 	}
 	my $offset = $localmin - $gmtmin;
 	my $offhour = $offset / 60;
 	my $offmin = abs($offset % 60);
 	if (abs($offhour) >= 24) {
-		die ("local time offset greater than or equal to 24 hours\n");
+		die __("local time offset greater than or equal to 24 hours\n");
This is good test.
quoted hunk
 	}
 
 	return sprintf("%s, %2d %s %d %02d:%02d:%02d %s%02d%02d",
@@ -199,13 +199,13 @@ sub do_edit {
 		map {
 			system('sh', '-c', $editor.' "$@"', $editor, $_);
 			if (($? & 127) || ($? >> 8)) {
-				die("the editor exited uncleanly, aborting everything");
+				die(__("the editor exited uncleanly, aborting everything"));
 			}
 		} @_;
 	} else {
 		system('sh', '-c', $editor.' "$@"', $editor, @_);
 		if (($? & 127) || ($? >> 8)) {
-			die("the editor exited uncleanly, aborting everything");
+			die(__("the editor exited uncleanly, aborting everything"));
 		}
 	}
Here we see some unnecessary code duplication, and thus
message duplication.

quoted hunk
@@ -1410,7 +1410,7 @@ Message-Id: $message_id
 		$smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message;
 	}
 	if ($quiet) {
-		printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject);
+		printf (($dry_run ? "Dry-" : ""). __("Sent %s\n"), $subject);
 	} else {
 		print (($dry_run ? "Dry-" : ""). __("OK. Log says:\n"));
 		if (!file_name_is_absolute($smtp_server)) {
You would want to translate Dry-Sent and Dry-OK.

-- 
Jakub Narębski

Re: [PATCH v2 03/11] i18n: add--interactive: mark strings with interpolation for translation

From: Vasco Almeida <hidden>
Date: 2016-10-03 12:42:08

W dniu 31.08.2016 o 14:31, Vasco Almeida pisze:
quoted
Use of sprintf following die or error_msg is necessary for
placeholder
substitution take place.
No, it is not.  Though I don't think that we have in out Git::I18N
the support for Perl i18n placeholder substitution.
I will try to change the commit message to better reflect the reality.
From gettext manual:
https://www.gnu.org/software/gettext/manual/gettext.html#perl_002dfor
mat

  15.3.16 Perl Format Strings

  There are two kinds format strings in Perl: those acceptable to the
Perl
  built-in function printf, labelled as ‘perl-format’, and those
acceptable
  to the libintl-perl function __x, labelled as ‘perl-brace-format’.

  Perl printf format strings are described in the sprintf section of
  ‘man perlfunc’.

  Perl brace format strings are described in the
Locale::TextDomain(3pm)
  manual page of the CPAN package libintl-perl. In brief, Perl format
uses
  placeholders put between braces (‘{’ and ‘}’). The placeholder must
have
  the syntax of simple identifiers.
 
Git doesn't use Locale::TextDomain, from what I understand, to
provide
fallback in no-gettext case.  Also, Locale::TextDomain is not in
core.
Yes that can be a reason not to use Locale::TextDomain. When Ævar
Arnfjörð Bjarmason added gettext support and i18n stuff, he chose no to
use TextDomain because it did more than he wanted it to do, and that
could introduce bugs and unnecessary work.

5e9637c ("i18n: add infrastructure for translating Git with gettext",
2011-11-18)

https://public-inbox.org/git/AANLkTilYD_NyIZMyj9dHtVk-ylVBfvyxpCC7982LW
nVd@mail.gmail.com/

quoted
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index e11a33d..4e1e857 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -612,12 +612,12 @@ sub list_and_choose {
                      else {
                              $bottom = $top = find_unique($choice, @stuff);
                              if (!defined $bottom) {
-                                     error_msg "Huh ($choice)?\n";
+                                     error_msg sprintf(__("Huh (%s)?\n"), $choice);
So this would be, self explained without need of comment
for translators:

  +                                     error_msg __x ("Huh ({choice})?\n"), choice => $choice);

quoted
                                      next TOPLOOP;
                              }
Though this is probably more work that you wanted to do.
The __x might be defined like this (borrowing from Locale::TextDomain),
which needs to be put into perl/Git/I18N.pm

  sub __ ($);
  sub __expand ($%);

  # With interpolation.
  sub __x ($@)
  {
        my ($msgid, %vars) = @_;

        return __expand (__($msgid), %vars);
  }
  
  sub __expand ($%)
  {
        my ($translation, %args) = @_;
    
        my $re = join '|', map { quotemeta $_ } keys %args;
        $translation =~ s/\{($re)\}/defined $args{$1} ? $args{$1} : "{$1}"/ge;

        return $translation;
  }
I wonder if it is worth the trouble to add and use these functions,
when there is already a way that works and for me looks simpler. One
reason, if valid, would be that translators already translate strings
with %d and %s from C source which is where the majority of the English
text comes from. Thus it would make little difference for them.
If we use in perl string like in C there is a chance that there will be
a match of some string and would lead to just one msgid instead of two
in the git.pot template for translation. Actually this happens for the
string with "Huh (%s)?" in clean.c.

Unfortunately, I do not know if I would add these changes because I
know little about perl and hence I am not comfortable to do so.

Maybe if you see it is indeed worth adding these to Git I18N.pm, you
could send a follow-up patch or a replacement for this one.

Re: [PATCH v2 04/11] i18n: add--interactive: mark plural strings

From: Vasco Almeida <hidden>
Date: 2016-10-03 12:46:40

A Sáb, 01-10-2016 às 18:49 +0200, Jakub Narębski escreveu:
W dniu 26.09.2016 o 20:15, Vasco Almeida pisze:
quoted
A Qua, 31-08-2016 às 12:31 +0000, Vasco Almeida escreveu:
quoted

Mark plural strings for translation.  Unfold each action case in
one
entire sentence.

Pass new keyword for xgettext to extract.

Update test to include new subrotine Q__() for plural strings
handling.
Why use Q__() as the name of the subroutine? [looks further]. Oh, I
see
that you are following the example of C shortcut functions (_, Q_ and
N_).

But this is Perl, not C.  The standard shortcut functions are those
defined in Locale::TextDomain, even if we can't use this module
directly.
Those that deal with plural strings handling are __n and __nx / __xn.

The Perl equivalent of Q_ shorthand function in C, C++, etc. is __n.
There is also a function __nx for combining handling plural strings
together with variable interpolation.
I was trying to follow the same style of C. I will change the name Q__
to __x for the sake of conformance.

quoted
quoted
diff --git a/git-add--interactive.perl b/git-add
--interactive.perl
index 4e1e857..08badfa 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -666,12 +666,18 @@ sub status_cmd {
 sub say_n_paths {
 	my $did = shift @_;
 	my $cnt = scalar @_;
-	print "$did ";
-	if (1 < $cnt) {
-		print "$cnt paths\n";
-	}
-	else {
-		print "one path\n";
+	if ($did eq 'added') {
+		printf(Q__("added one path\n", "added %d
paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'updated') {
+		printf(Q__("updated one path\n", "updated %d
paths\n",
+			   $cnt), $cnt);
+	} elsif ($did eq 'reverted') {
+		printf(Q__("reverted one path\n", "reverted %d
paths\n",
+			   $cnt), $cnt);
+	} else {
+		printf(Q__("touched one path\n", "touched %d
paths\n",
+			   $cnt), $cnt);
 	}
 }
One one hand side, it is recommended to avoid lego-like construction
of sentences.

  Translatable strings should be entire sentences. It is often not
  possible to translate single verbs or adjectives in a substitutable
  way.

I think however that the action part ($did in original non-i18n code)
is whole part in any language, so something like the following would
be enough:

  	# this hash is as much for validation, as for translation
   	my %actions = map { $_ => 1 } (N__"added", N__"updated",
N__"reverted");
   	if (exists $actions{$did}) {
   		print __nx("{did} one path\n", "{did} {count}
paths\n", $cnt,
   			   did => __($did), count => $cnt);
  	} else {
   		print __nx("touched one path\n", "touched {count}
paths\n", $cnt,
   			   count => $cnt);
   	}

Please correct me if I am wrong, and you know language where
"added %d paths", "updated %d paths", "reverted %d paths" etc. must
have
different word order.
We may never know. :-) I prefer not to make assumptions about other
languages and I think there is not much to gain from using this
approach instead of marking entire sentence as the patch does. I mean
the code verbosity is almost the same but maybe it gets harder to
translate.
Other thing, we want to avoid marking for translation single words
(when that is avoidable) because those could appear on other sites that
need a different translation according to the context. For example:
'commit' could be a verb or a noun from the context.
quoted
When $cnt is 1 I get the following warning:
Redundant argument in printf at .../libexec/git-core/git-add
--interactive line 680.
I wonder what is the case of C code - is similar warning here, or is
gettext smarter in that case...
I do not know but I know there is a few cases like this in C code.
quoted
The singular form does not have a %d to consume $cnt argument to
printf(). Either we find a way to suppress that warning or we
change
the singular form to contain %d.
Anyway, with __nx there should be no such problem.
quoted
quoted
@@ -1508,8 +1514,10 @@ sub patch_update_file {
...
-					print colored
$header_color, "Split into ",
-					scalar(@split), "
hunks.\n";
+					print colored
$header_color, sprintf(
+						Q__("Split into
%d hunk.\n",
+						    "Split into
%d hunks.\n",
+						    scalar(@spli
t)), scalar(@split));
Like we do with this.
Note that it is a bit of change in behavior: previously Git would
say "Split into 0 hunks.\n".


Though this is probably more work that you wanted to do.  The __n
would
take place of Q__, while the __nx function might be defined like this
(borrowing from Locale::TextDomain), which needs to be put into
Git::I18N:

# Plural with interpolation.
sub __nx ($$$@)
{
    my ($msgid, $msgid_plural, $count, %args) = @_;
    
    return __expand ((__n $msgid, $msgid_plural, $count),
		     %args);
}
I thank you very much for your code suggestion and review but I am not
comfortable enough with perl to fiddle much more and add this to the
i18n work of the perl scripts, as I said before.

So if you find this a good think do add, please send patches as a
follow-up or to replace this patch.

Re: [PATCH v2 02/11] i18n: add--interactive: mark simple here documents for translation

From: Vasco Almeida <hidden>
Date: 2016-10-03 13:22:01

A Sex, 30-09-2016 às 19:26 +0200, Jakub Narębski escreveu:
W dniu 31.08.2016 o 14:31, Vasco Almeida pisze:
quoted
Mark messages in here document without interpolation for
translation.
quoted
Marking for translation by removing here documents this way, rather
than
quoted
take advantage of "print __ << EOF" way, makes other instances of
help
quoted
messages in clean.c match the first two in this file.  Otherwise,
reusing here document would add a trailer newline to the message,
making
quoted
them not match 100%, hence creating two entries in pot template for
translation rather than a single entry.
This is good catch, but I think it goes backwards with the solution.

If the text to be translated is multi-line, and it must end with
newline,
why is this final newline not included in the msgid?  This would
involve
turning printf_ln into printf, and adding trailing newline in final
entry for builtin/clean.c:295, etc. - I think it is better solution
than
uglyifing git-add--interactive.perl

Though it is not much of uglifying thanks to Perl support for
embedded
newlines in double-quoted strings.
I will do this for the next re-roll. Thanks
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help