blob: d56d42d7f4555df69c9fdfceb4fa08ed1ddbcf18 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include <errno.h>
#include <locale.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "log.h"
#include "messages.h"
#include "file.h"
// MinGW doesn't define LC_MESSAGES
#ifndef LC_MESSAGES
#define LC_MESSAGES LC_ALL
#endif /* LC_MESSAGES */
// Windows needs O_BINARY
#ifndef O_BINARY
#define O_BINARY 0
#endif /* O_BINARY */
static char *localeDirectory = NULL;
static char *localeSpecifier = NULL;
static char *domainName = NULL;
const char *
getMessagesDirectory (void) {
return localeDirectory;
}
const char *
getMessagesLocale (void) {
return localeSpecifier;
}
const char *
getMessagesDomain (void) {
return domainName;
}
static const uint32_t magicNumber = UINT32_C(0X950412DE);
typedef uint32_t GetIntegerFunction (uint32_t value);
typedef struct {
uint32_t magicNumber;
uint32_t versionNumber;
uint32_t messageCount;
uint32_t sourceMessages;
uint32_t translatedMessages;
uint32_t hashSize;
uint32_t hashOffset;
} MessageCatalogHeader;
typedef struct {
union {
void *data;
const unsigned char *bytes;
const MessageCatalogHeader *header;
} view;
size_t dataSize;
GetIntegerFunction *getInteger;
} MessageCatalog;
static MessageCatalog messageCatalog = {
.view.data = NULL,
.dataSize = 0,
.getInteger = NULL,
};
static uint32_t
getNativeInteger (uint32_t value) {
return value;
}
static uint32_t
getFlippedInteger (uint32_t value) {
uint32_t result = 0;
while (value) {
result <<= 8;
result |= value & UINT8_MAX;
value >>= 8;
}
return result;
}
static int
checkMagicNumber (MessageCatalog *catalog) {
const MessageCatalogHeader *header = catalog->view.header;
{
static GetIntegerFunction *const functions[] = {
getNativeInteger,
getFlippedInteger,
NULL
};
GetIntegerFunction *const *function = functions;
while (*function) {
if ((*function)(header->magicNumber) == magicNumber) {
catalog->getInteger = *function;
return 1;
}
function += 1;
}
}
return 0;
}
static char *
makeLocaleDirectoryPath (void) {
size_t length = strlen(localeSpecifier);
char dialect[length + 1];
strcpy(dialect, localeSpecifier);
length = strcspn(dialect, ".@");
dialect[length] = 0;
char language[length + 1];
strcpy(language, dialect);
length = strcspn(language, "_");
language[length] = 0;
char *codes[] = {dialect, language, NULL};
char **code = codes;
while (*code && **code) {
char *path = makePath(localeDirectory, *code);
if (path) {
if (testDirectoryPath(path)) return path;
free(path);
}
code += 1;
}
logMessage(LOG_DEBUG, "messages locale not found: %s", localeSpecifier);
return NULL;
}
static char *
makeCatalogFilePath (void) {
char *locale = makeLocaleDirectoryPath();
if (locale) {
char *category = makePath(locale, "LC_MESSAGES");
free(locale);
locale = NULL;
if (category) {
char *catalog = makeFilePath(category, domainName, ".mo");
free(category);
category = NULL;
if (catalog) return catalog;
}
}
return NULL;
}
static int
setMessageCatalog (void *data, size_t size) {
MessageCatalog catalog = {
.view.data = data,
.dataSize = size
};
if (checkMagicNumber(&catalog)) {
messageCatalog = catalog;
return 1;
}
return 0;
}
static int
setEmptyMessageCatalog (void) {
MessageCatalogHeader *header;
size_t size = sizeof(*header);
if ((header = malloc(size))) {
memset(header, 0, size);
header->magicNumber = magicNumber;
header->sourceMessages = size;
header->translatedMessages = header->sourceMessages;
if (setMessageCatalog(header, size)) return 1;
free(header);
} else {
logMallocError();
}
return 0;
}
int
loadMessageCatalog (void) {
if (messageCatalog.view.data) return 1;
ensureAllMessagesProperties();
int loaded = 0;
char *path = makeCatalogFilePath();
if (path) {
int fd = open(path, (O_RDONLY | O_BINARY));
if (fd != -1) {
struct stat info;
if (fstat(fd, &info) != -1) {
size_t size = info.st_size;
void *data = NULL;
if (size) {
if ((data = malloc(size))) {
ssize_t count = read(fd, data, size);
if (count == -1) {
logMessage(LOG_WARNING,
"message catalog read error: %s: %s",
path, strerror(errno)
);
} else if (count < size) {
logMessage(LOG_WARNING,
"truncated message catalog: %"PRIssize" < %"PRIsize": %s",
count, size, path
);
} else if (setMessageCatalog(data, size)) {
data = NULL;
loaded = 1;
}
if (!loaded) free(data);
} else {
logMallocError();
}
} else {
logMessage(LOG_WARNING, "empty message catalog");
}
} else {
logMessage(LOG_WARNING,
"message catalog stat error: %s: %s",
path, strerror(errno)
);
}
close(fd);
} else {
logMessage(LOG_WARNING,
"message catalog open error: %s: %s",
path, strerror(errno)
);
}
free(path);
}
if (!loaded) {
if (setEmptyMessageCatalog()) {
loaded = 1;
logMessage(LOG_DEBUG, "no message translations");
}
}
return loaded;
}
void
releaseMessageCatalog (void) {
if (messageCatalog.view.data) free(messageCatalog.view.data);
memset(&messageCatalog, 0, sizeof(messageCatalog));
}
static inline const MessageCatalogHeader *
getHeader (void) {
return messageCatalog.view.header;
}
static inline const void *
getItem (uint32_t offset) {
return &messageCatalog.view.bytes[messageCatalog.getInteger(offset)];
}
uint32_t
getMessageCount (void) {
return messageCatalog.getInteger(getHeader()->messageCount);
}
struct MessageStruct {
uint32_t length;
uint32_t offset;
};
uint32_t
getMessageLength (const Message *message) {
return messageCatalog.getInteger(message->length);
}
const char *
getMessageText (const Message *message) {
return getItem(message->offset);
}
static inline const Message *
getSourceMessages (void) {
return getItem(getHeader()->sourceMessages);
}
static inline const Message *
getTranslatedMessages (void) {
return getItem(getHeader()->translatedMessages);
}
const Message *
getSourceMessage (unsigned int index) {
return &getSourceMessages()[index];
}
const Message *
getTranslatedMessage (unsigned int index) {
return &getTranslatedMessages()[index];
}
const char *
getMessagesMetadata (void) {
if (getMessageCount() == 0) return "";
{
const Message *message = getSourceMessage(0);
if (getMessageLength(message) != 0) return "";
}
return getMessageText(getTranslatedMessage(0));
}
char *
getMessagesProperty (const char *name) {
size_t nameLength = strlen(name);
const char *metadata = getMessagesMetadata();
while (metadata) {
const char *line = metadata;
size_t lineLength = strcspn(line, "\n\x00");
const char *end = line + lineLength;
metadata = *end? (line + lineLength + 1): NULL;
if (nameLength < lineLength) {
if (memcmp(line, name, nameLength) == 0) {
if (line[nameLength] == ':') {
const char *value = line + nameLength + 1;
while (iswspace(*value)) value += 1;
size_t valueLength = end - value;
char *copy = malloc(valueLength + 1);
if (copy) {
memcpy(copy, value, valueLength);
copy[valueLength] = 0;
} else {
logMallocError();
}
return copy;
}
}
}
}
return NULL;
}
char *
getMessagesAttribute (const char *property, const char *name) {
size_t nameLength = strlen(name);
const char *byte = property;
while (*byte) {
while (iswspace(*byte)) byte += 1;
const char *nameStart = byte;
while (iswalpha(*byte)) byte += 1;
const char *nameEnd = byte;
while (iswspace(*byte)) byte += 1;
if (!*byte) break;
if (*byte != '=') continue;
byte += 1;
while (iswspace(*byte)) byte += 1;
const char *valueStart = byte;
while (*byte && (*byte != ';')) byte += 1;
const char *valueEnd = byte;
if (*byte) byte += 1;
if ((nameEnd - nameStart) == nameLength) {
if (memcmp(name, nameStart, nameLength) == 0) {
size_t length = valueEnd - valueStart;
char *value = malloc(length + 1);
if (value) {
memcpy(value, valueStart, length);
value[length] = 0;
} else {
logMallocError();
}
return value;
}
}
}
return NULL;
}
int
findSourceMessage (const char *text, size_t textLength, unsigned int *index) {
const Message *messages = getSourceMessages();
int from = 0;
int to = getMessageCount();
while (from < to) {
int current = (from + to) / 2;
const Message *message = &messages[current];
uint32_t messageLength = getMessageLength(message);
int relation = memcmp(text, getMessageText(message), MIN(textLength, messageLength));
if (relation == 0) {
if (textLength == messageLength) {
*index = current;
return 1;
}
relation = (textLength < messageLength)? -1: 1;
}
if (relation < 0) {
to = current;
} else {
from = current + 1;
}
}
return 0;
}
const Message *
findSimpleTranslation (const char *text, size_t length) {
if (!text) return NULL;
if (!length) return NULL;
if (loadMessageCatalog()) {
unsigned int index;
if (findSourceMessage(text, length, &index)) {
return getTranslatedMessage(index);
}
}
return NULL;
}
const char *
getSimpleTranslation (const char *text) {
const Message *translation = findSimpleTranslation(text, strlen(text));
if (translation) return getMessageText(translation);
return text;
}
const Message *
findPluralTranslation (const char *const *strings) {
unsigned int count = 0;
while (strings[count]) count += 1;
if (!count) return NULL;
size_t size = 0;
size_t lengths[count];
for (unsigned int index=0; index<count; index+=1) {
size_t length = strlen(strings[index]);
lengths[index] = length;
size += length + 1;
}
char text[size];
char *byte = text;
for (unsigned int index=0; index<count; index+=1) {
byte = mempcpy(byte, strings[index], (lengths[index] + 1));
}
byte -= 1; // the length mustn't include the final NUL
return findSimpleTranslation(text, (byte - text));
}
const char *
getPluralTranslation (const char *singular, const char *plural, unsigned long int count) {
int useSingular = count == 1;
const char *const strings[] = {singular, plural, NULL};
const Message *message = findPluralTranslation(strings);
if (!message) return useSingular? singular: plural;
const char *translation = getMessageText(message);
if (!useSingular) translation += strlen(translation) + 1;
return translation;
}
#ifdef ENABLE_I18N_SUPPORT
static int
setDirectory (const char *directory) {
if (bindtextdomain(domainName, directory)) return 1;
logSystemError("bindtextdomain");
return 0;
}
static int
setDomain (const char *domain) {
if (!textdomain(domain)) {
logSystemError("textdomain");
return 0;
}
if (!bind_textdomain_codeset(domain, "UTF-8")) {
logSystemError("bind_textdomain_codeset");
}
return 1;
}
#else /* ENABLE_I18N_SUPPORT */
static int
setDirectory (const char *directory) {
return 1;
}
static int
setDomain (const char *domain) {
return 1;
}
char *
gettext (const char *text) {
return (char *)getSimpleTranslation(text);
}
char *
ngettext (const char *singular, const char *plural, unsigned long int count) {
return (char *)getPluralTranslation(singular, plural, count);
}
#endif /* ENABLE_I18N_SUPPORT */
static int
updateProperty (
char **property, const char *value, const char *defaultValue,
int (*setter) (const char *value)
) {
releaseMessageCatalog();
if (!(value && *value)) value = defaultValue;
char *copy = strdup(value);
if (copy) {
if (!setter || setter(value)) {
if (*property) free(*property);
*property = copy;
return 1;
}
free(copy);
} else {
logMallocError();
}
return 0;
}
int
setMessagesDirectory (const char *directory) {
return updateProperty(&localeDirectory, directory, LOCALE_DIRECTORY, setDirectory);
}
int
setMessagesLocale (const char *specifier) {
return updateProperty(&localeSpecifier, specifier, "C.UTF-8", NULL);
}
int
setMessagesDomain (const char *name) {
return updateProperty(&domainName, name, PACKAGE_TARNAME, setDomain);
}
void
ensureAllMessagesProperties (void) {
if (!localeSpecifier) {
setMessagesLocale(setlocale(LC_MESSAGES, ""));
}
if (!domainName) setMessagesDomain(NULL);
if (!localeDirectory) setMessagesDirectory(NULL);
}