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