Bram Moolenaar | 054f14b | 2020-07-22 19:11:19 +0200 | [diff] [blame] | 1 | /* vi:set ts=8 sts=4 sw=4 noet: |
| 2 | * |
| 3 | * VIM - Vi IMproved by Bram Moolenaar |
| 4 | * |
| 5 | * Do ":help uganda" in Vim to read copying and usage conditions. |
| 6 | * Do ":help credits" in Vim to see a list of people who contributed. |
| 7 | * See README.txt for an overview of the Vim source code. |
| 8 | */ |
| 9 | |
| 10 | /* |
| 11 | * locale.c: functions for language/locale configuration |
| 12 | */ |
| 13 | |
| 14 | #include "vim.h" |
| 15 | |
| 16 | #if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ |
| 17 | && (defined(FEAT_EVAL) || defined(FEAT_MULTI_LANG)) |
| 18 | # define HAVE_GET_LOCALE_VAL |
| 19 | static char_u * |
| 20 | get_locale_val(int what) |
| 21 | { |
| 22 | char_u *loc; |
| 23 | |
| 24 | // Obtain the locale value from the libraries. |
| 25 | loc = (char_u *)setlocale(what, NULL); |
| 26 | |
| 27 | # ifdef MSWIN |
| 28 | if (loc != NULL) |
| 29 | { |
| 30 | char_u *p; |
| 31 | |
| 32 | // setocale() returns something like "LC_COLLATE=<name>;LC_..." when |
| 33 | // one of the values (e.g., LC_CTYPE) differs. |
| 34 | p = vim_strchr(loc, '='); |
| 35 | if (p != NULL) |
| 36 | { |
| 37 | loc = ++p; |
| 38 | while (*p != NUL) // remove trailing newline |
| 39 | { |
| 40 | if (*p < ' ' || *p == ';') |
| 41 | { |
| 42 | *p = NUL; |
| 43 | break; |
| 44 | } |
| 45 | ++p; |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | # endif |
| 50 | |
| 51 | return loc; |
| 52 | } |
| 53 | #endif |
| 54 | |
| 55 | |
| 56 | #ifdef MSWIN |
| 57 | /* |
| 58 | * On MS-Windows locale names are strings like "German_Germany.1252", but |
| 59 | * gettext expects "de". Try to translate one into another here for a few |
| 60 | * supported languages. |
| 61 | */ |
| 62 | static char_u * |
| 63 | gettext_lang(char_u *name) |
| 64 | { |
| 65 | int i; |
| 66 | static char *(mtable[]) = { |
| 67 | "afrikaans", "af", |
| 68 | "czech", "cs", |
| 69 | "dutch", "nl", |
| 70 | "german", "de", |
| 71 | "english_united kingdom", "en_GB", |
| 72 | "spanish", "es", |
| 73 | "french", "fr", |
| 74 | "italian", "it", |
| 75 | "japanese", "ja", |
| 76 | "korean", "ko", |
| 77 | "norwegian", "no", |
| 78 | "polish", "pl", |
| 79 | "russian", "ru", |
| 80 | "slovak", "sk", |
| 81 | "swedish", "sv", |
| 82 | "ukrainian", "uk", |
| 83 | "chinese_china", "zh_CN", |
| 84 | "chinese_taiwan", "zh_TW", |
| 85 | NULL}; |
| 86 | |
| 87 | for (i = 0; mtable[i] != NULL; i += 2) |
| 88 | if (STRNICMP(mtable[i], name, STRLEN(mtable[i])) == 0) |
| 89 | return (char_u *)mtable[i + 1]; |
| 90 | return name; |
| 91 | } |
| 92 | #endif |
| 93 | |
| 94 | #if defined(FEAT_MULTI_LANG) || defined(PROTO) |
| 95 | /* |
| 96 | * Return TRUE when "lang" starts with a valid language name. |
| 97 | * Rejects NULL, empty string, "C", "C.UTF-8" and others. |
| 98 | */ |
| 99 | static int |
| 100 | is_valid_mess_lang(char_u *lang) |
| 101 | { |
| 102 | return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); |
| 103 | } |
| 104 | |
| 105 | /* |
| 106 | * Obtain the current messages language. Used to set the default for |
| 107 | * 'helplang'. May return NULL or an empty string. |
| 108 | */ |
| 109 | char_u * |
| 110 | get_mess_lang(void) |
| 111 | { |
| 112 | char_u *p; |
| 113 | |
| 114 | # ifdef HAVE_GET_LOCALE_VAL |
| 115 | # if defined(LC_MESSAGES) |
| 116 | p = get_locale_val(LC_MESSAGES); |
| 117 | # else |
| 118 | // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG |
| 119 | // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME |
| 120 | // and LC_MONETARY may be set differently for a Japanese working in the |
| 121 | // US. |
| 122 | p = get_locale_val(LC_COLLATE); |
| 123 | # endif |
| 124 | # else |
| 125 | p = mch_getenv((char_u *)"LC_ALL"); |
| 126 | if (!is_valid_mess_lang(p)) |
| 127 | { |
| 128 | p = mch_getenv((char_u *)"LC_MESSAGES"); |
| 129 | if (!is_valid_mess_lang(p)) |
| 130 | p = mch_getenv((char_u *)"LANG"); |
| 131 | } |
| 132 | # endif |
| 133 | # ifdef MSWIN |
| 134 | p = gettext_lang(p); |
| 135 | # endif |
| 136 | return is_valid_mess_lang(p) ? p : NULL; |
| 137 | } |
| 138 | #endif |
| 139 | |
| 140 | // Complicated #if; matches with where get_mess_env() is used below. |
| 141 | #if (defined(FEAT_EVAL) && !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ |
| 142 | && defined(LC_MESSAGES))) \ |
| 143 | || ((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ |
| 144 | && !defined(LC_MESSAGES)) |
| 145 | /* |
| 146 | * Get the language used for messages from the environment. |
| 147 | */ |
| 148 | static char_u * |
| 149 | get_mess_env(void) |
| 150 | { |
| 151 | char_u *p; |
| 152 | |
| 153 | p = mch_getenv((char_u *)"LC_ALL"); |
| 154 | if (p == NULL || *p == NUL) |
| 155 | { |
| 156 | p = mch_getenv((char_u *)"LC_MESSAGES"); |
| 157 | if (p == NULL || *p == NUL) |
| 158 | { |
| 159 | p = mch_getenv((char_u *)"LANG"); |
| 160 | if (p != NULL && VIM_ISDIGIT(*p)) |
| 161 | p = NULL; // ignore something like "1043" |
| 162 | # ifdef HAVE_GET_LOCALE_VAL |
| 163 | if (p == NULL || *p == NUL) |
| 164 | p = get_locale_val(LC_CTYPE); |
| 165 | # endif |
| 166 | } |
| 167 | } |
| 168 | return p; |
| 169 | } |
| 170 | #endif |
| 171 | |
| 172 | #if defined(FEAT_EVAL) || defined(PROTO) |
| 173 | |
| 174 | /* |
| 175 | * Set the "v:lang" variable according to the current locale setting. |
| 176 | * Also do "v:lc_time"and "v:ctype". |
| 177 | */ |
| 178 | void |
| 179 | set_lang_var(void) |
| 180 | { |
| 181 | char_u *loc; |
| 182 | |
| 183 | # ifdef HAVE_GET_LOCALE_VAL |
| 184 | loc = get_locale_val(LC_CTYPE); |
| 185 | # else |
| 186 | // setlocale() not supported: use the default value |
| 187 | loc = (char_u *)"C"; |
| 188 | # endif |
| 189 | set_vim_var_string(VV_CTYPE, loc, -1); |
| 190 | |
| 191 | // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall |
| 192 | // back to LC_CTYPE if it's empty. |
| 193 | # if defined(HAVE_GET_LOCALE_VAL) && defined(LC_MESSAGES) |
| 194 | loc = get_locale_val(LC_MESSAGES); |
| 195 | # else |
| 196 | loc = get_mess_env(); |
| 197 | # endif |
| 198 | set_vim_var_string(VV_LANG, loc, -1); |
| 199 | |
| 200 | # ifdef HAVE_GET_LOCALE_VAL |
| 201 | loc = get_locale_val(LC_TIME); |
| 202 | # endif |
| 203 | set_vim_var_string(VV_LC_TIME, loc, -1); |
| 204 | |
| 205 | # ifdef HAVE_GET_LOCALE_VAL |
| 206 | loc = get_locale_val(LC_COLLATE); |
| 207 | # else |
| 208 | // setlocale() not supported: use the default value |
| 209 | loc = (char_u *)"C"; |
| 210 | # endif |
| 211 | set_vim_var_string(VV_COLLATE, loc, -1); |
| 212 | } |
| 213 | #endif |
| 214 | |
| 215 | #if defined(HAVE_LOCALE_H) || defined(X_LOCALE) |
| 216 | /* |
| 217 | * Setup to use the current locale (for ctype() and many other things). |
| 218 | */ |
| 219 | void |
| 220 | init_locale(void) |
| 221 | { |
| 222 | setlocale(LC_ALL, ""); |
| 223 | |
| 224 | # ifdef FEAT_GUI_GTK |
| 225 | // Tell Gtk not to change our locale settings. |
| 226 | gtk_disable_setlocale(); |
| 227 | # endif |
| 228 | # if defined(FEAT_FLOAT) && defined(LC_NUMERIC) |
| 229 | // Make sure strtod() uses a decimal point, not a comma. |
| 230 | setlocale(LC_NUMERIC, "C"); |
| 231 | # endif |
| 232 | |
| 233 | # ifdef MSWIN |
| 234 | // Apparently MS-Windows printf() may cause a crash when we give it 8-bit |
| 235 | // text while it's expecting text in the current locale. This call avoids |
| 236 | // that. |
| 237 | setlocale(LC_CTYPE, "C"); |
| 238 | # endif |
| 239 | |
| 240 | # ifdef FEAT_GETTEXT |
| 241 | { |
| 242 | int mustfree = FALSE; |
| 243 | char_u *p; |
| 244 | |
| 245 | # ifdef DYNAMIC_GETTEXT |
| 246 | // Initialize the gettext library |
| 247 | dyn_libintl_init(); |
| 248 | # endif |
| 249 | // expand_env() doesn't work yet, because g_chartab[] is not |
| 250 | // initialized yet, call vim_getenv() directly |
| 251 | p = vim_getenv((char_u *)"VIMRUNTIME", &mustfree); |
| 252 | if (p != NULL && *p != NUL) |
| 253 | { |
| 254 | vim_snprintf((char *)NameBuff, MAXPATHL, "%s/lang", p); |
| 255 | bindtextdomain(VIMPACKAGE, (char *)NameBuff); |
| 256 | } |
| 257 | if (mustfree) |
| 258 | vim_free(p); |
| 259 | textdomain(VIMPACKAGE); |
| 260 | } |
| 261 | # endif |
| 262 | } |
| 263 | |
| 264 | /* |
| 265 | * ":language": Set the language (locale). |
| 266 | */ |
| 267 | void |
| 268 | ex_language(exarg_T *eap) |
| 269 | { |
| 270 | char *loc; |
| 271 | char_u *p; |
| 272 | char_u *name; |
| 273 | int what = LC_ALL; |
| 274 | char *whatstr = ""; |
| 275 | # ifdef LC_MESSAGES |
| 276 | # define VIM_LC_MESSAGES LC_MESSAGES |
| 277 | # else |
| 278 | # define VIM_LC_MESSAGES 6789 |
| 279 | # endif |
| 280 | |
| 281 | name = eap->arg; |
| 282 | |
| 283 | // Check for "messages {name}", "ctype {name}" or "time {name}" argument. |
| 284 | // Allow abbreviation, but require at least 3 characters to avoid |
| 285 | // confusion with a two letter language name "me" or "ct". |
| 286 | p = skiptowhite(eap->arg); |
| 287 | if ((*p == NUL || VIM_ISWHITE(*p)) && p - eap->arg >= 3) |
| 288 | { |
| 289 | if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) |
| 290 | { |
| 291 | what = VIM_LC_MESSAGES; |
| 292 | name = skipwhite(p); |
| 293 | whatstr = "messages "; |
| 294 | } |
| 295 | else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) |
| 296 | { |
| 297 | what = LC_CTYPE; |
| 298 | name = skipwhite(p); |
| 299 | whatstr = "ctype "; |
| 300 | } |
| 301 | else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) |
| 302 | { |
| 303 | what = LC_TIME; |
| 304 | name = skipwhite(p); |
| 305 | whatstr = "time "; |
| 306 | } |
| 307 | else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) |
| 308 | { |
| 309 | what = LC_COLLATE; |
| 310 | name = skipwhite(p); |
| 311 | whatstr = "collate "; |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | if (*name == NUL) |
| 316 | { |
| 317 | # ifndef LC_MESSAGES |
| 318 | if (what == VIM_LC_MESSAGES) |
| 319 | p = get_mess_env(); |
| 320 | else |
| 321 | # endif |
| 322 | p = (char_u *)setlocale(what, NULL); |
| 323 | if (p == NULL || *p == NUL) |
| 324 | p = (char_u *)"Unknown"; |
| 325 | smsg(_("Current %slanguage: \"%s\""), whatstr, p); |
| 326 | } |
| 327 | else |
| 328 | { |
| 329 | # ifndef LC_MESSAGES |
| 330 | if (what == VIM_LC_MESSAGES) |
| 331 | loc = ""; |
| 332 | else |
| 333 | # endif |
| 334 | { |
| 335 | loc = setlocale(what, (char *)name); |
| 336 | # if defined(FEAT_FLOAT) && defined(LC_NUMERIC) |
| 337 | // Make sure strtod() uses a decimal point, not a comma. |
| 338 | setlocale(LC_NUMERIC, "C"); |
| 339 | # endif |
| 340 | } |
| 341 | if (loc == NULL) |
Bram Moolenaar | 6d05701 | 2021-12-31 18:49:43 +0000 | [diff] [blame] | 342 | semsg(_(e_cannot_set_language_to_str), name); |
Bram Moolenaar | 054f14b | 2020-07-22 19:11:19 +0200 | [diff] [blame] | 343 | else |
| 344 | { |
| 345 | # ifdef HAVE_NL_MSG_CAT_CNTR |
| 346 | // Need to do this for GNU gettext, otherwise cached translations |
| 347 | // will be used again. |
| 348 | extern int _nl_msg_cat_cntr; |
| 349 | |
| 350 | ++_nl_msg_cat_cntr; |
| 351 | # endif |
| 352 | // Reset $LC_ALL, otherwise it would overrule everything. |
| 353 | vim_setenv((char_u *)"LC_ALL", (char_u *)""); |
| 354 | |
| 355 | if (what != LC_TIME && what != LC_COLLATE) |
| 356 | { |
| 357 | // Tell gettext() what to translate to. It apparently doesn't |
| 358 | // use the currently effective locale. Also do this when |
| 359 | // FEAT_GETTEXT isn't defined, so that shell commands use this |
| 360 | // value. |
| 361 | if (what == LC_ALL) |
| 362 | { |
| 363 | vim_setenv((char_u *)"LANG", name); |
| 364 | |
| 365 | // Clear $LANGUAGE because GNU gettext uses it. |
| 366 | vim_setenv((char_u *)"LANGUAGE", (char_u *)""); |
| 367 | # ifdef MSWIN |
| 368 | // Apparently MS-Windows printf() may cause a crash when |
| 369 | // we give it 8-bit text while it's expecting text in the |
| 370 | // current locale. This call avoids that. |
| 371 | setlocale(LC_CTYPE, "C"); |
| 372 | # endif |
| 373 | } |
| 374 | if (what != LC_CTYPE) |
| 375 | { |
| 376 | char_u *mname; |
| 377 | # ifdef MSWIN |
| 378 | mname = gettext_lang(name); |
| 379 | # else |
| 380 | mname = name; |
| 381 | # endif |
| 382 | vim_setenv((char_u *)"LC_MESSAGES", mname); |
| 383 | # ifdef FEAT_MULTI_LANG |
| 384 | set_helplang_default(mname); |
| 385 | # endif |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | # ifdef FEAT_EVAL |
| 390 | // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. |
| 391 | set_lang_var(); |
| 392 | # endif |
Bram Moolenaar | 054f14b | 2020-07-22 19:11:19 +0200 | [diff] [blame] | 393 | maketitle(); |
Bram Moolenaar | 054f14b | 2020-07-22 19:11:19 +0200 | [diff] [blame] | 394 | } |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | static char_u **locales = NULL; // Array of all available locales |
| 399 | |
| 400 | static int did_init_locales = FALSE; |
| 401 | |
| 402 | /* |
| 403 | * Return an array of strings for all available locales + NULL for the |
| 404 | * last element. Return NULL in case of error. |
| 405 | */ |
| 406 | static char_u ** |
| 407 | find_locales(void) |
| 408 | { |
| 409 | garray_T locales_ga; |
| 410 | char_u *loc; |
| 411 | char_u *locale_list; |
| 412 | # ifdef MSWIN |
| 413 | size_t len = 0; |
| 414 | # endif |
| 415 | |
| 416 | // Find all available locales by running command "locale -a". If this |
| 417 | // doesn't work we won't have completion. |
| 418 | # ifndef MSWIN |
| 419 | locale_list = get_cmd_output((char_u *)"locale -a", |
| 420 | NULL, SHELL_SILENT, NULL); |
| 421 | # else |
| 422 | // Find all available locales by examining the directories in |
| 423 | // $VIMRUNTIME/lang/ |
| 424 | { |
| 425 | int options = WILD_SILENT|WILD_USE_NL|WILD_KEEP_ALL; |
| 426 | expand_T xpc; |
| 427 | char_u *p; |
| 428 | |
| 429 | ExpandInit(&xpc); |
| 430 | xpc.xp_context = EXPAND_DIRECTORIES; |
| 431 | locale_list = ExpandOne(&xpc, (char_u *)"$VIMRUNTIME/lang/*", |
| 432 | NULL, options, WILD_ALL); |
| 433 | ExpandCleanup(&xpc); |
| 434 | if (locale_list == NULL) |
| 435 | // Add a dummy input, that will be skipped lated but we need to |
| 436 | // have something in locale_list so that the C locale is added at |
| 437 | // the end. |
| 438 | locale_list = vim_strsave((char_u *)".\n"); |
| 439 | p = locale_list; |
| 440 | // find the last directory delimiter |
| 441 | while (p != NULL && *p != NUL) |
| 442 | { |
| 443 | if (*p == '\n') |
| 444 | break; |
| 445 | if (*p == '\\') |
| 446 | len = p - locale_list; |
| 447 | p++; |
| 448 | } |
| 449 | } |
| 450 | # endif |
| 451 | if (locale_list == NULL) |
| 452 | return NULL; |
| 453 | ga_init2(&locales_ga, sizeof(char_u *), 20); |
| 454 | |
| 455 | // Transform locale_list string where each locale is separated by "\n" |
| 456 | // into an array of locale strings. |
| 457 | loc = (char_u *)strtok((char *)locale_list, "\n"); |
| 458 | |
| 459 | while (loc != NULL) |
| 460 | { |
| 461 | int ignore = FALSE; |
| 462 | |
| 463 | # ifdef MSWIN |
| 464 | if (len > 0) |
| 465 | loc += len + 1; |
| 466 | // skip locales with a dot (which indicates the charset) |
| 467 | if (vim_strchr(loc, '.') != NULL) |
| 468 | ignore = TRUE; |
| 469 | # endif |
| 470 | if (!ignore) |
| 471 | { |
| 472 | if (ga_grow(&locales_ga, 1) == FAIL) |
| 473 | break; |
| 474 | |
| 475 | loc = vim_strsave(loc); |
| 476 | if (loc == NULL) |
| 477 | break; |
| 478 | |
| 479 | ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = loc; |
| 480 | } |
| 481 | loc = (char_u *)strtok(NULL, "\n"); |
| 482 | } |
| 483 | |
| 484 | # ifdef MSWIN |
| 485 | // Add the C locale |
| 486 | if (ga_grow(&locales_ga, 1) == OK) |
| 487 | ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = |
| 488 | vim_strsave((char_u *)"C"); |
| 489 | # endif |
| 490 | |
| 491 | vim_free(locale_list); |
| 492 | if (ga_grow(&locales_ga, 1) == FAIL) |
| 493 | { |
| 494 | ga_clear(&locales_ga); |
| 495 | return NULL; |
| 496 | } |
| 497 | ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; |
| 498 | return (char_u **)locales_ga.ga_data; |
| 499 | } |
| 500 | |
| 501 | /* |
| 502 | * Lazy initialization of all available locales. |
| 503 | */ |
| 504 | static void |
| 505 | init_locales(void) |
| 506 | { |
| 507 | if (!did_init_locales) |
| 508 | { |
| 509 | did_init_locales = TRUE; |
| 510 | locales = find_locales(); |
| 511 | } |
| 512 | } |
| 513 | |
| 514 | # if defined(EXITFREE) || defined(PROTO) |
| 515 | void |
| 516 | free_locales(void) |
| 517 | { |
| 518 | int i; |
| 519 | if (locales != NULL) |
| 520 | { |
| 521 | for (i = 0; locales[i] != NULL; i++) |
| 522 | vim_free(locales[i]); |
| 523 | VIM_CLEAR(locales); |
| 524 | } |
| 525 | } |
| 526 | # endif |
| 527 | |
| 528 | /* |
| 529 | * Function given to ExpandGeneric() to obtain the possible arguments of the |
| 530 | * ":language" command. |
| 531 | */ |
| 532 | char_u * |
| 533 | get_lang_arg(expand_T *xp UNUSED, int idx) |
| 534 | { |
| 535 | if (idx == 0) |
| 536 | return (char_u *)"messages"; |
| 537 | if (idx == 1) |
| 538 | return (char_u *)"ctype"; |
| 539 | if (idx == 2) |
| 540 | return (char_u *)"time"; |
| 541 | if (idx == 3) |
| 542 | return (char_u *)"collate"; |
| 543 | |
| 544 | init_locales(); |
| 545 | if (locales == NULL) |
| 546 | return NULL; |
| 547 | return locales[idx - 4]; |
| 548 | } |
| 549 | |
| 550 | /* |
| 551 | * Function given to ExpandGeneric() to obtain the available locales. |
| 552 | */ |
| 553 | char_u * |
| 554 | get_locales(expand_T *xp UNUSED, int idx) |
| 555 | { |
| 556 | init_locales(); |
| 557 | if (locales == NULL) |
| 558 | return NULL; |
| 559 | return locales[idx]; |
| 560 | } |
| 561 | |
| 562 | #endif |