16#include <structmember.h>
32#ifdef WITH_INTERNATIONAL
71#ifdef WITH_INTERNATIONAL
94 static uint64_t hash_as(
const MessageKeyRef &key)
100inline bool operator==(
const MessageKey &a,
const MessageKey &
b)
102 return a.context ==
b.context && a.str ==
b.str;
105inline bool operator==(
const MessageKeyRef &a,
const MessageKey &
b)
107 return a.context ==
b.context && a.str ==
b.str;
123static std::unique_ptr<blender::Map<MessageKey, std::string>> &get_translations_cache()
125 static std::unique_ptr<blender::Map<MessageKey, std::string>> translations;
129static void _clear_translations_cache()
131 get_translations_cache().reset();
134static void _build_translations_cache(PyObject *py_messages,
const char *locale)
136 PyObject *uuid, *uuid_dict;
138 char *language =
nullptr, *language_country =
nullptr, *language_variant =
nullptr;
143 locale, &language,
nullptr,
nullptr, &language_country, &language_variant);
146 _clear_translations_cache();
147 get_translations_cache() = std::make_unique<blender::Map<MessageKey, std::string>>();
150 while (PyDict_Next(py_messages, &
pos, &uuid, &uuid_dict)) {
154 PyObject_Print(uuid_dict, stdout, 0);
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;
165 if (!lang_dict && language_variant) {
166 lang_dict = PyDict_GetItemString(uuid_dict, language_variant);
167 locale = language_variant;
169 if (!lang_dict && language) {
170 lang_dict = PyDict_GetItemString(uuid_dict, language);
175 PyObject *pykey, *trans;
178 if (!PyDict_Check(lang_dict)) {
179 printf(
"WARNING! In translations' dict of \"");
180 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
183 " Each language key must have a dictionary as value, \"%s\" is not valid, "
186 PyObject_Print(lang_dict, stdout, Py_PRINT_RAW);
192 while (PyDict_Next(lang_dict, &ppos, &pykey, &trans)) {
193 const char *msgctxt =
nullptr, *msgid =
nullptr;
194 bool invalid_key =
false;
196 if ((PyTuple_CheckExact(pykey) ==
false) || (PyTuple_GET_SIZE(pykey) != 2)) {
200 PyObject *tmp = PyTuple_GET_ITEM(pykey, 0);
201 if (tmp == Py_None) {
204 else if (PyUnicode_Check(tmp)) {
205 msgctxt = PyUnicode_AsUTF8(tmp);
211 tmp = PyTuple_GET_ITEM(pykey, 1);
212 if (PyUnicode_Check(tmp)) {
213 msgid = PyUnicode_AsUTF8(tmp);
221 printf(
"WARNING! In translations' dict of \"");
222 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
223 printf(
"\", %s language:\n", locale);
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);
231 if (PyUnicode_Check(trans) ==
false) {
232 printf(
"WARNING! In translations' dict of \"");
233 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
235 printf(
" Values must be strings, this one is not valid, skipping: ");
236 PyObject_Print(trans, stdout, Py_PRINT_RAW);
242 if (!BPY_app_translations_py_pgettext(msgctxt, msgid).has_value()) {
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));
260std::optional<StringRefNull> BPY_app_translations_py_pgettext(
const StringRef msgctxt,
263# define STATIC_LOCALE_SIZE 32
265 static char locale[STATIC_LOCALE_SIZE] =
"";
274 if (!
STREQ(tmp, locale) || !get_translations_cache()) {
276 PyGILState_STATE _py_state = PyGILState_Ensure();
281 _build_translations_cache(
_translations->py_messages, locale);
283 PyGILState_Release(_py_state);
291 const std::string *
result = get_translations_cache()->lookup_ptr_as(key);
297# undef STATIC_LOCALE_SIZE
304 app_translations_py_messages_register_doc,
305 ".. method:: register(module_name, translations_dict)\n"
307 " Registers an addon's UI translations.\n"
310 " Does nothing when Blender is built without internationalization support.\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"
322#ifdef WITH_INTERNATIONAL
323 static const char *kwlist[] = {
"module_name",
"translations_dict",
nullptr};
324 PyObject *module_name, *uuid_dict;
326 if (!PyArg_ParseTupleAndKeywords(args,
328 "O!O!:bpy.app.translations.register",
338 if (PyDict_Contains(
self->py_messages, module_name)) {
341 "bpy.app.translations.register: translations message cache already contains some data for "
343 PyUnicode_AsUTF8(module_name));
347 PyDict_SetItem(
self->py_messages, module_name, uuid_dict);
350 _clear_translations_cache();
363 app_translations_py_messages_unregister_doc,
364 ".. method:: unregister(module_name)\n"
366 " Unregisters an addon's UI translations.\n"
369 " Does nothing when Blender is built without internationalization support.\n"
371 " :arg module_name: The name identifying the addon.\n"
372 " :type module_name: str\n"
378#ifdef WITH_INTERNATIONAL
379 static const char *kwlist[] = {
"module_name",
nullptr};
380 PyObject *module_name;
382 if (!PyArg_ParseTupleAndKeywords(args,
384 "O!:bpy.app.translations.unregister",
392 if (PyDict_Contains(
self->py_messages, module_name)) {
393 PyDict_DelItem(
self->py_messages, module_name);
395 _clear_translations_cache();
426 "bpy.app.translations.contexts",
427 "This named tuple contains all predefined translation contexts",
434 PyObject *translations_contexts;
439 if (translations_contexts ==
nullptr) {
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))
459 return translations_contexts;
470 app_translations_contexts_doc,
471 "A named tuple containing all predefined translation contexts.\n"
475 "\", it would be internally\n"
476 " assimilated as the default one!\n");
479 app_translations_contexts_C_to_py_doc,
480 "A readonly dict mapping contexts' C-identifiers to their py-identifiers.\n");
487 app_translations_contexts_doc},
492 app_translations_contexts_C_to_py_doc},
498 app_translations_locale_doc,
499 "The actual locale currently in use (will always return a void string when Blender "
501 "internationalization support).");
510 app_translations_locales_doc,
511 "All locales currently known by Blender (i.e. available as translations).");
516 int num_locales = 0,
pos = 0;
527 ret = PyTuple_New(num_locales);
546 app_translations_locales_doc,
554 const char *(*_pgettext)(
const char *,
const char *))
556 static const char *kwlist[] = {
"msgid",
"msgctxt",
nullptr};
558#ifdef WITH_INTERNATIONAL
559 char *msgid, *msgctxt =
nullptr;
561 if (!PyArg_ParseTupleAndKeywords(
562 args, kw,
"s|z:bpy.app.translations.pgettext", (
char **)kwlist, &msgid, &msgctxt))
569 PyObject *msgid, *msgctxt;
572 if (!PyArg_ParseTupleAndKeywords(
573 args, kw,
"O|O:bpy.app.translations.pgettext", (
char **)kwlist, &msgid, &msgctxt))
578 return Py_NewRef(msgid);
584 app_translations_pgettext_doc,
585 ".. method:: pgettext(msgid, msgctxt=None)\n"
587 " Try to translate the given msgid (with optional msgctxt).\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"
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"
602 " Does nothing when Blender is built without internationalization support (hence always "
603 "returns ``msgid``).\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"
620 app_translations_pgettext_n_doc,
621 ".. method:: pgettext_n(msgid, msgctxt=None)\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"
627 " See :func:`pgettext` notes.\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"
639 static const char *kwlist[] = {
"msgid",
"msgctxt",
nullptr};
640 PyObject *msgid, *msgctxt;
643 if (!PyArg_ParseTupleAndKeywords(
644 args, kw,
"O|O:bpy.app.translations.pgettext", (
char **)kwlist, &msgid, &msgctxt))
649 return Py_NewRef(msgid);
654 app_translations_pgettext_iface_doc,
655 ".. method:: pgettext_iface(msgid, msgctxt=None)\n"
657 " Try to translate the given msgid (with optional msgctxt), if labels' translation "
661 " See :func:`pgettext` notes.\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"
678 app_translations_pgettext_tip_doc,
679 ".. method:: pgettext_tip(msgid, msgctxt=None)\n"
681 " Try to translate the given msgid (with optional msgctxt), if tooltips' "
682 "translation is enabled.\n"
685 " See :func:`pgettext` notes.\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"
702 app_translations_pgettext_rpt_doc,
703 ".. method:: pgettext_rpt(msgid, msgctxt=None)\n"
705 " Try to translate the given msgid (with optional msgctxt), if reports' translation "
709 " See :func:`pgettext` notes.\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"
726 app_translations_pgettext_data_doc,
727 ".. method:: pgettext_data(msgid, msgctxt=None)\n"
729 " Try to translate the given msgid (with optional msgctxt), if new data name's "
730 "translation is enabled.\n"
733 " See :func:`pgettext` notes.\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"
750 app_translations_locale_explode_doc,
751 ".. method:: locale_explode(locale)\n"
753 " Return all components and their combinations of the given ISO locale string.\n"
755 " >>> bpy.app.translations.locale_explode(\"sr_RS@latin\")\n"
756 " (\"sr\", \"RS\", \"latin\", \"sr_RS\", \"sr@latin\")\n"
758 " For non-complete locales, missing elements will be None.\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"
769 static const char *kwlist[] = {
"locale",
nullptr};
771 char *language, *country, *variant, *language_country, *language_variant;
773 if (!PyArg_ParseTupleAndKeywords(
774 args, kw,
"s:bpy.app.translations.locale_explode", (
char **)kwlist, &locale))
780 locale, &language, &country, &variant, &language_country, &language_variant);
782 ret_tuple = Py_BuildValue(
783 "sssss", language, country, variant, language_country, language_variant);
796# pragma clang diagnostic push
797# pragma clang diagnostic ignored "-Wcast-function-type"
799# pragma GCC diagnostic push
800# pragma GCC diagnostic ignored "-Wcast-function-type"
808 METH_VARARGS | METH_KEYWORDS,
809 app_translations_py_messages_register_doc},
812 METH_VARARGS | METH_KEYWORDS,
813 app_translations_py_messages_unregister_doc},
816 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
817 app_translations_pgettext_doc},
820 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
821 app_translations_pgettext_n_doc},
824 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
825 app_translations_pgettext_iface_doc},
828 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
829 app_translations_pgettext_tip_doc},
832 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
833 app_translations_pgettext_rpt_doc},
836 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
837 app_translations_pgettext_data_doc},
840 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
841 app_translations_locale_explode_doc},
847# pragma clang diagnostic pop
849# pragma GCC diagnostic pop
858 BLI_assert(PyTuple_GET_SIZE(args) == 0 && kw ==
nullptr);
871 PyObject *val = PyUnicode_FromString(ctxt->
py_id);
872 PyDict_SetItemString(py_ctxts, ctxt->
c_id, val);
894#ifdef WITH_INTERNATIONAL
895 _clear_translations_cache();
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"
907 PyVarObject_HEAD_INIT(
nullptr, 0)
908 "bpy_app_translations",
927 app_translations_doc,
965 PyStructSequence_Field *desc;
971 desc->name = ctxt->
py_id;
974 desc->name = desc->doc =
nullptr;
997#ifdef WITH_INTERNATIONAL
998 _clear_translations_cache();
#define STRNCPY_UTF8(dst, src)
#define UNUSED_VARS_NDEBUG(...)
void BLT_lang_locale_explode(const char *locale, char **language, char **country, char **variant, char **language_country, char **language_variant)
const char * BLT_lang_get()
const EnumPropertyItem * BLT_lang_RNA_enum_properties()
#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.
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)]
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)
unsigned long long int uint64_t
int context(const bContext *C, const char *member, bContextDataResult *result)
uint64_t get_default_hash(const T &v, const Args &...args)
header-only compatibility defines.
PyObject_HEAD const char * context_separator
PyObject * contexts_C_to_py