Blender V4.3
bpy_app_translations.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
13#include <Python.h>
14/* XXX Why bloody hell isn't that included in Python.h???? */
15#include <structmember.h>
16
17#include "BLI_utildefines.h"
18
19#include "BPY_extern.hh"
21
22#include "MEM_guardedalloc.h"
23
24#include "BLT_lang.hh"
25#include "BLT_translation.hh"
26
27#include "RNA_types.hh"
28
30
31#ifdef WITH_INTERNATIONAL
32# include "BLI_ghash.h"
33# include "BLI_string.h"
34#endif
35
36/* ------------------------------------------------------------------- */
41 PyObject_HEAD
43 const char *context_separator;
45 PyObject *contexts;
52 PyObject *py_messages;
53};
54
55/* Our singleton instance pointer */
57
60/* ------------------------------------------------------------------- */
64#ifdef WITH_INTERNATIONAL
65
66struct GHashKey {
67 const char *msgctxt;
68 const char *msgid;
69};
70
71static GHashKey *_ghashutil_keyalloc(const void *msgctxt, const void *msgid)
72{
73 GHashKey *key = static_cast<GHashKey *>(MEM_mallocN(sizeof(GHashKey), "Py i18n GHashKey"));
74 key->msgctxt = BLI_strdup(static_cast<const char *>(
75 BLT_is_default_context(static_cast<const char *>(msgctxt)) ? BLT_I18NCONTEXT_DEFAULT_BPYRNA :
76 msgctxt));
77 key->msgid = BLI_strdup(static_cast<const char *>(msgid));
78 return key;
79}
80
81static uint _ghashutil_keyhash(const void *ptr)
82{
83 const GHashKey *key = static_cast<const GHashKey *>(ptr);
84 const uint hash = BLI_ghashutil_strhash(key->msgctxt);
85 return hash ^ BLI_ghashutil_strhash(key->msgid);
86}
87
88static bool _ghashutil_keycmp(const void *a, const void *b)
89{
90 const GHashKey *A = static_cast<const GHashKey *>(a);
91 const GHashKey *B = static_cast<const GHashKey *>(b);
92
93 /* NOTE: comparing msgid first, most of the time it will be enough! */
94 if (BLI_ghashutil_strcmp(A->msgid, B->msgid) == false) {
95 return BLI_ghashutil_strcmp(A->msgctxt, B->msgctxt);
96 }
97 return true; /* true means they are not equal! */
98}
99
100static void _ghashutil_keyfree(void *ptr)
101{
102 const GHashKey *key = static_cast<const GHashKey *>(ptr);
103
104 /* We assume both msgctxt and msgid were BLI_strdup'ed! */
105 MEM_freeN((void *)key->msgctxt);
106 MEM_freeN((void *)key->msgid);
107 MEM_freeN((void *)key);
108}
109
110# define _ghashutil_valfree MEM_freeN
111
114/* ------------------------------------------------------------------- */
125static GHash *_translations_cache = nullptr;
126
127static void _clear_translations_cache()
128{
129 if (_translations_cache) {
130 BLI_ghash_free(_translations_cache, _ghashutil_keyfree, _ghashutil_valfree);
131 }
132 _translations_cache = nullptr;
133}
134
135static void _build_translations_cache(PyObject *py_messages, const char *locale)
136{
137 PyObject *uuid, *uuid_dict;
138 Py_ssize_t pos = 0;
139 char *language = nullptr, *language_country = nullptr, *language_variant = nullptr;
140
141 /* For each py dict, we'll search for full locale, then language+country, then language+variant,
142 * then only language keys... */
144 locale, &language, nullptr, nullptr, &language_country, &language_variant);
145
146 /* Clear the cached ghash if needed, and create a new one. */
147 _clear_translations_cache();
148 _translations_cache = BLI_ghash_new(_ghashutil_keyhash, _ghashutil_keycmp, __func__);
149
150 /* Iterate over all Python dictionaries. */
151 while (PyDict_Next(py_messages, &pos, &uuid, &uuid_dict)) {
152 PyObject *lang_dict;
153
154# if 0
155 PyObject_Print(uuid_dict, stdout, 0);
156 printf("\n");
157# endif
158
159 /* Try to get first complete locale, then language+country,
160 * then language+variant, then only language. */
161 lang_dict = PyDict_GetItemString(uuid_dict, locale);
162 if (!lang_dict && language_country) {
163 lang_dict = PyDict_GetItemString(uuid_dict, language_country);
164 locale = language_country;
165 }
166 if (!lang_dict && language_variant) {
167 lang_dict = PyDict_GetItemString(uuid_dict, language_variant);
168 locale = language_variant;
169 }
170 if (!lang_dict && language) {
171 lang_dict = PyDict_GetItemString(uuid_dict, language);
172 locale = language;
173 }
174
175 if (lang_dict) {
176 PyObject *pykey, *trans;
177 Py_ssize_t ppos = 0;
178
179 if (!PyDict_Check(lang_dict)) {
180 printf("WARNING! In translations' dict of \"");
181 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
182 printf("\":\n");
183 printf(
184 " Each language key must have a dictionary as value, \"%s\" is not valid, "
185 "skipping: ",
186 locale);
187 PyObject_Print(lang_dict, stdout, Py_PRINT_RAW);
188 printf("\n");
189 continue;
190 }
191
192 /* Iterate over all translations of the found language dict, and populate our ghash cache. */
193 while (PyDict_Next(lang_dict, &ppos, &pykey, &trans)) {
194 const char *msgctxt = nullptr, *msgid = nullptr;
195 bool invalid_key = false;
196
197 if ((PyTuple_CheckExact(pykey) == false) || (PyTuple_GET_SIZE(pykey) != 2)) {
198 invalid_key = true;
199 }
200 else {
201 PyObject *tmp = PyTuple_GET_ITEM(pykey, 0);
202 if (tmp == Py_None) {
204 }
205 else if (PyUnicode_Check(tmp)) {
206 msgctxt = PyUnicode_AsUTF8(tmp);
207 }
208 else {
209 invalid_key = true;
210 }
211
212 tmp = PyTuple_GET_ITEM(pykey, 1);
213 if (PyUnicode_Check(tmp)) {
214 msgid = PyUnicode_AsUTF8(tmp);
215 }
216 else {
217 invalid_key = true;
218 }
219 }
220
221 if (invalid_key) {
222 printf("WARNING! In translations' dict of \"");
223 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
224 printf("\", %s language:\n", locale);
225 printf(
226 " Keys must be tuples of (msgctxt [string or None], msgid [string]), "
227 "this one is not valid, skipping: ");
228 PyObject_Print(pykey, stdout, Py_PRINT_RAW);
229 printf("\n");
230 continue;
231 }
232 if (PyUnicode_Check(trans) == false) {
233 printf("WARNING! In translations' dict of \"");
234 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
235 printf("\":\n");
236 printf(" Values must be strings, this one is not valid, skipping: ");
237 PyObject_Print(trans, stdout, Py_PRINT_RAW);
238 printf("\n");
239 continue;
240 }
241
242 /* Do not overwrite existing keys! */
243 if (BPY_app_translations_py_pgettext(msgctxt, msgid) == msgid) {
244 GHashKey *key = _ghashutil_keyalloc(msgctxt, msgid);
245 Py_ssize_t trans_str_len;
246 const char *trans_str = PyUnicode_AsUTF8AndSize(trans, &trans_str_len);
247 BLI_ghash_insert(_translations_cache, key, BLI_strdupn(trans_str, trans_str_len));
248 }
249 }
250 }
251 }
252
253 /* Clean up! */
254 MEM_SAFE_FREE(language);
255 MEM_SAFE_FREE(language_country);
256 MEM_SAFE_FREE(language_variant);
257}
258
259const char *BPY_app_translations_py_pgettext(const char *msgctxt, const char *msgid)
260{
261# define STATIC_LOCALE_SIZE 32 /* Should be more than enough! */
262
263 GHashKey key;
264 static char locale[STATIC_LOCALE_SIZE] = "";
265 const char *tmp;
266
267 /* Just in case, should never happen! */
268 if (!_translations) {
269 return msgid;
270 }
271
272 tmp = BLT_lang_get();
273 if (!STREQ(tmp, locale) || !_translations_cache) {
274 PyGILState_STATE _py_state;
275
276 STRNCPY(locale, tmp);
277
278 /* Locale changed or cache does not exist, refresh the whole cache! */
279 /* This func may be called from C (i.e. outside of python interpreter 'context'). */
280 _py_state = PyGILState_Ensure();
281
282 _build_translations_cache(_translations->py_messages, locale);
283
284 PyGILState_Release(_py_state);
285 }
286
287 /* And now, simply create the key (context, messageid) and find it in the cached dict! */
288 key.msgctxt = BLT_is_default_context(msgctxt) ? BLT_I18NCONTEXT_DEFAULT_BPYRNA : msgctxt;
289 key.msgid = msgid;
290
291 tmp = static_cast<const char *>(BLI_ghash_lookup(_translations_cache, &key));
292
293 return tmp ? tmp : msgid;
294
295# undef STATIC_LOCALE_SIZE
296}
297
298#endif /* WITH_INTERNATIONAL */
299
301 /* Wrap. */
302 app_translations_py_messages_register_doc,
303 ".. method:: register(module_name, translations_dict)\n"
304 "\n"
305 " Registers an addon's UI translations.\n"
306 "\n"
307 " .. note::\n"
308 " Does nothing when Blender is built without internationalization support.\n"
309 "\n"
310 " :arg module_name: The name identifying the addon.\n"
311 " :type module_name: str\n"
312 " :arg translations_dict: A dictionary built like that:\n"
313 " ``{locale: {msg_key: msg_translation, ...}, ...}``\n"
314 " :type translations_dict: dict[str, dict[str, str]]\n"
315 "\n");
317 PyObject *args,
318 PyObject *kw)
319{
320#ifdef WITH_INTERNATIONAL
321 static const char *kwlist[] = {"module_name", "translations_dict", nullptr};
322 PyObject *module_name, *uuid_dict;
323
324 if (!PyArg_ParseTupleAndKeywords(args,
325 kw,
326 "O!O!:bpy.app.translations.register",
327 (char **)kwlist,
328 &PyUnicode_Type,
329 &module_name,
330 &PyDict_Type,
331 &uuid_dict))
332 {
333 return nullptr;
334 }
335
336 if (PyDict_Contains(self->py_messages, module_name)) {
337 PyErr_Format(
338 PyExc_ValueError,
339 "bpy.app.translations.register: translations message cache already contains some data for "
340 "addon '%s'",
341 (const char *)PyUnicode_AsUTF8(module_name));
342 return nullptr;
343 }
344
345 PyDict_SetItem(self->py_messages, module_name, uuid_dict);
346
347 /* Clear cached messages dict! */
348 _clear_translations_cache();
349#else
350 (void)self;
351 (void)args;
352 (void)kw;
353#endif
354
355 /* And we are done! */
356 Py_RETURN_NONE;
357}
358
360 /* Wrap. */
361 app_translations_py_messages_unregister_doc,
362 ".. method:: unregister(module_name)\n"
363 "\n"
364 " Unregisters an addon's UI translations.\n"
365 "\n"
366 " .. note::\n"
367 " Does nothing when Blender is built without internationalization support.\n"
368 "\n"
369 " :arg module_name: The name identifying the addon.\n"
370 " :type module_name: str\n"
371 "\n");
373 PyObject *args,
374 PyObject *kw)
375{
376#ifdef WITH_INTERNATIONAL
377 static const char *kwlist[] = {"module_name", nullptr};
378 PyObject *module_name;
379
380 if (!PyArg_ParseTupleAndKeywords(args,
381 kw,
382 "O!:bpy.app.translations.unregister",
383 (char **)kwlist,
384 &PyUnicode_Type,
385 &module_name))
386 {
387 return nullptr;
388 }
389
390 if (PyDict_Contains(self->py_messages, module_name)) {
391 PyDict_DelItem(self->py_messages, module_name);
392 /* Clear cached messages ghash! */
393 _clear_translations_cache();
394 }
395#else
396 (void)self;
397 (void)args;
398 (void)kw;
399#endif
400
401 /* And we are done! */
402 Py_RETURN_NONE;
403}
404
407/* ------------------------------------------------------------------- */
411/* This is always available (even when WITH_INTERNATIONAL is not defined). */
412
414
416
417/* These fields are just empty placeholders, actual values get set in app_translations_struct().
418 * This allows us to avoid many handwriting, and above all,
419 * to keep all context definition stuff in BLT_translation.hh! */
420static PyStructSequence_Field app_translations_contexts_fields[ARRAY_SIZE(_contexts)] = {
421 {nullptr}};
422
423static PyStructSequence_Desc app_translations_contexts_desc = {
424 /*name*/ "bpy.app.translations.contexts",
425 /*doc*/ "This named tuple contains all predefined translation contexts",
427 /*n_in_sequence*/ ARRAY_SIZE(app_translations_contexts_fields) - 1,
428};
429
431{
432 PyObject *translations_contexts;
434 int pos = 0;
435
436 translations_contexts = PyStructSequence_New(&BlenderAppTranslationsContextsType);
437 if (translations_contexts == nullptr) {
438 return nullptr;
439 }
440
441#define SetObjString(item) \
442 PyStructSequence_SET_ITEM(translations_contexts, pos++, PyUnicode_FromString(item))
443#define SetObjNone() PyStructSequence_SET_ITEM(translations_contexts, pos++, Py_NewRef(Py_None))
444
445 for (ctxt = _contexts; ctxt->c_id; ctxt++) {
446 if (ctxt->value) {
447 SetObjString(ctxt->value);
448 }
449 else {
450 SetObjNone();
451 }
452 }
453
454#undef SetObjString
455#undef SetObjNone
456
457 return translations_contexts;
458}
459
462/* ------------------------------------------------------------------- */
467 /* Wrap. */
468 app_translations_contexts_doc,
469 "A named tuple containing all predefined translation contexts.\n"
470 "\n"
471 ".. warning::\n"
472 " Never use a (new) context starting with \"" BLT_I18NCONTEXT_DEFAULT_BPYRNA
473 "\", it would be internally\n"
474 " assimilated as the default one!\n");
475
477 /* Wrap. */
478 app_translations_contexts_C_to_py_doc,
479 "A readonly dict mapping contexts' C-identifiers to their py-identifiers.");
480
481static PyMemberDef app_translations_members[] = {
482 {"contexts",
483 T_OBJECT_EX,
485 READONLY,
486 app_translations_contexts_doc},
487 {"contexts_C_to_py",
488 T_OBJECT_EX,
489 offsetof(BlenderAppTranslations, contexts_C_to_py),
490 READONLY,
491 app_translations_contexts_C_to_py_doc},
492 {nullptr},
493};
494
496 /* Wrap. */
497 app_translations_locale_doc,
498 "The actual locale currently in use (will always return a void string when Blender "
499 "is built without "
500 "internationalization support).");
501static PyObject *app_translations_locale_get(PyObject * /*self*/, void * /*userdata*/)
502{
503 return PyUnicode_FromString(BLT_lang_get());
504}
505
506/* NOTE: defining as getter, as (even if quite unlikely), this *may* change during runtime... */
508 /* Wrap. */
509 app_translations_locales_doc,
510 "All locales currently known by Blender (i.e. available as translations).");
511static PyObject *app_translations_locales_get(PyObject * /*self*/, void * /*userdata*/)
512{
513 PyObject *ret;
515 int num_locales = 0, pos = 0;
516
517 if (items) {
518 /* This is not elegant, but simple! */
519 for (it = items; it->identifier; it++) {
520 if (it->value) {
521 num_locales++;
522 }
523 }
524 }
525
526 ret = PyTuple_New(num_locales);
527
528 if (items) {
529 for (it = items; it->identifier; it++) {
530 if (it->value) {
531 PyTuple_SET_ITEM(ret, pos++, PyUnicode_FromString(it->description));
532 }
533 }
534 }
535
536 return ret;
537}
538
539static PyGetSetDef app_translations_getseters[] = {
540 /* {name, getter, setter, doc, userdata} */
541 {"locale", (getter)app_translations_locale_get, nullptr, app_translations_locale_doc, nullptr},
542 {"locales",
544 nullptr,
545 app_translations_locales_doc,
546 nullptr},
547 {nullptr},
548};
549
550/* pgettext helper. */
551static PyObject *_py_pgettext(PyObject *args,
552 PyObject *kw,
553 const char *(*_pgettext)(const char *, const char *))
554{
555 static const char *kwlist[] = {"msgid", "msgctxt", nullptr};
556
557#ifdef WITH_INTERNATIONAL
558 char *msgid, *msgctxt = nullptr;
559
560 if (!PyArg_ParseTupleAndKeywords(
561 args, kw, "s|z:bpy.app.translations.pgettext", (char **)kwlist, &msgid, &msgctxt))
562 {
563 return nullptr;
564 }
565
566 return PyUnicode_FromString((*_pgettext)(msgctxt ? msgctxt : BLT_I18NCONTEXT_DEFAULT, msgid));
567#else
568 PyObject *msgid, *msgctxt;
569 (void)_pgettext;
570
571 if (!PyArg_ParseTupleAndKeywords(
572 args, kw, "O|O:bpy.app.translations.pgettext", (char **)kwlist, &msgid, &msgctxt))
573 {
574 return nullptr;
575 }
576
577 return Py_NewRef(msgid);
578#endif
579}
580
582 /* Wrap. */
583 app_translations_pgettext_doc,
584 ".. method:: pgettext(msgid, msgctxt=None)\n"
585 "\n"
586 " Try to translate the given msgid (with optional msgctxt).\n"
587 "\n"
588 " .. note::\n"
589 " The ``(msgid, msgctxt)`` parameters order has been switched compared to gettext "
590 "function, to allow\n"
591 " single-parameter calls (context then defaults to BLT_I18NCONTEXT_DEFAULT).\n"
592 "\n"
593 " .. note::\n"
594 " You should really rarely need to use this function in regular addon code, as all "
595 "translation should be\n"
596 " handled by Blender internal code. The only exception are string containing formatting "
597 "(like \"File: %r\"),\n"
598 " but you should rather use :func:`pgettext_iface`/:func:`pgettext_tip` in those cases!\n"
599 "\n"
600 " .. note::\n"
601 " Does nothing when Blender is built without internationalization support (hence always "
602 "returns ``msgid``).\n"
603 "\n"
604 " :arg msgid: The string to translate.\n"
605 " :type msgid: str\n"
606 " :arg msgctxt: The translation context (defaults to BLT_I18NCONTEXT_DEFAULT).\n"
607 " :type msgctxt: str | None\n"
608 " :return: The translated string (or msgid if no translation was found).\n"
609 "\n");
611 PyObject *args,
612 PyObject *kw)
613{
614 return _py_pgettext(args, kw, BLT_pgettext);
615}
616
617PyDoc_STRVAR(app_translations_pgettext_n_doc,
618 ".. method:: pgettext_n(msgid, msgctxt=None)\n"
619 "\n"
620 " Extract the given msgid to translation files. This is a no-op function that will "
621 "only mark the string to extract, but not perform the actual translation.\n"
622 "\n"
623 " .. note::\n"
624 " See :func:`pgettext` notes.\n"
625 "\n"
626 " :arg msgid: The string to extract.\n"
627 " :type msgid: str\n"
628 " :arg msgctxt: The translation context (defaults to BLT_I18NCONTEXT_DEFAULT).\n"
629 " :type msgctxt: str | None\n"
630 " :return: The original string.\n"
631 "\n");
633 PyObject *args,
634 PyObject *kw)
635{
636 static const char *kwlist[] = {"msgid", "msgctxt", nullptr};
637 PyObject *msgid, *msgctxt;
638 // (void)_pgettext;
639
640 if (!PyArg_ParseTupleAndKeywords(
641 args, kw, "O|O:bpy.app.translations.pgettext", (char **)kwlist, &msgid, &msgctxt))
642 {
643 return nullptr;
644 }
645
646 return Py_NewRef(msgid);
647}
648
650 /* Wrap. */
651 app_translations_pgettext_iface_doc,
652 ".. method:: pgettext_iface(msgid, msgctxt=None)\n"
653 "\n"
654 " Try to translate the given msgid (with optional msgctxt), if labels' translation "
655 "is enabled.\n"
656 "\n"
657 " .. note::\n"
658 " See :func:`pgettext` notes.\n"
659 "\n"
660 " :arg msgid: The string to translate.\n"
661 " :type msgid: str\n"
662 " :arg msgctxt: The translation context (defaults to BLT_I18NCONTEXT_DEFAULT).\n"
663 " :type msgctxt: str | None\n"
664 " :return: The translated string (or msgid if no translation was found).\n"
665 "\n");
667 PyObject *args,
668 PyObject *kw)
669{
670 return _py_pgettext(args, kw, BLT_translate_do_iface);
671}
672
674 /* Wrap. */
675 app_translations_pgettext_tip_doc,
676 ".. method:: pgettext_tip(msgid, msgctxt=None)\n"
677 "\n"
678 " Try to translate the given msgid (with optional msgctxt), if tooltips' "
679 "translation is enabled.\n"
680 "\n"
681 " .. note::\n"
682 " See :func:`pgettext` notes.\n"
683 "\n"
684 " :arg msgid: The string to translate.\n"
685 " :type msgid: str\n"
686 " :arg msgctxt: The translation context (defaults to BLT_I18NCONTEXT_DEFAULT).\n"
687 " :type msgctxt: str | None\n"
688 " :return: The translated string (or msgid if no translation was found).\n"
689 "\n");
691 PyObject *args,
692 PyObject *kw)
693{
694 return _py_pgettext(args, kw, BLT_translate_do_tooltip);
695}
696
698 /* Wrap. */
699 app_translations_pgettext_rpt_doc,
700 ".. method:: pgettext_rpt(msgid, msgctxt=None)\n"
701 "\n"
702 " Try to translate the given msgid (with optional msgctxt), if reports' translation "
703 "is enabled.\n"
704 "\n"
705 " .. note::\n"
706 " See :func:`pgettext` notes.\n"
707 "\n"
708 " :arg msgid: The string to translate.\n"
709 " :type msgid: str\n"
710 " :arg msgctxt: The translation context (defaults to BLT_I18NCONTEXT_DEFAULT).\n"
711 " :type msgctxt: str | None\n"
712 " :return: The translated string (or msgid if no translation was found).\n"
713 "\n");
715 PyObject *args,
716 PyObject *kw)
717{
718 return _py_pgettext(args, kw, BLT_translate_do_report);
719}
720
722 /* Wrap. */
723 app_translations_pgettext_data_doc,
724 ".. method:: pgettext_data(msgid, msgctxt=None)\n"
725 "\n"
726 " Try to translate the given msgid (with optional msgctxt), if new data name's "
727 "translation is enabled.\n"
728 "\n"
729 " .. note::\n"
730 " See :func:`pgettext` notes.\n"
731 "\n"
732 " :arg msgid: The string to translate.\n"
733 " :type msgid: str\n"
734 " :arg msgctxt: The translation context (defaults to BLT_I18NCONTEXT_DEFAULT).\n"
735 " :type msgctxt: str | None\n"
736 " :return: The translated string (or ``msgid`` if no translation was found).\n"
737 "\n");
739 PyObject *args,
740 PyObject *kw)
741{
743}
744
746 /* Wrap. */
747 app_translations_locale_explode_doc,
748 ".. method:: locale_explode(locale)\n"
749 "\n"
750 " Return all components and their combinations of the given ISO locale string.\n"
751 "\n"
752 " >>> bpy.app.translations.locale_explode(\"sr_RS@latin\")\n"
753 " (\"sr\", \"RS\", \"latin\", \"sr_RS\", \"sr@latin\")\n"
754 "\n"
755 " For non-complete locales, missing elements will be None.\n"
756 "\n"
757 " :arg locale: The ISO locale string to explode.\n"
758 " :type msgid: str\n"
759 " :return: A tuple ``(language, country, variant, language_country, language@variant)``.\n"
760 "\n");
762 PyObject *args,
763 PyObject *kw)
764{
765 PyObject *ret_tuple;
766 static const char *kwlist[] = {"locale", nullptr};
767 const char *locale;
768 char *language, *country, *variant, *language_country, *language_variant;
769
770 if (!PyArg_ParseTupleAndKeywords(
771 args, kw, "s:bpy.app.translations.locale_explode", (char **)kwlist, &locale))
772 {
773 return nullptr;
774 }
775
777 locale, &language, &country, &variant, &language_country, &language_variant);
778
779 ret_tuple = Py_BuildValue(
780 "sssss", language, country, variant, language_country, language_variant);
781
782 MEM_SAFE_FREE(language);
783 MEM_SAFE_FREE(country);
784 MEM_SAFE_FREE(variant);
785 MEM_SAFE_FREE(language_country);
786 MEM_SAFE_FREE(language_variant);
787
788 return ret_tuple;
789}
790
791#if (defined(__GNUC__) && !defined(__clang__))
792# pragma GCC diagnostic push
793# pragma GCC diagnostic ignored "-Wcast-function-type"
794#endif
795
796static PyMethodDef app_translations_methods[] = {
797 /* Can't use METH_KEYWORDS alone, see http://bugs.python.org/issue11587 */
798 {"register",
800 METH_VARARGS | METH_KEYWORDS,
801 app_translations_py_messages_register_doc},
802 {"unregister",
804 METH_VARARGS | METH_KEYWORDS,
805 app_translations_py_messages_unregister_doc},
806 {"pgettext",
807 (PyCFunction)app_translations_pgettext,
808 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
809 app_translations_pgettext_doc},
810 {"pgettext_n",
811 (PyCFunction)app_translations_pgettext_n,
812 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
813 app_translations_pgettext_n_doc},
814 {"pgettext_iface",
816 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
817 app_translations_pgettext_iface_doc},
818 {"pgettext_tip",
820 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
821 app_translations_pgettext_tip_doc},
822 {"pgettext_rpt",
824 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
825 app_translations_pgettext_rpt_doc},
826 {"pgettext_data",
828 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
829 app_translations_pgettext_data_doc},
830 {"locale_explode",
832 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
833 app_translations_locale_explode_doc},
834 {nullptr},
835};
836
837#if (defined(__GNUC__) && !defined(__clang__))
838# pragma GCC diagnostic pop
839#endif
840
841static PyObject *app_translations_new(PyTypeObject *type, PyObject * /*args*/, PyObject * /*kw*/)
842{
843 // printf("%s (%p)\n", __func__, _translations);
844
845 if (!_translations) {
846 _translations = (BlenderAppTranslations *)type->tp_alloc(type, 0);
847 if (_translations) {
848 PyObject *py_ctxts;
850
852
853 py_ctxts = _PyDict_NewPresized(ARRAY_SIZE(_contexts));
854 for (ctxt = _contexts; ctxt->c_id; ctxt++) {
855 PyObject *val = PyUnicode_FromString(ctxt->py_id);
856 PyDict_SetItemString(py_ctxts, ctxt->c_id, val);
857 Py_DECREF(val);
858 }
859 _translations->contexts_C_to_py = PyDictProxy_New(py_ctxts);
860 Py_DECREF(py_ctxts); /* The actual dict is only owned by its proxy */
861
862 _translations->py_messages = PyDict_New();
863 }
864 }
865
866 return (PyObject *)_translations;
867}
868
870{
871 Py_DECREF(self->contexts);
872 Py_DECREF(self->contexts_C_to_py);
873 Py_DECREF(self->py_messages);
874
875 PyObject_Del(self);
876#ifdef WITH_INTERNATIONAL
877 _clear_translations_cache();
878#endif
879}
880
882 /* Wrap. */
883 app_translations_doc,
884 "This object contains some data/methods regarding internationalization in Blender, "
885 "and allows every py script\n"
886 "to feature translations for its own UI messages.\n"
887 "\n");
888static PyTypeObject BlenderAppTranslationsType = {
889 /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
890 /*tp_name*/ "bpy.app._translations_type",
891 /*tp_basicsize*/ sizeof(BlenderAppTranslations),
892 /*tp_itemsize*/ 0,
893 /*tp_dealloc*/ nullptr,
894 /*tp_vectorcall_offset*/ 0,
895 /*tp_getattr*/ nullptr,
896 /*tp_setattr*/ nullptr,
897 /*tp_as_async*/ nullptr,
898 /*tp_repr*/ nullptr,
899 /*tp_as_number*/ nullptr,
900 /*tp_as_sequence*/ nullptr,
901 /*tp_as_mapping*/ nullptr,
902 /*tp_hash*/ nullptr,
903 /*tp_call*/ nullptr,
904 /*tp_str*/ nullptr,
905 /*tp_getattro*/ nullptr,
906 /*tp_setattro*/ nullptr,
907 /*tp_as_buffer*/ nullptr,
908 /*tp_flags*/ Py_TPFLAGS_DEFAULT,
909 /*tp_doc*/ app_translations_doc,
910 /*tp_traverse*/ nullptr,
911 /*tp_clear*/ nullptr,
912 /*tp_richcompare*/ nullptr,
913 /*tp_weaklistoffset*/ 0,
914 /*tp_iter*/ nullptr,
915 /*tp_iternext*/ nullptr,
916 /*tp_methods*/ app_translations_methods,
917 /*tp_members*/ app_translations_members,
918 /*tp_getset*/ app_translations_getseters,
919 /*tp_base*/ nullptr,
920 /*tp_dict*/ nullptr,
921 /*tp_descr_get*/ nullptr,
922 /*tp_descr_set*/ nullptr,
923 /*tp_dictoffset*/ 0,
924 /*tp_init*/ nullptr,
925 /*tp_alloc*/ nullptr,
926 /*tp_new*/ (newfunc)app_translations_new,
927 /*tp_free*/ (freefunc)app_translations_free,
928 /*tp_is_gc*/ nullptr,
929 /*tp_bases*/ nullptr,
930 /*tp_mro*/ nullptr,
931 /*tp_cache*/ nullptr,
932 /*tp_subclasses*/ nullptr,
933 /*tp_weaklist*/ nullptr,
934 /*tp_del*/ nullptr,
935 /*tp_version_tag*/ 0,
936 /*tp_finalize*/ nullptr,
937 /*tp_vectorcall*/ nullptr,
938};
939
941{
942 PyObject *ret;
943
944 /* Let's finalize our contexts `PyStructSequence` definition! */
945 {
947 PyStructSequence_Field *desc;
948
949 /* We really populate the contexts' fields here! */
950 for (ctxt = _contexts, desc = app_translations_contexts_desc.fields; ctxt->c_id;
951 ctxt++, desc++)
952 {
953 desc->name = ctxt->py_id;
954 desc->doc = nullptr;
955 }
956 desc->name = desc->doc = nullptr; /* End sentinel! */
957
958 PyStructSequence_InitType(&BlenderAppTranslationsContextsType,
960 }
961
962 if (PyType_Ready(&BlenderAppTranslationsType) < 0) {
963 return nullptr;
964 }
965
966 ret = PyObject_CallObject((PyObject *)&BlenderAppTranslationsType, nullptr);
967
968 /* prevent user from creating new instances */
969 BlenderAppTranslationsType.tp_new = nullptr;
970 /* Without this we can't do `set(sys.modules)` #29635. */
971 BlenderAppTranslationsType.tp_hash = (hashfunc)_Py_HashPointer;
972
973 return ret;
974}
975
977{
978/* In case the object remains in a module's name-space, see #44127. */
979#ifdef WITH_INTERNATIONAL
980 _clear_translations_cache();
981#endif
982}
983
bool BLI_ghashutil_strcmp(const void *a, const void *b)
GHash * BLI_ghash_new(GHashHashFP hashfp, GHashCmpFP cmpfp, const char *info) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.c:686
void * BLI_ghash_lookup(const GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT
Definition BLI_ghash.c:731
void BLI_ghash_insert(GHash *gh, void *key, void *val)
Definition BLI_ghash.c:707
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
Definition BLI_ghash.c:860
#define BLI_ghashutil_strhash(key)
Definition BLI_ghash.h:574
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.c:40
#define STRNCPY(dst, src)
Definition BLI_string.h:593
char * BLI_strdupn(const char *str, size_t len) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.c:29
unsigned int uint
#define ARRAY_SIZE(arr)
#define STREQ(a, b)
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
#define BLT_I18NCONTEXTS_DESC
const char * BLT_pgettext(const char *msgctxt, const char *msgid)
#define BLT_I18NCONTEXT_DEFAULT
const char * BLT_translate_do_tooltip(const char *msgctxt, const char *msgid)
bool BLT_is_default_context(const char *msgctxt)
#define BLT_I18NCONTEXT_DEFAULT_BPYRNA
const char * BLT_translate_do_iface(const char *msgctxt, const char *msgid)
const char * BLT_translate_do_new_dataname(const char *msgctxt, const char *msgid)
const char * BLT_translate_do_report(const char *msgctxt, const char *msgid)
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
static PyObject * app_translations_new(PyTypeObject *type, PyObject *, PyObject *)
static PyObject * app_translations_pgettext_tip(BlenderAppTranslations *, PyObject *args, PyObject *kw)
PyObject * BPY_app_translations_struct()
static PyObject * app_translations_pgettext_iface(BlenderAppTranslations *, PyObject *args, PyObject *kw)
static PyGetSetDef app_translations_getseters[]
#define SetObjString(item)
static PyTypeObject BlenderAppTranslationsContextsType
static PyObject * app_translations_locale_explode(BlenderAppTranslations *, PyObject *args, PyObject *kw)
static PyObject * app_translations_pgettext(BlenderAppTranslations *, PyObject *args, PyObject *kw)
static PyObject * app_translations_locale_get(PyObject *, void *)
static PyStructSequence_Field app_translations_contexts_fields[ARRAY_SIZE(_contexts)]
#define SetObjNone()
static BlenderAppTranslations * _translations
static PyObject * app_translations_py_messages_register(BlenderAppTranslations *self, PyObject *args, PyObject *kw)
static PyTypeObject BlenderAppTranslationsType
static PyObject * app_translations_contexts_make()
PyDoc_STRVAR(app_translations_py_messages_register_doc, ".. method:: register(module_name, translations_dict)\n" "\n" " Registers an addon's UI translations.\n" "\n" " .. note::\n" " Does nothing when Blender is built without internationalization support.\n" "\n" " :arg module_name: The name identifying the addon.\n" " :type module_name: str\n" " :arg translations_dict: A dictionary built like that:\n" " ``{locale: {msg_key: msg_translation, ...}, ...}``\n" " :type translations_dict: dict[str, dict[str, str]]\n" "\n")
static PyObject * app_translations_py_messages_unregister(BlenderAppTranslations *self, PyObject *args, PyObject *kw)
static BLT_i18n_contexts_descriptor _contexts[]
void BPY_app_translations_end()
static PyObject * _py_pgettext(PyObject *args, PyObject *kw, const char *(*_pgettext)(const char *, const char *))
static PyMethodDef app_translations_methods[]
static PyObject * app_translations_pgettext_rpt(BlenderAppTranslations *, PyObject *args, PyObject *kw)
static PyObject * app_translations_locales_get(PyObject *, void *)
static void app_translations_free(BlenderAppTranslations *self)
static PyMemberDef app_translations_members[]
static PyStructSequence_Desc app_translations_contexts_desc
static PyObject * app_translations_pgettext_n(BlenderAppTranslations *, PyObject *args, PyObject *kw)
static PyObject * app_translations_pgettext_data(BlenderAppTranslations *, PyObject *args, PyObject *kw)
PyObject * self
local_group_size(16, 16) .push_constant(Type b
#define printf
#define offsetof(t, d)
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
#define B
#define hash
Definition noise.c:154
header-only utilities
return ret
PyObject_HEAD const char * context_separator
const char * identifier
Definition RNA_types.hh:506
PointerRNA * ptr
Definition wm_files.cc:4126