Merge branch 'va/i18n-perl-scripts'
authorJunio C Hamano <gitster@pobox.com>
Tue, 27 Dec 2016 08:11:40 +0000 (00:11 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 27 Dec 2016 08:11:40 +0000 (00:11 -0800)
Porcelain scripts written in Perl are getting internationalized.

* va/i18n-perl-scripts:
i18n: difftool: mark warnings for translation
i18n: send-email: mark composing message for translation
i18n: send-email: mark string with interpolation for translation
i18n: send-email: mark warnings and errors for translation
i18n: send-email: mark strings for translation
i18n: add--interactive: mark status words for translation
i18n: add--interactive: remove %patch_modes entries
i18n: add--interactive: mark edit_hunk_manually message for translation
i18n: add--interactive: i18n of help_patch_cmd
i18n: add--interactive: mark patch prompt for translation
i18n: add--interactive: mark plural strings
i18n: clean.c: match string with git-add--interactive.perl
i18n: add--interactive: mark strings with interpolation for translation
i18n: add--interactive: mark simple here-documents for translation
i18n: add--interactive: mark strings for translation
Git.pm: add subroutines for commenting lines

1  2 
Makefile
git-add--interactive.perl
git-difftool.perl
perl/Git.pm
diff --combined Makefile
index 05a35ad3b55bc4270150b99c49def71714f1ffe2,c79aceb42d4531a7873c3531c6c792c1d68c85b6..d861bd9985dd41c24d5ff08a985c9d280d9dc07c
+++ b/Makefile
@@@ -301,8 -301,7 +301,8 @@@ all:
  # crashes due to allocation and free working on different 'heaps'.
  # It's defined automatically if USE_NED_ALLOCATOR is set.
  #
 -# Define NO_REGEX if you have no or inferior regex support in your C library.
 +# Define NO_REGEX if your C library lacks regex support with REG_STARTEND
 +# feature.
  #
  # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the
  # user.
  #
  # Define NATIVE_CRLF if your platform uses CRLF for line endings.
  #
 -# Define XDL_FAST_HASH to use an alternative line-hashing method in
 -# the diff algorithm.  It gives a nice speedup if your processor has
 -# fast unaligned word loads.  Does NOT work on big-endian systems!
 -# Enabled by default on x86_64.
 -#
  # Define GIT_USER_AGENT if you want to change how git identifies itself during
  # network interactions.  The default is "git/$(GIT_VERSION)".
  #
@@@ -457,12 -461,10 +457,12 @@@ CURL_CONFIG = curl-confi
  PTHREAD_LIBS = -lpthread
  PTHREAD_CFLAGS =
  GCOV = gcov
 +SPATCH = spatch
  
  export TCL_PATH TCLTK_PATH
  
  SPARSE_FLAGS =
 +SPATCH_FLAGS = --all-includes
  
  
  
@@@ -827,7 -829,6 +827,7 @@@ LIB_OBJS += submodule-config.
  LIB_OBJS += symlinks.o
  LIB_OBJS += tag.o
  LIB_OBJS += tempfile.o
 +LIB_OBJS += tmp-objdir.o
  LIB_OBJS += trace.o
  LIB_OBJS += trailer.o
  LIB_OBJS += transport.o
@@@ -1042,7 -1043,6 +1042,7 @@@ ifeq ($(uname_S),Darwin
                endif
        endif
        ifndef NO_APPLE_COMMON_CRYPTO
 +              NO_OPENSSL = YesPlease
                APPLE_COMMON_CRYPTO = YesPlease
                COMPAT_CFLAGS += -DAPPLE_COMMON_CRYPTO
        endif
@@@ -1480,6 -1480,10 +1480,6 @@@ ifndef NO_MSGFMT_EXTENDED_OPTION
        MSGFMT += --check --statistics
  endif
  
 -ifneq (,$(XDL_FAST_HASH))
 -      BASIC_CFLAGS += -DXDL_FAST_HASH
 -endif
 -
  ifdef GMTIME_UNRELIABLE_ERRORS
        COMPAT_OBJS += compat/gmtime.o
        BASIC_CFLAGS += -DGMTIME_UNRELIABLE_ERRORS
@@@ -2105,7 -2109,8 +2105,8 @@@ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --
        --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__ --keyword="__n:1,2"
  LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
  LOCALIZED_SH = $(SCRIPT_SH)
  LOCALIZED_SH += git-parse-remote.sh
@@@ -2140,22 -2145,9 +2141,22 @@@ endi
  po/build/locale/%/LC_MESSAGES/git.mo: po/%.po
        $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $<
  
 -FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
 -                      $(FIND) . \( -name .git -type d -prune \) \
 -                              -o \( -name '*.[hcS]' -type f -print \) )
 +FIND_SOURCE_FILES = ( \
 +      git ls-files \
 +              '*.[hcS]' \
 +              '*.sh' \
 +              ':!*[tp][0-9][0-9][0-9][0-9]*' \
 +              ':!contrib' \
 +              2>/dev/null || \
 +      $(FIND) . \
 +              \( -name .git -type d -prune \) \
 +              -o \( -name '[tp][0-9][0-9][0-9][0-9]*' -prune \) \
 +              -o \( -name contrib -type d -prune \) \
 +              -o \( -name build -type d -prune \) \
 +              -o \( -name 'trash*' -type d -prune \) \
 +              -o \( -name '*.[hcS]' -type f -print \) \
 +              -o \( -name '*.sh' -type f -print \) \
 +      )
  
  $(ETAGS_TARGET): FORCE
        $(RM) $(ETAGS_TARGET)
@@@ -2317,18 -2309,6 +2318,18 @@@ check: common-cmds.
                exit 1; \
        fi
  
 +C_SOURCES = $(patsubst %.o,%.c,$(C_OBJ))
 +%.cocci.patch: %.cocci $(C_SOURCES)
 +      @echo '    ' SPATCH $<; \
 +      for f in $(C_SOURCES); do \
 +              $(SPATCH) --sp-file $< $$f $(SPATCH_FLAGS); \
 +      done >$@ 2>$@.log; \
 +      if test -s $@; \
 +      then \
 +              echo '    ' SPATCH result: $@; \
 +      fi
 +coccicheck: $(patsubst %.cocci,%.cocci.patch,$(wildcard contrib/coccinelle/*.cocci))
 +
  ### Installation rules
  
  ifneq ($(filter /%,$(firstword $(template_dir))),)
@@@ -2520,7 -2500,6 +2521,7 @@@ clean: profile-clean coverage-clea
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
 +      $(RM) contrib/coccinelle/*.cocci.patch*
        $(MAKE) -C Documentation/ clean
  ifndef NO_PERL
        $(MAKE) -C gitweb clean
index ee3d812695fa232f30682b0e546105ca0eea5d49,46dfc5276ca1570e5a818981ce19de08781313c5..4e0ab5a9bc2f8b22fb7e358b162ebaa93c55e139
@@@ -4,6 -4,7 +4,7 @@@ use 5.008
  use strict;
  use warnings;
  use Git;
+ use Git::I18N;
  
  binmode(STDOUT, ":raw");
  
@@@ -45,7 -46,6 +46,7 @@@ my ($diff_new_color) 
  my $normal_color = $repo->get_color("", "reset");
  
  my $diff_algorithm = $repo->config('diff.algorithm');
 +my $diff_indent_heuristic = $repo->config_bool('diff.indentheuristic');
  my $diff_compaction_heuristic = $repo->config_bool('diff.compactionheuristic');
  my $diff_filter = $repo->config('interactive.difffilter');
  
@@@ -92,6 -92,7 +93,7 @@@ sub colored 
  }
  
  # command line options
+ my $cmd;
  my $patch_mode;
  my $patch_mode_revision;
  
@@@ -104,9 -105,6 +106,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,
        },
                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,
        },
                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,
        },
                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,
        },
                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,
        },
                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,
        },
                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,
        },
  );
  
- 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') {
@@@ -253,8 -234,9 +235,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;
@@@ -312,7 -294,7 +295,7 @@@ sub list_modified 
                        my ($change, $bin);
                        $file = unquote_path($file);
                        if ($add eq '-' && $del eq '-') {
-                               $change = 'binary';
+                               $change = __('binary');
                                $bin = 1;
                        }
                        else {
                        $data{$file} = {
                                INDEX => $change,
                                BINARY => $bin,
-                               FILE => 'nothing',
+                               FILE => __('nothing'),
                        }
                }
                elsif (($adddel, $file) =
                        $file = unquote_path($file);
                        my ($change, $bin);
                        if ($add eq '-' && $del eq '-') {
-                               $change = 'binary';
+                               $change = __('binary');
                                $bin = 1;
                        }
                        else {
                        $file = unquote_path($2);
                        if (!exists $data{$file}) {
                                $data{$file} = +{
-                                       INDEX => 'unchanged',
+                                       INDEX => __('unchanged'),
                                        BINARY => 0,
                                };
                        }
  
                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, +{
@@@ -613,12 -595,12 +596,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++) {
  }
  
  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
@@@ -646,7 -628,7 +629,7 @@@ EO
  }
  
  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
@@@ -667,12 -649,18 +650,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);
        }
  }
  
@@@ -680,7 -668,7 +669,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) {
  }
  
  sub revert_cmd {
-       my @update = list_and_choose({ PROMPT => 'Revert',
+       my @update = list_and_choose({ PROMPT => __('Revert'),
                                       HEADER => $status_head, },
                                     list_modified());
        if (@update) {
                                    $_->{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});
                                }
                        }
                }
  }
  
  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";
  }
@@@ -751,9 -739,7 +740,9 @@@ sub parse_diff 
        if (defined $diff_algorithm) {
                splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
        }
 -      if ($diff_compaction_heuristic) {
 +      if ($diff_indent_heuristic) {
 +              splice @diff_cmd, 1, 0, "--indent-heuristic";
 +      } elsif ($diff_compaction_heuristic) {
                splice @diff_cmd, 1, 0, "--compaction-heuristic";
        }
        if (defined $patch_mode_revision) {
@@@ -1048,29 -1034,55 +1037,55 @@@ 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) = @_;
  
        my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
        my $fh;
        open $fh, '>', $hunkfile
-               or die "failed to open hunk edit file for writing: " . $!;
-       print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n";
+               or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
+       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 <<EOF;
- # ---
- # 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
- # an opportunity to edit again. If all lines of the hunk are removed,
- # then the edit is aborted and the hunk is left unchanged.
+       my $comment_line_char = Git::get_comment_line_char;
+       print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
+ ---
+ To remove '%s' lines, make them ' ' lines (context).
+ To remove '%s' lines, delete them.
+ Lines starting with %s will be removed.
  EOF
+ __($edit_hunk_manually_modes{$patch_mode}),
+ # TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
+ __ <<EOF2 ;
+ 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.
+ EOF2
        close $fh;
  
        chomp(my $editor = run_cmd_pipe(qw(git var GIT_EDITOR)));
        }
  
        open $fh, '<', $hunkfile
-               or die "failed to open hunk edit file for reading: " . $!;
-       my @newtext = grep { !/^#/ } <$fh>;
+               or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
+       my @newtext = grep { !/^$comment_line_char/ } <$fh>;
        close $fh;
        unlink $hunkfile;
  
@@@ -1166,22 -1178,66 +1181,66 @@@ 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;
                }
        }
  }
  
+ 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, <<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
+       print colored $help_color, __($help_patch_modes{$patch_mode}), "\n", __ <<EOF ;
  g - select a hunk to go to
  / - search for a hunk matching the given regex
  j - leave this hunk undecided, see next undecided hunk
@@@ -1213,11 -1269,11 +1272,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 {
  
  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;
  
  
        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;
        }
                @them = @mods;
        }
        else {
-               @them = list_and_choose({ PROMPT => 'Patch update',
+               @them = list_and_choose({ PROMPT => __('Patch update'),
                                          HEADER => $status_head, },
                                        @mods);
        }
@@@ -1297,6 -1353,44 +1356,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);
                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) {
                                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 = '';
                                        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(__n("Sorry, only %d hunk available.\n",
+                                                             "Sorry, only %d hunks available.\n", $num), $num);
                                }
                                next;
                        }
                        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;
                                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;
                                        $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;
                                        }
                                }
                                        $ix--;
                                }
                                else {
-                                       error_msg "No previous hunk\n";
+                                       error_msg __("No previous hunk\n");
                                }
                                next;
                        }
                                        $ix++;
                                }
                                else {
-                                       error_msg "No next hunk\n";
+                                       error_msg __("No next hunk\n");
                                }
                                next;
                        }
                                        }
                                }
                                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;
                                }
                        }
                        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;
@@@ -1560,23 -1655,25 +1658,25 @@@ 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);
  }
  
  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
@@@ -1594,39 -1691,40 +1694,40 @@@ 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";
+                               die sprintf(__("unknown --patch mode: %s"), $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 "--";
+               die sprintf(__("invalid argument %s, expecting --"),
+                              $arg) unless $arg eq "--";
                %patch_mode_flavour = %{$patch_modes{$patch_mode}};
+               $cmd = 1;
        }
        elsif ($arg ne "--") {
-               die "invalid argument $arg, expecting --";
+               die sprintf(__("invalid argument %s, expecting --"), $arg);
        }
  }
  
@@@ -1641,10 -1739,10 +1742,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) {
  
  process_args();
  refresh();
- if ($patch_mode) {
+ if ($cmd) {
        patch_update_cmd();
  }
  else {
diff --combined git-difftool.perl
index 959822d5f31f60c3b3366292c973e26f0948c45a,8d3632e556690eb815af3a9fee277f5ea355572a..674828de3920634505291eee03f8ea908430d938
@@@ -22,6 -22,7 +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 +123,7 @@@ sub setup_dir_dif
        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
                }
        }
  
 +      # Go to the root of the worktree so that the left index files
 +      # are properly setup -- the index is toplevel-relative.
 +      chdir($workdir);
 +
        # Setup temp directories
        my $tmpdir = tempdir('git-difftool.XXXXX', CLEANUP => 0, TMPDIR => 1);
        my $ldir = "$tmpdir/left";
@@@ -342,7 -339,7 +343,7 @@@ sub mai
                if (length($opts{difftool_cmd}) > 0) {
                        $ENV{GIT_DIFF_TOOL} = $opts{difftool_cmd};
                } else {
-                       print "No <tool> given for --tool=<tool>\n";
+                       print __("No <tool> given for --tool=<tool>\n");
                        usage(1);
                }
        }
                if (length($opts{extcmd}) > 0) {
                        $ENV{GIT_DIFFTOOL_EXTCMD} = $opts{extcmd};
                } else {
-                       print "No <cmd> given for --extcmd=<cmd>\n";
+                       print __("No <cmd> given for --extcmd=<cmd>\n");
                        usage(1);
                }
        }
@@@ -423,11 -420,11 +424,11 @@@ sub dir_dif
                }
  
                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;
                }
        }
        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);
diff --combined perl/Git.pm
index b2732822af0f34c6f7731df8a1a017d19f3e0508,f699fee9a17282f20ddcc811bc676a8217c7d558..bfce1f795dfa5fea05f4f96637a1ae2333038735
@@@ -59,7 -59,7 +59,7 @@@ require Exporter
                  command_bidi_pipe command_close_bidi_pipe
                  version exec_path html_path hash_object git_cmd_try
                  remote_refs prompt
 -                get_tz_offset
 +                get_tz_offset get_record
                  credential credential_read credential_write
                  temp_acquire temp_is_locked temp_release temp_reset temp_path);
  
@@@ -538,20 -538,6 +538,20 @@@ sub get_tz_offset 
        return sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]);
  }
  
 +=item get_record ( FILEHANDLE, INPUT_RECORD_SEPARATOR )
 +
 +Read one record from FILEHANDLE delimited by INPUT_RECORD_SEPARATOR,
 +removing any trailing INPUT_RECORD_SEPARATOR.
 +
 +=cut
 +
 +sub get_record {
 +      my ($fh, $rs) = @_;
 +      local $/ = $rs;
 +      my $rec = <$fh>;
 +      chomp $rec if defined $rs;
 +      $rec;
 +}
  
  =item prompt ( PROMPT , ISPASSWORD  )
  
@@@ -885,8 -871,6 +885,8 @@@ Return an array of mailboxes extracted 
  
  =cut
  
 +# Very close to Mail::Address's parser, but we still have minor
 +# differences in some cases (see t9000 for examples).
  sub parse_mailboxes {
        my $re_comment = qr/\((?:[^)]*)\)/;
        my $re_quote = qr/"(?:[^\"\\]|\\.)*"/;
        # divide the string in tokens of the above form
        my $re_token = qr/(?:$re_quote|$re_word|$re_comment|\S)/;
        my @tokens = map { $_ =~ /\s*($re_token)\s*/g } @_;
 +      my $end_of_addr_seen = 0;
  
        # add a delimiter to simplify treatment for the last mailbox
        push @tokens, ",";
                if ($token =~ /^[,;]$/) {
                        # if buffer still contains undeterminated strings
                        # append it at the end of @address or @phrase
 -                      if (@address) {
 -                              push @address, @buffer;
 -                      } else {
 +                      if ($end_of_addr_seen) {
                                push @phrase, @buffer;
 +                      } else {
 +                              push @address, @buffer;
                        }
  
                        my $str_phrase = join ' ', @phrase;
                        push @addr_list, $str_mailbox if ($str_mailbox);
  
                        @phrase = @address = @comment = @buffer = ();
 +                      $end_of_addr_seen = 0;
                } elsif ($token =~ /^\(/) {
                        push @comment, $token;
                } elsif ($token eq "<") {
                        push @phrase, (splice @address), (splice @buffer);
                } elsif ($token eq ">") {
 +                      $end_of_addr_seen = 1;
                        push @address, (splice @buffer);
 -              } elsif ($token eq "@") {
 +              } elsif ($token eq "@" && !$end_of_addr_seen) {
                        push @address, (splice @buffer), "@";
 -              } elsif ($token eq ".") {
 -                      push @address, (splice @buffer), ".";
                } else {
                        push @buffer, $token;
                }
@@@ -1438,6 -1421,44 +1438,44 @@@ sub END 
  
  } # %TEMP_* Lexical Context
  
+ =item prefix_lines ( PREFIX, STRING [, STRING... ])
+ Prefixes lines in C<STRING> with C<PREFIX>.
+ =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