From 98d70b96d106855ae1161a3eaa375fa3848316c9 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 4 Apr 2026 15:21:27 -0500 Subject: [PATCH] Added proper plural support --- assets/scene/main_menu/main_menu.lua | 2 +- .../asset/loader/locale/assetlocaleloader.c | 544 +++++++++++++++++- .../asset/loader/locale/assetlocaleloader.h | 112 +++- src/dusk/locale/localemanager.c | 241 +------- src/dusk/locale/localemanager.h | 55 +- src/dusk/script/module/locale/modulelocale.c | 10 +- src/dusk/util/memory.c | 2 +- 7 files changed, 663 insertions(+), 303 deletions(-) diff --git a/assets/scene/main_menu/main_menu.lua b/assets/scene/main_menu/main_menu.lua index b099254..0b14773 100644 --- a/assets/scene/main_menu/main_menu.lua +++ b/assets/scene/main_menu/main_menu.lua @@ -14,7 +14,7 @@ module('locale') screenSetBackground(colorCornflowerBlue()) camera = cameraCreate(CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) -text = localeGetText('cart.item_count', 2, 2) +text = localeGetText('cart.item_count', 52, 2) function sceneDispose() end diff --git a/src/dusk/asset/loader/locale/assetlocaleloader.c b/src/dusk/asset/loader/locale/assetlocaleloader.c index 1111484..eb0b6b1 100644 --- a/src/dusk/asset/loader/locale/assetlocaleloader.c +++ b/src/dusk/asset/loader/locale/assetlocaleloader.c @@ -11,15 +11,293 @@ #include "util/string.h" #include "assert/assert.h" -errorret_t assetLocaleLoader(assetfile_t *file) { - errorThrow("Locale asset loading is not yet implemented."); +errorret_t assetLocaleFileInit( + 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( - const char_t *path, - void *nothing +errorret_t assetLocaleFileDispose(assetlocalefile_t *localeFile) { + assertNotNull(localeFile, "Locale file cannot be NULL."); + + 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( @@ -140,15 +418,15 @@ errorret_t assetLocaleLineUnbuffer( } errorret_t assetLocaleGetString( - assetfile_t *file, + assetlocalefile_t *file, const char_t *messageId, - const int32_t pluralIndex, + const int32_t pluralCount, char_t *stringBuffer, const size_t stringBufferSize ) { assertNotNull(file, "Asset file 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."); assertTrue(stringBufferSize > 0, "String buffer size must be > 0"); assetfilelinereader_t reader; @@ -158,16 +436,17 @@ errorret_t assetLocaleGetString( uint8_t msgidPluralBuffer[256]; uint8_t readBuffer[1024]; uint8_t lineBuffer[1024]; + uint8_t pluralIndex = 0xFF; msgidBuffer[0] = '\0'; msgidPluralBuffer[0] = '\0'; stringBuffer[0] = '\0'; // Rewind and start reading lines. - errorChain(assetFileRewind(&LOCALE.file)); + errorChain(assetFileRewind(&file->file)); assetFileLineReaderInit( &reader, - &LOCALE.file, + &file->file, readBuffer, sizeof(readBuffer), lineBuffer, @@ -219,6 +498,13 @@ errorret_t assetLocaleGetString( sizeof(msgidPluralBuffer) ); msgidPluralFound = true; + + // At this point we determine the plural index to use by running the + // plural formula + pluralIndex = assetLocaleEvaluatePlural( + file, + pluralCount + ); continue; } @@ -281,5 +567,241 @@ errorret_t assetLocaleGetString( errorThrow("Failed to find msgstr for message ID: %s", messageId); } + 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(); } \ No newline at end of file diff --git a/src/dusk/asset/loader/locale/assetlocaleloader.h b/src/dusk/asset/loader/locale/assetlocaleloader.h index 9c7f7cc..26f27d0 100644 --- a/src/dusk/asset/loader/locale/assetlocaleloader.h +++ b/src/dusk/asset/loader/locale/assetlocaleloader.h @@ -7,26 +7,66 @@ #pragma once #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. - * @return Any error that occurs during loading. + * @param localeFile The locale file to initialize. + * @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 nothing Nothing yet. - * @return Any error that occurs during loading. + * @param localeFile The locale file to dispose of. + * @return An error code if a failure occurs. */ -errorret_t assetLocaleLoad( - const char_t *path, - void *nothing +errorret_t assetLocaleFileDispose(assetlocalefile_t *localeFile); + +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 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 stringBufferSize Size of the string buffer. * @return Any error that occurs during testing. */ errorret_t assetLocaleGetString( - assetfile_t *file, + assetlocalefile_t *file, const char_t *messageId, - const int32_t pluralIndex, + const int32_t pluralCount, char_t *stringBuffer, 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 ); \ No newline at end of file diff --git a/src/dusk/locale/localemanager.c b/src/dusk/locale/localemanager.c index 102f655..b44ff44 100644 --- a/src/dusk/locale/localemanager.c +++ b/src/dusk/locale/localemanager.c @@ -7,7 +7,6 @@ #include "localemanager.h" #include "util/memory.h" -#include "asset/loader/locale/assetlocaleloader.h" #include "assert/assert.h" localemanager_t LOCALE; @@ -22,254 +21,20 @@ errorret_t localeManagerInit() { errorret_t localeManagerSetLocale(const localeinfo_t *locale) { if(LOCALE.fileOpen) { - errorChain(assetFileClose(&LOCALE.file)); - errorCatch(errorPrint(assetFileDispose(&LOCALE.file))); + errorChain(assetLocaleFileDispose(&LOCALE.file)); LOCALE.fileOpen = false; } // Init the asset file - errorChain(assetFileInit(&LOCALE.file, locale->file, NULL, NULL)); + errorChain(assetLocaleFileInit(&LOCALE.file, locale->file)); 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(); } void localeManagerDispose() { if(LOCALE.fileOpen) { - errorCatch(errorPrint(assetFileClose(&LOCALE.file))); - errorCatch(errorPrint(assetFileDispose(&LOCALE.file))); + errorCatch(errorPrint(assetLocaleFileDispose(&LOCALE.file))); LOCALE.fileOpen = false; } } \ No newline at end of file diff --git a/src/dusk/locale/localemanager.h b/src/dusk/locale/localemanager.h index 3d3dfba..4a6bacd 100644 --- a/src/dusk/locale/localemanager.h +++ b/src/dusk/locale/localemanager.h @@ -9,29 +9,14 @@ #include "error/error.h" #include "localemanager.h" #include "locale/localeinfo.h" -#include "asset/assetfile.h" +#include "asset/loader/locale/assetlocaleloader.h" typedef struct { const localeinfo_t *locale; - assetfile_t file; + assetlocalefile_t file; bool_t fileOpen; } 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; /** @@ -59,13 +44,15 @@ errorret_t localeManagerSetLocale(const localeinfo_t *locale); * @param ... Additional arguments for formatting the string. * @return An error code if a failure occurs. */ -errorret_t localeManagerGetText( - const char_t *id, - char_t *buffer, - const size_t bufferSize, - const int32_t plural, - ... -); +#define localeManagerGetText(id, buffer, bufferSize, plural, ...) \ + assetLocaleGetStringWithVA( \ + &LOCALE.file, \ + id, \ + plural, \ + buffer, \ + bufferSize, \ + __VA_ARGS__ \ + ) /** * 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. * @return An error code if a failure occurs. */ -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 -); +#define localeManagerGetTextArgs( \ + id, buffer, bufferSize, plural, args, argCount \ +) \ + assetLocaleGetStringWithArgs( \ + &LOCALE.file, \ + id, \ + plural, \ + buffer, \ + bufferSize, \ + args, \ + argCount \ + ) /** * Dispose of the locale system. diff --git a/src/dusk/script/module/locale/modulelocale.c b/src/dusk/script/module/locale/modulelocale.c index 3ebb59a..ef0312f 100644 --- a/src/dusk/script/module/locale/modulelocale.c +++ b/src/dusk/script/module/locale/modulelocale.c @@ -52,8 +52,8 @@ int moduleLocaleGetText(lua_State *L) { // Build structured arg list from remaining Lua args size_t argCount = (top >= argStart) ? (size_t)(top - argStart + 1) : 0; #define MODULE_LOCALE_MAX_ARGS 16 - localearg_t argsStack[MODULE_LOCALE_MAX_ARGS]; - localearg_t *args = argsStack; + assetlocalearg_t argsStack[MODULE_LOCALE_MAX_ARGS]; + assetlocalearg_t *args = argsStack; if(argCount > 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; 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); } 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); } 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); } else { luaL_error(L, "Unsupported localization argument type"); diff --git a/src/dusk/util/memory.c b/src/dusk/util/memory.c index 838843c..ff69eb7 100644 --- a/src/dusk/util/memory.c +++ b/src/dusk/util/memory.c @@ -82,7 +82,7 @@ int_t memoryCompare( ) { assertNotNull(a, "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); }