From ec9443dcddf09a827b74ca7c2a18a3479ae85cfc Mon Sep 17 00:00:00 2001 From: nirenjan Date: Tue, 19 May 2020 08:07:10 -0700 Subject: [PATCH] Add gettext support to libx52 This change adds gettext support to libx52 using the Autotools framework. This should allow translators to translate the error messages provided by libx52_strerror into their corresponding localized versions. --- Makefile.am | 8 +- configure.ac | 9 +- gettext.h | 292 ++++++++++++++++++++++++++++++++++++++ lib/libx52/Makefile.am | 3 +- lib/libx52/libx52.h | 2 +- lib/libx52/x52_core.c | 5 + lib/libx52/x52_strerror.c | 5 +- po/Makevars | 78 ++++++++++ po/POTFILES.in | 2 + po/x52pro-linux.pot | 91 ++++++++++++ 10 files changed, 487 insertions(+), 8 deletions(-) create mode 100644 gettext.h create mode 100644 po/Makevars create mode 100644 po/POTFILES.in create mode 100644 po/x52pro-linux.pot diff --git a/Makefile.am b/Makefile.am index 5ba5dad..bfce2cb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,10 +6,14 @@ ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = lib utils tests +if USE_NLS +po_SUBDIRS = po +endif + +SUBDIRS = $(po_SUBDIRS) lib utils tests # Extra files that need to be in the distribution -EXTRA_DIST = AUTHORS ChangeLog.md README.md LICENSE Doxyfile.in +EXTRA_DIST = config.rpath ABOUT-NLS gettext.h AUTHORS ChangeLog.md README.md LICENSE Doxyfile.in # Doxygen support if HAVE_DOXYGEN diff --git a/configure.ac b/configure.ac index d6999ae..83ae32f 100644 --- a/configure.ac +++ b/configure.ac @@ -6,7 +6,7 @@ AC_INIT([x52pro-linux], [0.2.1], [nirenjan@gmail.com]) AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects]) +AM_INIT_AUTOMAKE([-Wall foreign subdir-objects]) AC_PROG_CC AC_PROG_CC_STDC AM_PROG_AR @@ -15,6 +15,11 @@ LT_INIT PKG_PROG_PKG_CONFIG PKG_INSTALLDIR +# Internationalization +AM_GNU_GETTEXT([external]) +AM_GNU_GETTEXT_VERSION(0.18) +AM_CONDITIONAL([USE_NLS], [test "x${USE_NLS}" == "xyes"]) + # Check for libusb-1.0 AX_PKG_CHECK_MODULES([LIBUSB], [libusb-1.0]) @@ -35,7 +40,7 @@ AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([Doxyfile])], [AC_MSG_WARN(["Doxygen not found; continuing without doxygen support"])]) -AC_CONFIG_FILES([ +AC_CONFIG_FILES([ po/Makefile.in Makefile lib/Makefile lib/libx52/Makefile diff --git a/gettext.h b/gettext.h new file mode 100644 index 0000000..841b072 --- /dev/null +++ b/gettext.h @@ -0,0 +1,292 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002, 2004-2006, 2009-2016 Free Software + Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by + the gettext() and ngettext() macros. This is an alternative to calling + textdomain(), and is useful for libraries. */ +# ifdef DEFAULT_TEXT_DOMAIN +# undef gettext +# define gettext(Msgid) \ + dgettext (DEFAULT_TEXT_DOMAIN, Msgid) +# undef ngettext +# define ngettext(Msgid1, Msgid2, N) \ + dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N) +# endif + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Many header files from the libstdc++ coming with g++ 3.3 or newer include + , which chokes if dcgettext is defined as a macro. So include + it now, to make later inclusions of a NOP. */ +#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3) +# include +# if (__GLIBC__ >= 2 && !defined __UCLIBC__) || _GLIBCXX_HAVE_LIBINTL_H +# include +# endif +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# undef gettext +# define gettext(Msgid) ((const char *) (Msgid)) +# undef dgettext +# define dgettext(Domainname, Msgid) ((void) (Domainname), gettext (Msgid)) +# undef dcgettext +# define dcgettext(Domainname, Msgid, Category) \ + ((void) (Category), dgettext (Domainname, Msgid)) +# undef ngettext +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 \ + ? ((void) (Msgid2), (const char *) (Msgid1)) \ + : ((void) (Msgid1), (const char *) (Msgid2))) +# undef dngettext +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((void) (Domainname), ngettext (Msgid1, Msgid2, N)) +# undef dcngettext +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((void) (Category), dngettext (Domainname, Msgid1, Msgid2, N)) +# undef textdomain +# define textdomain(Domainname) ((const char *) (Domainname)) +# undef bindtextdomain +# define bindtextdomain(Domainname, Dirname) \ + ((void) (Domainname), (const char *) (Dirname)) +# undef bind_textdomain_codeset +# define bind_textdomain_codeset(Domainname, Codeset) \ + ((void) (Domainname), (const char *) (Codeset)) + +#endif + +/* Prefer gnulib's setlocale override over libintl's setlocale override. */ +#ifdef GNULIB_defined_setlocale +# undef setlocale +# define setlocale rpl_setlocale +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +/* The separator between msgctxt and msgid in a .mo file. */ +#define GETTEXT_CONTEXT_GLUE "\004" + +/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a + MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be + short and rarely need to change. + The letter 'p' stands for 'particular' or 'special'. */ +#ifdef DEFAULT_TEXT_DOMAIN +# define pgettext(Msgctxt, Msgid) \ + pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) +#else +# define pgettext(Msgctxt, Msgid) \ + pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) +#endif +#define dpgettext(Domainname, Msgctxt, Msgid) \ + pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES) +#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \ + pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category) +#ifdef DEFAULT_TEXT_DOMAIN +# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \ + npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) +#else +# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \ + npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) +#endif +#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ + npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES) +#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \ + npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category) + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +pgettext_aux (const char *domain, + const char *msg_ctxt_id, const char *msgid, + int category) +{ + const char *translation = dcgettext (domain, msg_ctxt_id, category); + if (translation == msg_ctxt_id) + return msgid; + else + return translation; +} + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +npgettext_aux (const char *domain, + const char *msg_ctxt_id, const char *msgid, + const char *msgid_plural, unsigned long int n, + int category) +{ + const char *translation = + dcngettext (domain, msg_ctxt_id, msgid_plural, n, category); + if (translation == msg_ctxt_id || translation == msgid_plural) + return (n == 1 ? msgid : msgid_plural); + else + return translation; +} + +/* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID + can be arbitrary expressions. But for string literals these macros are + less efficient than those above. */ + +#include + +#if (((__GNUC__ >= 3 || __GNUG__ >= 2) && !defined __STRICT_ANSI__) \ + /* || __STDC_VERSION__ >= 199901L */ ) +# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 1 +#else +# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 0 +#endif + +#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS +#include +#endif + +#define pgettext_expr(Msgctxt, Msgid) \ + dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES) +#define dpgettext_expr(Domainname, Msgctxt, Msgid) \ + dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES) + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +dcpgettext_expr (const char *domain, + const char *msgctxt, const char *msgid, + int category) +{ + size_t msgctxt_len = strlen (msgctxt) + 1; + size_t msgid_len = strlen (msgid) + 1; + const char *translation; +#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + char msg_ctxt_id[msgctxt_len + msgid_len]; +#else + char buf[1024]; + char *msg_ctxt_id = + (msgctxt_len + msgid_len <= sizeof (buf) + ? buf + : (char *) malloc (msgctxt_len + msgid_len)); + if (msg_ctxt_id != NULL) +#endif + { + int found_translation; + memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1); + msg_ctxt_id[msgctxt_len - 1] = '\004'; + memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len); + translation = dcgettext (domain, msg_ctxt_id, category); + found_translation = (translation != msg_ctxt_id); +#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + if (msg_ctxt_id != buf) + free (msg_ctxt_id); +#endif + if (found_translation) + return translation; + } + return msgid; +} + +#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \ + dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) +#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \ + dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES) + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static const char * +dcnpgettext_expr (const char *domain, + const char *msgctxt, const char *msgid, + const char *msgid_plural, unsigned long int n, + int category) +{ + size_t msgctxt_len = strlen (msgctxt) + 1; + size_t msgid_len = strlen (msgid) + 1; + const char *translation; +#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + char msg_ctxt_id[msgctxt_len + msgid_len]; +#else + char buf[1024]; + char *msg_ctxt_id = + (msgctxt_len + msgid_len <= sizeof (buf) + ? buf + : (char *) malloc (msgctxt_len + msgid_len)); + if (msg_ctxt_id != NULL) +#endif + { + int found_translation; + memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1); + msg_ctxt_id[msgctxt_len - 1] = '\004'; + memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len); + translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category); + found_translation = !(translation == msg_ctxt_id || translation == msgid_plural); +#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS + if (msg_ctxt_id != buf) + free (msg_ctxt_id); +#endif + if (found_translation) + return translation; + } + return (n == 1 ? msgid : msgid_plural); +} + +#endif /* _LIBGETTEXT_H */ diff --git a/lib/libx52/Makefile.am b/lib/libx52/Makefile.am index 9d905a7..b11b8cd 100644 --- a/lib/libx52/Makefile.am +++ b/lib/libx52/Makefile.am @@ -12,8 +12,9 @@ lib_LTLIBRARIES = libx52.la # This library handles the USB communication between the host and the X52 libx52_la_SOURCES = x52_control.c x52_core.c x52_date_time.c x52_mfd_led.c \ x52_strerror.c -libx52_la_CFLAGS = @LIBUSB_CFLAGS@ +libx52_la_CFLAGS = @LIBUSB_CFLAGS@ -DLOCALEDIR=\"$(localedir)\" -I $(top_srcdir) libx52_la_LDFLAGS = -version-info 3:0:1 @LIBUSB_LIBS@ +libx52_la_LIBADD = @LTLIBINTL@ # Header files that need to be copied x52includedir = $(includedir)/x52pro diff --git a/lib/libx52/libx52.h b/lib/libx52/libx52.h index 6e68747..e0783b2 100644 --- a/lib/libx52/libx52.h +++ b/lib/libx52/libx52.h @@ -631,7 +631,7 @@ int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value); * @returns Pointer to a NULL terminated string describing the error. * Returned pointer must not be freed. */ -char * libx52_strerror(libx52_error_code error); +const char * libx52_strerror(libx52_error_code error); /** @} */ diff --git a/lib/libx52/x52_core.c b/lib/libx52/x52_core.c index be42d8e..85b1eca 100644 --- a/lib/libx52/x52_core.c +++ b/lib/libx52/x52_core.c @@ -16,6 +16,7 @@ #include "x52_commands.h" #include "x52_common.h" #include "x52_hotplug.h" +#include "gettext.h" #define VENDOR_SAITEK 0x06a3 #define X52_PROD_X52PRO 0x0762 @@ -110,6 +111,10 @@ int libx52_init(libx52_device **dev) } *dev = x52_dev; + + /* Setup the gettext utilities */ + bindtextdomain(PACKAGE, LOCALEDIR); + return LIBX52_SUCCESS; err_recovery: diff --git a/lib/libx52/x52_strerror.c b/lib/libx52/x52_strerror.c index 74ddb5f..1bee34c 100644 --- a/lib/libx52/x52_strerror.c +++ b/lib/libx52/x52_strerror.c @@ -9,14 +9,15 @@ #include #include "libx52.h" +#include "gettext.h" /** For future use in i18n */ -#define _(str) str +#define _(str) dgettext(PACKAGE, str) /* Error buffer used for building custom error strings */ static char error_buffer[256]; -char * libx52_strerror(libx52_error_code error) +const char * libx52_strerror(libx52_error_code error) { switch (error) { case LIBX52_SUCCESS: diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..d0a89dd --- /dev/null +++ b/po/Makevars @@ -0,0 +1,78 @@ +# Makefile variables for PO directory in any package using GNU gettext. + +# Usually the message domain is the same as the package name. +DOMAIN = $(PACKAGE) + +# These two variables depend on the location of this directory. +subdir = po +top_builddir = .. + +# These options get passed to xgettext. +XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ + +# This is the copyright holder that gets inserted into the header of the +# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding +# package. (Note that the msgstr strings, extracted from the package's +# sources, belong to the copyright holder of the package.) Translators are +# expected to transfer the copyright for their translations to this person +# or entity, or to disclaim their copyright. The empty string stands for +# the public domain; in this case the translators are expected to disclaim +# their copyright. +COPYRIGHT_HOLDER = Nirenjan Krishnan + +# This tells whether or not to prepend "GNU " prefix to the package +# name that gets inserted into the header of the $(DOMAIN).pot file. +# Possible values are "yes", "no", or empty. If it is empty, try to +# detect it automatically by scanning the files in $(top_srcdir) for +# "GNU packagename" string. +PACKAGE_GNU = no + +# This is the email address or URL to which the translators shall report +# bugs in the untranslated strings: +# - Strings which are not entire sentences, see the maintainer guidelines +# in the GNU gettext documentation, section 'Preparing Strings'. +# - Strings which use unclear terms or require additional context to be +# understood. +# - Strings which make invalid assumptions about notation of date, time or +# money. +# - Pluralisation problems. +# - Incorrect English spelling. +# - Incorrect formatting. +# It can be your email address, or a mailing list address where translators +# can write to without being subscribed, or the URL of a web page through +# which the translators can contact you. +MSGID_BUGS_ADDRESS = https://github.com/nirenjan/x52pro-linux/issues + +# This is the list of locale categories, beyond LC_MESSAGES, for which the +# message catalogs shall be used. It is usually empty. +EXTRA_LOCALE_CATEGORIES = + +# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt' +# context. Possible values are "yes" and "no". Set this to yes if the +# package uses functions taking also a message context, like pgettext(), or +# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument. +USE_MSGCTXT = no + +# These options get passed to msgmerge. +# Useful options are in particular: +# --previous to keep previous msgids of translated messages, +# --quiet to reduce the verbosity. +MSGMERGE_OPTIONS = + +# These options get passed to msginit. +# If you want to disable line wrapping when writing PO files, add +# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and +# MSGINIT_OPTIONS. +MSGINIT_OPTIONS = + +# This tells whether or not to regenerate a PO file when $(DOMAIN).pot +# has changed. Possible values are "yes" and "no". Set this to no if +# the POT file is checked in the repository and the version control +# program ignores timestamps. +PO_DEPENDS_ON_POT = yes + +# This tells whether or not to forcibly update $(DOMAIN).pot and +# regenerate PO files on "make dist". Possible values are "yes" and +# "no". Set this to no if the POT file and PO files are maintained +# externally. +DIST_DEPENDS_ON_UPDATE_PO = yes diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..0f0b4bc --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,2 @@ +# List of source files which contain translatable strings. +lib/libx52/x52_strerror.c diff --git a/po/x52pro-linux.pot b/po/x52pro-linux.pot new file mode 100644 index 0000000..d4e191d --- /dev/null +++ b/po/x52pro-linux.pot @@ -0,0 +1,91 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Nirenjan Krishnan +# This file is distributed under the same license as the x52pro-linux package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: x52pro-linux 0.2.1\n" +"Report-Msgid-Bugs-To: https://github.com/nirenjan/x52pro-linux/issues\n" +"POT-Creation-Date: 2020-05-19 08:00-0700\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: lib/libx52/x52_strerror.c:24 +msgid "Success" +msgstr "" + +#: lib/libx52/x52_strerror.c:27 +msgid "Initialization failure" +msgstr "" + +#: lib/libx52/x52_strerror.c:30 +msgid "Insufficient memory" +msgstr "" + +#: lib/libx52/x52_strerror.c:33 +msgid "Invalid parameter" +msgstr "" + +#: lib/libx52/x52_strerror.c:36 +msgid "Operation not supported" +msgstr "" + +#: lib/libx52/x52_strerror.c:39 +msgid "Try again" +msgstr "" + +#: lib/libx52/x52_strerror.c:42 +msgid "Input parameter out of range" +msgstr "" + +#: lib/libx52/x52_strerror.c:45 +msgid "USB transaction failure" +msgstr "" + +#: lib/libx52/x52_strerror.c:48 +msgid "USB input/output error" +msgstr "" + +#: lib/libx52/x52_strerror.c:51 +msgid "Access denied" +msgstr "" + +#: lib/libx52/x52_strerror.c:54 +msgid "No such device" +msgstr "" + +#: lib/libx52/x52_strerror.c:57 +msgid "Entity not found" +msgstr "" + +#: lib/libx52/x52_strerror.c:60 +msgid "Resource busy" +msgstr "" + +#: lib/libx52/x52_strerror.c:63 +msgid "Operation timeout" +msgstr "" + +#: lib/libx52/x52_strerror.c:66 +msgid "Overflow" +msgstr "" + +#: lib/libx52/x52_strerror.c:69 +msgid "Pipe error" +msgstr "" + +#: lib/libx52/x52_strerror.c:72 +msgid "System call interrupted" +msgstr "" + +#: lib/libx52/x52_strerror.c:76 +#, c-format +msgid "Unknown error %d" +msgstr ""