changeset 21437:b32b67a108f2 v8.2.1269

patch 8.2.1269: language and locale code spread out Commit: https://github.com/vim/vim/commit/054f14bbe58fece17f1a74ca63f0b37518f0b4de Author: Bram Moolenaar <Bram@vim.org> Date: Wed Jul 22 19:11:19 2020 +0200 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)
author Bram Moolenaar <Bram@vim.org>
date Wed, 22 Jul 2020 19:15:04 +0200
parents 31a3d49932c1
children c114f045e405
files Filelist src/Make_cyg_ming.mak src/Make_morph.mak src/Make_mvc.mak src/Make_vms.mms src/Makefile src/README.md src/ex_cmds2.c src/locale.c src/main.c src/proto.h src/proto/ex_cmds2.pro src/proto/locale.pro src/version.c
diffstat 14 files changed, 601 insertions(+), 561 deletions(-) [+]
line wrap: on
line diff
--- a/Filelist
+++ b/Filelist
@@ -76,6 +76,7 @@ SRC_ALL =	\
 		src/json_test.c \
 		src/kword_test.c \
 		src/list.c \
+		src/locale.c \
 		src/keymap.h \
 		src/macros.h \
 		src/main.c \
@@ -247,6 +248,7 @@ SRC_ALL =	\
 		src/proto/insexpand.pro \
 		src/proto/json.pro \
 		src/proto/list.pro \
+		src/proto/locale.pro \
 		src/proto/main.pro \
 		src/proto/map.pro \
 		src/proto/mark.pro \
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -751,6 +751,7 @@ OBJ = \
 	$(OUTDIR)/insexpand.o \
 	$(OUTDIR)/json.o \
 	$(OUTDIR)/list.o \
+	$(OUTDIR)/locale.o \
 	$(OUTDIR)/main.o \
 	$(OUTDIR)/map.o \
 	$(OUTDIR)/mark.o \
--- a/src/Make_morph.mak
+++ b/src/Make_morph.mak
@@ -70,6 +70,7 @@ SRC =	arabic.c						\
 	insexpand.c						\
 	json.c							\
 	list.c							\
+	locale.c						\
 	main.c							\
 	map.c							\
 	mark.c							\
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -773,6 +773,7 @@ OBJ = \
 	$(OUTDIR)\insexpand.obj \
 	$(OUTDIR)\json.obj \
 	$(OUTDIR)\list.obj \
+	$(OUTDIR)\locale.obj \
 	$(OUTDIR)\main.obj \
 	$(OUTDIR)\map.obj \
 	$(OUTDIR)\mark.obj \
@@ -1669,6 +1670,8 @@ lib$(MZSCHEME_MAIN_LIB)$(MZSCHEME_VER).l
 
 $(OUTDIR)/list.obj:	$(OUTDIR) list.c  $(INCL)
 
+$(OUTDIR)/locale.obj:	$(OUTDIR) locale.c  $(INCL)
+
 $(OUTDIR)/main.obj:	$(OUTDIR) main.c  $(INCL) $(CUI_INCL)
 
 $(OUTDIR)/map.obj:	$(OUTDIR) map.c  $(INCL)
@@ -1939,6 +1942,7 @@ proto.h: \
 	proto/insexpand.pro \
 	proto/json.pro \
 	proto/list.pro \
+	proto/locale.pro \
 	proto/main.pro \
 	proto/map.pro \
 	proto/mark.pro \
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -345,6 +345,7 @@ SRC = \
 	insexpand.c \
 	json.c \
 	list.c \
+	locale.c \
 	main.c \
 	map.c \
 	mark.c \
@@ -460,6 +461,7 @@ OBJ = \
 	insexpand.obj \
 	json.obj \
 	list.obj \
+	locale.obj \
 	main.obj \
 	map.obj \
 	mark.obj \
@@ -865,6 +867,10 @@ list.obj : list.c vim.h [.auto]config.h 
  ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
  beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h proto.h \
  globals.h
+locale.obj : locale.c vim.h [.auto]config.h feature.h os_unix.h \
+ ascii.h keymap.h term.h macros.h option.h structs.h regexp.h gui.h \
+ beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h proto.h \
+ globals.h
 main.obj : main.c vim.h [.auto]config.h feature.h os_unix.h   \
  ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \
  [.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h \
--- a/src/Makefile
+++ b/src/Makefile
@@ -1647,6 +1647,7 @@ BASIC_SRC = \
 	insexpand.c \
 	json.c \
 	list.c \
+	locale.c \
 	main.c \
 	map.c \
 	mark.c \
@@ -1798,6 +1799,7 @@ OBJ_COMMON = \
 	objects/indent.o \
 	objects/insexpand.o \
 	objects/list.o \
+	objects/locale.o \
 	objects/map.o \
 	objects/mark.o \
 	objects/match.o \
@@ -1973,6 +1975,7 @@ PRO_AUTO = \
 	insexpand.pro \
 	json.pro \
 	list.pro \
+	locale.pro \
 	main.pro \
 	map.pro \
 	mark.pro \
@@ -3378,6 +3381,9 @@ objects/kword_test.o: kword_test.c
 objects/list.o: list.c
 	$(CCC) -o $@ list.c
 
+objects/locale.o: locale.c
+	$(CCC) -o $@ locale.c
+
 objects/main.o: main.c
 	$(CCC) -o $@ main.c
 
@@ -3968,6 +3974,10 @@ objects/list.o: list.c vim.h protodef.h 
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+objects/locale.o: locale.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 objects/main.o: main.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
--- a/src/README.md
+++ b/src/README.md
@@ -52,6 +52,7 @@ help.c		| vim help related functions
 highlight.c	| syntax highlighting
 indent.c	| text indentation
 insexpand.c	| Insert mode completion
+locale.c	| locale/language handling
 map.c		| mapping and abbreviations
 mark.c		| marks
 match.c		| highlight matching
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -996,505 +996,3 @@ ex_checktime(exarg_T *eap)
     }
     no_check_timestamps = save_no_check_timestamps;
 }
-
-#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)
-/*
- * ":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
new file mode 100644
--- /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
--- a/src/main.c
+++ b/src/main.c
@@ -34,9 +34,6 @@
 static int file_owned(char *fname);
 #endif
 static void mainerr(int, char_u *);
-# if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
-static void init_locale(void);
-# endif
 static void early_arg_scan(mparm_T *parmp);
 #ifndef NO_VIM_MAIN
 static void usage(void);
@@ -1716,56 +1713,6 @@ getout(int exitval)
     mch_exit(exitval);
 }
 
-#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
-/*
- * Setup to use the current locale (for ctype() and many other things).
- */
-    static 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
-}
-#endif
-
 /*
  * Get the name of the display, before gui_prepare() removes it from
  * argv[].  Used for the xterm-clipboard display.
--- a/src/proto.h
+++ b/src/proto.h
@@ -101,6 +101,7 @@ extern int _stricoll(char *a, char *b);
 # include "insexpand.pro"
 # include "json.pro"
 # include "list.pro"
+# include "locale.pro"
 # include "blob.pro"
 # include "main.pro"
 # include "map.pro"
--- a/src/proto/ex_cmds2.pro
+++ b/src/proto/ex_cmds2.pro
@@ -15,10 +15,4 @@ void ex_pyxfile(exarg_T *eap);
 void ex_pyx(exarg_T *eap);
 void ex_pyxdo(exarg_T *eap);
 void ex_checktime(exarg_T *eap);
-char_u *get_mess_lang(void);
-void set_lang_var(void);
-void ex_language(exarg_T *eap);
-void free_locales(void);
-char_u *get_lang_arg(expand_T *xp, int idx);
-char_u *get_locales(expand_T *xp, int idx);
 /* vim: set ft=c : */
new file mode 100644
--- /dev/null
+++ b/src/proto/locale.pro
@@ -0,0 +1,9 @@
+/* locale.c */
+char_u *get_mess_lang(void);
+void set_lang_var(void);
+void init_locale(void);
+void ex_language(exarg_T *eap);
+void free_locales(void);
+char_u *get_lang_arg(expand_T *xp, int idx);
+char_u *get_locales(expand_T *xp, int idx);
+/* vim: set ft=c : */
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1269,
+/**/
     1268,
 /**/
     1267,