vim/src/locale.c
Yegappan Lakshmanan e857598896 patch 9.0.1196: code is indented more than necessary
Problem:    Code is indented more than necessary.
Solution:   Use an early return where it makes sense. (Yegappan Lakshmanan,
            closes #11813)
2023-01-14 12:32:28 +00:00

563 lines
13 KiB
C

/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* locale.c: functions for language/locale configuration
*/
#include "vim.h"
#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \
&& (defined(FEAT_EVAL) || defined(FEAT_MULTI_LANG))
# define HAVE_GET_LOCALE_VAL
static char_u *
get_locale_val(int what)
{
char_u *loc;
// Obtain the locale value from the libraries.
loc = (char_u *)setlocale(what, NULL);
# ifdef MSWIN
if (loc != NULL)
{
char_u *p;
// setocale() returns something like "LC_COLLATE=<name>;LC_..." when
// one of the values (e.g., LC_CTYPE) differs.
p = vim_strchr(loc, '=');
if (p != NULL)
{
loc = ++p;
while (*p != NUL) // remove trailing newline
{
if (*p < ' ' || *p == ';')
{
*p = NUL;
break;
}
++p;
}
}
}
# endif
return loc;
}
#endif
#ifdef MSWIN
/*
* On MS-Windows locale names are strings like "German_Germany.1252", but
* gettext expects "de". Try to translate one into another here for a few
* supported languages.
*/
static char_u *
gettext_lang(char_u *name)
{
int i;
static char *(mtable[]) = {
"afrikaans", "af",
"czech", "cs",
"dutch", "nl",
"german", "de",
"english_united kingdom", "en_GB",
"spanish", "es",
"french", "fr",
"italian", "it",
"japanese", "ja",
"korean", "ko",
"norwegian", "no",
"polish", "pl",
"russian", "ru",
"slovak", "sk",
"swedish", "sv",
"ukrainian", "uk",
"chinese_china", "zh_CN",
"chinese_taiwan", "zh_TW",
NULL};
for (i = 0; mtable[i] != NULL; i += 2)
if (STRNICMP(mtable[i], name, STRLEN(mtable[i])) == 0)
return (char_u *)mtable[i + 1];
return name;
}
#endif
#if defined(FEAT_MULTI_LANG) || defined(PROTO)
/*
* Return TRUE when "lang" starts with a valid language name.
* Rejects NULL, empty string, "C", "C.UTF-8" and others.
*/
static int
is_valid_mess_lang(char_u *lang)
{
return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]);
}
/*
* Obtain the current messages language. Used to set the default for
* 'helplang'. May return NULL or an empty string.
*/
char_u *
get_mess_lang(void)
{
char_u *p;
# ifdef HAVE_GET_LOCALE_VAL
# if defined(LC_MESSAGES)
p = get_locale_val(LC_MESSAGES);
# else
// This is necessary for Win32, where LC_MESSAGES is not defined and $LANG
// may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME
// and LC_MONETARY may be set differently for a Japanese working in the
// US.
p = get_locale_val(LC_COLLATE);
# endif
# else
p = mch_getenv((char_u *)"LC_ALL");
if (!is_valid_mess_lang(p))
{
p = mch_getenv((char_u *)"LC_MESSAGES");
if (!is_valid_mess_lang(p))
p = mch_getenv((char_u *)"LANG");
}
# endif
# ifdef MSWIN
p = gettext_lang(p);
# endif
return is_valid_mess_lang(p) ? p : NULL;
}
#endif
// Complicated #if; matches with where get_mess_env() is used below.
#if (defined(FEAT_EVAL) && !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \
&& defined(LC_MESSAGES))) \
|| ((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \
&& !defined(LC_MESSAGES))
/*
* Get the language used for messages from the environment.
*/
static char_u *
get_mess_env(void)
{
char_u *p;
p = mch_getenv((char_u *)"LC_ALL");
if (p != NULL && *p != NUL)
return p;
p = mch_getenv((char_u *)"LC_MESSAGES");
if (p != NULL && *p != NUL)
return p;
p = mch_getenv((char_u *)"LANG");
if (p != NULL && VIM_ISDIGIT(*p))
p = NULL; // ignore something like "1043"
# ifdef HAVE_GET_LOCALE_VAL
if (p == NULL || *p == NUL)
p = get_locale_val(LC_CTYPE);
# endif
return p;
}
#endif
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Set the "v:lang" variable according to the current locale setting.
* Also do "v:lc_time"and "v:ctype".
*/
void
set_lang_var(void)
{
char_u *loc;
# ifdef HAVE_GET_LOCALE_VAL
loc = get_locale_val(LC_CTYPE);
# else
// setlocale() not supported: use the default value
loc = (char_u *)"C";
# endif
set_vim_var_string(VV_CTYPE, loc, -1);
// When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall
// back to LC_CTYPE if it's empty.
# if defined(HAVE_GET_LOCALE_VAL) && defined(LC_MESSAGES)
loc = get_locale_val(LC_MESSAGES);
# else
loc = get_mess_env();
# endif
set_vim_var_string(VV_LANG, loc, -1);
# ifdef HAVE_GET_LOCALE_VAL
loc = get_locale_val(LC_TIME);
# endif
set_vim_var_string(VV_LC_TIME, loc, -1);
# ifdef HAVE_GET_LOCALE_VAL
loc = get_locale_val(LC_COLLATE);
# else
// setlocale() not supported: use the default value
loc = (char_u *)"C";
# endif
set_vim_var_string(VV_COLLATE, loc, -1);
}
#endif
#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
/*
* Setup to use the current locale (for ctype() and many other things).
*/
void
init_locale(void)
{
setlocale(LC_ALL, "");
# ifdef FEAT_GUI_GTK
// Tell Gtk not to change our locale settings.
gtk_disable_setlocale();
# endif
# if defined(LC_NUMERIC)
// Make sure strtod() uses a decimal point, not a comma.
setlocale(LC_NUMERIC, "C");
# endif
# ifdef MSWIN
// Apparently MS-Windows printf() may cause a crash when we give it 8-bit
// text while it's expecting text in the current locale. This call avoids
// that.
setlocale(LC_CTYPE, "C");
# endif
# ifdef FEAT_GETTEXT
{
int mustfree = FALSE;
char_u *p;
# ifdef DYNAMIC_GETTEXT
// Initialize the gettext library
dyn_libintl_init();
# endif
// expand_env() doesn't work yet, because g_chartab[] is not
// initialized yet, call vim_getenv() directly
p = vim_getenv((char_u *)"VIMRUNTIME", &mustfree);
if (p != NULL && *p != NUL)
{
vim_snprintf((char *)NameBuff, MAXPATHL, "%s/lang", p);
bindtextdomain(VIMPACKAGE, (char *)NameBuff);
}
if (mustfree)
vim_free(p);
textdomain(VIMPACKAGE);
}
# endif
}
/*
* ":language": Set the language (locale).
*/
void
ex_language(exarg_T *eap)
{
char *loc;
char_u *p;
char_u *name;
int what = LC_ALL;
char *whatstr = "";
# ifdef LC_MESSAGES
# define VIM_LC_MESSAGES LC_MESSAGES
# else
# define VIM_LC_MESSAGES 6789
# endif
name = eap->arg;
// Check for "messages {name}", "ctype {name}" or "time {name}" argument.
// Allow abbreviation, but require at least 3 characters to avoid
// confusion with a two letter language name "me" or "ct".
p = skiptowhite(eap->arg);
if ((*p == NUL || VIM_ISWHITE(*p)) && p - eap->arg >= 3)
{
if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0)
{
what = VIM_LC_MESSAGES;
name = skipwhite(p);
whatstr = "messages ";
}
else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0)
{
what = LC_CTYPE;
name = skipwhite(p);
whatstr = "ctype ";
}
else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0)
{
what = LC_TIME;
name = skipwhite(p);
whatstr = "time ";
}
else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0)
{
what = LC_COLLATE;
name = skipwhite(p);
whatstr = "collate ";
}
}
if (*name == NUL)
{
# ifndef LC_MESSAGES
if (what == VIM_LC_MESSAGES)
p = get_mess_env();
else
# endif
p = (char_u *)setlocale(what, NULL);
if (p == NULL || *p == NUL)
p = (char_u *)"Unknown";
smsg(_("Current %slanguage: \"%s\""), whatstr, p);
}
else
{
# ifndef LC_MESSAGES
if (what == VIM_LC_MESSAGES)
loc = "";
else
# endif
{
loc = setlocale(what, (char *)name);
# if defined(LC_NUMERIC)
// Make sure strtod() uses a decimal point, not a comma.
setlocale(LC_NUMERIC, "C");
# endif
}
if (loc == NULL)
semsg(_(e_cannot_set_language_to_str), name);
else
{
# ifdef HAVE_NL_MSG_CAT_CNTR
// Need to do this for GNU gettext, otherwise cached translations
// will be used again.
extern int _nl_msg_cat_cntr;
++_nl_msg_cat_cntr;
# endif
// Reset $LC_ALL, otherwise it would overrule everything.
vim_setenv((char_u *)"LC_ALL", (char_u *)"");
if (what != LC_TIME && what != LC_COLLATE)
{
// Tell gettext() what to translate to. It apparently doesn't
// use the currently effective locale. Also do this when
// FEAT_GETTEXT isn't defined, so that shell commands use this
// value.
if (what == LC_ALL)
{
vim_setenv((char_u *)"LANG", name);
// Clear $LANGUAGE because GNU gettext uses it.
vim_setenv((char_u *)"LANGUAGE", (char_u *)"");
# ifdef MSWIN
// Apparently MS-Windows printf() may cause a crash when
// we give it 8-bit text while it's expecting text in the
// current locale. This call avoids that.
setlocale(LC_CTYPE, "C");
# endif
}
if (what != LC_CTYPE)
{
char_u *mname;
# ifdef MSWIN
mname = gettext_lang(name);
# else
mname = name;
# endif
vim_setenv((char_u *)"LC_MESSAGES", mname);
# ifdef FEAT_MULTI_LANG
set_helplang_default(mname);
# endif
}
}
# ifdef FEAT_EVAL
// Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
set_lang_var();
# endif
maketitle();
}
}
}
static char_u **locales = NULL; // Array of all available locales
static int did_init_locales = FALSE;
/*
* Return an array of strings for all available locales + NULL for the
* last element. Return NULL in case of error.
*/
static char_u **
find_locales(void)
{
garray_T locales_ga;
char_u *loc;
char_u *locale_list;
# ifdef MSWIN
size_t len = 0;
# endif
// Find all available locales by running command "locale -a". If this
// doesn't work we won't have completion.
# ifndef MSWIN
locale_list = get_cmd_output((char_u *)"locale -a",
NULL, SHELL_SILENT, NULL);
# else
// Find all available locales by examining the directories in
// $VIMRUNTIME/lang/
{
int options = WILD_SILENT|WILD_USE_NL|WILD_KEEP_ALL;
expand_T xpc;
char_u *p;
ExpandInit(&xpc);
xpc.xp_context = EXPAND_DIRECTORIES;
locale_list = ExpandOne(&xpc, (char_u *)"$VIMRUNTIME/lang/*",
NULL, options, WILD_ALL);
ExpandCleanup(&xpc);
if (locale_list == NULL)
// Add a dummy input, that will be skipped lated but we need to
// have something in locale_list so that the C locale is added at
// the end.
locale_list = vim_strsave((char_u *)".\n");
p = locale_list;
// find the last directory delimiter
while (p != NULL && *p != NUL)
{
if (*p == '\n')
break;
if (*p == '\\')
len = p - locale_list;
p++;
}
}
# endif
if (locale_list == NULL)
return NULL;
ga_init2(&locales_ga, sizeof(char_u *), 20);
// Transform locale_list string where each locale is separated by "\n"
// into an array of locale strings.
loc = (char_u *)strtok((char *)locale_list, "\n");
while (loc != NULL)
{
int ignore = FALSE;
# ifdef MSWIN
if (len > 0)
loc += len + 1;
// skip locales with a dot (which indicates the charset)
if (vim_strchr(loc, '.') != NULL)
ignore = TRUE;
# endif
if (!ignore)
{
if (ga_grow(&locales_ga, 1) == FAIL)
break;
loc = vim_strsave(loc);
if (loc == NULL)
break;
((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = loc;
}
loc = (char_u *)strtok(NULL, "\n");
}
# ifdef MSWIN
// Add the C locale
if (ga_grow(&locales_ga, 1) == OK)
((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] =
vim_strsave((char_u *)"C");
# endif
vim_free(locale_list);
if (ga_grow(&locales_ga, 1) == FAIL)
{
ga_clear(&locales_ga);
return NULL;
}
((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL;
return (char_u **)locales_ga.ga_data;
}
/*
* Lazy initialization of all available locales.
*/
static void
init_locales(void)
{
if (did_init_locales)
return;
did_init_locales = TRUE;
locales = find_locales();
}
# if defined(EXITFREE) || defined(PROTO)
void
free_locales(void)
{
int i;
if (locales == NULL)
return;
for (i = 0; locales[i] != NULL; i++)
vim_free(locales[i]);
VIM_CLEAR(locales);
}
# endif
/*
* Function given to ExpandGeneric() to obtain the possible arguments of the
* ":language" command.
*/
char_u *
get_lang_arg(expand_T *xp UNUSED, int idx)
{
if (idx == 0)
return (char_u *)"messages";
if (idx == 1)
return (char_u *)"ctype";
if (idx == 2)
return (char_u *)"time";
if (idx == 3)
return (char_u *)"collate";
init_locales();
if (locales == NULL)
return NULL;
return locales[idx - 4];
}
/*
* Function given to ExpandGeneric() to obtain the available locales.
*/
char_u *
get_locales(expand_T *xp UNUSED, int idx)
{
init_locales();
if (locales == NULL)
return NULL;
return locales[idx];
}
#endif