patch 8.2.1269: language and locale code spread out

Problem:    Language and locale code spread out.
Solution:   Move relevant code to src/locale.c. (Yegappan Lakshmanan,
            closes #6509)
diff --git a/src/locale.c b/src/locale.c
new file mode 100644
index 0000000..7fad9d9
--- /dev/null
+++ b/src/locale.c
@@ -0,0 +1,564 @@
+/* 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)
+    {
+	p = mch_getenv((char_u *)"LC_MESSAGES");
+	if (p == NULL || *p == NUL)
+	{
+	    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(FEAT_FLOAT) && 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(FEAT_FLOAT) && defined(LC_NUMERIC)
+	    // Make sure strtod() uses a decimal point, not a comma.
+	    setlocale(LC_NUMERIC, "C");
+# endif
+	}
+	if (loc == NULL)
+	    semsg(_("E197: Cannot set language to \"%s\""), 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
+# ifdef FEAT_TITLE
+	    maketitle();
+# endif
+	}
+    }
+}
+
+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)
+    {
+	did_init_locales = TRUE;
+	locales = find_locales();
+    }
+}
+
+# if defined(EXITFREE) || defined(PROTO)
+    void
+free_locales(void)
+{
+    int			i;
+    if (locales != NULL)
+    {
+	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