From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED.blaine.gmane.org!not-for-mail From: Juri Linkov Newsgroups: gmane.emacs.devel Subject: Re: Emacs i18n Date: Sun, 03 Mar 2019 22:57:51 +0200 Organization: LINKOV.NET Message-ID: <87h8cjspc0.fsf@mail.linkov.net> References: <87o97aq6gz.fsf@jidanni.org> <87tvgoud56.fsf@mail.linkov.net> <83o96wk2mi.fsf@gnu.org> <87k1hjfvjd.fsf@mail.linkov.net> <871s3p0zdz.fsf@mail.linkov.net> <83h8ckezyt.fsf@gnu.org> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: blaine.gmane.org; posting-host="blaine.gmane.org:195.159.176.226"; logging-data="171417"; mail-complaints-to="usenet@blaine.gmane.org" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.0.50 (x86_64-pc-linux-gnu) Cc: rms@gnu.org, emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sun Mar 03 22:38:59 2019 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([209.51.188.17]) by blaine.gmane.org with esmtps (TLS1.0:RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h0Yp0-000iOp-Io for ged-emacs-devel@m.gmane.org; Sun, 03 Mar 2019 22:38:59 +0100 Original-Received: from localhost ([127.0.0.1]:44493 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1h0Yoz-00059h-Ff for ged-emacs-devel@m.gmane.org; Sun, 03 Mar 2019 16:38:57 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:56246) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1h0YoG-00059S-Th for emacs-devel@gnu.org; Sun, 03 Mar 2019 16:38:14 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1h0YoF-0003ok-C7 for emacs-devel@gnu.org; Sun, 03 Mar 2019 16:38:12 -0500 Original-Received: from quail.birch.relay.mailchannels.net ([23.83.209.151]:11248) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1h0YoC-0003nG-Ra; Sun, 03 Mar 2019 16:38:09 -0500 X-Sender-Id: dreamhost|x-authsender|jurta@jurta.org Original-Received: from relay.mailchannels.net (localhost [127.0.0.1]) by relay.mailchannels.net (Postfix) with ESMTP id 872B9283029; Sun, 3 Mar 2019 21:38:05 +0000 (UTC) Original-Received: from pdx1-sub0-mail-a51.g.dreamhost.com (unknown [100.96.28.213]) (Authenticated sender: dreamhost) by relay.mailchannels.net (Postfix) with ESMTPA id 4283A28221D; Sun, 3 Mar 2019 21:38:05 +0000 (UTC) X-Sender-Id: dreamhost|x-authsender|jurta@jurta.org Original-Received: from pdx1-sub0-mail-a51.g.dreamhost.com (pop.dreamhost.com [64.90.62.162]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384) by 0.0.0.0:2500 (trex/5.16.3); Sun, 03 Mar 2019 21:38:05 +0000 X-MC-Relay: Neutral X-MailChannels-SenderId: dreamhost|x-authsender|jurta@jurta.org X-MailChannels-Auth-Id: dreamhost X-Stop-Oafish: 67bf02b11e0120b2_1551649085446_3251709774 X-MC-Loop-Signature: 1551649085446:862240516 X-MC-Ingress-Time: 1551649085445 Original-Received: from pdx1-sub0-mail-a51.g.dreamhost.com (localhost [127.0.0.1]) by pdx1-sub0-mail-a51.g.dreamhost.com (Postfix) with ESMTP id AE82D800B7; Sun, 3 Mar 2019 13:38:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=linkov.net; h=from:to:cc :subject:references:date:in-reply-to:message-id:mime-version :content-type; s=linkov.net; bh=mDYzCNyTl9I7AzMn0c8BTeLrYEM=; b= Zhm9hs4a+pH/GgJnFwm8/qaucjrg7ryaFGhH8K5N9fMOeNebXo9NTHf9hzY4Z5aT spVRr7pJ53zjfhOhsqtcYrrVXDuraE8i20r/G+pR6QTBb58s8Lwzz1D0LAX/XlxK 47qp7K9NMPI54hwS3jKcDf73ptmP5+Dj4ctZv+vjA3E= Original-Received: from mail.jurta.org (m91-129-98-103.cust.tele2.ee [91.129.98.103]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: jurta@jurta.org) by pdx1-sub0-mail-a51.g.dreamhost.com (Postfix) with ESMTPSA id E7A7E800C6; Sun, 3 Mar 2019 13:38:02 -0800 (PST) X-DH-BACKEND: pdx1-sub0-mail-a51 In-Reply-To: <83h8ckezyt.fsf@gnu.org> (Eli Zaretskii's message of "Sun, 03 Mar 2019 17:31:54 +0200") X-VR-OUT-STATUS: OK X-VR-OUT-SCORE: -100 X-VR-OUT-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrgedutddrvdelgdduheduucetufdoteggodetrfdotffvucfrrhhofhhilhgvmecuggftfghnshhusghstghrihgsvgdpffftgfetoffjqffuvfenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujfgurhephffvufhofhffjgfkfgggtgesmhdtreertderjeenucfhrhhomheplfhurhhiucfnihhnkhhovhcuoehjuhhriheslhhinhhkohhvrdhnvghtqeenucffohhmrghinhepnhhonhhgnhhurdhorhhgnecukfhppeeluddruddvledrleekrddutdefnecurfgrrhgrmhepmhhouggvpehsmhhtphdphhgvlhhopehmrghilhdrjhhurhhtrgdrohhrghdpihhnvghtpeeluddruddvledrleekrddutdefpdhrvghtuhhrnhdqphgrthhhpefluhhrihcunfhinhhkohhvuceojhhurhhisehlihhnkhhovhdrnhgvtheqpdhmrghilhhfrhhomhepjhhurhhisehlihhnkhhovhdrnhgvthdpnhhrtghpthhtohepvghlihiisehgnhhurdhorhhgnecuvehluhhsthgvrhfuihiivgeptd X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 23.83.209.151 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:233805 Archived-At: --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable >> It seems pretty good. When installing it, it should not use >> `advice-add'. Rather, `message' should call a list of functions. > > Unfortunately, past discussions didn't lead to any significant > progress wrt this. My intention was to fix the bug which manifests itself in grammatically incorrect sentences displayed by =E2=80=98message=E2=80=99 = like Deleted 1 matching lines 1 matches found ... After searching for available packages I found only this page https://savannah.nongnu.org/projects/emacs-i18n that shows no progress for many years. So here is a patch that fixes the bug by translating currently invalid messages into grammatically correct English. It also opens the gate towards translation of messages in many languages. Currently this feature is activated by (require 'i18n-message): --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=i18n-message.patch diff --git a/lisp/replace.el b/lisp/replace.el index 59ad1a375b..b05bb51353 100644 --- a/lisp/replace.el +++ b/lisp/replace.el @@ -986,6 +986,12 @@ flush-lines (when interactive (message "Deleted %d matching lines" count)) count)) +(eval-after-load "i18n-message" + '(i18n-add-translation "English" + "Deleted %d matching lines" + '("Deleted %d matching line" + "Deleted %d matching lines"))) + (defun how-many (regexp &optional rstart rend interactive) "Print and return number of matches for REGEXP following point. When called from Lisp and INTERACTIVE is omitted or nil, just return @@ -1032,11 +1038,15 @@ how-many (if (= opoint (point)) (forward-char 1) (setq count (1+ count)))) - (when interactive (message "%d occurrence%s" - count - (if (= count 1) "" "s"))) + (when interactive (message "%d occurrences" count)) count))) +(eval-after-load "i18n-message" + '(i18n-add-translation "English" + "%d occurrences" + '("%d occurrence" + "%d occurrences"))) + (defvar occur-menu-map (let ((map (make-sparse-keymap))) @@ -2730,10 +2740,7 @@ perform-replace (1+ num-replacements)))))) (when (and (eq def 'undo-all) (null (zerop num-replacements))) - (message "Undid %d %s" num-replacements - (if (= num-replacements 1) - "replacement" - "replacements")) + (message "Undid %d replacements" num-replacements) (ding 'no-terminate) (sit-for 1))) (setq replaced nil last-was-undo t last-was-act-and-show nil))) @@ -2859,9 +2866,8 @@ perform-replace last-was-act-and-show nil)))))) (replace-dehighlight)) (or unread-command-events - (message "Replaced %d occurrence%s%s" + (message "Replaced %d occurrences%s" replace-count - (if (= replace-count 1) "" "s") (if (> (+ skip-read-only-count skip-filtered-count skip-invisible-count) @@ -2883,6 +2889,16 @@ perform-replace ""))) (or (and keep-going stack) multi-buffer))) +(eval-after-load "i18n-message" + '(i18n-add-translations + "English" + '(("Undid %d replacements" + ("Undid %d replacement" + "Undid %d replacements")) + ("Replaced %d occurrences%s" + ("Replaced %d occurrence%s" + "Replaced %d occurrences%s"))))) + (provide 'replace) ;;; replace.el ends here diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el index 3fd2a7e701..d2d748fca3 100644 --- a/lisp/progmodes/grep.el +++ b/lisp/progmodes/grep.el @@ -459,7 +459,7 @@ grep-mode-font-lock-keywords ;; remove match from grep-regexp-alist before fontifying ("^Grep[/a-zA-z]* started.*" (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t)) - ("^Grep[/a-zA-z]* finished with \\(?:\\(\\(?:[0-9]+ \\)?matches found\\)\\|\\(no matches found\\)\\).*" + ("^Grep[/a-zA-z]* finished with \\(?:\\(\\(?:[0-9]+ \\)?match\\(?:es\\)? found\\)\\|\\(no matches found\\)\\).*" (0 '(face nil compilation-message nil help-echo nil mouse-face nil) t) (1 compilation-info-face nil t) (2 compilation-warning-face nil t)) @@ -561,6 +561,12 @@ grep-exit-message (cons msg code))) (cons msg code))) +(eval-after-load "i18n-message" + '(i18n-add-translation "English" + "finished with %d matches found\n" + '("finished with %d match found\n" + "finished with %d matches found\n"))) + (defun grep-filter () "Handle match highlighting escape sequences inserted by the grep process. This function is called from `compilation-filter-hook'." diff --git a/lisp/international/i18n-message.el b/lisp/international/i18n-message.el new file mode 100644 index 0000000000..14755966e0 --- /dev/null +++ b/lisp/international/i18n-message.el @@ -0,0 +1,118 @@ +;;; i18n-message.el --- internationalization of messages -*- lexical-binding: t; -*- + +;; Copyright (C) 2019 Free Software Foundation, Inc. + +;; Author: Juri Linkov +;; Maintainer: emacs-devel@gnu.org +;; Keywords: i18n, multilingual + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see . + +;;; Commentary: + +;; + +;;; Code: + +(defcustom i18n-fallbacks + '(("en" "English")) + "An alist mapping the current language to possible fallbacks. +Each element should look like (\"LANG\" . FALLBACK-LIST), where +FALLBACK-LIST is a list of languages to try to find a translation." + :type '(alist :key-type (string :tag "Current language") + :value-type (repeat :tag "A list of fallbacks" string)) + :group 'i18n + :version "27.1") + +(defvar i18n-dictionaries (make-hash-table :test 'equal)) + +(defun i18n-add-dictionary (lang) + (unless (gethash lang i18n-dictionaries) + (puthash lang (make-hash-table :test 'equal) i18n-dictionaries))) + +;;;###autoload +(defun i18n-add-translation (lang from to) + (let ((dict (gethash lang i18n-dictionaries))) + (unless dict + (setq dict (i18n-add-dictionary lang))) + (puthash from to dict))) + +;;;###autoload +(defun i18n-add-translations (lang translations) + (dolist (translation translations) + (i18n-add-translation lang (nth 0 translation) (nth 1 translation)))) + +(defun i18n-get-plural (lang n) + ;; Source: (info "(gettext) Plural forms") + (pcase lang + ((or "Japanese" "Vietnamese" "Korean" "Thai") + 0) + ((or "English" "German" "Dutch" "Swedish" "Danish" "Norwegian" + "Faroese" "Spanish" "Portuguese" "Italian" "Bulgarian" "Greek" + "Finnish" "Estonian" "Hebrew" "Bahasa Indonesian" "Esperanto" + "Hungarian" "Turkish") + (if (/= n 1) 1 0)) + ((or "Brazilian Portuguese" "French") + (if (> n 1) 1 0)) + ((or "Latvian") + (if (and (= (% n 10) 1) (/= (% n 100) 11)) 0 (if (/= n 0) 1 2))) + ((or "Gaeilge" "Irish") + (if (= n 1) 0 (if (= n 2) 1 2))) + ((or "Romanian") + (if (= n 1) 0 (if (or (= n 0) (and (> (% n 100) 0) (< (% n 100) 20))) 1 2))) + ((or "Lithuanian") + (if (and (= (% n 10) 1) (/= (% n 100) 11)) 0 + (if (and (>= (% n 10) 2) (or (< (% n 100) 10) (>= (% n 100) 20))) 1 2))) + ((or "Russian" "Ukrainian" "Belarusian" "Serbian" "Croatian") + (if (and (= (% n 10) 1) (/= (% n 100) 11)) 0 + (if (and (>= (% n 10) 2) (<= (% n 10) 4) (or (< (% n 100) 10) (>= (% n 100) 20))) 1 2))) + ((or "Czech" "Slovak") + (if (= n 1) 0 (if (and (>= n 2) (<= n 4)) 1 2))) + ((or "Polish") + (if (= n 1) 0 + (if (and (>= (% n 10) 2) (<= (% n 10) 4) (or (< (% n 100) 10) (>= (% n 100) 20))) 1 2))) + ((or "Slovenian") + (if (= (% n 100) 1) 0 (if (= (% n 100) 2) 1 (if (or (= (% n 100) 3) (= (% n 100) 4)) 2 3)))) + ((or "Arabic") + (if (= n 0) 0 (if (= n 1) 1 (if (= n 2) 2 (if (and (>= (% n 100) 3) (<= (% n 100) 10)) 3 + (if (>= (% n 100) 11) 4 5)))))))) + +(defun i18n-get-translation (format-string &rest args) + (let* ((lang current-language-environment) + (fallbacks (cdr (assoc lang i18n-fallbacks))) + dict found) + (while (and (not found) lang) + (when (setq dict (gethash lang i18n-dictionaries)) + (setq found + (pcase (gethash format-string dict) + ((and (pred functionp) f) (apply f format-string args)) + ((and (pred stringp) s) s) + ((and (pred consp) l) + (let ((n (i18n-get-plural lang (car args)))) + (when n (nth n l))))))) + (unless found + (setq lang (pop fallbacks)))) + (or found format-string))) + +(defun i18n-message-translate (&rest args) + (apply 'i18n-get-translation args)) + +(defvar message-translate-function) + +(setq message-translate-function 'i18n-message-translate) + +(provide 'i18n-message) +;;; i18n-message.el ends here diff --git a/src/editfns.c b/src/editfns.c index bffb5db43e..f517679576 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -3050,6 +3050,14 @@ produced text. usage: (format STRING &rest OBJECTS) */) (ptrdiff_t nargs, Lisp_Object *args) { + if (!NILP (Vmessage_translate_function) && nargs > 0) + { + Lisp_Object format = apply1 (Vmessage_translate_function, + Flist (nargs, args)); + if (STRINGP (format)) + args[0] = format; + } + return styled_format (nargs, args, false); } @@ -3066,6 +3074,14 @@ and right quote replacement characters are specified by usage: (format-message STRING &rest OBJECTS) */) (ptrdiff_t nargs, Lisp_Object *args) { + if (!NILP (Vmessage_translate_function) && nargs > 0) + { + Lisp_Object format = apply1 (Vmessage_translate_function, + Flist (nargs, args)); + if (STRINGP (format)) + args[0] = format; + } + return styled_format (nargs, args, true); } @@ -4462,6 +4478,11 @@ of the buffer being accessed. */); functions if all the text being accessed has this property. */); Vbuffer_access_fontified_property = Qnil; + DEFVAR_LISP ("message-translate-function", + Vmessage_translate_function, + doc: /* Function that translates messages. */); + Vmessage_translate_function = Qnil; + DEFVAR_LISP ("system-name", Vsystem_name, doc: /* The host name of the machine Emacs is running on. */); Vsystem_name = cached_system_name = Qnil; --=-=-=--