From 2db87101fc3b221b1c015e763d80ee82681d8753 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:24 -0100 Subject: [PATCH 01/16] Git.pm: add subroutines for commenting lines Add subroutines prefix_lines and comment_lines. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- perl/Git.pm | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/perl/Git.pm b/perl/Git.pm index ce7e4e8da3..f699fee9a1 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -1421,6 +1421,44 @@ sub END { } # %TEMP_* Lexical Context +=item prefix_lines ( PREFIX, STRING [, STRING... ]) + +Prefixes lines in C with C. + +=cut + +sub prefix_lines { + my $prefix = shift; + my $string = join("\n", @_); + $string =~ s/^/$prefix/mg; + return $string; +} + +=item get_comment_line_char ( ) + +Gets the core.commentchar configuration value. +The value falls-back to '#' if core.commentchar is set to 'auto'. + +=cut + +sub get_comment_line_char { + my $comment_line_char = config("core.commentchar") || '#'; + $comment_line_char = '#' if ($comment_line_char eq 'auto'); + $comment_line_char = '#' if (length($comment_line_char) != 1); + return $comment_line_char; +} + +=item comment_lines ( STRING [, STRING... ]) + +Comments lines following core.commentchar configuration. + +=cut + +sub comment_lines { + my $comment_line_char = get_comment_line_char; + return prefix_lines("$comment_line_char ", @_); +} + =back =head1 ERROR HANDLING From 258e7790b3a57c3b1e8c1fdc3e2d1697b9e0b184 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:25 -0100 Subject: [PATCH 02/16] i18n: add--interactive: mark strings for translation 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 Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 76 +++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 642cce1ac6..a4b29840a9 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,8 +253,9 @@ sub list_untracked { run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); } -my $status_fmt = '%12s %12s %s'; -my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path'); +# TRANSLATORS: you can adjust this to align "git add -i" status menu +my $status_fmt = __('%12s %12s %s'); +my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path')); { my $initial; @@ -679,7 +681,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) { @@ -691,7 +693,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) { @@ -725,13 +727,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"; } @@ -1163,8 +1165,14 @@ 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. + # Consider translating (saying "no" discards!) as + # (saying "n" for "no" discards!) if the translation + # of the word "no" does not start with n. + __('Your edited hunk does not apply. Edit again ' + . '(saying "no" discards!) [y/n]? ') ) or return undef; } } @@ -1210,11 +1218,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 { @@ -1234,9 +1242,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; } @@ -1244,7 +1252,7 @@ sub patch_update_cmd { @them = @mods; } else { - @them = list_and_choose({ PROMPT => 'Patch update', + @them = list_and_choose({ PROMPT => __('Patch update'), HEADER => $status_head, }, @mods); } @@ -1394,12 +1402,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 = " ( to see more)"; + print __("go to which hunk ( to see more)? "); + } else { + print __("go to which hunk? "); } - print "go to which hunk$extra? "; $response = ; if (!defined $response) { $response = ''; @@ -1436,7 +1444,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 = ; if (defined $regex) { chomp $regex; @@ -1459,7 +1467,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; } } @@ -1471,7 +1479,7 @@ sub patch_update_file { $ix--; } else { - error_msg "No previous hunk\n"; + error_msg __("No previous hunk\n"); } next; } @@ -1480,7 +1488,7 @@ sub patch_update_file { $ix++; } else { - error_msg "No next hunk\n"; + error_msg __("No next hunk\n"); } next; } @@ -1493,13 +1501,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; } } @@ -1557,18 +1565,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); } @@ -1591,32 +1599,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 "--"; @@ -1638,10 +1646,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) { From 5fa832640a627d6930df2194a28e9cc4aa4ef5c9 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:26 -0100 Subject: [PATCH 03/16] i18n: add--interactive: mark simple here-documents for translation Mark messages in here-documents without interpolation for translation. The here-document delimiter \EOF, which is the same as 'EOF', indicates that the text is to be treated literally without interpolation of its content. Unfortunately xgettext is not able to extract here-documents delimited with \EOF but it is with delimiter enclosed in single quotes. So change \EOF to 'EOF', although in this case does not make difference what variation of here-document to use since there is nothing to interpolate. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index a4b29840a9..88c8d9dd6b 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -638,7 +638,7 @@ sub list_and_choose { } sub singleton_prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a numbered item foo - select item based on unique prefix @@ -647,7 +647,7 @@ EOF } sub prompt_help_cmd { - print colored $help_color, <<\EOF ; + print colored $help_color, __ <<'EOF' ; Prompt help: 1 - select a single item 3-5 - select a range of items @@ -1581,7 +1581,9 @@ sub quit_cmd { } sub help_cmd { - print colored $help_color, <<\EOF ; +# TRANSLATORS: please do not translate the command names +# 'status', 'update', 'revert', etc. + print colored $help_color, __ <<'EOF' ; 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 From 13c58c1754393c17fb50b63ae9a3bc07195e520c Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:27 -0100 Subject: [PATCH 04/16] i18n: add--interactive: mark strings with interpolation for translation Since at this point Git::I18N.perl lacks support for Perl i18n placeholder substitution, use of sprintf following die or error_msg is necessary for placeholder substitution take place. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 88c8d9dd6b..c1d4eb8156 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 "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++) { @@ -716,7 +716,7 @@ sub revert_cmd { $_->{INDEX_ADDDEL} eq 'create') { system(qw(git update-index --force-remove --), $_->{VALUE}); - print "note: $_->{VALUE} is untracked now.\n"; + printf(__("note: %s is untracked now.\n"), $_->{VALUE}); } } } @@ -1053,7 +1053,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}; @@ -1080,7 +1080,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; @@ -1233,7 +1233,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; @@ -1415,7 +1415,8 @@ 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 { @@ -1457,7 +1458,7 @@ sub patch_update_file { if ($@) { my ($err,$exp) = ($@, $1); $err =~ s/ at .*git-add--interactive line \d+, 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; @@ -1622,18 +1623,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); } } From 901707babc95950a6e6bd0fcea2070f63b704ef4 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:28 -0100 Subject: [PATCH 05/16] i18n: clean.c: match string with git-add--interactive.perl Change strings for help to match the ones in git-add--interactive.perl. The strings now represent one entry to translate each rather then two entries each different only by an ending newline character. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- builtin/clean.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin/clean.c b/builtin/clean.c index 0371010afb..d6bc3aaaea 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -287,11 +287,11 @@ static void pretty_print_menus(struct string_list *menu_list) static void prompt_help_cmd(int singleton) { clean_print_color(CLEAN_COLOR_HELP); - printf_ln(singleton ? + printf(singleton ? _("Prompt help:\n" "1 - select a numbered item\n" "foo - select item based on unique prefix\n" - " - (empty) select nothing") : + " - (empty) select nothing\n") : _("Prompt help:\n" "1 - select a single item\n" "3-5 - select a range of items\n" @@ -299,7 +299,7 @@ static void prompt_help_cmd(int singleton) "foo - select item based on unique prefix\n" "-... - unselect specified items\n" "* - choose all items\n" - " - (empty) finish selecting")); + " - (empty) finish selecting\n")); clean_print_color(CLEAN_COLOR_RESET); } @@ -508,7 +508,7 @@ static int parse_choice(struct menu_stuff *menu_stuff, if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || (is_single && bottom != top)) { clean_print_color(CLEAN_COLOR_ERROR); - printf_ln(_("Huh (%s)?"), (*ptr)->buf); + printf(_("Huh (%s)?\n"), (*ptr)->buf); clean_print_color(CLEAN_COLOR_RESET); continue; } @@ -774,7 +774,7 @@ static int ask_each_cmd(void) static int quit_cmd(void) { string_list_clear(&del_list, 0); - printf_ln(_("Bye.")); + printf(_("Bye.\n")); return MENU_RETURN_NO_LOOP; } From c4a85c3b8eef8c3b37f5103870e82894d9e5e7d0 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:29 -0100 Subject: [PATCH 06/16] i18n: add--interactive: mark plural strings 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 subroutine __n() for plural strings handling. Update documentation to include a description of the new __n() subroutine. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- Makefile | 3 ++- git-add--interactive.perl | 27 ++++++++++++++++++--------- perl/Git/I18N.pm | 10 +++++++++- t/t0202/test.pl | 11 ++++++++++- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index df4f86bb30..12b56cead7 100644 --- a/Makefile +++ b/Makefile @@ -2109,7 +2109,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="__n:1,2" LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh diff --git a/git-add--interactive.perl b/git-add--interactive.perl index c1d4eb8156..04eea239a4 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -668,12 +668,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(__n("added %d path\n", "added %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'updated') { + printf(__n("updated %d path\n", "updated %d paths\n", + $cnt), $cnt); + } elsif ($did eq 'reverted') { + printf(__n("reverted %d path\n", "reverted %d paths\n", + $cnt), $cnt); + } else { + printf(__n("touched %d path\n", "touched %d paths\n", + $cnt), $cnt); } } @@ -1420,7 +1426,8 @@ sub patch_update_file { } elsif (0 < $response && $response <= $num) { $ix = $response - 1; } else { - error_msg "Sorry, only $num hunks available.\n"; + error_msg sprintf(__n("Sorry, only %d hunk available.\n", + "Sorry, only %d hunks available.\n", $num), $num); } next; } @@ -1515,8 +1522,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( + __n("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 f889fd6da9..617d8c2a17 100644 --- a/perl/Git/I18N.pm +++ b/perl/Git/I18N.pm @@ -13,7 +13,7 @@ BEGIN { } } -our @EXPORT = qw(__); +our @EXPORT = qw(__ __n); our @EXPORT_OK = @EXPORT; sub __bootstrap_locale_messages { @@ -44,6 +44,7 @@ BEGIN eval { __bootstrap_locale_messages(); *__ = \&Locale::Messages::gettext; + *__n = \&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] }; + *__n = sub ($$$) { $_[2] == 1 ? $_[0] : $_[1] }; }; } @@ -70,6 +72,8 @@ Git::I18N - Perl interface to Git's Gettext localizations printf __("The following error occurred: %s\n"), $error; + printf __n("commited %d file\n", "commited %d files\n", $files), $files; + =head1 DESCRIPTION Git's internal Perl interface to gettext via L. If @@ -87,6 +91,10 @@ it. L's gettext function if all goes well, otherwise our passthrough fallback function. +=head2 __n($$$) + +L's ngettext function or passthrough fallback function. + =head1 AUTHOR Evar ArnfjErE Bjarmason diff --git a/t/t0202/test.pl b/t/t0202/test.pl index 2c10cb4693..4101833a8e 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( __ $ + __n $$$ )); 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(__n($got_singular, $got_plural, 1), $expect_singular, + "Get singular string through __n() in C locale"); + is(__n($got_singular, $got_plural, 2), $expect_plural, + "Get plural string through __n() in C locale"); } # Test a basic message on different locales From 0539d5e6d535760a99694ff5d5592e46ed929d15 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:30 -0100 Subject: [PATCH 07/16] i18n: add--interactive: mark patch prompt for translation Mark prompt 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 Signed-off-by: Junio C Hamano --- Makefile | 2 +- git-add--interactive.perl | 54 +++++++++++++++++++++++++++++++++------ perl/Git/I18N.pm | 11 +++++++- t/t0202/test.pl | 5 +++- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 12b56cead7..c79aceb42d 100644 --- a/Makefile +++ b/Makefile @@ -2110,7 +2110,7 @@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \ XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \ --keyword=gettextln --keyword=eval_gettextln XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \ - --keyword=__ --keyword="__n:1,2" + --keyword=__ --keyword=N__ --keyword="__n:1,2" LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 04eea239a4..745462da53 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -92,6 +92,7 @@ sub colored { } # command line options +my $cmd; my $patch_mode; my $patch_mode_revision; @@ -172,7 +173,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') { @@ -1308,6 +1310,44 @@ sub display_hunks { return $i; } +my %patch_update_prompt_modes = ( + stage => { + mode => N__("Stage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stage this hunk [y,n,q,a,d,/%s,?]? "), + }, + stash => { + mode => N__("Stash mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Stash deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Stash this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_head => { + mode => N__("Unstage mode change [y,n,q,a,d,/%s,?]? "), + deletion => N__("Unstage deletion [y,n,q,a,d,/%s,?]? "), + hunk => N__("Unstage this hunk [y,n,q,a,d,/%s,?]? "), + }, + reset_nothead => { + mode => N__("Apply mode change to index [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index [y,n,q,a,d,/%s,?]? "), + }, + checkout_index => { + mode => N__("Discard mode change from worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_head => { + mode => N__("Discard mode change from index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Discard deletion from index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d,/%s,?]? "), + }, + checkout_nothead => { + mode => N__("Apply mode change to index and worktree [y,n,q,a,d,/%s,?]? "), + deletion => N__("Apply deletion to index and worktree [y,n,q,a,d,/%s,?]? "), + hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d,/%s,?]? "), + }, +); + sub patch_update_file { my $quit = 0; my ($ix, $num); @@ -1380,12 +1420,9 @@ 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,?]? "; + print colored $prompt_color, + sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other); + my $line = prompt_single_character; last unless defined $line; if ($line) { @@ -1641,6 +1678,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); @@ -1677,7 +1715,7 @@ sub main_loop { process_args(); refresh(); -if ($patch_mode) { +if ($cmd) { patch_update_cmd(); } else { diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm index 617d8c2a17..c41425c8d0 100644 --- a/perl/Git/I18N.pm +++ b/perl/Git/I18N.pm @@ -13,7 +13,7 @@ BEGIN { } } -our @EXPORT = qw(__ __n); +our @EXPORT = qw(__ __n N__); our @EXPORT_OK = @EXPORT; sub __bootstrap_locale_messages { @@ -54,6 +54,8 @@ BEGIN *__ = sub ($) { $_[0] }; *__n = sub ($$$) { $_[2] == 1 ? $_[0] : $_[1] }; }; + + sub N__($) { return shift; } } 1; @@ -74,6 +76,7 @@ Git::I18N - Perl interface to Git's Gettext localizations printf __n("commited %d file\n", "commited %d files\n", $files), $files; + =head1 DESCRIPTION Git's internal Perl interface to gettext via L. If @@ -95,6 +98,12 @@ passthrough fallback function. L's ngettext function or passthrough fallback function. +=head2 N__($) + +No-operation that only returns its argument. Use this if you want xgettext to +extract the text to the pot template but do not want to trigger retrival of the +translation at run time. + =head1 AUTHOR Evar ArnfjErE Bjarmason diff --git a/t/t0202/test.pl b/t/t0202/test.pl index 4101833a8e..2cbf7b9590 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 => 11; +use Test::More tests => 13; use Git::I18N; my $has_gettext_library = $Git::I18N::__HAS_LIBRARY; @@ -32,6 +32,7 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export my %prototypes = (qw( __ $ __n $$$ + N__ $ )); while (my ($sub, $proto) = each %prototypes) { is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype"); @@ -55,6 +56,8 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export "Get singular string through __n() in C locale"); is(__n($got_singular, $got_plural, 2), $expect_plural, "Get plural string through __n() in C locale"); + + is(N__($got), $expect, "Passing a string through N__() in the C locale works"); } # Test a basic message on different locales From 186b52c52a46f843207839ad1bc78b6a8716c929 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:31 -0100 Subject: [PATCH 08/16] i18n: add--interactive: i18n of help_patch_cmd 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 Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 54 +++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 745462da53..1d267165fb 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1186,15 +1186,53 @@ sub edit_hunk_loop { } } +my %help_patch_modes = ( + stage => N__( +"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"), + stash => N__( +"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"), + reset_head => N__( +"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"), + reset_nothead => N__( +"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"), + checkout_index => N__( +"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"), + checkout_head => N__( +"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"), + checkout_nothead => N__( +"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"), +); + sub help_patch_cmd { - my $verb = lc $patch_mode_flavour{VERB}; - my $target = $patch_mode_flavour{TARGET}; - print colored $help_color, < Date: Wed, 14 Dec 2016 11:54:32 -0100 Subject: [PATCH 09/16] i18n: add--interactive: mark edit_hunk_manually message for translation 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 use cases, but left it for easier change in the future, e.g., wanting to change wording of one particular use case. The comment character is now used according to the git configuration core.commentchar. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 52 +++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 1d267165fb..9282dd5c63 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1055,6 +1055,30 @@ sub color_diff { } @_; } +my %edit_hunk_manually_modes = ( + stage => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for staging."), + stash => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for stashing."), + reset_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for unstaging."), + reset_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), + checkout_index => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding"), + checkout_head => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for discarding."), + checkout_nothead => N__( +"If the patch applies cleanly, the edited hunk will immediately be +marked for applying."), +); + sub edit_hunk_manually { my ($oldtext) = @_; @@ -1062,22 +1086,24 @@ 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 Git::comment_lines __("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 <; + my @newtext = grep { !/^$comment_line_char/ } <$fh>; close $fh; unlink $hunkfile; From ef84426308c4aa3837839566ba3185323e3cf117 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:33 -0100 Subject: [PATCH 10/16] i18n: add--interactive: remove %patch_modes entries Remove unnecessary entries from %patch_modes. After the i18n conversion, these entries are not used anymore. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 9282dd5c63..d6d627feea 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -105,9 +105,6 @@ my %patch_modes = ( DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stage', - TARGET => '', - PARTICIPLE => 'staging', FILTER => 'file-only', IS_REVERSE => 0, }, @@ -115,9 +112,6 @@ my %patch_modes = ( DIFF => 'diff-index -p HEAD', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Stash', - TARGET => '', - PARTICIPLE => 'stashing', FILTER => undef, IS_REVERSE => 0, }, @@ -125,9 +119,6 @@ my %patch_modes = ( DIFF => 'diff-index -p --cached', APPLY => sub { apply_patch 'apply -R --cached', @_; }, APPLY_CHECK => 'apply -R --cached', - VERB => 'Unstage', - TARGET => '', - PARTICIPLE => 'unstaging', FILTER => 'index-only', IS_REVERSE => 1, }, @@ -135,9 +126,6 @@ my %patch_modes = ( DIFF => 'diff-index -R -p --cached', APPLY => sub { apply_patch 'apply --cached', @_; }, APPLY_CHECK => 'apply --cached', - VERB => 'Apply', - TARGET => ' to index', - PARTICIPLE => 'applying', FILTER => 'index-only', IS_REVERSE => 0, }, @@ -145,9 +133,6 @@ my %patch_modes = ( DIFF => 'diff-files -p', APPLY => sub { apply_patch 'apply -R', @_; }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from worktree', - PARTICIPLE => 'discarding', FILTER => 'file-only', IS_REVERSE => 1, }, @@ -155,9 +140,6 @@ my %patch_modes = ( DIFF => 'diff-index -p', APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, APPLY_CHECK => 'apply -R', - VERB => 'Discard', - TARGET => ' from index and worktree', - PARTICIPLE => 'discarding', FILTER => undef, IS_REVERSE => 1, }, @@ -165,9 +147,6 @@ my %patch_modes = ( DIFF => 'diff-index -R -p', APPLY => sub { apply_patch_for_checkout_commit '', @_ }, APPLY_CHECK => 'apply', - VERB => 'Apply', - TARGET => ' to index and worktree', - PARTICIPLE => 'applying', FILTER => undef, IS_REVERSE => 0, }, From 55aa04423f14df3099466dc7eea4ca2d027b7d4c Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:34 -0100 Subject: [PATCH 11/16] i18n: add--interactive: mark status words for translation Mark words 'nothing', 'unchanged' and 'binary' used to display what has been staged or not, in "git add -i" status command. Alternatively one could mark N__('nothing') no-op in order to xgettext(1) extract the string and then trigger the translation at run time only with __($print->{FILE}), but that has the side effect of triggering retrieval of translations for the changes indicator too (e.g. +2/-1) which may or may not be a problem. To avoid that potential problem, mark only where there is certain to trigger translation only of those words but in this case we must also retrieve the translation for the eq tests, since the value assigned was of the translation, not the English source. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-add--interactive.perl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index d6d627feea..46dfc5276c 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -294,7 +294,7 @@ sub list_modified { my ($change, $bin); $file = unquote_path($file); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -303,7 +303,7 @@ sub list_modified { $data{$file} = { INDEX => $change, BINARY => $bin, - FILE => 'nothing', + FILE => __('nothing'), } } elsif (($adddel, $file) = @@ -319,7 +319,7 @@ sub list_modified { $file = unquote_path($file); my ($change, $bin); if ($add eq '-' && $del eq '-') { - $change = 'binary'; + $change = __('binary'); $bin = 1; } else { @@ -339,7 +339,7 @@ sub list_modified { $file = unquote_path($2); if (!exists $data{$file}) { $data{$file} = +{ - INDEX => 'unchanged', + INDEX => __('unchanged'), BINARY => 0, }; } @@ -354,10 +354,10 @@ sub list_modified { if ($only) { if ($only eq 'index-only') { - next if ($it->{INDEX} eq 'unchanged'); + next if ($it->{INDEX} eq __('unchanged')); } if ($only eq 'file-only') { - next if ($it->{FILE} eq 'nothing'); + next if ($it->{FILE} eq __('nothing')); } } push @return, +{ From a4dde4c4e9059416a6249dce3958ffce5a134e0f Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:35 -0100 Subject: [PATCH 12/16] i18n: send-email: mark strings for translation Mark strings often displayed to the user for translation. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 54 +++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index da81be40cb..06e64699b4 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 /; @@ -797,12 +798,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"); } @@ -829,7 +830,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 ", @@ -859,7 +860,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); } @@ -918,7 +919,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) { @@ -1293,17 +1297,23 @@ 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 __ < qr/^(?:yes|y|no|n|quit|q|all|a)/i, default => $ask_default); die "Send this email reply required" unless defined $_; @@ -1405,7 +1415,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") : __("OK. Log says:\n")); if (!file_name_is_absolute($smtp_server)) { print "Server: $smtp_server\n"; print "MAIL FROM:<$raw_from>\n"; @@ -1480,13 +1490,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; } @@ -1500,7 +1510,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; } @@ -1534,7 +1544,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) { @@ -1557,7 +1567,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; } } From 464931053bba771c670fa52ed6ca2eea72a55109 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:36 -0100 Subject: [PATCH 13/16] i18n: send-email: mark warnings and errors for translation Mark warnings, errors and other messages for translation. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 06e64699b4..00d234e114 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); } @@ -638,7 +638,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(); } @@ -730,7 +730,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 $_; @@ -739,7 +739,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) { @@ -1316,7 +1316,7 @@ EOF $_ = 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) { @@ -1342,7 +1342,7 @@ EOF } 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') { @@ -1427,10 +1427,10 @@ EOF } 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"); } } @@ -1703,7 +1703,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'); @@ -1711,7 +1711,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 { From 3c5cd20c7d177f0d93532271f421037c1440a2ff Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:37 -0100 Subject: [PATCH 14/16] i18n: send-email: mark string with interpolation for translation Mark warnings, errors and other messages that are interpolated for translation. We call sprintf() before calling die() and in few other circumstances in order to replace the values on the placeholders. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 87 ++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 00d234e114..7f3297cdfb 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,11 +585,11 @@ sub is_format_patch_arg { if (defined($format_patch)) { return $format_patch; } - die(< $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 || ''; @@ -692,10 +696,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; @@ -769,7 +773,9 @@ sub ask { return $resp; } if ($confirm_only) { - my $yesno = $term->readline("Are you sure you want to use <$resp> [y/N]? "); + my $yesno = $term->readline( + # TRANSLATORS: please keep [y/N] as is. + sprintf(__("Are you sure you want to use <%s> [y/N]? "), $resp)); if (defined $yesno && $yesno =~ /y/i) { return $resp; } @@ -811,9 +817,9 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) { if (!$force) { for my $f (@files) { if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) { - die "Refusing to send because the patch\n\t$f\n" + die sprintf(__("Refusing to send because the patch\n\t%s\n" . "has the template subject '*** SUBJECT HERE ***'. " - . "Pass --force if you really want to send.\n"; + . "Pass --force if you really want to send.\n"), $f); } } } @@ -848,7 +854,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; @@ -910,7 +916,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; } @@ -918,7 +924,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. @@ -1223,7 +1229,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); } } @@ -1386,14 +1392,14 @@ EOF # 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); } } } if (!$smtp) { - die "Unable to initialize SMTP properly. Check config and use --smtp-debug. ", - "VALUES: server=$smtp_server ", + die __("Unable to initialize SMTP properly. Check config and use --smtp-debug."), + " VALUES: server=$smtp_server ", "encryption=$smtp_encryption ", "hello=$smtp_domain", defined $smtp_server_port ? " port=$smtp_server_port" : ""; @@ -1410,10 +1416,10 @@ EOF $smtp->datasend("$line") or die $smtp->message; } $smtp->dataend() or die $smtp->message; - $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message; + $smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message; } if ($quiet) { - printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject); + printf($dry_run ? __("Dry-Sent %s\n") : __("Sent %s\n"), $subject); } else { print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n")); if (!file_name_is_absolute($smtp_server)) { @@ -1443,7 +1449,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; @@ -1665,18 +1671,18 @@ sub recipients_cmd { my @addresses = (); open my $fh, "-|", "$cmd \Q$file\E" - or die "($prefix) Could not execute '$cmd'"; + or die sprintf(__("(%s) Could not execute '%s'"), $prefix, $cmd); while (my $address = <$fh>) { $address =~ s/^\s*//g; $address =~ s/\s*$//g; $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; } @@ -1730,10 +1736,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; @@ -1749,10 +1755,11 @@ sub handle_backup { (substr($file, 0, $lastlen) eq $last) && ($suffix = substr($file, $lastlen)) !~ /^[a-z0-9]/i) { if (defined $known_suffix && $suffix eq $known_suffix) { - print "Skipping $file with backup suffix '$known_suffix'.\n"; + printf(__("Skipping %s with backup suffix '%s'.\n"), $file, $known_suffix); $skip = 1; } else { - my $answer = ask("Do you really want to send $file? (y|N): ", + # TRANSLATORS: please keep "[y|N]" as is. + my $answer = ask(sprintf(__("Do you really want to send %s? [y|N]: "), $file), valid_re => qr/^(?:y|n)/i, default => 'n'); $skip = ($answer ne 'y'); @@ -1780,7 +1787,7 @@ sub handle_backup_files { 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:]]/; } @@ -1790,7 +1797,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:]]/; From 70aedfb3ecef6660962d84cbd1455704cd9be5b8 Mon Sep 17 00:00:00 2001 From: Vasco Almeida Date: Wed, 14 Dec 2016 11:54:38 -0100 Subject: [PATCH 15/16] i18n: send-email: mark composing message for translation When composing an e-mail, there is a message for the user whose lines begin in "GIT:" that can be marked for translation. Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-send-email.perl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/git-send-email.perl b/git-send-email.perl index 7f3297cdfb..068d60b3e6 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -672,18 +672,20 @@ if ($compose) { my $tpl_subject = $initial_subject || ''; my $tpl_reply_to = $initial_reply_to || ''; - print $c < Date: Wed, 14 Dec 2016 11:54:39 -0100 Subject: [PATCH 16/16] i18n: difftool: mark warnings for translation Signed-off-by: Vasco Almeida Signed-off-by: Junio C Hamano --- git-difftool.perl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/git-difftool.perl b/git-difftool.perl index a5790d03a0..8d3632e556 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 { @@ -122,7 +123,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 @@ -338,7 +339,7 @@ sub main if (length($opts{difftool_cmd}) > 0) { $ENV{GIT_DIFF_TOOL} = $opts{difftool_cmd}; } else { - print "No given for --tool=\n"; + print __("No given for --tool=\n"); usage(1); } } @@ -346,7 +347,7 @@ sub main if (length($opts{extcmd}) > 0) { $ENV{GIT_DIFFTOOL_EXTCMD} = $opts{extcmd}; } else { - print "No given for --extcmd=\n"; + print __("No given for --extcmd=\n"); usage(1); } } @@ -419,11 +420,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; @@ -435,8 +436,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);