naev 0.11.5
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
55 /* Try to get info from SDL. */
56 SDL_Locale *locales = SDL_GetPreferredLocales();
57 if (locales != NULL) {
58 if (locales[0].language != NULL) {
59 gettext_systemLanguage = strdup( locales[0].language );
60 SDL_free( locales );
61 return;
62 }
63 SDL_free( locales );
64 }
65
67 for (size_t i=0; i < sizeof(env_vars)/sizeof(env_vars[0]); i++) {
68 const char *language = SDL_getenv( env_vars[i] );
69 if (language != NULL && *language != 0) {
70 gettext_systemLanguage = strdup( language );
71 return; /* The first env var with language settings wins. */
72 }
73 }
74 gettext_systemLanguage = strdup( "" );
75}
76
98
103{
105}
106
112const char* gettext_getLanguage (void)
113{
116 else
117 return "en";
118}
119
125void gettext_setLanguage( const char* lang )
126{
127 translation_t *newtrans;
128 char root[256], **paths, **available_langs;
129
130 if (lang == NULL)
132 if (gettext_activeTranslation != NULL && !strcmp( lang, gettext_activeTranslation->language ))
133 return;
134
135 /* Search for the selected language in the loaded translations. */
136 for (translation_t *ptrans = gettext_translations; ptrans != NULL; ptrans = ptrans->next)
137 if (!strcmp( lang, ptrans->language )) {
139 return;
140 }
141
142 /* Load a new translation chain from ndata, and activate it. */
143 newtrans = calloc( 1, sizeof( translation_t ) );
144 newtrans->language = strdup( lang );
145 newtrans->chain = array_create( msgcat_t );
146 newtrans->chain_lang = array_create( char* );
147 newtrans->next = gettext_translations;
148 gettext_translations = newtrans;
149
150 available_langs = PHYSFS_enumerateFiles( GETTEXT_PATH );
151
152 /* @TODO This code orders the translations alphabetically by file path.
153 * That doesn't make sense, but this is a new use case and it's unclear
154 * how we should determine precedence in case multiple .mo files exist. */
155 while (lang != NULL && *lang != 0) {
156 size_t map_size, lang_part_len;
157 const char *lang_part = lang, *lang_match;
158 lang = strchr(lang, ':');
159 if (lang == NULL)
160 lang_part_len = strlen(lang_part);
161 else {
162 lang_part_len = (size_t)(lang-lang_part);
163 lang++;
164 }
165 lang_match = gettext_matchLanguage( lang_part, lang_part_len, available_langs );
166 if (lang_match == NULL)
167 continue;
168 snprintf( root, sizeof(root), GETTEXT_PATH"%s", lang_match );
169 paths = ndata_listRecursive( root );
170 for (int i=0; i<array_size(paths); i++) {
171 const char *map = ndata_read( paths[i], &map_size );
172 if (map != NULL) {
173 msgcat_init( &array_grow( &newtrans->chain ), map, map_size );
174 array_push_back( &newtrans->chain_lang, strdup( lang_match ) );
175 DEBUG( _("Adding translations from %s"), paths[i] );
176 }
177 free( paths[i] );
178 }
179 array_free( paths );
180 }
181 PHYSFS_freeList( available_langs );
182 gettext_activeTranslation = newtrans;
183}
184
190static const char* gettext_matchLanguage( const char* lang, size_t lang_len, char*const* available )
191{
192 const char *best = NULL;
193
194 if (lang_len == 0)
195 return NULL;
196
197 /* Good enough for now: Return the greatest (thus longest) string matching up to their common length. */
198 for (size_t i=0; available[i]!=NULL; i++) {
199 int c = strncmp( lang, available[i], MIN( lang_len, strlen(available[i]) ) );
200 if (c < 0)
201 break;
202 else if (c == 0)
203 best = available[i];
204 }
205 return best;
206}
207
217const char* gettext_ngettext( const char* msgid, const char* msgid_plural, uint64_t n )
218{
219 if (gettext_activeTranslation != NULL) {
221 for (int i=0; i<array_size(chain); i++) {
222 const char *trans = msgcat_ngettext( &chain[i], msgid, msgid_plural, n );
223 if (trans != NULL)
224 return (char*)trans;
225 }
226 }
227
228 return n>1 && msgid_plural!=NULL ? msgid_plural : msgid;
229}
230
234const char* gettext_pgettext_impl( const char* lookup, const char* msgid )
235{
236 const char *trans = _( lookup );
237 return trans==lookup ? msgid : trans;
238}
239
244static void gettext_readStats (void)
245{
246 char **paths = ndata_listRecursive( GETTEXT_STATS_PATH );
247
248 for (int i=0; i<array_size(paths); i++) {
249 size_t size;
250 char *text = ndata_read( paths[i], &size );
251 if (text != NULL)
252 gettext_nstrings += strtoul( text, NULL, 10 );
253 free( text );
254 free( paths[i] );
255 }
256 array_free( paths );
257}
258
264{
266 LanguageOption en = { .language = strdup("en"), .coverage = 1. };
267 char **dirs = PHYSFS_enumerateFiles( GETTEXT_PATH );
268
269 array_push_back( &opts, en );
270 for (size_t i=0; dirs[i]!=NULL; i++) {
271 LanguageOption *opt = &array_grow( &opts );
272 opt->language = strdup( dirs[i] );
273 opt->coverage = gettext_languageCoverage( dirs[i] );
274 }
275 PHYSFS_freeList( dirs );
276
277 return opts;
278}
279
285double gettext_languageCoverage( const char* lang )
286{
287 uint32_t translated = 0;
288 char **paths, dirpath[PATH_MAX], buf[12];
289
290 /* We nail 100% of the translations we don't have to do. :) */
291 if (!strcmp( lang, "en" ))
292 return 1.;
293
294 /* Initialize gettext_nstrings, if needed. */
295 if (gettext_nstrings == 0)
297
298 snprintf( dirpath, sizeof(dirpath), GETTEXT_PATH"%s", lang );
299 paths = ndata_listRecursive( dirpath );
300 for (int j=0; j<array_size(paths); j++) {
301 PHYSFS_file *file;
302 PHYSFS_sint64 size;
303 file = PHYSFS_openRead( paths[j] );
304 free( paths[j] );
305 if (file == NULL)
306 continue;
307 size = PHYSFS_readBytes( file, buf, 12 );
308 if (size < 12)
309 continue;
310 translated += msgcat_nstringsFromHeader( buf );
311 }
312 array_free( paths );
313 return (double)translated / gettext_nstrings;
314}
315
316/* The function is almost the same as p_() but msgctxt and msgid can be string variables.
317 */
318const char* pgettext_var( const char* msgctxt, const char* msgid )
319{
320 char *lookup = NULL;
321 const char* trans;
322 SDL_asprintf( &lookup, "%s" GETTEXT_CONTEXT_GLUE "%s", msgctxt, msgid );
323 trans = gettext_pgettext_impl( lookup, msgid );
324 free( lookup );
325 return trans;
326}
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:217
void gettext_exit(void)
Free resources associated with the translation system. This invalidates previously returned pointers ...
Definition gettext.c:81
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:285
void gettext_setLanguage(const char *lang)
Set the translation language.
Definition gettext.c:125
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:112
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:190
const char * pgettext_var(const char *msgctxt, const char *msgid)
Definition gettext.c:318
static void gettext_readStats(void)
Read the GETTEXT_STATS_PATH data and compute gettext_nstrings. (Common case: just a "naev....
Definition gettext.c:244
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:263
const char * gettext_getSystemLanguage(void)
Gets the current system language as detected by Naev.
Definition gettext.c:102
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:234
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:231
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