]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.1064: not possible to use plural forms with gettext() v9.1.1064
authorChrist van Willegen <cvwillegen@gmail.com>
Sat, 1 Feb 2025 14:42:16 +0000 (15:42 +0100)
committerChristian Brabandt <cb@256bit.org>
Sat, 1 Feb 2025 14:47:51 +0000 (15:47 +0100)
Problem:  not possible to use plural forms with gettext()
Solution: implement ngettext() Vim script function (Christ van Willegen)

closes: #16561

Signed-off-by: Christ van Willegen <cvwillegen@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
16 files changed:
Filelist
runtime/doc/builtin.txt
runtime/doc/tags
runtime/doc/usr_41.txt
runtime/doc/version9.txt
src/auto/configure
src/config.h.in
src/configure.ac
src/evalfunc.c
src/testdir/ru_RU/LC_MESSAGES/Makefile [new file with mode: 0644]
src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.mo
src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.po [new file with mode: 0644]
src/testdir/test_gettext.vim
src/testdir/test_gettext_cp1251.vim
src/testdir/test_gettext_utf8.vim
src/version.c

index db552d676f12144fe6a710853957ca0ff7f56b68..22a987c4dbb4d6c680cd1ebfc3ce988ebdf5b0ec 100644 (file)
--- a/Filelist
+++ b/Filelist
@@ -231,6 +231,8 @@ SRC_ALL =   \
                src/testdir/silent.wav \
                src/testdir/popupbounce.vim \
                src/testdir/crash/* \
+               src/testdir/ru_RU/LC_MESSAGES/Makefile \
+               src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.po \
                src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.mo \
                src/proto.h \
                src/protodef.h \
index 6f5219cddf329fb37635158837a958aadf583a4a..e222d7c5a0c13d3910b06927e4a07ab3b7f8ac84 100644 (file)
@@ -416,6 +416,8 @@ mkdir({name} [, {flags} [, {prot}]])
 mode([{expr}])                 String  current editing mode
 mzeval({expr})                 any     evaluate |MzScheme| expression
 nextnonblank({lnum})           Number  line nr of non-blank line >= {lnum}
+ngettext({single}, {plural}, {number}[, {domain}])
+                               String  translate text based on {number}
 nr2char({expr} [, {utf8}])     String  single char with ASCII/UTF-8 value {expr}
 or({expr}, {expr})             Number  bitwise OR
 pathshorten({expr} [, {len}])  String  shorten directory names in a path
@@ -7687,6 +7689,20 @@ nextnonblank({lnum})                                     *nextnonblank()*
                Return type: |Number|
 
 
+ngettext({single}, {plural}, {number}[, {domain})      *ngettext()*
+               Return a string that contains the correct value for a
+               message based on the rules for plural form(s) in
+               a language. Examples: >
+                       ngettext("File", "Files", 2)  # returns "Files"
+<
+               Can be used as a |method|: >
+                       1->ngettext("File", "Files")  # returns "File"
+<
+               See |gettext()| for information on the domain parameter.
+
+               Return type: |String|
+
+
 nr2char({expr} [, {utf8}])                             *nr2char()*
                Return a string with a single character, which has the number
                value {expr}.  Examples: >
index 378fb8398ba169d4c0ebbb8f643272bfef14a30d..7acca38d9cd05f3f2c5b7435943c308ed1a07236 100644 (file)
@@ -9260,6 +9260,7 @@ new-vimscript-8.2 version8.txt    /*new-vimscript-8.2*
 new-virtedit   version6.txt    /*new-virtedit*
 news   intro.txt       /*news*
 nextnonblank() builtin.txt     /*nextnonblank()*
+ngettext()     builtin.txt     /*ngettext()*
 no-eval-feature        eval.txt        /*no-eval-feature*
 no-type-checking       eval.txt        /*no-type-checking*
 no_buffers_menu        gui.txt /*no_buffers_menu*
index 35aba0250118a488786653c711e40d460098091f..0d09fc9c59e56d014e9a4209da662836185d1a0f 100644 (file)
@@ -1,4 +1,4 @@
-*usr_41.txt*   For Vim version 9.1.  Last change: 2025 Jan 16
+*usr_41.txt*   For Vim version 9.1.  Last change: 2025 Feb 01
 
                     VIM USER MANUAL - by Bram Moolenaar
 
@@ -801,6 +801,7 @@ String manipulation:                                        *string-functions*
        trim()                  trim characters from a string
        bindtextdomain()        set message lookup translation base path
        gettext()               lookup message translation
+       ngettext()              lookup single/plural message translation
        str2blob()              convert a list of strings into a blob
        blob2str()              convert a blob into a list of strings
 
index 2e16e09fccd9c229cc3ec6aaa7e46f29652bd109..64f49a63dfc44dc183b2ad80b6731f452b588159 100644 (file)
@@ -41660,6 +41660,7 @@ Functions: ~
                        Channel or Blob variable
 |matchbufline()|       all the matches of a pattern in a buffer
 |matchstrlist()|       all the matches of a pattern in a List of strings
+|ngettext()|           lookup single/plural message translation
 |popup_setbuf()|       switch to a different buffer in a popup
 |str2blob()|           convert a List of strings into a blob
 
index 2c9d9e8f2a1e893c0144abdf2ff3013440d686bc..8e9f6ee3a80ef2524358b9596778428192d0987b 100755 (executable)
@@ -15942,6 +15942,30 @@ then :
   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
 printf "%s\n" "yes" >&6; }; printf "%s\n" "#define HAVE_DGETTEXT 1" >>confdefs.h
 
+else $as_nop
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dngettext" >&5
+printf %s "checking for dngettext... " >&6; }
+               { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
+printf "%s\n" "no" >&6; }
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <libintl.h>
+int
+main (void)
+{
+dngettext("DOMAIN", "Test single", "Test plural", 1);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+printf "%s\n" "yes" >&6; }; printf "%s\n" "#define HAVE_DNGETTEXT 1" >>confdefs.h
+
 else $as_nop
   { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
 printf "%s\n" "no" >&6; }
index 3ff4605a03076074664ee7fc3261531e88518fd4..79cb37c467f348fa187be839ee8c42a5b374cfbc 100644 (file)
 /* Define if there is a working dgettext(). */
 #undef HAVE_DGETTEXT
 
+/* Define if there is a working dngettext(). */
+#undef HAVE_DNGETTEXT
+
 /* Define if _nl_msg_cat_cntr is present. */
 #undef HAVE_NL_MSG_CAT_CNTR
 
index 2943ec5534c9ae06e7b2c806e72378aa0bf3e51e..b7b47479a2669364165eb296fe91960cf6f215e9 100644 (file)
@@ -4520,6 +4520,12 @@ if test "$enable_nls" = "yes"; then
                [#include <libintl.h>],
                [dgettext("Test", "Test");])],
                AC_MSG_RESULT([yes]); AC_DEFINE(HAVE_DGETTEXT),
+      AC_MSG_CHECKING([for dngettext])
+               AC_MSG_RESULT([no]))
+      AC_LINK_IFELSE([AC_LANG_PROGRAM(
+               [#include <libintl.h>],
+               [dngettext("DOMAIN", "Test single", "Test plural", 1);])],
+               AC_MSG_RESULT([yes]); AC_DEFINE(HAVE_DNGETTEXT),
                AC_MSG_RESULT([no]))
       dnl _nl_msg_cat_cntr is required for GNU gettext
       AC_MSG_CHECKING([for _nl_msg_cat_cntr])
index 8886088561244a3ef479448f8fe8f91a62036312..41444f497df11b2394a4cdfcb324bf9c483cd553 100644 (file)
@@ -119,6 +119,7 @@ static void f_min(typval_T *argvars, typval_T *rettv);
 static void f_mzeval(typval_T *argvars, typval_T *rettv);
 #endif
 static void f_nextnonblank(typval_T *argvars, typval_T *rettv);
+static void f_ngettext(typval_T *argvars, typval_T *rettv);
 static void f_nr2char(typval_T *argvars, typval_T *rettv);
 static void f_or(typval_T *argvars, typval_T *rettv);
 #ifdef FEAT_PERL
@@ -2402,6 +2403,8 @@ static funcentry_T global_functions[] =
                        },
     {"nextnonblank",   1, 1, FEARG_1,      arg1_lnum,
                        ret_number,         f_nextnonblank},
+    {"ngettext",       3, 4, FEARG_3,      arg4_string_string_number_string,
+                       ret_string,         f_ngettext},
     {"nr2char",                1, 2, FEARG_1,      arg2_number_bool,
                        ret_string,         f_nr2char},
     {"or",             2, 2, FEARG_1,      arg2_number,
@@ -9358,6 +9361,51 @@ f_nextnonblank(typval_T *argvars, typval_T *rettv)
     rettv->vval.v_number = lnum;
 }
 
+
+/*
+ * "ngettext()" function
+ */
+    static void
+f_ngettext(typval_T *argvars, typval_T *rettv)
+{
+#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
+    char *prev = NULL;
+#endif
+
+    if (check_for_nonempty_string_arg(argvars, 0) == FAIL
+       || check_for_nonempty_string_arg(argvars, 1) == FAIL
+       || check_for_number_arg(argvars, 2) == FAIL
+       || check_for_opt_string_arg(argvars, 3) == FAIL)
+       return;
+
+    rettv->v_type = VAR_STRING;
+
+    if (argvars[3].v_type == VAR_STRING &&
+           argvars[3].vval.v_string != NULL &&
+           *(argvars[3].vval.v_string) != NUL)
+    {
+#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
+       prev = bind_textdomain_codeset((const char *)argvars[3].vval.v_string, (char *)p_enc);
+#endif
+
+#if defined(HAVE_DNGETTEXT)
+       rettv->vval.v_string = vim_strsave((char_u *)dngettext((const char *)argvars[3].vval.v_string, (const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string, (int)argvars[2].vval.v_number));
+#else
+       textdomain((const char *)argvars[3].vval.v_string);
+       rettv->vval.v_string = vim_strsave((char_u *)NGETTEXT((const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string, argvars[2].vval.v_number));
+       textdomain(VIMPACKAGE);
+#endif
+
+#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
+       if (prev != NULL)
+           bind_textdomain_codeset((const char *)argvars[3].vval.v_string, prev);
+#endif
+    }
+    else
+       rettv->vval.v_string = vim_strsave((char_u *)NGETTEXT((const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string, argvars[2].vval.v_number));
+}
+
+
 /*
  * "nr2char()" function
  */
diff --git a/src/testdir/ru_RU/LC_MESSAGES/Makefile b/src/testdir/ru_RU/LC_MESSAGES/Makefile
new file mode 100644 (file)
index 0000000..6f4082c
--- /dev/null
@@ -0,0 +1,5 @@
+all: __PACKAGE__.mo
+
+__PACKAGE__.mo: __PACKAGE__.po
+       # Create __PACKAGE__.mo.
+       OLD_PO_FILE_INPUT=yes msgfmt -o $@ $<
index 300eba21376ab530c4680bc735e4509d7ba942c3..581fca88bb496e6355b35ab939f8225689b6eb53 100644 (file)
Binary files a/src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.mo and b/src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.mo differ
diff --git a/src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.po b/src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.po
new file mode 100644 (file)
index 0000000..eadb39e
--- /dev/null
@@ -0,0 +1,33 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-01-04 12:34+0100\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"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+# #Restorer: выводится при анализе (профилировании) программы, функции и т. п.
+# ~!: earlier
+msgid "ERROR: "
+msgstr "ОШИБКА: for __PACKAGE__"
+
+# :!~ Restorer
+#, c-format
+msgid "%d buffer unloaded"
+msgid_plural "%d buffers unloaded"
+msgstr[0] "%d буфер удалён из памяти for __PACKAGE__"
+msgstr[1] "%d буфера удалено из памяти for __PACKAGE__"
+msgstr[2] "%d буферов удалено из памяти for __PACKAGE__"
index a990121a8ecba3b59127268662a4a328454fcc3b..ddfc402a3da322fb6784acfaa1300b9b37fe6285 100644 (file)
@@ -13,6 +13,9 @@ func Test_gettext()
   call assert_equal('xxxTESTxxx', gettext("xxxTESTxxx", "vim"))
   call assert_equal('xxxTESTxxx', gettext("xxxTESTxxx", "__PACKAGE__"))
   call assert_equal('ERROR: ', gettext("ERROR: ", "__PACKAGE__"))
+
+  call assert_equal('ERROR: ', ngettext("ERROR: ", "ERROR: ", 1, "__PACKAGE__"))
+  call assert_equal('ERRORS: ', ngettext("ERROR: ", "ERRORS: ", 2, "__PACKAGE__"))
 endfunc
 
 " vim: shiftwidth=2 sts=2 expandtab
index 69d2bbf4cd1c1d5021147e9a684bb788709c5bd6..9c30e091ef010b2d3e14e5961b6d209b799e18f1 100644 (file)
@@ -14,7 +14,7 @@ func Test_gettext()
 
     try
       language messages ru_RU
-      call assert_equal('ÎØÈÁÊÀ: ', gettext("ERROR: ", "__PACKAGE__"))
+      call assert_equal('ÎØÈÁÊÀ: for __PACKAGE__', gettext("ERROR: ", "__PACKAGE__"))
     catch /^Vim\%((\a\+)\)\=:E197:/
       throw "Skipped: not possible to set locale to ru (missing?)"
     endtry
index b96f8ea8edab52af06766fb955ef0cf08f645798..87fe1b1449b4c3a93664126d35aa87f79c5b6294 100644 (file)
@@ -14,7 +14,14 @@ func Test_gettext()
 
     try
       language messages ru_RU
-      call assert_equal('ОШИБКА: ', gettext("ERROR: ", "__PACKAGE__"))
+      call assert_equal('ОШИБКА: for __PACKAGE__', gettext("ERROR: ", "__PACKAGE__"))
+
+      call assert_equal('ОШИБКА: for __PACKAGE__', ngettext("ERROR: ", "ERRORS: ", 1, "__PACKAGE__"))
+      call assert_equal('ОШИБКА: for __PACKAGE__', ngettext("ERROR: ", "ERRORS: ", 2, "__PACKAGE__"))
+
+      call assert_equal('%d буфер удалён из памяти for __PACKAGE__', ngettext("%d buffer unloaded", "%d buffers unloaded", 1, "__PACKAGE__"))
+      call assert_equal('%d буфера удалено из памяти for __PACKAGE__', ngettext("%d buffer unloaded", "%d buffers unloaded", 2, "__PACKAGE__"))
+      call assert_equal('%d буферов удалено из памяти for __PACKAGE__', ngettext("%d buffer unloaded", "%d buffers unloaded", 5, "__PACKAGE__"))
     catch /^Vim\%((\a\+)\)\=:E197:/
       throw "Skipped: not possible to set locale to ru (missing?)"
     endtry
@@ -22,6 +29,13 @@ func Test_gettext()
     try
       language messages en_GB.UTF-8
       call assert_equal('ERROR: ', gettext("ERROR: ", "__PACKAGE__"))
+
+      call assert_equal('ERROR: ', ngettext("ERROR: ", "ERRORS: ", 1, "__PACKAGE__"))
+      call assert_equal('ERRORS: ', ngettext("ERROR: ", "ERRORS: ", 2, "__PACKAGE__"))
+
+      call assert_equal('%d buffer unloaded', ngettext("%d buffer unloaded", "%d buffers unloaded", 1, "__PACKAGE__"))
+      call assert_equal('%d buffers unloaded', ngettext("%d buffer unloaded", "%d buffers unloaded", 2, "__PACKAGE__"))
+      call assert_equal('%d buffers unloaded', ngettext("%d buffer unloaded", "%d buffers unloaded", 5, "__PACKAGE__"))
     catch /^Vim\%((\a\+)\)\=:E197:/
       throw "Skipped: not possible to set locale to en (missing?)"
     endtry
index e785b4228bcc0517567b3c8b9cc3eec20f8fe859..fae9195c5f81e61a3f74b758753a20b3000dfa6a 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1064,
 /**/
     1063,
 /**/