summaryrefslogtreecommitdiffstats
path: root/src/extension
diff options
context:
space:
mode:
authorPatrick Storz <eduard.braun2@gmx.de>2019-10-17 00:16:32 +0000
committerPatrick Storz <eduard.braun2@gmx.de>2019-10-19 12:33:34 +0000
commit76ce73061550ff42e97c1fec9836c35cc0e24d64 (patch)
tree9ea6f017e13bc4f5cc63eb17f8a23e1e58ccee3a /src/extension
parentAdd export/import PDF blend modes and add isolation modifier (diff)
downloadinkscape-76ce73061550ff42e97c1fec9836c35cc0e24d64.tar.gz
inkscape-76ce73061550ff42e97c1fec9836c35cc0e24d64.zip
Extensions: Implement translationdomain functionality
Inkscape will read the "translationdomain" attribute from the <inkscape-extension> root element in the .inx file. It will then attempt to lookup a message catalog that matches this domain, register it with gettext, and use it for translations. Message catalogs may be located in either - the .inx file's location - the root of the "extensions" directory containing the .inx - the system location Inkscape's own catalog is loaded from To make this functionality available to script extensions, Inkscape will set the environment variables INKEX_GETTEXT_DOMAIN and INKEX_GETTEXT_DIRECTORY so extension scripts can then use something like bindtextdomain(INKEX_GETTEXT_DOMAIN, INKEX_GETTEXT_DIRECTORY) textdomain(INKEX_GETTEXT_DOMAIN) to enable the feature. See also https://gitlab.com/inkscape/inkscape/issues/333 https://gitlab.com/inkscape/extensions/issues/117
Diffstat (limited to 'src/extension')
-rw-r--r--src/extension/extension.cpp129
-rw-r--r--src/extension/extension.h6
-rw-r--r--src/extension/implementation/script.cpp13
3 files changed, 136 insertions, 12 deletions
diff --git a/src/extension/extension.cpp b/src/extension/extension.cpp
index 02f7f517d..9089dc950 100644
--- a/src/extension/extension.cpp
+++ b/src/extension/extension.cpp
@@ -19,6 +19,9 @@
#include "extension.h"
#include "implementation/implementation.h"
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
#include <glib/gstdio.h>
#include <glib/gprintf.h>
@@ -80,14 +83,22 @@ Extension::Extension(Inkscape::XML::Node *in_repr, Implementation::Implementatio
}
// get name of the translation catalog ("gettext textdomain") that the extension wants to use for translations
+ // and lookup the locale directory for it
const char *translationdomain = repr->attribute("translationdomain");
if (translationdomain) {
- _translationdomain = g_strdup(translationdomain);
-
+ _translationdomain = translationdomain;
+ } else {
+ _translationdomain = "inkscape"; // default to the Inkscape catalog
+ }
+ if (!strcmp(_translationdomain, "none")) {
// special keyword "none" means the extension author does not want translation of extension strings
- if (!strcmp(translationdomain, "none")) {
- _translation_enabled = false;
- }
+ _translation_enabled = false;
+ _translationdomain = nullptr;
+ } else if (!strcmp(_translationdomain, "inkscape")) {
+ // this is our default domain; we know the location already (also respects INKSCAPE_LOCALEDIR)
+ _gettext_catalog_dir = bindtextdomain("inkscape", nullptr);
+ } else {
+ lookup_translation_catalog();
}
// Read XML tree and parse extension
@@ -425,6 +436,95 @@ std::string Extension::get_dependency_location(const char *name)
return "";
}
+/** recursively searches directory for a file named filename; returns true if found */
+static bool _find_filename_recursive(std::string directory, std::string filename) {
+ Glib::Dir dir(directory);
+
+ std::string name = dir.read_name();
+ while (!name.empty()) {
+ std::string fullpath = Glib::build_filename(directory, name);
+ // g_message("%s", fullpath.c_str());
+
+ if (Glib::file_test(fullpath, Glib::FILE_TEST_IS_DIR)) {
+ if (_find_filename_recursive(fullpath, filename)) {
+ return true;
+ }
+ } else if (name == filename) {
+ return true;
+ }
+ name = dir.read_name();
+ }
+
+ return false;
+}
+
+/** Searches for a gettext catalog matching the extension's translationdomain
+ *
+ * This function will attempt to find the correct gettext catalog for the translationdomain
+ * requested by the extension.
+ *
+ * For this the following three locations are recursively searched for "${translationdomain}.mo":
+ * - the 'locale' directory in the .inx file's folder
+ * - the 'locale' directory in the "extensions" folder containing the .inx
+ * - the system location for gettext catalogs, i.e. where Inkscape's own catalog is located
+ *
+ * If one matching file is found, the directory is assumed to be the correct location and registered with gettext
+ */
+void Extension::lookup_translation_catalog() {
+ g_assert(!_base_directory.empty());
+
+ // get locale folder locations
+ std::string locale_dir_current_extension;
+ std::string locale_dir_extensions;
+ std::string locale_dir_system;
+
+ locale_dir_current_extension = Glib::build_filename(_base_directory, "locale");
+
+ size_t index = _base_directory.find_last_of("extensions");
+ if (index != std::string::npos) {
+ locale_dir_extensions = Glib::build_filename(_base_directory.substr(0, index+1), "locale");
+ }
+
+ locale_dir_system = bindtextdomain("inkscape", nullptr);
+
+ // collect unique locations into vector
+ std::vector<std::string> locale_dirs;
+ if (locale_dir_current_extension != locale_dir_extensions) {
+ locale_dirs.push_back(std::move(locale_dir_current_extension));
+ }
+ locale_dirs.push_back(std::move(locale_dir_extensions));
+ locale_dirs.push_back(std::move(locale_dir_system));
+
+ // iterate over locations and look for the one that has the correct catalog
+ std::string search_name;
+ search_name += _translationdomain;
+ search_name += ".mo";
+ for (auto locale_dir : locale_dirs) {
+ if (!Glib::file_test(locale_dir, Glib::FILE_TEST_IS_DIR)) {
+ continue;
+ }
+
+ if (_find_filename_recursive(locale_dir, search_name)) {
+ _gettext_catalog_dir = locale_dir;
+ break;
+ }
+ }
+
+ // register catalog with gettext if found, disable translation for this extension otherwise
+ if (!_gettext_catalog_dir.empty()) {
+ const char *current_dir = bindtextdomain(_translationdomain, nullptr);
+ if (_gettext_catalog_dir != current_dir) {
+ g_info("Binding textdomain '%s' to '%s'.", _translationdomain, _gettext_catalog_dir.c_str());
+ bindtextdomain(_translationdomain, _gettext_catalog_dir.c_str());
+ bind_textdomain_codeset(_translationdomain, "UTF-8");
+ }
+ } else {
+ g_warning("Failed to locate message catalog for textdomain '%s'.", _translationdomain);
+ _translation_enabled = false;
+ _translationdomain = nullptr;
+ }
+}
+
/** Gets a translation within the context of the current extension
*
* Query gettext for the translated version of the input string,
@@ -440,8 +540,6 @@ const char *Extension::get_translation(const char *msgid, const char *msgctxt) {
return msgid;
}
- // Note: _translationdomain might be NULL, which is fine.
- // We will simply default to the domain set via textdomain() in this case (which should be 'inkscape')
if (msgctxt) {
return g_dpgettext2(_translationdomain, msgctxt, msgid);
} else {
@@ -449,6 +547,23 @@ const char *Extension::get_translation(const char *msgid, const char *msgctxt) {
}
}
+/** Sets environment suitable for executing this Extension
+ *
+ * Currently sets the environment variables INKEX_GETTEXT_DOMAIN and INKEX_GETTEXT_DIRECTORY
+ * to make the "translationdomain" accessible to child processes spawned by this extension's Implementation.
+ */
+void Extension::set_environment() {
+ Glib::unsetenv("INKEX_GETTEXT_DOMAIN");
+ Glib::unsetenv("INKEX_GETTEXT_DIRECTORY");
+
+ if (_translationdomain) {
+ Glib::setenv("INKEX_GETTEXT_DOMAIN", std::string(_translationdomain));
+ }
+ if (!_gettext_catalog_dir.empty()) {
+ Glib::setenv("INKEX_GETTEXT_DIRECTORY", _gettext_catalog_dir);
+ }
+}
+
/**
\brief A function to get the parameters in a string form
\return An array with all the parameters in it.
diff --git a/src/extension/extension.h b/src/extension/extension.h
index 2ddd8f3f4..476be37d5 100644
--- a/src/extension/extension.h
+++ b/src/extension/extension.h
@@ -126,8 +126,13 @@ protected:
* relative paths in the extension should usually be relative to it */
ExpirationTimer * timer = nullptr; /**< Timeout to unload after a given time */
bool _translation_enabled = true; /**< Attempt translation of strings provided by the extension? */
+
+private:
const char *_translationdomain = nullptr; /**< Domainname of gettext textdomain that should
* be used for translation of the extension's strings */
+ std::string _gettext_catalog_dir; /**< Directory containing the gettext catalog for _translationdomain */
+
+ void lookup_translation_catalog();
public:
Extension(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory);
@@ -151,6 +156,7 @@ public:
void set_base_directory(std::string base_directory) { _base_directory = base_directory; };
std::string get_dependency_location(const char *name);
const char *get_translation(const char* msgid, const char *msgctxt=nullptr);
+ void set_environment();
/* Parameter Stuff */
private:
diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp
index c38cc516f..12e4e809b 100644
--- a/src/extension/implementation/script.cpp
+++ b/src/extension/implementation/script.cpp
@@ -124,7 +124,7 @@ std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpName
// on Windows, so no extra code is necessary.
if (!Glib::path_is_absolute(interpreter_path)) {
std::string found_path = Glib::find_program_in_path(interpreter_path);
- if (found_path.empty()) {
+ if (found_path.empty()) {
g_critical("Script::resolveInterpreterExecutable(): failed to locate script interpreter '%s'; "
"'%s' not found on PATH", interpNameArg.c_str(), interpreter_path.c_str());
}
@@ -247,7 +247,7 @@ bool Script::check(Inkscape::Extension::Extension *module)
while (child_repr != nullptr) {
if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
script_count++;
-
+
// check if all helper_extensions attached to this script were registered
child_repr = child_repr->firstChild();
while (child_repr != nullptr) {
@@ -359,7 +359,7 @@ Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
the header of ink_ext_.
The extension is then executed using the 'execute' function
- with the filename assigned and then the temporary filename.
+ with the filename assigned and then the temporary filename.
After execution the SVG should be in the temporary file.
Finally, the temporary file is opened using the SVG input module and
@@ -372,6 +372,7 @@ SPDocument *Script::open(Inkscape::Extension::Input *module,
{
std::list<std::string> params;
module->paramListString(params);
+ module->set_environment();
std::string tempfilename_out;
int tempfd_out = 0;
@@ -447,6 +448,7 @@ void Script::save(Inkscape::Extension::Output *module,
{
std::list<std::string> params;
module->paramListString(params);
+ module->set_environment();
std::string tempfilename_in;
int tempfd_in = 0;
@@ -545,6 +547,7 @@ void Script::effect(Inkscape::Extension::Effect *module,
std::list<std::string> params;
module->paramListString(params);
+ module->set_environment();
parent_window = module->get_execution_env()->get_working_dialog();
@@ -616,7 +619,7 @@ void Script::effect(Inkscape::Extension::Effect *module,
// Getting the named view from the document generated by the extension
SPNamedView *nv = sp_document_namedview(mydoc, nullptr);
-
+
//Check if it has a default layer set up
SPObject *layer = nullptr;
if ( nv != nullptr)
@@ -631,7 +634,7 @@ void Script::effect(Inkscape::Extension::Effect *module,
}
desktop->showGrids(nv->grids_visible);
}
-
+
sp_namedview_update_layers_from_document(desktop);
//If that layer exists,
if (layer) {