common: Implement i18n_localegettext.
[gnupg.git] / common / i18n.c
1 /* i18n.c - gettext initialization
2  * Copyright (C) 2007, 2010 Free Software Foundation, Inc.
3  * Copyright (C) 2015 g10 Code GmbH
4  *
5  * This file is free software; you can redistribute it and/or modify
6  * it under the terms of either
7  *
8  *   - the GNU Lesser General Public License as published by the Free
9  *     Software Foundation; either version 3 of the License, or (at
10  *     your option) any later version.
11  *
12  * or
13  *
14  *   - the GNU General Public License as published by the Free
15  *     Software Foundation; either version 2 of the License, or (at
16  *     your option) any later version.
17  *
18  * or both in parallel, as here.
19  *
20  * This file is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, see <http://www.gnu.org/licenses/>.
27  */
28
29 #include <config.h>
30 #ifdef HAVE_LOCALE_H
31 #include <locale.h>
32 #endif
33 #ifdef HAVE_LANGINFO_CODESET
34 #include <langinfo.h>
35 #endif
36
37 #include "util.h"
38 #include "i18n.h"
39
40
41 /* An object to store pointers to static strings and there static
42    translation.  A linked list is not optimal but given that we only
43    have a few dozen messages it should be acceptable. */
44 struct msg_cache_s
45 {
46   struct msg_cache_s *next;
47   const char *key;
48   const char *value;
49 };
50
51 /* A object to store an lc_messages string and a link to the cache
52    object.  */
53 struct msg_cache_heads_s
54 {
55   struct msg_cache_heads_s *next;
56   struct msg_cache_s *cache;
57   char lc_messages[1];
58 };
59
60 /* Out static cache of translated messages.  We need this because
61    there is no gettext API to return a translation depending on the
62    locale.  Switching the locale for each access to a translatable
63    string seems to be too expensive.  Note that this is used only for
64    strings in gpg-agent which are passed to Pinentry.  All other
65    strings are using the regular gettext interface.  Note that we can
66    never release this memory because consumers take the result as
67    static strings.  */
68 static struct msg_cache_heads_s *msgcache;
69
70
71
72 void
73 i18n_init (void)
74 {
75 #ifdef USE_SIMPLE_GETTEXT
76   bindtextdomain (PACKAGE_GT, gnupg_localedir ());
77   textdomain (PACKAGE_GT);
78 #else
79 # ifdef ENABLE_NLS
80   setlocale (LC_ALL, "" );
81   bindtextdomain (PACKAGE_GT, LOCALEDIR);
82   textdomain (PACKAGE_GT);
83 # endif
84 #endif
85 }
86
87
88 /* The Assuan agent protocol requires us to transmit utf-8 strings
89    thus we need a way to temporary switch gettext from native to
90    utf8.  */
91 char *
92 i18n_switchto_utf8 (void)
93 {
94 #ifdef USE_SIMPLE_GETTEXT
95   /* Return an arbitrary pointer as true value.  */
96   return gettext_use_utf8 (1) ? (char*)(-1) : NULL;
97 #elif defined(ENABLE_NLS)
98   char *orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL);
99 # ifdef HAVE_LANGINFO_CODESET
100   if (!orig_codeset)
101     orig_codeset = nl_langinfo (CODESET);
102 # endif
103   if (orig_codeset)
104     { /* We only switch when we are able to restore the codeset later.
105          Note that bind_textdomain_codeset does only return on memory
106          errors but not if a codeset is not available.  Thus we don't
107          bother printing a diagnostic here. */
108       orig_codeset = xstrdup (orig_codeset);
109       if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8"))
110         {
111           xfree (orig_codeset);
112           orig_codeset = NULL;
113         }
114     }
115   return orig_codeset;
116 #else
117   return NULL;
118 #endif
119 }
120
121 /* Switch back to the saved codeset.  */
122 void
123 i18n_switchback (char *saved_codeset)
124 {
125 #ifdef USE_SIMPLE_GETTEXT
126   gettext_use_utf8 (!!saved_codeset);
127 #elif defined(ENABLE_NLS)
128   if (saved_codeset)
129     {
130       bind_textdomain_codeset (PACKAGE_GT, saved_codeset);
131       xfree (saved_codeset);
132     }
133 #else
134   (void)saved_codeset;
135 #endif
136 }
137
138
139 /* Gettext variant which temporary switches to utf-8 for string. */
140 const char *
141 i18n_utf8 (const char *string)
142 {
143   char *saved = i18n_switchto_utf8 ();
144   const char *result = _(string);
145   i18n_switchback (saved);
146   return result;
147 }
148
149
150 /* A variant of gettext which allows to specify the local to use for
151    translating the message.  The function assumes that utf-8 is used
152    for the encoding.  */
153 const char *
154 i18n_localegettext (const char *lc_messages, const char *string)
155 {
156 #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)             \
157   && !defined(USE_SIMPLE_GETTEXT) && defined(ENABLE_NLS)
158   const char *result = NULL;
159   char *saved = NULL;
160   struct msg_cache_heads_s *mh;
161   struct msg_cache_s *mc;
162
163   if (!lc_messages)
164     goto leave;
165
166   /* Lookup in the cache.  */
167   for (mh = msgcache; mh; mh = mh->next)
168     if (!strcmp (mh->lc_messages, lc_messages))
169       break;
170   if (mh)
171     {
172       /* A cache entry for this local exists - find the string.
173          Because the system is designed for static strings it is
174          sufficient to compare the pointers.  */
175       for (mc = mh->cache; mc; mc = mc->next)
176         if (mc->key == string)
177           {
178             /* Cache hit.  */
179             result = mc->value;
180             goto leave;
181           }
182     }
183
184   /* Cached miss.  Change the locale, translate, reset locale.  */
185   saved = setlocale (LC_MESSAGES, NULL);
186   if (!saved)
187     goto leave;
188   saved = xtrystrdup (saved);
189   if (!saved)
190     goto leave;
191   if (!setlocale (LC_MESSAGES, lc_messages))
192     goto leave;
193
194   bindtextdomain (PACKAGE_GT, LOCALEDIR);
195   result = gettext (string);
196   setlocale (LC_MESSAGES, saved);
197   bindtextdomain (PACKAGE_GT, LOCALEDIR);
198
199   /* Cache the result.  */
200   if (!mh)
201     {
202       /* First use of this locale - create an entry.  */
203       mh = xtrymalloc (sizeof *mh + strlen (lc_messages));
204       if (!mh)
205         goto leave;
206       strcpy (mh->lc_messages, lc_messages);
207       mh->cache = NULL;
208       mh->next = msgcache;
209       msgcache = mh;
210     }
211   mc = xtrymalloc (sizeof *mc);
212   if (!mc)
213     goto leave;
214   mc->key = string;
215   mc->value = result;
216   mc->next = mh->cache;
217   mh->cache = mc;
218
219  leave:
220   xfree (saved);
221   return result? result : _(string);
222
223 #else /*!(HAVE_SETLOCALE && LC_MESSAGES ...)*/
224   (void)lc_messages;
225   return _(string);
226 #endif /*!(HAVE_SETLOCALE && LC_MESSAGES ...)*/
227 }