naev 0.12.6
gettext.c
Go to the documentation of this file.
1/*
2 * See Licensing and Copyright notice in naev.h
3 */
10#include "SDL_locale.h"
11#include "physfs.h"
12#include <locale.h>
13#include <stdlib.h>
14
15#include "naev.h"
17
18#include "gettext.h"
19
20#include "array.h"
21#include "log.h"
22#include "msgcat.h"
23#include "ndata.h"
24
25typedef struct translation {
26 char *language;
28 char **chain_lang;
29 struct translation
32
34 NULL;
37 NULL;
39 NULL;
40static uint32_t gettext_nstrings =
41 0;
42
43static void gettext_readStats( void );
44static const char *gettext_matchLanguage( const char *lang, size_t lang_len,
45 char *const *available );
46
53void gettext_init( void )
54{
55 const char *env_vars[] = { "LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG" };
56
57 setlocale( LC_ALL, "" );
58 /* If we don't disable LC_NUMERIC, lots of stuff blows up because 1,000 can
59 * be interpreted as 1.0 in certain languages. */
60 setlocale( LC_NUMERIC, "C" ); /* Disable numeric locale part. */
61
62 /* Try to get info from SDL. */
63 SDL_Locale *locales = SDL_GetPreferredLocales();
64 if ( locales != NULL ) {
65 if ( locales[0].language != NULL ) {
66 gettext_systemLanguage = strdup( locales[0].language );
67 SDL_free( locales );
68 return;
69 }
70 SDL_free( locales );
71 }
72
74 for ( size_t i = 0; i < sizeof( env_vars ) / sizeof( env_vars[0] ); i++ ) {
75 const char *language = SDL_getenv( env_vars[i] );
76 if ( language != NULL && *language != 0 ) {
77 gettext_systemLanguage = strdup( language );
78 return; /* The first env var with language settings wins. */
79 }
80 }
81 gettext_systemLanguage = strdup( "" );
82}
83
89void gettext_exit( void )
90{
93 while ( gettext_translations != NULL ) {
94 for ( int i = 0; i < array_size( gettext_translations->chain_lang ); i++ )
95 free( gettext_translations->chain_lang[i] );
96 array_free( gettext_translations->chain_lang );
97 for ( int i = 0; i < array_size( gettext_translations->chain ); i++ )
98 free( (void *)gettext_translations->chain[i].map );
100 free( gettext_translations->language );
102 free( gettext_translations );
104 }
105}
106
110const char *gettext_getSystemLanguage( void )
111{
113}
114
122const char *gettext_getLanguage( void )
123{
124 if ( array_size( gettext_activeTranslation->chain_lang ) )
125 return gettext_activeTranslation->chain_lang[0];
126 else
127 return "en";
128}
129
135void gettext_setLanguage( const char *lang )
136{
137 translation_t *newtrans;
138 char root[256], **paths, **available_langs;
139
140 if ( lang == NULL )
142 if ( gettext_activeTranslation != NULL &&
143 !strcmp( lang, gettext_activeTranslation->language ) )
144 return;
145
146 /* Search for the selected language in the loaded translations. */
147 for ( translation_t *ptrans = gettext_translations; ptrans != NULL;
148 ptrans = ptrans->next )
149 if ( !strcmp( lang, ptrans->language ) ) {
151 return;
152 }
153
154 /* Load a new translation chain from ndata, and activate it. */
155 newtrans = calloc( 1, sizeof( translation_t ) );
156 newtrans->language = strdup( lang );
157 newtrans->chain = array_create( msgcat_t );
158 newtrans->chain_lang = array_create( char * );
159 newtrans->next = gettext_translations;
160 gettext_translations = newtrans;
161
162 available_langs = PHYSFS_enumerateFiles( GETTEXT_PATH );
163
164 /* @TODO This code orders the translations alphabetically by file path.
165 * That doesn't make sense, but this is a new use case and it's unclear
166 * how we should determine precedence in case multiple .mo files exist. */
167 while ( lang != NULL && *lang != 0 ) {
168 size_t map_size, lang_part_len;
169 const char *lang_part = lang, *lang_match;
170 lang = strchr( lang, ':' );
171 if ( lang == NULL )
172 lang_part_len = strlen( lang_part );
173 else {
174 lang_part_len = (size_t)( lang - lang_part );
175 lang++;
176 }
177 lang_match =
178 gettext_matchLanguage( lang_part, lang_part_len, available_langs );
179 if ( lang_match == NULL )
180 continue;
181 snprintf( root, sizeof( root ), GETTEXT_PATH "%s", lang_match );
182 paths = ndata_listRecursive( root );
183 for ( int i = 0; i < array_size( paths ); i++ ) {
184 const char *map = ndata_read( paths[i], &map_size );
185 if ( map != NULL ) {
186 msgcat_init( &array_grow( &newtrans->chain ), map, map_size );
187 array_push_back( &newtrans->chain_lang, strdup( lang_match ) );
188 DEBUG( _( "Adding translations from %s" ), paths[i] );
189 }
190 free( paths[i] );
191 }
192 array_free( paths );
193 }
194 PHYSFS_freeList( available_langs );
195 gettext_activeTranslation = newtrans;
196}
197
204static const char *gettext_matchLanguage( const char *lang, size_t lang_len,
205 char *const *available )
206{
207 const char *best = NULL;
208
209 if ( lang_len == 0 )
210 return NULL;
211
212 /* Good enough for now: Return the greatest (thus longest) string matching up
213 * to their common length. */
214 for ( size_t i = 0; available[i] != NULL; i++ ) {
215 int c =
216 strncmp( lang, available[i], MIN( lang_len, strlen( available[i] ) ) );
217 if ( c < 0 )
218 break;
219 else if ( c == 0 )
220 best = available[i];
221 }
222 return best;
223}
224
238const char *gettext_ngettext( const char *msgid, const char *msgid_plural,
239 uint64_t n )
240{
241 if ( gettext_activeTranslation != NULL ) {
242 msgcat_t *chain = gettext_activeTranslation->chain;
243 for ( int i = 0; i < array_size( chain ); i++ ) {
244 const char *trans =
245 msgcat_ngettext( &chain[i], msgid, msgid_plural, n );
246 if ( trans != NULL )
247 return (char *)trans;
248 }
249 }
250
251 return ( ( ( n > 1 ) || ( n == 0 ) ) && msgid_plural != NULL ) ? msgid_plural
252 : msgid;
253}
254
259const char *gettext_pgettext_impl( const char *lookup, const char *msgid )
260{
261 const char *trans = _( lookup );
262 return trans == lookup ? msgid : trans;
263}
264
270static void gettext_readStats( void )
271{
272 char **paths = ndata_listRecursive( GETTEXT_STATS_PATH );
273
274 for ( int i = 0; i < array_size( paths ); i++ ) {
275 size_t size;
276 char *text = ndata_read( paths[i], &size );
277 if ( text != NULL )
278 gettext_nstrings += strtoul( text, NULL, 10 );
279 free( text );
280 free( paths[i] );
281 }
282 array_free( paths );
283}
284
290{
292 LanguageOption en = { .language = strdup( "en" ), .coverage = 1. };
293 char **dirs = PHYSFS_enumerateFiles( GETTEXT_PATH );
294
295 array_push_back( &opts, en );
296 for ( size_t i = 0; dirs[i] != NULL; i++ ) {
297 LanguageOption *opt = &array_grow( &opts );
298 opt->language = strdup( dirs[i] );
299 opt->coverage = gettext_languageCoverage( dirs[i] );
300 }
301 PHYSFS_freeList( dirs );
302
303 return opts;
304}
305
312double gettext_languageCoverage( const char *lang )
313{
314 uint32_t translated = 0;
315 char **paths, dirpath[PATH_MAX], buf[12];
316
317 /* We nail 100% of the translations we don't have to do. :) */
318 if ( !strcmp( lang, "en" ) )
319 return 1.;
320
321 /* Initialize gettext_nstrings, if needed. */
322 if ( gettext_nstrings == 0 )
324
325 snprintf( dirpath, sizeof( dirpath ), GETTEXT_PATH "%s", lang );
326 paths = ndata_listRecursive( dirpath );
327 for ( int j = 0; j < array_size( paths ); j++ ) {
328 PHYSFS_file *file;
329 PHYSFS_sint64 size;
330 file = PHYSFS_openRead( paths[j] );
331 free( paths[j] );
332 if ( file == NULL )
333 continue;
334 size = PHYSFS_readBytes( file, buf, 12 );
335 if ( size < 12 )
336 continue;
337 translated += msgcat_nstringsFromHeader( buf );
338 }
339 array_free( paths );
340 return (double)translated / gettext_nstrings;
341}
342
343/* The function is almost the same as p_() but msgctxt and msgid can be string
344 * variables.
345 */
346const char *pgettext_var( const char *msgctxt, const char *msgid )
347{
348 char *lookup = NULL;
349 const char *trans;
350 SDL_asprintf( &lookup, "%s" GETTEXT_CONTEXT_GLUE "%s", msgctxt, msgid );
351 trans = gettext_pgettext_impl( lookup, msgid );
352 free( lookup );
353 return trans;
354}
Provides macros to work with dynamic arrays.
#define array_free(ptr_array)
Frees memory allocated and sets array to NULL.
Definition array.h:170
static ALWAYS_INLINE int array_size(const void *array)
Returns number of elements in the array.
Definition array.h:179
#define array_grow(ptr_array)
Increases the number of elements by one and returns the last element.
Definition array.h:122
#define array_push_back(ptr_array, element)
Adds a new element at the end of the array.
Definition array.h:134
#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:238
void gettext_exit(void)
Free resources associated with the translation system. This invalidates previously returned pointers ...
Definition gettext.c:89
static translation_t * gettext_activeTranslation
Definition gettext.c:38
double gettext_languageCoverage(const char *lang)
Return the fraction of strings which have a translation into the given language.
Definition gettext.c:312
void gettext_setLanguage(const char *lang)
Set the translation language.
Definition gettext.c:135
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:122
const char * pgettext_var(const char *msgctxt, const char *msgid)
Definition gettext.c:346
static void gettext_readStats(void)
Read the GETTEXT_STATS_PATH data and compute gettext_nstrings. (Common case: just a "naev....
Definition gettext.c:270
static translation_t * gettext_translations
Definition gettext.c:36
static char * gettext_systemLanguage
Definition gettext.c:33
LanguageOption * gettext_languageOptions(void)
List the available languages, with completeness statistics.
Definition gettext.c:289
const char * gettext_getSystemLanguage(void)
Gets the current system language as detected by Naev.
Definition gettext.c:110
static uint32_t gettext_nstrings
Definition gettext.c:40
void gettext_init(void)
Initialize the translation system. There's no presumption that PhysicsFS is available,...
Definition gettext.c:53
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:204
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:259
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:98
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:56
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:166
Header file with generic functions and naev-specifics.
#define MIN(x, y)
Definition naev.h:39
#define PATH_MAX
Definition naev.h:57
void * ndata_read(const char *path, size_t *filesize)
Reads a file from the ndata (will be NUL terminated).
Definition ndata.c:207
char ** ndata_listRecursive(const char *path)
Lists all the visible files in a directory, at any depth.
Definition ndata.c:286
static const double c[]
Definition rng.c:256
char * language
Definition gettext.h:14
double coverage
Definition gettext.h:15
char * language
Definition gettext.c:26
char ** chain_lang
Definition gettext.c:28
msgcat_t * chain
Definition gettext.c:27
struct translation * next
Definition gettext.c:29