Blender V4.3
blt_lang.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
11#include <cstdio>
12#include <cstdlib>
13#include <cstring>
14
15#ifndef _WIN32
16# include <clocale>
17#endif
18
19#include "RNA_types.hh"
20
21#include "BLT_lang.hh" /* own include */
22#include "BLT_translation.hh"
23
24#include "BLI_path_utils.hh"
25#include "BLI_string.h"
26#include "BLI_utildefines.h"
27
28#include "BKE_appdir.hh"
29
30#include "IMB_thumbs.hh"
31
32#include "DNA_userdef_types.h"
33
34#include "MEM_guardedalloc.h"
35
36#ifdef WITH_INTERNATIONAL
37
38# include "BLI_fileops.h"
39# include "BLI_linklist.h"
40
41# include "boost_locale_wrapper.h"
42
43/* Locale options. */
44static const char **locales = nullptr;
45static int num_locales = 0;
46static EnumPropertyItem *locales_menu = nullptr;
47static int num_locales_menu = 0;
48
49static void free_locales()
50{
51 if (locales) {
52 int idx = num_locales_menu - 1; /* Last item does not need to be freed! */
53 while (idx--) {
54 MEM_freeN((void *)locales_menu[idx].identifier);
55 MEM_freeN((void *)locales_menu[idx].name);
56 MEM_freeN((void *)locales_menu[idx].description); /* Also frees locales's relevant value! */
57 }
58
59 MEM_freeN((void *)locales);
60 locales = nullptr;
61 }
62 MEM_SAFE_FREE(locales_menu);
63 num_locales = num_locales_menu = 0;
64}
65
66static void fill_locales()
67{
68 std::optional<std::string> languages_path = BKE_appdir_folder_id(BLENDER_DATAFILES, "locale");
69 char languages[FILE_MAX];
70 LinkNode *lines = nullptr, *line = nullptr;
71 char *str;
72 int idx = 0;
73
74 free_locales();
75
76 if (languages_path.has_value()) {
77 BLI_path_join(languages, FILE_MAX, languages_path->c_str(), "languages");
78 line = lines = BLI_file_read_as_lines(languages);
79 }
80
81 /* This whole "parsing" code is a bit weak, in that it expects strictly formatted input file...
82 * Should not be a problem, though, as this file is script-generated! */
83
84 /* First loop to find highest locale ID */
85 while (line) {
86 int t;
87 str = (char *)line->link;
88 if (ELEM(str[0], '#', '\0')) {
89 line = line->next;
90 continue; /* Comment or void... */
91 }
92 t = atoi(str);
93 if (t >= num_locales) {
94 num_locales = t + 1;
95 }
96 num_locales_menu++;
97 line = line->next;
98 }
99 num_locales_menu++; /* The "closing" void item... */
100
101 /* And now, build locales and locale_menu! */
102 locales_menu = static_cast<EnumPropertyItem *>(
103 MEM_callocN(num_locales_menu * sizeof(EnumPropertyItem), __func__));
104 line = lines;
105 /* Do not allocate locales with zero-sized mem,
106 * as LOCALE macro uses nullptr locales as invalid marker! */
107 if (num_locales > 0) {
108 locales = static_cast<const char **>(MEM_callocN(num_locales * sizeof(char *), __func__));
109 while (line) {
110 const char *loc, *sep1, *sep2, *sep3;
111
112 str = (char *)line->link;
113 if (ELEM(str[0], '#', '\0')) {
114 line = line->next;
115 continue;
116 }
117
118 const int id = atoi(str);
119 sep1 = strchr(str, ':');
120 if (sep1) {
121 sep1++;
122 sep2 = strchr(sep1, ':');
123 if (sep2) {
124 locales_menu[idx].value = id;
125 locales_menu[idx].icon = 0;
126 locales_menu[idx].name = BLI_strdupn(sep1, sep2 - sep1);
127
128 sep2++;
129 sep3 = strchr(sep2, ':');
130
131 if (sep3) {
132 locales_menu[idx].identifier = loc = BLI_strdupn(sep2, sep3 - sep2);
133 }
134 else {
135 locales_menu[idx].identifier = loc = BLI_strdup(sep2);
136 }
137
138 if (id == 0) {
139 /* The DEFAULT/Automatic item... */
140 if (BLI_strnlen(loc, 2)) {
141 locales[id] = "";
142 /* Keep this tip in sync with the one in rna_userdef
143 * (rna_enum_language_default_items). */
144 locales_menu[idx].description = BLI_strdup(
145 "Automatically choose system's defined language "
146 "if available, or fall-back to English");
147 }
148 /* Menu "label", not to be stored in locales! */
149 else {
150 locales_menu[idx].description = BLI_strdup("");
151 }
152 }
153 else {
154 locales[id] = locales_menu[idx].description = BLI_strdup(loc);
155 }
156 idx++;
157 }
158 }
159
160 line = line->next;
161 }
162 }
163
164 /* Add closing item to menu! */
165 locales_menu[idx].identifier = nullptr;
166 locales_menu[idx].value = locales_menu[idx].icon = 0;
167 locales_menu[idx].name = locales_menu[idx].description = "";
168
169 BLI_file_free_lines(lines);
170}
171#endif /* WITH_INTERNATIONAL */
172
174{
175#ifdef WITH_INTERNATIONAL
176 return locales_menu;
177#else
178 return nullptr;
179#endif
180}
181
183{
184#ifdef WITH_INTERNATIONAL
185 const std::optional<std::string> messagepath = BKE_appdir_folder_id(BLENDER_DATAFILES, "locale");
186#endif
187
188/* Make sure LANG is correct and wouldn't cause #std::runtime_error. */
189#ifndef _WIN32
190 /* TODO(sergey): This code only ensures LANG is set properly, so later when
191 * Cycles will try to use file system API from boost there will be no runtime
192 * exception generated by #std::locale() which _requires_ having proper LANG
193 * set in the environment.
194 *
195 * Ideally we also need to ensure LC_ALL, LC_MESSAGES and others are also
196 * set to a proper value, but currently it's not a huge deal and doesn't
197 * cause any headache.
198 *
199 * Would also be good to find nicer way to check if LANG is correct.
200 */
201 const char *lang = BLI_getenv("LANG");
202 if (lang != nullptr) {
203 char *old_locale = setlocale(LC_ALL, nullptr);
204 /* Make a copy so subsequent #setlocale() doesn't interfere. */
205 old_locale = BLI_strdup(old_locale);
206 if (setlocale(LC_ALL, lang) == nullptr) {
207 setenv("LANG", "C", 1);
208 printf("Warning: Falling back to the standard locale (\"C\")\n");
209 }
210 setlocale(LC_ALL, old_locale);
211 MEM_freeN(old_locale);
212 }
213#endif
214
215#ifdef WITH_INTERNATIONAL
216 if (messagepath.has_value()) {
217 bl_locale_init(messagepath->c_str(), TEXT_DOMAIN_NAME);
218 fill_locales();
219 }
220 else {
221 printf("%s: 'locale' data path for translations not found, continuing\n", __func__);
222 }
223#else
224#endif
225}
226
228{
229#ifdef WITH_INTERNATIONAL
230 free_locales();
231#else
232#endif
233}
234
235#ifdef WITH_INTERNATIONAL
236static uint lang_from_userdef()
237{
238 const uint language = uint(U.language);
239 if ((language >= ULANGUAGE_AUTO) && (language < num_locales)) {
240 return language;
241 }
242 return uint(ULANGUAGE_ENGLISH);
243}
244#endif
245
246#ifdef WITH_INTERNATIONAL
247# define ULANGUAGE lang_from_userdef()
248# define LOCALE(_id) (locales ? locales[(_id)] : "")
249#endif
250
251void BLT_lang_set(const char *str)
252{
253#ifdef WITH_INTERNATIONAL
254 int ulang = ULANGUAGE;
255 const char *short_locale = str ? str : LOCALE(ulang);
256 const char *short_locale_utf8 = nullptr;
257
258 /* We want to avoid locales like '.UTF-8'! */
259 if (short_locale[0]) {
260 /* Hooray! Encoding needs to be placed *before* variant! */
261 const char *variant = strchr(short_locale, '@');
262 if (variant) {
263 char *locale = BLI_strdupn(short_locale, variant - short_locale);
264 short_locale_utf8 = BLI_sprintfN("%s.UTF-8%s", locale, variant);
265 MEM_freeN(locale);
266 }
267 else {
268 short_locale_utf8 = BLI_sprintfN("%s.UTF-8", short_locale);
269 }
270 bl_locale_set(short_locale_utf8);
271 MEM_freeN((void *)short_locale_utf8);
272 }
273 else {
274 bl_locale_set(short_locale);
275 }
276#else
277 (void)str;
278#endif
279}
280
281const char *BLT_lang_get()
282{
283#ifdef WITH_INTERNATIONAL
284 if (BLT_translate()) {
285 const char *locale = LOCALE(ULANGUAGE);
286 if (locale[0] == '\0') {
287 /* Default locale, we have to find which one we are actually using! */
288 locale = bl_locale_get();
289 }
290 return locale;
291 }
292 return "en_US"; /* Kind of default locale in Blender when no translation enabled. */
293#else
294 return "";
295#endif
296}
297
298#undef LOCALE
299#undef ULANGUAGE
300
301void BLT_lang_locale_explode(const char *locale,
302 char **language,
303 char **country,
304 char **variant,
305 char **language_country,
306 char **language_variant)
307{
308 const char *m1, *m2;
309 char *_t = nullptr;
310
311 m1 = strchr(locale, '_');
312 m2 = strchr(locale, '@');
313
314 if (language || language_variant) {
315 if (m1 || m2) {
316 _t = m1 ? BLI_strdupn(locale, m1 - locale) : BLI_strdupn(locale, m2 - locale);
317 if (language) {
318 *language = _t;
319 }
320 }
321 else if (language) {
322 *language = BLI_strdup(locale);
323 }
324 }
325 if (country) {
326 if (m1) {
327 *country = m2 ? BLI_strdupn(m1 + 1, m2 - (m1 + 1)) : BLI_strdup(m1 + 1);
328 }
329 else {
330 *country = nullptr;
331 }
332 }
333 if (variant) {
334 if (m2) {
335 *variant = BLI_strdup(m2 + 1);
336 }
337 else {
338 *variant = nullptr;
339 }
340 }
341 if (language_country) {
342 if (m1) {
343 *language_country = m2 ? BLI_strdupn(locale, m2 - locale) : BLI_strdup(locale);
344 }
345 else {
346 *language_country = nullptr;
347 }
348 }
349 if (language_variant) {
350 if (m2) {
351 *language_variant = m1 ? BLI_strdupcat(_t, m2) : BLI_strdup(locale);
352 }
353 else {
354 *language_variant = nullptr;
355 }
356 }
357 if (_t && !language) {
358 MEM_freeN(_t);
359 }
360}
@ BLENDER_DATAFILES
std::optional< std::string > BKE_appdir_folder_id(int folder_id, const char *subfolder) ATTR_WARN_UNUSED_RESULT
Definition appdir.cc:704
File and directory operations.
struct LinkNode * BLI_file_read_as_lines(const char *filepath) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:554
void BLI_file_free_lines(struct LinkNode *lines)
Definition storage.cc:600
#define FILE_MAX
#define BLI_path_join(...)
const char * BLI_getenv(const char *env) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
char * BLI_sprintfN(const char *__restrict format,...) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC ATTR_PRINTF_FORMAT(1
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.c:40
char * BLI_strdupcat(const char *__restrict str1, const char *__restrict str2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char * BLI_strdupn(const char *str, size_t len) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.c:29
int char char int int int int size_t BLI_strnlen(const char *str, size_t maxlen) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.c:909
unsigned int uint
#define ELEM(...)
#define TEXT_DOMAIN_NAME
bool BLT_translate()
@ ULANGUAGE_ENGLISH
@ ULANGUAGE_AUTO
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
void BLT_lang_locale_explode(const char *locale, char **language, char **country, char **variant, char **language_country, char **language_variant)
Definition blt_lang.cc:301
const char * BLT_lang_get()
Definition blt_lang.cc:281
const EnumPropertyItem * BLT_lang_RNA_enum_properties()
Definition blt_lang.cc:173
void BLT_lang_set(const char *str)
Definition blt_lang.cc:251
void BLT_lang_init()
Definition blt_lang.cc:182
void BLT_lang_free()
Definition blt_lang.cc:227
const char * bl_locale_get(void)
void bl_locale_set(const char *locale)
void bl_locale_init(const char *_messages_path, const char *_default_domain)
unsigned int U
Definition btGjkEpa3.h:78
#define printf
#define str(s)
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
const char * identifier
Definition RNA_types.hh:506
const char * name
Definition RNA_types.hh:510
const char * description
Definition RNA_types.hh:512
void * link