neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

lang.c (9505B)


      1 #if defined(__APPLE__) && !defined(ZIG_BUILD)
      2 # define Boolean CFBoolean  // Avoid conflict with API's Boolean
      3 # define FileInfo CSFileInfo  // Avoid conflict with API's Fileinfo
      4 # include <CoreServices/CoreServices.h>
      5 
      6 # undef Boolean
      7 # undef FileInfo
      8 #endif
      9 
     10 #include <locale.h>
     11 #include <stdbool.h>
     12 #include <stdio.h>
     13 
     14 #include "auto/config.h"
     15 #include "nvim/ascii_defs.h"
     16 #include "nvim/buffer.h"
     17 #include "nvim/charset.h"
     18 #include "nvim/cmdexpand_defs.h"
     19 #include "nvim/eval/vars.h"
     20 #include "nvim/ex_cmds_defs.h"
     21 #include "nvim/garray.h"
     22 #include "nvim/gettext_defs.h"
     23 #include "nvim/globals.h"
     24 #include "nvim/macros_defs.h"
     25 #include "nvim/memory.h"
     26 #include "nvim/message.h"
     27 #include "nvim/option.h"
     28 #include "nvim/os/lang.h"
     29 #include "nvim/os/os.h"
     30 #include "nvim/os/shell.h"
     31 #include "nvim/path.h"
     32 #include "nvim/profile.h"
     33 #include "nvim/vim_defs.h"
     34 
     35 #include "os/lang.c.generated.h"
     36 
     37 static char *get_locale_val(int what)
     38 {
     39  // Obtain the locale value from the libraries.
     40  char *loc = setlocale(what, NULL);
     41 
     42  return loc;
     43 }
     44 
     45 /// @return  true when "lang" starts with a valid language name.
     46 ///          Rejects NULL, empty string, "C", "C.UTF-8" and others.
     47 static bool is_valid_mess_lang(const char *lang)
     48 {
     49  return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]);
     50 }
     51 
     52 /// Obtain the current messages language.  Used to set the default for
     53 /// 'helplang'.  May return NULL or an empty string.
     54 char *get_mess_lang(void)
     55 {
     56  char *p;
     57 
     58 #if defined(LC_MESSAGES)
     59  p = get_locale_val(LC_MESSAGES);
     60 #else
     61  // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG
     62  // may be set to the LCID number.  LC_COLLATE is the best guess, LC_TIME
     63  // and LC_MONETARY may be set differently for a Japanese working in the
     64  // US.
     65  p = get_locale_val(LC_COLLATE);
     66 #endif
     67  return is_valid_mess_lang(p) ? p : NULL;
     68 }
     69 
     70 /// Get the language used for messages from the environment.
     71 /// The function may be using NameBuff.
     72 ///
     73 /// This uses LC_MESSAGES when available, which it is for most systems we build for
     74 /// except for windows. Then fallback to get the value from the environment
     75 /// ourselves, and use LC_CTYPE as a last resort.
     76 static char *get_mess_env(void)
     77 {
     78 #ifdef LC_MESSAGES
     79  return get_locale_val(LC_MESSAGES);
     80 #else
     81  char *p = os_getenv_noalloc("LC_ALL");
     82  if (p != NULL) {
     83    return p;
     84  }
     85 
     86  p = os_getenv_noalloc("LC_MESSAGES");
     87  if (p != NULL) {
     88    return p;
     89  }
     90 
     91  p = os_getenv_noalloc("LANG");
     92  if (p != NULL && ascii_isdigit(*p)) {
     93    p = NULL;  // ignore something like "1043"
     94  }
     95  if (p == NULL) {
     96    p = get_locale_val(LC_CTYPE);
     97  }
     98  return p;
     99 #endif
    100 }
    101 
    102 /// Set the "v:lang" variable according to the current locale setting.
    103 /// Also do "v:lc_time"and "v:ctype".
    104 void set_lang_var(void)
    105 {
    106  const char *loc = get_locale_val(LC_CTYPE);
    107  set_vim_var_string(VV_CTYPE, loc, -1);
    108 
    109  loc = get_mess_env();
    110  set_vim_var_string(VV_LANG, loc, -1);
    111 
    112  loc = get_locale_val(LC_TIME);
    113  set_vim_var_string(VV_LC_TIME, loc, -1);
    114 
    115  loc = get_locale_val(LC_COLLATE);
    116  set_vim_var_string(VV_COLLATE, loc, -1);
    117 }
    118 
    119 /// Setup to use the current locale (for ctype() and many other things).
    120 void init_locale(void)
    121 {
    122  setlocale(LC_ALL, "");
    123 
    124 #ifdef LC_NUMERIC
    125  // Make sure strtod() uses a decimal point, not a comma.
    126  setlocale(LC_NUMERIC, "C");
    127 #endif
    128 
    129  char localepath[MAXPATHL] = { 0 };
    130  snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH));
    131  char *tail = path_tail_with_sep(localepath);
    132  *tail = NUL;
    133  tail = path_tail(localepath);
    134  xstrlcpy(tail, "share/locale",
    135           sizeof(localepath) - (size_t)(tail - localepath));
    136  bindtextdomain(PROJECT_NAME, localepath);
    137  textdomain(PROJECT_NAME);
    138  TIME_MSG("locale set");
    139 }
    140 
    141 /// ":language":  Set the language (locale).
    142 ///
    143 /// @param eap
    144 void ex_language(exarg_T *eap)
    145 {
    146  char *loc;
    147  int what = LC_ALL;
    148  char *whatstr = "";
    149 #ifdef LC_MESSAGES
    150 # define VIM_LC_MESSAGES LC_MESSAGES
    151 #else
    152 # define VIM_LC_MESSAGES 6789
    153 #endif
    154 
    155  char *name = eap->arg;
    156 
    157  // Check for "messages {name}", "ctype {name}" or "time {name}" argument.
    158  // Allow abbreviation, but require at least 3 characters to avoid
    159  // confusion with a two letter language name "me" or "ct".
    160  char *p = skiptowhite(eap->arg);
    161  if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) {
    162    if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) {
    163      what = VIM_LC_MESSAGES;
    164      name = skipwhite(p);
    165      whatstr = "messages ";
    166    } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) {
    167      what = LC_CTYPE;
    168      name = skipwhite(p);
    169      whatstr = "ctype ";
    170    } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) {
    171      what = LC_TIME;
    172      name = skipwhite(p);
    173      whatstr = "time ";
    174    } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) {
    175      what = LC_COLLATE;
    176      name = skipwhite(p);
    177      whatstr = "collate ";
    178    }
    179  }
    180 
    181  if (*name == NUL) {
    182    if (what == VIM_LC_MESSAGES) {
    183      p = get_mess_env();
    184    } else {
    185      p = setlocale(what, NULL);
    186    }
    187    if (p == NULL || *p == NUL) {
    188      p = "Unknown";
    189    }
    190    smsg(0, _("Current %slanguage: \"%s\""), whatstr, p);
    191  } else {
    192 #ifndef LC_MESSAGES
    193    if (what == VIM_LC_MESSAGES) {
    194      loc = "";
    195    } else {
    196 #endif
    197    loc = setlocale(what, name);
    198 #ifdef LC_NUMERIC
    199    // Make sure strtod() uses a decimal point, not a comma.
    200    setlocale(LC_NUMERIC, "C");
    201 #endif
    202 #ifndef LC_MESSAGES
    203  }
    204 #endif
    205    if (loc == NULL) {
    206      semsg(_("E197: Cannot set language to \"%s\""), name);
    207    } else {
    208 #ifdef HAVE_NL_MSG_CAT_CNTR
    209      // Need to do this for GNU gettext, otherwise cached translations
    210      // will be used again.
    211      extern int _nl_msg_cat_cntr;  // NOLINT(bugprone-reserved-identifier)
    212 
    213      _nl_msg_cat_cntr++;
    214 #endif
    215      // Reset $LC_ALL, otherwise it would overrule everything.
    216      os_setenv("LC_ALL", "", 1);
    217 
    218      if (what != LC_TIME && what != LC_COLLATE) {
    219        // Tell gettext() what to translate to.  It apparently doesn't
    220        // use the currently effective locale.
    221        if (what == LC_ALL) {
    222          os_setenv("LANG", name, 1);
    223 
    224          // Clear $LANGUAGE because GNU gettext uses it.
    225          os_setenv("LANGUAGE", "", 1);
    226        }
    227        if (what != LC_CTYPE) {
    228          os_setenv("LC_MESSAGES", name, 1);
    229          set_helplang_default(name);
    230        }
    231      }
    232 
    233      // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
    234      set_lang_var();
    235      maketitle();
    236    }
    237  }
    238 }
    239 
    240 static char **locales = NULL;       // Array of all available locales
    241 
    242 #ifndef MSWIN
    243 static bool did_init_locales = false;
    244 
    245 /// @return  an array of strings for all available locales + NULL for the
    246 ///          last element or,
    247 ///          NULL in case of error.
    248 static char **find_locales(void)
    249 {
    250  garray_T locales_ga;
    251  char *saveptr = NULL;
    252 
    253  // Find all available locales by running command "locale -a".  If this
    254  // doesn't work we won't have completion.
    255  char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL);
    256  if (locale_a == NULL) {
    257    return NULL;
    258  }
    259  ga_init(&locales_ga, sizeof(char *), 20);
    260 
    261  // Transform locale_a string where each locale is separated by "\n"
    262  // into an array of locale strings.
    263  char *loc = os_strtok(locale_a, "\n", &saveptr);
    264 
    265  while (loc != NULL) {
    266    loc = xstrdup(loc);
    267    GA_APPEND(char *, &locales_ga, loc);
    268    loc = os_strtok(NULL, "\n", &saveptr);
    269  }
    270  xfree(locale_a);
    271  // Guarantee that .ga_data is NULL terminated
    272  ga_grow(&locales_ga, 1);
    273  ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL;
    274  return locales_ga.ga_data;
    275 }
    276 #endif
    277 
    278 /// Lazy initialization of all available locales.
    279 static void init_locales(void)
    280 {
    281 #ifndef MSWIN
    282  if (did_init_locales) {
    283    return;
    284  }
    285 
    286  did_init_locales = true;
    287  locales = find_locales();
    288 #endif
    289 }
    290 
    291 #if defined(EXITFREE)
    292 void free_locales(void)
    293 {
    294  if (locales == NULL) {
    295    return;
    296  }
    297 
    298  for (int i = 0; locales[i] != NULL; i++) {
    299    xfree(locales[i]);
    300  }
    301  XFREE_CLEAR(locales);
    302 }
    303 #endif
    304 
    305 /// Function given to ExpandGeneric() to obtain the possible arguments of the
    306 /// ":language" command.
    307 char *get_lang_arg(expand_T *xp, int idx)
    308 {
    309  if (idx == 0) {
    310    return "messages";
    311  }
    312  if (idx == 1) {
    313    return "ctype";
    314  }
    315  if (idx == 2) {
    316    return "time";
    317  }
    318  if (idx == 3) {
    319    return "collate";
    320  }
    321 
    322  init_locales();
    323  if (locales == NULL) {
    324    return NULL;
    325  }
    326  return locales[idx - 4];
    327 }
    328 
    329 /// Function given to ExpandGeneric() to obtain the available locales.
    330 char *get_locales(expand_T *xp, int idx)
    331 {
    332  init_locales();
    333  if (locales == NULL) {
    334    return NULL;
    335  }
    336  return locales[idx];
    337 }
    338 
    339 void lang_init(void)
    340 {
    341 #if defined(__APPLE__) && !defined(ZIG_BUILD)
    342  if (!os_env_exists("LANG", true)) {
    343    char buf[50] = { 0 };
    344 
    345    // $LANG is not set, either because it was unset or Nvim was started
    346    // from the Dock. Query the system locale.
    347    if (LocaleRefGetPartString(NULL,
    348                               kLocaleLanguageMask | kLocaleLanguageVariantMask |
    349                               kLocaleRegionMask | kLocaleRegionVariantMask,
    350                               sizeof(buf) - 10, buf) == noErr && *buf) {
    351      if (strcasestr(buf, "utf-8") == NULL) {
    352        xstrlcat(buf, ".UTF-8", sizeof(buf));
    353      }
    354      os_setenv("LANG", buf, true);
    355      setlocale(LC_ALL, "");
    356      // Make sure strtod() uses a decimal point, not a comma.
    357      setlocale(LC_NUMERIC, "C");
    358    } else {
    359      ELOG("$LANG is empty and the macOS primary language cannot be inferred.");
    360    }
    361  }
    362 #endif
    363 }