db99d6bcf922091b38502f52f0b2df526f3a9d20
[blender.git] / source / blender / python / intern / bpy_app_translations.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s): Bastien Montagne
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/python/intern/bpy_app_translations.c
24  *  \ingroup pythonintern
25  *
26  * This file defines a singleton py object accessed via 'bpy.app.translations',
27  * which exposes various data and functions useful in i18n work.
28  * Most notably, it allows to extend main translations with py dicts.
29  */
30
31 #include <Python.h>
32 /* XXX Why bloody hell isn't that included in Python.h???? */
33 #include <structmember.h>
34
35 #include "BLI_string.h"
36 #include "BLI_ghash.h"
37 #include "BLI_utildefines.h"
38
39 #include "BPY_extern.h"
40 #include "bpy_app_translations.h"
41
42 #include "MEM_guardedalloc.h"
43
44 #include "BLF_translation.h"
45
46 #include "RNA_types.h"
47 #include "RNA_access.h"
48
49
50 typedef struct
51 {
52         PyObject_HEAD
53         /* The string used to separate context from actual message in PY_TRANSLATE RNA props. */
54         const char *context_separator;
55         /* A "named tuple" (StructSequence actually...) containing all C-defined contexts. */
56         PyObject *contexts;
57         /* A readonly mapping {C context id: python id}  (actually, a MappingProxy). */
58         PyObject *contexts_C_to_py;
59         /* A py dict containing all registered py dicts (order is more or less random, first match wins!). */
60         PyObject *py_messages;
61 } BlenderAppTranslations;
62
63 /* Our singleton instance pointer */
64 static BlenderAppTranslations *_translations = NULL;
65
66 #ifdef WITH_INTERNATIONAL
67
68 /***** Helpers for ghash *****/
69 typedef struct GHashKey {
70         const char *msgctxt;
71         const char *msgid;
72 } GHashKey;
73
74 static GHashKey *_ghashutil_keyalloc(const void *msgctxt, const void *msgid)
75 {
76         GHashKey *key = MEM_mallocN(sizeof(GHashKey), "Py i18n GHashKey");
77         key->msgctxt = BLI_strdup(msgctxt ? msgctxt : BLF_I18NCONTEXT_DEFAULT_BPY_INTERN);
78         key->msgid = BLI_strdup(msgid);
79         return key;
80 }
81
82 static unsigned int _ghashutil_keyhash(const void *ptr)
83 {
84         const GHashKey *key = ptr;
85         unsigned int hash =  BLI_ghashutil_strhash(key->msgctxt);
86         return hash ^ BLI_ghashutil_strhash(key->msgid);
87 }
88
89 static int _ghashutil_keycmp(const void *a, const void *b)
90 {
91         const GHashKey *A = a;
92         const GHashKey *B = b;
93
94         /* Note: comparing msgid first, most of the time it will be enough! */
95         int cmp = BLI_ghashutil_strcmp(A->msgid, B->msgid);
96         if (cmp == 0)
97                 return BLI_ghashutil_strcmp(A->msgctxt, B->msgctxt);
98         return cmp;
99 }
100
101 static void _ghashutil_keyfree(void *ptr)
102 {
103         const GHashKey *key = ptr;
104
105         /* We assume both msgctxt and msgid were BLI_strdup'ed! */
106         MEM_freeN((void *)key->msgctxt);
107         MEM_freeN((void *)key->msgid);
108         MEM_freeN((void *)key);
109 }
110
111 static void _ghashutil_valfree(void *ptr)
112 {
113         MEM_freeN(ptr);
114 }
115
116 /***** Python's messages cache *****/
117
118 /* We cache all messages available for a given locale from all py dicts into a single ghash.
119  * Changing of locale is not so common, while looking for a message translation is, so let's try to optimize
120  * the later as much as we can!
121  * Note changing of locale, as well as (un)registering a message dict, invalidate that cache.
122  */
123 static GHash *_translations_cache = NULL;
124
125 static void _clear_translations_cache(void)
126 {
127         if (_translations_cache) {
128                 BLI_ghash_free(_translations_cache, _ghashutil_keyfree, _ghashutil_valfree);
129         }
130         _translations_cache = NULL;
131 }
132
133 static void _build_translations_cache(PyObject *py_messages, const char *locale)
134 {
135         PyObject *uuid, *uuid_dict;
136         Py_ssize_t pos = 0;
137         char *language = NULL, *language_country = NULL, *language_variant = NULL;
138
139         /* For each py dict, we'll search for full locale, then language+country, then language+variant,
140          * then only language keys... */
141         BLF_locale_explode(locale, &language, NULL, NULL, &language_country, &language_variant);
142
143         /* Clear the cached ghash if needed, and create a new one. */
144         _clear_translations_cache();
145         _translations_cache = BLI_ghash_new(_ghashutil_keyhash, _ghashutil_keycmp, __func__);
146
147         /* Iterate over all py dicts. */
148         while (PyDict_Next(py_messages, &pos, &uuid, &uuid_dict)) {
149                 PyObject *lang_dict;
150
151 #if 0
152                 PyObject_Print(uuid_dict, stdout, 0);
153                 printf("\n");
154 #endif
155
156                 /* Try to get first complete locale, then language+country, then language+variant, then only language */
157                 lang_dict = PyDict_GetItemString(uuid_dict, locale);
158                 if (!lang_dict && language_country) {
159                         lang_dict = PyDict_GetItemString(uuid_dict, language_country);
160                         locale = language_country;
161                 }
162                 if (!lang_dict && language_variant) {
163                         lang_dict = PyDict_GetItemString(uuid_dict, language_variant);
164                         locale = language_variant;
165                 }
166                 if (!lang_dict && language) {
167                         lang_dict = PyDict_GetItemString(uuid_dict, language);
168                         locale = language;
169                 }
170
171                 if (lang_dict) {
172                         PyObject *pykey, *trans;
173                         Py_ssize_t ppos = 0;
174
175                         if (!PyDict_Check(lang_dict)) {
176                                 printf("WARNING! In translations' dict of \"");
177                                 PyObject_Print(uuid, stdout, Py_PRINT_RAW);
178                                 printf("\":\n");
179                                 printf("    Each language key must have a dictionary as value, \"%s\" is not valid, skipping: ",
180                                        locale);
181                                 PyObject_Print(lang_dict, stdout, Py_PRINT_RAW);
182                                 printf("\n");
183                                 continue;
184                         }
185
186                         /* Iterate over all translations of the found language dict, and populate our ghash cache. */
187                         while (PyDict_Next(lang_dict, &ppos, &pykey, &trans)) {
188                                 GHashKey *key;
189                                 const char *msgctxt = NULL, *msgid = NULL;
190                                 bool invalid_key = false;
191
192                                 if ((PyTuple_CheckExact(pykey) == false) || (PyTuple_GET_SIZE(pykey) != 2)) {
193                                         invalid_key = true;
194                                 }
195                                 else {
196                                         PyObject *tmp = PyTuple_GET_ITEM(pykey, 0);
197                                         if (tmp == Py_None) {
198                                                 msgctxt = BLF_I18NCONTEXT_DEFAULT;
199                                         }
200                                         else if (PyUnicode_Check(tmp)) {
201                                                 msgctxt = _PyUnicode_AsString(tmp);
202                                         }
203                                         else {
204                                                 invalid_key = true;
205                                         }
206
207                                         tmp = PyTuple_GET_ITEM(pykey, 1);
208                                         if (PyUnicode_Check(tmp)) {
209                                                 msgid = _PyUnicode_AsString(tmp);
210                                         }
211                                         else {
212                                                 invalid_key = true;
213                                         }
214                                 }
215
216                                 if (invalid_key) {
217                                         printf("WARNING! In translations' dict of \"");
218                                         PyObject_Print(uuid, stdout, Py_PRINT_RAW);
219                                         printf("\", %s language:\n", locale);
220                                         printf("    Keys must be tuples of (msgctxt [string or None], msgid [string]), "
221                                                "this one is not valid, skipping: ");
222                                         PyObject_Print(pykey, stdout, Py_PRINT_RAW);
223                                         printf("\n");
224                                         continue;
225                                 }
226                                 if (PyUnicode_Check(trans) == false) {
227                                         printf("WARNING! In translations' dict of \"");
228                                         PyObject_Print(uuid, stdout, Py_PRINT_RAW);
229                                         printf("\":\n");
230                                         printf("    Values must be strings, this one is not valid, skipping: ");
231                                         PyObject_Print(trans, stdout, Py_PRINT_RAW);
232                                         printf("\n");
233                                         continue;
234                                 }
235
236                                 key = _ghashutil_keyalloc(msgctxt, msgid);
237
238                                 /* Do not overwrite existing keys! */
239                                 if (BLI_ghash_lookup(_translations_cache, (void *)key)) {
240                                         continue;
241                                 }
242
243                                 BLI_ghash_insert(_translations_cache, key, BLI_strdup(_PyUnicode_AsString(trans)));
244                         }
245                 }
246         }
247
248         /* Clean up! */
249         if (language)
250                 MEM_freeN(language);
251         if (language_country)
252                 MEM_freeN(language_country);
253         if (language_variant)
254                 MEM_freeN(language_variant);
255 }
256
257 const char *BPY_app_translations_py_pgettext(const char *msgctxt, const char *msgid)
258 {
259 #define STATIC_LOCALE_SIZE 32  /* Should be more than enough! */
260
261         GHashKey *key;
262         static char locale[STATIC_LOCALE_SIZE] = "";
263         const char *tmp;
264
265         /* Just in case, should never happen! */
266         if (!_translations)
267                 return msgid;
268
269         tmp = BLF_lang_get();
270         if (strcmp(tmp, locale) || !_translations_cache) {
271                 PyGILState_STATE _py_state;
272
273                 BLI_strncpy(locale, tmp, STATIC_LOCALE_SIZE);
274
275                 /* Locale changed or cache does not exist, refresh the whole cache! */
276                 /* This func may be called from C (i.e. outside of python interpreter 'context'). */
277                 _py_state = PyGILState_Ensure();
278
279                 _build_translations_cache(_translations->py_messages, locale);
280
281                 PyGILState_Release(_py_state);
282         }
283
284         /* And now, simply create the key (context, messageid) and find it in the cached dict! */
285         key = _ghashutil_keyalloc(msgctxt, msgid);
286
287         tmp = BLI_ghash_lookup(_translations_cache, key);
288
289         _ghashutil_keyfree((void *)key);
290
291         if (tmp)
292                 return tmp;
293         return msgid;
294
295 #undef STATIC_LOCALE_SIZE
296 }
297
298 #endif  /* WITH_INTERNATIONAL */
299
300 PyDoc_STRVAR(app_translations_py_messages_register_doc,
301 ".. method:: register(module_name, translations_dict)\n"
302 "\n"
303 "   Registers an addon's UI translations.\n"
304 "\n"
305 "   Note: Does nothing when Blender is built without internationalization support.\n"
306 "\n"
307 "   :arg module_name: The name identifying the addon.\n"
308 "   :type module_name: string\n"
309 "   :arg translations_dict: A dictionary built like that:\n"
310 "       {locale: {msg_key: msg_translation, ...}, ...}\n"
311 "   :type translations_dict: dict\n"
312 "\n"
313 );
314 static PyObject *app_translations_py_messages_register(BlenderAppTranslations *self, PyObject *args, PyObject *kw)
315 {
316 #ifdef WITH_INTERNATIONAL
317         static const char *kwlist[] = {"module_name", "translations_dict", NULL};
318         PyObject *module_name, *uuid_dict;
319
320         if (!PyArg_ParseTupleAndKeywords(args, kw, "O!O!:bpy.app.translations.register", (char **)kwlist, &PyUnicode_Type,
321                                          &module_name, &PyDict_Type, &uuid_dict))
322         {
323                 return NULL;
324         }
325
326         if (PyDict_Contains(self->py_messages, module_name)) {
327                 PyErr_Format(PyExc_ValueError,
328                              "bpy.app.translations.register: translations message cache already contains some data for "
329                              "addon '%s'", (const char *)_PyUnicode_AsString(module_name));
330                 return NULL;
331         }
332
333         PyDict_SetItem(self->py_messages, module_name, uuid_dict);
334
335         /* Clear cached messages dict! */
336         _clear_translations_cache();
337 #else
338         (void)self;
339         (void)args;
340         (void)kw;
341 #endif
342
343         /* And we are done! */
344         Py_RETURN_NONE;
345 }
346
347 PyDoc_STRVAR(app_translations_py_messages_unregister_doc,
348 ".. method:: unregister(module_name)\n"
349 "\n"
350 "   Unregisters an addon's UI translations.\n"
351 "\n"
352 "   Note: Does nothing when Blender is built without internationalization support.\n"
353 "\n"
354 "   :arg module_name: The name identifying the addon.\n"
355 "   :type module_name: string\n"
356 "\n"
357 );
358 static PyObject *app_translations_py_messages_unregister(BlenderAppTranslations *self, PyObject *args, PyObject *kw)
359 {
360 #ifdef WITH_INTERNATIONAL
361         static const char *kwlist[] = {"module_name", NULL};
362         PyObject *module_name;
363
364         if (!PyArg_ParseTupleAndKeywords(args, kw, "O!:bpy.app.translations.unregister", (char **)kwlist, &PyUnicode_Type,
365                                          &module_name))
366         {
367                 return NULL;
368         }
369
370         if (PyDict_Contains(self->py_messages, module_name)) {
371                 PyDict_DelItem(self->py_messages, module_name);
372                 /* Clear cached messages ghash! */
373                 _clear_translations_cache();
374         }
375 #else
376         (void)self;
377         (void)args;
378         (void)kw;
379 #endif
380
381         /* And we are done! */
382         Py_RETURN_NONE;
383 }
384
385 /***** C-defined contexts *****/
386 /* This is always available (even when WITH_INTERNATIONAL is not defined). */
387
388 static PyTypeObject BlenderAppTranslationsContextsType;
389
390 static BLF_i18n_contexts_descriptor _contexts[] = BLF_I18NCONTEXTS_DESC;
391
392 /* These fields are just empty placeholders, actual values get set in app_translations_struct().
393  * This allows us to avoid many handwriting, and above all, to keep all context definition stuff in BLF_translation.h!
394  */
395 static PyStructSequence_Field
396 app_translations_contexts_fields[sizeof(_contexts) / sizeof(BLF_i18n_contexts_descriptor)] = {{NULL}};
397
398 static PyStructSequence_Desc app_translations_contexts_desc = {
399         (char *)"bpy.app.translations.contexts",     /* name */
400         (char *)"This named tuple contains all pre-defined translation contexts",    /* doc */
401         app_translations_contexts_fields,    /* fields */
402         (sizeof(app_translations_contexts_fields) / sizeof(PyStructSequence_Field)) - 1
403 };
404
405 static PyObject *app_translations_contexts_make(void)
406 {
407         PyObject *translations_contexts;
408         BLF_i18n_contexts_descriptor *ctxt;
409         int pos = 0;
410
411         translations_contexts = PyStructSequence_New(&BlenderAppTranslationsContextsType);
412         if (translations_contexts == NULL) {
413                 return NULL;
414         }
415
416 #define SetObjString(item) PyStructSequence_SET_ITEM(translations_contexts, pos++, PyUnicode_FromString((item)))
417 #define SetObjNone() Py_INCREF(Py_None); PyStructSequence_SET_ITEM(translations_contexts, pos++, Py_None)
418
419         for (ctxt = _contexts; ctxt->c_id; ctxt++) {
420                 if (ctxt->value) {
421                         SetObjString(ctxt->value);
422                 }
423                 else {
424                         SetObjNone();
425                 }
426         }
427
428 #undef SetObjString
429 #undef SetObjNone
430
431         return translations_contexts;
432 }
433
434 /***** Main BlenderAppTranslations Py object definition *****/
435
436 PyDoc_STRVAR(app_translations_contexts_doc,
437         "A named tuple containing all pre-defined translation contexts.\n"
438         "WARNING: do not use the \"" BLF_I18NCONTEXT_DEFAULT_BPY_INTERN "\" context, it is internally assimilated as the "
439         "default one!\n"
440 );
441
442 PyDoc_STRVAR(app_translations_contexts_C_to_py_doc,
443         "A readonly dict mapping contexts' C-identifiers to their py-identifiers."
444 );
445
446 PyMemberDef app_translations_members[] = {
447         {(char *)"contexts", T_OBJECT_EX, offsetof(BlenderAppTranslations, contexts), READONLY,
448                              app_translations_contexts_doc},
449         {(char *)"contexts_C_to_py", T_OBJECT_EX, offsetof(BlenderAppTranslations, contexts_C_to_py), READONLY,
450                                      app_translations_contexts_C_to_py_doc},
451         {NULL}
452 };
453
454 PyDoc_STRVAR(app_translations_locale_doc,
455         "The actual locale currently in use (will always return a void string when Blender is built without "
456         "internationalization support)."
457 );
458 static PyObject *app_translations_locale_get(PyObject *UNUSED(self), void *UNUSED(userdata))
459 {
460         return PyUnicode_FromString(BLF_lang_get());
461 }
462
463 /* Note: defining as getter, as (even if quite unlikely), this *may* change during runtime... */
464 PyDoc_STRVAR(app_translations_locales_doc, "All locales currently known by Blender (i.e. available as translations).");
465 static PyObject *app_translations_locales_get(PyObject *UNUSED(self), void *UNUSED(userdata))
466 {
467         PyObject *ret;
468         EnumPropertyItem *it, *items = BLF_RNA_lang_enum_properties();
469         int num_locales = 0, pos = 0;
470
471         if (items) {
472                 /* This is not elegant, but simple! */
473                 for (it = items; it->identifier; it++) {
474                         if (it->value)
475                                 num_locales++;
476                 }
477         }
478
479         ret = PyTuple_New(num_locales);
480
481         if (items) {
482                 for (it = items; it->identifier; it++) {
483                         if (it->value)
484                                 PyTuple_SET_ITEM(ret, pos++, PyUnicode_FromString(it->description));
485                 }
486         }
487
488         return ret;
489 }
490
491 PyGetSetDef app_translations_getseters[] = {
492         /* {name, getter, setter, doc, userdata} */
493         {(char *)"locale", (getter)app_translations_locale_get, NULL, app_translations_locale_doc, NULL},
494         {(char *)"locales", (getter)app_translations_locales_get, NULL, app_translations_locales_doc, NULL},
495         {NULL}
496 };
497
498 PyDoc_STRVAR(app_translations_pgettext_doc,
499 ".. method:: pgettext(msgid, msgctxt)\n"
500 "\n"
501 "   Try to translate the given msgid (with optional msgctxt).\n"
502 "   NOTE: The (msgid, msgctxt) parameter orders has been switched compared to gettext function, to allow\n"
503 "         single-parameter calls (context then defaults to BLF_I18NCONTEXT_DEFAULT).\n"
504 "   NOTE: You should really rarely need to use this function in regular addon code, as all translation should be\n"
505 "         handled by Blender internal code.\n"
506 "   Note: Does nothing when Blender is built without internationalization support (hence always returns msgid).\n"
507 "\n"
508 "   :arg msgid: The string to translate.\n"
509 "   :type msgid: string\n"
510 "   :arg msgctxt: The translation context.\n"
511 "   :type msgctxt: string or None\n"
512 "   :default msgctxt: BLF_I18NCONTEXT_DEFAULT value.\n"
513 "   :return: The translated string (or msgid if no translation was found).\n"
514 "\n"
515 );
516 static PyObject *app_translations_pgettext(BlenderAppTranslations *UNUSED(self), PyObject *args, PyObject *kw)
517 {
518         /* Note we could optimize this a bit when WITH_INTERNATIONAL is not defined, but don't think "code complexity" would
519          * be worth it, as this func should not often be used!
520          */
521         /* XXX This code fails with scons when WITH_INTERNATIONAL is not defined, at link time, stating that BLF_pgettext
522          * is undefined... So using #ifdef after all, rather than removing scons from blender trunk!
523          */
524         static const char *kwlist[] = {"msgid", "msgctxt", NULL};
525         char *msgid, *msgctxt = NULL;
526
527         if (!PyArg_ParseTupleAndKeywords(args, kw, "s|z:bpy.app.translations.pgettext", (char **)kwlist,
528                                          &msgid, &msgctxt))
529         {
530                 return NULL;
531         }
532
533 #ifdef WITH_INTERNATIONAL
534         return PyUnicode_FromString(BLF_pgettext(msgctxt ? msgctxt : BLF_I18NCONTEXT_DEFAULT, msgid));
535 #else
536         return PyUnicode_FromString(msgid);
537 #endif
538 }
539
540 PyDoc_STRVAR(app_translations_locale_explode_doc,
541 ".. method:: locale_explode(locale)\n"
542 "\n"
543 "   Return all components and their combinations  of the given ISO locale string.\n"
544 "\n"
545 "   >>> bpy.app.translations.locale_explode(\"sr_RS@latin\")\n"
546 "   (\"sr\", \"RS\", \"latin\", \"sr_RS\", \"sr@latin\")\n"
547 "\n"
548 "   For non-complete locales, missing elements will be None.\n"
549 "\n"
550 "   :arg locale: The ISO locale string to explode.\n"
551 "   :type msgid: string\n"
552 "   :return: A tuple (language, country, variant, language_country, language@variant).\n"
553 "\n"
554 );
555 static PyObject *app_translations_locale_explode(BlenderAppTranslations *UNUSED(self), PyObject *args, PyObject *kw)
556 {
557         static const char *kwlist[] = {"locale", NULL};
558         const char *locale;
559         char *language, *country, *variant, *language_country, *language_variant;
560
561         if (!PyArg_ParseTupleAndKeywords(args, kw, "s:bpy.app.translations.locale_explode", (char **)kwlist, &locale)) {
562                 return NULL;
563         }
564
565         BLF_locale_explode(locale, &language, &country, &variant, &language_country, &language_variant);
566
567         return Py_BuildValue("sssss", language, country, variant, language_country, language_variant);
568 }
569
570 PyMethodDef app_translations_methods[] = {
571         /* Can't use METH_KEYWORDS alone, see http://bugs.python.org/issue11587 */
572         {(char *)"register", (PyCFunction)app_translations_py_messages_register, METH_VARARGS | METH_KEYWORDS,
573                              app_translations_py_messages_register_doc},
574         {(char *)"unregister", (PyCFunction)app_translations_py_messages_unregister, METH_VARARGS | METH_KEYWORDS,
575                                app_translations_py_messages_unregister_doc},
576         {(char *)"pgettext", (PyCFunction)app_translations_pgettext, METH_VARARGS | METH_KEYWORDS | METH_STATIC,
577                              app_translations_pgettext_doc},
578         {(char *)"locale_explode", (PyCFunction)app_translations_locale_explode, METH_VARARGS | METH_KEYWORDS | METH_STATIC,
579                                    app_translations_locale_explode_doc},
580         {NULL}
581 };
582
583 static PyObject *app_translations_new(PyTypeObject *type, PyObject *UNUSED(args), PyObject *UNUSED(kw))
584 {
585 /*      printf("%s (%p)\n", __func__, _translations); */
586
587         if (!_translations) {
588                 _translations = (BlenderAppTranslations *)type->tp_alloc(type, 0);
589                 if (_translations) {
590                         PyObject *py_ctxts;
591                         BLF_i18n_contexts_descriptor *ctxt;
592
593                         _translations->contexts = app_translations_contexts_make();
594
595                         py_ctxts = PyDict_New();
596                         for (ctxt = _contexts; ctxt->c_id; ctxt++) {
597                                 PyObject *val = PyUnicode_FromString(ctxt->py_id);
598                                 PyDict_SetItemString(py_ctxts, ctxt->c_id, val);
599                                 Py_DECREF(val);
600                         }
601                         _translations->contexts_C_to_py = PyDictProxy_New(py_ctxts);
602                         Py_DECREF(py_ctxts);  /* The actual dict is only owned by its proxy */
603
604                         _translations->py_messages = PyDict_New();
605                 }
606         }
607
608         return (PyObject *)_translations;
609 }
610
611 static void app_translations_free(void *obj)
612 {
613         PyObject_Del(obj);
614 #ifdef WITH_INTERNATIONAL
615         _clear_translations_cache();
616 #endif
617 }
618
619 PyDoc_STRVAR(app_translations_doc,
620 "   This object contains some data/methods regarding internationalization in Blender, and allows every py script\n"
621 "   to feature translations for its own UI messages.\n"
622 "\n"
623 "   WARNING: Most of this object should only be useful if you actually manipulate i18n stuff from Python.\n"
624 "            If you are a regular addon, you should only bother about :contexts: and :context_sep: members, and the \n"
625 "            :register:/:unregister: functions!"
626 "\n"
627 "   To add translations to your python script, you must define a dictionary formatted like that:\n"
628 "       {locale: {msg_key: msg_translation, ...}, ...}\n"
629 "       where:\n"
630 "           locale is either a lang iso code (e.g. 'fr'), a lang+country code (e.g. 'pt_BR'),\n"
631 "                  a lang+variant code (e.g. 'sr@latin'), or a full code (e.g. 'uz_UZ@cyrilic').\n"
632 "           msg_key is a tuple (context, org message) - use, as much as possible, the predefined :contexts:.\n"
633 "           msg_translation is the translated message in given language!"
634 "   Then, call bpy.app.translations.register(__name__, your_dict) in your register() function, and \n"
635 "   bpy.app.translations.unregister(__name__) in your unregister() one.\n"
636 "\n"
637 "   bl_i18n_utils module has several functions to help you collect strings to translate, and generate the needed\n"
638 "   python code (the translation dictionary), as well as optional intermediary po files if you want some...\n"
639 "   See its documentation for more details.\n"
640 "\n"
641 );
642 static PyTypeObject BlenderAppTranslationsType = {
643         PyVarObject_HEAD_INIT(NULL, 0)
644                                     /* tp_name */
645         (char *)"bpy.app._translations_type",
646                                     /* tp_basicsize */
647         sizeof(BlenderAppTranslations),
648         0,                          /* tp_itemsize */
649         /* methods */
650         /* No destructor, this is a singleton! */
651         NULL,                       /* tp_dealloc */
652         NULL,                       /* printfunc tp_print; */
653         NULL,                       /* getattrfunc tp_getattr; */
654         NULL,                       /* setattrfunc tp_setattr; */
655         NULL,                       /* tp_compare */ /* DEPRECATED in python 3.0! */
656         NULL,                       /* tp_repr */
657
658         /* Method suites for standard classes */
659         NULL,                       /* PyNumberMethods *tp_as_number; */
660         NULL,                       /* PySequenceMethods *tp_as_sequence; */
661         NULL,                       /* PyMappingMethods *tp_as_mapping; */
662
663         /* More standard operations (here for binary compatibility) */
664         NULL,                       /* hashfunc tp_hash; */
665         NULL,                       /* ternaryfunc tp_call; */
666         NULL,                       /* reprfunc tp_str; */
667         NULL,                       /* getattrofunc tp_getattro; */
668         NULL,                       /* setattrofunc tp_setattro; */
669
670         /* Functions to access object as input/output buffer */
671         NULL,                       /* PyBufferProcs *tp_as_buffer; */
672
673         /*** Flags to define presence of optional/expanded features ***/
674         Py_TPFLAGS_DEFAULT,         /* long tp_flags; */
675
676         app_translations_doc,       /* char *tp_doc;  Documentation string */
677
678         /*** Assigned meaning in release 2.0 ***/
679         /* call function for all accessible objects */
680         NULL,                       /* traverseproc tp_traverse; */
681
682         /* delete references to contained objects */
683         NULL,                       /* inquiry tp_clear; */
684
685         /***  Assigned meaning in release 2.1 ***/
686         /*** rich comparisons ***/
687         NULL,                       /* richcmpfunc tp_richcompare; */
688
689         /***  weak reference enabler ***/
690         0,                          /* long tp_weaklistoffset */
691
692         /*** Added in release 2.2 ***/
693         /*   Iterators */
694         NULL,                       /* getiterfunc tp_iter; */
695         NULL,                       /* iternextfunc tp_iternext; */
696
697         /*** Attribute descriptor and subclassing stuff ***/
698         app_translations_methods,   /* struct PyMethodDef *tp_methods; */
699         app_translations_members,   /* struct PyMemberDef *tp_members; */
700         app_translations_getseters, /* struct PyGetSetDef *tp_getset; */
701         NULL,                       /* struct _typeobject *tp_base; */
702         NULL,                       /* PyObject *tp_dict; */
703         NULL,                       /* descrgetfunc tp_descr_get; */
704         NULL,                       /* descrsetfunc tp_descr_set; */
705         0,                          /* long tp_dictoffset; */
706         NULL,                       /* initproc tp_init; */
707         NULL,                       /* allocfunc tp_alloc; */
708                                     /* newfunc tp_new; */
709         (newfunc)app_translations_new,
710         /*  Low-level free-memory routine */
711         app_translations_free,                       /* freefunc tp_free;  */
712         /* For PyObject_IS_GC */
713         NULL,                       /* inquiry tp_is_gc;  */
714         NULL,                       /* PyObject *tp_bases; */
715         /* method resolution order */
716         NULL,                       /* PyObject *tp_mro;  */
717         NULL,                       /* PyObject *tp_cache; */
718         NULL,                       /* PyObject *tp_subclasses; */
719         NULL,                       /* PyObject *tp_weaklist; */
720         NULL
721 };
722
723 PyObject *BPY_app_translations_struct(void)
724 {
725         PyObject *ret;
726
727         /* Let's finalize our contexts structseq definition! */
728         {
729                 BLF_i18n_contexts_descriptor *ctxt;
730                 PyStructSequence_Field *desc;
731
732                 /* We really populate the contexts' fields here! */
733                 for (ctxt = _contexts, desc = app_translations_contexts_desc.fields; ctxt->c_id; ctxt++, desc++) {
734                         desc->name = (char *)ctxt->py_id;
735                         desc->doc = NULL;
736                 }
737                 desc->name = desc->doc = NULL;  /* End sentinel! */
738
739                 PyStructSequence_InitType(&BlenderAppTranslationsContextsType, &app_translations_contexts_desc);
740         }
741
742         if (PyType_Ready(&BlenderAppTranslationsType) < 0)
743                 return NULL;
744
745         ret = PyObject_CallObject((PyObject *)&BlenderAppTranslationsType, NULL);
746
747         /* prevent user from creating new instances */
748         BlenderAppTranslationsType.tp_new = NULL;
749         /* without this we can't do set(sys.modules) [#29635] */
750         BlenderAppTranslationsType.tp_hash = (hashfunc)_Py_HashPointer;
751
752         return ret;
753 }