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 }