From ea238e4e5aeb003bca9160939f02208f66d4df11 Mon Sep 17 00:00:00 2001 From: lloyd konneker Date: Sat, 1 Feb 2025 15:43:00 -0500 Subject: [PATCH] ScriptFu: fix #12837 i18n for independent scripts See /scripts/test/test-i18n.scm, which has tests and documents use cases. Some incidental refactoring and editing of comments. --- .../script-fu-interpreter-plugin.c | 102 +++++++- .../interpreter/script-fu-interpreter.c | 222 +++++++++++++----- .../interpreter/script-fu-interpreter.h | 10 +- .../script-fu/libscriptfu/scheme-wrapper.c | 11 + .../script-fu/libscriptfu/script-fu-lib.c | 61 +++-- .../script-fu/libscriptfu/script-fu-lib.h | 13 +- .../libscriptfu/script-fu-proc-factory.c | 22 +- .../libscriptfu/script-fu-proc-factory.h | 1 - .../script-fu/libscriptfu/script-fu-script.c | 36 +++ .../script-fu/libscriptfu/script-fu-script.h | 7 + .../script-fu/libscriptfu/script-fu-scripts.c | 159 +++++++++++-- .../script-fu/libscriptfu/script-fu-scripts.h | 5 +- .../script-fu/libscriptfu/script-fu-types.h | 2 + plug-ins/script-fu/libscriptfu/script-fu.def | 5 +- plug-ins/script-fu/scripts/test-sphere-v3.scm | 3 + plug-ins/script-fu/scripts/test/meson.build | 4 + .../script-fu/scripts/test/testi18n/LINGUAS | 2 + .../script-fu/scripts/test/testi18n/de.po | 17 ++ .../script-fu/scripts/test/testi18n/es.po | 17 ++ .../scripts/test/testi18n/meson.build | 44 ++++ .../scripts/test/testi18n/test-i18n-more.scm | 41 ++++ .../scripts/test/testi18n/test-i18n.scm | 202 ++++++++++++++++ 22 files changed, 866 insertions(+), 120 deletions(-) create mode 100644 plug-ins/script-fu/scripts/test/testi18n/LINGUAS create mode 100644 plug-ins/script-fu/scripts/test/testi18n/de.po create mode 100644 plug-ins/script-fu/scripts/test/testi18n/es.po create mode 100644 plug-ins/script-fu/scripts/test/testi18n/meson.build create mode 100644 plug-ins/script-fu/scripts/test/testi18n/test-i18n-more.scm create mode 100644 plug-ins/script-fu/scripts/test/testi18n/test-i18n.scm diff --git a/plug-ins/script-fu/interpreter/script-fu-interpreter-plugin.c b/plug-ins/script-fu/interpreter/script-fu-interpreter-plugin.c index 9f0b42253f..8a0112270e 100644 --- a/plug-ins/script-fu/interpreter/script-fu-interpreter-plugin.c +++ b/plug-ins/script-fu/interpreter/script-fu-interpreter-plugin.c @@ -52,6 +52,18 @@ G_DEFINE_TYPE (ScriptFuInterpreter, script_fu_interpreter, GIMP_TYPE_PLUG_IN) */ static gchar * path_to_this_script; + +/* For any plugin interpreted by self, these are the names, not always enforced: + * plugin file name is plugin-.scm (it has shebang) + * progname is the same (not the name of the interpreter) + * plugin name is plugin- + * PDB procedure name is plugin- + * run_func in Scheme is named plugin- (not script-fu-) + * C run func called by GIMP is e.g. script_fu_run_image_procedure + */ + + + /* Connect to Gimp. See libgimp/gimp.c. * * Can't use GIMP_MAIN macro, it doesn't omit argv[0]. @@ -84,6 +96,79 @@ int main (int argc, char *argv[]) g_debug ("Exit script-fu-interpreter."); } +/* A callback from GIMP. + * A method of GimpPlugin. + * GIMP calls often, before any phase (query, create, init, run.) + * + * It is only necessary before the create phase, + * when we declare args and menu item possibly requiring i18n. + * FUTURE: avoid this work for phases other than create and run. + * + * Since it is *before* the create phase, + * SF has not read the script and interpreted it's registration functions, + * especially a call to script-fu-register-i18n + * We must do that to get the declared i18n domain and catalog. + */ +static gboolean +script_fu_set_i18n (GimpPlugIn *plug_in, + const gchar *procedure_name, + gchar **gettext_domain, + gchar **catalog_dir) +{ + gchar *declared_i18n_domain = NULL; + gchar *declared_i18n_catalog = NULL; + gboolean result; + + /* assert that *gettext_domain and *catalog_dir are NULL and don't need free. */ + + g_debug ("%s", G_STRFUNC); + + /* Get script author's declared i18n into local vars.*/ + script_fu_interpreter_get_i18n ( plug_in, + procedure_name, + path_to_this_script, + &declared_i18n_domain, + &declared_i18n_catalog); + + /* Convert script declared i18n keywords to other values. */ + if (declared_i18n_domain == NULL || + g_strcmp0 (declared_i18n_domain, "None") == 0) + { + /* The script has not called script-fu-register-i18n. + * OR with domain_name of "None". + * Return FALSE to mean: no translations. + */ + *gettext_domain = NULL; + result = FALSE; + } + else if ( g_strcmp0 (declared_i18n_domain, "Standard") == 0) + { + /* Script author wants default domain name and catalog. + * Set to NULL, and return TRUE tells GimpPlugin to use default. + */ + *gettext_domain = NULL; + *catalog_dir = NULL; + result = TRUE; /* want translation. */ + } + else + { + /* Script author provided non-standard domain and catalog. + * Return allocated copy to caller. + */ + *gettext_domain = g_strdup (declared_i18n_domain); + *catalog_dir = g_strdup (declared_i18n_catalog); + result = TRUE; /* want translation. */ + } + + g_debug ("%s returns %d domain %s catalog %s", + G_STRFUNC, result, declared_i18n_domain, declared_i18n_catalog); + + g_free (declared_i18n_domain); + g_free (declared_i18n_catalog); + + return result; +} + static void script_fu_interpreter_class_init (ScriptFuInterpreterClass *klass) { @@ -92,17 +177,12 @@ script_fu_interpreter_class_init (ScriptFuInterpreterClass *klass) plug_in_class->query_procedures = script_fu_interpreter_query_procedures; plug_in_class->create_procedure = script_fu_interpreter_create_procedure; - /* Do not override virtual method set_i18n. + /* Override virtual method set_i18n. * Default implementation finds translations in: * GIMP's .../plug-ins//locale//LC_MESSAGES/.mo - * - * For any plugin interpreted by self: - * plugin file name is plugin-.scm (it has shebang) - * progname is the same (not the name of the interpreter) - * plugin name is plugin- - * PDB procedure name is plugin- - * run_func in Scheme is named plugin- (not script-fu-) + * and throws error to console when not exists. */ + plug_in_class->set_i18n = script_fu_set_i18n; } @@ -121,7 +201,7 @@ script_fu_interpreter_query_procedures (GimpPlugIn *plug_in) { GList *result = NULL; - g_debug ("queried"); + g_debug ("%s", G_STRFUNC); /* Init ui, gegl, babl. * Need gegl in registration phase, to get defaults for color formal args. @@ -154,7 +234,5 @@ script_fu_interpreter_create_procedure (GimpPlugIn *plug_in, */ gimp_ui_init ("script-fu-interpreter"); - return script_fu_interpreter_create_proc_at_path (plug_in, - proc_name, - path_to_this_script); + return script_fu_interpreter_create_proc (plug_in, proc_name, path_to_this_script); } diff --git a/plug-ins/script-fu/interpreter/script-fu-interpreter.c b/plug-ins/script-fu/interpreter/script-fu-interpreter.c index 5bbd10fe9f..d1a776b772 100644 --- a/plug-ins/script-fu/interpreter/script-fu-interpreter.c +++ b/plug-ins/script-fu/interpreter/script-fu-interpreter.c @@ -43,19 +43,36 @@ * When we call script_fu_init_embedded_interpreter(), * the passed paths should include the path to /scripts * because that is the location of scripts for initialization and compatibility - * (script-fu.init, plug-in-compat.init and script-fu-compat.init, - * which are really scheme files.) + * e.g. init.scm. * * scrip-fu-interpreter always inits embedded interpreter(allow_register=TRUE) * In the "run" phase, you don't need script-fu-register to be defined, but its harmless. + * + * The usual sequence of phases and callbacks from GimpPlugin is: + * query phase + * set_i18n script_fu_interpreter_get_i18n + * query procedures script_fu_interpreter_list_defined_proc_names + * run phase + * set_i18n script_fu_interpreter_get_i18n + * create procedure script_fu_interpreter_create_proc + * set_i18n script_fu_interpreter_get_i18n + * run procedure (calls directly a C func defined in script-fu-run-func.c) + * We only init interpreter and load scripts once per phase. */ static GFile *script_fu_get_plugin_parent_path (const gchar *path_to_this_script); static void script_fu_free_path_list (GList **list); - +static void script_fu_interpreter_init_inner (void); +static void script_fu_interpreter_load_script ( + GimpPlugIn *plug_in, + const gchar *path_to_script); +static void script_fu_interpreter_init_and_load_script ( + GimpPlugIn *plug_in, + const gchar *path_to_script); /* Return a list of PDB procedure names defined in all .scm files in * the parent dir of the given path, which is a filename of the one being queried. + * This is called in the "query" phase, and subsequently the interpreter will exit. * * Each .scm file may contain many calls to script-fu-register, which defines a PDB procedure. * All .scm files in the parent dir are searched. @@ -74,14 +91,9 @@ script_fu_interpreter_list_defined_proc_names (GimpPlugIn *plug_in, GList *name_list = NULL; /* list of strings */ GList *path_list = NULL; /* list of GFile */ - /* path_list is /scripts dir etc. from which we will load compat and init scripts. - * second argument TRUE means define script-fu-register into the interpreter. - */ - path_list = script_fu_search_path (); - script_fu_init_embedded_interpreter (path_list, TRUE, GIMP_RUN_NONINTERACTIVE, FALSE); - script_fu_free_path_list (&path_list); + script_fu_interpreter_init_inner(); - /* Reuse path_list, now a list of one path, the parent dir of the queried script. */ + /* List one path, the parent dir of the queried script. */ path_list = g_list_append (path_list, script_fu_get_plugin_parent_path (path_to_this_script)); name_list = script_fu_find_scripts_list_proc_names (plug_in, path_list); @@ -93,31 +105,14 @@ script_fu_interpreter_list_defined_proc_names (GimpPlugIn *plug_in, return name_list; } - -/* Create a PDB proc of type PLUGIN with the given name. - * Unlike extension-script-fu, create proc of type PLUGIN. - * - * We are in "create procedure" phase of call from GIMP. - * Create a PDB procedure that the script-fu-interpreter wraps. - * - * A GimpPDBProcedure has a run function, here script_fu_script_proc() - * of this outer interpreter. - * Sometime after the create, GIMP calls the run func, passing a name aka command. - * In ScriptFu, the same name is used for the PDB proc and the Scheme function - * which is the inner run func defined in the script. - * script_fu_script_proc calls the TinyScheme interpreter to evaluate - * the inner run func in the script. - */ GimpProcedure * -script_fu_interpreter_create_proc_at_path (GimpPlugIn *plug_in, - const gchar *proc_name, - const gchar *path_to_this_script - ) +script_fu_interpreter_create_proc (GimpPlugIn *plug_in, + const gchar *proc_name, + const gchar *path_to_script) { GimpProcedure *procedure = NULL; - GList *path_list = NULL; /* list of GFile */ - g_debug ("script_fu_interpreter_create_proc_at_path, name: %s", proc_name); + g_debug ("%s name: %s", G_STRFUNC, proc_name); /* Require proc_name is a suitable name for a PDB procedure eg "script-fu-test". * (Not tested for canonical name "script-fu-") @@ -130,35 +125,118 @@ script_fu_interpreter_create_proc_at_path (GimpPlugIn *plug_in, * Otherwise, we simply won't find the proc_name defined in any .scm file, * and will fail gracefully, returning NULL. */ - - path_list = script_fu_search_path (); - path_list = g_list_append (path_list, - script_fu_get_plugin_parent_path (path_to_this_script)); - /* path_list are the /scripts dir, for .init and compat.scm, plus the path to this. - * second arg TRUE means define script-fu-register so it is effective. + /* Load scripts. + * We loaded scripts prior for callback gimp_plugin_set_18n, + * but then i18n was not in effect. + * Load again, but now i18n will translate GUI strings for declared args. */ - script_fu_init_embedded_interpreter (path_list, TRUE, GIMP_RUN_NONINTERACTIVE, FALSE); + script_fu_interpreter_load_script (plug_in, path_to_script); - /* Reuse path_list, now a list of only the path to this script. */ - script_fu_free_path_list (&path_list); - path_list = g_list_append (path_list, - script_fu_get_plugin_parent_path (path_to_this_script)); + /* Assert loaded scripts has proc_name. Else this fails, returns NULL. */ + procedure = script_fu_create_PDB_proc_plugin (plug_in, proc_name); - procedure = script_fu_find_scripts_create_PDB_proc_plugin (plug_in, path_list, proc_name); - script_fu_free_path_list (&path_list); - - /* When procedure is not NULL, assert: - * some .scm was evaluated. - * the script defined many PDB procedures locally, i.e. in script-tree - * we created a single PDB procedure (but not put it in the GIMP PDB) - * - * Ensure procedure is-a GimpProcedure or NULL. - * GIMP is the caller and will put non-NULL procedure in the PDB. - */ + /* When procedure is not NULL, the caller GIMP will it in the PDB. */ return procedure; } +/* Return i18n domain and catalog declared by script at path. + * Returns NULL when script did not call script-fu-register-i18n. + * Returned strings are new allocated. + * Returned strings are returned at the given handles. + * + * This is only used by the interpreter. + * extension-script-fu does not let old-style scripts in /scripts + * declare i18n; such scripts use only the shared translations gimp30-script-fu.mo. + * + * This is called twice when GIMP is running the procedure: + * 1. before create + * 2. before run + * For the first call, we initialize the interpreter and load the script. + * For the second call, the script is still loaded, + * and we return the same results as the first call. + */ +void +script_fu_interpreter_get_i18n (GimpPlugIn *plug_in, + const gchar *proc_name, + const gchar *path_to_this_script, + gchar **i18n_domain, /* OUT handles */ + gchar **i18n_catalog) +{ + /* As discussed above, this may be called many times in same interpreter session. + * Only init interpreter once. + */ + if (! script_fu_is_scripts_loaded ()) + { + script_fu_interpreter_init_and_load_script (plug_in, path_to_this_script); + } + + /* This will allocate strings and set the handles. */ + return script_fu_get_i18n_for_proc (proc_name, i18n_domain, i18n_catalog); +} + +/* Load plugin .scm files from directory at path. + * Side effects on the interpreter's tree of scripts. + * + * This may load many files which define many PDB procedures. + * This does not install the PDB procedures defined by the scripts. + * + * This can be called sequentially but the effect is not cumulative: + * it frees any scripts already loaded into internal tree. + * A second call reloads the tree. + */ +static void +script_fu_interpreter_load_script (GimpPlugIn *plug_in, + const gchar *path_to_script) +{ + GList *path_list = NULL; /* list of GFile */ + + /* Convert file name to list of one parent path. + * A SF independently interpreted file must be in its own dir, + * and load_scripts wants a dir, not a file. + */ + path_list = g_list_append (path_list, + script_fu_get_plugin_parent_path (path_to_script)); + + /* Get scripts into global state: scripts_tree. */ + (void) script_fu_load_scripts_into_tree (plug_in, path_list); + + script_fu_free_path_list (&path_list); + + /* Not ensure script_fu_is_scripts_loaded(), + * when the path is bad or is to a file that is not a valid SF script file. + * Not ensure that the file declared any particular procedure name. + */ +} + + + +/* Init the SF interpreter and load plugin script files at path. + * + * Is an error to call more than once. + * + * The path is to a directory expected to contain one or more plugin .scm file. + * + * Initting the interpreter also "loads" non-plugin .scm files, + * where "load" means in Scheme: read and evaluate. + */ +/* FUTURE we should not need to pass plug_in. See script_fu_remove_script */ +static void +script_fu_interpreter_init_and_load_script (GimpPlugIn *plug_in, + const gchar *path_to_script) +{ + if (script_fu_is_scripts_loaded ()) + { + g_error ("%s interpreter already init", G_STRFUNC); + return; + } + + script_fu_interpreter_init_inner(); + + script_fu_interpreter_load_script (plug_in, path_to_script); +} + + /* Return GFile of the parent directory of this plugin, whose filename is given. * * Caller must free the GFile. @@ -190,3 +268,41 @@ script_fu_free_path_list (GList **list) /* !!! g_steal_pointer takes a handle. */ g_list_free_full (g_steal_pointer (list), g_object_unref); } + +/* Init the TinyScheme interpreter + * and the ScriptFu interpreter that wraps it. + * + * Side effects only, on the state of the interpreter. + * + * Ensures: + * Innermost TinyScheme interpreter is initialized. + * It has loaded init.scm (and some other scripts in /scripts/init) + * The ScriptFu registration functions are defined + * (and other functions unique to ScriptFu outer interpreter.) + * + * It has NOT loaded plugin scripts: + * in /scripts, served by extension-script-fu + * in /plug-ins, served by independent interpreter + */ +static void +script_fu_interpreter_init_inner (void) +{ + GList *path_list = NULL; /* list of GFile */ + + g_debug ("%s", G_STRFUNC); + + path_list = script_fu_search_path (); + /* path_list is /scripts dir which has subdir /init of compat and init scripts. */ + + /* Second argument TRUE means define script-fu-register + * and other registration functions into the interpreter. + * So that plugin scripts WILL load IN THE FUTURE. + * This does not load any plugins, + * but subsequently, the interpreter will recognize registration functions + * when interpreter loads a plugin .scm file. + * + * Fourth argument FALSE means use no progress reporting. + */ + script_fu_init_embedded_interpreter (path_list, TRUE, GIMP_RUN_NONINTERACTIVE, FALSE); + script_fu_free_path_list (&path_list); +} diff --git a/plug-ins/script-fu/interpreter/script-fu-interpreter.h b/plug-ins/script-fu/interpreter/script-fu-interpreter.h index ec48ce52fb..6a56c1bb3b 100644 --- a/plug-ins/script-fu/interpreter/script-fu-interpreter.h +++ b/plug-ins/script-fu/interpreter/script-fu-interpreter.h @@ -21,9 +21,15 @@ GList *script_fu_interpreter_list_defined_proc_names ( GimpPlugIn *plug_in, const gchar *path_to_this_plugin); -GimpProcedure *script_fu_interpreter_create_proc_at_path ( +GimpProcedure *script_fu_interpreter_create_proc ( GimpPlugIn *plug_in, const gchar *proc_name, - const gchar *path_to_this_script); + const gchar *path_to_script); +void script_fu_interpreter_get_i18n ( + GimpPlugIn *plug_in, + const gchar *proc_name, + const gchar *path_to_this_script, + gchar **i18n_domain, + gchar **i18n_catalog); #endif /* __SCRIPT_FU_INTERPRETER_H__ */ diff --git a/plug-ins/script-fu/libscriptfu/scheme-wrapper.c b/plug-ins/script-fu/libscriptfu/scheme-wrapper.c index c5aebb2cf3..b34f7e62a4 100644 --- a/plug-ins/script-fu/libscriptfu/scheme-wrapper.c +++ b/plug-ins/script-fu/libscriptfu/scheme-wrapper.c @@ -112,6 +112,8 @@ static pointer script_fu_register_call_procedure (scheme pointer a); static pointer script_fu_menu_register_call (scheme *sc, pointer a); +static pointer script_fu_register_i18n_call (scheme *sc, + pointer a); static pointer script_fu_use_v3_call (scheme *sc, pointer a); static pointer script_fu_use_v2_call (scheme *sc, @@ -553,6 +555,7 @@ ts_define_procedure (sc, "load-extension", scm_load_ext); ts_define_procedure (sc, "script-fu-register-filter", script_fu_register_call_filter); ts_define_procedure (sc, "script-fu-register-procedure", script_fu_register_call_procedure); ts_define_procedure (sc, "script-fu-menu-register", script_fu_menu_register_call); + ts_define_procedure (sc, "script-fu-register-i18n", script_fu_register_i18n_call); } else { @@ -560,6 +563,7 @@ ts_define_procedure (sc, "load-extension", scm_load_ext); ts_define_procedure (sc, "script-fu-register-filter", script_fu_nil_call); ts_define_procedure (sc, "script-fu-register-procedure", script_fu_nil_call); ts_define_procedure (sc, "script-fu-menu-register", script_fu_nil_call); + ts_define_procedure (sc, "script-fu-register-i18n", script_fu_nil_call); } ts_define_procedure (sc, "script-fu-use-v3", script_fu_use_v3_call); @@ -2346,6 +2350,13 @@ script_fu_menu_register_call (scheme *sc, return script_fu_add_menu (sc, a); } +static pointer +script_fu_register_i18n_call (scheme *sc, + pointer a) +{ + return script_fu_add_i18n (sc, a); +} + static pointer script_fu_use_v3_call (scheme *sc, pointer a) diff --git a/plug-ins/script-fu/libscriptfu/script-fu-lib.c b/plug-ins/script-fu/libscriptfu/script-fu-lib.c index ad368a3ff5..375cd792a9 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-lib.c +++ b/plug-ins/script-fu/libscriptfu/script-fu-lib.c @@ -25,6 +25,7 @@ #include "script-fu-types.h" /* SFScript */ #include "scheme-wrapper.h" /* tinyscheme_init etc, */ #include "script-fu-scripts.h" /* script_fu_find_scripts */ +#include "script-fu-script.h" /* script_fu_script_get_i18n */ #include "script-fu-interface.h" /* script_fu_interface_is_active */ #include "script-fu-proc-factory.h" @@ -105,21 +106,38 @@ script_fu_init_embedded_interpreter (GList *paths, tinyscheme_init (paths, allow_register); ts_set_run_mode (run_mode); /* - * Ensure the embedded interpreter is running - * and has loaded its internal Scheme scripts - * and has defined existing PDB procs as Scheme foreign functions - * (is ready to interpret PDB-like function calls in scheme scripts.) + * Ensure the embedded interpreter is running and: + * loaded its internal Scheme scripts e.g. init.scm + * defined existing PDB procs as Scheme foreign functions + * (is ready to interpret PDB-like function calls in scheme scripts.) + * has loaded other init and compat scripts in /scripts/init + * e.g. script-fu-compat.scm * - * scripts/...init and scripts/...compat.scm are loaded - * iff paths includes the "/scripts" dir. - * - * The .scm file(s) for plugins are loaded - * iff paths includes their parent directory (e.g. /scripts) - * Loaded does not imply yet registered in the PDB - * (yet, they soon might be for some phases of the plugin.) + * The .scm file(s) for plugins in /scripts are NOT loaded. + * Any util scripts in /scripts are NOT loaded, e.g. script-fu-utils.scm. */ } +/* Load script files at paths. + * Side effect: create state script_tree. + * Requires interpreter initialized. + */ +void +script_fu_load_scripts_into_tree (GimpPlugIn *plugin, + GList *paths) +{ + script_fu_scripts_load_into_tree (plugin, paths); +} + +/* Has the interpreter been initialized and a script loaded + * i.e. interpreted for registration into interpreter state: script_tree. + */ +gboolean +script_fu_is_scripts_loaded (void) +{ + return script_fu_scripts_are_loaded (); +} + void script_fu_set_print_flag (gboolean should_print) { @@ -337,13 +355,16 @@ script_fu_is_init_directory (GFile *dir) return result; } +/* Create a PDB procedure from the SFScript for the given proc name. + * Does not register into the PDB. + * Requires scripts already loaded i.e. SFScript exist. + */ GimpProcedure * -script_fu_find_scripts_create_PDB_proc_plugin (GimpPlugIn *plug_in, - GList *paths, - const gchar *name) +script_fu_create_PDB_proc_plugin (GimpPlugIn *plug_in, + const gchar *name) { /* Delegate to factory. */ - return script_fu_proc_factory_make_PLUGIN (plug_in, paths, name); + return script_fu_proc_factory_make_PLUGIN (plug_in, name); } GList * @@ -353,3 +374,13 @@ script_fu_find_scripts_list_proc_names (GimpPlugIn *plug_in, /* Delegate to factory. */ return script_fu_proc_factory_list_names (plug_in, paths); } + +/* Requires scripts already loaded. */ +void +script_fu_get_i18n_for_proc (const gchar *proc_name, + gchar **declared_i18n_domain, + gchar **declared_i18n_catalog) +{ + SFScript *script = script_fu_find_script (proc_name); + script_fu_script_get_i18n (script, declared_i18n_domain, declared_i18n_catalog); +} diff --git a/plug-ins/script-fu/libscriptfu/script-fu-lib.h b/plug-ins/script-fu/libscriptfu/script-fu-lib.h index 7f257a366e..1829293ae8 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-lib.h +++ b/plug-ins/script-fu/libscriptfu/script-fu-lib.h @@ -36,6 +36,9 @@ void script_fu_init_embedded_interpreter (GList *paths, gboolean allow_register, GimpRunMode run_mode, gboolean report_progress); +void script_fu_load_scripts_into_tree (GimpPlugIn *plugin, + GList *paths); +gboolean script_fu_is_scripts_loaded (void); void script_fu_set_print_flag (gboolean should_print); void script_fu_redirect_output_to_gstr (GString *output); @@ -52,10 +55,14 @@ void script_fu_run_read_eval_print_loop (void); void script_fu_register_quit_callback (void (*func) (void)); void script_fu_register_post_command_callback (void (*func) (void)); -GimpProcedure *script_fu_find_scripts_create_PDB_proc_plugin (GimpPlugIn *plug_in, - GList *paths, - const gchar *name); GList *script_fu_find_scripts_list_proc_names (GimpPlugIn *plug_in, GList *paths); +GimpProcedure *script_fu_create_PDB_proc_plugin (GimpPlugIn *plug_in, + const gchar *name); +void script_fu_get_i18n_for_proc (const gchar *proc_name, + gchar **declared_i18n_domain, + gchar **declared_i18n_catalog); + + #endif /* __SCRIPT_FU_LIB_H__ */ diff --git a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c index 33a01be0a3..1c72cff56d 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c +++ b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c @@ -50,34 +50,22 @@ static void script_fu_add_menu_to_procedure (GimpProcedure *procedure, /* Create and return a single PDB procedure of type PLUGIN, - * for the given proc name, by reading the script file in the given paths. + * for the given proc name, from script_tree already loaded. * Also add a menu for the procedure. * * PDB proc of type PLUGIN has permanent lifetime, unlike type TEMPORARY. * - * The list of paths is usually just one directory, a subdir of /plug-ins. - * The directory may contain many .scm files. - * The plugin manager only queries one .scm file, - * having the same name as its parent dir and and having execute permission. - * But here we read all the .scm files in the directory. - * Each .scm file may register (and define run func for) many PDB procedures. - * - * Here, one name is passed, and though we load all the .scm files, - * we only create a PDB procedure for the passed name. + * Loaded .scm file(s) may have defined many procedures. + * Create a PDB procedure only for the one passed name. */ GimpProcedure * script_fu_proc_factory_make_PLUGIN (GimpPlugIn *plug_in, - GList *paths, const gchar *proc_name) { SFScript * script = NULL; GimpProcedure * procedure = NULL; - /* Reads all .scm files at paths, even though only one is pertinent. - * The returned script_tree is also in the state of the interpreter, - * we don't need the result here. - */ - (void) script_fu_find_scripts_into_tree (plug_in, paths); + /* Require SFScripts already defined, one or more. */ /* Get the pertinent script from the tree. */ script = script_fu_find_script (proc_name); @@ -138,7 +126,7 @@ script_fu_proc_factory_list_names (GimpPlugIn *plug_in, GTree * script_tree = NULL; /* Load (eval) all .scm files in all dirs in paths. */ - script_tree = script_fu_find_scripts_into_tree (plug_in, paths); + script_tree = script_fu_scripts_load_into_tree (plug_in, paths); /* Iterate over the tree, adding each script name to result list */ g_tree_foreach (script_tree, diff --git a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.h b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.h index 5daa7cc2b9..64adc7db64 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.h +++ b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.h @@ -19,7 +19,6 @@ #define __SCRIPT_FU_PDB_PROC_FACTORY_H__ GimpProcedure *script_fu_proc_factory_make_PLUGIN (GimpPlugIn *plug_in, - GList *paths, const gchar *name); GList *script_fu_proc_factory_list_names (GimpPlugIn *plug_in, GList *paths); diff --git a/plug-ins/script-fu/libscriptfu/script-fu-script.c b/plug-ins/script-fu/libscriptfu/script-fu-script.c index 53f531165b..559829468e 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-script.c +++ b/plug-ins/script-fu/libscriptfu/script-fu-script.c @@ -80,6 +80,8 @@ script_fu_script_new (const gchar *name, script->copyright = g_strdup (copyright); script->date = g_strdup (date); script->image_types = g_strdup (image_types); + script->i18n_domain_name = NULL; + script->i18n_catalog_relative_path = NULL; script->n_args = n_args; script->args = g_new0 (SFArg, script->n_args); @@ -103,6 +105,8 @@ script_fu_script_free (SFScript *script) g_free (script->copyright); g_free (script->date); g_free (script->image_types); + g_free (script->i18n_domain_name); + g_free (script->i18n_catalog_relative_path); for (i = 0; i < script->n_args; i++) { @@ -746,3 +750,35 @@ script_fu_script_get_is_old_style (SFScript *script) { return script->is_old_style; } + +/* Set script's i18n from strings owned by inner interpreter. + * First free any existing data since might have been set already: + * a script author may mistakenly call script-fu-register-i18n twice + * for the same procedure. + */ +void +script_fu_script_set_i18n (SFScript *script, + gchar *domain, + gchar *catalog) +{ + g_free (script->i18n_domain_name); + g_free (script->i18n_catalog_relative_path); + script->i18n_domain_name = g_strdup (domain); + script->i18n_catalog_relative_path = g_strdup (catalog); +} + +/* Return a copy of script's i18n, to the handles. + * + * Require *handles is NULL, not already allocated. + * + * May return NULL, when script author has not called script-fu-register-i18n. + */ +void +script_fu_script_get_i18n (SFScript *script, + gchar **domain, + gchar **catalog) +{ + *domain = g_strdup (script->i18n_domain_name); + *catalog = g_strdup (script->i18n_catalog_relative_path); +} + diff --git a/plug-ins/script-fu/libscriptfu/script-fu-script.h b/plug-ins/script-fu/libscriptfu/script-fu-script.h index 3de556097f..d4666e84ac 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-script.h +++ b/plug-ins/script-fu/libscriptfu/script-fu-script.h @@ -65,4 +65,11 @@ void script_fu_script_set_drawable_arity_none (SFScript *scrip void script_fu_script_set_is_old_style (SFScript *script); gboolean script_fu_script_get_is_old_style (SFScript *script); +void script_fu_script_set_i18n (SFScript *script, + gchar *domain, + gchar *catalog); +void script_fu_script_get_i18n (SFScript *script, + gchar **domain, + gchar **catalog); + #endif /* __SCRIPT_FU_SCRIPT__ */ diff --git a/plug-ins/script-fu/libscriptfu/script-fu-scripts.c b/plug-ins/script-fu/libscriptfu/script-fu-scripts.c index 073f1693b4..b45f5bde35 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-scripts.c +++ b/plug-ins/script-fu/libscriptfu/script-fu-scripts.c @@ -73,10 +73,34 @@ static GList *script_menu_list = NULL; * Function definitions */ + + +/* A method on the internal tree of scripts. + * + * Uninstall any PDB procedures declared by the scripts, + * and free the tree of scripts. + * + * For some phases of the plugin protocol + * the PDB procedures declared by the scripts were not installed. + * In other words, uninstall is a try that may have no effect. + */ +static void +script_fu_scripts_clear_tree ( GimpPlugIn *plug_in) +{ + if (script_tree != NULL) + { + g_tree_foreach (script_tree, + (GTraverseFunc) script_fu_remove_script, + plug_in); + g_tree_destroy (script_tree); + } +} + + /* Traverse list of paths, finding .scm files. * Load and eval any found script texts. - * Script texts will call Scheme functions script-fu-register - * and script-fu-menu-register, + * Script texts will call ScriptFu registration functions + * e.g. script-fu-register and script-fu-menu-register, * which insert a SFScript record into script_tree, * and insert a SFMenu record into script_menu_list. * These are side effects on the state of the outer (SF) interpreter. @@ -85,20 +109,29 @@ static GList *script_menu_list = NULL; * The other result (script_menu_list) is not returned, see script_fu_get_menu_list. * * Caller should free script_tree and script_menu_list, - * This should only be called once. + * but we usually don't, the interpreter just exits. + * + * This can be called more than once but does not accumulate into the tree: + * it clears the tree on every call before reloading it. + * + * When we load plugin script files (.scm) + * the list of paths can be just one directory, + * a plugin's subdir of /plug-ins (independent interpreter). + * The plugin manager only queries one .scm file, + * having the same name as its parent dir and and having execute permission. + * + * But the list of paths can be many: + * the sys and user /scripts dirs(extension-script-fu). + * + * Any dir in the paths may contain many .scm files. + * We read all the .scm files in the directory. + * Each .scm file may register (and define run func for) many PDB procedures. */ GTree * -script_fu_find_scripts_into_tree ( GimpPlugIn *plug_in, +script_fu_scripts_load_into_tree ( GimpPlugIn *plug_in, GList *paths) { - /* Clear any existing scripts */ - if (script_tree != NULL) - { - g_tree_foreach (script_tree, - (GTraverseFunc) script_fu_remove_script, - plug_in); - g_tree_destroy (script_tree); - } + script_fu_scripts_clear_tree (plug_in); script_tree = g_tree_new ((GCompareFunc) g_utf8_collate); @@ -138,7 +171,7 @@ void script_fu_find_scripts (GimpPlugIn *plug_in, GList *path) { - script_fu_find_scripts_into_tree (plug_in, path); + script_fu_scripts_load_into_tree (plug_in, path); /* Now that all scripts are read in and sorted, tell gimp about them */ g_tree_foreach (script_tree, @@ -335,6 +368,97 @@ script_fu_add_menu (scheme *sc, return sc->NIL; } +/* For a call to script-fu-register-i18n, + * marshall scheme values into local SFScript struct. + * + * Returns sc->NIL on success, else a foreign_error. + * Many kinds of failure will not prevent the plugin from registering and working, + * only prevent the plugin from being translated properly. + * + * Although the set_18n callback is called many times in the same interpreter session, + * before create proc and run proc, the script is only interpreted for it's registrations once. + * However, an ill-formed script can call script-fu-register-i18n + * many times for the same procedure, see below. + */ +pointer +script_fu_add_i18n (scheme *sc, + pointer a) +{ + SFScript *script; + const gchar *proc_name; + + gchar *i18n_domain = NULL; + gchar *i18n_catalog_relative_path = NULL; + + g_debug ("%s", G_STRFUNC); + + /* Check arg count */ + if (sc->vptr->list_length (sc, a) < 2) + return foreign_error (sc, "script-fu-register-i18n takes two or three args", 0); + + /* PDB procedure name. */ + if (sc->vptr->is_string (sc->vptr->pair_car (a))) + { + proc_name = sc->vptr->string_value (sc->vptr->pair_car (a)); + a = sc->vptr->pair_cdr (a); + } + else + { + return foreign_error (sc, "script-fu-register-i18n requires first arg is string script name", 0); + } + + script = script_fu_find_script (proc_name); + + if (! script) + return foreign_error (sc, "script-fu-register-i18n called with invalid procedure name", 0); + + /* Not an error to interpret script-fu-register-i18n twice for the same procedure. + * When there are two calls to script-fu-register-i18n for the same procedure + * in one script, the latter will have effect. + */ + if (script->i18n_domain_name != NULL || script->i18n_catalog_relative_path != NULL) + g_warning ("%s called twice for same procedure %s", G_STRFUNC, proc_name); + + /* i18n domain name */ + if (sc->vptr->is_string (sc->vptr->pair_car (a))) + { + i18n_domain = sc->vptr->string_value (sc->vptr->pair_car (a)); + a = sc->vptr->pair_cdr (a); + } + else + { + return foreign_error (sc, "script-fu-register-i18n requires second arg is string domain name", 0); + } + + /* optional catalog path */ + if (a != sc->NIL) + { + if (sc->vptr->is_string (sc->vptr->pair_car (a))) + { + i18n_catalog_relative_path = sc->vptr->string_value (sc->vptr->pair_car (a)); + a = sc->vptr->pair_cdr (a); + } + else + { + return foreign_error (sc, "script-fu-register-i18n requires optional third arg is catalog path", 0); + } + } + + /* Call setter from local vars, strings owned by inner interpreter TS. */ + script_fu_script_set_i18n (script, i18n_domain, i18n_catalog_relative_path); + + return sc->NIL; /* success */ +} + +/* Have one or more SFScript (global data structs) been created? + * i.e. one or more script files loaded, i.e. interpreted for their registration functions. + * Returns a state of the interpreter. + */ +gboolean +script_fu_scripts_are_loaded (void) +{ + return (script_tree != NULL); +} /* private functions */ @@ -478,8 +602,11 @@ script_fu_install_menu (SFMenu *menu) g_slice_free (SFMenu, menu); } -/* - * The following function is a GTraverseFunction. +/* Traverse list of scripts, uninstalling from PDB and + * freeing the script data. + * Then free the list, now empty of content. + * + * This function has type GTraverseFunction. */ static gboolean script_fu_remove_script (gpointer foo G_GNUC_UNUSED, @@ -489,6 +616,8 @@ script_fu_remove_script (gpointer foo G_GNUC_UNUSED, GimpPlugIn *plug_in = data; GList *list; + g_debug ("%s", G_STRFUNC); + for (list = scripts; list; list = g_list_next (list)) { SFScript *script = list->data; diff --git a/plug-ins/script-fu/libscriptfu/script-fu-scripts.h b/plug-ins/script-fu/libscriptfu/script-fu-scripts.h index f41dd2bc94..fd228be655 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-scripts.h +++ b/plug-ins/script-fu/libscriptfu/script-fu-scripts.h @@ -28,9 +28,12 @@ pointer script_fu_add_script_regular (scheme *sc, pointer a); pointer script_fu_add_menu (scheme *sc, pointer a); +pointer script_fu_add_i18n (scheme *sc, + pointer a); -GTree * script_fu_find_scripts_into_tree (GimpPlugIn *plug_in, +GTree * script_fu_scripts_load_into_tree (GimpPlugIn *plug_in, GList *path); +gboolean script_fu_scripts_are_loaded (void); SFScript * script_fu_find_script (const gchar *name); GList * script_fu_get_menu_list (void); diff --git a/plug-ins/script-fu/libscriptfu/script-fu-types.h b/plug-ins/script-fu/libscriptfu/script-fu-types.h index 9631b7055d..91790dc6f0 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-types.h +++ b/plug-ins/script-fu/libscriptfu/script-fu-types.h @@ -88,6 +88,8 @@ typedef struct gchar *copyright; gchar *date; gchar *image_types; + gchar *i18n_domain_name; + gchar *i18n_catalog_relative_path; gint n_args; SFArg *args; diff --git a/plug-ins/script-fu/libscriptfu/script-fu.def b/plug-ins/script-fu/libscriptfu/script-fu.def index 08ccaeef32..5e64288aa4 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu.def +++ b/plug-ins/script-fu/libscriptfu/script-fu.def @@ -15,5 +15,8 @@ EXPORTS script_fu_register_quit_callback script_fu_register_post_command_callback script_fu_search_path - script_fu_find_scripts_create_PDB_proc_plugin + script_fu_create_PDB_proc_plugin script_fu_find_scripts_list_proc_names + script_fu_is_scripts_loaded + script_fu_load_scripts_into_tree + script_fu_get_i18n_for_proc diff --git a/plug-ins/script-fu/scripts/test-sphere-v3.scm b/plug-ins/script-fu/scripts/test-sphere-v3.scm index d5b4317d41..0e1d4ebfb9 100644 --- a/plug-ins/script-fu/scripts/test-sphere-v3.scm +++ b/plug-ins/script-fu/scripts/test-sphere-v3.scm @@ -189,3 +189,6 @@ (script-fu-menu-register "script-fu-test-sphere-v3" "/Filters/Development/Plug-In Examples") + +; Use the translations data common to all Scheme plugins distributed with GIMP. +(script-fu-register-i18n "script-fu-test-sphere-v3" "gimp30-script-fu" ) diff --git a/plug-ins/script-fu/scripts/test/meson.build b/plug-ins/script-fu/scripts/test/meson.build index 7f798a41d1..c8693fbb0f 100644 --- a/plug-ins/script-fu/scripts/test/meson.build +++ b/plug-ins/script-fu/scripts/test/meson.build @@ -15,6 +15,10 @@ elif get_option('buildtype') != 'debug' endif +# test of i18n is in its own subdir +subdir('testi18n') + + # scripts interpreted by extension-script-fu, installed to /scripts scripts = [ 'contactsheet.scm', diff --git a/plug-ins/script-fu/scripts/test/testi18n/LINGUAS b/plug-ins/script-fu/scripts/test/testi18n/LINGUAS new file mode 100644 index 0000000000..4c3c73225a --- /dev/null +++ b/plug-ins/script-fu/scripts/test/testi18n/LINGUAS @@ -0,0 +1,2 @@ +es +de diff --git a/plug-ins/script-fu/scripts/test/testi18n/de.po b/plug-ins/script-fu/scripts/test/testi18n/de.po new file mode 100644 index 0000000000..1b67b51955 --- /dev/null +++ b/plug-ins/script-fu/scripts/test/testi18n/de.po @@ -0,0 +1,17 @@ +# A file for testing. +# For domain "scriptfu-test" + +# Not actually from translators, hacked together by Lloyd Konneker + +msgid "" +msgstr "" + +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" + +#: source line not identified, is used in test-i18n.scm +msgid "Orientation" +msgstr "Orientflugel" + +msgid "Elevation" +msgstr "Eleveflugel" \ No newline at end of file diff --git a/plug-ins/script-fu/scripts/test/testi18n/es.po b/plug-ins/script-fu/scripts/test/testi18n/es.po new file mode 100644 index 0000000000..d7417b296e --- /dev/null +++ b/plug-ins/script-fu/scripts/test/testi18n/es.po @@ -0,0 +1,17 @@ + +# A file for testing. +# For domain "scriptfu-test" + +# Not actually from translators, hacked together by Lloyd Konneker + +msgid "" +msgstr "" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" + +#: source line not identified, is used in test-i18n.scm +msgid "Orientation" +msgstr "Orientación" + +msgid "Elevation" +msgstr "Elevación" \ No newline at end of file diff --git a/plug-ins/script-fu/scripts/test/testi18n/meson.build b/plug-ins/script-fu/scripts/test/testi18n/meson.build new file mode 100644 index 0000000000..fdfcf95701 --- /dev/null +++ b/plug-ins/script-fu/scripts/test/testi18n/meson.build @@ -0,0 +1,44 @@ +# test i18n plugins + +# Not for translators: translation data is mocked up + +# scripts interpreted by gimp-script-fu-interpreter +scripts_independent = [ + { 'name': 'test-i18n' }, + { 'name': 'test-i18n-more' }, +] + +foreach plugin : scripts_independent + name = plugin.get('name') + srcs = plugin.get('srcs', name + '.scm') + + install_data(srcs, + install_dir: gimpplugindir / 'plug-ins' / name, + install_mode: 'rwxr-xr-x') +endforeach + + +# Install test translations for a suite of PDB procedures +# under two i18n domain names: "script-fu-test" and "scriptfu-test-more" + +# See "Internationalizing" at the gimp developer website +# Two plugin files define three PDB procedures +# that share the same translations data. +# "Suite" means: sharing the same translations data files. +# suite is one-to-many with i18n domain names, unfortunately. + +# ! install_dir: does NOT accept a list + +# test-i18n.scm defines two PDB procedures +i18n.gettext ('scriptfu-test', + preset: 'glib', + install_dir: gimpplugindir / 'plug-ins' / 'test-i18n' / 'locale' + ) + +# test-i18n-more.scm defines one PDB procedure +# Duplicate, install i18n data in a second place +# and give it a different domain name, "scriptfu-test-more !!! +i18n.gettext ('scriptfu-test-more', + preset: 'glib', + install_dir: gimpplugindir / 'plug-ins' / 'test-i18n-more' / 'locale' + ) \ No newline at end of file diff --git a/plug-ins/script-fu/scripts/test/testi18n/test-i18n-more.scm b/plug-ins/script-fu/scripts/test/testi18n/test-i18n-more.scm new file mode 100644 index 0000000000..da4995d8c0 --- /dev/null +++ b/plug-ins/script-fu/scripts/test/testi18n/test-i18n-more.scm @@ -0,0 +1,41 @@ +#!/usr/bin/env gimp-script-fu-interpreter-3.0 +;!# Close comment started on first line. Needed by gettext. + +; An independently interpreted Scheme plugin +; to test the registration function script-fu-register-18n. + +; This is marked with translateable strings, +; but we don't expect translators to translate. +; We mock up incomplete translation data. + +; Has translatable dialog. +; Dialog appears in native language +; when the script-fu-register-18n call is proper +; and mockup translation data installed corresponding to said call. +(define (plug-in-test-i18n-3 orientation) + ; does nothing +) + + +; Not a filter, always enabled. +(script-fu-register-procedure "plug-in-test-i18n-3" + _"Test SF i18n Three..." ; menu item + "" ; tooltip + "LKK" + "2025" + ; One arg, just to test the translation of its label + SF-OPTION _"Orientation" '(_"Horizontal" + _"Vertical") +) + +(script-fu-menu-register "plug-in-test-i18n-3" + "/Filters/Development/Test") + +; !!! Note the domain name is not the same as for the +; other two PDB procedures in the suite +(script-fu-register-i18n "plug-in-test-i18n-3" ; plugin name + "scriptfu-test-more") ; domain name + + + + diff --git a/plug-ins/script-fu/scripts/test/testi18n/test-i18n.scm b/plug-ins/script-fu/scripts/test/testi18n/test-i18n.scm new file mode 100644 index 0000000000..b843ff236a --- /dev/null +++ b/plug-ins/script-fu/scripts/test/testi18n/test-i18n.scm @@ -0,0 +1,202 @@ +#!/usr/bin/env gimp-script-fu-interpreter-3.0 +;!# Close comment started on first line. Needed by gettext. + +; An independently interpreted Scheme plugin +; to test the registration function script-fu-register-18n. + +; This is marked with translateable strings, +; but we don't expect translators to translate. +; We mock up translation data. + +; Has translateable dialog. +; Dialog appears in native language +; only when the script-fu-register-18n call is proper +; and mockup translation data installed corresponding to said call. +(define (plug-in-test-i18n-1 orientation) + ; does nothing +) +(define (plug-in-test-i18n-2 elevation) + ; does nothing +) + + +; Not a filter, always enabled. +(script-fu-register-procedure "plug-in-test-i18n-1" + _"Test SF i18n One..." ; menu item + "" ; tooltip + "LKK" + "2025" + ; a non-filter procedure has no image types, always enabled + ; a non-filter procedure has no drawable arity, always enabled + + ; One arg, just to test the translation of its label + SF-OPTION _"Orientation" '(_"Horizontal" + _"Vertical") +) +(script-fu-register-procedure "plug-in-test-i18n-2" + _"Test SF i18n Two..." ; menu item + "" ; tooltip + "LKK" + "2025" + SF-OPTION _"Elevation" '(_"High" + _"Low") +) + +(script-fu-menu-register "plug-in-test-i18n-1" + "/Filters/Development/Test") +(script-fu-menu-register "plug-in-test-i18n-2" + "/Filters/Development/Test") + + + +; This documents the cases for a script to declare translations data. +; This is only for plugins installed to /plug-ins (independently interpreted.) +; +; The script must also have GUI strings marked for translation using notation _"foo". +; A script may be marked, but not have translator produced translation files. +; +; 1. The script can simply omit a call to script-fu-register-i18n. +; This means: there is no translation data for the procedure. +; +; 2. The script can call (script-fu-register-i18n "proc_name" "None") +; This also means: there is no translation data for the procedure. +; +; 3. The script can call (script-fu-register-i18n "proc_name" "Standard") +; The script will be in native language subject to the existence of translation files +; installed the usual, standard way. +; This means: the domain name is the script's name +; and the translation files are in .../plug-ins//locale directory +; for example for French language there exists a file +; .../plug-ins//locale/fr/LC_MESSAGES/.mo +; all installed, for example, +; in GIMP's installed data e.g. /usr/lib/share/GIMP/3.0/plug-ins +; (when the plugin is an official plugin supported by GIMP) +; or in the user's ~/.config/GIMP/3.0/plug-ins +; (when the user wrote or installed a third-party plugin for their own private use.) +; +; 4. The script can call (script-fu-register-i18n "proc_name" "foo_domain" "bar_path") +; This means a custom install location for the translation files, +; and a custom name for the domain, i.e. name of the .mo files. +; +; Typically, this is NOT USEFUL: why change the names? +; Typically this is NOT USEFUL for a group of plugins sharing translation files. +; +; It is not useful for group of plugins to share translations data because +; "bar_path" must be a relative (not absolute) path to the plugin's directory, +; AND a subdirectory of the plugin's directory. +; NOT ALLOWED: "../bar" meaning parent dir of bar i.e. in /plug-ins. +; NOT ALLOWED: "/usr/bar" meaning some absolute path starting at root. +; +; As of this writing, the useful ways for a group of third-party plugins to share +; common translations data are: +; a. Put all the procedures in the same .scm file (you can do that) +; with a call (script-fu-register-i18n "proc_nameX" "Standard") +; for each procedure X in the .scm file, +; and install the usual way e.g. .../plug-ins/plugin-name/plugin-name.scm +; and install .../plug-ins/plugin-name/locale/fr/LC_MESSAGES/plugin-name.mo +; b. Install each plugin in its own subdirectory of .../plugins +; with a call (script-fu-register-i18n "proc_nameX" "Standard") +; and at install time, distribute the one shared translation file foo.mo +; to each of the many catalog directories, with renaming, +; e.g. to .../plug-ins/plugin-name1/locale/fr/LC_MESSAGES/plugin-name1. +; and also to .../plug-ins/plugin-name2/locale/fr/LC_MESSAGES/plugin-name2. +; +; In the example, the translations files will be found +; in .../plug-ins/proc_name/bar_path +; which will contain for example one or more files like +; .../plug-in/proc_name/bar_path/fr/LC_MESSAGES/foo_domain.mo +; +; 5. The script can call (script-fu-register-i18n "proc_name" "gimp30-script-fu") +; This means use the translation files +; common to all Scheme plugins distributed with GIMP. +; This is only useful for official plugins distributed with GIMP. +; It is not useful for third-party plugins, +; since they should not be installed in the sys GIMP data directory +; (else they will be lost in an upgrade) +; and also since the official translations data installed with GIMP +; should not be altered by a third-party plugin. + +; Note that one script file may define many PDB procedures +; and call script-fu-register-i18n for each of them. +; Some procedures may be translated, and others not. +; +; A script can (but shouldn't) call script-fu-register-i18n more than once +; for the same procedure. +; Only the last one will have effect. + + + + +; These are test cases for calls to script-fu-register-i18n. +; To test, comment out one or more trailing cases and run again. +; Only the last uncommented one has effect. + + +; Valid use cases +; No errors at registration time, but can error at runtime +; depending on existence of translation data. + +; Case: Standard translation data +; As of this writing there exists no mockup translation data for this case. +; Expect this will throw an error at plugin run time, +; to the console where GIMP was started, +; since the standard catalog directory (owned by the plugin) does not yet exist: +; .../plug-ins/test-i18n/locale/es/LC_MESSAGES/test-i18n.mo +;(script-fu-register-i18n "plug-in-test-i18n" "Standard") + +; Case: no translation data, plugin not in native language. +; Just omit the call to script-fu-register-i18n + +; Case: declare domain "None", no translation data, plugin not in native language. +; Expect this does not throw an error, and never translates the plugin. +; Same as previous case, but documents that no translation is done. +;(script-fu-register-i18n "plug-in-test-i18n" "None") + +; Case: Rename the domain but not the catalog. +; Expect plugin translates when /plug-ins/test-i18n/locale exists +; and contains es/LC_MESSAGES/scriptfu-test.mo. +; Expect throws an error at plugin run time when said catalog directory not exist +; in a subdirectory of the plugin root dir +; i.e. /usr/local/lib/gimp/3.0/plug-ins/test-i18n/locale/es/LC_MESSAGES/scriptfu-test.mo +(script-fu-register-i18n "plug-in-test-i18n-1" ; plugin name + "scriptfu-test") ; domain name +(script-fu-register-i18n "plug-in-test-i18n-2" ; plugin name + "scriptfu-test") ; domain name +; This is the same as: (script-fu-register-i18n "plug-in-test-i18n" "fooDomain" "locale" ) + +; Case: Rename the domain and the catalog. +; Expect plugin translates when /plug-ins/plug-in-test-i18n/barCatalog exists +; and contains fr/LC_MESSAGES/fooDomain.mo. +; Expect throws an error at plugin run time when said catalog directory not exist. +; (script-fu-register-i18n "plug-in-test-i18n" "fooDomain" "barCatalog") + +; Case: Rename the domain to the one shared by official GIMP ScriptFu plugins. +; Expect plugin translates when GIMP is properly installed, +; and the shared translations data contains translation pairs +; that match translateable strings in the plugin. +; Expect throws an error at plugin run time when GIMP is not properly installed. +; Expect strings in the plugin are in the native language +; (only since by design the string "Orientation" is in gimp30-script-fu.mo) +;(script-fu-register-i18n "plug-in-test-i18n" "gimp30-script-fu") + +; Error cases: + +; Error case: a relative path to a catalog above the plugin's directory. +; GIMP requires the catalog dir is a subdir of, i.e. beneath, a plugins' install dir. +; Expect this throws an error: "The catalog directory set by set_i18n() is not a subdirectory: ../bar" +; at plugin run time, not registration time. +;(script-fu-register-i18n "plug-in-test-i18n" "fooDomain" "../bar" ) + +; Error case: an absolute path to a catalog. +; Because GIMP requires path to the catalog dir is not absolute, i.e. starting with "/" +; Expect this throws an error: The catalog directory set by set_i18n() is not relative: /bar +; at plugin run time, not registration time. +;(script-fu-register-i18n "plug-in-test-i18n" "fooDomain" "/bar" ) + +; Error Case: not enough arguments. +; Expect: Error: script-fu-register-i18n takes two or three args +; in the console, at registration time, but the plugin will still work, without translations. +; (script-fu-register-i18n "plug-in-test-i18n") + + +