diff options
Diffstat (limited to 'src/locale.c')
-rw-r--r-- | src/locale.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/src/locale.c b/src/locale.c new file mode 100644 index 0000000000..7fad9d9fe5 --- /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 |