Added proper plural support
This commit is contained in:
@@ -14,7 +14,7 @@ module('locale')
|
|||||||
screenSetBackground(colorCornflowerBlue())
|
screenSetBackground(colorCornflowerBlue())
|
||||||
camera = cameraCreate(CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC)
|
camera = cameraCreate(CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC)
|
||||||
|
|
||||||
text = localeGetText('cart.item_count', 2, 2)
|
text = localeGetText('cart.item_count', 52, 2)
|
||||||
|
|
||||||
function sceneDispose()
|
function sceneDispose()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,15 +11,293 @@
|
|||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
|
||||||
errorret_t assetLocaleLoader(assetfile_t *file) {
|
errorret_t assetLocaleFileInit(
|
||||||
errorThrow("Locale asset loading is not yet implemented.");
|
assetlocalefile_t *localeFile,
|
||||||
|
const char_t *path
|
||||||
|
) {
|
||||||
|
assertNotNull(localeFile, "Locale file cannot be NULL.");
|
||||||
|
assertNotNull(path, "Locale file path cannot be NULL.");
|
||||||
|
|
||||||
|
memoryZero(localeFile, sizeof(assetlocalefile_t));
|
||||||
|
|
||||||
|
// Init the asset file.
|
||||||
|
errorChain(assetFileInit(&localeFile->file, path, NULL, NULL));
|
||||||
|
|
||||||
|
// Open the file handle
|
||||||
|
errorChain(assetFileOpen(&localeFile->file));
|
||||||
|
|
||||||
|
// Get the blank key, this is basically the header info for po files
|
||||||
|
char_t buffer[1024];
|
||||||
|
errorChain(assetLocaleGetString(localeFile, "", 0, buffer, sizeof(buffer)));
|
||||||
|
errorChain(assetLocaleParseHeader(localeFile, buffer, sizeof(buffer)));
|
||||||
|
|
||||||
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t assetLocaleLoad(
|
errorret_t assetLocaleFileDispose(assetlocalefile_t *localeFile) {
|
||||||
const char_t *path,
|
assertNotNull(localeFile, "Locale file cannot be NULL.");
|
||||||
void *nothing
|
|
||||||
|
errorChain(assetFileClose(&localeFile->file));
|
||||||
|
errorChain(assetFileDispose(&localeFile->file));
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetLocaleParseHeader(
|
||||||
|
assetlocalefile_t *localeFile,
|
||||||
|
char_t *headerBuffer,
|
||||||
|
const size_t headerBufferSize
|
||||||
) {
|
) {
|
||||||
errorThrow("Locale asset loading is not yet implemented.");
|
assertNotNull(localeFile, "Locale file cannot be NULL.");
|
||||||
|
assertNotNull(headerBuffer, "Header buffer cannot be NULL.");
|
||||||
|
assertTrue(headerBufferSize > 0, "Header buffer size must be > 0.");
|
||||||
|
|
||||||
|
// Find "Plural-Forms: " line and parse out plural form info
|
||||||
|
char_t *pluralFormsLine = strstr(headerBuffer, "Plural-Forms:");
|
||||||
|
if(!pluralFormsLine) {
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
pluralFormsLine += strlen("Plural-Forms:");
|
||||||
|
|
||||||
|
// Expect nplurals
|
||||||
|
char_t *npluralsStr = strstr(pluralFormsLine, "nplurals=");
|
||||||
|
if(!npluralsStr) {
|
||||||
|
errorThrow("Failed to find nplurals in Plural-Forms header.");
|
||||||
|
}
|
||||||
|
npluralsStr += strlen("nplurals=");
|
||||||
|
localeFile->pluralStateCount = (uint8_t)atoi(npluralsStr);
|
||||||
|
|
||||||
|
if(localeFile->pluralStateCount == 0) {
|
||||||
|
errorThrow("nplurals must be greater than 0.");
|
||||||
|
}
|
||||||
|
if(localeFile->pluralStateCount > ASSET_LOCALE_FILE_PLURAL_FORM_COUNT) {
|
||||||
|
errorThrow(
|
||||||
|
"nplurals exceeds maximum supported plural forms: %d > %d",
|
||||||
|
localeFile->pluralStateCount,
|
||||||
|
ASSET_LOCALE_FILE_PLURAL_FORM_COUNT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect plural=
|
||||||
|
char_t *pluralStr = strstr(pluralFormsLine, "plural=");
|
||||||
|
if(!pluralStr) {
|
||||||
|
errorThrow("Failed to find plural in Plural-Forms header.");
|
||||||
|
}
|
||||||
|
pluralStr += strlen("plural=");
|
||||||
|
|
||||||
|
// Expect ( [expressions] )
|
||||||
|
char_t *openParen = strchr(pluralStr, '(');
|
||||||
|
char_t *closeParen = strrchr(pluralStr, ')');
|
||||||
|
if(!openParen || !closeParen || closeParen < openParen) {
|
||||||
|
errorThrow("Failed to find plural expression in Plural-Forms header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse:
|
||||||
|
// n [op] value ? index : n [op] value ? index : ... : final_index
|
||||||
|
char_t *ptr = openParen + 1;
|
||||||
|
uint8_t pluralIndex = 0;
|
||||||
|
uint8_t definedCount = 0;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
|
||||||
|
// Allow grouped subexpressions like:
|
||||||
|
// (n<7 ? 2 : 3)
|
||||||
|
// or
|
||||||
|
// (((3)))
|
||||||
|
uint8_t parenDepth = 0;
|
||||||
|
while(*ptr == '(') {
|
||||||
|
parenDepth++;
|
||||||
|
ptr++;
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final fallback: just an integer
|
||||||
|
if(*ptr != 'n') {
|
||||||
|
char_t *endPtr = NULL;
|
||||||
|
int32_t finalIndex = (int32_t)strtol(ptr, &endPtr, 10);
|
||||||
|
if(endPtr == ptr) {
|
||||||
|
errorThrow("Expected final plural index.");
|
||||||
|
}
|
||||||
|
ptr = endPtr;
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
while(parenDepth > 0) {
|
||||||
|
if(*ptr != ')') {
|
||||||
|
errorThrow("Expected ')' after final plural index.");
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
parenDepth--;
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(*ptr != ')') {
|
||||||
|
errorThrow("Expected ')' at end of plural expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(finalIndex < 0 || finalIndex >= localeFile->pluralStateCount) {
|
||||||
|
errorThrow(
|
||||||
|
"Final plural expression index out of bounds: %d (nplurals: %d)",
|
||||||
|
finalIndex,
|
||||||
|
localeFile->pluralStateCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
localeFile->pluralDefaultIndex = (uint8_t)finalIndex;
|
||||||
|
definedCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pluralIndex >= localeFile->pluralStateCount - 1) {
|
||||||
|
errorThrow(
|
||||||
|
"Too many plural conditions. Expected %d conditional clauses for nplurals=%d.",
|
||||||
|
localeFile->pluralStateCount - 1,
|
||||||
|
localeFile->pluralStateCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr++; // skip 'n'
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
|
||||||
|
// Determine operator
|
||||||
|
assetlocalepluraloperation_t op;
|
||||||
|
if(strncmp(ptr, "==", 2) == 0) {
|
||||||
|
op = ASSET_LOCALE_PLURAL_OP_EQUAL;
|
||||||
|
ptr += 2;
|
||||||
|
} else if(strncmp(ptr, "!=", 2) == 0) {
|
||||||
|
op = ASSET_LOCALE_PLURAL_OP_NOT_EQUAL;
|
||||||
|
ptr += 2;
|
||||||
|
} else if(strncmp(ptr, "<=", 2) == 0) {
|
||||||
|
op = ASSET_LOCALE_PLURAL_OP_LESS_EQUAL;
|
||||||
|
ptr += 2;
|
||||||
|
} else if(strncmp(ptr, ">=", 2) == 0) {
|
||||||
|
op = ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL;
|
||||||
|
ptr += 2;
|
||||||
|
} else if(*ptr == '<') {
|
||||||
|
op = ASSET_LOCALE_PLURAL_OP_LESS;
|
||||||
|
ptr++;
|
||||||
|
} else if(*ptr == '>') {
|
||||||
|
op = ASSET_LOCALE_PLURAL_OP_GREATER;
|
||||||
|
ptr++;
|
||||||
|
} else {
|
||||||
|
errorThrow("Unsupported plural operator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
|
||||||
|
// Parse the comparitor value
|
||||||
|
char_t *endPtr = NULL;
|
||||||
|
int32_t value = (int32_t)strtol(ptr, &endPtr, 10);
|
||||||
|
if(endPtr == ptr) {
|
||||||
|
errorThrow("Expected value for plural expression.");
|
||||||
|
}
|
||||||
|
ptr = endPtr;
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
|
||||||
|
// Parse ternary operator
|
||||||
|
if(*ptr != '?') {
|
||||||
|
errorThrow("Expected '?' after plural expression.");
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
|
||||||
|
// Parse the indice
|
||||||
|
endPtr = NULL;
|
||||||
|
int32_t index = (int32_t)strtol(ptr, &endPtr, 10);
|
||||||
|
if(endPtr == ptr) {
|
||||||
|
errorThrow("Expected index for plural expression.");
|
||||||
|
}
|
||||||
|
ptr = endPtr;
|
||||||
|
|
||||||
|
if(index < 0 || index >= localeFile->pluralStateCount) {
|
||||||
|
errorThrow(
|
||||||
|
"Plural expression index out of bounds: %d (nplurals: %d)",
|
||||||
|
index,
|
||||||
|
localeFile->pluralStateCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store plural expression.
|
||||||
|
localeFile->pluralIndices[pluralIndex] = (uint8_t)index;
|
||||||
|
localeFile->pluralOps[pluralIndex] = op;
|
||||||
|
localeFile->pluralValues[pluralIndex] = value;
|
||||||
|
pluralIndex++;
|
||||||
|
definedCount++;
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
|
||||||
|
// Close any grouping parens that wrapped this conditional branch
|
||||||
|
while(parenDepth > 0) {
|
||||||
|
if(*ptr != ')') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
parenDepth--;
|
||||||
|
|
||||||
|
while(*ptr == ' ') ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(*ptr != ':') {
|
||||||
|
errorThrow("Expected ':' after plural expression.");
|
||||||
|
}
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must define exactly nplurals outcomes:
|
||||||
|
// (nplurals - 1) conditional results + 1 final fallback result
|
||||||
|
if(
|
||||||
|
pluralIndex != localeFile->pluralStateCount - 1 ||
|
||||||
|
definedCount != localeFile->pluralStateCount
|
||||||
|
) {
|
||||||
|
errorThrow("Plural expression count does not match nplurals.");
|
||||||
|
}
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t assetLocaleEvaluatePlural(
|
||||||
|
assetlocalefile_t *file,
|
||||||
|
const int32_t pluralCount
|
||||||
|
) {
|
||||||
|
assertNotNull(file, "Locale file cannot be NULL.");
|
||||||
|
assertTrue(pluralCount >= 0, "Plural count cannot be negative.");
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < file->pluralStateCount - 1; i++) {
|
||||||
|
int32_t value = file->pluralValues[i];
|
||||||
|
switch(file->pluralOps[i]) {
|
||||||
|
case ASSET_LOCALE_PLURAL_OP_EQUAL:
|
||||||
|
if(pluralCount == value) return file->pluralIndices[i];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASSET_LOCALE_PLURAL_OP_NOT_EQUAL:
|
||||||
|
if(pluralCount != value) return file->pluralIndices[i];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASSET_LOCALE_PLURAL_OP_LESS:
|
||||||
|
if(pluralCount < value) return file->pluralIndices[i];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASSET_LOCALE_PLURAL_OP_LESS_EQUAL:
|
||||||
|
if(pluralCount <= value) return file->pluralIndices[i];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASSET_LOCALE_PLURAL_OP_GREATER:
|
||||||
|
if(pluralCount > value) return file->pluralIndices[i];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL:
|
||||||
|
if(pluralCount >= value) return file->pluralIndices[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file->pluralDefaultIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t assetLocaleLineSkipBlanks(
|
errorret_t assetLocaleLineSkipBlanks(
|
||||||
@@ -140,15 +418,15 @@ errorret_t assetLocaleLineUnbuffer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
errorret_t assetLocaleGetString(
|
errorret_t assetLocaleGetString(
|
||||||
assetfile_t *file,
|
assetlocalefile_t *file,
|
||||||
const char_t *messageId,
|
const char_t *messageId,
|
||||||
const int32_t pluralIndex,
|
const int32_t pluralCount,
|
||||||
char_t *stringBuffer,
|
char_t *stringBuffer,
|
||||||
const size_t stringBufferSize
|
const size_t stringBufferSize
|
||||||
) {
|
) {
|
||||||
assertNotNull(file, "Asset file cannot be NULL.");
|
assertNotNull(file, "Asset file cannot be NULL.");
|
||||||
assertNotNull(messageId, "Message ID cannot be NULL.");
|
assertNotNull(messageId, "Message ID cannot be NULL.");
|
||||||
assertTrue(pluralIndex >= 0, "Plural index cannot be negative.");
|
assertTrue(pluralCount >= 0, "Plural index cannot be negative.");
|
||||||
assertNotNull(stringBuffer, "String buffer cannot be NULL.");
|
assertNotNull(stringBuffer, "String buffer cannot be NULL.");
|
||||||
assertTrue(stringBufferSize > 0, "String buffer size must be > 0");
|
assertTrue(stringBufferSize > 0, "String buffer size must be > 0");
|
||||||
assetfilelinereader_t reader;
|
assetfilelinereader_t reader;
|
||||||
@@ -158,16 +436,17 @@ errorret_t assetLocaleGetString(
|
|||||||
uint8_t msgidPluralBuffer[256];
|
uint8_t msgidPluralBuffer[256];
|
||||||
uint8_t readBuffer[1024];
|
uint8_t readBuffer[1024];
|
||||||
uint8_t lineBuffer[1024];
|
uint8_t lineBuffer[1024];
|
||||||
|
uint8_t pluralIndex = 0xFF;
|
||||||
|
|
||||||
msgidBuffer[0] = '\0';
|
msgidBuffer[0] = '\0';
|
||||||
msgidPluralBuffer[0] = '\0';
|
msgidPluralBuffer[0] = '\0';
|
||||||
stringBuffer[0] = '\0';
|
stringBuffer[0] = '\0';
|
||||||
|
|
||||||
// Rewind and start reading lines.
|
// Rewind and start reading lines.
|
||||||
errorChain(assetFileRewind(&LOCALE.file));
|
errorChain(assetFileRewind(&file->file));
|
||||||
assetFileLineReaderInit(
|
assetFileLineReaderInit(
|
||||||
&reader,
|
&reader,
|
||||||
&LOCALE.file,
|
&file->file,
|
||||||
readBuffer,
|
readBuffer,
|
||||||
sizeof(readBuffer),
|
sizeof(readBuffer),
|
||||||
lineBuffer,
|
lineBuffer,
|
||||||
@@ -219,6 +498,13 @@ errorret_t assetLocaleGetString(
|
|||||||
sizeof(msgidPluralBuffer)
|
sizeof(msgidPluralBuffer)
|
||||||
);
|
);
|
||||||
msgidPluralFound = true;
|
msgidPluralFound = true;
|
||||||
|
|
||||||
|
// At this point we determine the plural index to use by running the
|
||||||
|
// plural formula
|
||||||
|
pluralIndex = assetLocaleEvaluatePlural(
|
||||||
|
file,
|
||||||
|
pluralCount
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,3 +569,239 @@ errorret_t assetLocaleGetString(
|
|||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorret_t assetLocaleGetStringWithVA(
|
||||||
|
assetlocalefile_t *file,
|
||||||
|
const char_t *messageId,
|
||||||
|
const int32_t pluralIndex,
|
||||||
|
char_t *buffer,
|
||||||
|
const size_t bufferSize,
|
||||||
|
...
|
||||||
|
) {
|
||||||
|
assertNotNull(file, "Asset file cannot be NULL.");
|
||||||
|
assertNotNull(messageId, "Message ID cannot be NULL.");
|
||||||
|
assertNotNull(buffer, "Buffer cannot be NULL.");
|
||||||
|
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
||||||
|
assertTrue(pluralIndex >= 0, "Plural cannot be negative.");
|
||||||
|
|
||||||
|
char_t *tempBuffer;
|
||||||
|
tempBuffer = memoryAllocate(bufferSize);
|
||||||
|
errorret_t ret = assetLocaleGetString(
|
||||||
|
file,
|
||||||
|
messageId,
|
||||||
|
pluralIndex,
|
||||||
|
tempBuffer,
|
||||||
|
bufferSize
|
||||||
|
);
|
||||||
|
if(ret.code != ERROR_OK) {
|
||||||
|
memoryFree(tempBuffer);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, bufferSize);
|
||||||
|
int result = vsnprintf(buffer, bufferSize, tempBuffer, args);
|
||||||
|
va_end(args);
|
||||||
|
memoryFree(tempBuffer);
|
||||||
|
|
||||||
|
if(result < 0) {
|
||||||
|
errorThrow("Failed to format locale string for ID: %s", messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetLocaleGetStringWithArgs(
|
||||||
|
assetlocalefile_t *file,
|
||||||
|
const char_t *id,
|
||||||
|
const int32_t plural,
|
||||||
|
char_t *buffer,
|
||||||
|
const size_t bufferSize,
|
||||||
|
const assetlocalearg_t *args,
|
||||||
|
const size_t argCount
|
||||||
|
) {
|
||||||
|
assertNotNull(id, "Message ID cannot be NULL.");
|
||||||
|
assertNotNull(buffer, "Buffer cannot be NULL.");
|
||||||
|
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
||||||
|
assertTrue(plural >= 0, "Plural cannot be negative.");
|
||||||
|
assertTrue(
|
||||||
|
argCount == 0 || args != NULL,
|
||||||
|
"Args cannot be NULL when argCount > 0."
|
||||||
|
);
|
||||||
|
|
||||||
|
char_t *format = memoryAllocate(bufferSize);
|
||||||
|
if(format == NULL) {
|
||||||
|
errorThrow("Failed to allocate format buffer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t ret = assetLocaleGetString(
|
||||||
|
file,
|
||||||
|
id,
|
||||||
|
plural,
|
||||||
|
format,
|
||||||
|
bufferSize
|
||||||
|
);
|
||||||
|
if(ret.code != ERROR_OK) {
|
||||||
|
memoryFree(format);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t inIndex = 0;
|
||||||
|
size_t outIndex = 0;
|
||||||
|
size_t nextArg = 0;
|
||||||
|
|
||||||
|
buffer[0] = '\0';
|
||||||
|
|
||||||
|
while(format[inIndex] != '\0') {
|
||||||
|
if(format[inIndex] != '%') {
|
||||||
|
if(outIndex + 1 >= bufferSize) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[outIndex++] = format[inIndex++];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inIndex++;
|
||||||
|
|
||||||
|
/* Escaped percent */
|
||||||
|
if(format[inIndex] == '%') {
|
||||||
|
if(outIndex + 1 >= bufferSize) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[outIndex++] = '%';
|
||||||
|
inIndex++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nextArg >= argCount) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Not enough locale arguments for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
char_t specBuffer[32];
|
||||||
|
char_t valueBuffer[256];
|
||||||
|
size_t specLength = 0;
|
||||||
|
int written = 0;
|
||||||
|
char_t specifier;
|
||||||
|
|
||||||
|
specBuffer[specLength++] = '%';
|
||||||
|
|
||||||
|
/* Allow flags / width / precision */
|
||||||
|
while(format[inIndex] != '\0') {
|
||||||
|
char_t ch = format[inIndex];
|
||||||
|
|
||||||
|
if(
|
||||||
|
ch == '-' || ch == '+' || ch == ' ' || ch == '#' || ch == '0' ||
|
||||||
|
ch == '.' || (ch >= '0' && ch <= '9')
|
||||||
|
) {
|
||||||
|
if(specLength + 1 >= sizeof(specBuffer)) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Format specifier too long for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
specBuffer[specLength++] = ch;
|
||||||
|
inIndex++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(format[inIndex] == '\0') {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Incomplete format specifier for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
specifier = format[inIndex++];
|
||||||
|
|
||||||
|
if(specifier != 's' && specifier != 'd' && specifier != 'f') {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow(
|
||||||
|
"Unsupported format specifier '%%%c' for ID: %s",
|
||||||
|
specifier,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
specBuffer[specLength++] = specifier;
|
||||||
|
specBuffer[specLength] = '\0';
|
||||||
|
|
||||||
|
switch(specifier) {
|
||||||
|
case 's':
|
||||||
|
if(args[nextArg].type != ASSET_LOCALE_ARG_STRING) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Expected string locale argument for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
written = snprintf(
|
||||||
|
valueBuffer,
|
||||||
|
sizeof(valueBuffer),
|
||||||
|
specBuffer,
|
||||||
|
args[nextArg].stringValue ? args[nextArg].stringValue : ""
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
if(args[nextArg].type != ASSET_LOCALE_ARG_INT) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Expected int locale argument for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
written = snprintf(
|
||||||
|
valueBuffer,
|
||||||
|
sizeof(valueBuffer),
|
||||||
|
specBuffer,
|
||||||
|
args[nextArg].intValue
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
if(
|
||||||
|
args[nextArg].type != ASSET_LOCALE_ARG_FLOAT &&
|
||||||
|
args[nextArg].type != ASSET_LOCALE_ARG_INT
|
||||||
|
) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Expected float or int locale argument for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
float_t floatValue = (
|
||||||
|
args[nextArg].type == ASSET_LOCALE_ARG_FLOAT ?
|
||||||
|
args[nextArg].floatValue :
|
||||||
|
(float_t)args[nextArg].intValue
|
||||||
|
);
|
||||||
|
|
||||||
|
written = snprintf(
|
||||||
|
valueBuffer,
|
||||||
|
sizeof(valueBuffer),
|
||||||
|
specBuffer,
|
||||||
|
floatValue
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextArg++;
|
||||||
|
|
||||||
|
if(written < 0) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Failed to format locale string for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outIndex + (size_t)written >= bufferSize) {
|
||||||
|
memoryFree(format);
|
||||||
|
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryCopy(buffer + outIndex, valueBuffer, (size_t)written);
|
||||||
|
outIndex += (size_t)written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[outIndex] = '\0';
|
||||||
|
memoryFree(format);
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
@@ -7,26 +7,66 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "locale/localemanager.h"
|
|
||||||
|
#define ASSET_LOCALE_FILE_PLURAL_FORM_COUNT 6
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ASSET_LOCALE_PLURAL_OP_EQUAL,
|
||||||
|
ASSET_LOCALE_PLURAL_OP_NOT_EQUAL,
|
||||||
|
ASSET_LOCALE_PLURAL_OP_LESS,
|
||||||
|
ASSET_LOCALE_PLURAL_OP_LESS_EQUAL,
|
||||||
|
ASSET_LOCALE_PLURAL_OP_GREATER,
|
||||||
|
ASSET_LOCALE_PLURAL_OP_GREATER_EQUAL
|
||||||
|
} assetlocalepluraloperation_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ASSET_LOCALE_ARG_STRING,
|
||||||
|
ASSET_LOCALE_ARG_INT,
|
||||||
|
ASSET_LOCALE_ARG_FLOAT
|
||||||
|
} assetlocaleargtype_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetlocaleargtype_t type;
|
||||||
|
union {
|
||||||
|
const char_t *stringValue;
|
||||||
|
int32_t intValue;
|
||||||
|
float_t floatValue;
|
||||||
|
};
|
||||||
|
} assetlocalearg_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
assetfile_t file;
|
||||||
|
assetlocalepluraloperation_t pluralOps[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||||
|
int32_t pluralValues[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||||
|
int32_t pluralIndices[ASSET_LOCALE_FILE_PLURAL_FORM_COUNT];
|
||||||
|
uint8_t pluralStateCount;
|
||||||
|
uint8_t pluralDefaultIndex;
|
||||||
|
} assetlocalefile_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for locale assets.
|
* Initialize a locale asset file.
|
||||||
*
|
*
|
||||||
* @param file Asset file to load the locale from.
|
* @param localeFile The locale file to initialize.
|
||||||
* @return Any error that occurs during loading.
|
* @param path The path to the locale file.
|
||||||
|
* @return An error code if a failure occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t assetLocaleLoader(assetfile_t *file);
|
errorret_t assetLocaleFileInit(
|
||||||
|
assetlocalefile_t *localeFile,
|
||||||
|
const char_t *path
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a locale from the specified path.
|
* Dispose of a locale asset file.
|
||||||
*
|
*
|
||||||
* @param path Path to the locale asset.
|
* @param localeFile The locale file to dispose of.
|
||||||
* @param nothing Nothing yet.
|
* @return An error code if a failure occurs.
|
||||||
* @return Any error that occurs during loading.
|
|
||||||
*/
|
*/
|
||||||
errorret_t assetLocaleLoad(
|
errorret_t assetLocaleFileDispose(assetlocalefile_t *localeFile);
|
||||||
const char_t *path,
|
|
||||||
void *nothing
|
errorret_t assetLocaleParseHeader(
|
||||||
|
assetlocalefile_t *localeFile,
|
||||||
|
char_t *headerBuffer,
|
||||||
|
const size_t headerBufferSize
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,15 +105,57 @@ errorret_t assetLocaleLineUnbuffer(
|
|||||||
*
|
*
|
||||||
* @param file Asset file to test loading from.
|
* @param file Asset file to test loading from.
|
||||||
* @param messageId The message ID to retrieve.
|
* @param messageId The message ID to retrieve.
|
||||||
* @param pluralIndex The plural index to retrieve.
|
* @param pluralCount Count for formulating the plural variant.
|
||||||
* @param stringBuffer Buffer to write the retrieved string to.
|
* @param stringBuffer Buffer to write the retrieved string to.
|
||||||
* @param stringBufferSize Size of the string buffer.
|
* @param stringBufferSize Size of the string buffer.
|
||||||
* @return Any error that occurs during testing.
|
* @return Any error that occurs during testing.
|
||||||
*/
|
*/
|
||||||
errorret_t assetLocaleGetString(
|
errorret_t assetLocaleGetString(
|
||||||
assetfile_t *file,
|
assetlocalefile_t *file,
|
||||||
const char_t *messageId,
|
const char_t *messageId,
|
||||||
const int32_t pluralIndex,
|
const int32_t pluralCount,
|
||||||
char_t *stringBuffer,
|
char_t *stringBuffer,
|
||||||
const size_t stringBufferSize
|
const size_t stringBufferSize
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test function for locale asset loading with a variable argument list.
|
||||||
|
*
|
||||||
|
* @param file Asset file to test loading from.
|
||||||
|
* @param messageId The message ID to retrieve.
|
||||||
|
* @param pluralCount Count for formulating the plural variant.
|
||||||
|
* @param buffer Buffer to write the retrieved string to.
|
||||||
|
* @param bufferSize Size of the buffer.
|
||||||
|
* @param ... Additional arguments for formatting the string.
|
||||||
|
* @return Any error that occurs during testing.
|
||||||
|
*/
|
||||||
|
errorret_t assetLocaleGetStringWithVA(
|
||||||
|
assetlocalefile_t *file,
|
||||||
|
const char_t *messageId,
|
||||||
|
const int32_t pluralCount,
|
||||||
|
char_t *buffer,
|
||||||
|
const size_t bufferSize,
|
||||||
|
...
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test function for locale asset loading with a list of arguments.
|
||||||
|
*
|
||||||
|
* @param file Asset file to test loading from.
|
||||||
|
* @param messageId The message ID to retrieve.
|
||||||
|
* @param pluralCount Count for formulating the plural variant.
|
||||||
|
* @param buffer Buffer to write the retrieved string to.
|
||||||
|
* @param bufferSize Size of the buffer.
|
||||||
|
* @param args List of arguments for formatting the string.
|
||||||
|
* @param argCount Number of arguments in the list.
|
||||||
|
* @return Any error that occurs during testing.
|
||||||
|
*/
|
||||||
|
errorret_t assetLocaleGetStringWithArgs(
|
||||||
|
assetlocalefile_t *file,
|
||||||
|
const char_t *messageId,
|
||||||
|
const int32_t pluralCount,
|
||||||
|
char_t *buffer,
|
||||||
|
const size_t bufferSize,
|
||||||
|
const assetlocalearg_t *args,
|
||||||
|
const size_t argCount
|
||||||
|
);
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "localemanager.h"
|
#include "localemanager.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "asset/loader/locale/assetlocaleloader.h"
|
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
|
||||||
localemanager_t LOCALE;
|
localemanager_t LOCALE;
|
||||||
@@ -22,254 +21,20 @@ errorret_t localeManagerInit() {
|
|||||||
|
|
||||||
errorret_t localeManagerSetLocale(const localeinfo_t *locale) {
|
errorret_t localeManagerSetLocale(const localeinfo_t *locale) {
|
||||||
if(LOCALE.fileOpen) {
|
if(LOCALE.fileOpen) {
|
||||||
errorChain(assetFileClose(&LOCALE.file));
|
errorChain(assetLocaleFileDispose(&LOCALE.file));
|
||||||
errorCatch(errorPrint(assetFileDispose(&LOCALE.file)));
|
|
||||||
LOCALE.fileOpen = false;
|
LOCALE.fileOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init the asset file
|
// Init the asset file
|
||||||
errorChain(assetFileInit(&LOCALE.file, locale->file, NULL, NULL));
|
errorChain(assetLocaleFileInit(&LOCALE.file, locale->file));
|
||||||
LOCALE.fileOpen = true;
|
LOCALE.fileOpen = true;
|
||||||
|
|
||||||
// Open the file handle.
|
|
||||||
errorChain(assetFileOpen(&LOCALE.file));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t localeManagerGetText(
|
|
||||||
const char_t *id,
|
|
||||||
char_t *buffer,
|
|
||||||
const size_t bufferSize,
|
|
||||||
const int32_t plural,
|
|
||||||
...
|
|
||||||
) {
|
|
||||||
assertNotNull(id, "Message ID cannot be NULL.");
|
|
||||||
assertNotNull(buffer, "Buffer cannot be NULL.");
|
|
||||||
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
|
||||||
assertTrue(plural >= 0, "Plural cannot be negative.");
|
|
||||||
|
|
||||||
char_t *tempBuffer;
|
|
||||||
tempBuffer = memoryAllocate(bufferSize);
|
|
||||||
errorret_t ret = assetLocaleGetString(
|
|
||||||
&LOCALE.file,
|
|
||||||
id,
|
|
||||||
plural,
|
|
||||||
tempBuffer,
|
|
||||||
bufferSize
|
|
||||||
);
|
|
||||||
if(ret.code != ERROR_OK) {
|
|
||||||
memoryFree(tempBuffer);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
va_list args;
|
|
||||||
va_start(args, plural);
|
|
||||||
int result = vsnprintf(buffer, bufferSize, tempBuffer, args);
|
|
||||||
va_end(args);
|
|
||||||
memoryFree(tempBuffer);
|
|
||||||
|
|
||||||
if(result < 0) {
|
|
||||||
errorThrow("Failed to format locale string for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
errorret_t localeManagerGetTextArgs(
|
|
||||||
const char_t *id,
|
|
||||||
char_t *buffer,
|
|
||||||
const size_t bufferSize,
|
|
||||||
const int32_t plural,
|
|
||||||
const localearg_t *args,
|
|
||||||
const size_t argCount
|
|
||||||
) {
|
|
||||||
assertNotNull(id, "Message ID cannot be NULL.");
|
|
||||||
assertNotNull(buffer, "Buffer cannot be NULL.");
|
|
||||||
assertTrue(bufferSize > 0, "Buffer size must be > 0.");
|
|
||||||
assertTrue(plural >= 0, "Plural cannot be negative.");
|
|
||||||
assertTrue(argCount == 0 || args != NULL, "Args cannot be NULL when argCount > 0.");
|
|
||||||
|
|
||||||
char_t *format = memoryAllocate(bufferSize);
|
|
||||||
if(format == NULL) {
|
|
||||||
errorThrow("Failed to allocate format buffer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t ret = assetLocaleGetString(
|
|
||||||
&LOCALE.file,
|
|
||||||
id,
|
|
||||||
plural,
|
|
||||||
format,
|
|
||||||
bufferSize
|
|
||||||
);
|
|
||||||
if(ret.code != ERROR_OK) {
|
|
||||||
memoryFree(format);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t inIndex = 0;
|
|
||||||
size_t outIndex = 0;
|
|
||||||
size_t nextArg = 0;
|
|
||||||
|
|
||||||
buffer[0] = '\0';
|
|
||||||
|
|
||||||
while(format[inIndex] != '\0') {
|
|
||||||
if(format[inIndex] != '%') {
|
|
||||||
if(outIndex + 1 >= bufferSize) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[outIndex++] = format[inIndex++];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
inIndex++;
|
|
||||||
|
|
||||||
/* Escaped percent */
|
|
||||||
if(format[inIndex] == '%') {
|
|
||||||
if(outIndex + 1 >= bufferSize) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[outIndex++] = '%';
|
|
||||||
inIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(nextArg >= argCount) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Not enough locale arguments for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char_t specBuffer[32];
|
|
||||||
char_t valueBuffer[256];
|
|
||||||
size_t specLength = 0;
|
|
||||||
int written = 0;
|
|
||||||
char_t specifier;
|
|
||||||
|
|
||||||
specBuffer[specLength++] = '%';
|
|
||||||
|
|
||||||
/* Allow flags / width / precision */
|
|
||||||
while(format[inIndex] != '\0') {
|
|
||||||
char_t ch = format[inIndex];
|
|
||||||
|
|
||||||
if(
|
|
||||||
ch == '-' || ch == '+' || ch == ' ' || ch == '#' || ch == '0' ||
|
|
||||||
ch == '.' || (ch >= '0' && ch <= '9')
|
|
||||||
) {
|
|
||||||
if(specLength + 1 >= sizeof(specBuffer)) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Format specifier too long for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
specBuffer[specLength++] = ch;
|
|
||||||
inIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(format[inIndex] == '\0') {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Incomplete format specifier for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
specifier = format[inIndex++];
|
|
||||||
|
|
||||||
if(specifier != 's' && specifier != 'd' && specifier != 'f') {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow(
|
|
||||||
"Unsupported format specifier '%%%c' for ID: %s",
|
|
||||||
specifier,
|
|
||||||
id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
specBuffer[specLength++] = specifier;
|
|
||||||
specBuffer[specLength] = '\0';
|
|
||||||
|
|
||||||
switch(specifier) {
|
|
||||||
case 's':
|
|
||||||
if(args[nextArg].type != LOCALE_ARG_STRING) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Expected string locale argument for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
written = snprintf(
|
|
||||||
valueBuffer,
|
|
||||||
sizeof(valueBuffer),
|
|
||||||
specBuffer,
|
|
||||||
args[nextArg].stringValue ? args[nextArg].stringValue : ""
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'd':
|
|
||||||
if(args[nextArg].type != LOCALE_ARG_INT) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Expected int locale argument for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
written = snprintf(
|
|
||||||
valueBuffer,
|
|
||||||
sizeof(valueBuffer),
|
|
||||||
specBuffer,
|
|
||||||
args[nextArg].intValue
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'f':
|
|
||||||
if(
|
|
||||||
args[nextArg].type != LOCALE_ARG_FLOAT &&
|
|
||||||
args[nextArg].type != LOCALE_ARG_INT
|
|
||||||
) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Expected float or int locale argument for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
float_t floatValue = (
|
|
||||||
args[nextArg].type == LOCALE_ARG_FLOAT ?
|
|
||||||
args[nextArg].floatValue :
|
|
||||||
(float_t)args[nextArg].intValue
|
|
||||||
);
|
|
||||||
|
|
||||||
written = snprintf(
|
|
||||||
valueBuffer,
|
|
||||||
sizeof(valueBuffer),
|
|
||||||
specBuffer,
|
|
||||||
floatValue
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextArg++;
|
|
||||||
|
|
||||||
if(written < 0) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Failed to format locale string for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(outIndex + (size_t)written >= bufferSize) {
|
|
||||||
memoryFree(format);
|
|
||||||
errorThrow("Formatted locale string buffer overflow for ID: %s", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryCopy(buffer + outIndex, valueBuffer, (size_t)written);
|
|
||||||
outIndex += (size_t)written;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[outIndex] = '\0';
|
|
||||||
memoryFree(format);
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void localeManagerDispose() {
|
void localeManagerDispose() {
|
||||||
if(LOCALE.fileOpen) {
|
if(LOCALE.fileOpen) {
|
||||||
errorCatch(errorPrint(assetFileClose(&LOCALE.file)));
|
errorCatch(errorPrint(assetLocaleFileDispose(&LOCALE.file)));
|
||||||
errorCatch(errorPrint(assetFileDispose(&LOCALE.file)));
|
|
||||||
LOCALE.fileOpen = false;
|
LOCALE.fileOpen = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,29 +9,14 @@
|
|||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "localemanager.h"
|
#include "localemanager.h"
|
||||||
#include "locale/localeinfo.h"
|
#include "locale/localeinfo.h"
|
||||||
#include "asset/assetfile.h"
|
#include "asset/loader/locale/assetlocaleloader.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const localeinfo_t *locale;
|
const localeinfo_t *locale;
|
||||||
assetfile_t file;
|
assetlocalefile_t file;
|
||||||
bool_t fileOpen;
|
bool_t fileOpen;
|
||||||
} localemanager_t;
|
} localemanager_t;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
LOCALE_ARG_STRING,
|
|
||||||
LOCALE_ARG_INT,
|
|
||||||
LOCALE_ARG_FLOAT
|
|
||||||
} localeargtype_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
localeargtype_t type;
|
|
||||||
union {
|
|
||||||
const char_t *stringValue;
|
|
||||||
int32_t intValue;
|
|
||||||
float_t floatValue;
|
|
||||||
};
|
|
||||||
} localearg_t;
|
|
||||||
|
|
||||||
extern localemanager_t LOCALE;
|
extern localemanager_t LOCALE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,13 +44,15 @@ errorret_t localeManagerSetLocale(const localeinfo_t *locale);
|
|||||||
* @param ... Additional arguments for formatting the string.
|
* @param ... Additional arguments for formatting the string.
|
||||||
* @return An error code if a failure occurs.
|
* @return An error code if a failure occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t localeManagerGetText(
|
#define localeManagerGetText(id, buffer, bufferSize, plural, ...) \
|
||||||
const char_t *id,
|
assetLocaleGetStringWithVA( \
|
||||||
char_t *buffer,
|
&LOCALE.file, \
|
||||||
const size_t bufferSize,
|
id, \
|
||||||
const int32_t plural,
|
plural, \
|
||||||
...
|
buffer, \
|
||||||
);
|
bufferSize, \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a localized string for the given message ID with a list of arguments.
|
* Get a localized string for the given message ID with a list of arguments.
|
||||||
@@ -78,14 +65,18 @@ errorret_t localeManagerGetText(
|
|||||||
* @param argCount Number of arguments in the list.
|
* @param argCount Number of arguments in the list.
|
||||||
* @return An error code if a failure occurs.
|
* @return An error code if a failure occurs.
|
||||||
*/
|
*/
|
||||||
errorret_t localeManagerGetTextArgs(
|
#define localeManagerGetTextArgs( \
|
||||||
const char_t *id,
|
id, buffer, bufferSize, plural, args, argCount \
|
||||||
char_t *buffer,
|
) \
|
||||||
const size_t bufferSize,
|
assetLocaleGetStringWithArgs( \
|
||||||
const int32_t plural,
|
&LOCALE.file, \
|
||||||
const localearg_t *args,
|
id, \
|
||||||
const size_t argCount
|
plural, \
|
||||||
);
|
buffer, \
|
||||||
|
bufferSize, \
|
||||||
|
args, \
|
||||||
|
argCount \
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispose of the locale system.
|
* Dispose of the locale system.
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ int moduleLocaleGetText(lua_State *L) {
|
|||||||
// Build structured arg list from remaining Lua args
|
// Build structured arg list from remaining Lua args
|
||||||
size_t argCount = (top >= argStart) ? (size_t)(top - argStart + 1) : 0;
|
size_t argCount = (top >= argStart) ? (size_t)(top - argStart + 1) : 0;
|
||||||
#define MODULE_LOCALE_MAX_ARGS 16
|
#define MODULE_LOCALE_MAX_ARGS 16
|
||||||
localearg_t argsStack[MODULE_LOCALE_MAX_ARGS];
|
assetlocalearg_t argsStack[MODULE_LOCALE_MAX_ARGS];
|
||||||
localearg_t *args = argsStack;
|
assetlocalearg_t *args = argsStack;
|
||||||
|
|
||||||
if(argCount > MODULE_LOCALE_MAX_ARGS) {
|
if(argCount > MODULE_LOCALE_MAX_ARGS) {
|
||||||
luaL_error(L, "Too many args (max %d)", MODULE_LOCALE_MAX_ARGS);
|
luaL_error(L, "Too many args (max %d)", MODULE_LOCALE_MAX_ARGS);
|
||||||
@@ -64,13 +64,13 @@ int moduleLocaleGetText(lua_State *L) {
|
|||||||
int luaIndex = argStart + (int)i;
|
int luaIndex = argStart + (int)i;
|
||||||
|
|
||||||
if(lua_isinteger(L, luaIndex)) {
|
if(lua_isinteger(L, luaIndex)) {
|
||||||
args[i].type = LOCALE_ARG_INT;
|
args[i].type = ASSET_LOCALE_ARG_INT;
|
||||||
args[i].intValue = (int32_t)lua_tonumber(L, luaIndex);
|
args[i].intValue = (int32_t)lua_tonumber(L, luaIndex);
|
||||||
} else if(lua_isnumber(L, luaIndex)) {
|
} else if(lua_isnumber(L, luaIndex)) {
|
||||||
args[i].type = LOCALE_ARG_FLOAT;
|
args[i].type = ASSET_LOCALE_ARG_FLOAT;
|
||||||
args[i].floatValue = lua_tonumber(L, luaIndex);
|
args[i].floatValue = lua_tonumber(L, luaIndex);
|
||||||
} else if(lua_isstring(L, luaIndex)) {
|
} else if(lua_isstring(L, luaIndex)) {
|
||||||
args[i].type = LOCALE_ARG_STRING;
|
args[i].type = ASSET_LOCALE_ARG_STRING;
|
||||||
args[i].stringValue = lua_tostring(L, luaIndex);
|
args[i].stringValue = lua_tostring(L, luaIndex);
|
||||||
} else {
|
} else {
|
||||||
luaL_error(L, "Unsupported localization argument type");
|
luaL_error(L, "Unsupported localization argument type");
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ int_t memoryCompare(
|
|||||||
) {
|
) {
|
||||||
assertNotNull(a, "Cannot compare NULL memory.");
|
assertNotNull(a, "Cannot compare NULL memory.");
|
||||||
assertNotNull(b, "Cannot compare NULL memory.");
|
assertNotNull(b, "Cannot compare NULL memory.");
|
||||||
assertTrue(size > 0, "Cannot compare 0 bytes of memory.");
|
assertTrue(size >= 0, "Cannot compare negative bytes of memory.");
|
||||||
return memcmp(a, b, size);
|
return memcmp(a, b, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user