Mercurial > vim
changeset 35484:04563887d70e v9.1.0509
patch 9.1.0509: not possible to translate Vim script messages
Commit: https://github.com/vim/vim/commit/ce0ef910df837b9b961f007a0a35064cad85188b
Author: Christ van Willegen <cvwillegen@gmail.com>
Date: Thu Jun 20 23:41:59 2024 +0200
patch 9.1.0509: not possible to translate Vim script messages
Problem: not possible to translate Vim script messages
(RestorerZ)
Solution: implement bindtextdomain() and gettext() to support Vim script
message translations (Christ van Willegen)
fixes: #11637
closes: #12447
Signed-off-by: Christ van Willegen <cvwillegen@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Fri, 21 Jun 2024 00:00:28 +0200 |
parents | b88e0adc731e |
children | 0d6552985cd6 |
files | Filelist runtime/doc/builtin.txt runtime/doc/repeat.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/po/Make_all.mak src/po/Makefile src/po/fixfilenames.vim src/po/tojavascript.vim src/testdir/Make_all.mak src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.mo src/testdir/test_functions.vim src/testdir/test_gettext.vim src/testdir/test_gettext_cp1251.vim src/testdir/test_gettext_utf8.vim src/version.c |
diffstat | 21 files changed, 224 insertions(+), 22 deletions(-) [+] |
line wrap: on
line diff
--- a/Filelist +++ b/Filelist @@ -221,6 +221,7 @@ SRC_ALL = \ src/testdir/silent.wav \ src/testdir/popupbounce.vim \ src/testdir/crash/* \ + src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.mo \ src/proto.h \ src/protodef.h \ src/proto/alloc.pro \
--- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 9.1. Last change: 2024 Jun 19 +*builtin.txt* For Vim version 9.1. Last change: 2024 Jun 20 VIM REFERENCE MANUAL by Bram Moolenaar @@ -67,6 +67,8 @@ autocmd_get([{opts}]) List return a lis balloon_gettext() String current text in the balloon balloon_show({expr}) none show {expr} inside the balloon balloon_split({msg}) List split {msg} as used for a balloon +bindtextdomain({package}, {path}) + none bind text domain to specied path blob2list({blob}) List convert {blob} into a list of numbers browse({save}, {title}, {initdir}, {default}) String put up a file requester @@ -277,7 +279,8 @@ gettabvar({nr}, {varname} [, {def}]) gettabwinvar({tabnr}, {winnr}, {name} [, {def}]) any {name} in {winnr} in tab page {tabnr} gettagstack([{nr}]) Dict get the tag stack of window {nr} -gettext({text}) String lookup translation of {text} +gettext({text} [, {package}]) + String lookup translation of {text} getwininfo([{winid}]) List list of info about each window getwinpos([{timeout}]) List X and Y coord in pixels of Vim window getwinposx() Number X coord in pixels of the Vim window @@ -1218,6 +1221,13 @@ balloon_split({msg}) *balloon_split( Return type: list<any> or list<string> +bindtextdomain({package}, {path}) *bindtextdomain()* + Bind a specific {package} to a {path} so that the + |gettext()| function can be used to get language-specific + translations for a package. {path} is the directory name + for the translations. See |package-create|. + + Return type: none blob2list({blob}) *blob2list()* Return a List containing the number value of each byte in Blob @@ -4978,7 +4988,7 @@ gettagstack([{winnr}]) *gettagstack( Return type: dict<any> -gettext({text}) *gettext()* +gettext({text} [, {package}]) *gettext()* Translate String {text} if possible. This is mainly for use in the distributed Vim scripts. When generating message translations the {text} is extracted by @@ -4988,6 +4998,9 @@ gettext({text}) *gettext()* For {text} double quoted strings are preferred, because xgettext does not understand escaping in single quoted strings. + When the {package} is specified, the translation is looked up + for that specific package. You need to specify the path to + look for translations with the |bindtextdomain()| function. Return type: |String|
--- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -1,4 +1,4 @@ -*repeat.txt* For Vim version 9.1. Last change: 2023 May 26 +*repeat.txt* For Vim version 9.1. Last change: 2024 Jun 20 VIM REFERENCE MANUAL by Bram Moolenaar @@ -735,6 +735,10 @@ Your directory layout would be like this start/foobar/autoload/foo.vim " loaded when foo command used start/foobar/doc/foo.txt " help for foo.vim start/foobar/doc/tags " help tags + start/foobar/lang/<lang_id>/LC_MESSAGES/foo.po + " messages for the plugin in the + " <lang_id> language. These files are + " optional. opt/fooextra/plugin/extra.vim " optional plugin, defines commands opt/fooextra/autoload/extra.vim " loaded when extra command used opt/fooextra/doc/extra.txt " help for extra.vim @@ -762,6 +766,35 @@ the command after changing the plugin he :helptags path/start/foobar/doc :helptags path/opt/fooextra/doc +The messages that are in the lang/<lang_id>/LC_MESSAGES/foo.po file need to be +translated to a format that the |gettext()| function understands by running the +msgfmt program. This will result in a lang/<lang_id>/LC_MESSAGES/foo.mo +file. See |multilang| on how to specify languages. + +In your plugin, you need to call the |bindtextdomain()| function as follows. +This assumes that the directory structure is as above: > + :call bindtextdomain("foo", fnamemodify(expand("<script>"), ':p:h') + .. '/../lang/') +< +You only need to do this once. After this call, you can use: > + :echo gettext("Hello", "foo") +< +to get the text "Hello" translated to the user's preferred language (if the +plugin messages have been translated to this language). + +To create the foo.po file, you need to create a foo.pot file first. The +entries in this file need to be translated to the language(s) you want to be +supported by your plugin. + +To create the foo.pot file, run the following command: > + cd ~/.vim/pack/start/foobar + make -f ~/src/vim/src/po/Makefile PACKAGE=foo \ + PO_BASEDIR=~/src/vim/src/po PO_INPUTLIST= \ + PO_VIM_JSLIST="plugin__foo.js plugin__bar.js \ + autoload__foo.js" \ + PO_VIM_INPUTLIST="plugin/foo.vim plugin/bar.vim autoload/foo.vim" \ + foo.pot +< Dependencies between plugins ~ *packload-two-steps*
--- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -6148,6 +6148,7 @@ beval_text-variable eval.txt /*beval_tex beval_winid-variable eval.txt /*beval_winid-variable* beval_winnr-variable eval.txt /*beval_winnr-variable* binary-number eval.txt /*binary-number* +bindtextdomain() builtin.txt /*bindtextdomain()* bitwise-function usr_41.txt /*bitwise-function* bitwise-shift eval.txt /*bitwise-shift* blob eval.txt /*blob* @@ -10598,6 +10599,7 @@ termdebug-mappings terminal.txt /*termde termdebug-prompt terminal.txt /*termdebug-prompt* termdebug-starting terminal.txt /*termdebug-starting* termdebug-stepping terminal.txt /*termdebug-stepping* +termdebug-timeout terminal.txt /*termdebug-timeout* termdebug-variables terminal.txt /*termdebug-variables* termdebug_disasm_window terminal.txt /*termdebug_disasm_window* termdebug_map_K terminal.txt /*termdebug_map_K*
--- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -798,6 +798,7 @@ String manipulation: *string-functio execute() execute an Ex command and get the output win_execute() like execute() but in a specified window trim() trim characters from a string + bindtextdomain() set message lookup translation base path gettext() lookup message translation List manipulation: *list-functions*
--- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -41563,6 +41563,9 @@ Support for the XDG Desktop Specificatio Support highlighting the matched text for insert-mode completion and command-line completion in |ins-completion-menu|. +Support for translating messages in Vim script plugins using the |gettext()| +and |bindtextdomain()| functions. + *changed-9.2* Changed~ ------- @@ -41579,6 +41582,7 @@ Various syntax, indent and other plugins Functions: ~ +|bindtextdomain()| set message lookup translation base path |diff()| diff two Lists of strings |filecopy()| copy a file {from} to {to} |foreach()| apply function to List items
--- a/src/auto/configure +++ b/src/auto/configure @@ -15876,6 +15876,30 @@ then : fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for dgettext" >&5 +printf %s "checking for dgettext... " >&6; } + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <libintl.h> +int +main (void) +{ +dgettext("Test", "Test"); + ; + 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_DGETTEXT 1" >>confdefs.h + +else $as_nop + { 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 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for _nl_msg_cat_cntr" >&5 printf %s "checking for _nl_msg_cat_cntr... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext
--- a/src/config.h.in +++ b/src/config.h.in @@ -222,7 +222,6 @@ #undef HAVE_UNSETENV #undef HAVE_USLEEP #undef HAVE_UTIME -#undef HAVE_BIND_TEXTDOMAIN_CODESET #undef HAVE_MBLEN #undef HAVE_TIMER_CREATE #undef HAVE_CLOCK_GETTIME @@ -424,6 +423,12 @@ /* Define if there is a working gettext(). */ #undef HAVE_GETTEXT +/* Define if there is a working bind_textdomain_codeset(). */ +#undef HAVE_BIND_TEXTDOMAIN_CODESET + +/* Define if there is a working dgettext(). */ +#undef HAVE_DGETTEXT + /* Define if _nl_msg_cat_cntr is present. */ #undef HAVE_NL_MSG_CAT_CNTR
--- a/src/configure.ac +++ b/src/configure.ac @@ -4497,6 +4497,12 @@ if test "$enable_nls" = "yes"; then AC_SUBST(MAKEMO) dnl this was added in GNU gettext 0.10.36 AC_CHECK_FUNCS(bind_textdomain_codeset) + AC_MSG_CHECKING([for dgettext]) + AC_LINK_IFELSE([AC_LANG_PROGRAM( + [#include <libintl.h>], + [dgettext("Test", "Test");])], + AC_MSG_RESULT([yes]); AC_DEFINE(HAVE_DGETTEXT), + AC_MSG_RESULT([no])) dnl _nl_msg_cat_cntr is required for GNU gettext AC_MSG_CHECKING([for _nl_msg_cat_cntr]) AC_LINK_IFELSE([AC_LANG_PROGRAM(
--- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -28,6 +28,7 @@ static void f_balloon_show(typval_T *arg static void f_balloon_split(typval_T *argvars, typval_T *rettv); # endif #endif +static void f_bindtextdomain(typval_T *argvars, typval_T *rettv); static void f_byte2line(typval_T *argvars, typval_T *rettv); static void f_call(typval_T *argvars, typval_T *rettv); static void f_changenr(typval_T *argvars, typval_T *rettv); @@ -1824,6 +1825,8 @@ static funcentry_T global_functions[] = NULL #endif }, + {"bindtextdomain", 2, 2, 0, arg2_string, + ret_void, f_bindtextdomain}, {"blob2list", 1, 1, FEARG_1, arg1_blob, ret_list_number, f_blob2list}, {"browse", 4, 4, 0, arg4_browse, @@ -2154,7 +2157,7 @@ static funcentry_T global_functions[] = ret_any, f_gettabwinvar}, {"gettagstack", 0, 1, FEARG_1, arg1_number, ret_dict_any, f_gettagstack}, - {"gettext", 1, 1, FEARG_1, arg1_string, + {"gettext", 1, 2, FEARG_1, arg2_string, ret_string, f_gettext}, {"getwininfo", 0, 1, FEARG_1, arg1_number, ret_list_dict_any, f_getwininfo}, @@ -3477,6 +3480,24 @@ get_buf_arg(typval_T *arg) } /* + * "bindtextdomain(package, path)" function + */ + static void +f_bindtextdomain(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + if (check_for_nonempty_string_arg(argvars, 0) == FAIL + || check_for_nonempty_string_arg(argvars, 1) == FAIL) + return; + + if (strcmp((const char *)argvars[0].vval.v_string, VIMPACKAGE) == 0) + semsg(_(e_invalid_argument_str), tv_get_string(&argvars[0])); + else + bindtextdomain((const char *)argvars[0].vval.v_string, (const char *)argvars[1].vval.v_string); + + return; +} + +/* * "byte2line(byte)" function */ static void @@ -6033,11 +6054,39 @@ f_gettagstack(typval_T *argvars, typval_ static void f_gettext(typval_T *argvars, typval_T *rettv) { - if (check_for_nonempty_string_arg(argvars, 0) == FAIL) +#if defined(HAVE_BIND_TEXTDOMAIN_CODESET) + char *prev = NULL; +#endif + + if (check_for_nonempty_string_arg(argvars, 0) == FAIL + || check_for_opt_string_arg(argvars, 1) == FAIL) return; rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave((char_u *)_(argvars[0].vval.v_string)); + + if (argvars[1].v_type == VAR_STRING && + argvars[1].vval.v_string != NULL && + *(argvars[1].vval.v_string) != NUL) + { +#if defined(HAVE_BIND_TEXTDOMAIN_CODESET) + prev = bind_textdomain_codeset((const char *)argvars[1].vval.v_string, (char *)p_enc); +#endif + +#if defined(HAVE_DGETTEXT) + rettv->vval.v_string = vim_strsave((char_u *)dgettext((const char *)argvars[1].vval.v_string, (const char *)argvars[0].vval.v_string)); +#else + textdomain((const char *)argvars[1].vval.v_string); + rettv->vval.v_string = vim_strsave((char_u *)_(argvars[0].vval.v_string)); + textdomain(VIMPACKAGE); +#endif + +#if defined(HAVE_BIND_TEXTDOMAIN_CODESET) + if (prev != NULL) + bind_textdomain_codeset((const char *)argvars[1].vval.v_string, prev); +#endif + } + else + rettv->vval.v_string = vim_strsave((char_u *)_(argvars[0].vval.v_string)); } // for VIM_VERSION_ defines
--- a/src/po/Make_all.mak +++ b/src/po/Make_all.mak @@ -189,8 +189,8 @@ PO_VIM_INPUTLIST = \ ../../runtime/defaults.vim PO_VIM_JSLIST = \ - optwin.js \ - defaults.js + ________runtime__optwin.js \ + ________runtime__defaults.js # Arguments for xgettext to pick up messages to translate from the source code. XGETTEXT_KEYWORDS = --keyword=_ --keyword=N_ --keyword=NGETTEXT:1,2 --keyword=PLURAL_MSG:2,4
--- a/src/po/Makefile +++ b/src/po/Makefile @@ -1,17 +1,18 @@ # Makefile for the Vim message translations. +PO_BASEDIR = . # Include stuff found by configure. -include ../auto/config.mk +include $(PO_BASEDIR)/../auto/config.mk # Get LANGUAGES, MOFILES, MOCONVERTED and others. -include Make_all.mak +include $(PO_BASEDIR)/Make_all.mak # Note: ja.sjis, *.cp1250 and zh_CN.cp936 are only for MS-Windows, they are # not installed on Unix. PACKAGE = vim SHELL = /bin/sh -VIM = ../vim +VIM = $(PO_BASEDIR)/../vim # MacOS sed is locale aware, set $LANG to avoid problems. SED = LANG=C sed @@ -261,13 +262,13 @@ PO_INPUTLIST = \ $(PACKAGE).pot: $(PO_INPUTLIST) $(PO_VIM_INPUTLIST) # Convert the Vim scripts to (what looks like) Javascript. - $(VIM) -u NONE --not-a-term -S tojavascript.vim $(PACKAGE).pot $(PO_VIM_INPUTLIST) + $(VIM) -u NONE --not-a-term -S $(PO_BASEDIR)/tojavascript.vim $(PACKAGE).pot $(PO_VIM_INPUTLIST) # Create vim.pot. $(XGETTEXT) --default-domain=$(PACKAGE) --add-comments \ $(XGETTEXT_KEYWORDS) $(PO_INPUTLIST) $(PO_VIM_JSLIST) mv -f $(PACKAGE).po $(PACKAGE).pot # Fix Vim scripts names, so that "gf" works. - $(VIM) -u NONE --not-a-term -S fixfilenames.vim $(PACKAGE).pot $(PO_VIM_INPUTLIST) + $(VIM) -u NONE --not-a-term -S $(PO_BASEDIR)/fixfilenames.vim $(PACKAGE).pot $(PO_VIM_INPUTLIST) # Delete the temporary files. rm *.js
--- a/src/po/fixfilenames.vim +++ b/src/po/fixfilenames.vim @@ -4,7 +4,7 @@ set shortmess+=A for name in argv()[1:] - let jsname = fnamemodify(name, ":t:r") .. ".js" + let jsname = fnamemodify(name, ":r:gs?\\~?_?:gs?\\.?_?:gs?/?__?:gs?\\?__?") .. ".js" exe "%s+" .. jsname .. "+" .. substitute(name, '\\', '/', 'g') .. "+" endfor
--- a/src/po/tojavascript.vim +++ b/src/po/tojavascript.vim @@ -13,7 +13,7 @@ for name in argv()[1:] g/^\s*set .*"/s/.*// " Write as .js file, xgettext recognizes them - exe 'w! ' .. fnamemodify(name, ":t:r") .. ".js" + exe 'w! ' .. fnamemodify(name, ":r:gs?\\~?_?:gs?\\.?_?:gs?/?__?:gs?\\?__?") .. ".js" endfor quit
--- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -161,6 +161,9 @@ NEW_TESTS = \ test_function_lists \ test_ga \ test_getcwd \ + test_gettext \ + test_gettext_cp1251 \ + test_gettext_utf8 \ test_getvar \ test_gf \ test_glob2regpat \ @@ -420,6 +423,9 @@ NEW_TESTS_RES = \ test_functions.res \ test_function_lists.res \ test_getcwd.res \ + test_gettext.res \ + test_gettext_cp1251.res \ + test_gettext_utf8.res \ test_getvar.res \ test_gf.res \ test_gn.res \
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..300eba21376ab530c4680bc735e4509d7ba942c3 GIT binary patch literal 875 zc${rg&ubGw6drA>u!3N3f`{j!wM8f0O<N3`Y?ay+3WkOdy_6!;WSXvSX2Z;EY@nBV z5TzdUAQTT?ym)M@Qneb7UMF}I{9Amxo3uq7_;|m+_h7z1qr<NWz7fO~!~)_xA|UB| zhVT&25f>0Y`}JRlS;TL|(=&uzLNw9eJxj<a+H*sMTts^r?L68EwAez1`@AE4i|u46 zcC3}Pj^=6=bf{SXEC|9HU6-8KVJwI!;s*EvBE=!`Tbw-_L>8fRfd^dkI;EsosVrAq zAS*Q_IVO~Rs+o{ZOkPtdMOuh=404u5$})V&Ynm?A>3uGh9|{-Re)EBhtO{?1Qqz)Z z_;orTH59FeE?DJdTH$R!Zlw!Ma~HC9HcRcBG&=)M&YhmUZcp2`b(bkktC9&7Fda%4 zN=&zT2dYdm6}F+!WLj_V$2yH~cbKU2&5VrZa8xuR*5I_tSrb<dVizirQa%$_X{l5^ zIoQeA)}2sjE^uR~g?*ZD>Zw-1e36IR29t_w?_PD0=1%(J%htK1MNtduzG%3RTl2M5 z4kF0{x){o)azV6`P<hjNNL_D2j5)UFIWRsBv1?!T90zWJ4Q>+8dD%g!;LXHtGGP}S z8@9I65$v%iamK-!>}1|TIhcL3XTJ2F^mbOk9Q3x$C-cE{V|T}V#Na4HO&8M#rfc>y zCn7PQG5G_NN4*#3Fq3|@`G#a)d)t}+m1FsscSyQtj*$4f>7t6vTl3!RnpftHc^Qi- Y*?Wj{2k}9t)O7l|WLGMvf6}O|zwO{5PXGV_
--- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -3865,11 +3865,6 @@ func Test_default_arg_value() call assert_equal('msg', HasDefault()) endfunc -" Test for gettext() -func Test_gettext() - call assert_fails('call gettext(1)', 'E1174:') -endfunc - func Test_builtin_check() call assert_fails('let g:["trim"] = {x -> " " .. x}', 'E704:') call assert_fails('let g:.trim = {x -> " " .. x}', 'E704:')
new file mode 100644 --- /dev/null +++ b/src/testdir/test_gettext.vim @@ -0,0 +1,16 @@ +source check.vim + +" Test for gettext() +func Test_gettext() + call assert_fails('call bindtextdomain("test")', 'E119:') + call assert_fails('call bindtextdomain("vim", "test")', 'E475:') + + call assert_fails('call gettext(1)', 'E1174:') + call assert_equal('xxxTESTxxx', gettext("xxxTESTxxx")) + + call assert_equal('xxxTESTxxx', gettext("xxxTESTxxx", "vim")) + call assert_equal('xxxTESTxxx', gettext("xxxTESTxxx", "__PACKAGE__")) + call assert_equal('ERROR: ', gettext("ERROR: ", "__PACKAGE__")) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab
new file mode 100644 --- /dev/null +++ b/src/testdir/test_gettext_cp1251.vim @@ -0,0 +1,22 @@ +source check.vim + +" Test for gettext() +func Test_gettext() + set encoding=cp1251 + call bindtextdomain("__PACKAGE__", getcwd()) + try + language ru_RU + call assert_equal(': ', gettext("ERROR: ", "__PACKAGE__")) + catch /^Vim\%((\a\+)\)\=:E197:/ + throw "Skipped: not possible to set locale to ru (missing?)" + endtry + try + language en_GB.UTF-8 + call assert_equal('ERROR: ', gettext("ERROR: ", "__PACKAGE__")) + catch /^Vim\%((\a\+)\)\=:E197:/ + throw "Skipped: not possible to set locale to en (missing?)" + endtry + set encoding& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab
new file mode 100644 --- /dev/null +++ b/src/testdir/test_gettext_utf8.vim @@ -0,0 +1,22 @@ +source check.vim + +" Test for gettext() +func Test_gettext() + set encoding=utf-8 + call bindtextdomain("__PACKAGE__", getcwd()) + try + language ru_RU + call assert_equal('ОШИБКА: ', gettext("ERROR: ", "__PACKAGE__")) + catch /^Vim\%((\a\+)\)\=:E197:/ + throw "Skipped: not possible to set locale to ru (missing?)" + endtry + try + language en_GB.UTF-8 + call assert_equal('ERROR: ', gettext("ERROR: ", "__PACKAGE__")) + catch /^Vim\%((\a\+)\)\=:E197:/ + throw "Skipped: not possible to set locale to en (missing?)" + endtry + set encoding& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab