Index: src/OFLocalization.h ================================================================== --- src/OFLocalization.h +++ src/OFLocalization.h @@ -24,10 +24,13 @@ #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. */ @@ -35,10 +38,12 @@ { 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. * @@ -115,10 +120,17 @@ * * @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 @@ -127,10 +139,17 @@ * * @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 @@ -147,10 +166,11 @@ * 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 Index: src/OFLocalization.m ================================================================== --- src/OFLocalization.m +++ src/OFLocalization.m @@ -18,10 +18,12 @@ #include #import "OFLocalization.h" #import "OFString.h" +#import "OFArray.h" +#import "OFDictionary.h" #import "OFInvalidArgumentException.h" static OFLocalization *sharedLocalization = nil; @@ -51,14 +53,26 @@ + (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; @@ -66,19 +80,18 @@ 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) { - size_t tmpLen; - *tmp++ = '\0'; tmpLen = strlen(tmp); for (size_t i = 0; i < tmpLen; i++) tmp[i] = of_ascii_tolower(tmp[i]); @@ -101,18 +114,29 @@ } /* 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]; + 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]; + encoding: OF_STRING_ENCODING_ASCII + length: tmpLen]; _decimalPoint = [[OFString alloc] initWithCString: localeconv()->decimal_point encoding: _encoding]; } @catch (id e) { @@ -124,10 +148,48 @@ 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; @@ -146,15 +208,35 @@ 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; + 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 Index: utils/ofhttp/Makefile ================================================================== --- utils/ofhttp/Makefile +++ utils/ofhttp/Makefile @@ -10,9 +10,13 @@ PACKAGE_NAME = ofhttp ${PROG}: ${LIBOBJFW_DEP_LVL2} -CPPFLAGS += -I../../src -I../../src/runtime -I../../src/exceptions -I../.. +CPPFLAGS += -I../../src \ + -I../../src/runtime \ + -I../../src/exceptions \ + -I../.. \ + -DDATADIR=\"${datadir}\" LIBS := -L../../src -lobjfw ${LIBS} LD = ${OBJC} LDFLAGS += ${LDFLAGS_RPATH} Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -245,10 +245,13 @@ }; 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; Index: utils/ofhttp/lang/de.json ================================================================== --- utils/ofhttp/lang/de.json +++ utils/ofhttp/lang/de.json @@ -1,22 +1,23 @@ { "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" + " -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", Index: utils/ofhttp/lang/languages.json ================================================================== --- utils/ofhttp/lang/languages.json +++ utils/ofhttp/lang/languages.json @@ -1,8 +1,8 @@ { "de": { - "*": "de" + "": "de" }, "german": { - "*": "de" + "": "de" } }