summaryrefslogtreecommitdiff
path: root/src/locale
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2014-07-27 04:29:56 -0400
committerRich Felker <dalias@aerifal.cx>2014-07-27 04:29:56 -0400
commit2068b4e8911a3a49cded44b4568f6c943a8c98f8 (patch)
treed662b28c1a9f87674d0bc25be051a8fb69312316 /src/locale
parentc5b8f1930512d206a7c1cf1093a4a47e1722a414 (diff)
downloadmusl-2068b4e8911a3a49cded44b4568f6c943a8c98f8.tar.gz
implement gettext message translation functions
this commit replaces the stub implementations with working message translation functions. translation units are factored so as to prevent pulling in the legacy, non-library-safe functions which use a global textdomain in modern code which is using the versions with an explicit domain argument. bind_textdomain_codeset is also placed in its own file since it should not be needed by most programs. this implementation is still missing some features: the LANGUAGE environment variable (for multiple fallback languages) is not honored, and non-default plural-form rules are not supported. these issues will be addressed in a later commit. one notable difference from the GNU implementation is that there is no default path for loading translation files. in principle one could be added, but since the documented correct usage is to call the bindtextdomain function, a default path is probably unnecessary.
Diffstat (limited to 'src/locale')
-rw-r--r--src/locale/bind_textdomain_codeset.c11
-rw-r--r--src/locale/dcngettext.c216
-rw-r--r--src/locale/intl.c68
-rw-r--r--src/locale/textdomain.c44
4 files changed, 271 insertions, 68 deletions
diff --git a/src/locale/bind_textdomain_codeset.c b/src/locale/bind_textdomain_codeset.c
new file mode 100644
index 00000000..5ebfd5e8
--- /dev/null
+++ b/src/locale/bind_textdomain_codeset.c
@@ -0,0 +1,11 @@
+#include <libintl.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+
+char *bind_textdomain_codeset(const char *domainname, const char *codeset)
+{
+ if (codeset && strcasecmp(codeset, "UTF-8"))
+ errno = EINVAL;
+ return NULL;
+}
diff --git a/src/locale/dcngettext.c b/src/locale/dcngettext.c
new file mode 100644
index 00000000..4f9e4174
--- /dev/null
+++ b/src/locale/dcngettext.c
@@ -0,0 +1,216 @@
+#include <libintl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include "locale_impl.h"
+#include "libc.h"
+#include "atomic.h"
+
+struct binding {
+ struct binding *next;
+ int dirlen;
+ int active;
+ char *domainname;
+ char *dirname;
+ char buf[];
+};
+
+static void *volatile bindings;
+
+static char *gettextdir(const char *domainname, size_t *dirlen)
+{
+ struct binding *p;
+ for (p=bindings; p; p=p->next) {
+ if (!strcmp(p->domainname, domainname) && p->active) {
+ *dirlen = p->dirlen;
+ return (char *)p->dirname;
+ }
+ }
+ return 0;
+}
+
+char *bindtextdomain(const char *domainname, const char *dirname)
+{
+ static int lock[2];
+ struct binding *p, *q;
+
+ if (!domainname) return 0;
+ if (!dirname) return gettextdir(domainname, &(size_t){0});
+
+ size_t domlen = strlen(domainname);
+ size_t dirlen = strlen(dirname);
+ if (domlen > NAME_MAX || dirlen >= PATH_MAX) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ LOCK(lock);
+
+ for (p=bindings; p; p=p->next) {
+ if (!strcmp(p->domainname, domainname) &&
+ !strcmp(p->dirname, dirname)) {
+ break;
+ }
+ }
+
+ if (!p) {
+ p = malloc(sizeof *p + domlen + dirlen + 2);
+ if (!p) {
+ UNLOCK(lock);
+ return 0;
+ }
+ p->next = bindings;
+ p->dirlen = dirlen;
+ p->domainname = p->buf;
+ p->dirname = p->buf + domlen + 1;
+ memcpy(p->domainname, domainname, domlen+1);
+ memcpy(p->dirname, dirname, dirlen+1);
+ a_cas_p(&bindings, bindings, p);
+ }
+
+ a_store(&p->active, 1);
+
+ for (q=bindings; q; q=q->next) {
+ if (!strcmp(p->domainname, domainname) && q != p)
+ a_store(&q->active, 0);
+ }
+
+ UNLOCK(lock);
+
+ return (char *)p->dirname;
+}
+
+static const char catnames[][12] = {
+ "LC_TIME",
+ "LC_COLLATE",
+ "LC_MONETARY",
+ "LC_MESSAGES",
+};
+
+static const char catlens[] = { 7, 10, 11, 11 };
+
+struct msgcat {
+ struct msgcat *next;
+ const void *map;
+ size_t map_size;
+ char name[];
+};
+
+static char *dummy_gettextdomain()
+{
+ return "messages";
+}
+
+weak_alias(dummy_gettextdomain, __gettextdomain);
+
+const unsigned char *__map_file(const char *, size_t *);
+int __munmap(void *, size_t);
+
+char *dcngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n, int category)
+{
+ static struct msgcat *volatile cats;
+ struct msgcat *p;
+ struct __locale_struct *loc = CURRENT_LOCALE;
+ struct __locale_map *lm;
+ const char *dirname, *locname, *catname;
+ size_t dirlen, loclen, catlen, domlen;
+
+ if (!domainname) domainname = __gettextdomain();
+
+ domlen = strlen(domainname);
+ if (domlen > NAME_MAX) goto notrans;
+
+ dirname = gettextdir(domainname, &dirlen);
+ if (!dirname) goto notrans;
+
+ switch (category) {
+ case LC_MESSAGES:
+ locname = loc->messages_name;
+ if (!*locname) goto notrans;
+ break;
+ case LC_TIME:
+ case LC_MONETARY:
+ case LC_COLLATE:
+ lm = loc->cat[category-2];
+ if (!lm) goto notrans;
+ locname = lm->name;
+ break;
+ default:
+notrans:
+ return (char *) ((n == 1) ? msgid1 : msgid2);
+ }
+
+ catname = catnames[category-2];
+ catlen = catlens[category-2];
+ loclen = strlen(locname);
+
+ size_t namelen = dirlen+1 + loclen+1 + catlen+1 + domlen+3;
+ char name[namelen+1], *s = name;
+
+ memcpy(s, dirname, dirlen);
+ s[dirlen] = '/';
+ s += dirlen + 1;
+ memcpy(s, locname, loclen);
+ s[loclen] = '/';
+ s += loclen + 1;
+ memcpy(s, catname, catlen);
+ s[catlen] = '/';
+ s += catlen + 1;
+ memcpy(s, domainname, domlen);
+ s[domlen] = '.';
+ s[domlen+1] = 'm';
+ s[domlen+2] = 'o';
+ s[domlen+3] = 0;
+
+ for (p=cats; p; p=p->next)
+ if (!strcmp(p->name, name))
+ break;
+
+ if (!p) {
+ void *old_cats;
+ size_t map_size;
+ const void *map = __map_file(name, &map_size);
+ if (!map) goto notrans;
+ p = malloc(sizeof *p + namelen + 1);
+ if (!p) {
+ __munmap((void *)map, map_size);
+ goto notrans;
+ }
+ p->map = map;
+ p->map_size = map_size;
+ memcpy(p->name, name, namelen+1);
+ do {
+ old_cats = cats;
+ p->next = old_cats;
+ } while (a_cas_p(&cats, old_cats, p) != old_cats);
+ }
+
+ const char *trans = __mo_lookup(p->map, p->map_size, msgid1);
+ if (!trans) goto notrans;
+
+ /* FIXME: support alternate plural rules */
+ if (n != 1) {
+ size_t l = strlen(trans);
+ if (l+1 >= p->map_size - (trans - (char *)p->map))
+ goto notrans;
+ trans += l+1;
+ }
+ return (char *)trans;
+}
+
+char *dcgettext(const char *domainname, const char *msgid, int category)
+{
+ return dcngettext(domainname, msgid, msgid, 1, category);
+}
+
+char *dngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n)
+{
+ return dcngettext(domainname, msgid1, msgid2, n, LC_MESSAGES);
+}
+
+char *dgettext(const char *domainname, const char *msgid)
+{
+ return dcngettext(domainname, msgid, msgid, 1, LC_MESSAGES);
+}
diff --git a/src/locale/intl.c b/src/locale/intl.c
deleted file mode 100644
index ad040524..00000000
--- a/src/locale/intl.c
+++ /dev/null
@@ -1,68 +0,0 @@
-#include <libintl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-#include <errno.h>
-
-char *gettext(const char *msgid)
-{
- return (char *) msgid;
-}
-
-char *dgettext(const char *domainname, const char *msgid)
-{
- return (char *) msgid;
-}
-
-char *dcgettext(const char *domainname, const char *msgid, int category)
-{
- return (char *) msgid;
-}
-
-char *ngettext(const char *msgid1, const char *msgid2, unsigned long int n)
-{
- return (char *) ((n == 1) ? msgid1 : msgid2);
-}
-
-char *dngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n)
-{
- return (char *) ((n == 1) ? msgid1 : msgid2);
-}
-
-char *dcngettext(const char *domainname, const char *msgid1, const char *msgid2, unsigned long int n, int category)
-{
- return (char *) ((n == 1) ? msgid1 : msgid2);
-}
-
-char *textdomain(const char *domainname)
-{
- static const char default_str[] = "messages";
-
- if (domainname && *domainname && strcmp(domainname, default_str)) {
- errno = EINVAL;
- return NULL;
- }
- return (char *) default_str;
-}
-
-char *bindtextdomain(const char *domainname, const char *dirname)
-{
- static const char dir[] = "/";
-
- if (!domainname || !*domainname
- || (dirname && ((dirname[0] != '/') || dirname[1]))
- ) {
- errno = EINVAL;
- return NULL;
- }
-
- return (char *) dir;
-}
-
-char *bind_textdomain_codeset(const char *domainname, const char *codeset)
-{
- if (!domainname || !*domainname || (codeset && strcasecmp(codeset, "UTF-8"))) {
- errno = EINVAL;
- }
- return NULL;
-}
diff --git a/src/locale/textdomain.c b/src/locale/textdomain.c
new file mode 100644
index 00000000..c501501d
--- /dev/null
+++ b/src/locale/textdomain.c
@@ -0,0 +1,44 @@
+#include <libintl.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include "libc.h"
+#include "atomic.h"
+
+static char *current_domain;
+
+char *__gettextdomain()
+{
+ return current_domain ? current_domain : "messages";
+}
+
+char *textdomain(const char *domainname)
+{
+ if (!domainname) return __gettextdomain();
+
+ size_t domlen = strlen(domainname);
+ if (domlen > NAME_MAX) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ if (!current_domain) {
+ current_domain = malloc(NAME_MAX+1);
+ if (!current_domain) return 0;
+ }
+
+ memcpy(current_domain, domainname, domlen+1);
+
+ return current_domain;
+}
+
+char *gettext(const char *msgid)
+{
+ return dgettext(0, msgid);
+}
+
+char *ngettext(const char *msgid1, const char *msgid2, unsigned long int n)
+{
+ return dngettext(0, msgid1, msgid2, n);
+}