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