ObjFW  Check-in [e731dc4c75]

Overview
Comment:Implement support for localized strings
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: e731dc4c7597387cd8ec8211dc8730b70649bb17ab9656c2355e57606551144c
User & Date: js on 2017-01-10 19:39:13
Other Links: manifest | tags
Context
2017-01-10
19:51
Do not use DATADIR as a define check-in: e3e38ed68d user: js tags: trunk
19:39
Implement support for localized strings check-in: e731dc4c75 user: js tags: trunk
00:46
Add files that I forgot to add check-in: 967b411ee5 user: js tags: trunk
Changes

Modified src/OFLocalization.h from [93e52fa91a] to [e9be33b3dd].

22
23
24
25
26
27
28



29
30
31
32
33
34
35
36
37
38
39


40
41
42
43
44
45
46
/*! @file */

#define OF_LOCALIZED(ID, ...)				\
	[[OFLocalization sharedLocalization]		\
	    localizedStringForID: ID			\
			fallback: __VA_ARGS__, nil]




/*!
 * @class OFLocalization OFLocalization.h ObjFW/OFLocalization.h
 *
 * @brief A class for querying the locale and retrieving localized strings.
 */
@interface OFLocalization: OFObject
{
	OFString *_language;
	OFString *_territory;
	of_string_encoding_t _encoding;
	OFString *_decimalPoint;


}

/**
 * The language of the locale.
 *
 * If the language is unknown, it is `nil`.
 */







>
>
>











>
>







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*! @file */

#define OF_LOCALIZED(ID, ...)				\
	[[OFLocalization sharedLocalization]		\
	    localizedStringForID: ID			\
			fallback: __VA_ARGS__, nil]

@class OFMutableArray OF_GENERIC(ObjectType);
@class OFDictionary OF_GENERIC(KeyType, ObjectType);

/*!
 * @class OFLocalization OFLocalization.h ObjFW/OFLocalization.h
 *
 * @brief A class for querying the locale and retrieving localized strings.
 */
@interface OFLocalization: OFObject
{
	OFString *_language;
	OFString *_territory;
	of_string_encoding_t _encoding;
	OFString *_decimalPoint;
	OFMutableArray OF_GENERIC(OFDictionary OF_GENERIC(OFString*, id)*)
	    *_localizedStrings;
}

/**
 * The language of the locale.
 *
 * If the language is unknown, it is `nil`.
 */
113
114
115
116
117
118
119







120
121
122
123
124
125
126
127
128
129
130
131







132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

152
153
154
155
156
157
158
/*!
 * @brief Returns the decimal point of the system's locale.
 *
 * @return The decimal point of the system's locale
 */
+ (OFString*)decimalPoint;








/*!
 * @brief Initializes the OFLocalization singleton with the specified locale.
 *
 * @warning You should never call this yourself, except if you do not use
 *	    @ref OFApplication. In this case, you need to allocate exactly one
 *	    instance of OFLocalization, which will be come the singleton, and
 *	    call this method.
 *
 * @param locale The locale used, as returned from `setlocale()`
 */
- initWithLocale: (char*)locale;








/*!
 * @brief Returns the localized string for the specified ID, using the fallback
 *	  string if it cannot be looked up or is missing.
 *
 * @note This takes a variadic argument, terminated by `nil`, that consists of
 *	 pairs of variable names and variable values, which will be replaced
 *	 inside the localized string. For example, you can pass
 *	 `@"name", @"foo", nil`, causing `%[name]` to be replaced with `foo` in
 *	 the localized string.
 *
 * @note Generally, you want to use @ref OF_LOCALIZED instead, which also takes
 *	 care of the `nil` sentinel automatically.
 *
 * @param ID The ID for the localized string
 * @param fallback The fallback to use in case the localized string cannot be
 *		   looked up or is missing
 * @return The localized string
 */
- (OFString*)localizedStringForID: (OFConstantString*)ID
			 fallback: (OFConstantString*)fallback, ... OF_SENTINEL;

/**
 * @brief Returns the localized string for the specified ID, using the fallback
 *	  string if it cannot be looked up or is missing.
 *
 * @note This takes a variadic argument, terminated by `nil` and passed as
 *	 va_list, that consists of pairs of variable names and variable values,
 *	 which will be replaced inside the localized string. For example, you







>
>
>
>
>
>
>












>
>
>
>
>
>
>




















>







118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*!
 * @brief Returns the decimal point of the system's locale.
 *
 * @return The decimal point of the system's locale
 */
+ (OFString*)decimalPoint;

/*!
 * @brief Adds a directory to scan for language files.
 *
 * @param path The path to the directory to scan for language files
 */
+ (void)addLanguageDirectory: (OFString*)path;

/*!
 * @brief Initializes the OFLocalization singleton with the specified locale.
 *
 * @warning You should never call this yourself, except if you do not use
 *	    @ref OFApplication. In this case, you need to allocate exactly one
 *	    instance of OFLocalization, which will be come the singleton, and
 *	    call this method.
 *
 * @param locale The locale used, as returned from `setlocale()`
 */
- initWithLocale: (char*)locale;

/*!
 * @brief Adds a directory to scan for language files.
 *
 * @param path The path to the directory to scan for language files
 */
- (void)addLanguageDirectory: (OFString*)path;

/*!
 * @brief Returns the localized string for the specified ID, using the fallback
 *	  string if it cannot be looked up or is missing.
 *
 * @note This takes a variadic argument, terminated by `nil`, that consists of
 *	 pairs of variable names and variable values, which will be replaced
 *	 inside the localized string. For example, you can pass
 *	 `@"name", @"foo", nil`, causing `%[name]` to be replaced with `foo` in
 *	 the localized string.
 *
 * @note Generally, you want to use @ref OF_LOCALIZED instead, which also takes
 *	 care of the `nil` sentinel automatically.
 *
 * @param ID The ID for the localized string
 * @param fallback The fallback to use in case the localized string cannot be
 *		   looked up or is missing
 * @return The localized string
 */
- (OFString*)localizedStringForID: (OFConstantString*)ID
			 fallback: (OFConstantString*)fallback, ... OF_SENTINEL;

/**
 * @brief Returns the localized string for the specified ID, using the fallback
 *	  string if it cannot be looked up or is missing.
 *
 * @note This takes a variadic argument, terminated by `nil` and passed as
 *	 va_list, that consists of pairs of variable names and variable values,
 *	 which will be replaced inside the localized string. For example, you

Modified src/OFLocalization.m from [ea48dae0ba] to [d001be40f2].

16
17
18
19
20
21
22


23
24
25
26
27
28
29

#include "config.h"

#include <locale.h>

#import "OFLocalization.h"
#import "OFString.h"



#import "OFInvalidArgumentException.h"

static OFLocalization *sharedLocalization = nil;

@implementation OFLocalization
@synthesize language = _language, territory = _territory, encoding = _encoding;







>
>







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

#include "config.h"

#include <locale.h>

#import "OFLocalization.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFDictionary.h"

#import "OFInvalidArgumentException.h"

static OFLocalization *sharedLocalization = nil;

@implementation OFLocalization
@synthesize language = _language, territory = _territory, encoding = _encoding;
49
50
51
52
53
54
55





56
57
58
59







60
61
62
63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
	return [sharedLocalization encoding];
}

+ (OFString*)decimalPoint
{
	return [sharedLocalization decimalPoint];
}






- initWithLocale: (char*)locale
{
	self = [super init];








	if (locale == NULL) {
		_encoding = OF_STRING_ENCODING_UTF_8;
		_decimalPoint = @".";
		return self;
	}

	locale = of_strdup(locale);

	@try {
		char *tmp;


		/* We don't care for extras behind the @ */
		if ((tmp = strrchr(locale, '@')) != NULL)
			*tmp = '\0';

		/* Encoding */
		if ((tmp = strrchr(locale, '.')) != NULL) {
			size_t tmpLen;

			*tmp++ = '\0';

			tmpLen = strlen(tmp);
			for (size_t i = 0; i < tmpLen; i++)
				tmp[i] = of_ascii_tolower(tmp[i]);

			if (strcmp(tmp, "utf8") == 0 ||







>
>
>
>
>




>
>
>
>
>
>
>











>







<
<







51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92


93
94
95
96
97
98
99
	return [sharedLocalization encoding];
}

+ (OFString*)decimalPoint
{
	return [sharedLocalization decimalPoint];
}

+ (void)addLanguageDirectory: (OFString*)path
{
	[sharedLocalization addLanguageDirectory: path];
}

- initWithLocale: (char*)locale
{
	self = [super init];

	@try {
		_localizedStrings = [[OFMutableArray alloc] init];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	if (locale == NULL) {
		_encoding = OF_STRING_ENCODING_UTF_8;
		_decimalPoint = @".";
		return self;
	}

	locale = of_strdup(locale);

	@try {
		char *tmp;
		size_t tmpLen;

		/* We don't care for extras behind the @ */
		if ((tmp = strrchr(locale, '@')) != NULL)
			*tmp = '\0';

		/* Encoding */
		if ((tmp = strrchr(locale, '.')) != NULL) {


			*tmp++ = '\0';

			tmpLen = strlen(tmp);
			for (size_t i = 0; i < tmpLen; i++)
				tmp[i] = of_ascii_tolower(tmp[i]);

			if (strcmp(tmp, "utf8") == 0 ||
99
100
101
102
103
104
105





106
107
108

109
110




111
112
113

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128






































129
130
131
132
133
134
135
			else if (strcmp(tmp, "1252") == 0)
				_encoding = OF_STRING_ENCODING_WINDOWS_1252;
		}

		/* Territory */
		if ((tmp = strrchr(locale, '_')) != NULL) {
			*tmp++ = '\0';





			_territory = [[OFString alloc]
			    initWithCString: tmp
				   encoding: OF_STRING_ENCODING_ASCII];

		}





		_language = [[OFString alloc]
		    initWithCString: locale
			   encoding: OF_STRING_ENCODING_ASCII];


		_decimalPoint = [[OFString alloc]
		    initWithCString: localeconv()->decimal_point
			   encoding: _encoding];
	} @catch (id e) {
		[self release];
		@throw e;
	} @finally {
		free(locale);
	}

	sharedLocalization = self;

	return self;
}







































- (OFString*)localizedStringForID: (OFConstantString*)ID
			 fallback: (OFConstantString*)fallback, ...
{
	OFString *ret;
	va_list args;








>
>
>
>
>


|
>


>
>
>
>


|
>















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
			else if (strcmp(tmp, "1252") == 0)
				_encoding = OF_STRING_ENCODING_WINDOWS_1252;
		}

		/* Territory */
		if ((tmp = strrchr(locale, '_')) != NULL) {
			*tmp++ = '\0';

			tmpLen = strlen(tmp);
			for (size_t i = 0; i < tmpLen; i++)
				tmp[i] = of_ascii_tolower(tmp[i]);

			_territory = [[OFString alloc]
			    initWithCString: tmp
				   encoding: OF_STRING_ENCODING_ASCII
				     length: tmpLen];
		}

		tmpLen = strlen(tmp);
		for (size_t i = 0; i < tmpLen; i++)
			tmp[i] = of_ascii_tolower(tmp[i]);

		_language = [[OFString alloc]
		    initWithCString: locale
			   encoding: OF_STRING_ENCODING_ASCII
			     length: tmpLen];

		_decimalPoint = [[OFString alloc]
		    initWithCString: localeconv()->decimal_point
			   encoding: _encoding];
	} @catch (id e) {
		[self release];
		@throw e;
	} @finally {
		free(locale);
	}

	sharedLocalization = self;

	return self;
}

- (void)dealloc
{
	[_language release];
	[_territory release];
	[_decimalPoint release];
	[_localizedStrings release];

	[super dealloc];
}

- (void)addLanguageDirectory: (OFString*)path
{
	void *pool = objc_autoreleasePoolPush();
	OFString *mapPath =
	    [path stringByAppendingPathComponent: @"languages.json"];
	OFDictionary *map =
	    [[OFString stringWithContentsOfFile: mapPath] JSONValue];
	OFString *languageFile;

	languageFile = [[map objectForKey: _language] objectForKey: _territory];
	if (languageFile == nil)
		languageFile = [[map objectForKey: _language]
		    objectForKey: @""];

	if (languageFile == nil) {
		objc_autoreleasePoolPop(pool);
		return;
	}

	languageFile = [path stringByAppendingPathComponent:
	    [languageFile stringByAppendingString: @".json"]];

	[_localizedStrings addObject:
	    [[OFString stringWithContentsOfFile: languageFile] JSONValue]];

	objc_autoreleasePoolPop(pool);
}

- (OFString*)localizedStringForID: (OFConstantString*)ID
			 fallback: (OFConstantString*)fallback, ...
{
	OFString *ret;
	va_list args;

144
145
146
147
148
149
150
151
152
153
154
155





















156
157
158
159
160
161
162

- (OFString*)localizedStringForID: (OFConstantString*)ID
			 fallback: (OFConstantString*)fallback
			arguments: (va_list)arguments
{
	OFMutableString *ret = [OFMutableString string];
	void *pool = objc_autoreleasePoolPush();
	const char *UTF8String = [fallback UTF8String];
	size_t UTF8StringLength = [fallback UTF8StringLength];
	size_t last = 0;
	int state = 0;






















	for (size_t i = 0; i < UTF8StringLength; i++) {
		switch (state) {
		case 0:
			if (UTF8String[i] == '%') {
				[ret appendUTF8String: UTF8String + last
					       length: i - last];








|
<
|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







206
207
208
209
210
211
212
213

214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244

- (OFString*)localizedStringForID: (OFConstantString*)ID
			 fallback: (OFConstantString*)fallback
			arguments: (va_list)arguments
{
	OFMutableString *ret = [OFMutableString string];
	void *pool = objc_autoreleasePoolPush();
	const char *UTF8String = NULL;

	size_t last, UTF8StringLength;
	int state = 0;

	for (OFDictionary *strings in _localizedStrings) {
		id string = [strings objectForKey: ID];

		if (string == nil)
			continue;

		if ([string isKindOfClass: [OFArray class]])
			string = [string componentsJoinedByString: @""];

		UTF8String = [string UTF8String];
		UTF8StringLength = [string UTF8StringLength];
		break;
	}

	if (UTF8String == NULL) {
		UTF8String = [fallback UTF8String];
		UTF8StringLength = [fallback UTF8StringLength];
	}

	state = 0;
	last = 0;
	for (size_t i = 0; i < UTF8StringLength; i++) {
		switch (state) {
		case 0:
			if (UTF8String[i] == '%') {
				[ret appendUTF8String: UTF8String + last
					       length: i - last];

Modified utils/ofhttp/Makefile from [dda167b0c2] to [f803e607df].

8
9
10
11
12
13
14
15




16
17
18

include ../../buildsys.mk

PACKAGE_NAME = ofhttp

${PROG}: ${LIBOBJFW_DEP_LVL2}

CPPFLAGS += -I../../src -I../../src/runtime -I../../src/exceptions -I../..




LIBS := -L../../src -lobjfw ${LIBS}
LD = ${OBJC}
LDFLAGS += ${LDFLAGS_RPATH}







|
>
>
>
>



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

include ../../buildsys.mk

PACKAGE_NAME = ofhttp

${PROG}: ${LIBOBJFW_DEP_LVL2}

CPPFLAGS += -I../../src			\
	    -I../../src/runtime		\
	    -I../../src/exceptions	\
	    -I../..			\
	    -DDATADIR=\"${datadir}\"
LIBS := -L../../src -lobjfw ${LIBS}
LD = ${OBJC}
LDFLAGS += ${LDFLAGS_RPATH}

Modified utils/ofhttp/OFHTTP.m from [2334e6806d] to [9a10462bff].

243
244
245
246
247
248
249



250
251
252
253
254
255
256
		{ 'v', @"verbose", 0, &_verbose, NULL },
		{ '\0', nil, 0, NULL, NULL }
	};
	OFOptionsParser *optionsParser = [OFOptionsParser
	    parserWithOptions: options];
	of_unichar_t option;




	while ((option = [optionsParser nextOption]) != '\0') {
		switch (option) {
		case 'b':
			[self setBody: [optionsParser argument]];
			break;
		case 'h':
			help(of_stdout, true, 0);







>
>
>







243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
		{ 'v', @"verbose", 0, &_verbose, NULL },
		{ '\0', nil, 0, NULL, NULL }
	};
	OFOptionsParser *optionsParser = [OFOptionsParser
	    parserWithOptions: options];
	of_unichar_t option;

	[OFLocalization addLanguageDirectory: [OFString pathWithComponents:
	    [OFArray arrayWithObjects: @DATADIR, @"ofhttp", @"lang", nil]]];

	while ((option = [optionsParser nextOption]) != '\0') {
		switch (option) {
		case 'b':
			[self setBody: [optionsParser argument]];
			break;
		case 'h':
			help(of_stdout, true, 0);

Modified utils/ofhttp/lang/de.json from [28e8f2837c] to [0371c6a7bd].

1
2
3
4
5
6
7

8
9
10
11
12
13
14
15
16

17
18
19
20
21
22
23
24
{
    "usage": "Benutzung: %[prog] -[cehHmoOPqv] url1 [url2 ...]\n",
    "full_usage": [
        "\n",
        "Optionen:\n",
        "    -b  --body             Specify the file to send as body\n",
        "    -c  --continue         Continue download of existing file\n",

        "    -f  --force            Force / overwrite existing file\n",
        "    -h  --help             Show this help\n",
        "    -H  --header           Add a header (e.g. X-Foo:Bar)\n",
        "    -m  --method           Set the method of the HTTP request\n",
        "    -o  --output           Specify output file name\n",
        "    -O  --detect-filename  Do a HEAD request to detect the file name",
        "\n",
        "    -P  --proxy            Specify SOCKS5 proxy\n",
        "    -q  --quiet            Quiet mode (no output, except errors)\n",

        "    -v  --verbose          Verbose mode (print headers)\n"
    ],
    "invalid_input_header": [
        "%[prog]: Header müssen im Format Name:Wert sein!\n"
    ],
    "invalid_input_method": "%[prog]: Ungültige Request-Methode %[method]!\n",
    "invalid_input_proxy": "%[prog]: Proxy muss im Format Host:Port sein!\n",
    "long_argument_missing": "%[prog]: Argument für Option --%[opt] fehlt\n",





|
|
>
|
|
|
|
|
|
<
|
|
>
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14

15
16
17
18
19
20
21
22
23
24
25
{
    "usage": "Benutzung: %[prog] -[cehHmoOPqv] url1 [url2 ...]\n",
    "full_usage": [
        "\n",
        "Optionen:\n",
        "    -b  --body             Angegebene Datei als Body übergeben\n",
        "    -c  --continue         Download von existierender Datei ",
	"fortsetzen\n",
        "    -f  --force            Existierende Datei überschreiben\n",
        "    -h  --help             Diese Hilfe anzeigen\n",
        "    -H  --header           Einen Header (z.B. X-Foo:Bar) hinzufügen\n",
        "    -m  --method           HTTP Request-Methode setzen\n",
        "    -o  --output           Ausgabe-Dateiname angeben\n",
        "    -O  --detect-filename  Dateiname mittels HEAD-Request ermitteln\n",

        "    -P  --proxy            SOCKS5-Proxy angeben\n",
        "    -q  --quiet            Ruhiger Modus (keine Ausgabe außer Fehler)",
	"\n",
        "    -v  --verbose          Geschwätziger Modus (gibt Header aus)\n"
    ],
    "invalid_input_header": [
        "%[prog]: Header müssen im Format Name:Wert sein!\n"
    ],
    "invalid_input_method": "%[prog]: Ungültige Request-Methode %[method]!\n",
    "invalid_input_proxy": "%[prog]: Proxy muss im Format Host:Port sein!\n",
    "long_argument_missing": "%[prog]: Argument für Option --%[opt] fehlt\n",

Modified utils/ofhttp/lang/languages.json from [68d643bb62] to [59396279d5].

1
2
3
4
5
6
7
8
{
    "de": {
        "*": "de"
    },
    "german": {
        "*": "de"
    }
}


|


|


1
2
3
4
5
6
7
8
{
    "de": {
        "": "de"
    },
    "german": {
        "": "de"
    }
}