add po-mode.el from the gettext repo
authorAmin Bandali <bandali@gnu.org>
Sun, 16 May 2021 01:04:27 +0000 (21:04 -0400)
committerAmin Bandali <bandali@gnu.org>
Sun, 16 May 2021 01:04:27 +0000 (21:04 -0400)
https://git.savannah.gnu.org/gitweb/?p=gettext.git;a=blob;f=gettext-tools/emacs/po-mode.el;h=e04428772c1c487773282d68115672dd1e823fa5;hb=HEAD

.emacs.d/lisp/po-mode.el [new file with mode: 0644]

diff --git a/.emacs.d/lisp/po-mode.el b/.emacs.d/lisp/po-mode.el
new file mode 100644 (file)
index 0000000..e044287
--- /dev/null
@@ -0,0 +1,3427 @@
+;;; po-mode.el --- major mode for GNU gettext PO files
+
+;; Copyright (C) 1995-2002, 2005-2008, 2010, 2013-2017, 2019-2020 Free Software
+;; Foundation, Inc.
+
+;; Authors: François Pinard <pinard@iro.umontreal.ca>
+;;          Greg McGary <gkm@magilla.cichlid.com>
+;; Keywords: i18n gettext
+;; Created: 1995
+
+;; This file is part of GNU gettext.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package provides the tools meant to help editing PO files,
+;; as documented in the GNU gettext user's manual.  See this manual
+;; for user documentation, which is not repeated here.
+
+;; To install, merely put this file somewhere GNU Emacs will find it,
+;; then add the following lines to your .emacs file:
+;;
+;;   (autoload 'po-mode "po-mode"
+;;             "Major mode for translators to edit PO files" t)
+;;   (setq auto-mode-alist (cons '("\\.po\\'\\|\\.po\\." . po-mode)
+;;                               auto-mode-alist))
+;;
+;; To use the right coding system automatically under Emacs 20 or newer,
+;; also add:
+;;
+;;   (autoload 'po-find-file-coding-system "po-compat")
+;;   (modify-coding-system-alist 'file "\\.po\\'\\|\\.po\\."
+;;                               'po-find-file-coding-system)
+;;
+;; You may also adjust some variables, below, by defining them in your
+;; '.emacs' file, either directly or through command 'M-x customize'.
+
+;; TODO:
+;; Plural form editing:
+;;  - When in edit mode, currently it highlights (in green) the msgid;
+;;    it should also highlight the msgid_plural string, I would say, since
+;;    the translator has to look at both.
+;;  - After the translator finished the translation of msgstr[0], it would
+;;    be nice if the cursor would automatically move to the beginning of the
+;;    msgstr[1] line, so that the translator just needs to press RET to edit
+;;    that.
+;;  - If msgstr[1] is empty but msgstr[0] is not, it would be ergonomic if the
+;;    contents of msgstr[0] would be copied. (Not sure if this should happen
+;;    at the end of the editing msgstr[0] or at the beginning of the editing
+;;    of msgstr[1].) Reason: These two strings are usually very similar.
+
+;;; Code:
+\f
+(defconst po-mode-version-string "2.27" "\
+Version number of this version of po-mode.el.")
+
+;;; Emacs portability matters - part I.
+;;; Here is the minimum for customization to work.  See part II.
+
+;; Experiment with Emacs LISP message internationalisation.
+(eval-and-compile
+  (or (fboundp 'set-translation-domain)
+      (defsubst set-translation-domain (string) nil))
+  (or (fboundp 'translate-string)
+      (defsubst translate-string (string) string)))
+(defsubst _ (string) (translate-string string))
+(defsubst N_ (string) string)
+
+;; Handle missing 'customs' package.
+(eval-and-compile
+  (condition-case ()
+      (require 'custom)
+    (error nil))
+  (if (and (featurep 'custom) (fboundp 'custom-declare-variable))
+      nil
+    (defmacro defgroup (&rest args)
+      nil)
+    (defmacro defcustom (var value doc &rest args)
+      `(defvar ,var ,value ,doc))))
+\f
+;;; Customisation.
+
+(defgroup po nil
+  "Major mode for editing PO files"
+  :group 'i18n)
+
+(defcustom po-auto-edit-with-msgid nil
+  "*Automatically use msgid when editing untranslated entries."
+  :type 'boolean
+  :group 'po)
+
+(defcustom po-auto-fuzzy-on-edit nil
+  "*Automatically mark entries fuzzy when being edited."
+  :type 'boolean
+  :group 'po)
+
+(defcustom po-auto-delete-previous-msgid t
+  "*Automatically delete previous msgid (marked #|) when editing entry.
+Value is nil, t, or ask."
+  :type '(choice (const nil)
+                 (const t)
+                 (const ask))
+  :group 'po)
+
+(defcustom po-auto-select-on-unfuzzy nil
+  "*Automatically select some new entry while making an entry not fuzzy."
+  :type 'boolean
+  :group 'po)
+
+(defcustom po-keep-mo-file nil
+  "*Set whether MO file should be kept or discarded after validation."
+  :type 'boolean
+  :group 'po)
+
+(defcustom po-auto-update-file-header t
+  "*Automatically revise headers.  Value is nil, t, or ask."
+  :type '(choice (const nil)
+                 (const t)
+                 (const ask))
+  :group 'po)
+
+(defcustom po-auto-replace-revision-date t
+  "*Automatically revise date in headers.  Value is nil, t, or ask."
+  :type '(choice (const nil)
+                 (const t)
+                 (const ask))
+  :group 'po)
+
+(defcustom po-default-file-header "\
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Free Software Foundation, Inc.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid \"\"
+msgstr \"\"
+\"Project-Id-Version: PACKAGE VERSION\\n\"
+\"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\\n\"
+\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"
+\"Language-Team: LANGUAGE <LL@li.org>\\n\"
+\"MIME-Version: 1.0\\n\"
+\"Content-Type: text/plain; charset=CHARSET\\n\"
+\"Content-Transfer-Encoding: 8bit\\n\"
+"
+  "*Default PO file header."
+  :type 'string
+  :group 'po)
+
+(defcustom po-translation-project-address
+  "robot@translationproject.org"
+  "*Electronic mail address of the Translation Project.
+Typing \\[po-send-mail] (normally bound to `M') the user will send the PO file
+to this email address."
+  :type 'string
+  :group 'po)
+
+(defcustom po-translation-project-mail-label "TP-Robot"
+  "*Subject label when sending the PO file to `po-translation-project-address'."
+  :type 'string
+  :group 'po)
+
+(defcustom po-highlighting t
+  "*Highlight text whenever appropriate, when non-nil.
+However, on older Emacses, a yet unexplained highlighting bug causes files
+to get mangled."
+  :type 'boolean
+  :group 'po)
+
+(defcustom po-highlight-face 'highlight
+  "*The face used for PO mode highlighting.  For Emacses with overlays.
+Possible values are 'highlight', 'modeline', 'secondary-selection',
+'region', and 'underline'.
+This variable can be set by the user to whatever face they desire.
+It's most convenient if the cursor color and highlight color are
+slightly different."
+  :type 'face
+  :group 'po)
+
+(defcustom po-team-name-to-code
+  ;; All possible languages, a complete ISO 639 list, the inverse of
+  ;; gettext-tools/src/lang-table.c, and a little more.
+  '(("LANGUAGE" . "LL")
+    ("(Afan) Oromo" . "om")
+    ("Abkhazian" . "ab")
+    ("Achinese" . "ace")
+    ("Afar" . "aa")
+    ("Afrikaans" . "af")
+    ("Akan" . "ak")
+    ("Albanian" . "sq")
+    ("Amharic" . "am")
+    ("Arabic" . "ar")
+    ("Aragonese" . "an")
+    ("Argentinian" . "es_AR")
+    ("Armenian" . "hy")
+    ("Assamese" . "as")
+    ("Austrian" . "de_AT")
+    ("Avaric" . "av")
+    ("Avestan" . "ae")
+    ("Awadhi" . "awa")
+    ("Aymara" . "ay")
+    ("Azerbaijani" . "az")
+    ("Balinese" . "ban")
+    ("Baluchi" . "bal")
+    ("Bambara" . "bm")
+    ("Bashkir" . "ba")
+    ("Basque" . "eu")
+    ("Beja" . "bej")
+    ("Belarusian" . "be")
+    ("Bemba" . "bem")
+    ("Bengali" . "bn")
+    ("Bhojpuri" . "bho")
+    ("Bihari" . "bh")
+    ("Bikol" . "bik")
+    ("Bini" . "bin")
+    ("Bislama" . "bi")
+    ("Bosnian" . "bs")
+    ("Brazilian Portuguese" . "pt_BR")
+    ("Breton" . "br")
+    ("Buginese" . "bug")
+    ("Bulgarian" . "bg")
+    ("Burmese" . "my")
+    ("Catalan" . "ca")
+    ("Cebuano" . "ceb")
+    ("Central Khmer" . "km")
+    ("Chamorro" . "ch")
+    ("Chechen" . "ce")
+    ("Chinese" . "zh")
+    ("Chinese (Hong Kong)" . "zh_HK")
+    ("Chinese (simplified)" . "zh_CN")
+    ("Chinese (traditional)" . "zh_TW")
+    ("Church Slavic" . "cu")
+    ("Chuvash" . "cv")
+    ("Cornish" . "kw")
+    ("Corsican" . "co")
+    ("Cree" . "cr")
+    ("Croatian" . "hr")
+    ("Czech" . "cs")
+    ("Danish" . "da")
+    ("Dinka" . "din")
+    ("Divehi" . "dv")
+    ("Dogri" . "doi")
+    ("Dutch" . "nl")
+    ("Dzongkha" . "dz")
+    ("English" . "en")
+    ("English (British)" . "en_GB")
+    ("Esperanto" . "eo")
+    ("Estonian" . "et")
+    ("Ewe" . "ee")
+    ("Faroese" . "fo")
+    ("Fijian" . "fj")
+    ("Filipino" . "fil")
+    ("Finnish" . "fi")
+    ("Fon" . "fon")
+    ("French" . "fr")
+    ("Frisian" . "fy")
+    ("Fulah" . "ff")
+    ("Galician" . "gl")
+    ("Ganda" . "lg")
+    ("Georgian" . "ka")
+    ("German" . "de")
+    ("Gondi" . "gon")
+    ("Greek" . "el")
+    ("Guarani" . "gn")
+    ("Gujarati" . "gu")
+    ("Haitian" . "ht")
+    ("Hausa" . "ha")
+    ("Hebrew" . "he")
+    ("Herero" . "hz")
+    ("Hiligaynon" . "hil")
+    ("Hindi" . "hi")
+    ("Hiri Motu" . "ho")
+    ("Hmong" . "hmn")
+    ("Hungarian" . "hu")
+    ("Hyam" . "jab")
+    ("Icelandic" . "is")
+    ("Ido" . "io")
+    ("Igbo" . "ig")
+    ("Iloko" . "ilo")
+    ("Indonesian" . "id")
+    ("Interlingua" . "ia")
+    ("Interlingue" . "ie")
+    ("Inuktitut" . "iu")
+    ("Inupiak" . "ik")
+    ("Irish" . "ga")
+    ("Italian" . "it")
+    ("Japanese" . "ja")
+    ("Javanese" . "jv")
+    ("Jju" . "kaj")
+    ("Kabardian" . "kbd")
+    ("Kabyle" . "kab")
+    ("Kagoma" . "kdm")
+    ("Kalaallisut" . "kl")
+    ("Kamba" . "kam")
+    ("Kannada" . "kn")
+    ("Kanuri" . "kr")
+    ("Kashmiri" . "ks")
+    ("Kashubian" . "csb")
+    ("Kazakh" . "kk")
+    ("Khmer" . "km") ; old name
+    ("Kikuyu" . "ki")
+    ("Kimbundu" . "kmb")
+    ("Kinyarwanda" . "rw")
+    ("Kirghiz" . "ky")
+    ("Kirundi" . "rn")
+    ("Komi" . "kv")
+    ("Kongo" . "kg")
+    ("Konkani" . "kok")
+    ("Korean" . "ko")
+    ("Kuanyama" . "kj")
+    ("Kurdish" . "ku")
+    ("Kurukh" . "kru")
+    ("Laotian" . "lo")
+    ("Latin" . "la")
+    ("Latvian" . "lv")
+    ("Letzeburgesch" . "lb")
+    ("Limburgish" . "li")
+    ("Lingala" . "ln")
+    ("Lithuanian" . "lt")
+    ("Low Saxon" . "nds")
+    ("Luba-Katanga" . "lu")
+    ("Luba-Lulua" . "lua")
+    ("Luo" . "luo")
+    ("Macedonian" . "mk")
+    ("Madurese" . "mad")
+    ("Magahi" . "mag")
+    ("Maithili" . "mai")
+    ("Makasar" . "mak")
+    ("Malagasy" . "mg")
+    ("Malay" . "ms")
+    ("Malayalam" . "ml")
+    ("Maltese" . "mt")
+    ("Mandingo" . "man")
+    ("Manipuri" . "mni")
+    ("Manx" . "gv")
+    ("Maori" . "mi")
+    ("Marathi" . "mr")
+    ("Marshall" . "mh")
+    ("Marshallese" . "mh")
+    ("Marwari" . "mwr")
+    ("Mayan" . "myn")
+    ("Mende" . "men")
+    ("Minangkabau" . "min")
+    ("Moldavian" . "mo")
+    ("Mongolian" . "mn")
+    ("Mossi" . "mos")
+    ("Nahuatl" . "nah")
+    ("Nauru" . "na")
+    ("Navajo" . "nv")
+    ("Ndonga" . "ng")
+    ("Neapolitan" . "nap")
+    ("Nepali" . "ne")
+    ("North Ndebele" . "nd")
+    ("Northern Sami" . "se")
+    ("Northern Sotho" . "nso")
+    ("Norwegian Bokmal" . "nb")
+    ("Norwegian Nynorsk" . "nn")
+    ("Norwegian" . "no")
+    ("Nyamwezi" . "nym")
+    ("Nyanja" . "ny")
+    ("Nyankole" . "nyn")
+    ("Occitan" . "oc")
+    ("Ojibwa" . "oj")
+    ("Old English" . "ang")
+    ("Oriya" . "or")
+    ("Ossetian" . "os")
+    ("Páez" . "pbb")
+    ("Pali" . "pi")
+    ("Pampanga" . "pam")
+    ("Pangasinan" . "pag")
+    ("Pashto" . "ps")
+    ("Persian" . "fa")
+    ("Polish" . "pl")
+    ("Portuguese" . "pt")
+    ("Punjabi" . "pa")
+    ("Quechua" . "qu")
+    ("Rajasthani" . "raj")
+    ("Rhaeto-Roman" . "rm") ; old name
+    ("Romanian" . "ro")
+    ("Romansh" . "rm")
+    ("Russian" . "ru")
+    ("Samoan" . "sm")
+    ("Sango" . "sg")
+    ("Sanskrit" . "sa")
+    ("Santali" . "sat")
+    ("Sardinian" . "sc")
+    ("Sasak" . "sas")
+    ("Scots" . "gd") ; old name
+    ("Scottish Gaelic" . "gd")
+    ("Serbian" . "sr")
+    ("Serer" . "srr")
+    ("Sesotho" . "st")
+    ("Setswana" . "tn")
+    ("Shan" . "shn")
+    ("Shona" . "sn")
+    ("Sichuan Yi" . "ii")
+    ("Sicilian" . "scn")
+    ("Sidamo" . "sid")
+    ("Sindhi" . "sd")
+    ("Sinhala" . "si")
+    ("Sinhalese" . "si")
+    ("Siswati" . "ss")
+    ("Slovak" . "sk")
+    ("Slovenian" . "sl")
+    ("Somali" . "so")
+    ("Sorbian" . "wen")
+    ("South Ndebele" . "nr")
+    ("Spanish" . "es")
+    ("Spanish (Canary Islands)" . "es_IC")
+    ("Sukuma" . "suk")
+    ("Sundanese" . "su")
+    ("Susu" . "sus")
+    ("Swahili" . "sw")
+    ("Swedish" . "sv")
+    ("Swiss German" . "gsw")
+    ("Tagalog" . "tl")
+    ("Tahitian" . "ty")
+    ("Tajik" . "tg")
+    ("Tamil" . "ta")
+    ("Tatar" . "tt")
+    ("Telugu" . "te")
+    ("Tetum" . "tet")
+    ("Thai" . "th")
+    ("Tibetan" . "bo")
+    ("Tigrinya" . "ti")
+    ("Timne" . "tem")
+    ("Tiv" . "tiv")
+    ("Tonga" . "to")
+    ("Tsonga" . "ts")
+    ("Tumbuka" . "tum")
+    ("Turkish" . "tr")
+    ("Turkmen" . "tk")
+    ("Twi" . "tw")
+    ("Tyap" . "kcg")
+    ("Uighur" . "ug")
+    ("Ukrainian" . "uk")
+    ("Umbundu" . "umb")
+    ("Urdu" . "ur")
+    ("Uzbek" . "uz")
+    ("Venda" . "ve")
+    ("Vietnamese" . "vi")
+    ("Volapuk" . "vo")
+    ("Walloon" . "wa")
+    ("Walamo" . "wal")
+    ("Waray" . "war")
+    ("Welsh" . "cy")
+    ("Western Frisian" . "fy")
+    ("Wolof" . "wo")
+    ("Xhosa" . "xh")
+    ("Yao" . "yao")
+    ("Yiddish" . "yi")
+    ("Yoruba" . "yo")
+    ("Zapotec" . "zap")
+    ("Zhuang" . "za")
+    ("Zulu" . "zu")
+    )
+  "*Association list giving team codes from team names.
+This is used for generating a submission file name for the 'M' command.
+If a string instead of an alist, it is a team code to use unconditionnally."
+  :type 'sexp
+  :group 'po)
+
+(defcustom po-gzip-uuencode-command "gzip -9 | uuencode -m"
+  "*The filter to use for preparing a mail invoice of the PO file.
+Normally \"gzip -9 | uuencode -m\", remove the -9 for lesser compression,
+or remove the -m if you are not using the GNU version of 'uuencode'."
+  :type 'string
+  :group 'po)
+
+(defvar po-subedit-mode-syntax-table
+  (copy-syntax-table text-mode-syntax-table)
+  "Syntax table used while in PO mode.")
+\f
+;;; Emacs portability matters - part II.
+
+;;; Many portability matters are addressed in this page.  The few remaining
+;;; cases, elsewhere, all involve  'eval-and-compile', 'boundp' or 'fboundp'.
+
+;; Protect string comparisons from text properties if possible.
+(eval-and-compile
+  (fset 'po-buffer-substring
+        (symbol-function (if (fboundp 'buffer-substring-no-properties)
+                             'buffer-substring-no-properties
+                           'buffer-substring)))
+
+  (if (fboundp 'match-string-no-properties)
+      (fset 'po-match-string (symbol-function 'match-string-no-properties))
+    (defun po-match-string (number)
+      "Return string of text matched by last search."
+      (po-buffer-substring (match-beginning number) (match-end number)))))
+
+;; Handle missing 'with-temp-buffer' function.
+(eval-and-compile
+  (if (fboundp 'with-temp-buffer)
+      (fset 'po-with-temp-buffer (symbol-function 'with-temp-buffer))
+
+    (defmacro po-with-temp-buffer (&rest forms)
+      "Create a temporary buffer, and evaluate FORMS there like 'progn'."
+      (let ((curr-buffer (make-symbol "curr-buffer"))
+            (temp-buffer (make-symbol "temp-buffer")))
+        `(let ((,curr-buffer (current-buffer))
+               (,temp-buffer (get-buffer-create
+                              (generate-new-buffer-name " *po-temp*"))))
+           (unwind-protect
+               (progn
+                 (set-buffer ,temp-buffer)
+                 ,@forms)
+             (set-buffer ,curr-buffer)
+             (and (buffer-name ,temp-buffer)
+                  (kill-buffer ,temp-buffer))))))))
+
+;; Handle missing 'kill-new' function.
+(eval-and-compile
+  (if (fboundp 'kill-new)
+      (fset 'po-kill-new (symbol-function 'kill-new))
+
+    (defun po-kill-new (string)
+      "Push STRING onto the kill ring, for Emacs 18 where kill-new is missing."
+      (po-with-temp-buffer
+        (insert string)
+        (kill-region (point-min) (point-max))))))
+
+;; Handle missing 'read-event' function.
+(eval-and-compile
+  (fset 'po-read-event
+        (cond ((fboundp 'read-event)
+               ;; GNU Emacs.
+               'read-event)
+              (t
+               ;; Older Emacses.
+               'read-char))))
+
+;; Handle missing 'force-mode-line-update' function.
+(eval-and-compile
+  (if (fboundp 'force-mode-line-update)
+      (fset 'po-force-mode-line-update
+            (symbol-function 'force-mode-line-update))
+
+    (defun po-force-mode-line-update ()
+      "Force the mode-line of the current buffer to be redisplayed."
+      (set-buffer-modified-p (buffer-modified-p)))))
+
+;; Handle portable highlighting.  Code has been adapted (OK... stolen! :-)
+;; from 'ispell.el'.
+
+(defun po-create-overlay ()
+  "Create and return a deleted overlay structure.
+The variable 'po-highlight-face' selects the face to use for highlighting."
+  (let ((overlay (make-overlay (point) (point))))
+    (overlay-put overlay 'face po-highlight-face)
+    ;; The fun thing is that a deleted overlay retains its face, and is
+    ;; movable.
+    (delete-overlay overlay)
+    overlay))
+
+(defun po-highlight (overlay start end &optional buffer)
+  "Use OVERLAY to highlight the string from START to END.
+If limits are not relative to the current buffer, use optional BUFFER."
+  (move-overlay overlay start end (or buffer (current-buffer))))
+
+(defun po-dehighlight (overlay)
+  "Display normally the last string which OVERLAY highlighted.
+The current buffer should be in PO mode, when this function is called."
+  (delete-overlay overlay))
+
+;;; Buffer local variables.
+
+;; The following block of declarations has the main purpose of avoiding
+;; byte compiler warnings.  It also introduces some documentation for
+;; each of these variables, all meant to be local to PO mode buffers.
+
+;; Flag telling that MODE-LINE-STRING should be displayed.  See 'Window'
+;; page below.  Exceptionally, this variable is local to *all* buffers.
+(defvar po-mode-flag)
+
+;; PO buffers are kept read-only to prevent random modifications.  READ-ONLY
+;; holds the value of the read-only flag before PO mode was entered.
+(defvar po-read-only)
+
+;; The current entry extends from START-OF-ENTRY to END-OF-ENTRY, it
+;; includes preceding whitespace and excludes following whitespace.  The
+;; start of keyword lines are START-OF-MSGID and START-OF-MSGSTR.
+;; ENTRY-TYPE classifies the entry.
+(defvar po-start-of-entry)
+(defvar po-start-of-msgctxt) ; = po-start-of-msgid if there is no msgctxt
+(defvar po-start-of-msgid)
+(defvar po-start-of-msgid_plural) ; = nil if there is no msgid_plural
+(defvar po-start-of-msgstr-block)
+(defvar po-start-of-msgstr-form)
+(defvar po-end-of-msgstr-form)
+(defvar po-end-of-entry)
+(defvar po-entry-type)
+
+;; A few counters are usefully shown in the Emacs mode line.
+(defvar po-translated-counter)
+(defvar po-fuzzy-counter)
+(defvar po-untranslated-counter)
+(defvar po-obsolete-counter)
+(defvar po-mode-line-string)
+
+;; PO mode keeps track of fields being edited, for one given field should
+;; have one editing buffer at most, and for exiting a PO buffer properly
+;; should offer to close all pending edits.  Variable EDITED-FIELDS holds an
+;; an list of "slots" of the form: (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
+;; To allow simultaneous edition of the comment and the msgstr of an entry,
+;; ENTRY-MARKER points to the msgid line if a comment is being edited, or to
+;; the msgstr line if the msgstr is being edited.  EDIT-BUFFER is the
+;; temporary Emacs buffer used to edit the string.  OVERLAY-INFO, when not
+;; nil, holds an overlay (or if overlays are not supported, a cons of two
+;; markers) for this msgid string which became highlighted for the edit.
+(defvar po-edited-fields)
+
+;; We maintain a set of movable pointers for returning to entries.
+(defvar po-marker-stack)
+
+;; SEARCH path contains a list of directories where files may be found,
+;; in a format suitable for read completion.  Each directory includes
+;; its trailing slash.  PO mode starts with "./" and "../".
+(defvar po-search-path)
+
+;; The following variables are meaningful only when REFERENCE-CHECK
+;; is identical to START-OF-ENTRY, else they should be recomputed.
+;; REFERENCE-ALIST contains all known references for the current
+;; entry, each list element is (PROMPT FILE LINE), where PROMPT may
+;; be used for completing read, FILE is a string and LINE is a number.
+;; REFERENCE-CURSOR is a cycling cursor into REFERENCE-ALIST.
+(defvar po-reference-alist)
+(defvar po-reference-cursor)
+(defvar po-reference-check)
+
+;; The following variables are for marking translatable strings in program
+;; sources.  KEYWORDS is the list of keywords for marking translatable
+;; strings, kept in a format suitable for reading with completion.
+;; STRING-CONTENTS holds the value of the most recent string found in sources,
+;; and when it is not nil, then STRING-BUFFER, STRING-START and STRING-END
+;; describe where it is.  MARKING-OVERLAY, if not 'nil', holds the overlay
+;; which highlight the last found string; for older Emacses, it holds the cons
+;; of two markers around the highlighted region.
+(defvar po-keywords)
+(defvar po-string-contents)
+(defvar po-string-buffer)
+(defvar po-string-start)
+(defvar po-string-end)
+(defvar po-marking-overlay)
+\f
+;;; PO mode variables and constants (usually not to customize).
+
+;; The textdomain should really be "gettext", only trying it for now.
+;; All this requires more thinking, we cannot just do this like that.
+(set-translation-domain "po-mode")
+
+(defun po-mode-version ()
+  "Show Emacs PO mode version."
+  (interactive)
+  (message (_"Emacs PO mode, version %s") po-mode-version-string))
+
+(defconst po-help-display-string
+  (_"\
+PO Mode Summary           Next Previous            Miscellaneous
+*: Later, /: Docum        n    p    Any type       .     Redisplay
+                          t    T    Translated     /v    Version info
+Moving around             f    F    Fuzzy          ?, h  This help
+<    First if any         o    O    Obsolete       =     Current index
+>    Last if any          u    U    Untranslated   0     Other window
+/SPC Auto select                                   V     Validate
+                        Msgstr Comments            M     Mail officially
+Modifying entries         RET  #    Call editor    _     Undo
+TAB   Remove fuzzy mark   k    K    Kill to        E     Edit out full
+DEL   Fuzzy or fade out   w    W    Copy to        Q     Forceful quit
+LFD   Init with msgid     y    Y    Yank from      q     Confirm and quit
+
+gettext Keyword Marking                            Position Stack
+,    Find next string     Compendiums              m  Mark and push current
+M-,  Mark translatable    *c    To compendium      r  Pop and return
+M-.  Change mark, mark    *M-C  Select, save       x  Exchange current/top
+
+Program Sources           Auxiliary Files          Lexicography
+s    Cycle reference      a    Cycle file          *l    Lookup translation
+M-s  Select reference     C-c C-a  Select file     *M-l  Add/edit translation
+S    Consider path        A    Consider PO file    *L    Consider lexicon
+M-S  Ignore path          M-A  Ignore PO file      *M-L  Ignore lexicon
+")
+  "Help page for PO mode.")
+
+(defconst po-mode-menu-layout
+  `("PO"
+    ("Moving around"
+     ["Auto select" po-auto-select-entry
+      :help "Jump to next interesting entry"]
+     "---"
+     ;; Forward
+     ["Any next" po-next-entry
+      :help "Jump to next entry"]
+     ["Next translated" po-next-translated-entry
+      :help "Jump to next translated entry"]
+     ["Next fuzzy" po-next-fuzzy-entry
+      :help "Jump to next fuzzy entry"]
+     ["Next obsolete" po-next-obsolete-entry
+      :help "Jump to next obsolete entry"]
+     ["Next untranslated" po-next-untranslated-entry
+      :help "Jump to next untranslated entry"]
+     ["Last file entry" po-last-entry
+      :help "Jump to last entry"]
+     "---"
+     ;; Backward
+     ["Any previous" po-previous-entry
+      :help "Jump to previous entry"]
+     ["Previous translated" po-previous-translated-entry
+      :help "Jump to previous translated entry"]
+     ["Previous fuzzy" po-previous-fuzzy-entry
+      :help "Jump to previous fuzzy entry"]
+     ["Previous obsolete" po-previous-obsolete-entry
+      :help "Jump to previous obsolete entry"]
+     ["Previous untranslated" po-previous-untranslated-entry
+      :help "Jump to previous untranslated entry"]
+     ["First file entry" po-first-entry
+      :help "Jump to first entry"]
+     "---"
+     ;; "Position stack"
+     ["Mark and push current" po-push-location
+      :help "Remember current location"]
+     ["Pop and return" po-pop-location
+      :help "Jump to last remembered location and forget about it"]
+     ["Exchange current/top" po-exchange-location
+      :help "Jump to last remembered location and remember current location"]
+     "---"
+     ["Redisplay" po-current-entry
+      :help "Make current entry properly visible"]
+     ["Current index" po-statistics
+      :help "Statistical info on current translation file"])
+    ("Modifying entries"
+     ["Undo" po-undo
+      :help "Revoke last changed entry"]
+     "---"
+     ;; "Msgstr"
+     ["Edit msgstr" po-edit-msgstr
+      :help "Edit current translation"]
+     ["Ediff and merge msgstr" po-edit-msgstr-and-ediff
+      :help "Call `ediff' on current translation for merging"]
+     ["Cut msgstr" po-kill-msgstr
+      :help "Cut (kill) current translation"]
+     ["Copy msgstr" po-kill-ring-save-msgstr
+      :help "Copy current translation"]
+     ["Paste msgstr" po-yank-msgstr
+      :help "Paste (yank) text most recently cut/copied translation"]
+     "---"
+     ;; "Comments"
+     ["Edit comment" po-edit-comment
+      :help "Edit current comment"]
+     ["Ediff and merge comment" po-edit-comment-and-ediff
+      :help "Call `ediff' on current comment for merging"]
+     ["Cut comment" po-kill-comment
+      :help "Cut (kill) current comment"]
+     ["Copy comment" po-kill-ring-save-comment
+      :help "Copy current translation"]
+     ["Paste comment" po-yank-comment
+      :help "Paste (yank) text most recently cut/copied"]
+     "---"
+     ["Remove fuzzy mark" po-unfuzzy
+      :help "Remove \"#, fuzzy\""]
+     ["Fuzzy or fade out" po-fade-out-entry
+      :help "Set current entry fuzzy, or if already fuzzy delete it"]
+     ["Init with msgid" po-msgid-to-msgstr
+      :help "Initialize or replace current translation with the original message"])
+    ("Other files"
+     ["Other window" po-other-window
+      :help "Select other window; if necessay split current frame"]
+     "---"
+     ;; "Program sources"
+     ["Cycle reference in source file" po-cycle-source-reference t]
+     ["Select reference" po-select-source-reference t]
+     ["Consider path" po-consider-source-path t]
+     ["Ignore path" po-ignore-source-path t]
+     ;; "---"
+     ;; ;; "Compendiums"
+     ;; ["To add entry to compendium" po-save-entry nil]
+     ;; ["Select from compendium, save" po-select-and-save-entry nil]
+     "---"
+     ;; "Auxiliary files"
+     ["Cycle through auxilicary file" po-cycle-auxiliary t]
+     ["Select auxilicary file" po-select-auxiliary t]
+     ["Consider as auxilicary file" po-consider-as-auxiliary t]
+     ["Ignore as auxilicary file" po-ignore-as-auxiliary t]
+     ;; "---"
+     ;; ;; "Lexicography"
+     ;; ["Lookup translation" po-lookup-lexicons nil]
+     ;; ["Add/edit translation" po-edit-lexicon-entry nil]
+     ;; ["Consider lexicon" po-consider-lexicon-file nil]
+     ;; ["Ignore lexicon" po-ignore-lexicon-file nil])
+     "---"
+     "Source marking"
+     ["Find first string" (po-tags-search '(nil)) t]
+     ["Prefer keyword" (po-select-mark-and-mark '(nil)) t]
+     ["Find next string" po-tags-search t]
+     ["Mark preferred" po-mark-translatable t]
+     ["Mark with keyword" po-select-mark-and-mark t])
+     "---"
+     ["Version info" po-mode-version
+      :help "Display version number of PO mode"]
+     ["Help page" po-help
+      :help "Show the PO mode help screen"]
+     ["Validate" po-validate
+      :help "Check validity of current translation file using `msgfmt'"]
+     ["Mail officially" po-send-mail
+      :help "Send current translation file to the Translation Robot by mail"]
+     ["Edit out full" po-edit-out-full
+      :help "Leave PO mode to edit translation file using fundamental mode"]
+     "---"
+     ["Forceful quit" po-quit
+      :help "Close (kill) current translation file without saving"]
+     ["Soft quit" po-confirm-and-quit
+      :help "Save current translation file, than close (kill) it"]))
+
+
+(defconst po-subedit-mode-menu-layout
+  `("PO-Edit"
+    ["Ediff and merge translation variants" po-subedit-ediff
+      :help "Call `ediff' for merging variants"]
+    ["Cycle through auxiliary files" po-subedit-cycle-auxiliary t]
+    "---"
+    ["Abort edit" po-subedit-abort
+     :help "Don't change the translation"]
+    ["Exit edit" po-subedit-exit
+     :help "Use this text as the translation and close current edit buffer"]))
+
+(defconst po-subedit-message
+  (_"Type 'C-c C-c' once done, or 'C-c C-k' to abort edit")
+  "Message to post in the minibuffer when an edit buffer is displayed.")
+
+(defvar po-auxiliary-list nil
+  "List of auxiliary PO files, in completing read format.")
+
+(defvar po-auxiliary-cursor nil
+  "Cursor into the 'po-auxiliary-list'.")
+
+(defvar po-compose-mail-function
+  (let ((functions '(compose-mail-other-window
+                     message-mail-other-window
+                     compose-mail
+                     message-mail))
+        result)
+    (while (and (not result) functions)
+      (if (fboundp (car functions))
+          (setq result (car functions))
+        (setq functions (cdr functions))))
+    (cond (result)
+          ((fboundp 'mail-other-window)
+           (function (lambda (to subject)
+                       (mail-other-window nil to subject))))
+          ((fboundp 'mail)
+           (function (lambda (to subject)
+                       (mail nil to subject))))
+          (t (function (lambda (to subject)
+                         (error (_"I do not know how to mail to '%s'") to))))))
+  "Function to start composing an electronic message.")
+
+(defvar po-any-previous-msgctxt-regexp
+  "^#\\(~\\)?|[ \t]*msgctxt.*\n\\(#\\(~\\)?|[ \t]*\".*\n\\)*"
+  "Regexp matching a whole #| msgctxt field, whether obsolete or not.")
+
+(defvar po-any-previous-msgid-regexp
+  "^#\\(~\\)?|[ \t]*msgid.*\n\\(#\\(~\\)?|[ \t]*\".*\n\\)*"
+  "Regexp matching a whole #| msgid field, whether obsolete or not.")
+
+(defvar po-any-previous-msgid_plural-regexp
+  "^#\\(~\\)?|[ \t]*msgid_plural.*\n\\(#\\(~\\)?|[ \t]*\".*\n\\)*"
+  "Regexp matching a whole #| msgid_plural field, whether obsolete or not.")
+
+(defvar po-any-msgctxt-msgid-regexp
+  "^\\(#~[ \t]*\\)?msg\\(ctxt\\|id\\).*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
+  "Regexp matching a whole msgctxt or msgid field, whether obsolete or not.")
+
+(defvar po-any-msgid-regexp
+  "^\\(#~[ \t]*\\)?msgid.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
+  "Regexp matching a whole msgid field, whether obsolete or not.")
+
+(defvar po-any-msgid_plural-regexp
+  "^\\(#~[ \t]*\\)?msgid_plural.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
+  "Regexp matching a whole msgid_plural field, whether obsolete or not.")
+
+(defvar po-any-msgstr-block-regexp
+  "^\\(#~[ \t]*\\)?msgstr\\([ \t]\\|\\[0\\]\\).*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*\\(\\(#~[ \t]*\\)?msgstr\\[[0-9]\\].*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*\\)*"
+  "Regexp matching a whole msgstr or msgstr[] field, whether obsolete or not.")
+
+(defvar po-any-msgstr-form-regexp
+  ;; "^\\(#~[ \t]*\\)?msgstr.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
+  "^\\(#~[ \t]*\\)?msgstr\\(\\[[0-9]\\]\\)?.*\n\\(\\(#~[ \t]*\\)?\".*\n\\)*"
+  "Regexp matching just one msgstr or msgstr[] field, whether obsolete or not.")
+
+(defvar po-msgstr-idx-keyword-regexp
+  "^\\(#~[ \t]*\\)?msgstr\\[[0-9]\\]"
+  "Regexp matching an indexed msgstr keyword, whether obsolete or not.")
+
+(defvar po-msgfmt-program "msgfmt"
+  "Path to msgfmt program from GNU gettext package.")
+
+;; Font lock based highlighting code.
+(defconst po-font-lock-keywords
+  '(
+    ("^# .*\\|^#[:,]?" . font-lock-comment-face)
+    ("^#:\\(.*\\)" 1 font-lock-reference-face)
+    ("^#,\\(.*\\)" 1 font-lock-function-name-face)
+    ("^\\(\\(msg\\(ctxt\\|id\\(_plural\\)?\\|str\\(\\[[0-9]\\]\\)?\\)\\) \\)?\"\\|\"$"
+     . font-lock-keyword-face)
+    ("\\\\.\\|%[*$-.0-9hjltuzL]*[a-zA-Z]" . font-lock-variable-name-face)
+    )
+  "Additional expressions to highlight in PO mode.")
+
+;; Old activator for 'font lock'.  Is it still useful?  I don't think so.
+;;(if (boundp 'font-lock-keywords)
+;;    (put 'po-mode 'font-lock-keywords 'po-font-lock-keywords))
+
+;; 'hilit19' based highlighting code has been disabled, as most probably
+;; nobody really needs it (it also generates ugly byte-compiler warnings).
+;;
+;;(if (fboundp 'hilit-set-mode-patterns)
+;;    (hilit-set-mode-patterns 'po-mode
+;;                             '(("^# .*\\|^#$" nil comment)
+;;                               ("^#[.,:].*" nil include)
+;;                               ("^\\(msgid\\|msgstr\\) *\"" nil keyword)
+;;                               ("^\"\\|\"$" nil keyword))))
+\f
+;;; Mode activation.
+
+;; Emacs 21.2 comes with po-find-file-coding-system. We give preference
+;; to the version shipped with Emacs.
+(if (not (fboundp 'po-find-file-coding-system))
+  (require 'po-compat))
+
+(defvar po-mode-abbrev-table nil
+  "Abbrev table used while in PO mode.")
+(define-abbrev-table 'po-mode-abbrev-table ())
+
+(defvar po-mode-map
+  ;; Use (make-keymap) because (make-sparse-keymap) does not work on Demacs.
+  (let ((po-mode-map (make-keymap)))
+    (suppress-keymap po-mode-map)
+    (define-key po-mode-map "\C-i" 'po-unfuzzy)
+    (define-key po-mode-map "\C-j" 'po-msgid-to-msgstr)
+    (define-key po-mode-map "\C-m" 'po-edit-msgstr)
+    (define-key po-mode-map " " 'po-auto-select-entry)
+    (define-key po-mode-map "?" 'po-help)
+    (define-key po-mode-map "#" 'po-edit-comment)
+    (define-key po-mode-map "," 'po-tags-search)
+    (define-key po-mode-map "." 'po-current-entry)
+    (define-key po-mode-map "<" 'po-first-entry)
+    (define-key po-mode-map "=" 'po-statistics)
+    (define-key po-mode-map ">" 'po-last-entry)
+    (define-key po-mode-map "a" 'po-cycle-auxiliary)
+;;;;  (define-key po-mode-map "c" 'po-save-entry)
+    (define-key po-mode-map "f" 'po-next-fuzzy-entry)
+    (define-key po-mode-map "h" 'po-help)
+    (define-key po-mode-map "k" 'po-kill-msgstr)
+;;;;  (define-key po-mode-map "l" 'po-lookup-lexicons)
+    (define-key po-mode-map "m" 'po-push-location)
+    (define-key po-mode-map "n" 'po-next-entry)
+    (define-key po-mode-map "o" 'po-next-obsolete-entry)
+    (define-key po-mode-map "p" 'po-previous-entry)
+    (define-key po-mode-map "q" 'po-confirm-and-quit)
+    (define-key po-mode-map "r" 'po-pop-location)
+    (define-key po-mode-map "s" 'po-cycle-source-reference)
+    (define-key po-mode-map "t" 'po-next-translated-entry)
+    (define-key po-mode-map "u" 'po-next-untranslated-entry)
+    (define-key po-mode-map "v" 'po-mode-version)
+    (define-key po-mode-map "w" 'po-kill-ring-save-msgstr)
+    (define-key po-mode-map "x" 'po-exchange-location)
+    (define-key po-mode-map "y" 'po-yank-msgstr)
+    (define-key po-mode-map "A" 'po-consider-as-auxiliary)
+    (define-key po-mode-map "E" 'po-edit-out-full)
+    (define-key po-mode-map "F" 'po-previous-fuzzy-entry)
+    (define-key po-mode-map "K" 'po-kill-comment)
+;;;;  (define-key po-mode-map "L" 'po-consider-lexicon-file)
+    (define-key po-mode-map "M" 'po-send-mail)
+    (define-key po-mode-map "O" 'po-previous-obsolete-entry)
+    (define-key po-mode-map "T" 'po-previous-translated-entry)
+    (define-key po-mode-map "U" 'po-previous-untranslated-entry)
+    (define-key po-mode-map "Q" 'po-quit)
+    (define-key po-mode-map "S" 'po-consider-source-path)
+    (define-key po-mode-map "V" 'po-validate)
+    (define-key po-mode-map "W" 'po-kill-ring-save-comment)
+    (define-key po-mode-map "Y" 'po-yank-comment)
+    (define-key po-mode-map "_" 'po-undo)
+    (define-key po-mode-map "\C-_" 'po-undo)
+    (define-key po-mode-map "\C-xu" 'po-undo)
+    (define-key po-mode-map "0" 'po-other-window)
+    (define-key po-mode-map "\177" 'po-fade-out-entry)
+    (define-key po-mode-map "\C-c\C-a" 'po-select-auxiliary)
+    (define-key po-mode-map "\C-c\C-e" 'po-edit-msgstr-and-ediff)
+    (define-key po-mode-map [?\C-c?\C-#] 'po-edit-comment-and-ediff)
+    (define-key po-mode-map "\C-c\C-C" 'po-edit-comment-and-ediff)
+    (define-key po-mode-map "\M-," 'po-mark-translatable)
+    (define-key po-mode-map "\M-." 'po-select-mark-and-mark)
+;;;;  (define-key po-mode-map "\M-c" 'po-select-and-save-entry)
+;;;;  (define-key po-mode-map "\M-l" 'po-edit-lexicon-entry)
+    (define-key po-mode-map "\M-s" 'po-select-source-reference)
+    (define-key po-mode-map "\M-A" 'po-ignore-as-auxiliary)
+;;;;  (define-key po-mode-map "\M-L" 'po-ignore-lexicon-file)
+    (define-key po-mode-map "\M-S" 'po-ignore-source-path)
+    po-mode-map)
+  "Keymap for PO mode.")
+
+;;;###autoload
+(defun po-mode ()
+  "Major mode for translators when they edit PO files.
+
+Special commands:
+\\{po-mode-map}
+Turning on PO mode calls the value of the variable 'po-mode-hook',
+if that value is non-nil.  Behaviour may be adjusted through some variables,
+all reachable through 'M-x customize', in group 'Emacs.Editing.I18n.Po'."
+  (interactive)
+  (kill-all-local-variables)
+  (setq major-mode 'po-mode
+        mode-name "PO")
+  (use-local-map po-mode-map)
+  (if (fboundp 'easy-menu-define)
+      (easy-menu-define po-mode-menu po-mode-map "" po-mode-menu-layout))
+  (set (make-local-variable 'font-lock-defaults) '(po-font-lock-keywords t))
+
+  (set (make-local-variable 'po-read-only) buffer-read-only)
+  (setq buffer-read-only t)
+
+  (make-local-variable 'po-start-of-entry)
+  (make-local-variable 'po-start-of-msgctxt)
+  (make-local-variable 'po-start-of-msgid)
+  (make-local-variable 'po-start-of-msgid_plural)
+  (make-local-variable 'po-start-of-msgstr-block)
+  (make-local-variable 'po-end-of-entry)
+  (make-local-variable 'po-entry-type)
+
+  (make-local-variable 'po-translated-counter)
+  (make-local-variable 'po-fuzzy-counter)
+  (make-local-variable 'po-untranslated-counter)
+  (make-local-variable 'po-obsolete-counter)
+  (make-local-variable 'po-mode-line-string)
+
+  (setq po-mode-flag t)
+
+  (po-check-file-header)
+  (po-compute-counters nil)
+
+  (set (make-local-variable 'po-edited-fields) nil)
+  (set (make-local-variable 'po-marker-stack) nil)
+  (set (make-local-variable 'po-search-path) '(("./") ("../")))
+
+  (set (make-local-variable 'po-reference-alist) nil)
+  (set (make-local-variable 'po-reference-cursor) nil)
+  (set (make-local-variable 'po-reference-check) 0)
+
+  (set (make-local-variable 'po-keywords)
+       '(("gettext") ("gettext_noop") ("_") ("N_")))
+  (set (make-local-variable 'po-string-contents) nil)
+  (set (make-local-variable 'po-string-buffer) nil)
+  (set (make-local-variable 'po-string-start) nil)
+  (set (make-local-variable 'po-string-end) nil)
+  (set (make-local-variable 'po-marking-overlay) (po-create-overlay))
+
+  (add-hook 'write-contents-hooks 'po-replace-revision-date)
+
+  (run-hooks 'po-mode-hook)
+  (message (_"You may type 'h' or '?' for a short PO mode reminder.")))
+
+(defvar po-subedit-mode-map
+  ;; Use (make-keymap) because (make-sparse-keymap) does not work on Demacs.
+  (let ((po-subedit-mode-map (make-keymap)))
+    (define-key po-subedit-mode-map "\C-c\C-a" 'po-subedit-cycle-auxiliary)
+    (define-key po-subedit-mode-map "\C-c\C-c" 'po-subedit-exit)
+    (define-key po-subedit-mode-map "\C-c\C-e" 'po-subedit-ediff)
+    (define-key po-subedit-mode-map "\C-c\C-k" 'po-subedit-abort)
+    po-subedit-mode-map)
+  "Keymap while editing a PO mode entry (or the full PO file).")
+\f
+;;; Window management.
+
+(make-variable-buffer-local 'po-mode-flag)
+
+(defvar po-mode-line-entry '(po-mode-flag ("  " po-mode-line-string))
+  "Mode line format entry displaying MODE-LINE-STRING.")
+
+;; Insert MODE-LINE-ENTRY in mode line, but on first load only.
+(or (member po-mode-line-entry mode-line-format)
+    ;; mode-line-format usually contains global-mode-string, but some
+    ;; people customize this variable. As a last resort, append at the end.
+    (let ((prev-entry (or (member 'global-mode-string mode-line-format)
+                          (member "   " mode-line-format)
+                          (last mode-line-format))))
+      (setcdr prev-entry (cons po-mode-line-entry (cdr prev-entry)))))
+
+(defun po-update-mode-line-string ()
+  "Compute a new statistics string to display in mode line."
+  (setq po-mode-line-string
+        (concat (format "%dt" po-translated-counter)
+                (if (> po-fuzzy-counter 0)
+                    (format "+%df" po-fuzzy-counter))
+                (if (> po-untranslated-counter 0)
+                    (format "+%du" po-untranslated-counter))
+                (if (> po-obsolete-counter 0)
+                    (format "+%do" po-obsolete-counter))))
+  (po-force-mode-line-update))
+
+(defun po-type-counter ()
+  "Return the symbol name of the counter appropriate for the current entry."
+  (cond ((eq po-entry-type 'obsolete) 'po-obsolete-counter)
+        ((eq po-entry-type 'fuzzy) 'po-fuzzy-counter)
+        ((eq po-entry-type 'translated) 'po-translated-counter)
+        ((eq po-entry-type 'untranslated) 'po-untranslated-counter)
+        (t (error (_"Unknown entry type")))))
+
+(defun po-decrease-type-counter ()
+  "Decrease the counter corresponding to the nature of the current entry."
+  (let ((counter (po-type-counter)))
+    (set counter (1- (eval counter)))))
+
+(defun po-increase-type-counter ()
+  "Increase the counter corresponding to the nature of the current entry.
+Then, update the mode line counters."
+  (let ((counter (po-type-counter)))
+    (set counter (1+ (eval counter))))
+  (po-update-mode-line-string))
+
+;; Avoid byte compiler warnings.
+(defvar po-fuzzy-regexp)
+(defvar po-untranslated-regexp)
+
+(defun po-compute-counters (flag)
+  "Prepare counters for mode line display.  If FLAG, also echo entry position."
+  (and flag (po-find-span-of-entry))
+  (setq po-translated-counter 0
+        po-fuzzy-counter 0
+        po-untranslated-counter 0
+        po-obsolete-counter 0)
+  (let ((position 0) (total 0) current here)
+    ;; FIXME 'here' looks obsolete / 2001-08-23 03:54:26 CEST -ke-
+    (save-excursion
+      (po-find-span-of-entry)
+      (setq current po-start-of-msgstr-block)
+      (goto-char (point-min))
+      ;; While counting, skip the header entry, for consistency with msgfmt.
+      (po-find-span-of-entry)
+      (if (string-equal (po-get-msgid) "")
+          (goto-char po-end-of-entry))
+      (if (re-search-forward "^msgid" (point-max) t)
+          (progn
+            ;; Start counting
+            (while (re-search-forward po-any-msgstr-block-regexp nil t)
+              (and (= (% total 20) 0)
+                   (if flag
+                       (message (_"Position %d/%d") position total)
+                     (message (_"Position %d") total)))
+              (setq here (point))
+              (goto-char (match-beginning 0))
+              (setq total (1+ total))
+              (and flag (eq (point) current) (setq position total))
+              (cond ((eq (following-char) ?#)
+                     (setq po-obsolete-counter (1+ po-obsolete-counter)))
+                    ((looking-at po-untranslated-regexp)
+                     (setq po-untranslated-counter (1+ po-untranslated-counter)))
+                    (t (setq po-translated-counter (1+ po-translated-counter))))
+              (goto-char here))
+
+            ;; Make another pass just for the fuzzy entries, kind of kludgey.
+            ;; FIXME: Counts will be wrong if untranslated entries are fuzzy, yet
+            ;; this should not normally happen.
+            (goto-char (point-min))
+            (while (re-search-forward po-fuzzy-regexp nil t)
+              (setq po-fuzzy-counter (1+ po-fuzzy-counter)))
+            (setq po-translated-counter (- po-translated-counter po-fuzzy-counter)))
+        '()))
+
+    ;; Push the results out.
+    (if flag
+        (message (_"\
+Position %d/%d; %d translated, %d fuzzy, %d untranslated, %d obsolete")
+                 position total po-translated-counter po-fuzzy-counter
+                 po-untranslated-counter po-obsolete-counter)
+      (message "")))
+  (po-update-mode-line-string))
+
+(defun po-redisplay ()
+  "Redisplay the current entry."
+  ;; FIXME: Should try to fit the whole entry on the window.  If this is not
+  ;; possible, should try to fit the comment and the msgid.  Otherwise,
+  ;; should try to fit the msgid.  Else, the first line of the msgid should
+  ;; be at the top of the window.
+  (goto-char po-start-of-msgid))
+
+(defun po-other-window ()
+  "Get the cursor into another window, out of PO mode."
+  (interactive)
+  (if (one-window-p t)
+      (progn
+        (split-window)
+        (switch-to-buffer (other-buffer)))
+    (other-window 1)))
+\f
+;;; Processing the PO file header entry.
+
+(defun po-check-file-header ()
+  "Create a missing PO mode file header, or replace an oldish one.
+Can be customized with the `po-auto-update-file-header' variable."
+  (if (or (eq po-auto-update-file-header t)
+          (and (eq po-auto-update-file-header 'ask)
+               (y-or-n-p (_"May I update the PO Header Entry? "))))
+      (save-excursion
+        (save-restriction
+          (widen) ; in case of a narrowed view to the buffer
+          (let ((buffer-read-only po-read-only)
+                insert-flag end-of-header)
+            (goto-char (point-min))
+            (if (re-search-forward po-any-msgstr-block-regexp nil t)
+                (progn
+                  ;; There is at least one entry.
+                  (goto-char (match-beginning 0))
+                  (forward-line -1)
+                  (setq end-of-header (match-end 0))
+                  (if (looking-at "msgid \"\"\n")
+                      ;; There is indeed a PO file header.
+                      (if (re-search-forward "\n\"PO-Revision-Date: "
+                                             end-of-header t)
+                          nil
+                        ;; This is an oldish header.  Replace it all.
+                        (goto-char end-of-header)
+                        (while (> (point) (point-min))
+                          (forward-line -1)
+                          (insert "#~ ")
+                          (beginning-of-line))
+                        (beginning-of-line)
+                        (setq insert-flag t))
+                    ;; The first entry is not a PO file header, insert one.
+                    (setq insert-flag t)))
+              ;; Not a single entry found.
+              (setq insert-flag t))
+            (goto-char (point-min))
+            (if insert-flag
+                (progn
+                  (insert po-default-file-header)
+                  (if (not (eobp))
+                      (insert "\n")))))))
+    (message (_"PO Header Entry was not updated..."))))
+
+(defun po-replace-revision-date ()
+  "Replace the revision date by current time in the PO file header."
+  (if (fboundp 'format-time-string)
+      (if (or (eq po-auto-replace-revision-date t)
+              (and (eq po-auto-replace-revision-date 'ask)
+                   (y-or-n-p (_"May I set PO-Revision-Date? "))))
+          (save-excursion
+            (goto-char (point-min))
+            (if (re-search-forward "^\"PO-Revision-Date:.*" nil t)
+                (let* ((buffer-read-only po-read-only)
+                       (time (current-time))
+                       (seconds (or (car (current-time-zone time)) 0))
+                       (minutes (/ (abs seconds) 60))
+                       (zone (format "%c%02d%02d"
+                                     (if (< seconds 0) ?- ?+)
+                                     (/ minutes 60)
+                                     (% minutes 60))))
+                  (replace-match
+                       (concat "\"PO-Revision-Date: "
+                               (format-time-string "%Y-%m-%d %H:%M" time)
+                               zone "\\n\"")
+                       t t))))
+        (message ""))
+    (message (_"PO-Revision-Date should be adjusted...")))
+  ;; Return nil to indicate that the buffer has not yet been saved.
+  nil)
+\f
+;;; Handling span of entry, entry type and entry attributes.
+
+(defun po-find-span-of-entry ()
+  "Find the extent of the PO file entry where the cursor is.
+Set variables po-start-of-entry, po-start-of-msgctxt, po-start-of-msgid,
+po-start-of-msgid_plural, po-start-of-msgstr-block, po-end-of-entry, and
+po-entry-type to meaningful values. po-entry-type may be set to: obsolete,
+fuzzy, untranslated, or translated."
+  (let ((here (point)))
+    (if (re-search-backward po-any-msgstr-block-regexp nil t)
+        (progn
+          ;; After a backward match, (match-end 0) will not extend
+          ;; beyond point, in case point was *inside* the regexp.  We
+          ;; need a dependable (match-end 0), so we redo the match in
+          ;; the forward direction.
+          (re-search-forward po-any-msgstr-block-regexp)
+          (if (<= (match-end 0) here)
+              (progn
+                ;; We most probably found the msgstr of the previous
+                ;; entry.  The current entry then starts just after
+                ;; its end, save this information just in case.
+                (setq po-start-of-entry (match-end 0))
+                ;; However, it is also possible that we are located in
+                ;; the crumb after the last entry in the file.  If
+                ;; yes, we know the middle and end of last PO entry.
+                (setq po-start-of-msgstr-block (match-beginning 0)
+                      po-end-of-entry (match-end 0))
+                (if (re-search-forward po-any-msgstr-block-regexp nil t)
+                    (progn
+                      ;; We definitely were not in the crumb.
+                      (setq po-start-of-msgstr-block (match-beginning 0)
+                            po-end-of-entry (match-end 0)))
+                  ;; We were in the crumb.  The start of the last PO
+                  ;; file entry is the end of the previous msgstr if
+                  ;; any, or else, the beginning of the file.
+                  (goto-char po-start-of-msgstr-block)
+                  (setq po-start-of-entry
+                        (if (re-search-backward po-any-msgstr-block-regexp nil t)
+                            (match-end 0)
+                          (point-min)))))
+            ;; The cursor was inside msgstr of the current entry.
+            (setq po-start-of-msgstr-block (match-beginning 0)
+                  po-end-of-entry (match-end 0))
+            ;; The start of this entry is the end of the previous
+            ;; msgstr if any, or else, the beginning of the file.
+            (goto-char po-start-of-msgstr-block)
+            (setq po-start-of-entry
+                  (if (re-search-backward po-any-msgstr-block-regexp nil t)
+                      (match-end 0)
+                    (point-min)))))
+      ;; The cursor was before msgstr in the first entry in the file.
+      (setq po-start-of-entry (point-min))
+      (goto-char po-start-of-entry)
+      ;; There is at least the PO file header, so this should match.
+      (re-search-forward po-any-msgstr-block-regexp)
+      (setq po-start-of-msgstr-block (match-beginning 0)
+            po-end-of-entry (match-end 0)))
+    ;; Find start of msgid.
+    (goto-char po-start-of-entry)
+    (re-search-forward po-any-msgctxt-msgid-regexp)
+    (setq po-start-of-msgctxt (match-beginning 0))
+    (goto-char po-start-of-entry)
+    (re-search-forward po-any-msgid-regexp)
+    (setq po-start-of-msgid (match-beginning 0))
+    (save-excursion
+      (goto-char po-start-of-msgid)
+      (setq po-start-of-msgid_plural
+            (if (re-search-forward po-any-msgid_plural-regexp
+                                   po-start-of-msgstr-block t)
+                (match-beginning 0)
+              nil)))
+    (save-excursion
+      (when (>= here po-start-of-msgstr-block)
+        ;; point was somewhere inside of msgstr*
+        (goto-char here)
+        (end-of-line)
+        (re-search-backward "^\\(#~[ \t]*\\)?msgstr"))
+      ;; Detect the boundaries of the msgstr we are interested in.
+      (re-search-forward po-any-msgstr-form-regexp)
+      (setq po-start-of-msgstr-form (match-beginning 0)
+            po-end-of-msgstr-form (match-end 0)))
+    ;; Classify the entry.
+    (setq po-entry-type
+          (if (eq (following-char) ?#)
+              'obsolete
+            (goto-char po-start-of-entry)
+            (if (re-search-forward po-fuzzy-regexp po-start-of-msgctxt t)
+                'fuzzy
+              (goto-char po-start-of-msgstr-block)
+              (if (looking-at po-untranslated-regexp)
+                  'untranslated
+                'translated))))
+    ;; Put the cursor back where it was.
+    (goto-char here)))
+
+(defun po-add-attribute (name)
+  "Add attribute NAME to the current entry, unless it is already there."
+  (save-excursion
+    (let ((buffer-read-only po-read-only))
+      (goto-char po-start-of-entry)
+      (if (re-search-forward "\n#, .*" po-start-of-msgctxt t)
+          (save-restriction
+            (narrow-to-region (match-beginning 0) (match-end 0))
+            (goto-char (point-min))
+            (if (re-search-forward (concat "\\b" name "\\b") nil t)
+                nil
+              (goto-char (point-max))
+              (insert ", " name)))
+        (skip-chars-forward "\n")
+        (while (eq (following-char) ?#)
+          (forward-line 1))
+        (insert "#, " name "\n")))))
+
+(defun po-delete-attribute (name)
+  "Delete attribute NAME from the current entry, if any."
+  (save-excursion
+    (let ((buffer-read-only po-read-only))
+      (goto-char po-start-of-entry)
+      (if (re-search-forward "\n#, .*" po-start-of-msgctxt t)
+          (save-restriction
+            (narrow-to-region (match-beginning 0) (match-end 0))
+            (goto-char (point-min))
+            (if (re-search-forward
+                 (concat "\\(\n#, " name "$\\|, " name "$\\| " name ",\\)")
+                 nil t)
+                (replace-match "" t t)))))))
+\f
+;;; Entry positionning.
+
+(defun po-say-location-depth ()
+  "Tell how many entries in the entry location stack."
+  (let ((depth (length po-marker-stack)))
+    (cond ((= depth 0) (message (_"Empty location stack")))
+          ((= depth 1) (message (_"One entry in location stack")))
+          (t (message (_"%d entries in location stack") depth)))))
+
+(defun po-push-location ()
+  "Stack the location of the current entry, for later return."
+  (interactive)
+  (po-find-span-of-entry)
+  (save-excursion
+    (goto-char po-start-of-msgid)
+    (setq po-marker-stack (cons (point-marker) po-marker-stack)))
+  (po-say-location-depth))
+
+(defun po-pop-location ()
+  "Unstack a saved location, and return to the corresponding entry."
+  (interactive)
+  (if po-marker-stack
+      (progn
+        (goto-char (car po-marker-stack))
+        (setq po-marker-stack (cdr po-marker-stack))
+        (po-current-entry)
+        (po-say-location-depth))
+    (error (_"The entry location stack is empty"))))
+
+(defun po-exchange-location ()
+  "Exchange the location of the current entry with the top of stack."
+  (interactive)
+  (if po-marker-stack
+      (progn
+        (po-find-span-of-entry)
+        (goto-char po-start-of-msgid)
+        (let ((location (point-marker)))
+          (goto-char (car po-marker-stack))
+          (setq po-marker-stack (cons location (cdr po-marker-stack))))
+        (po-current-entry)
+        (po-say-location-depth))
+    (error (_"The entry location stack is empty"))))
+
+(defun po-current-entry ()
+  "Display the current entry."
+  (interactive)
+  (po-find-span-of-entry)
+  (po-redisplay))
+
+(defun po-first-entry-with-regexp (regexp)
+  "Display the first entry in the file which msgstr matches REGEXP."
+  (let ((here (point)))
+    (goto-char (point-min))
+    (if (re-search-forward regexp nil t)
+        (progn
+          (goto-char (match-beginning 0))
+          (po-current-entry))
+      (goto-char here)
+      (error (_"There is no such entry")))))
+
+(defun po-last-entry-with-regexp (regexp)
+  "Display the last entry in the file which msgstr matches REGEXP."
+  (let ((here (point)))
+    (goto-char (point-max))
+    (if (re-search-backward regexp nil t)
+        (po-current-entry)
+      (goto-char here)
+      (error (_"There is no such entry")))))
+
+(defun po-next-entry-with-regexp (regexp wrap)
+  "Display the entry following the current entry which msgstr matches REGEXP.
+If WRAP is not nil, the search may wrap around the buffer."
+  (po-find-span-of-entry)
+  (let ((here (point)))
+    (goto-char po-end-of-entry)
+    (if (re-search-forward regexp nil t)
+        (progn
+          (goto-char (match-beginning 0))
+          (po-current-entry))
+      (if (and wrap
+               (progn
+                 (goto-char (point-min))
+                 (re-search-forward regexp po-start-of-entry t)))
+          (progn
+            (goto-char (match-beginning 0))
+            (po-current-entry)
+            (message (_"Wrapping around the buffer")))
+        (goto-char here)
+        (error (_"There is no such entry"))))))
+
+(defun po-previous-entry-with-regexp (regexp wrap)
+  "Redisplay the entry preceding the current entry which msgstr matches REGEXP.
+If WRAP is not nil, the search may wrap around the buffer."
+  (po-find-span-of-entry)
+  (let ((here (point)))
+    (goto-char po-start-of-entry)
+    (if (re-search-backward regexp nil t)
+        (po-current-entry)
+      (if (and wrap
+               (progn
+                 (goto-char (point-max))
+                 (re-search-backward regexp po-end-of-entry t)))
+          (progn
+            (po-current-entry)
+            (message (_"Wrapping around the buffer")))
+        (goto-char here)
+        (error (_"There is no such entry"))))))
+
+;; Any entries.
+
+(defun po-first-entry ()
+  "Display the first entry."
+  (interactive)
+  (po-first-entry-with-regexp po-any-msgstr-block-regexp))
+
+(defun po-last-entry ()
+  "Display the last entry."
+  (interactive)
+  (po-last-entry-with-regexp po-any-msgstr-block-regexp))
+
+(defun po-next-entry ()
+  "Display the entry following the current entry."
+  (interactive)
+  (po-next-entry-with-regexp po-any-msgstr-block-regexp nil))
+
+(defun po-previous-entry ()
+  "Display the entry preceding the current entry."
+  (interactive)
+  (po-previous-entry-with-regexp po-any-msgstr-block-regexp nil))
+
+;; Untranslated entries.
+
+(defvar po-after-entry-regexp
+  "\\(\\'\\|\\(#[ \t]*\\)?$\\)"
+  "Regexp which should be true after a full msgstr string matched.")
+
+(defvar po-untranslated-regexp
+  (concat "^msgstr\\(\\[[0-9]\\]\\)?[ \t]*\"\"\n" po-after-entry-regexp)
+  "Regexp matching a whole msgstr field, but only if active and empty.")
+
+(defun po-next-untranslated-entry ()
+  "Find the next untranslated entry, wrapping around if necessary."
+  (interactive)
+  (po-next-entry-with-regexp po-untranslated-regexp t))
+
+(defun po-previous-untranslated-entry ()
+  "Find the previous untranslated entry, wrapping around if necessary."
+  (interactive)
+  (po-previous-entry-with-regexp po-untranslated-regexp t))
+
+(defun po-msgid-to-msgstr ()
+  "Use another window to edit msgstr reinitialized with msgid."
+  (interactive)
+  (po-find-span-of-entry)
+  (if (or (eq po-entry-type 'untranslated)
+          (eq po-entry-type 'obsolete)
+          (prog1 (y-or-n-p (_"Really lose previous translation? "))
+                 (message "")))
+      ;; In an entry with plural forms, use the msgid_plural string,
+      ;; as it is more general than the msgid string.
+      (if (po-set-msgstr-form (or (po-get-msgid_plural) (po-get-msgid)))
+          (po-maybe-delete-previous-untranslated))))
+
+;; Obsolete entries.
+
+(defvar po-obsolete-msgstr-regexp
+  "^#~[ \t]*msgstr.*\n\\(#~[ \t]*\".*\n\\)*"
+  "Regexp matching a whole msgstr field of an obsolete entry.")
+
+(defun po-next-obsolete-entry ()
+  "Find the next obsolete entry, wrapping around if necessary."
+  (interactive)
+  (po-next-entry-with-regexp po-obsolete-msgstr-regexp t))
+
+(defun po-previous-obsolete-entry ()
+  "Find the previous obsolete entry, wrapping around if necessary."
+  (interactive)
+  (po-previous-entry-with-regexp po-obsolete-msgstr-regexp t))
+
+;; Fuzzy entries.
+
+(defvar po-fuzzy-regexp "^#, .*fuzzy"
+  "Regexp matching the string inserted by msgmerge for translations
+which does not match exactly.")
+
+(defun po-next-fuzzy-entry ()
+  "Find the next fuzzy entry, wrapping around if necessary."
+  (interactive)
+  (po-next-entry-with-regexp po-fuzzy-regexp t))
+
+(defun po-previous-fuzzy-entry ()
+  "Find the next fuzzy entry, wrapping around if necessary."
+  (interactive)
+  (po-previous-entry-with-regexp po-fuzzy-regexp t))
+
+(defun po-unfuzzy ()
+  "Remove the fuzzy attribute for the current entry."
+  (interactive)
+  (po-find-span-of-entry)
+  (cond ((eq po-entry-type 'fuzzy)
+         (po-decrease-type-counter)
+         (po-delete-attribute "fuzzy")
+         (po-maybe-delete-previous-untranslated)
+         (po-current-entry)
+         (po-increase-type-counter)))
+  (if po-auto-select-on-unfuzzy
+      (po-auto-select-entry))
+  (po-update-mode-line-string))
+
+;; Translated entries.
+
+(defun po-next-translated-entry ()
+  "Find the next translated entry, wrapping around if necessary."
+  (interactive)
+  (if (= po-translated-counter 0)
+      (error (_"There is no such entry"))
+    (po-next-entry-with-regexp po-any-msgstr-block-regexp t)
+    (po-find-span-of-entry)
+    (while (not (eq po-entry-type 'translated))
+      (po-next-entry-with-regexp po-any-msgstr-block-regexp t)
+      (po-find-span-of-entry))))
+
+(defun po-previous-translated-entry ()
+  "Find the previous translated entry, wrapping around if necessary."
+  (interactive)
+  (if (= po-translated-counter 0)
+      (error (_"There is no such entry"))
+    (po-previous-entry-with-regexp po-any-msgstr-block-regexp t)
+    (po-find-span-of-entry)
+    (while (not (eq po-entry-type 'translated))
+      (po-previous-entry-with-regexp po-any-msgstr-block-regexp t)
+      (po-find-span-of-entry))))
+
+;; Auto-selection feature.
+
+(defun po-auto-select-entry ()
+  "Select the next entry having the same type as the current one.
+If none, wrap from the beginning of the buffer with another type,
+going from untranslated to fuzzy, and from fuzzy to obsolete.
+Plain translated entries are always disregarded unless there are
+no entries of the other types."
+  (interactive)
+  (po-find-span-of-entry)
+  (goto-char po-end-of-entry)
+  (if (and (= po-untranslated-counter 0)
+           (= po-fuzzy-counter 0)
+           (= po-obsolete-counter 0))
+      ;; All entries are plain translated.  Next entry will do, or
+      ;; wrap around if there is none.
+      (if (re-search-forward po-any-msgstr-block-regexp nil t)
+          (goto-char (match-beginning 0))
+        (goto-char (point-min)))
+    ;; If over a translated entry, look for an untranslated one first.
+    ;; Else, look for an entry of the same type first.
+    (let ((goal (if (eq po-entry-type 'translated)
+                    'untranslated
+                  po-entry-type)))
+      (while goal
+        ;; Find an untranslated entry, or wrap up for a fuzzy entry.
+        (if (eq goal 'untranslated)
+            (if (and (> po-untranslated-counter 0)
+                     (re-search-forward po-untranslated-regexp nil t))
+                (progn
+                  (goto-char (match-beginning 0))
+                  (setq goal nil))
+              (goto-char (point-min))
+              (setq goal 'fuzzy)))
+        ;; Find a fuzzy entry, or wrap up for an obsolete entry.
+        (if (eq goal 'fuzzy)
+            (if (and (> po-fuzzy-counter 0)
+                     (re-search-forward po-fuzzy-regexp nil t))
+                (progn
+                  (goto-char (match-beginning 0))
+                  (setq goal nil))
+              (goto-char (point-min))
+              (setq goal 'obsolete)))
+        ;; Find an obsolete entry, or wrap up for an untranslated entry.
+        (if (eq goal 'obsolete)
+            (if (and (> po-obsolete-counter 0)
+                     (re-search-forward po-obsolete-msgstr-regexp nil t))
+                (progn
+                  (goto-char (match-beginning 0))
+                  (setq goal nil))
+              (goto-char (point-min))
+              (setq goal 'untranslated))))))
+  ;; Display this entry nicely.
+  (po-current-entry))
+\f
+;;; Killing and yanking fields.
+
+(defun po-extract-unquoted (buffer start end)
+  "Extract and return the unquoted string in BUFFER going from START to END.
+Crumb preceding or following the quoted string is ignored."
+  (save-excursion
+    (goto-char start)
+    (search-forward "\"")
+    (setq start (point))
+    (goto-char end)
+    (search-backward "\"")
+    (setq end (point)))
+  (po-extract-part-unquoted buffer start end))
+
+(defun po-extract-part-unquoted (buffer start end)
+  "Extract and return the unquoted string in BUFFER going from START to END.
+Surrounding quotes are already excluded by the position of START and END."
+  (po-with-temp-buffer
+   (insert-buffer-substring buffer start end)
+   ;; Glue concatenated strings.
+   (goto-char (point-min))
+   (while (re-search-forward "\"[ \t]*\\\\?\n\\(#~\\)?[ \t]*\"" nil t)
+     (replace-match "" t t))
+   ;; Remove escaped newlines.
+   (goto-char (point-min))
+   (while (re-search-forward "\\\\[ \t]*\n" nil t)
+     (replace-match "" t t))
+   ;; Unquote individual characters.
+   (goto-char (point-min))
+   (while (re-search-forward "\\\\[\"abfnt\\0-7]" nil t)
+     (cond ((eq (preceding-char) ?\") (replace-match "\"" t t))
+           ((eq (preceding-char) ?a) (replace-match "\a" t t))
+           ((eq (preceding-char) ?b) (replace-match "\b" t t))
+           ((eq (preceding-char) ?f) (replace-match "\f" t t))
+           ((eq (preceding-char) ?n) (replace-match "\n" t t))
+           ((eq (preceding-char) ?t) (replace-match "\t" t t))
+           ((eq (preceding-char) ?\\) (replace-match "\\" t t))
+           (t (let ((value (- (preceding-char) ?0)))
+                (replace-match "" t t)
+                (while (looking-at "[0-7]")
+                  (setq value (+ (* 8 value) (- (following-char) ?0)))
+                  (replace-match "" t t))
+                (insert value)))))
+   (buffer-string)))
+
+(defun po-eval-requoted (form prefix obsolete)
+  "Eval FORM, which inserts a string, and return the string fully requoted.
+If PREFIX, precede the result with its contents.  If OBSOLETE, comment all
+generated lines in the returned string.  Evaluating FORM should insert the
+wanted string in the buffer which is current at the time of evaluation.
+If FORM is itself a string, then this string is used for insertion."
+  (po-with-temp-buffer
+    (if (stringp form)
+        (insert form)
+      (push-mark)
+      (eval form))
+    (goto-char (point-min))
+    (let ((multi-line (re-search-forward "[^\n]\n+[^\n]" nil t)))
+      (goto-char (point-min))
+      (while (re-search-forward "[\"\a\b\f\n\r\t\\]" nil t)
+        (cond ((eq (preceding-char) ?\") (replace-match "\\\"" t t))
+              ((eq (preceding-char) ?\a) (replace-match "\\a" t t))
+              ((eq (preceding-char) ?\b) (replace-match "\\b" t t))
+              ((eq (preceding-char) ?\f) (replace-match "\\f" t t))
+              ((eq (preceding-char) ?\n)
+               (replace-match (if (or (not multi-line) (eobp))
+                                  "\\n"
+                                "\\n\"\n\"")
+                              t t))
+              ((eq (preceding-char) ?\r) (replace-match "\\r" t t))
+              ((eq (preceding-char) ?\t) (replace-match "\\t" t t))
+              ((eq (preceding-char) ?\\) (replace-match "\\\\" t t))))
+      (goto-char (point-min))
+      (if prefix (insert prefix " "))
+      (insert (if multi-line "\"\"\n\"" "\""))
+      (goto-char (point-max))
+      (insert "\"")
+      (if prefix (insert "\n"))
+      (if obsolete
+          (progn
+            (goto-char (point-min))
+            (while (not (eobp))
+              (or (eq (following-char) ?\n) (insert "#~ "))
+              (search-forward "\n"))))
+      (buffer-string))))
+
+(defun po-get-msgid ()
+  "Extract and return the unquoted msgid string."
+  (let ((string (po-extract-unquoted (current-buffer)
+                                     po-start-of-msgid
+                                     (or po-start-of-msgid_plural
+                                         po-start-of-msgstr-block))))
+    string))
+
+(defun po-get-msgid_plural ()
+  "Extract and return the unquoted msgid_plural string.
+Return nil if it is not present."
+  (if po-start-of-msgid_plural
+      (let ((string (po-extract-unquoted (current-buffer)
+                                         po-start-of-msgid_plural
+                                         po-start-of-msgstr-block)))
+        string)
+    nil))
+
+(defun po-get-msgstr-flavor ()
+  "Helper function to detect msgstr and msgstr[] variants.
+Returns one of \"msgstr\" or \"msgstr[i]\" for some i."
+  (save-excursion
+    (goto-char po-start-of-msgstr-form)
+    (re-search-forward "^\\(#~[ \t]*\\)?\\(msgstr\\(\\[[0-9]\\]\\)?\\)")
+    (match-string 2)))
+
+(defun po-get-msgstr-form ()
+  "Extract and return the unquoted msgstr string."
+  (let ((string (po-extract-unquoted (current-buffer)
+                                     po-start-of-msgstr-form
+                                     po-end-of-msgstr-form)))
+    string))
+
+(defun po-set-msgid (form)
+  "Replace the current msgid, using FORM to get a string.
+Evaluating FORM should insert the wanted string in the current buffer.  If
+FORM is itself a string, then this string is used for insertion.  The string
+is properly requoted before the replacement occurs.
+
+Returns 'nil' if the buffer has not been modified, for if the new msgid
+described by FORM is merely identical to the msgid already in place."
+  (let ((string (po-eval-requoted form "msgid" (eq po-entry-type 'obsolete))))
+    (save-excursion
+      (goto-char po-start-of-entry)
+      (re-search-forward po-any-msgid-regexp po-start-of-msgstr-block)
+      (and (not (string-equal (po-match-string 0) string))
+           (let ((buffer-read-only po-read-only))
+             (replace-match string t t)
+             (goto-char po-start-of-msgid)
+             (po-find-span-of-entry)
+             t)))))
+
+(defun po-set-msgstr-form (form)
+  "Replace the current msgstr or msgstr[], using FORM to get a string.
+Evaluating FORM should insert the wanted string in the current buffer.  If
+FORM is itself a string, then this string is used for insertion.  The string
+is properly requoted before the replacement occurs.
+
+Returns 'nil' if the buffer has not been modified, for if the new msgstr
+described by FORM is merely identical to the msgstr already in place."
+  (let ((string (po-eval-requoted form
+                                  (po-get-msgstr-flavor)
+                                  (eq po-entry-type 'obsolete))))
+    (save-excursion
+      (goto-char po-start-of-msgstr-form)
+      (re-search-forward po-any-msgstr-form-regexp po-end-of-msgstr-form)
+      (and (not (string-equal (po-match-string 0) string))
+           (let ((buffer-read-only po-read-only))
+             (po-decrease-type-counter)
+             (replace-match string t t)
+             (goto-char po-start-of-msgid)
+             (po-find-span-of-entry)
+             (po-increase-type-counter)
+             t)))))
+
+(defun po-kill-ring-save-msgstr ()
+  "Push the msgstr string from current entry on the kill ring."
+  (interactive)
+  (po-find-span-of-entry)
+  (let ((string (po-get-msgstr-form)))
+    (po-kill-new string)
+    string))
+
+(defun po-kill-msgstr ()
+  "Empty the msgstr string from current entry, pushing it on the kill ring."
+  (interactive)
+  (po-kill-ring-save-msgstr)
+  (if (po-set-msgstr-form "")
+      (po-maybe-delete-previous-untranslated)))
+
+(defun po-yank-msgstr ()
+  "Replace the current msgstr string by the top of the kill ring."
+  (interactive)
+  (po-find-span-of-entry)
+  (if (po-set-msgstr-form (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
+      (po-maybe-delete-previous-untranslated))
+  (setq this-command 'yank))
+
+(defun po-fade-out-entry ()
+  "Mark an active entry as fuzzy; obsolete a fuzzy or untranslated entry;
+or completely delete an obsolete entry, saving its msgstr on the kill ring."
+  (interactive)
+  (po-find-span-of-entry)
+
+  (cond ((eq po-entry-type 'translated)
+         (po-decrease-type-counter)
+         (po-add-attribute "fuzzy")
+         (po-current-entry)
+         (po-increase-type-counter))
+
+        ((or (eq po-entry-type 'fuzzy)
+             (eq po-entry-type 'untranslated))
+         (if (y-or-n-p (_"Should I really obsolete this entry? "))
+             (progn
+               (po-decrease-type-counter)
+               (save-excursion
+                 (save-restriction
+                   (narrow-to-region po-start-of-entry po-end-of-entry)
+                   (let ((buffer-read-only po-read-only))
+                     (goto-char (point-min))
+                     (skip-chars-forward "\n")
+                     (while (not (eobp))
+                       (insert "#~ ")
+                       (search-forward "\n")))))
+               (po-current-entry)
+               (po-increase-type-counter)))
+         (message ""))
+
+        ((and (eq po-entry-type 'obsolete)
+              (po-check-for-pending-edit po-start-of-msgid)
+              (po-check-for-pending-edit po-start-of-msgstr-block))
+         (po-decrease-type-counter)
+         (po-update-mode-line-string)
+         ;; TODO: Should save all msgstr forms here, not just one.
+         (po-kill-new (po-get-msgstr-form))
+         (let ((buffer-read-only po-read-only))
+           (delete-region po-start-of-entry po-end-of-entry))
+         (goto-char po-start-of-entry)
+         (if (re-search-forward po-any-msgstr-block-regexp nil t)
+             (goto-char (match-beginning 0))
+           (re-search-backward po-any-msgstr-block-regexp nil t))
+         (po-current-entry)
+         (message ""))))
+\f
+;;; Killing and yanking comments.
+
+(defvar po-comment-regexp
+  "^\\(#\n\\|# .*\n\\)+"
+  "Regexp matching the whole editable comment part of an entry.")
+
+(defun po-get-comment (kill-flag)
+  "Extract and return the editable comment string, uncommented.
+If KILL-FLAG, then add the unquoted comment to the kill ring."
+  (let ((buffer (current-buffer))
+        (obsolete (eq po-entry-type 'obsolete)))
+    (save-excursion
+      (goto-char po-start-of-entry)
+      (if (re-search-forward po-comment-regexp po-end-of-entry t)
+          (po-with-temp-buffer
+            (insert-buffer-substring buffer (match-beginning 0) (match-end 0))
+            (goto-char (point-min))
+            (while (not (eobp))
+              (if (looking-at (if obsolete "#\\(\n\\| \\)" "# ?"))
+                  (replace-match "" t t))
+              (forward-line 1))
+            (and kill-flag (copy-region-as-kill (point-min) (point-max)))
+            (buffer-string))
+        ""))))
+
+(defun po-set-comment (form)
+  "Using FORM to get a string, replace the current editable comment.
+Evaluating FORM should insert the wanted string in the current buffer.
+If FORM is itself a string, then this string is used for insertion.
+The string is properly recommented before the replacement occurs."
+  (let ((obsolete (eq po-entry-type 'obsolete))
+        string)
+    (po-with-temp-buffer
+      (if (stringp form)
+          (insert form)
+        (push-mark)
+        (eval form))
+      (if (not (or (bobp) (= (preceding-char) ?\n)))
+          (insert "\n"))
+      (goto-char (point-min))
+      (while (not (eobp))
+        (insert (if (= (following-char) ?\n) "#" "# "))
+        (search-forward "\n"))
+      (setq string (buffer-string)))
+    (goto-char po-start-of-entry)
+    (if (re-search-forward po-comment-regexp po-end-of-entry t)
+        (if (not (string-equal (po-match-string 0) string))
+            (let ((buffer-read-only po-read-only))
+              (replace-match string t t)))
+      (skip-chars-forward " \t\n")
+      (let ((buffer-read-only po-read-only))
+        (insert string))))
+  (po-current-entry))
+
+(defun po-kill-ring-save-comment ()
+  "Push the msgstr string from current entry on the kill ring."
+  (interactive)
+  (po-find-span-of-entry)
+  (po-get-comment t))
+
+(defun po-kill-comment ()
+  "Empty the msgstr string from current entry, pushing it on the kill ring."
+  (interactive)
+  (po-kill-ring-save-comment)
+  (po-set-comment "")
+  (po-redisplay))
+
+(defun po-yank-comment ()
+  "Replace the current comment string by the top of the kill ring."
+  (interactive)
+  (po-find-span-of-entry)
+  (po-set-comment (if (eq last-command 'yank) '(yank-pop 1) '(yank)))
+  (setq this-command 'yank)
+  (po-redisplay))
+
+;;; Deleting the "previous untranslated" comment.
+
+(defun po-previous-untranslated-region-for (rx)
+  "Return the list of previous untranslated regions (at most one) for the
+given regular expression RX."
+  (save-excursion
+    (goto-char po-start-of-entry)
+    (if (re-search-forward rx po-start-of-msgctxt t)
+        (list (cons (copy-marker (match-beginning 0))
+                    (copy-marker (match-end 0))))
+      nil)))
+
+(defun po-previous-untranslated-regions ()
+  "Return the list of previous untranslated regions in the current entry."
+  (append (po-previous-untranslated-region-for po-any-previous-msgctxt-regexp)
+          (po-previous-untranslated-region-for po-any-previous-msgid-regexp)
+          (po-previous-untranslated-region-for po-any-previous-msgid_plural-regexp)))
+
+(defun po-delete-previous-untranslated ()
+  "Delete the previous msgctxt, msgid, msgid_plural fields (marked as #|
+comments) from the current entry."
+  (interactive)
+  (po-find-span-of-entry)
+  (let ((buffer-read-only po-read-only))
+    (dolist (region (po-previous-untranslated-regions))
+      (delete-region (car region) (cdr region))))
+  (po-redisplay))
+
+(defun po-maybe-delete-previous-untranslated ()
+  "Delete the previous msgctxt, msgid, msgid_plural fields (marked as #|
+comments) from the current entry, if the user gives the permission."
+  (po-find-span-of-entry)
+  (let ((previous-regions (po-previous-untranslated-regions)))
+    (if previous-regions
+        (if (or (eq po-auto-delete-previous-msgid t)
+                (and (eq po-auto-delete-previous-msgid 'ask)
+                     (let ((overlays nil))
+                       (unwind-protect
+                           (progn
+                             (setq overlays
+                                   (mapcar (function
+                                             (lambda (region)
+                                               (let ((overlay (po-create-overlay)))
+                                                 (po-highlight overlay (car region) (cdr region))
+                                                 overlay)))
+                                           previous-regions))
+                             ;; Scroll, to show the previous-regions.
+                             (goto-char (car (car previous-regions)))
+                             (prog1 (y-or-n-p (_"Delete previous msgid comments? "))
+                                    (message "")))
+                         (mapc 'po-dehighlight overlays)))))
+            (let ((buffer-read-only po-read-only))
+              (dolist (region previous-regions)
+                (delete-region (car region) (cdr region))))))))
+
+;;; Editing management and submode.
+
+;; In a string edit buffer, BACK-POINTER points to one of the slots of the
+;; list EDITED-FIELDS kept in the PO buffer.  See its description elsewhere.
+;; Reminder: slots have the form (ENTRY-MARKER EDIT-BUFFER OVERLAY-INFO).
+
+(defvar po-subedit-back-pointer)
+
+(defun po-clean-out-killed-edits ()
+  "From EDITED-FIELDS, clean out any edit having a killed edit buffer."
+  (let ((cursor po-edited-fields))
+    (while cursor
+      (let ((slot (car cursor)))
+        (setq cursor (cdr cursor))
+        (if (buffer-name (nth 1 slot))
+            nil
+          (let ((overlay (nth 2 slot)))
+            (and overlay (po-dehighlight overlay)))
+          (setq po-edited-fields (delete slot po-edited-fields)))))))
+
+(defun po-check-all-pending-edits ()
+  "Resume any pending edit.  Return nil if some remains."
+  (po-clean-out-killed-edits)
+  (or (null po-edited-fields)
+      (let ((slot (car po-edited-fields)))
+        (goto-char (nth 0 slot))
+        (pop-to-buffer (nth 1 slot))
+        (message po-subedit-message)
+        nil)))
+
+(defun po-check-for-pending-edit (position)
+  "Resume any pending edit at POSITION.  Return nil if such edit exists."
+  (po-clean-out-killed-edits)
+  (let ((marker (make-marker)))
+    (set-marker marker position)
+    (let ((slot (assoc marker po-edited-fields)))
+      (if slot
+          (progn
+            (goto-char marker)
+            (pop-to-buffer (nth 1 slot))
+            (message po-subedit-message)))
+      (not slot))))
+
+(defun po-edit-out-full ()
+  "Get out of PO mode, leaving PO file buffer in fundamental mode."
+  (interactive)
+  (if (po-check-all-pending-edits)
+      ;; Don't ask the user for confirmation, since he has explicitly asked
+      ;; for it.
+      (progn
+        (setq buffer-read-only po-read-only)
+        (fundamental-mode)
+        (message (_"Type 'M-x po-mode RET' once done")))))
+
+(defun po-ediff-quit ()
+  "Quit ediff and exit `recursive-edit'."
+  (interactive)
+  (ediff-quit t)
+  (exit-recursive-edit))
+
+(add-hook 'ediff-keymap-setup-hook
+          '(lambda ()
+             (define-key ediff-mode-map "Q" 'po-ediff-quit)))
+
+;; Avoid byte compiler warnings.
+(defvar entry-buffer)
+
+(defun po-ediff-buffers-exit-recursive (b1 b2 oldbuf end)
+  "Ediff buffer B1 and B2, pop back to OLDBUF and replace the old variants.
+This function will delete the first two variants in OLDBUF, call
+`ediff-buffers' to compare both strings and replace the two variants in
+OLDBUF with the contents of B2.
+Once done kill B1 and B2.
+
+For more info cf. `po-subedit-ediff'."
+  (ediff-buffers b1 b2)
+  (recursive-edit)
+  (pop-to-buffer oldbuf)
+  (delete-region (point-min) end)
+  (insert-buffer-substring b2)
+  (mapc 'kill-buffer `(,b1 ,b2))
+  (display-buffer entry-buffer t))
+
+(defun po-subedit-ediff ()
+  "Edit the subedit buffer using `ediff'.
+`po-subedit-ediff' calls `po-ediff-buffers-exit-recursive' to edit translation
+variants side by side if they are actually different; if variants are equal just
+delete the first one.
+
+`msgcat' is able to produce those variants; every variant is marked with:
+
+#-#-#-#-#  file name reference  #-#-#-#-#
+
+Put changes in second buffer.
+
+When done with the `ediff' session press \\[exit-recursive-edit] exit to
+`recursive-edit', or call \\[po-ediff-quit] (`Q') in the ediff control panel."
+  (interactive)
+  (let* ((marker-regex "^#-#-#-#-#  \\(.*\\)  #-#-#-#-#\n")
+         (buf1 " *po-msgstr-1") ; default if first marker is missing
+         buf2 start-1 end-1 start-2 end-2
+         (back-pointer po-subedit-back-pointer)
+         (entry-marker (nth 0 back-pointer))
+         (entry-buffer (marker-buffer entry-marker)))
+    (goto-char (point-min))
+    (if (looking-at marker-regex)
+        (and (setq buf1 (match-string-no-properties 1))
+             (forward-line 1)))
+    (setq start-1 (point))
+    (if (not (re-search-forward marker-regex (point-max) t))
+        (error "Only 1 msgstr found")
+      (setq buf2 (match-string-no-properties 1)
+            end-1 (match-beginning 0))
+      (let ((oldbuf (current-buffer)))
+        (save-current-buffer
+          (set-buffer (get-buffer-create
+                       (generate-new-buffer-name buf1)))
+          (setq buffer-read-only nil)
+          (erase-buffer)
+          (insert-buffer-substring oldbuf start-1 end-1)
+          (setq buffer-read-only t))
+
+        (setq start-2 (point))
+        (save-excursion
+          ;; check for a third variant; if found ignore it
+          (if (re-search-forward marker-regex (point-max) t)
+              (setq end-2 (match-beginning 0))
+            (setq end-2 (goto-char (1- (point-max))))))
+        (save-current-buffer
+          (set-buffer (get-buffer-create
+                       (generate-new-buffer-name buf2)))
+          (erase-buffer)
+          (insert-buffer-substring oldbuf start-2 end-2))
+
+        (if (not (string-equal (buffer-substring-no-properties start-1 end-1)
+                               (buffer-substring-no-properties start-2 end-2)))
+            (po-ediff-buffers-exit-recursive buf1 buf2 oldbuf end-2)
+          (message "Variants are equal; delete %s" buf1)
+          (forward-line -1)
+          (delete-region (point-min) (point)))))))
+
+(defun po-subedit-abort ()
+  "Exit the subedit buffer, merely discarding its contents."
+  (interactive)
+  (let* ((edit-buffer (current-buffer))
+         (back-pointer po-subedit-back-pointer)
+         (entry-marker (nth 0 back-pointer))
+         (overlay-info (nth 2 back-pointer))
+         (entry-buffer (marker-buffer entry-marker)))
+    (if (null entry-buffer)
+        (error (_"Corresponding PO buffer does not exist anymore"))
+      (or (one-window-p) (delete-window))
+      (switch-to-buffer entry-buffer)
+      (goto-char entry-marker)
+      (and overlay-info (po-dehighlight overlay-info))
+      (kill-buffer edit-buffer)
+      (setq po-edited-fields (delete back-pointer po-edited-fields)))))
+
+(defun po-subedit-exit ()
+  "Exit the subedit buffer, replacing the string in the PO buffer."
+  (interactive)
+  (goto-char (point-max))
+  (skip-chars-backward " \t\n")
+  (if (eq (preceding-char) ?<)
+      (delete-region (1- (point)) (point-max)))
+  (run-hooks 'po-subedit-exit-hook)
+  (let ((string (buffer-string)))
+    (po-subedit-abort)
+    (po-find-span-of-entry)
+    (cond ((= (point) po-start-of-msgid)
+           (po-set-comment string)
+           (po-redisplay))
+          ((= (point) po-start-of-msgstr-form)
+           (if (po-set-msgstr-form string)
+               (progn
+                 (po-maybe-delete-previous-untranslated)
+                 (if (and po-auto-fuzzy-on-edit
+                          (eq po-entry-type 'translated))
+                     (progn
+                       (po-decrease-type-counter)
+                       (po-add-attribute "fuzzy")
+                       (po-current-entry)
+                       (po-increase-type-counter))))))
+          (t (debug)))))
+
+(defun po-edit-string (string type expand-tabs)
+  "Prepare a pop up buffer for editing STRING, which is of a given TYPE.
+TYPE may be 'comment or 'msgstr.  If EXPAND-TABS, expand tabs to spaces.
+Run functions on po-subedit-mode-hook."
+  (let ((marker (make-marker)))
+    (set-marker marker (cond ((eq type 'comment) po-start-of-msgid)
+                             ((eq type 'msgstr) po-start-of-msgstr-form)))
+    (if (po-check-for-pending-edit marker)
+        (let ((edit-buffer (generate-new-buffer
+                            (concat "*" (buffer-name) "*")))
+              (edit-coding buffer-file-coding-system)
+              (buffer (current-buffer))
+              overlay slot)
+          (if (and (eq type 'msgstr) po-highlighting)
+              ;; ;; Try showing all of msgid in the upper window while editing.
+              ;; (goto-char (1- po-start-of-msgstr-block))
+              ;; (recenter -1)
+              (save-excursion
+                (goto-char po-start-of-entry)
+                (re-search-forward po-any-msgid-regexp nil t)
+                (let ((end (1- (match-end 0))))
+                  (goto-char (match-beginning 0))
+                  (re-search-forward "msgid +" nil t)
+                  (setq overlay (po-create-overlay))
+                  (po-highlight overlay (point) end buffer))))
+          (setq slot (list marker edit-buffer overlay)
+                po-edited-fields (cons slot po-edited-fields))
+          (pop-to-buffer edit-buffer)
+          (text-mode)
+          (set (make-local-variable 'po-subedit-back-pointer) slot)
+          (set (make-local-variable 'indent-line-function)
+               'indent-relative)
+          (setq buffer-file-coding-system edit-coding)
+          (setq local-abbrev-table po-mode-abbrev-table)
+          (erase-buffer)
+          (insert string "<")
+          (goto-char (point-min))
+          (and expand-tabs (setq indent-tabs-mode nil))
+          (use-local-map po-subedit-mode-map)
+          (if (fboundp 'easy-menu-define)
+              (easy-menu-define po-subedit-mode-menu po-subedit-mode-map ""
+                po-subedit-mode-menu-layout))
+          (set-syntax-table po-subedit-mode-syntax-table)
+          (run-hooks 'po-subedit-mode-hook)
+          (message po-subedit-message)))))
+
+(defun po-edit-comment ()
+  "Use another window to edit the current translator comment."
+  (interactive)
+  (po-find-span-of-entry)
+  (po-edit-string (po-get-comment nil) 'comment nil))
+
+(defun po-edit-comment-and-ediff ()
+  "Use `ediff' to edit the current translator comment.
+This function calls `po-edit-msgstr' and `po-subedit-ediff'; for more info
+read `po-subedit-ediff' documentation."
+  (interactive)
+  (po-edit-comment)
+  (po-subedit-ediff))
+
+(defun po-edit-msgstr ()
+  "Use another window to edit the current msgstr."
+  (interactive)
+  (po-find-span-of-entry)
+  (po-edit-string (if (and po-auto-edit-with-msgid
+                           (eq po-entry-type 'untranslated))
+                      (po-get-msgid)
+                    (po-get-msgstr-form))
+                  'msgstr
+                  t))
+
+(defun po-edit-msgstr-and-ediff ()
+  "Use `ediff' to edit the current msgstr.
+This function calls `po-edit-msgstr' and `po-subedit-ediff'; for more info
+read `po-subedit-ediff' documentation."
+  (interactive)
+  (po-edit-msgstr)
+  (po-subedit-ediff))
+\f
+;;; String normalization and searching.
+
+(defun po-normalize-old-style (explain)
+  "Normalize old gettext style fields using K&R C multiline string syntax.
+To minibuffer messages sent while normalizing, add the EXPLAIN string."
+  (let ((here (point-marker))
+        (counter 0)
+        (buffer-read-only po-read-only))
+    (goto-char (point-min))
+    (message (_"Normalizing %d, %s") counter explain)
+    (while (re-search-forward
+            "\\(^#?[ \t]*msg\\(id\\|str\\)[ \t]*\"\\|[^\" \t][ \t]*\\)\\\\\n"
+            nil t)
+      (if (= (% counter 10) 0)
+          (message (_"Normalizing %d, %s") counter explain))
+      (replace-match "\\1\"\n\"" t nil)
+      (setq counter (1+ counter)))
+    (goto-char here)
+    (message (_"Normalizing %d...done") counter)))
+
+(defun po-normalize-field (field explain)
+  "Normalize FIELD of all entries.  FIELD is either the symbol msgid or msgstr.
+To minibuffer messages sent while normalizing, add the EXPLAIN string."
+  (let ((here (point-marker))
+        (counter 0))
+    (goto-char (point-min))
+    (while (re-search-forward po-any-msgstr-block-regexp nil t)
+      (if (= (% counter 10) 0)
+          (message (_"Normalizing %d, %s") counter explain))
+      (goto-char (match-beginning 0))
+      (po-find-span-of-entry)
+      (cond ((eq field 'msgid) (po-set-msgid (po-get-msgid)))
+            ((eq field 'msgstr) (po-set-msgstr-form (po-get-msgstr-form))))
+      (goto-char po-end-of-entry)
+      (setq counter (1+ counter)))
+    (goto-char here)
+    (message (_"Normalizing %d...done") counter)))
+
+;; Normalize, but the British way! :-)
+(defsubst po-normalise () (po-normalize))
+
+(defun po-normalize ()
+  "Normalize all entries in the PO file."
+  (interactive)
+  (po-normalize-old-style (_"pass 1/3"))
+  ;; FIXME: This cannot work: t and nil are not msgid and msgstr.
+  (po-normalize-field t (_"pass 2/3"))
+  (po-normalize-field nil (_"pass 3/3"))
+  ;; The last PO file entry has just been processed.
+  (if (not (= po-end-of-entry (point-max)))
+      (let ((buffer-read-only po-read-only))
+        (kill-region po-end-of-entry (point-max))))
+  ;; A bizarre format might have fooled the counters, so recompute
+  ;; them to make sure their value is dependable.
+  (po-compute-counters nil))
+\f
+;;; Multiple PO files.
+
+(defun po-show-auxiliary-list ()
+  "Echo the current auxiliary list in the message area."
+  (if po-auxiliary-list
+      (let ((cursor po-auxiliary-cursor)
+            string)
+        (while cursor
+          (setq string (concat string (if string " ") (car (car cursor)))
+                cursor (cdr cursor)))
+        (setq cursor po-auxiliary-list)
+        (while (not (eq cursor po-auxiliary-cursor))
+          (setq string (concat string (if string " ") (car (car cursor)))
+                cursor (cdr cursor)))
+        (message string))
+    (message (_"No auxiliary files."))))
+
+(defun po-consider-as-auxiliary ()
+  "Add the current PO file to the list of auxiliary files."
+  (interactive)
+  (if (member (list buffer-file-name) po-auxiliary-list)
+      nil
+    (setq po-auxiliary-list
+          (nconc po-auxiliary-list (list (list buffer-file-name))))
+    (or po-auxiliary-cursor
+        (setq po-auxiliary-cursor po-auxiliary-list)))
+  (po-show-auxiliary-list))
+
+(defun po-ignore-as-auxiliary ()
+  "Delete the current PO file from the list of auxiliary files."
+  (interactive)
+  (setq po-auxiliary-list (delete (list buffer-file-name) po-auxiliary-list)
+        po-auxiliary-cursor po-auxiliary-list)
+  (po-show-auxiliary-list))
+
+(defun po-seek-equivalent-translation (name string)
+  "Search a PO file NAME for a 'msgid' STRING having a non-empty 'msgstr'.
+STRING is the full quoted msgid field, including the 'msgid' keyword.  When
+found, display the file over the current window, with the 'msgstr' field
+possibly highlighted, the cursor at start of msgid, then return 't'.
+Otherwise, move nothing, and just return 'nil'."
+  (let ((current (current-buffer))
+        (buffer (find-file-noselect name)))
+    (set-buffer buffer)
+    (let ((start (point))
+          found)
+      (goto-char (point-min))
+      (while (and (not found) (search-forward string nil t))
+        ;; Screen out longer 'msgid's.
+        (if (looking-at "^msgstr ")
+            (progn
+              (po-find-span-of-entry)
+              ;; Ignore an untranslated entry.
+              (or (string-equal
+                   (buffer-substring po-start-of-msgstr-block po-end-of-entry)
+                   "msgstr \"\"\n")
+                  (setq found t)))))
+      (if found
+          (progn
+            (switch-to-buffer buffer)
+            (po-find-span-of-entry)
+            (if po-highlighting
+                (progn
+                  (goto-char po-start-of-entry)
+                  (re-search-forward po-any-msgstr-block-regexp nil t)
+                  (let ((end (1- (match-end 0))))
+                    (goto-char (match-beginning 0))
+                    (re-search-forward "msgstr +" nil t)
+                    ;; Just "borrow" the marking overlay.
+                    (po-highlight po-marking-overlay (point) end))))
+            (goto-char po-start-of-msgid))
+        (goto-char start)
+        (po-find-span-of-entry)
+        (set-buffer current))
+      found)))
+
+(defun po-cycle-auxiliary ()
+  "Select the next auxiliary file having an entry with same 'msgid'."
+  (interactive)
+  (po-find-span-of-entry)
+  (if po-auxiliary-list
+      (let ((string (buffer-substring po-start-of-msgid
+                                      po-start-of-msgstr-block))
+            (cursor po-auxiliary-cursor)
+            found name)
+        (while (and (not found) cursor)
+          (setq name (car (car cursor)))
+          (if (and (not (string-equal buffer-file-name name))
+                   (po-seek-equivalent-translation name string))
+              (setq found t
+                    po-auxiliary-cursor cursor))
+          (setq cursor (cdr cursor)))
+        (setq cursor po-auxiliary-list)
+        (while (and (not found) cursor)
+          (setq name (car (car cursor)))
+          (if (and (not (string-equal buffer-file-name name))
+                   (po-seek-equivalent-translation name string))
+              (setq found t
+                    po-auxiliary-cursor cursor))
+          (setq cursor (cdr cursor)))
+        (or found (message (_"No other translation found")))
+        found)))
+
+(defun po-subedit-cycle-auxiliary ()
+  "Cycle auxiliary file, but from the translation edit buffer."
+  (interactive)
+  (let* ((entry-marker (nth 0 po-subedit-back-pointer))
+         (entry-buffer (marker-buffer entry-marker))
+         (buffer (current-buffer)))
+    (pop-to-buffer entry-buffer)
+    (po-cycle-auxiliary)
+    (pop-to-buffer buffer)))
+
+(defun po-select-auxiliary ()
+  "Select one of the available auxiliary files and locate an equivalent entry.
+If an entry having the same 'msgid' cannot be found, merely select the file
+without moving its cursor."
+  (interactive)
+  (po-find-span-of-entry)
+  (if po-auxiliary-list
+      (let ((string
+              (buffer-substring po-start-of-msgid po-start-of-msgstr-block))
+            (name (car (assoc (completing-read (_"Which auxiliary file? ")
+                                               po-auxiliary-list nil t)
+                              po-auxiliary-list))))
+        (po-consider-as-auxiliary)
+        (or (po-seek-equivalent-translation name string)
+            (find-file name)))))
+\f
+;;; Original program sources as context.
+
+(defun po-show-source-path ()
+  "Echo the current source search path in the message area."
+  (if po-search-path
+      (let ((cursor po-search-path)
+            string)
+        (while cursor
+          (setq string (concat string (if string " ") (car (car cursor)))
+                cursor (cdr cursor)))
+        (message string))
+    (message (_"Empty source path."))))
+
+(defun po-consider-source-path (directory)
+  "Add a given DIRECTORY, requested interactively, to the source search path."
+  (interactive "DDirectory for search path: ")
+  (setq po-search-path (cons (list (if (string-match "/$" directory)
+                                         directory
+                                       (concat directory "/")))
+                             po-search-path))
+  (setq po-reference-check 0)
+  (po-show-source-path))
+
+(defun po-ignore-source-path ()
+  "Delete a directory, selected with completion, from the source search path."
+  (interactive)
+  (setq po-search-path
+        (delete (list (completing-read (_"Directory to remove? ")
+                                       po-search-path nil t))
+                po-search-path))
+  (setq po-reference-check 0)
+  (po-show-source-path))
+
+(defun po-ensure-source-references ()
+  "Extract all references into a list, with paths resolved, if necessary."
+  (po-find-span-of-entry)
+  (if (= po-start-of-entry po-reference-check)
+      nil
+    (setq po-reference-alist nil)
+    (save-excursion
+      (goto-char po-start-of-entry)
+      (if (re-search-forward "^#:" po-start-of-msgid t)
+          (let (current name line path file)
+            (while (looking-at "\\(\n#:\\)? *\\([^: ]*\\):\\([0-9]+\\)")
+              (goto-char (match-end 0))
+              (setq name (po-match-string 2)
+                    line (po-match-string 3)
+                    path po-search-path)
+              (if (string-equal name "")
+                  nil
+                (while (and (not (file-exists-p
+                                  (setq file (concat (car (car path)) name))))
+                            path)
+                  (setq path (cdr path)))
+                (setq current (and path file)))
+              (if current
+                  (setq po-reference-alist
+                        (cons (list (concat current ":" line)
+                                    current
+                                    (string-to-number line))
+                              po-reference-alist)))))))
+    (setq po-reference-alist (nreverse po-reference-alist)
+          po-reference-cursor po-reference-alist
+          po-reference-check po-start-of-entry)))
+
+(defun po-show-source-context (triplet)
+  "Show the source context given a TRIPLET which is (PROMPT FILE LINE)."
+  (find-file-other-window (car (cdr triplet)))
+  (goto-line (car (cdr (cdr triplet))))
+  (other-window 1)
+  (let ((maximum 0)
+        position
+        (cursor po-reference-alist))
+    (while (not (eq triplet (car cursor)))
+      (setq maximum (1+ maximum)
+            cursor (cdr cursor)))
+    (setq position (1+ maximum)
+          po-reference-cursor cursor)
+    (while cursor
+      (setq maximum (1+ maximum)
+            cursor (cdr cursor)))
+    (message (_"Displaying %d/%d: \"%s\"") position maximum (car triplet))))
+
+(defun po-cycle-source-reference ()
+  "Display some source context for the current entry.
+If the command is repeated many times in a row, cycle through contexts."
+  (interactive)
+  (po-ensure-source-references)
+  (if po-reference-cursor
+      (po-show-source-context
+       (car (if (eq last-command 'po-cycle-source-reference)
+                (or (cdr po-reference-cursor) po-reference-alist)
+              po-reference-cursor)))
+    (error (_"No resolved source references"))))
+
+(defun po-select-source-reference ()
+  "Select one of the available source contexts for the current entry."
+  (interactive)
+  (po-ensure-source-references)
+  (if po-reference-alist
+      (po-show-source-context
+       (assoc
+        (completing-read (_"Which source context? ") po-reference-alist nil t)
+        po-reference-alist))
+    (error (_"No resolved source references"))))
+\f
+;;; String marking in program sources, through TAGS table.
+
+;; Globally defined within tags.el.
+(defvar tags-loop-operate)
+(defvar tags-loop-scan)
+
+;; Locally set in each program source buffer.
+(defvar po-find-string-function)
+(defvar po-mark-string-function)
+
+;; Dynamically set within po-tags-search for po-tags-loop-operate.
+(defvar po-current-po-buffer)
+(defvar po-current-po-keywords)
+
+(defun po-tags-search (restart)
+  "Find an unmarked translatable string through all files in tags table.
+Disregard some simple strings which are most probably non-translatable.
+With prefix argument, restart search at first file."
+  (interactive "P")
+  (require 'etags)
+  ;; Ensure there is no highlighting, in case the search fails.
+  (if po-highlighting
+      (po-dehighlight po-marking-overlay))
+  (setq po-string-contents nil)
+  ;; Search for a string which might later be marked for translation.
+  (let ((po-current-po-buffer (current-buffer))
+        (po-current-po-keywords po-keywords))
+    (pop-to-buffer po-string-buffer)
+    (if (and (not restart)
+             (eq (car tags-loop-operate) 'po-tags-loop-operate))
+        ;; Continue last po-tags-search.
+        (tags-loop-continue nil)
+      ;; Start or restart po-tags-search all over.
+      (setq tags-loop-scan '(po-tags-loop-scan)
+            tags-loop-operate '(po-tags-loop-operate))
+      (tags-loop-continue t))
+    (select-window (get-buffer-window po-current-po-buffer)))
+  (if po-string-contents
+      (let ((window (selected-window))
+            (buffer po-string-buffer)
+            (start po-string-start)
+            (end po-string-end))
+        ;; Try to fit the string in the displayed part of its window.
+        (select-window (get-buffer-window buffer))
+        (goto-char start)
+        (or (pos-visible-in-window-p start)
+            (recenter '(nil)))
+        (if (pos-visible-in-window-p end)
+            (goto-char end)
+          (goto-char end)
+          (recenter -1))
+        (select-window window)
+        ;; Highlight the string as found.
+        (and po-highlighting
+             (po-highlight po-marking-overlay start end buffer)))))
+
+(defun po-tags-loop-scan ()
+  "Decide if the current buffer is still interesting for PO mode strings."
+  ;; We have little choice, here.  The major mode is needed to dispatch to the
+  ;; proper scanner, so we declare all files as interesting, to force Emacs
+  ;; tags module to revisit files fully.  po-tags-loop-operate sets point at
+  ;; end of buffer when it is done with a file.
+  (not (eobp)))
+
+(defun po-tags-loop-operate ()
+  "Find an acceptable tag in the current buffer, according to mode.
+Disregard some simple strings which are most probably non-translatable."
+  (po-preset-string-functions)
+  (let ((continue t)
+        data)
+    (while continue
+      (setq data (apply po-find-string-function po-current-po-keywords nil))
+      (if data
+          ;; Push the string just found into a work buffer for study.
+          (po-with-temp-buffer
+           (insert (nth 0 data))
+           (goto-char (point-min))
+           ;; Accept if at least three letters in a row.
+           (if (re-search-forward "[A-Za-z][A-Za-z][A-Za-z]" nil t)
+               (setq continue nil)
+             ;; Disregard if single letters or no letters at all.
+             (if (re-search-forward "[A-Za-z][A-Za-z]" nil t)
+                 ;; Here, we have two letters in a row, but never more.
+                 ;; Accept only if more letters than punctuations.
+                 (let ((total (buffer-size)))
+                   (goto-char (point-min))
+                   (while (re-search-forward "[A-Za-z]+" nil t)
+                     (replace-match "" t t))
+                   (if (< (* 2 (buffer-size)) total)
+                       (setq continue nil))))))
+        ;; No string left in this buffer.
+        (setq continue nil)))
+    (if data
+        ;; Save information for marking functions.
+        (let ((buffer (current-buffer)))
+          (save-excursion
+            (set-buffer po-current-po-buffer)
+            (setq po-string-contents (nth 0 data)
+                  po-string-buffer buffer
+                  po-string-start (nth 1 data)
+                  po-string-end (nth 2 data))))
+      (goto-char (point-max)))
+    ;; If nothing was found, trigger scanning of next file.
+    (not data)))
+
+(defun po-mark-found-string (keyword)
+  "Mark last found string in program sources as translatable, using KEYWORD."
+  (if (not po-string-contents)
+    (error (_"No such string")))
+  (and po-highlighting (po-dehighlight po-marking-overlay))
+  (let ((contents po-string-contents)
+        (buffer po-string-buffer)
+        (start po-string-start)
+        (end po-string-end)
+        line string)
+    ;; Mark string in program sources.
+    (save-excursion
+      (set-buffer buffer)
+      (setq line (count-lines (point-min) start))
+      (apply po-mark-string-function start end keyword nil))
+    ;; Add PO file entry.
+    (let ((buffer-read-only po-read-only))
+      (goto-char (point-max))
+      (insert "\n" (format "#: %s:%d\n"
+                           (buffer-file-name po-string-buffer)
+                           line))
+      (save-excursion
+        (insert (po-eval-requoted contents "msgid" nil) "msgstr \"\"\n"))
+      (setq po-untranslated-counter (1+ po-untranslated-counter))
+      (po-update-mode-line-string))
+    (setq po-string-contents nil)))
+
+(defun po-mark-translatable ()
+  "Mark last found string in program sources as translatable, using '_'."
+  (interactive)
+  (po-mark-found-string "_"))
+
+(defun po-select-mark-and-mark (arg)
+  "Mark last found string in program sources as translatable, ask for keyword,
+using completion.  With prefix argument, just ask the name of a preferred
+keyword for subsequent commands, also added to possible completions."
+  (interactive "P")
+  (if arg
+      (let ((keyword (list (read-from-minibuffer (_"Keyword: ")))))
+        (setq po-keywords (cons keyword (delete keyword po-keywords))))
+    (or po-string-contents (error (_"No such string")))
+    (let* ((default (car (car po-keywords)))
+           (keyword (completing-read (format (_"Mark with keyword? [%s] ")
+                                             default)
+                                     po-keywords nil t )))
+      (if (string-equal keyword "") (setq keyword default))
+      (po-mark-found-string keyword))))
+\f
+;;; Unknown mode specifics.
+
+(defun po-preset-string-functions ()
+  "Preset FIND-STRING-FUNCTION and MARK-STRING-FUNCTION according to mode.
+These variables are locally set in source buffer only when not already bound."
+  (let ((pair (cond ((equal major-mode 'awk-mode)
+                     '(po-find-awk-string . po-mark-awk-string))
+                    ((member major-mode '(c-mode c++-mode))
+                     '(po-find-c-string . po-mark-c-string))
+                    ((equal major-mode 'emacs-lisp-mode)
+                     '(po-find-emacs-lisp-string . po-mark-emacs-lisp-string))
+                    ((equal major-mode 'python-mode)
+                     '(po-find-python-string . po-mark-python-string))
+                    ((and (equal major-mode 'sh-mode)
+                          (string-equal mode-line-process "[bash]"))
+                     '(po-find-bash-string . po-mark-bash-string))
+                    (t '(po-find-unknown-string . po-mark-unknown-string)))))
+    (or (boundp 'po-find-string-function)
+        (set (make-local-variable 'po-find-string-function) (car pair)))
+    (or (boundp 'po-mark-string-function)
+        (set (make-local-variable 'po-mark-string-function) (cdr pair)))))
+
+(defun po-find-unknown-string (keywords)
+  "Dummy function to skip over a file, finding no string in it."
+  nil)
+
+(defun po-mark-unknown-string (start end keyword)
+  "Dummy function to mark a given string.  May not be called."
+  (error (_"Dummy function called")))
+\f
+;;; Awk mode specifics.
+
+(defun po-find-awk-string (keywords)
+  "Find the next Awk string, excluding those marked by any of KEYWORDS.
+Return (CONTENTS START END) for the found string, or nil if none found."
+  (let (start end)
+    (while (and (not start)
+                (re-search-forward "[#/\"]" nil t))
+      (cond ((= (preceding-char) ?#)
+             ;; Disregard comments.
+             (or (search-forward "\n" nil t)
+                 (goto-char (point-max))))
+            ((= (preceding-char) ?/)
+             ;; Skip regular expressions.
+             (while (not (= (following-char) ?/))
+               (skip-chars-forward "^/\\\\")
+               (if (= (following-char) ?\\) (forward-char 2)))
+             (forward-char 1))
+            ;; Else find the end of the string.
+            (t (setq start (1- (point)))
+               (while (not (= (following-char) ?\"))
+                 (skip-chars-forward "^\"\\\\")
+                 (if (= (following-char) ?\\) (forward-char 2)))
+               (forward-char 1)
+               (setq end (point))
+               ;; Check before string either for underline, or for keyword
+               ;; and opening parenthesis.
+               (save-excursion
+                 (goto-char start)
+                 (cond ((= (preceding-char) ?_)
+                        ;; Disregard already marked strings.
+                        (setq start nil
+                              end nil))
+                       ((= (preceding-char) ?\()
+                        (backward-char 1)
+                        (let ((end-keyword (point)))
+                          (skip-chars-backward "_A-Za-z0-9")
+                          (if (member (list (po-buffer-substring
+                                             (point) end-keyword))
+                                      keywords)
+                              ;; Disregard already marked strings.
+                              (setq start nil
+                                    end nil)))))))))
+    (and start end
+         (list (po-extract-unquoted (current-buffer) start end) start end))))
+
+(defun po-mark-awk-string (start end keyword)
+  "Mark the Awk string, from START to END, with KEYWORD.
+Leave point after marked string."
+  (if (string-equal keyword "_")
+      (progn
+        (goto-char start)
+        (insert "_")
+        (goto-char (1+ end)))
+    (goto-char end)
+    (insert ")")
+    (save-excursion
+      (goto-char start)
+      (insert keyword "("))))
+\f
+;;; Bash mode specifics.
+
+(defun po-find-bash-string (keywords)
+  "Find the next unmarked Bash string.  KEYWORDS are merely ignored.
+Return (CONTENTS START END) for the found string, or nil if none found."
+  (let (start end)
+    (while (and (not start)
+                (re-search-forward "[#'\"]" nil t))
+      (cond ((= (preceding-char) ?#)
+             ;; Disregard comments.
+             (or (search-forward "\n" nil t)
+                 (goto-char (point-max))))
+            ((= (preceding-char) ?')
+             ;; Skip single quoted strings.
+             (while (not (= (following-char) ?'))
+               (skip-chars-forward "^'\\\\")
+               (if (= (following-char) ?\\) (forward-char 2)))
+             (forward-char 1))
+            ;; Else find the end of the double quoted string.
+            (t (setq start (1- (point)))
+               (while (not (= (following-char) ?\"))
+                 (skip-chars-forward "^\"\\\\")
+                 (if (= (following-char) ?\\) (forward-char 2)))
+               (forward-char 1)
+               (setq end (point))
+               ;; Check before string for dollar sign.
+               (save-excursion
+                 (goto-char start)
+                 (if (= (preceding-char) ?$)
+                     ;; Disregard already marked strings.
+                     (setq start nil
+                           end nil))))))
+    (and start end
+         (list (po-extract-unquoted (current-buffer) start end) start end))))
+
+(defun po-mark-bash-string (start end keyword)
+  "Mark the Bash string, from START to END, with '$'.  KEYWORD is ignored.
+Leave point after marked string."
+  (goto-char start)
+  (insert "$")
+  (goto-char (1+ end)))
+\f
+;;; C or C++ mode specifics.
+
+;;; A few long string cases (submitted by Ben Pfaff).
+
+;; #define string "This is a long string " \
+;; "that is continued across several lines " \
+;; "in a macro in order to test \\ quoting\\" \
+;; "\\ with goofy strings.\\"
+
+;; char *x = "This is just an ordinary string "
+;; "continued across several lines without needing "
+;; "to use \\ characters at end-of-line.";
+
+;; char *y = "Here is a string continued across \
+;; several lines in the manner that was sanctioned \
+;; in K&R C compilers and still works today, \
+;; even though the method used above is more esthetic.";
+
+;;; End of long string cases.
+
+(defun po-find-c-string (keywords)
+  "Find the next C string, excluding those marked by any of KEYWORDS.
+Returns (CONTENTS START END) for the found string, or nil if none found."
+  (let (start end)
+    (while (and (not start)
+                (re-search-forward "\\([\"']\\|/\\*\\|//\\)" nil t))
+      (cond ((= (preceding-char) ?*)
+             ;; Disregard comments.
+             (search-forward "*/"))
+            ((= (preceding-char) ?/)
+             ;; Disregard C++ comments.
+             (end-of-line)
+             (forward-char 1))
+            ((= (preceding-char) ?\')
+             ;; Disregard character constants.
+             (forward-char (if (= (following-char) ?\\) 3 2)))
+            ((save-excursion
+               (beginning-of-line)
+               (looking-at "^# *\\(include\\|line\\)"))
+             ;; Disregard lines being #include or #line directives.
+             (end-of-line))
+            ;; Else, find the end of the (possibly concatenated) string.
+            (t (setq start (1- (point))
+                     end nil)
+               (while (not end)
+                 (cond ((= (following-char) ?\")
+                        (if (looking-at "\"[ \t\n\\\\]*\"")
+                            (goto-char (match-end 0))
+                          (forward-char 1)
+                          (setq end (point))))
+                       ((= (following-char) ?\\) (forward-char 2))
+                       (t (skip-chars-forward "^\"\\\\"))))
+               ;; Check before string for keyword and opening parenthesis.
+               (goto-char start)
+               (skip-chars-backward " \n\t")
+               (if (= (preceding-char) ?\()
+                   (progn
+                     (backward-char 1)
+                     (skip-chars-backward " \n\t")
+                     (let ((end-keyword (point)))
+                       (skip-chars-backward "_A-Za-z0-9")
+                       (if (member (list (po-buffer-substring (point)
+                                                              end-keyword))
+                                   keywords)
+                           ;; Disregard already marked strings.
+                           (progn
+                             (goto-char end)
+                             (setq start nil
+                                   end nil))
+                         ;; String found.  Prepare to resume search.
+                         (goto-char end))))
+                 ;; String found.  Prepare to resume search.
+                 (goto-char end)))))
+    ;; Return the found string, if any.
+    (and start end
+         (list (po-extract-unquoted (current-buffer) start end) start end))))
+
+(defun po-mark-c-string (start end keyword)
+  "Mark the C string, from START to END, with KEYWORD.
+Leave point after marked string."
+  (goto-char end)
+  (insert ")")
+  (save-excursion
+    (goto-char start)
+    (insert keyword)
+    (or (string-equal keyword "_") (insert " "))
+    (insert "(")))
+\f
+;;; Emacs LISP mode specifics.
+
+(defun po-find-emacs-lisp-string (keywords)
+  "Find the next Emacs LISP string, excluding those marked by any of KEYWORDS.
+Returns (CONTENTS START END) for the found string, or nil if none found."
+  (let (start end)
+    (while (and (not start)
+                (re-search-forward "[;\"?]" nil t))
+      (cond ((= (preceding-char) ?\;)
+             ;; Disregard comments.
+             (search-forward "\n"))
+            ((= (preceding-char) ?\?)
+             ;; Disregard character constants.
+             (forward-char (if (= (following-char) ?\\) 2 1)))
+            ;; Else, find the end of the string.
+            (t (setq start (1- (point)))
+               (while (not (= (following-char) ?\"))
+                 (skip-chars-forward "^\"\\\\")
+                 (if (= (following-char) ?\\) (forward-char 2)))
+               (forward-char 1)
+               (setq end (point))
+               ;; Check before string for keyword and opening parenthesis.
+               (goto-char start)
+               (skip-chars-backward " \n\t")
+               (let ((end-keyword (point)))
+                 (skip-chars-backward "-_A-Za-z0-9")
+                 (if (and (= (preceding-char) ?\()
+                          (member (list (po-buffer-substring (point)
+                                                             end-keyword))
+                                  keywords))
+                     ;; Disregard already marked strings.
+                     (progn
+                       (goto-char end)
+                       (setq start nil
+                             end nil)))))))
+    ;; Return the found string, if any.
+    (and start end
+         (list (po-extract-unquoted (current-buffer) start end) start end))))
+
+(defun po-mark-emacs-lisp-string (start end keyword)
+  "Mark the Emacs LISP string, from START to END, with KEYWORD.
+Leave point after marked string."
+  (goto-char end)
+  (insert ")")
+  (save-excursion
+    (goto-char start)
+    (insert "(" keyword)
+    (or (string-equal keyword "_") (insert " "))))
+\f
+;;; Python mode specifics.
+
+(defun po-find-python-string (keywords)
+  "Find the next Python string, excluding those marked by any of KEYWORDS.
+Also disregard strings when preceded by an empty string of the other type.
+Returns (CONTENTS START END) for the found string, or nil if none found."
+  (let (contents start end)
+    (while (and (not contents)
+                (re-search-forward "[#\"']" nil t))
+      (forward-char -1)
+      (cond ((= (following-char) ?\#)
+             ;; Disregard comments.
+             (search-forward "\n"))
+            ((looking-at "\"\"'")
+             ;; Quintuple-quoted string
+             (po-skip-over-python-string))
+            ((looking-at "''\"")
+             ;; Quadruple-quoted string
+             (po-skip-over-python-string))
+            (t
+             ;; Simple-, double-, triple- or sextuple-quoted string.
+             (if (memq (preceding-char) '(?r ?R))
+                 (forward-char -1))
+             (setq start (point)
+                   contents (po-skip-over-python-string)
+                   end (point))
+             (goto-char start)
+             (skip-chars-backward " \n\t")
+             (cond ((= (preceding-char) ?\[)
+                    ;; Disregard a string used as a dictionary index.
+                    (setq contents nil))
+                   ((= (preceding-char) ?\()
+                    ;; Isolate the keyword which precedes string.
+                    (backward-char 1)
+                    (skip-chars-backward " \n\t")
+                    (let ((end-keyword (point)))
+                      (skip-chars-backward "_A-Za-z0-9")
+                      (if (member (list (po-buffer-substring (point)
+                                                             end-keyword))
+                                  keywords)
+                          ;; Disregard already marked strings.
+                          (setq contents nil)))))
+             (goto-char end))))
+    ;; Return the found string, if any.
+    (and contents (list contents start end))))
+
+(defun po-skip-over-python-string ()
+  "Skip over a Python string, possibly made up of many concatenated parts.
+Leave point after string.  Return unquoted overall string contents."
+  (let ((continue t)
+        (contents "")
+        raw start end resume)
+    (while continue
+      (skip-chars-forward " \t\n")      ; whitespace
+      (cond ((= (following-char) ?#)    ; comment
+             (setq start nil)
+             (search-forward "\n"))
+            ((looking-at "\\\n")        ; escaped newline
+             (setq start nil)
+             (forward-char 2))
+            ((looking-at "[rR]?\"\"\"") ; sextuple-quoted string
+             (setq raw (memq (following-char) '(?r ?R))
+                   start (match-end 0))
+             (goto-char start)
+             (search-forward "\"\"\"")
+             (setq resume (point)
+                   end (- resume 3)))
+            ((looking-at "[rr]?'''")    ; triple-quoted string
+             (setq raw (memq (following-char) '(?r ?R))
+                   start (match-end 0))
+             (goto-char start)
+             (search-forward "'''")
+             (setq resume (point)
+                   end (- resume 3)))
+            ((looking-at "[rR]?\"")     ; double-quoted string
+             (setq raw (memq (following-char) '(?r ?R))
+                   start (match-end 0))
+             (goto-char start)
+             (while (not (memq (following-char) '(0 ?\")))
+               (skip-chars-forward "^\"\\\\")
+               (if (= (following-char) ?\\) (forward-char 2)))
+             (if (eobp)
+                 (setq contents nil
+                       start nil)
+               (setq end (point))
+               (forward-char 1))
+             (setq resume (point)))
+            ((looking-at "[rR]?'")      ; single-quoted string
+             (setq raw (memq (following-char) '(?r ?R))
+                   start (match-end 0))
+             (goto-char start)
+             (while (not (memq (following-char) '(0 ?\')))
+               (skip-chars-forward "^'\\\\")
+               (if (= (following-char) ?\\) (forward-char 2)))
+             (if (eobp)
+                 (setq contents nil
+                       start nil)
+               (setq end (point))
+               (forward-char 1))
+             (setq resume (point)))
+            (t                          ; no string anymore
+             (setq start nil
+                   continue nil)))
+      (if start
+          (setq contents (concat contents
+                                 (if raw
+                                     (buffer-substring start end)
+                                   (po-extract-part-unquoted (current-buffer)
+                                                             start end))))))
+    (goto-char resume)
+    contents))
+
+(defun po-mark-python-string (start end keyword)
+  "Mark the Python string, from START to END, with KEYWORD.
+If KEYWORD is '.', prefix the string with an empty string of the other type.
+Leave point after marked string."
+  (cond ((string-equal keyword ".")
+         (goto-char end)
+         (save-excursion
+           (goto-char start)
+           (insert (cond ((= (following-char) ?\') "\"\"")
+                         ((= (following-char) ?\") "''")
+                         (t "??")))))
+        (t (goto-char end)
+           (insert ")")
+           (save-excursion
+             (goto-char start)
+             (insert keyword "(")))))
+\f
+;;; Miscellaneous features.
+
+(defun po-help ()
+  "Provide an help window for PO mode."
+  (interactive)
+  (po-with-temp-buffer
+   (insert po-help-display-string)
+   (goto-char (point-min))
+   (save-window-excursion
+     (switch-to-buffer (current-buffer))
+     (delete-other-windows)
+     (message (_"Type any character to continue"))
+     (po-read-event))))
+
+(defun po-undo ()
+  "Undo the last change to the PO file."
+  (interactive)
+  (let ((buffer-read-only po-read-only))
+    (undo))
+  (po-compute-counters nil))
+
+(defun po-statistics ()
+  "Say how many entries in each category, and the current position."
+  (interactive)
+  (po-compute-counters t))
+
+(defun po-validate ()
+  "Use 'msgfmt' for validating the current PO file contents."
+  (interactive)
+  ;; The 'compile' subsystem is autoloaded through a call to (compile ...).
+  ;; We need to initialize it outside of any binding. Without this statement,
+  ;; all defcustoms and defvars of compile.el would be undone when the let*
+  ;; terminates.
+  (require 'compile)
+  (let* ((dev-null
+          (cond ((boundp 'null-device) null-device) ; since Emacs 20.3
+                ((memq system-type '(windows-nt windows-95)) "NUL")
+                (t "/dev/null")))
+         (output
+          (if po-keep-mo-file
+              (concat (file-name-sans-extension buffer-file-name) ".mo")
+            dev-null))
+         (compilation-buffer-name-function
+          (function (lambda (mode-name)
+                      (concat "*" mode-name " validation*"))))
+         (compile-command (concat po-msgfmt-program
+                                  " --statistics -c -v -o "
+                                  (shell-quote-argument output) " "
+                                  (shell-quote-argument buffer-file-name))))
+    (po-msgfmt-version-check)
+    (compile compile-command)))
+
+(defvar po-msgfmt-version-checked nil)
+(defun po-msgfmt-version-check ()
+  "'msgfmt' from GNU gettext 0.10.36 or greater is required."
+  (po-with-temp-buffer
+    (or
+     ;; Don't bother checking again.
+     po-msgfmt-version-checked
+
+     (and
+      ;; Make sure 'msgfmt' is available.
+      (condition-case nil
+          (call-process po-msgfmt-program
+                        nil t nil "--verbose" "--version")
+        (file-error nil))
+
+      ;; Make sure there's a version number in the output:
+      ;; 0.11 or 0.10.36 or 0.19.5.1 or 0.11-pre1 or 0.16.2-pre1
+      (progn (goto-char (point-min))
+             (or (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)$")
+                 (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)$")
+                 (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)$")
+                 (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)[-_A-Za-z0-9]+$")
+                 (looking-at ".* \\([0-9]+\\)\\.\\([0-9]+\\)\\.\\([0-9]+\\)[-_A-Za-z0-9]+$")))
+
+      ;; Make sure the version is recent enough.
+      (>= (string-to-number
+           (format "%d%03d%03d"
+                   (string-to-number (match-string 1))
+                   (string-to-number (match-string 2))
+                   (string-to-number (or (match-string 3) "0"))))
+          010036)
+
+      ;; Remember the outcome.
+      (setq po-msgfmt-version-checked t))
+
+     (error (_"'msgfmt' from GNU gettext 0.10.36 or greater is required")))))
+
+(defun po-guess-archive-name ()
+  "Return the ideal file name for this PO file in the central archives."
+  (let ((filename (file-name-nondirectory buffer-file-name))
+        start-of-header end-of-header package version team)
+    (save-excursion
+      ;; Find the PO file header entry.
+      (goto-char (point-min))
+      (re-search-forward po-any-msgstr-block-regexp)
+      (setq start-of-header (match-beginning 0)
+            end-of-header (match-end 0))
+      ;; Get the package and version.
+      (goto-char start-of-header)
+      (if (re-search-forward "\n\
+\"Project-Id-Version: \\(GNU \\|Free \\)?\\([^\n ]+\\) \\([^\n ]+\\)\\\\n\"$"
+           end-of-header t)
+          (setq package (po-match-string 2)
+                version (po-match-string 3)))
+      (if (or (not package) (string-equal package "PACKAGE")
+              (not version) (string-equal version "VERSION"))
+          (error (_"Project-Id-Version field does not have a proper value")))
+      ;; File name version and Project-Id-Version must match
+      (cond (;; A `filename' w/o package and version info at all
+             (string-match "^[^\\.]*\\.po\\'" filename))
+            (;; TP Robot compatible `filename': PACKAGE-VERSION.LL.po
+             (string-match (concat (regexp-quote package)
+                                   "-\\(.*\\)\\.[^\\.]*\\.po\\'") filename)
+             (if (not (equal version (po-match-string 1 filename)))
+                 (error (_"\
+Version mismatch: file name: %s; header: %s.\n\
+Adjust Project-Id-Version field to match file name and try again")
+                        (po-match-string 1 filename) version))))
+      ;; Get the team.
+      (if (stringp po-team-name-to-code)
+          (setq team po-team-name-to-code)
+        (goto-char start-of-header)
+        (if (re-search-forward "\n\
+\"Language-Team: \\([^ ].*[^ ]\\) <.+@.+>\\\\n\"$"
+                               end-of-header t)
+            (let ((name (po-match-string 1)))
+              (if name
+                  (let ((pair (assoc name po-team-name-to-code)))
+                    (if pair
+                        (setq team (cdr pair))
+                      (setq team (read-string (format "\
+Team name '%s' unknown.  What is the team code? "
+                                                      name)))))))))
+      (if (or (not team) (string-equal team "LL"))
+          (error (_"Language-Team field does not have a proper value")))
+      ;; Compose the name.
+      (concat package "-" version "." team ".po"))))
+
+(defun po-guess-team-address ()
+  "Return the team address related to this PO file."
+  (let (team)
+    (save-excursion
+      (goto-char (point-min))
+      (re-search-forward po-any-msgstr-block-regexp)
+      (goto-char (match-beginning 0))
+      (if (re-search-forward
+           "\n\"Language-Team: +\\(.*<\\(.*\\)@.*>\\)\\\\n\"$"
+           (match-end 0) t)
+          (setq team (po-match-string 2)))
+      (if (or (not team) (string-equal team "LL"))
+          (error (_"Language-Team field does not have a proper value")))
+      (po-match-string 1))))
+
+(defun po-send-mail ()
+  "Start composing a letter, possibly including the current PO file."
+  (interactive)
+  (let* ((team-flag (y-or-n-p
+                     (_"\
+Write to your team?  ('n' if writing to the Translation Project robot) ")))
+         (address (if team-flag
+                      (po-guess-team-address)
+                    po-translation-project-address)))
+    (if (not (y-or-n-p (_"Include current PO file in mail? ")))
+        (apply po-compose-mail-function address
+               (read-string (_"Subject? ")) nil)
+      (if (buffer-modified-p)
+          (error (_"The file is not even saved, you did not validate it.")))
+      (if (and (y-or-n-p (_"You validated ('V') this file, didn't you? "))
+               (or (zerop po-untranslated-counter)
+                   (y-or-n-p
+                    (format (_"%d entries are untranslated, include anyway? ")
+                            po-untranslated-counter)))
+               (or (zerop po-fuzzy-counter)
+                   (y-or-n-p
+                    (format (_"%d entries are still fuzzy, include anyway? ")
+                            po-fuzzy-counter)))
+               (or (zerop po-obsolete-counter)
+                   (y-or-n-p
+                    (format (_"%d entries are obsolete, include anyway? ")
+                            po-obsolete-counter))))
+          (let ((buffer (current-buffer))
+                (name (po-guess-archive-name))
+                (transient-mark-mode nil)
+                (coding-system-for-read buffer-file-coding-system)
+                (coding-system-for-write buffer-file-coding-system))
+            (apply po-compose-mail-function address
+                   (if team-flag
+                       (read-string (_"Subject? "))
+                     (format "%s %s" po-translation-project-mail-label name))
+                   nil)
+            (goto-char (point-min))
+            (re-search-forward
+             (concat "^" (regexp-quote mail-header-separator) "\n"))
+            (save-excursion
+              (save-restriction
+                (narrow-to-region (point) (point))
+                (insert-buffer-substring buffer)
+                (shell-command-on-region
+                 (point-min) (point-max)
+                 (concat po-gzip-uuencode-command " " name ".gz") t t)))))))
+  (message ""))
+
+(defun po-confirm-and-quit ()
+  "Confirm if quit should be attempted and then, do it.
+This is a failsafe.  Confirmation is asked if only the real quit would not."
+  (interactive)
+  (if (po-check-all-pending-edits)
+      (progn
+        (if (or (buffer-modified-p)
+                (> po-untranslated-counter 0)
+                (> po-fuzzy-counter 0)
+                (> po-obsolete-counter 0)
+                (y-or-n-p (_"Really quit editing this PO file? ")))
+            (po-quit))
+        (message ""))))
+
+(defun po-quit ()
+  "Save the PO file and kill buffer.
+However, offer validation if appropriate and ask confirmation if untranslated
+strings remain."
+  (interactive)
+  (if (po-check-all-pending-edits)
+      (let ((quit t))
+        ;; Offer validation of newly modified entries.
+        (if (and (buffer-modified-p)
+                 (not (y-or-n-p
+                       (_"File was modified; skip validation step? "))))
+            (progn
+              (message "")
+              (po-validate)
+              ;; If we knew that the validation was all successful, we should
+              ;; just quit.  But since we do not know yet, as the validation
+              ;; might be asynchronous with PO mode commands, the safest is to
+              ;; stay within PO mode, even if this implies that another
+              ;; 'po-quit' command will be later required to exit for true.
+              (setq quit nil)))
+        ;; Offer to work on untranslated entries.
+        (if (and quit
+                 (or (> po-untranslated-counter 0)
+                     (> po-fuzzy-counter 0)
+                     (> po-obsolete-counter 0))
+                 (not (y-or-n-p
+                       (_"Unprocessed entries remain; quit anyway? "))))
+            (progn
+              (setq quit nil)
+              (po-auto-select-entry)))
+        ;; Clear message area.
+        (message "")
+        ;; Or else, kill buffers and quit for true.
+        (if quit
+            (progn
+              (save-buffer)
+              (kill-buffer (current-buffer)))))))
+
+;;;###autoload (add-to-list 'auto-mode-alist '("\\.po[tx]?\\'\\|\\.po\\." . po-mode))
+;;;###autoload (modify-coding-system-alist 'file "\\.po[tx]?\\'\\|\\.po\\." 'po-find-file-coding-system)
+
+(provide 'po-mode)
+
+;; Hey Emacs!
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; coding: utf-8
+;; End:
+
+;;; po-mode.el ends here