naev 0.10.4
gettext.c
Go to the documentation of this file.
1/*
2 * See Licensing and Copyright notice in naev.h
3 */
10#include <ctype.h>
11#include <locale.h>
12#include <stdlib.h>
13#include "physfs.h"
14
15#include "naev.h"
18#include "gettext.h"
19
20#include "array.h"
21#include "env.h"
22#include "log.h"
23#include "msgcat.h"
24#include "ndata.h"
25
26typedef struct translation {
27 char *language;
29 char **chain_lang;
30 struct translation *next;
32
33static char *gettext_systemLanguage = NULL;
36static uint32_t gettext_nstrings = 0;
38static void gettext_readStats (void);
39static const char* gettext_matchLanguage( const char* lang, size_t lang_len, char*const* available );
40
46void gettext_init (void)
47{
48 const char *env_vars[] = {"LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"};
49
50 setlocale( LC_ALL, "" );
51 /* If we don't disable LC_NUMERIC, lots of stuff blows up because 1,000 can be interpreted as
52 * 1.0 in certain languages. */
53 setlocale( LC_NUMERIC, "C" ); /* Disable numeric locale part. */
54
56 for (size_t i=0; i < sizeof(env_vars)/sizeof(env_vars[0]); i++) {
57 const char *language = getenv( env_vars[i] );
58 if (language != NULL && *language != 0) {
59 gettext_systemLanguage = strdup( language );
60 return; /* The first env var with language settings wins. */
61 }
62 }
63 gettext_systemLanguage = strdup( "" );
64}
65
70void gettext_exit (void)
71{
74 while (gettext_translations != NULL) {
75 for (int i = 0; i < array_size(gettext_translations->chain_lang); i++)
78 for (int i = 0; i < array_size(gettext_translations->chain); i++)
79 free( (void*) gettext_translations->chain[i].map );
85 }
86}
87
93const char* gettext_getLanguage (void)
94{
97 else
98 return "en";
99}
100
106void gettext_setLanguage( const char* lang )
107{
108 translation_t *newtrans;
109 char root[256], **paths, **available_langs;
110
111 if (lang == NULL)
113 if (gettext_activeTranslation != NULL && !strcmp( lang, gettext_activeTranslation->language ))
114 return;
115
116 /* Search for the selected language in the loaded translations. */
117 for (translation_t *ptrans = gettext_translations; ptrans != NULL; ptrans = ptrans->next)
118 if (!strcmp( lang, ptrans->language )) {
120 return;
121 }
122
123 /* Load a new translation chain from ndata, and activate it. */
124 newtrans = calloc( 1, sizeof( translation_t ) );
125 newtrans->language = strdup( lang );
126 newtrans->chain = array_create( msgcat_t );
127 newtrans->chain_lang = array_create( char* );
128 newtrans->next = gettext_translations;
129 gettext_translations = newtrans;
130
131 available_langs = PHYSFS_enumerateFiles( GETTEXT_PATH );
132
133 /* @TODO This code orders the translations alphabetically by file path.
134 * That doesn't make sense, but this is a new use case and it's unclear
135 * how we should determine precedence in case multiple .mo files exist. */
136 while (lang != NULL && *lang != 0) {
137 size_t map_size, lang_part_len;
138 const char *lang_part = lang, *lang_match;
139 lang = strchr(lang, ':');
140 if (lang == NULL)
141 lang_part_len = strlen(lang_part);
142 else {
143 lang_part_len = (size_t)(lang-lang_part);
144 lang++;
145 }
146 lang_match = gettext_matchLanguage( lang_part, lang_part_len, available_langs );
147 if (lang_match == NULL)
148 continue;
149 snprintf( root, sizeof(root), GETTEXT_PATH"%s", lang_match );
150 paths = ndata_listRecursive( root );
151 for (int i=0; i<array_size(paths); i++) {
152 const char *map = ndata_read( paths[i], &map_size );
153 if (map != NULL) {
154 msgcat_init( &array_grow( &newtrans->chain ), map, map_size );
155 array_push_back( &newtrans->chain_lang, strdup( lang_match ) );
156 DEBUG( _("Adding translations from %s"), paths[i] );
157 }
158 free( paths[i] );
159 }
160 array_free( paths );
161 }
162 PHYSFS_freeList( available_langs );
163 gettext_activeTranslation = newtrans;
164}
165
171static const char* gettext_matchLanguage( const char* lang, size_t lang_len, char*const* available )
172{
173 const char *best = NULL;
174
175 if (lang_len == 0)
176 return NULL;
177
178 /* Good enough for now: Return the greatest (thus longest) string matching up to their common length. */
179 for (size_t i=0; available[i]!=NULL; i++) {
180 int c = strncmp( lang, available[i], MIN( lang_len, strlen(available[i]) ) );
181 if (c < 0)
182 break;
183 else if (c == 0)
184 best = available[i];
185 }
186 return best;
187}
188
198const char* gettext_ngettext( const char* msgid, const char* msgid_plural, uint64_t n )
199{
200 if (gettext_activeTranslation != NULL) {
202 for (int i=0; i<array_size(chain); i++) {
203 const char *trans = msgcat_ngettext( &chain[i], msgid, msgid_plural, n );
204 if (trans != NULL)
205 return (char*)trans;
206 }
207 }
208
209 return n>1 && msgid_plural!=NULL ? msgid_plural : msgid;
210}
211
215const char* gettext_pgettext_impl( const char* lookup, const char* msgid )
216{
217 const char *trans = _( lookup );
218 return trans==lookup ? msgid : trans;
219}
220
221
226static void gettext_readStats (void)
227{
228 char **paths = ndata_listRecursive( GETTEXT_STATS_PATH );
229
230 for (int i=0; i<array_size(paths); i++) {
231 size_t size;
232 char *text = ndata_read( paths[i], &size );
233 if (text != NULL)
234 gettext_nstrings += strtoul( text, NULL, 10 );
235 free( text );
236 free( paths[i] );
237 }
238 array_free( paths );
239}
240
241
247{
249 LanguageOption en = { .language = strdup("en"), .coverage = 1. };
250 char **dirs = PHYSFS_enumerateFiles( GETTEXT_PATH );
251
252 array_push_back( &opts, en );
253 for (size_t i=0; dirs[i]!=NULL; i++) {
254 LanguageOption *opt = &array_grow( &opts );
255 opt->language = strdup( dirs[i] );
256 opt->coverage = gettext_languageCoverage( dirs[i] );
257 }
258 PHYSFS_freeList( dirs );
259
260 return opts;
261}
262
263
269double gettext_languageCoverage( const char* lang )
270{
271 uint32_t translated = 0;
272 char **paths, dirpath[PATH_MAX], buf[12];
273
274 /* We nail 100% of the translations we don't have to do. :) */
275 if (!strcmp( lang, "en" ))
276 return 1.;
277
278 /* Initialize gettext_nstrings, if needed. */
279 if (gettext_nstrings == 0)
281
282 snprintf( dirpath, sizeof(dirpath), GETTEXT_PATH"%s", lang );
283 paths = ndata_listRecursive( dirpath );
284 for (int j=0; j<array_size(paths); j++) {
285 PHYSFS_file *file;
286 PHYSFS_sint64 size;
287 file = PHYSFS_openRead( paths[j] );
288 free( paths[j] );
289 if (file == NULL)
290 continue;
291 size = PHYSFS_readBytes( file, buf, 12 );
292 if (size < 12)
293 continue;
294 translated += msgcat_nstringsFromHeader( buf );
295 }
296 array_free( paths );
297 return (double)translated / gettext_nstrings;
298}
299
300
301/* The function is almost the same as p_() but msgctxt and msgid can be string variables.
302 */
303const char* pgettext_var( const char* msgctxt, const char* msgid )
304{
305 char *lookup = NULL;
306 const char* trans;
307 asprintf( &lookup, "%s" GETTEXT_CONTEXT_GLUE "%s", msgctxt, msgid );
308 trans = gettext_pgettext_impl( lookup, msgid );
309 free( lookup );
310 return trans;
311}
Provides macros to work with dynamic arrays.
#define array_free(ptr_array)
Frees memory allocated and sets array to NULL.
Definition: array.h:158
static ALWAYS_INLINE int array_size(const void *array)
Returns number of elements in the array.
Definition: array.h:168
#define array_grow(ptr_array)
Increases the number of elements by one and returns the last element.
Definition: array.h:119
#define array_push_back(ptr_array, element)
Adds a new element at the end of the array.
Definition: array.h:129
#define array_create(basic_type)
Creates a new dynamic array of ‘basic_type’.
Definition: array.h:93
const char * gettext_ngettext(const char *msgid, const char *msgid_plural, uint64_t n)
Return a translated version of the input, using the current language catalogs.
Definition: gettext.c:198
void gettext_exit(void)
Free resources associated with the translation system. This invalidates previously returned pointers ...
Definition: gettext.c:70
static translation_t * gettext_activeTranslation
Definition: gettext.c:35
double gettext_languageCoverage(const char *lang)
Return the fraction of strings which have a translation into the given language.
Definition: gettext.c:269
void gettext_setLanguage(const char *lang)
Set the translation language.
Definition: gettext.c:106
const char * gettext_getLanguage(void)
Gets the active (primary) translation language. Even in case of a complex locale, this will be the na...
Definition: gettext.c:93
static const char * gettext_matchLanguage(const char *lang, size_t lang_len, char *const *available)
Pick the best match from "available" (a physfs listing) for the string-slice with address lang,...
Definition: gettext.c:171
const char * pgettext_var(const char *msgctxt, const char *msgid)
Definition: gettext.c:303
static void gettext_readStats(void)
Read the GETTEXT_STATS_PATH data and compute gettext_nstrings. (Common case: just a "naev....
Definition: gettext.c:226
static translation_t * gettext_translations
Definition: gettext.c:34
static char * gettext_systemLanguage
Definition: gettext.c:33
LanguageOption * gettext_languageOptions(void)
List the available languages, with completeness statistics.
Definition: gettext.c:246
static uint32_t gettext_nstrings
Definition: gettext.c:36
void gettext_init(void)
Initialize the translation system. There's no presumption that PhysicsFS is available,...
Definition: gettext.c:46
const char * gettext_pgettext_impl(const char *lookup, const char *msgid)
Helper function for p_(): Return _(lookup) with a fallback of msgid rather than lookup.
Definition: gettext.c:215
const char * msgcat_ngettext(const msgcat_t *p, const char *msgid1, const char *msgid2, uint64_t n)
Return a translation, if present, from the given message catalog.
Definition: msgcat.c:101
void msgcat_init(msgcat_t *p, const void *map, size_t map_size)
Initialize a msgcat_t, given the contents and content-length of a .mo file.
Definition: msgcat.c:59
uint32_t msgcat_nstringsFromHeader(const char buf[12])
Return the number of strings in a message catalog, given its first 12 bytes.
Definition: msgcat.c:169
Header file with generic functions and naev-specifics.
#define MIN(x, y)
Definition: naev.h:40
#define PATH_MAX
Definition: naev.h:50
void * ndata_read(const char *path, size_t *filesize)
Reads a file from the ndata (will be NUL terminated).
Definition: ndata.c:154
char ** ndata_listRecursive(const char *path)
Lists all the visible files in a directory, at any depth.
Definition: ndata.c:232
int asprintf(char **strp, const char *fmt,...)
Like sprintf(), but it allocates a large-enough string and returns the pointer in the first argument....
Definition: nstring.c:161
static const double c[]
Definition: rng.c:264
char * language
Definition: gettext.h:13
double coverage
Definition: gettext.h:14
const void * map
Definition: msgcat.h:11
char * language
Definition: gettext.c:27
char ** chain_lang
Definition: gettext.c:29
msgcat_t * chain
Definition: gettext.c:28
struct translation * next
Definition: gettext.c:30