15#include <structmember.h>
31#ifdef WITH_INTERNATIONAL
64#ifdef WITH_INTERNATIONAL
71static GHashKey *_ghashutil_keyalloc(
const void *msgctxt,
const void *msgid)
73 GHashKey *key =
static_cast<GHashKey *
>(
MEM_mallocN(
sizeof(GHashKey),
"Py i18n GHashKey"));
74 key->msgctxt =
BLI_strdup(
static_cast<const char *
>(
77 key->msgid =
BLI_strdup(
static_cast<const char *
>(msgid));
81static uint _ghashutil_keyhash(
const void *
ptr)
83 const GHashKey *key =
static_cast<const GHashKey *
>(
ptr);
88static bool _ghashutil_keycmp(
const void *a,
const void *
b)
90 const GHashKey *A =
static_cast<const GHashKey *
>(a);
91 const GHashKey *
B =
static_cast<const GHashKey *
>(
b);
100static void _ghashutil_keyfree(
void *
ptr)
102 const GHashKey *key =
static_cast<const GHashKey *
>(
ptr);
110# define _ghashutil_valfree MEM_freeN
125static GHash *_translations_cache =
nullptr;
127static void _clear_translations_cache()
129 if (_translations_cache) {
130 BLI_ghash_free(_translations_cache, _ghashutil_keyfree, _ghashutil_valfree);
132 _translations_cache =
nullptr;
135static void _build_translations_cache(PyObject *py_messages,
const char *locale)
137 PyObject *uuid, *uuid_dict;
139 char *language =
nullptr, *language_country =
nullptr, *language_variant =
nullptr;
144 locale, &language,
nullptr,
nullptr, &language_country, &language_variant);
147 _clear_translations_cache();
148 _translations_cache =
BLI_ghash_new(_ghashutil_keyhash, _ghashutil_keycmp, __func__);
151 while (PyDict_Next(py_messages, &
pos, &uuid, &uuid_dict)) {
155 PyObject_Print(uuid_dict, stdout, 0);
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;
166 if (!lang_dict && language_variant) {
167 lang_dict = PyDict_GetItemString(uuid_dict, language_variant);
168 locale = language_variant;
170 if (!lang_dict && language) {
171 lang_dict = PyDict_GetItemString(uuid_dict, language);
176 PyObject *pykey, *trans;
179 if (!PyDict_Check(lang_dict)) {
180 printf(
"WARNING! In translations' dict of \"");
181 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
184 " Each language key must have a dictionary as value, \"%s\" is not valid, "
187 PyObject_Print(lang_dict, stdout, Py_PRINT_RAW);
193 while (PyDict_Next(lang_dict, &ppos, &pykey, &trans)) {
194 const char *msgctxt =
nullptr, *msgid =
nullptr;
195 bool invalid_key =
false;
197 if ((PyTuple_CheckExact(pykey) ==
false) || (PyTuple_GET_SIZE(pykey) != 2)) {
201 PyObject *tmp = PyTuple_GET_ITEM(pykey, 0);
202 if (tmp == Py_None) {
205 else if (PyUnicode_Check(tmp)) {
206 msgctxt = PyUnicode_AsUTF8(tmp);
212 tmp = PyTuple_GET_ITEM(pykey, 1);
213 if (PyUnicode_Check(tmp)) {
214 msgid = PyUnicode_AsUTF8(tmp);
222 printf(
"WARNING! In translations' dict of \"");
223 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
224 printf(
"\", %s language:\n", locale);
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);
232 if (PyUnicode_Check(trans) ==
false) {
233 printf(
"WARNING! In translations' dict of \"");
234 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
236 printf(
" Values must be strings, this one is not valid, skipping: ");
237 PyObject_Print(trans, stdout, Py_PRINT_RAW);
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);
259const char *BPY_app_translations_py_pgettext(
const char *msgctxt,
const char *msgid)
261# define STATIC_LOCALE_SIZE 32
264 static char locale[STATIC_LOCALE_SIZE] =
"";
273 if (!
STREQ(tmp, locale) || !_translations_cache) {
274 PyGILState_STATE _py_state;
280 _py_state = PyGILState_Ensure();
284 PyGILState_Release(_py_state);
291 tmp =
static_cast<const char *
>(
BLI_ghash_lookup(_translations_cache, &key));
293 return tmp ? tmp : msgid;
295# undef STATIC_LOCALE_SIZE
302 app_translations_py_messages_register_doc,
303 ".. method:: register(module_name, translations_dict)\n"
305 " Registers an addon's UI translations.\n"
308 " Does nothing when Blender is built without internationalization support.\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"
320#ifdef WITH_INTERNATIONAL
321 static const char *kwlist[] = {
"module_name",
"translations_dict",
nullptr};
322 PyObject *module_name, *uuid_dict;
324 if (!PyArg_ParseTupleAndKeywords(args,
326 "O!O!:bpy.app.translations.register",
336 if (PyDict_Contains(
self->py_messages, module_name)) {
339 "bpy.app.translations.register: translations message cache already contains some data for "
341 (
const char *)PyUnicode_AsUTF8(module_name));
345 PyDict_SetItem(
self->py_messages, module_name, uuid_dict);
348 _clear_translations_cache();
361 app_translations_py_messages_unregister_doc,
362 ".. method:: unregister(module_name)\n"
364 " Unregisters an addon's UI translations.\n"
367 " Does nothing when Blender is built without internationalization support.\n"
369 " :arg module_name: The name identifying the addon.\n"
370 " :type module_name: str\n"
376#ifdef WITH_INTERNATIONAL
377 static const char *kwlist[] = {
"module_name",
nullptr};
378 PyObject *module_name;
380 if (!PyArg_ParseTupleAndKeywords(args,
382 "O!:bpy.app.translations.unregister",
390 if (PyDict_Contains(
self->py_messages, module_name)) {
391 PyDict_DelItem(
self->py_messages, module_name);
393 _clear_translations_cache();
424 "bpy.app.translations.contexts",
425 "This named tuple contains all predefined translation contexts",
432 PyObject *translations_contexts;
437 if (translations_contexts ==
nullptr) {
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))
457 return translations_contexts;
468 app_translations_contexts_doc,
469 "A named tuple containing all predefined translation contexts.\n"
473 "\", it would be internally\n"
474 " assimilated as the default one!\n");
478 app_translations_contexts_C_to_py_doc,
479 "A readonly dict mapping contexts' C-identifiers to their py-identifiers.");
486 app_translations_contexts_doc},
491 app_translations_contexts_C_to_py_doc},
497 app_translations_locale_doc,
498 "The actual locale currently in use (will always return a void string when Blender "
500 "internationalization support).");
509 app_translations_locales_doc,
510 "All locales currently known by Blender (i.e. available as translations).");
515 int num_locales = 0,
pos = 0;
526 ret = PyTuple_New(num_locales);
531 PyTuple_SET_ITEM(
ret,
pos++, PyUnicode_FromString(it->description));
545 app_translations_locales_doc,
553 const char *(*_pgettext)(
const char *,
const char *))
555 static const char *kwlist[] = {
"msgid",
"msgctxt",
nullptr};
557#ifdef WITH_INTERNATIONAL
558 char *msgid, *msgctxt =
nullptr;
560 if (!PyArg_ParseTupleAndKeywords(
561 args, kw,
"s|z:bpy.app.translations.pgettext", (
char **)kwlist, &msgid, &msgctxt))
568 PyObject *msgid, *msgctxt;
571 if (!PyArg_ParseTupleAndKeywords(
572 args, kw,
"O|O:bpy.app.translations.pgettext", (
char **)kwlist, &msgid, &msgctxt))
577 return Py_NewRef(msgid);
583 app_translations_pgettext_doc,
584 ".. method:: pgettext(msgid, msgctxt=None)\n"
586 " Try to translate the given msgid (with optional msgctxt).\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"
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"
601 " Does nothing when Blender is built without internationalization support (hence always "
602 "returns ``msgid``).\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"
618 ".. method:: pgettext_n(msgid, msgctxt=None)\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"
624 " See :func:`pgettext` notes.\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"
636 static const char *kwlist[] = {
"msgid",
"msgctxt",
nullptr};
637 PyObject *msgid, *msgctxt;
640 if (!PyArg_ParseTupleAndKeywords(
641 args, kw,
"O|O:bpy.app.translations.pgettext", (
char **)kwlist, &msgid, &msgctxt))
646 return Py_NewRef(msgid);
651 app_translations_pgettext_iface_doc,
652 ".. method:: pgettext_iface(msgid, msgctxt=None)\n"
654 " Try to translate the given msgid (with optional msgctxt), if labels' translation "
658 " See :func:`pgettext` notes.\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"
675 app_translations_pgettext_tip_doc,
676 ".. method:: pgettext_tip(msgid, msgctxt=None)\n"
678 " Try to translate the given msgid (with optional msgctxt), if tooltips' "
679 "translation is enabled.\n"
682 " See :func:`pgettext` notes.\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"
699 app_translations_pgettext_rpt_doc,
700 ".. method:: pgettext_rpt(msgid, msgctxt=None)\n"
702 " Try to translate the given msgid (with optional msgctxt), if reports' translation "
706 " See :func:`pgettext` notes.\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"
723 app_translations_pgettext_data_doc,
724 ".. method:: pgettext_data(msgid, msgctxt=None)\n"
726 " Try to translate the given msgid (with optional msgctxt), if new data name's "
727 "translation is enabled.\n"
730 " See :func:`pgettext` notes.\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"
747 app_translations_locale_explode_doc,
748 ".. method:: locale_explode(locale)\n"
750 " Return all components and their combinations of the given ISO locale string.\n"
752 " >>> bpy.app.translations.locale_explode(\"sr_RS@latin\")\n"
753 " (\"sr\", \"RS\", \"latin\", \"sr_RS\", \"sr@latin\")\n"
755 " For non-complete locales, missing elements will be None.\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"
766 static const char *kwlist[] = {
"locale",
nullptr};
768 char *language, *country, *variant, *language_country, *language_variant;
770 if (!PyArg_ParseTupleAndKeywords(
771 args, kw,
"s:bpy.app.translations.locale_explode", (
char **)kwlist, &locale))
777 locale, &language, &country, &variant, &language_country, &language_variant);
779 ret_tuple = Py_BuildValue(
780 "sssss", language, country, variant, language_country, language_variant);
791#if (defined(__GNUC__) && !defined(__clang__))
792# pragma GCC diagnostic push
793# pragma GCC diagnostic ignored "-Wcast-function-type"
800 METH_VARARGS | METH_KEYWORDS,
801 app_translations_py_messages_register_doc},
804 METH_VARARGS | METH_KEYWORDS,
805 app_translations_py_messages_unregister_doc},
808 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
809 app_translations_pgettext_doc},
812 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
813 app_translations_pgettext_n_doc},
816 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
817 app_translations_pgettext_iface_doc},
820 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
821 app_translations_pgettext_tip_doc},
824 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
825 app_translations_pgettext_rpt_doc},
828 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
829 app_translations_pgettext_data_doc},
832 METH_VARARGS | METH_KEYWORDS | METH_STATIC,
833 app_translations_locale_explode_doc},
837#if (defined(__GNUC__) && !defined(__clang__))
838# pragma GCC diagnostic pop
855 PyObject *val = PyUnicode_FromString(ctxt->
py_id);
856 PyDict_SetItemString(py_ctxts, ctxt->
c_id, val);
871 Py_DECREF(
self->contexts);
872 Py_DECREF(
self->contexts_C_to_py);
873 Py_DECREF(
self->py_messages);
876#ifdef WITH_INTERNATIONAL
877 _clear_translations_cache();
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"
889 PyVarObject_HEAD_INIT(
nullptr, 0)
890 "bpy.app._translations_type",
909 app_translations_doc,
947 PyStructSequence_Field *desc;
953 desc->name = ctxt->
py_id;
956 desc->name = desc->doc =
nullptr;
979#ifdef WITH_INTERNATIONAL
980 _clear_translations_cache();
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
void * BLI_ghash_lookup(const GHash *gh, const void *key) ATTR_WARN_UNUSED_RESULT
void BLI_ghash_insert(GHash *gh, void *key, void *val)
void BLI_ghash_free(GHash *gh, GHashKeyFreeFP keyfreefp, GHashValFreeFP valfreefp)
#define BLI_ghashutil_strhash(key)
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
#define STRNCPY(dst, src)
char * BLI_strdupn(const char *str, size_t len) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
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
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.
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)]
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)
local_group_size(16, 16) .push_constant(Type b
void *(* MEM_mallocN)(size_t len, const char *str)
void MEM_freeN(void *vmemh)
PyObject_HEAD const char * context_separator
PyObject * contexts_C_to_py