From: Bruno Haible Date: Sun, 19 Apr 2020 22:46:44 +0000 (+0200) Subject: intl: Support any Unicode characters in the locale dir on native Windows. X-Git-Tag: v0.21~89 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2e397fbb43fa0c6008897b50ccd2b9ffcd0a0cb5;p=thirdparty%2Fgettext.git intl: Support any Unicode characters in the locale dir on native Windows. Reported at . * gettext-runtime/intl/libgnuintl.in.h (libintl_wbindtextdomain): New declaration. (wbindtextdomain): New redirect. * gettext-runtime/intl/bindtextdom.c (set_binding_values): Accept a wdirnamep argument. Set not only binding->dirname but also binding->wdirname. (BINDTEXTDOMAIN, BIND_TEXTDOMAIN_CODESET): Pass NULL as wdirnamep. (libintl_wbindtextdomain): New function. * gettext-runtime/intl/loadinfo.h (struct loaded_l10nfile): Add a wfilename field. (_nl_make_l10nflist): On native Windows, accept wdirlist, wdirlist_len parameters. * gettext-runtime/intl/loadmsgcat.c (_nl_load_domain): On native Windows, use _wopen() instead of open() to open a file with a name given as wchar_t[]. * gettext-runtime/intl/l10nflist.c: Include . (_nl_make_l10nflist): On native Windows, accept wdirlist, wdirlist_len parameters. Construct abs_wfilename and use in the search for existing 'struct loaded_l10nfile' and when allocating a fresh 'struct loaded_l10nfile'. * gettext-runtime/intl/gettextP.h (struct binding): Add a wdirname field. (_nl_find_domain): On native Windows, accept a wdirname parameter. * gettext-runtime/intl/finddomain.c (_nl_find_domain): On native Windows, accept a wdirname parameter. Pass it to _nl_make_l10nflist. * gettext-runtime/intl/dcigettext.c (DCIGETTEXT): Consider not only binding->dirname but also binding->wdirname. On native Windows, use _wgetcwd instead of getcwd. Pass also wdirname to _nl_find_domain. * gettext-tools/tests/intl-6: New file, based on gettext-tools/tests/intl-1. * gettext-tools/tests/intl-6-prg.c: New file, based on gettext-tools/tests/intl-1-prg.c. * gettext-tools/tests/Makefile.am (TESTS): Add intl-6. (check_PROGRAMS): Add intl-6-prg. (intl_6_prg_SOURCES, intl_6_prg_LDADD): New variables. * gettext-tools/doc/gettext.texi (Ambiguities, src/Makefile, Language Implementors, C): Document wbindtextdomain. * gettext-runtime/NEWS: Mention the change. * NEWS: Likewise. --- diff --git a/NEWS b/NEWS index 81838e0a3..443a7c1a6 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,13 @@ Version 0.21 - April 2020 - JavaScript: xgettext parses JSX expressions more reliably. +* Runtime behaviour: + - On native Windows platforms, the directory that contains the message + catalogs may now contain arbitrary Unicode characters. To make use of + this feature, use the new function 'wbindtextdomain' instead of + 'bindtextdomain'. It allows to pass a directory name in wchar_t[] encoding. + Note: 'wbindtextdomain' exists only on native Windows platforms. + * Libtextstyle: - Added support for emitting hyperlinks. - New API for doing formatted output. diff --git a/gettext-runtime/NEWS b/gettext-runtime/NEWS index b98f49d52..d8b162d61 100644 --- a/gettext-runtime/NEWS +++ b/gettext-runtime/NEWS @@ -1,4 +1,12 @@ -Version 0.21 - December 2019 +Version 0.21 - April 2020 + +* On native Windows platforms, the directory that contains the message + catalogs may now contain arbitrary Unicode characters. To make use of + this feature, use the new function 'wbindtextdomain' instead of + 'bindtextdomain'. It allows to pass a directory name in wchar_t[] encoding. + Note: 'wbindtextdomain' exists only on native Windows platforms. + +Version 0.20.2 - April 2020 * The interpretation of the language preferences on macOS has been improved, especially in the case where a system locale does not exist for the diff --git a/gettext-runtime/intl/bindtextdom.c b/gettext-runtime/intl/bindtextdom.c index ec25a7000..57cf1f486 100644 --- a/gettext-runtime/intl/bindtextdom.c +++ b/gettext-runtime/intl/bindtextdom.c @@ -1,5 +1,5 @@ /* Implementation of the bindtextdomain(3) function - Copyright (C) 1995-2016 Free Software Foundation, Inc. + Copyright (C) 1995-2020 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -65,15 +65,18 @@ gl_rwlock_define (extern, _nl_state_lock attribute_hidden) # define BIND_TEXTDOMAIN_CODESET libintl_bind_textdomain_codeset #endif -/* Specifies the directory name *DIRNAMEP and the output codeset *CODESETP - to be used for the DOMAINNAME message catalog. - If *DIRNAMEP or *CODESETP is NULL, the corresponding attribute is not - modified, only the current value is returned. - If DIRNAMEP or CODESETP is NULL, the corresponding attribute is neither - modified nor returned. */ +/* Specifies the directory name *DIRNAMEP, the directory name *WDIRNAMEP + (only on native Windows), and the output codeset *CODESETP to be used + for the DOMAINNAME message catalog. + If *DIRNAMEP or *WDIRNAMEP or *CODESETP is NULL, the corresponding attribute + is not modified, only the current value is returned. + If DIRNAMEP or WDIRNAMEP or CODESETP is NULL, the corresponding attribute is + neither modified nor returned, except that setting WDIRNAME erases DIRNAME + and vice versa. */ static void set_binding_values (const char *domainname, - const char **dirnamep, const char **codesetp) + const char **dirnamep, const wchar_t **wdirnamep, + const char **codesetp) { struct binding *binding; int modified; @@ -83,6 +86,10 @@ set_binding_values (const char *domainname, { if (dirnamep) *dirnamep = NULL; +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirnamep) + *wdirnamep = NULL; +#endif if (codesetp) *codesetp = NULL; return; @@ -121,7 +128,7 @@ set_binding_values (const char *domainname, one are equal we simply do nothing. Otherwise replace the old binding. */ char *result = binding->dirname; - if (strcmp (dirname, result) != 0) + if (result == NULL || strcmp (dirname, result) != 0) { if (strcmp (dirname, _nl_default_dirname) == 0) result = (char *) _nl_default_dirname; @@ -141,8 +148,13 @@ set_binding_values (const char *domainname, { if (binding->dirname != _nl_default_dirname) free (binding->dirname); - binding->dirname = result; + +#if defined _WIN32 && !defined __CYGWIN__ + free (binding->wdirname); + binding->wdirname = NULL; +#endif + modified = 1; } } @@ -150,6 +162,41 @@ set_binding_values (const char *domainname, } } +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirnamep) + { + const wchar_t *wdirname = *wdirnamep; + + if (wdirname == NULL) + /* The current binding has be to returned. */ + *wdirnamep = binding->wdirname; + else + { + /* The domain is already bound. If the new value and the old + one are equal we simply do nothing. Otherwise replace the + old binding. */ + wchar_t *result = binding->wdirname; + if (result == NULL || wcscmp (wdirname, result) != 0) + { + result = _wcsdup (wdirname); + + if (__builtin_expect (result != NULL, 1)) + { + if (binding->dirname != _nl_default_dirname) + free (binding->dirname); + binding->dirname = NULL; + + free (binding->wdirname); + binding->wdirname = result; + + modified = 1; + } + } + *wdirnamep = result; + } + } +#endif + if (codesetp) { const char *codeset = *codesetp; @@ -187,11 +234,18 @@ set_binding_values (const char *domainname, } } else if ((dirnamep == NULL || *dirnamep == NULL) +#if defined _WIN32 && !defined __CYGWIN__ + && (wdirnamep == NULL || *wdirnamep == NULL) +#endif && (codesetp == NULL || *codesetp == NULL)) { /* Simply return the default values. */ if (dirnamep) *dirnamep = _nl_default_dirname; +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirnamep) + *wdirnamep = NULL; +#endif if (codesetp) *codesetp = NULL; } @@ -212,8 +266,15 @@ set_binding_values (const char *domainname, const char *dirname = *dirnamep; if (dirname == NULL) - /* The default value. */ - dirname = _nl_default_dirname; + { +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirnamep && *wdirnamep != NULL) + dirname = NULL; + else +#endif + /* The default value. */ + dirname = _nl_default_dirname; + } else { if (strcmp (dirname, _nl_default_dirname) == 0) @@ -239,8 +300,34 @@ set_binding_values (const char *domainname, new_binding->dirname = (char *) dirname; } else - /* The default value. */ - new_binding->dirname = (char *) _nl_default_dirname; + { +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirnamep && *wdirnamep != NULL) + new_binding->dirname = NULL; + else +#endif + /* The default value. */ + new_binding->dirname = (char *) _nl_default_dirname; + } + +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirnamep) + { + const wchar_t *wdirname = *wdirnamep; + + if (wdirname != NULL) + { + wchar_t *result = _wcsdup (wdirname); + if (__builtin_expect (result == NULL, 0)) + goto failed_wdirname; + wdirname = result; + } + *wdirnamep = wdirname; + new_binding->wdirname = (wchar_t *) wdirname; + } + else + new_binding->wdirname = NULL; +#endif if (codesetp) { @@ -293,6 +380,10 @@ set_binding_values (const char *domainname, if (0) { failed_codeset: +#if defined _WIN32 && !defined __CYGWIN__ + free (new_binding->wdirname); + failed_wdirname: +#endif if (new_binding->dirname != _nl_default_dirname) free (new_binding->dirname); failed_dirname: @@ -300,6 +391,10 @@ set_binding_values (const char *domainname, failed: if (dirnamep) *dirnamep = NULL; +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirnamep) + *wdirnamep = NULL; +#endif if (codesetp) *codesetp = NULL; } @@ -346,19 +441,30 @@ BINDTEXTDOMAIN (const char *domainname, const char *dirname) } } #endif - set_binding_values (domainname, &dirname, NULL); + set_binding_values (domainname, &dirname, NULL, NULL); #ifdef __EMX__ dirname = saved_dirname; #endif return (char *) dirname; } +#if defined _WIN32 && !defined __CYGWIN__ +/* Specify that the DOMAINNAME message catalog will be found + in WDIRNAME rather than in the system locale data base. */ +wchar_t * +libintl_wbindtextdomain (const char *domainname, const wchar_t *wdirname) +{ + set_binding_values (domainname, NULL, &wdirname, NULL); + return (wchar_t *) wdirname; +} +#endif + /* Specify the character encoding in which the messages from the DOMAINNAME message catalog will be returned. */ char * BIND_TEXTDOMAIN_CODESET (const char *domainname, const char *codeset) { - set_binding_values (domainname, NULL, &codeset); + set_binding_values (domainname, NULL, NULL, &codeset); return (char *) codeset; } diff --git a/gettext-runtime/intl/dcigettext.c b/gettext-runtime/intl/dcigettext.c index d0b574a25..0e9710bcf 100644 --- a/gettext-runtime/intl/dcigettext.c +++ b/gettext-runtime/intl/dcigettext.c @@ -480,6 +480,9 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, const char *categoryname; const char *categoryvalue; const char *dirname; +#if defined _WIN32 && !defined __CYGWIN__ + const wchar_t *wdirname; +#endif char *xdomainname; char *single_locale; char *retval; @@ -585,6 +588,9 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, and _nl_load_domain and _nl_find_domain just pass it through. */ binding = NULL; dirname = bindtextdomain (domainname, NULL); +# if defined _WIN32 && !defined __CYGWIN__ + wdirname = wbindtextdomain (domainname, NULL); +# endif #else for (binding = _nl_domain_bindings; binding != NULL; binding = binding->next) { @@ -601,11 +607,79 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, } if (binding == NULL) - dirname = _nl_default_dirname; + { + dirname = _nl_default_dirname; +# if defined _WIN32 && !defined __CYGWIN__ + wdirname = NULL; +# endif + } else { dirname = binding->dirname; +# if defined _WIN32 && !defined __CYGWIN__ + wdirname = binding->wdirname; +# endif #endif +#if defined _WIN32 && !defined __CYGWIN__ + if (wdirname != NULL + ? IS_RELATIVE_FILE_NAME (wdirname) + : IS_RELATIVE_FILE_NAME (dirname)) + { + /* We have a relative path. Make it absolute now. */ + size_t wdirname_len; + size_t path_max; + wchar_t *resolved_wdirname; + wchar_t *ret; + wchar_t *p; + + if (wdirname != NULL) + wdirname_len = wcslen (wdirname); + else + { + wdirname_len = mbstowcs (NULL, dirname, 0); + + if (wdirname_len == (size_t)(-1)) + /* dirname contains invalid multibyte characters. Don't signal + an error but simply return the default string. */ + goto return_untranslated; + } + wdirname_len++; + + path_max = (unsigned int) PATH_MAX; + path_max += 2; /* The getcwd docs say to do this. */ + + for (;;) + { + resolved_wdirname = + (wchar_t *) + alloca ((path_max + wdirname_len) * sizeof (wchar_t)); + ADD_BLOCK (block_list, resolved_wdirname); + + __set_errno (0); + ret = _wgetcwd (resolved_wdirname, path_max); + if (ret != NULL || errno != ERANGE) + break; + + path_max += path_max / 2; + path_max += PATH_INCR; + } + + if (ret == NULL) + /* We cannot get the current working directory. Don't signal an + error but simply return the default string. */ + goto return_untranslated; + + p = wcschr (resolved_wdirname, L'\0'); + *p++ = L'/'; + if (wdirname != NULL) + wcscpy (p, wdirname); + else + mbstowcs (p, dirname, wdirname_len); + + wdirname = resolved_wdirname; + dirname = NULL; + } +#else if (IS_RELATIVE_FILE_NAME (dirname)) { /* We have a relative path. Make it absolute now. */ @@ -639,6 +713,7 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, stpcpy (stpcpy (strchr (resolved_dirname, '\0'), "/"), dirname); dirname = resolved_dirname; } +#endif #ifndef IN_LIBGLOCALE } #endif @@ -705,7 +780,11 @@ DCIGETTEXT (const char *domainname, const char *msgid1, const char *msgid2, /* Find structure describing the message catalog matching the DOMAINNAME and CATEGORY. */ - domain = _nl_find_domain (dirname, single_locale, xdomainname, binding); + domain = _nl_find_domain (dirname, +#if defined _WIN32 && !defined __CYGWIN__ + wdirname, +#endif + single_locale, xdomainname, binding); if (domain != NULL) { diff --git a/gettext-runtime/intl/finddomain.c b/gettext-runtime/intl/finddomain.c index 4e28ca040..830558d59 100644 --- a/gettext-runtime/intl/finddomain.c +++ b/gettext-runtime/intl/finddomain.c @@ -55,11 +55,15 @@ gl_rwlock_define_initialized (static, lock); /* Return a data structure describing the message catalog described by - the DIRNAME, LOCALE, and DOMAINNAME parameters with respect to the - currently established bindings. */ + the DIRNAME or WDIRNAME, LOCALE, and DOMAINNAME parameters with respect + to the currently established bindings. */ struct loaded_l10nfile * internal_function -_nl_find_domain (const char *dirname, char *locale, +_nl_find_domain (const char *dirname, +#if defined _WIN32 && !defined __CYGWIN__ + const wchar_t *wdirname, +#endif + char *locale, const char *domainname, struct binding *domainbinding) { struct loaded_l10nfile *retval; @@ -90,9 +94,15 @@ _nl_find_domain (const char *dirname, char *locale, /* If we have already tested for this locale entry there has to be one data set in the list of loaded domains. */ - retval = _nl_make_l10nflist (&_nl_loaded_domains, dirname, - strlen (dirname) + 1, 0, locale, NULL, NULL, - NULL, NULL, domainname, 0); + retval = _nl_make_l10nflist (&_nl_loaded_domains, + dirname, + dirname != NULL ? strlen (dirname) + 1 : 0, +#if defined _WIN32 && !defined __CYGWIN__ + wdirname, + wdirname != NULL ? wcslen (wdirname) + 1 : 0, +#endif + 0, locale, NULL, NULL, NULL, NULL, + domainname, 0); gl_rwlock_unlock (lock); @@ -153,8 +163,14 @@ _nl_find_domain (const char *dirname, char *locale, /* Create all possible locale entries which might be interested in generalization. */ - retval = _nl_make_l10nflist (&_nl_loaded_domains, dirname, - strlen (dirname) + 1, mask, language, territory, + retval = _nl_make_l10nflist (&_nl_loaded_domains, + dirname, + dirname != NULL ? strlen (dirname) + 1 : 0, +#if defined _WIN32 && !defined __CYGWIN__ + wdirname, + wdirname != NULL ? wcslen (wdirname) + 1 : 0, +#endif + mask, language, territory, codeset, normalized_codeset, modifier, domainname, 1); diff --git a/gettext-runtime/intl/gettextP.h b/gettext-runtime/intl/gettextP.h index 0d7dbd6d4..1efeb192c 100644 --- a/gettext-runtime/intl/gettextP.h +++ b/gettext-runtime/intl/gettextP.h @@ -1,5 +1,5 @@ /* Header describing internals of libintl library. - Copyright (C) 1995-2016 Free Software Foundation, Inc. + Copyright (C) 1995-2020 Free Software Foundation, Inc. Written by Ulrich Drepper , 1995. This program is free software: you can redistribute it and/or modify @@ -212,6 +212,9 @@ struct binding { struct binding *next; char *dirname; +#if defined _WIN32 && !defined __CYGWIN__ + wchar_t *wdirname; +#endif char *codeset; char domainname[ZERO]; }; @@ -256,7 +259,11 @@ extern const char *_nl_locale_name_default (void); const char *categoryname); */ #endif -struct loaded_l10nfile *_nl_find_domain (const char *__dirname, char *__locale, +struct loaded_l10nfile *_nl_find_domain (const char *__dirname, +#if defined _WIN32 && !defined __CYGWIN__ + const wchar_t *__wdirname, +#endif + char *__locale, const char *__domainname, struct binding *__domainbinding) internal_function; diff --git a/gettext-runtime/intl/l10nflist.c b/gettext-runtime/intl/l10nflist.c index 5add7ad80..d97060628 100644 --- a/gettext-runtime/intl/l10nflist.c +++ b/gettext-runtime/intl/l10nflist.c @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2016, 2018, 2020 Free Software Foundation, Inc. +/* Copyright (C) 1995-2020 Free Software Foundation, Inc. Contributed by Ulrich Drepper , 1995. This program is free software: you can redistribute it and/or modify @@ -33,6 +33,9 @@ #include #include #include +#if defined _WIN32 && !defined __CYGWIN__ +# include +#endif #include "loadinfo.h" @@ -86,23 +89,33 @@ pop (int x) struct loaded_l10nfile * _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list, const char *dirlist, size_t dirlist_len, +#if defined _WIN32 && !defined __CYGWIN__ + const wchar_t *wdirlist, size_t wdirlist_len, +#endif int mask, const char *language, const char *territory, const char *codeset, const char *normalized_codeset, const char *modifier, const char *filename, int do_allocate) { char *abs_filename; +#if defined _WIN32 && !defined __CYGWIN__ + wchar_t *abs_wfilename; +#endif struct loaded_l10nfile **lastp; struct loaded_l10nfile *retval; - char *cp; size_t dirlist_count; size_t entries; int cnt; /* If LANGUAGE contains an absolute directory specification, we ignore - DIRLIST. */ + DIRLIST and WDIRLIST. */ if (!IS_RELATIVE_FILE_NAME (language)) - dirlist_len = 0; + { + dirlist_len = 0; +#if defined _WIN32 && !defined __CYGWIN__ + wdirlist_len = 0; +#endif + } /* Allocate room for the full file name. */ abs_filename = (char *) malloc (dirlist_len @@ -121,50 +134,105 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list, return NULL; /* Construct file name. */ - cp = abs_filename; - if (dirlist_len > 0) - { - memcpy (cp, dirlist, dirlist_len); + { + char *cp; + + cp = abs_filename; + if (dirlist_len > 0) + { + memcpy (cp, dirlist, dirlist_len); #ifdef _LIBC - __argz_stringify (cp, dirlist_len, PATH_SEPARATOR); + __argz_stringify (cp, dirlist_len, PATH_SEPARATOR); #endif - cp += dirlist_len; - cp[-1] = '/'; - } + cp += dirlist_len; + cp[-1] = '/'; + } - cp = stpcpy (cp, language); + cp = stpcpy (cp, language); - if ((mask & XPG_TERRITORY) != 0) - { - *cp++ = '_'; - cp = stpcpy (cp, territory); - } - if ((mask & XPG_CODESET) != 0) - { - *cp++ = '.'; - cp = stpcpy (cp, codeset); - } - if ((mask & XPG_NORM_CODESET) != 0) - { - *cp++ = '.'; - cp = stpcpy (cp, normalized_codeset); - } - if ((mask & XPG_MODIFIER) != 0) + if ((mask & XPG_TERRITORY) != 0) + { + *cp++ = '_'; + cp = stpcpy (cp, territory); + } + if ((mask & XPG_CODESET) != 0) + { + *cp++ = '.'; + cp = stpcpy (cp, codeset); + } + if ((mask & XPG_NORM_CODESET) != 0) + { + *cp++ = '.'; + cp = stpcpy (cp, normalized_codeset); + } + if ((mask & XPG_MODIFIER) != 0) + { + *cp++ = '@'; + cp = stpcpy (cp, modifier); + } + + *cp++ = '/'; + stpcpy (cp, filename); + } + +#if defined _WIN32 && !defined __CYGWIN__ + /* Construct wide-char file name. */ + if (wdirlist_len > 0) { - *cp++ = '@'; - cp = stpcpy (cp, modifier); - } + /* Since dirlist_len == 0, just concatenate wdirlist and abs_filename. */ + /* An upper bound for wcslen (mbstowcs (abs_filename)). */ + size_t abs_filename_bound = mbstowcs (NULL, abs_filename, 0); + if (abs_filename_bound == (size_t)-1) + { + free (abs_filename); + return NULL; + } + + /* Allocate and fill abs_wfilename. */ + abs_wfilename = + (wchar_t *) + malloc ((wdirlist_len + abs_filename_bound + 1) * sizeof (wchar_t)); + if (abs_wfilename == NULL) + { + free (abs_filename); + return NULL; + } + wmemcpy (abs_wfilename, wdirlist, wdirlist_len - 1); + abs_wfilename[wdirlist_len - 1] = L'/'; + if (mbstowcs (abs_wfilename + wdirlist_len, abs_filename, + abs_filename_bound + 1) + > abs_filename_bound) + { + free (abs_filename); + free (abs_wfilename); + return NULL; + } - *cp++ = '/'; - stpcpy (cp, filename); + free (abs_filename); + abs_filename = NULL; + } + else + abs_wfilename = NULL; +#endif /* Look in list of already loaded domains whether it is already available. */ lastp = l10nfile_list; for (retval = *l10nfile_list; retval != NULL; retval = retval->next) - if (retval->filename != NULL) + if (retval->filename != NULL +#if defined _WIN32 && !defined __CYGWIN__ + || retval->wfilename != NULL +#endif + ) { - int compare = strcmp (retval->filename, abs_filename); + int compare = +#if defined _WIN32 && !defined __CYGWIN__ + abs_wfilename != NULL + ? retval->wfilename != NULL && wcscmp (retval->wfilename, abs_wfilename) + : retval->filename != NULL && strcmp (retval->filename, abs_filename); +#else + strcmp (retval->filename, abs_filename); +#endif if (compare == 0) /* We found it! */ break; @@ -181,6 +249,9 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list, if (retval != NULL || do_allocate == 0) { free (abs_filename); +#if defined _WIN32 && !defined __CYGWIN__ + free (abs_wfilename); +#endif return retval; } @@ -199,10 +270,16 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list, if (retval == NULL) { free (abs_filename); +#if defined _WIN32 && !defined __CYGWIN__ + free (abs_wfilename); +#endif return NULL; } retval->filename = abs_filename; +#if defined _WIN32 && !defined __CYGWIN__ + retval->wfilename = abs_wfilename; +#endif /* We set retval->data to NULL here; it is filled in later. Setting retval->decided to 1 here means that retval does not @@ -250,7 +327,11 @@ _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list, else #endif retval->successor[entries++] - = _nl_make_l10nflist (l10nfile_list, dirlist, dirlist_len, + = _nl_make_l10nflist (l10nfile_list, + dirlist, dirlist_len, +#if defined _WIN32 && !defined __CYGWIN__ + wdirlist, wdirlist_len, +#endif cnt, language, territory, codeset, normalized_codeset, modifier, filename, 1); } diff --git a/gettext-runtime/intl/libgnuintl.in.h b/gettext-runtime/intl/libgnuintl.in.h index cfefb32cb..cad4bcb78 100644 --- a/gettext-runtime/intl/libgnuintl.in.h +++ b/gettext-runtime/intl/libgnuintl.in.h @@ -292,6 +292,27 @@ extern char *bindtextdomain (const char *__domainname, const char *__dirname) _INTL_ASM (libintl_bindtextdomain); #endif +#if defined _WIN32 && !defined __CYGWIN__ +/* Specify that the DOMAINNAME message catalog will be found + in WDIRNAME rather than in the system locale data base. */ +#ifdef _INTL_REDIRECT_INLINE +extern wchar_t *libintl_wbindtextdomain (const char *__domainname, + const wchar_t *__wdirname); +static inline wchar_t *wbindtextdomain (const char *__domainname, + const wchar_t *__wdirname) +{ + return libintl_wbindtextdomain (__domainname, __wdirname); +} +#else +#ifdef _INTL_REDIRECT_MACROS +# define wbindtextdomain libintl_wbindtextdomain +#endif +extern wchar_t *wbindtextdomain (const char *__domainname, + const wchar_t *__wdirname) + _INTL_ASM (libintl_wbindtextdomain); +#endif +#endif + /* Specify the character encoding in which the messages from the DOMAINNAME message catalog will be returned. */ #ifdef _INTL_REDIRECT_INLINE diff --git a/gettext-runtime/intl/loadinfo.h b/gettext-runtime/intl/loadinfo.h index 3d61652c4..c0a31d23b 100644 --- a/gettext-runtime/intl/loadinfo.h +++ b/gettext-runtime/intl/loadinfo.h @@ -63,6 +63,9 @@ struct loaded_l10nfile { const char *filename; +#if defined _WIN32 && !defined __CYGWIN__ + const wchar_t *wfilename; +#endif int decided; const void *data; @@ -82,13 +85,22 @@ extern const char *_nl_normalize_codeset (const char *codeset, /* Lookup a locale dependent file. *L10NFILE_LIST denotes a pool of lookup results of locale dependent files of the same kind, sorted in decreasing order of ->filename. + DIRLIST and DIRLIST_LEN are an argz list of directories in which to - look, containing at least one directory (i.e. DIRLIST_LEN > 0). + look. + Likewise, on native Windows, WDIRLIST and WDIRLIST_LEN are wide-char + list of directories in which to look. Only one of DIRLIST, WDIRLIST + is non-NULL. + DIRLIST and WDIRLIST contain at least one directory, i.e. + DIRLIST_LEN + WDIRLIST_LEN > 0. Outside glibc, only one directory is used, i.e. - DIRLIST_LEN == strlen (DIRLIST) + 1. + DIRLIST_LEN == strlen (DIRLIST) + 1, WDIRLIST_LEN == 0, or + DIRLIST_LEN == 0, WDIRLIST_LEN == wcslen (WDIRLIST) + 1. + MASK, LANGUAGE, TERRITORY, CODESET, NORMALIZED_CODESET, MODIFIER are the pieces of the locale name, as produced by _nl_explode_name(). FILENAME is the filename suffix. + The return value is the lookup result, either found in *L10NFILE_LIST, or - if DO_ALLOCATE is nonzero - freshly allocated, or possibly NULL. If the return value is non-NULL, it is added to *L10NFILE_LIST, and @@ -97,7 +109,11 @@ extern const char *_nl_normalize_codeset (const char *codeset, results from which this lookup result inherits. */ extern struct loaded_l10nfile * _nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list, - const char *dirlist, size_t dirlist_len, int mask, + const char *dirlist, size_t dirlist_len, +#if defined _WIN32 && !defined __CYGWIN__ + const wchar_t *wdirlist, size_t wdirlist_len, +#endif + int mask, const char *language, const char *territory, const char *codeset, const char *normalized_codeset, const char *modifier, diff --git a/gettext-runtime/intl/loadmsgcat.c b/gettext-runtime/intl/loadmsgcat.c index ea514c101..78c40ca1a 100644 --- a/gettext-runtime/intl/loadmsgcat.c +++ b/gettext-runtime/intl/loadmsgcat.c @@ -1,5 +1,5 @@ /* Load needed message catalogs. - Copyright (C) 1995-2019 Free Software Foundation, Inc. + Copyright (C) 1995-2020 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -836,12 +836,23 @@ _nl_load_domain (struct loaded_l10nfile *domain_file, might be NULL. This can happen when according to the given specification the locale file name is different for XPG and CEN syntax. */ - if (domain_file->filename == NULL) - goto out; - - /* Try to open the addressed file. */ - fd = open (domain_file->filename, O_RDONLY | O_BINARY); - if (fd == -1) + if (domain_file->filename != NULL) + { + /* Try to open the addressed file. */ + fd = open (domain_file->filename, O_RDONLY | O_BINARY); + if (fd == -1) + goto out; + } +#if defined _WIN32 && !defined __CYGWIN__ + else if (domain_file->wfilename != NULL) + { + /* Try to open the addressed file. */ + fd = _wopen (domain_file->wfilename, O_RDONLY | O_BINARY); + if (fd == -1) + goto out; + } +#endif + else goto out; /* We must know about the size of the file. */ diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index 6479786f5..2f9037241 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -5894,6 +5894,15 @@ achieved when the program executes a @code{chdir} command. Relative paths should always be avoided to avoid dependencies and unreliabilities. +@example +wchar_t *wbindtextdomain (const char *domain_name, + const wchar_t *dir_name); +@end example + +This function is provided only on native Windows platforms. It is like +@code{bindtextdomain}, except that the @var{dir_name} parameter is a +wide string (in UTF-16 encoding, as usual on Windows). + @node Locating Catalogs @subsection Locating Message Catalog Files @cindex message catalog files location @@ -8295,6 +8304,9 @@ bindtextdomain (@var{PACKAGE}, LOCALEDIR); textdomain (@var{PACKAGE}); @end example +On native Windows platforms, the @code{main} function may call +@code{wbindtextdomain} instead of @code{bindtextdomain}. + To make LOCALEDIR known to the program, add the following lines to @file{Makefile.in}: @@ -8971,6 +8983,7 @@ making the @code{textdomain} function available from within the language, or by introducing a magic variable called @code{TEXTDOMAIN}. Similarly, you should allow the programmer to designate where to search for message catalogs, by providing access to the @code{bindtextdomain} +function or --- on native Windows platforms --- to the @code{wbindtextdomain} function. @item @@ -9545,7 +9558,7 @@ For C: @code{c}, @code{h}. @code{textdomain} function @item bindtextdomain -@code{bindtextdomain} function +@code{bindtextdomain} and @code{wbindtextdomain} functions @item setlocale Programmer must call @code{setlocale (LC_ALL, "")} diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index b2bd779e4..055b92c74 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -1,5 +1,5 @@ ## Makefile for the gettext-tools/tests subdirectory of GNU gettext -## Copyright (C) 1995-1997, 2001-2010, 2012-2016, 2018-2019 Free Software Foundation, Inc. +## Copyright (C) 1995-1997, 2001-2010, 2012-2016, 2018-2020 Free Software Foundation, Inc. ## ## 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 @@ -21,7 +21,7 @@ EXTRA_DIST = MOSTLYCLEANFILES = core *.stackdump TESTS = gettext-1 gettext-2 \ - intl-1 intl-2 intl-3 intl-4 intl-5 \ + intl-1 intl-2 intl-3 intl-4 intl-5 intl-6 \ intl-setlocale-1 intl-setlocale-2 \ intl-thread-1 intl-thread-2 intl-thread-3 \ intl-version \ @@ -234,7 +234,7 @@ DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ LDADD = $(LDADD_@USE_INCLUDED_LIBINTL@) @INTL_MACOSX_LIBS@ LDADD_yes = ../intl/libintl.la @LTLIBTHREAD@ LDADD_no = ../intl/libgnuintl.la @LTLIBTHREAD@ @LTLIBINTL@ -check_PROGRAMS = tstgettext tstngettext testlocale intl-1-prg intl-3-prg intl-4-prg intl-5-prg intl-setlocale-1-prg intl-setlocale-2-prg intl-thread-1-prg intl-thread-2-prg intl-thread-3-prg intl-version-prg cake fc3 fc4 fc5 gettextpo-1-prg sentence-1-prg +check_PROGRAMS = tstgettext tstngettext testlocale intl-1-prg intl-3-prg intl-4-prg intl-5-prg intl-6-prg intl-setlocale-1-prg intl-setlocale-2-prg intl-thread-1-prg intl-thread-2-prg intl-thread-3-prg intl-version-prg cake fc3 fc4 fc5 gettextpo-1-prg sentence-1-prg tstgettext_SOURCES = \ tstgettext.c ../../gettext-runtime/src/escapes.h \ setlocale.c @@ -252,6 +252,8 @@ intl_4_prg_SOURCES = intl-4-prg.c intl_4_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD) intl_5_prg_SOURCES = intl-5-prg.c intl_5_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD) +intl_6_prg_SOURCES = intl-6-prg.c +intl_6_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD) intl_setlocale_1_prg_SOURCES = intl-setlocale-1-prg.c intl_setlocale_1_prg_LDADD = ../gnulib-lib/libgettextlib.la $(LDADD) intl_setlocale_2_prg_SOURCES = intl-setlocale-2-prg.c diff --git a/gettext-tools/tests/intl-6 b/gettext-tools/tests/intl-6 new file mode 100755 index 000000000..0e91569f8 --- /dev/null +++ b/gettext-tools/tests/intl-6 @@ -0,0 +1,42 @@ +#! /bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test that gettext() does basic translation lookup, even when the directory +# with the message catalogs contains arbitrary Unicode characters. + +test -d in-6 || mkdir in-6 +test -d in-6/fr || mkdir in-6/fr +test -d in-6/fr/LC_MESSAGES || mkdir in-6/fr/LC_MESSAGES + +: ${MSGFMT=msgfmt} +${MSGFMT} -o in-6/fr/LC_MESSAGES/tstprog.mo "$wabs_srcdir"/intl-1.po + +: ${DIFF=diff} +cat < in-6.ok +fromage +EOF + +: ${LOCALE_FR=fr_FR} +: ${LOCALE_FR_UTF8=fr_FR.UTF-8} +if test $LOCALE_FR != none; then + prepare_locale_ in-6/fr in-6/$LOCALE_FR + ../intl-6-prg in-6 $LOCALE_FR > in-6.tmp || Exit 1 + LC_ALL=C tr -d '\r' < in-6.tmp > in-6.out || Exit 1 + ${DIFF} in-6.ok in-6.out || Exit 1 +fi +if test $LOCALE_FR_UTF8 != none; then + prepare_locale_ in-6/fr in-6/$LOCALE_FR_UTF8 + ../intl-6-prg in-6 $LOCALE_FR_UTF8 > in-6.tmp || Exit 1 + LC_ALL=C tr -d '\r' < in-6.tmp > in-6.out || Exit 1 + ${DIFF} in-6.ok in-6.out || Exit 1 +fi +if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then + if test -f /usr/bin/localedef; then + echo "Skipping test: no french locale is installed" + else + echo "Skipping test: no french locale is supported" + fi + Exit 77 +fi + +Exit 0 diff --git a/gettext-tools/tests/intl-6-prg.c b/gettext-tools/tests/intl-6-prg.c new file mode 100644 index 000000000..f2f9a65dc --- /dev/null +++ b/gettext-tools/tests/intl-6-prg.c @@ -0,0 +1,98 @@ +/* Test program, used by the intl-6 test. + Copyright (C) 2000, 2005, 2007, 2013, 2018, 2020 Free Software Foundation, Inc. + + 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#if defined _WIN32 && !defined __CYGWIN__ +# include +#endif + +#include "xsetenv.h" +/* Make sure we use the included libintl, not the system's one. */ +#undef _LIBINTL_H +#include "libgnuintl.h" + +const char unicodedir[] = "русский…日本語…हिंदी…😷"; +#if defined _WIN32 && !defined __CYGWIN__ +const wchar_t wunicodedir[] = /* the same string in UTF-16 encoding */ + { 0x0440, 0x0443, 0x0441, 0x0441, 0x043A, 0x0438, 0x0439, 0x2026, + 0x65E5, 0x672C, 0x8A9E, 0x2026, + 0x0939, 0x093F, 0x0902, 0x0926, 0x0940, 0x2026, + 0xD83D, 0xDE37, 0 + }; +#endif + +int +main (int argc, char *argv[]) +{ + const char *dir = argv[1]; + const char *locale = argv[2]; + wchar_t *wdir; + int ret; + + wdir = (wchar_t *) malloc ((strlen (dir) + 1) * sizeof (wchar_t)); + mbstowcs (wdir, dir, strlen (dir) + 1); + + /* Rename the directory. */ +#if defined _WIN32 && !defined __CYGWIN__ + ret = _wrename (wdir, wunicodedir); +#else + ret = rename (dir, unicodedir); +#endif + if (ret != 0) + { + fprintf (stderr, "Initial rename failed.\n"); + exit (1); + } + + /* Clean up environment. */ + unsetenv ("LANGUAGE"); + unsetenv ("OUTPUT_CHARSET"); + + textdomain ("tstprog"); + + xsetenv ("LC_ALL", locale, 1); + if (setlocale (LC_ALL, "") == NULL) + setlocale (LC_ALL, "C"); + +#if defined _WIN32 && !defined __CYGWIN__ + wbindtextdomain ("tstprog", wunicodedir); +#else + bindtextdomain ("tstprog", unicodedir); +#endif + + printf ("%s\n", gettext ("cheese")); + + /* Rename the directory back. */ +#if defined _WIN32 && !defined __CYGWIN__ + ret = _wrename (wunicodedir, wdir); +#else + ret = rename (unicodedir, dir); +#endif + if (ret != 0) + { + fprintf (stderr, "Final rename failed.\n"); + exit (1); + } + + return 0; +}