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