diff --git a/data/languages/en.json b/data/languages/en.json index 6287dfb..e5825ea 100644 --- a/data/languages/en.json +++ b/data/languages/en.json @@ -4,5 +4,10 @@ "name": "English", "code": "en" } + }, + "test": { + "npc": { + "text": "Hello, {{ name }}." + } } } \ No newline at end of file diff --git a/src/dusk/entity/npc.c b/src/dusk/entity/npc.c index 217a24f..ae25d3b 100644 --- a/src/dusk/entity/npc.c +++ b/src/dusk/entity/npc.c @@ -8,6 +8,7 @@ #include "npc.h" #include "ui/uitextbox.h" #include "locale/language.h" +#include "assert/assert.h" void npcInit(entity_t *entity) { @@ -24,5 +25,29 @@ void npcInteract(entity_t *player, entity_t *self) { // "of Darth Plagueis the Wise? He was a dark lord of the Sith, " // "so powerful and so wise he could use the Force to influence the midichlorians" // ); - uiTextboxSetText(languageGet("meta.language.name")); + const char_t *name = "Dom"; + const char_t *key = "name"; + uint16_t len = languageFormat( + "test.npc.text", + NULL, + 0, + (const char_t *[]){ key }, + (const char_t *[]){ name }, + 1 + ); + + char_t *buffer = malloc(sizeof(char_t) + len + 1); + assertNotNull(buffer, "Failed to allocate buffer for NPC text"); + + languageFormat( + "test.npc.text", + buffer, + len + 1, + (const char_t *[]){ key }, + (const char_t *[]){ name }, + 1 + ); + + uiTextboxSetText(buffer); + free(buffer); } \ No newline at end of file diff --git a/src/dusk/locale/language.c b/src/dusk/locale/language.c index 0fc6899..2c70fd1 100644 --- a/src/dusk/locale/language.c +++ b/src/dusk/locale/language.c @@ -19,10 +19,8 @@ const char_t * languageGet(const char_t *key) { assertTrue(LANGUAGE.current < LANGUAGE_COUNT, "Invalid language index"); int16_t keyIndex = -1; - for(uint16_t i = 0; i < LANGUAGE_COUNT; i++) { - if(strcmp(LANGUAGE_KEYS[LANGUAGE.current][i], key) != 0) { - continue; - } + for(uint16_t i = 0; i < LANGUAGE_COUNTS[LANGUAGE.current]; i++) { + if(strcmp(LANGUAGE_KEYS[LANGUAGE.current][i], key) != 0) continue; keyIndex = i; break; } @@ -35,13 +33,113 @@ uint16_t langaugeGetLength(const char_t *key) { assertTrue(LANGUAGE.current < LANGUAGE_COUNT, "Invalid language index"); int16_t keyIndex = -1; - for(uint16_t i = 0; i < LANGUAGE_COUNT; i++) { - if(strcmp(LANGUAGE_KEYS[LANGUAGE.current][i], key) != 0) { - continue; - } + for(uint16_t i = 0; i < LANGUAGE_COUNTS[LANGUAGE.current]; i++) { + if(strcmp(LANGUAGE_KEYS[LANGUAGE.current][i], key) != 0) continue; keyIndex = i; break; } assertTrue(keyIndex != -1, "Key not found in language"); return strlen(LANGUAGE_VALUES[LANGUAGE.current][keyIndex]); +} + +uint16_t languageFormat( + const char_t *key, + char_t *buffer, + uint16_t buffSize, + const char_t **keys, + const char_t **values, + const uint16_t valueCount +) { + if(buffer != NULL) { + assertTrue(buffSize > 0, "Buffer size must be greater than 0"); + } else { + assertTrue(buffSize == 0, "Buffer size must be 0 if buffer is NULL"); + } + + assertNotNull(key, "Key cannot be NULL"); + assertNotNull(keys, "Keys cannot be NULL"); + assertNotNull(values, "Values cannot be NULL"); + + const char_t *val = languageGet(key); + assertNotNull(val, "Value for key cannot be NULL"); + + char_t c; + uint16_t i = 0; + uint16_t j = 0; + uint8_t k = 0; + bool_t inBraces = false; + char_t braceBuffer[64] = {0}; + + #define bufferChar(c) ( \ + (buffer ? (buffer[j++] = c) : (j++)), \ + assertTrue(buffer ? j < buffSize : true, "Buffer overflow") \ + ) + + while((c = val[i++]) != '\0') { + if(c == '{' && val[i] == '{') { + goto startBraces; + } else if(c == '}' && val[i] == '}') { + goto endBraces; + } else if(inBraces) { + goto braceBuffering; + } else { + goto character; + } + + character: { + bufferChar(c); + continue; + } + + braceBuffering: { + assertFalse(val[i] == '\0', "Unexpected end of string."); + braceBuffer[k++] = c; + assertTrue(k < sizeof(braceBuffer), "Brace buffer overflow"); + if(val[i] == ' ') i++; + continue; + } + + startBraces: { + assertFalse(inBraces, "Nested braces are not allowed"); + + inBraces = true; + i++; + k = 0; + assertFalse(val[i] == '\0', "Unexpected end of string."); + if(val[i] == ' ') i++; + continue; + } + + endBraces: { + assertTrue(inBraces, "Unmatched closing brace found"); + + inBraces = false; + i++; + braceBuffer[k] = '\0'; + + uint16_t l; + for(l = 0; l < valueCount; l++) { + if(strcmp(braceBuffer, keys[l]) != 0) { + continue; + } + const char_t *replacement = values[l]; + + uint16_t r = 0; + while((c = replacement[r++]) != '\0') { + bufferChar(c); + } + break; + } + assertTrue(l < valueCount, "No string replacement found!"); + continue; + } + } + + if(buffer){ + assertTrue(j < buffSize, "Buffer overflow"); + buffer[j] = '\0'; + } + + assertFalse(inBraces, "Unmatched opening brace found"); + return j; } \ No newline at end of file diff --git a/src/dusk/locale/language.h b/src/dusk/locale/language.h index ce521ed..d80d925 100644 --- a/src/dusk/locale/language.h +++ b/src/dusk/locale/language.h @@ -36,4 +36,30 @@ const char_t * languageGet(const char_t *key); * @param key The key for the language string. * @return The length of the language string associated with the key. */ -uint16_t langaugeGetLength(const char_t *key); \ No newline at end of file +uint16_t langaugeGetLength(const char_t *key); + +/** + * Formats a language string with given keys and values. + * + * This function replaces placeholders in the language string with the provided + * values based on the keys. + * + * If buffer is NULL, the function will instead calculate the length of the + * formatted string. + * + * @param key The key for the language string to format. + * @param buffer The buffer to store the formatted string. + * @param buffSize The size of the buffer. + * @param keys An array of keys to replace in the language string. + * @param values An array of values corresponding to the keys. + * @param valueCount The number of key-value pairs. + * @return The number of characters written to the buffer. + */ +uint16_t languageFormat( + const char_t *key, + char_t *buffer, + uint16_t buffSize, + const char_t **keys, + const char_t **values, + const uint16_t valueCount +); \ No newline at end of file